对象与类
对象构造
- 重载
- 重载(overloading)是在一个类的里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。最常用的地方就是构造器的重载。
- 重载规则
- 被重载的方法必须改变参数列表(参数列表不一样);
- 被重载的方法可以改变返回值;
- 被重载的方法可以改变访问修饰符 ;
- 被重载的方法可以声明新的或更广的检查异常;
- 方法能够在同一个类中或者在一个子类中被重载;
- 无法以返回值类型作为重载函数的区分标准
- 编译器必须挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。如果编译器找不到匹配的参数, 就会产生编译时错误,因为根本不存在匹配, 或者没有一个比其他的更好,即重载解析(overloading resolution)。
- Java 允许重载任何方法, 而不只是构造器方法。因此,要完整地描述一个方法,需要指出方法名以及参数类型。这叫做方法的签名(signature)。
- 默认域初始化
- 如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值: 数值为 0、布尔值为 false、 对象引用为 null。然而,只有缺少程序设计经验的人才会这样做。确实, 如果不明确地对域进行初始化,就会影响程序代码的可读性。
- 这是域与局部变量的主要不同点。 必须明确地初始化方法中的局部变量。但是,如果没有初始化类中的域, 将会被自动初始化为默认值( 0、false 或 null )。
- 无参数的构造器
- 很多类都包含一个无参数的构造函数,对象由无参数构造函数创建时, 其状态会设置为适当的默认值。
- 如果在编写一个类时没有编写构造器, 那么系统就会提供一个无参数构造器。这个构造器将所有的实例域设置为默认值。于是, 实例域中的数值型数据设置为 0、 布尔型数据设置为 false、 所有对象变量将设置为 null。
- 如果类中提供了至少一个构造器, 但是没有提供无参数的构造器, 则在构造对象时如果没有提供参数就会被视为不合法。
- 仅当类没有提供任何构造器的时候, 系统才会提供一个默认的构造器如果在编写类的时候,给出了一个构造器, 哪怕是很简单的, 要想让这个类的用户能够采用构造器方式构造实例,就必须提供一个默认的构造器 ( 即不带参数的构造器)。 当然, 如果希望所有域被赋予默认值, 可以采用下列格式:
public ClassName() {}
- 显式域初始化
-
通过重载类的构造器方法,可以采用多种形式设置类的实例域的初始状态。确保不管怎样调用构造器,每个实例域都可以被设置为一个有意义的初值,这是一种很好的设计习惯。
可以在类的定义中,直接将一个值赋给任何域。
class Employee { private String name = ""; ... }
-
在执行构造器之前,先执行赋值操作。当一个类的所有构造器都希望把相同的值赋予某个特定的实例域时,这种方式特别有用。
初始值不一定是常量值。在下面的例子中, 可以调用方法对域进行初始化。
class Employee { private static int nextId; private int id = assignId(); ... private static int assignId() { int r = nextId; nextId++; return r; } ... }
-
- 参数名
- 在编写很小的构造器时,参数使用单个字符命名,例如
,这样会使代码的阅读变得复杂,有时可以在参数前加上前缀“a”,例如name = n
,这样使读者可以更清晰的理解参数的意义。name = aName
- 基于参数变量用同样的名字将实例域屏蔽起来。例 如,如果将参数命名为 salary, salary 将引用这个参数, 而不是实例域。 但是,可以采用 this,salary 的形式访问实例域。回想一下,this 指示隐式参数, 也就是所构造的对象。
- 在 C++ 中, 经常用下划线或某个固定的字母(一般选用 m 或 x ) 作为实例域的前缀例如,salary 域可能被命名为 _salary、mSalary 或 xSalary Java 程序员通常不这样做。
- 在编写很小的构造器时,参数使用单个字符命名,例如
- 调用另一个构造器
- 关键字 this 引用方法的隐式参数。另外,如果构造器的第一个语句形如 this(…), 这个构造器将调用同一个类的另一个构造器。
当调用new Employee(6000)时,Employee(double)构造器将调用Employee(String , double)构造器。public Employee(double s) { // calls Employee(String, double) this("Employee #" + nextId, s); nextId++; }
- 采用这样的方法使用 this 关键字非常有用,这样对公共的构造器代码部分只编写一次即可。
- 在 Java 中, this 引用等价于 C++ 的 this 指针。但是, 在 C++ 中, 一个构造器不能调用另一个构造器 ,, 在 C++ 中, 必须将抽取出的公共初始化代码编写成一个独立的方法。
- 关键字 this 引用方法的隐式参数。另外,如果构造器的第一个语句形如 this(…), 这个构造器将调用同一个类的另一个构造器。
- 初始化块
- 初始化数据域三种方法:
- 在构造器中设置值
- 在声明中赋值
- 初始化块(initialization block):在一个类的声明中,可以包含多个代码块。只有构造类的对象,这些块就会被执行。
在这个示例中,无论使用哪个构造器构造对象,id 域都在对象初始化块中被初始化。首先运行初始化块,然后才运行构造器的主体部分。class Employee { private static int nextId; private int id; private String name; private double salary; // object initialization block { id = nextId; nextId++; } public Employee(String n, double s) { name = n; salary = s; } public Employee() { name = ""; salary = 0; } ... }
- 即使在类的后面定义, 仍然可以在初始化块中设置域。但是, 为了避免循环定义,不要读取在后面初始化的域。
- 初始化数据域三种方法:
初始化数据
由于初始化数据域有多种途径,所以列出构造过程的所有路径可能相当混乱。下面是调用构造器的具体处理步骤:
- 所有数据域被初始化为默认值(0、false 或 null)。
- 按照在类声明中出现的次序, 依次执行所有域初始化语句和初始化块。
- 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体。
- 执行这个构造器的主体。
可以通过提供一个初始化值, 或者使用一个静态的初始化块来对静态域进行初始化。
private static int nextId = 1;
如果对类的静态域进行初始化的代码比较复杂,那么可以使用静态的初始化块。将代码放在一个块中,并标记关键字 static。
// static initialization block
static
{
Random generator = new Random();
nextId = generator.nextInt(10000);
}
在类第一次加载的时候, 将会进行静态域的初始化。与实例域一样,除非将它们显式地设置成其他值, 否则默认的初始值是 0、 false 或 null。 所有的静态初始化语句以及静态初始化块都将依照类定义的顺序执行。
- 对象析构与finalize方法
- 有些面向对象的程序设计语言,特别是 C++, 有显式的析构器方法,其中放置一些当对象不再使用时需要执行的清理代码。在析构器中, 最常见的操作是回收分配给对象的存储空间。由于 Java 有自动的垃圾回收器,不需要人工回收内存, 所以 Java 不支持析构器。
- 某些对象使用了内存之外的其他资源, 例如,文件或使用了系统资源的另一个对象的句柄。在这种情况下,当资源不再需要时, 将其回收和再利用将显得十分重要。
- 可以为任何一个类添加 finalize 方法。finalize 方法将在垃圾回收器清除对象之前调用。在实际应用中,不要依赖于使用 finalize 方法回收任何短缺的资源, 这是因为很难知道这个方法什么时候才能够调用。
- 如果某个资源需要在使用完毕后立刻被关闭, 那么就需要由人工来管理。对象用完时,可以应用一个 close 方法来完成相应的清理操作。
import java.util.*;
public class ConstructorTest {
public static void main(String[] args) {
// fill the staff array with three Employee objects
Employee[] staff = new Employee[3];
staff[0] = new Employee("Harry",40000);
staff[1] = new Employee(60000);
staff[2] = new Employee();
// printout information about all Employee objects
for(Employee e:staff)
System.out.println("name="+e.getName()+", id="+e.getId()+", salary="+e.getSalary());
}
}
class Employee
{
private static int nextId;
private int id;
private String name = ""; // instance field initialization
private double salary;
// static initialization block
static {
Random generator = new Random();
// set nextId to a random number between 0 and 9999
nextId = generator.nextInt(10000);
}
//object initialization block
{
id = nextId;
nextId++;
}
// three overloaded constructors
public Employee(String n,double s)
{
name = n;
salary = s;
}
public Employee(double s)
{
// calls the Employee(String, double) constructor
this("Employee #"+nextId,s);
}
// the default constructor
public Employee()
{
// name initialized to ""--see above
// salary not explicitly set--initialized to 0
// id initialized in initialization block
}
public String getName()
{
return name;
}
public double getSalary()
{
return salary;
}
public int getId()
{
return id;
}
}
/*
name=Harry, id=7023, salary=40000.0
name=Employee #7024, id=7024, salary=60000.0
name=, id=7025, salary=0.0
**/