java核心技术 要点笔记2

第4章   对象和类

1.面向对象

类:构造对象的模板,创建类的实例:由类构造对象的过程,封装,继承;

对象:对象的特性——对象的行为,对象的状态,对象的标识;

类之间的关系:

  依赖(“user-a”),一个类的方法操纵另一个类的对象

  聚合(“has-a”),类A的对象包含类B的对象

  继承(“is-a”),类A 拓展了类B

 

2.使用预定义类

并不是所有的类都具有面向对象的特征,例如Math类;

对象不会自动被初始化为null,而必须通过调用new或将它们设置为null进行初始化(可以将java对象看成是c++的对象指针);

 

 

3.用户自定义类

主力类:类中没有main方法,有自定义的实例域和实例方法;

在一个源文件中,只能有一个共有类,但可以有任意数目的非公有类;

多个源文件的使用: 编若两个类单独存在于两个源文件,则编译   javac Employee*.java  或者  javac EmployeeTest.java (会自动搜索Employee.java)

构造器:    构造器与类同名,  每个类可以有一个以上的构造器,   构造器可以有0个,1个或多参数,  构造器没有返回值,  伴随new操作一起使用;

隐式参数和显式参数: 关键字this表示隐式参数;import java.util.*;

 1 public class StaticTest
 2 {
 3    public static void main(String[] args)
 4    {
 5       // fill the staff array with three Employee objects
 6       Employee[] staff = new Employee[3];
 7 
 8       staff[0] = new Employee("Tom", 40000);
 9       staff[1] = new Employee("Dick", 60000);
10       staff[2] = new Employee("Harry", 65000);
11 
12       // print out information about all Employee objects
13       for (Employee e : staff)
14       {
15          e.setId();
16          System.out.println("name=" + e.getName() + ",id=" + e.getId() + ",salary="
17                + e.getSalary());
18       }
19 
20       int n = Employee.getNextId(); // calls static method
21       System.out.println("Next available id=" + n);
22    }
23 }
24 
25 class Employee
26 {
27    private String name;
28    private double salary;
29    private int id;
30    private static int nextId = 1;
31    public Employee(String n, double s)
32    {
33       name = n;
34       salary = s;
35       id = 0;
36    }
37 
38    public String getName()
39    {
40       return name;
41    }
42 
43    public double getSalary()
44    {
45       return salary;
46    }
47 
48    public int getId()
49    {
50       return id;
51    }
52 
53    public void setId()
54    {
55       id = nextId; // set id to next available id
56       nextId++;
57    }
58 
59    public static int getNextId()
60    {
61       return nextId; // returns static field
62    }
63 
64    public static void main(String[] args) // unit test
65    {
66       Employee e = new Employee("Harry", 50000);
67       System.out.println(e.getName() + " " + e.getSalary());
68    }
69 
70 
71 }

 

 

4.静态域和静态方法

静态域:若将域定义为static,每个类中只有一个这样的域。而每个对象对于所有的实例域都有一份自己的拷贝;属于类,不属于任何独立的对象;

静态方法:静态方法是一种不能向对象实施操作的方法,因此不能访问实例域,但可以访问自身类中的静态域。

使用静态方法的情形:

     1、一个方法不需要访问对象状态,其所有参数都通过显示参数提供。如Math.pow(x,a)

     2、一个方法只需要访问静态域。

main方法:main方法也是一个静态方法;

每一个类都可以有一个main方法,进行单元测试; 此例中,java Employee 进行独立测试, java  StaticTest,则Employee类中的main方法不会被执行

 

 

5.方法参数

 java采用按值传输;

一个方法不能修改一个基本数据类型的参数(即数值型和布尔型);

一个方法可以改变一个对象参数的状态;

一个方法不能让对象参数引用一个新的对象;

 1 public class ParamTest
 2 {
 3    public static void main(String[] args)
 4    {
 5       /*
 6        * Test 1: Methods can't modify numeric parameters
 7        */
 8       System.out.println("Testing tripleValue:");
 9       double percent = 10;
10       System.out.println("Before: percent=" + percent);
11       tripleValue(percent);
12       System.out.println("After: percent=" + percent);
13 
14       /*
15        * Test 2: Methods can change the state of object parameters
16        */
17       System.out.println("\nTesting tripleSalary:");
18       Employee harry = new Employee("Harry", 50000);
19       System.out.println("Before: salary=" + harry.getSalary());
20       tripleSalary(harry);
21       System.out.println("After: salary=" + harry.getSalary());
22 
23       /*
24        * Test 3: Methods can't attach new objects to object parameters
25        */
26       System.out.println("\nTesting swap:");
27       Employee a = new Employee("Alice", 70000);
28       Employee b = new Employee("Bob", 60000);
29       System.out.println("Before: a=" + a.getName());
30       System.out.println("Before: b=" + b.getName());
31       swap(a, b);
32       System.out.println("After: a=" + a.getName());
33       System.out.println("After: b=" + b.getName());
34    }
35 
36    public static void tripleValue(double x) // doesn't work
37    {
38       x = 3 * x;
39       System.out.println("End of method: x=" + x);
40    }
41 
42    public static void tripleSalary(Employee x) // works
43    {
44       x.raiseSalary(200);
45       System.out.println("End of method: salary=" + x.getSalary());
46    }
47 
48    public static void swap(Employee x, Employee y)
49    {
50       Employee temp = x;
51       x = y;
52       y = temp;
53       System.out.println("End of method: x=" + x.getName());
54       System.out.println("End of method: y=" + y.getName());
55    }
56 }
57 
58 class Employee // simplified Employee class
59 {
60    private String name;
61    private double salary;
62 
63    public Employee(String n, double s)
64    {
65       name = n;
66       salary = s;
67    }
68 
69    public String getName()
70    {
71       return name;
72    }
73 
74    public double getSalary()
75    {
76       return salary;
77    }
78 
79    public void raiseSalary(double byPercent)
80    {
81       double raise = salary * byPercent / 100;
82       salary += raise;
83    }
84 
85 
86 }

 

 

 

Java中只有按值传递,没有按引用传递!

 

方法参数有两种类型:

(1)  基本数据类型(数字,布尔型)

 1 ... ...  
 2 //定义了一个改变参数值的函数  
 3 public static void changeValue(int x) {  
 4 x = x *2;  
 5 }  
 6 ... ...  
 7 //调用该函数  
 8 int num = 5;  
 9 System.out.println(num);  
10 changeValue(num);  
11 System.out.println(num);  
12 ... ...  
13  

值传递的过程:

 

  num作为参数传递给changeValue()方法时,是将内存空间中num所指向的那个存储单元中存放的值,即"5",传送给了changeValue()方法中的x变量,而这个x变量也在内存空间中分配了一个存储单元,这个时候,就把num的值5传送给了这个存储单元中。此后,在changeValue()方法中对x的一切操作都是针对x所指向的这个存储单元,与num所指向的那个存储单元没有关系了!自然,在函数调用之后,num所指向的存储单元的值还是没有发生变化,这就是所谓的“值传递”!值传递的精髓是:传递的是存储单元中的内容,而非地址或者引用!

 

(2)对象引用

 1 ... ...  
 2 class person {  
 3 public static String name = "Jack";  
 4 ... ...  
 5 }  
 6 ... ...  
 7 //定义一个改变对象属性的方法  
 8 public static void changeName(Person p) {  
 9 p.name = "Rose";  
10 }  
11 ... ...  
12 public static void main(String[] args) {  
13 //定义一个Person对象,person是这个对象的引用  
14 Person person = new Person();  
15 //先显示这个对象的name属性  
16 System.out.println(person.name);  
17 //调用changeName(Person p)方法  
18 changeName(person);  
19 //再显示这个对象的name属性,看是否发生了变化  
20 System.out.println(person.name);  
21 }  
22  

第一次显示:“Jack”

第二次显示:“Rose”

Java 编程语言只有值传递参数。当一个对象实例作为一个参数被传递到方法中时,参数的值就是该对象的引用一个副本。指向同一个对象,对象的内容可以在被调用的方法中改变,但对象的引用(不是引用的副本)是永远不会改变的。 

 

 主函数中new 了一个对象Person,实际分配了两个对象:新创建的Person类的实体对象,和指向该对象的引用变量person。

【注意:在java中,新创建的实体对象在堆内存中开辟空间,而引用变量在栈内存中开辟空间】

正如如上图所示,左侧是堆空间,用来分配内存给新创建的实体对象,红色框是新建的Person类的实体对象,000012是该实体对象的起始地址;而右侧是栈空间,用来给引用变量和一些临时变量分配内存,新实体对象的引用person就在其中,可以看到它的存储单元的内容是000012,记录的正是新建Person类实体对象的起始地址,也就是说它指向该实体对象。

调用了changeName()方法,person作为对象参数传入该方法,但是大家特别注意,它传入的是什么!!!person引用变量将自己的存储单元的内容传给了changeName()方法的p变量!也就是将实体对象的地址传给了p变量,从此,在changeName()方法中对p的一切操作都是针对p所指向的这个存储单元,与person引用变量所指向的那个存储单元再没有关系了!

p所指向的那个存储单元中的内容是实体对象的地址,使得p也指向了该实体对象,所以才能改变对象内部的属性!

 

 

 

 

 

 

 

 6.对象的构造

重载:方法签名包括(方法名,参数类型,但是不包括返回类型),java允许重载任何方法,而不只是构造器方法;

 

默认域参数:  如果在构造器中没有显示的给域赋予初值,会自动赋予默认值(数值为0,布尔值为false,对象引用为null);

 

无参数的构造器: 若没有编写构造器,系统提供默认的无参数构造器,将所有实例域设置为默认值;

 

显示域初始化:

 

参数名: 参数变量用同样的名字将实例域屏蔽起来,此时可以采用this访问实例域

1 public Employee(String name , double salary)
2 {
3     this.name = name;
4     this.salary =salary;
5 }

 

调用另一个构造器:如果构造器的第一个语句形如this(...),这个构造器将调用另一个类的另一个构造器;

1 public Employee  (double s)
2 {
3     //call  Employee(String,double)
4     this("Employee#" + naxtId, s);
5     nextId++;
6 }

 

初始化数据域的三种方法:

  1). 在构造器中设置之;

  2). 在声明中赋值;

  3). 初始化块(程序先运行初始化块,再运行构造器的主体部分)

 

调用构造器的步骤:

  1). 所以数据域被初始化为默认值(0,false,null);

  2). 按照在类声明中出现的顺序,依次执行所有域初始化语句和初始化块;

  3). 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体;

  4). 执行这个构造器的主体;

 1 import java.util.*;
 2 
 3 /**
 4  * 重载构造器
 5  * this(...)调用另一个构造器
 6  * 无参数构造器
 7  *对象初始化块
 8  *静态初始化块
 9  *实例域初始化块
10  */
11 public class ConstructorTest
12 {
13    public static void main(String[] args)
14    {
15       // fill the staff array with three Employee objects
16       Employee[] staff = new Employee[3];
17 
18       staff[0] = new Employee("Harry", 40000);
19       staff[1] = new Employee(60000);
20       staff[2] = new Employee();
21 
22       // print out information about all Employee objects
23       for (Employee e : staff)
24          System.out.println("name=" + e.getName() + ",id=" + e.getId() + ",salary="
25                + e.getSalary());
26    }
27 }
28 
29 class Employee
30 {
31 
32    private static int nextId;
33 
34    private int id;
35    private String name = ""; // instance field initialization
36    private double salary;
37 
38    // static initialization block
39    static
40    {
41       Random generator = new Random();
42       // set nextId to a random number between 0 and 9999
43       nextId = generator.nextInt(10000);
44    }
45 
46    // object initialization block
47    {
48       id = nextId;
49       nextId++;
50    }
51 
52    // three overloaded constructors
53    public Employee(String n, double s)
54    {
55       name = n;
56       salary = s;
57    }
58 
59    public Employee(double s)
60    {
61       // calls the Employee(String, double) constructor
62       this("Employee #" + nextId, s);
63    }
64 
65    // the default constructor
66    public Employee()
67    {
68       // name initialized to ""--see below
69       // salary not explicitly set--initialized to 0
70       // id initialized in initialization block
71    }
72 
73    public String getName()
74    {
75       return name;
76    }
77 
78    public double getSalary()
79    {
80       return salary;
81    }
82 
83    public int getId()
84    {
85       return id;
86    }
87 
88 }

 

 

7.  包

包的作用

  • 把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。
  • 如同文件夹一样,包也采用了树形目录的存储方式。同一个包中的类名字是不同的,不同的包中的类的名字是可以相同的,当同时调用两个不同包中相同类名的类时,应该加上包名加以区别。因此,包可以避免名字冲突。
  • 包也限定了访问权限,拥有包访问权限的类才能访问某个包中的类。

Java使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。

包语句的语法格式为:

package pkg1[.pkg2[.pkg3…]];

例如,一个Something.java 文件它的内容

package net.java.util
public class Something{
   ...
}

那么它的路径应该是 net/java/util/Something.java 这样保存的。 package(包)的作用是把不同的java程序分类保存,更方便的被其他java程序调用。

一个包(package)可以定义为一组相互联系的类型(类、接口、枚举和注释),为这些类型提供访问保护和命名空间管理的功能。

以下是一些Java中的包:

  • java.lang-打包基础的类
  • java.io-包含输入输出功能的函数

开发者可以自己把一组类和接口等打包,并定义自己的package。而且在实际开发中这样做是值得提倡的,当你自己完成类的实现之后,将相关的类分组,可以让其他的编程者更容易地确定哪些类、接口、枚举和注释等是相关的。

由于package创建了新的命名空间(namespace),所以不会跟其他package中的任何名字产生命名冲突。使用包这种机制,更容易实现访问控制,并且让定位相关类更加简单。


创建包

创建package的时候,你需要为这个package取一个合适的名字。之后,如果其他的一个源文件包含了这个包提供的类、接口、枚举或者注释类型的时候,都必须将这个package的声明放在这个源文件的开头。包声明应该在源文件的第一行,每个源文件只能有一个包声明,这个文件中的每个类型都应用于它。如果一个源文件中没有使用包声明,那么其中的类,函数,枚举,注释等将被放在一个无名的包(unnamed package)中。

通常使用小写的字母来命名避免与类、接口名字的冲突。

在animals包中加入一个接口(interface):

/* 文件名: Animal.java */
package animals;

interface Animal {
   public void eat();
   public void travel();
}

接下来,在同一个包中加入该接口的实现:

package animals;

/* 文件名 : MammalInt.java */
public class MammalInt implements Animal{

   public void eat(){
      System.out.println("Mammal eats");
   }

   public void travel(){
      System.out.println("Mammal travels");
   } 

   public int noOfLegs(){
      return 0;
   }

   public static void main(String args[]){
      MammalInt m = new MammalInt();
      m.eat();
      m.travel();
   }
}

然后,编译这两个文件,并把他们放在一个叫做animals的子目录中。 用下面的命令来运行:

$ mkdir animals
$ cp Animal.class  MammalInt.class animals
$ java animals/MammalInt
Mammal eats
Mammal travel

import关键字

为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用"import"语句可完成此功能。在 java 源文件中 import 语句应位于 package 语句之后,所有类的定义之前,可以没有,也可以有多条,其语法格式为:

import package1[.package2…].(classname|*);

如果在一个包中,一个类想要使用本包中的另一个类,那么该包名可以省略。

下面的payroll包已经包含了Employee类,接下来向payroll包中添加一个Boss类。Boss类引用Employee类的时候可以不用使用payroll前缀,Boss类的实例如下。

package payroll;

public class Boss
{
   public void payEmployee(Employee e)
   {
      e.mailCheck();
   }
}

如果Boss类不在payroll包中又会怎样?Boss类必须使用下面几种方法之一来引用其他包中的类

1).使用类全名描述,例如:

payroll.Employee

2).用import关键字引入,使用通配符"*"

import payroll.*;

3).使用import关键字引入Employee类

import payroll.Employee;

注意:

       类文件中可以包含任意数量的import声明。import声明必须在包声明之后,类声明之前。


package的目录结构

类放在包中会有两种主要的结果:

  • 包名成为类名的一部分,正如我们前面讨论的一样。
  • 包名必须与相应的字节码所在的目录结构相吻合。

下面是管理你自己java中文件的一种简单方式:

将类、接口等类型的源码放在一个文本中,这个文件的名字就是这个类型的名字,并以.java作为扩展名。例如:

// 文件名 :  Car.java

package vehicle;

public class Car {
   // 类实现  
}

接下来,把源文件放在一个目录中,这个目录要对应类所在包的名字。

....\vehicle\Car.java

现在,正确的类名和路径将会是如下样子:

  • 类名 -> vehicle.Car

  • 路径名 -> vehicle\Car.java (in windows)

通常,一个公司使用它互联网域名的颠倒形式来作为它的包名.例如:互联网域名是apple.com,所有的包名都以com.apple开头。包名中的每一个部分对应一个子目录。

例如:这个公司有一个com.apple.computers的包,这个包包含一个叫做Dell.java的源文件,那么相应的,应该有如下面的一连串子目录:

....\com\apple\computers\Dell.java

编译的时候,编译器为包中定义的每个类、接口等类型各创建一个不同的输出文件,输出文件的名字就是这个类型的名字,并加上.class作为扩展后缀。 例如:

// 文件名: Dell.java

package com.apple.computers;
public class Dell{
      
}
class Ups{
      
}

现在,我们用-d选项来编译这个文件,如下:

$javac -d . Dell.java

这样会像下面这样放置编译了的文件:

.\com\apple\computers\Dell.class.\com\apple\computers\Ups.class

你可以像下面这样来导入所有 \com\apple\computers\中定义的类、接口等:

import com.apple.computers.*;

编译之后的.class文件应该和.java源文件一样,它们放置的目录应该跟包的名字对应起来。但是,并不要求.class文件的路径跟相应的.java的路径一样。你可以分开来安排源码和类的目录。

<path-one>\sources\com\apple\computers\Dell.java
<path-two>\classes\com\apple\computers\Dell.class

这样,你可以将你的类目录分享给其他的编程人员,而不用透露自己的源码。用这种方法管理源码和类文件可以让编译器和java虚拟机(JVM)可以找到你程序中使用的所有类型。

类目录的绝对路径叫做class path。设置在系统变量CLASSPATH中。编译器和java虚拟机通过将package名字加到class path后来构造.class文件的路径。

<path- two>\classes是class path,package名字是com.apple.computers,而编译器和JVM会在 <path-two>\classes\com\apple\compters中找.class文件。

一个class path可能会包含好几个路径。多路径应该用分隔符分开。默认情况下,编译器和JVM查找当前目录。JAR文件按包含Java平台相关的类,所以他们的目录默认放在了class path中。


设置CLASSPATH系统变量

用下面的命令显示当前的CLASSPATH变量:

  • Windows平台(DOS 命令行下)-> C:\> set CLASSPATH
  • UNIX平台(Bourne shell下)-> % echo $CLASSPATH

删除当前CLASSPATH变量内容:

  • Windows平台(DOS 命令行下)-> C:\> set CLASSPATH=
  • UNIX平台(Bourne shell下)-> % unset CLASSPATH; export CLASSPATH

设置CLASSPATH变量:

    • Windows平台(DOS 命令行下)-> set CLASSPATH=C:\users\jack\java\classes
    • UNIX平台(Bourne shell下)-> % CLASSPATH=/home/jack/java/classes; export CLASSPATH

 

 

 

 

 

8. 文档注释

java只是三种注释方式。前两种分别是// 和/* */,第三种被称作说明注释,它以/** 开始,以 */结束;

说明注释允许你在程序中嵌入关于程序的信息。你可以使用javadoc工具软件来生成信息,并输出到HTML文件中;


javadoc 标签

javadoc工具软件识别以下标签:

标签描述示例
@author 标识一个类的作者 @author description
@deprecated 指名一个过期的类或成员 @deprecated description
{@docRoot} 指明当前文档根目录的路径 Directory Path
@exception 标志一个类抛出的异常 @exception exception-name explanation
{@inheritDoc} 从直接父类继承的注释 Inherits a comment from the immediate surperclass.
{@link} 插入一个到另一个主题的链接 {@link name text}
{@linkplain} 插入一个到另一个主题的链接,但是该链接显示纯文本字体 Inserts an in-line link to another topic.
@param 说明一个方法的参数 @param parameter-name explanation
@return 说明返回值类型 @return explanation
@see 指定一个到另一个主题的链接 @see anchor
@serial 说明一个序列化属性 @serial description
@serialData 说明通过writeObject( ) 和 writeExternal( )方法写的数据 @serialData description
@serialField 说明一个ObjectStreamField组件 @serialField name type description
@since 标记当引入一个特定的变化时 @since release
@throws 和 @exception标签一样. The @throws tag has the same meaning as the @exception tag.
{@value} 显示常量的值,该常量必须是static属性。 Displays the value of a constant, which must be a static field.
@version 指定类的版本 @version info

文档注释

在开始的/**之后,第一行或几行是关于类、变量和方法的主要描述。之后,你可以包含一个或多个何种各样的@标签。每一个@标签必须在一个新行的开始或者在一行的开始紧跟星号(*).多个相同类型的标签应该放成一组。例如,如果你有三个@see标签,可以将它们一个接一个的放在一起。

下面是一个类的说明注释的示例:

/*** This class draws a bar chart.
* @author Zara Ali
* @version 1.2
*/

javadoc输出什么

javadoc工具将你Java程序的源代码作为输入,输出一些包含你程序注释的HTML文件;

每一个类的信息将在独自的HTML文件里。javadoc也可以输出继承的树形结构和索引;

由于javadoc的实现不同,工作也可能不同,你需要检查你的Java开发系统的版本等细节,选择合适的Javadoc版本;

下面是一个使用说明注释的简单实例。注意每一个注释都在它描述的项目的前面。

在经过javadoc处理之后,SquareNum类的注释将在SquareNum.html中找到。

import java.io.*;
 
/**
* This class demonstrates documentation comments.
* @author Ayan Amhed
* @version 1.2
*/
public class SquareNum {
   /**
   * This method returns the square of num.
   * This is a multiline description. You can use
   * as many lines as you like.
   * @param num The value to be squared.
   * @return num squared.
   */
   public double square(double num) {
      return num * num;
   }
   /**
   * This method inputs a number from the user.
   * @return The value input as a double.
   * @exception IOException On input error.
   * @see IOException
   */
   public double getNumber() throws IOException {
      InputStreamReader isr = new InputStreamReader(System.in);
      BufferedReader inData = new BufferedReader(isr);
      String str;
      str = inData.readLine();
      return (new Double(str)).doubleValue();
   }
   /**
   * This method demonstrates square().
   * @param args Unused.
   * @return Nothing.
   * @exception IOException On input error.
   * @see IOException
   */
   public static void main(String args[]) throws IOException
   {
      SquareNum ob = new SquareNum();
      double val;
      System.out.println("Enter value to be squared: ");
      val = ob.getNumber();
      val = ob.square(val);
      System.out.println("Squared value is " + val);
   }
}

如下,使用javadoc工具处理SquareNum.java文件:

$ javadoc SquareNum.java
Loading source file SquareNum.java...
Constructing Javadoc information...
Standard Doclet version 1.5.0_13
Building tree for all the packages and classes...
Generating SquareNum.html...
SquareNum.java:39: warning - @return tag cannot be used\
                      in method with void return type.
Generating package-frame.html...
Generating package-summary.html...
Generating package-tree.html...
Generating constant-values.html...
Building index for all the packages and classes...
Generating overview-tree.html...
Generating index-all.html...
Generating deprecated-list.html...
Building index for all classes...
Generating allclasses-frame.html...
Generating allclasses-noframe.html...
Generating index.html...
Generating help-doc.html...
Generating stylesheet.css...
1 warning
$

 

 

9.类的设计技巧

保证数据私有;

要对数据初始化;

不要在类中使用过多的基本类型;

不是所有的域都需要独立的域访问器和域更改器;

将职责过多的类进行分解;

类名和方法名要能够体现他们的职责;

 

posted @ 2015-11-04 21:35  zxqstrong  阅读(658)  评论(0编辑  收藏  举报