4.6 对象构造

4.6 对象构造

构造器,可以定义对象的初始状态。
构造非常重要。java提供了多种编写构造器的机制。

4.6.1 重载

有些类有多个构造器。
构造一个空的StringBuilder对象,

var message=new StringBuilder();

指定一个初始字符串:

var todoList=new StringBuilder("To do:\n");

这就是重载!多个方法有相同的名字,不同的参数,就是重载。编译器必须决定使用那个方法。它用各个方法首部中的参数类型与方法调用中所使用的值类型进行匹配,选出正确的方法。编译器查找匹配的过程称为重载解析。
java允许重载任何方法,不只是构造方法。要完整的描述一个方法,要指定方法名和参数类型。
注意,不能有两个名字相同、参数类型也相同,返回类型不同的方法!!!

4.6.2 默认字段初始化

如果在构造器中没有显示的为字段设置初值,那么就会自动的被赋予默认值:数值为0,布尔值为false,对象引用为null。如果不明确的对字段进行初始化,会影响程序代码的可读性。
这是局部变量和字段的一个重要区别。方法中的局部变量必须明确的初始化。而在类中,如果没有初始化类中的字段,会自动初始化。
就像Employee类,假如没有在构造器中指定如何初始化某些字段,默认情况下,salary会初始化为0,name和hireDay会初始化为null。但,这不是一个好的情况。

4.6.3 无参数的构造器

由无参构造器创建对象时,对象的状态会设置为适当的默认值。

public Employee(){
	name="";
	salary=0;
	hireDay=LocalDate.now();
}

如果写类的时候没有写构造器,就会为你提供一个无参构造器。这个构造器会将所有的实例字段设置为默认值。即,数值型为0,布尔型为false,对象变量为null。
如果类中提供了至少一个构造器,但没有无参的构造器,那么构造对象时如果不提供参数就是不合法的。
记住,只有当类中没有任何构造器的情况下,java才会提供一个默认的无参构造器。如果你写了一个自己的构造器,但是想要这个类的用户可以使用无参构造去构造一个对象。那么就必须自己提供一个无参数的构造函数。可以只提供像下面的代码就可以:

public ClassName{
	//空
}

4.6.4 显示字段初始化

通过重载类的构造器,可以采取多种形式设置类的实例字段的初始状态。
可以在类中直接为任何字段赋值。

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;
	}
	...
}

4.6.5 参数名

编写构造器时,常常为参数的命名感到困惑。
通常喜欢使用单个字母作为参数名:

public Employee(String n,double s){
	name=n;
	salary=-s;
}

这样做的缺点是,阅读了代码才能知道n,s的含义。
有的程序员喜欢在每个参数前加一个“a”。

public Employee(String aName,double aSalary){
	name=aName;
	salary=aSalary;
}

这样很清晰。
还有一种技巧。因为:参数变量会遮蔽同名的实例字段。例如,如果将参数命名为salary,salary将指示这个参数,而不是实例字段。
但是,可以使用this.salary访问实例字段。this指示隐式参数,也就是所构造的对象。

public Employee(String name,double salary){
	this.name=name;
	this.salary=salary;
}

4.6.6 调用另一个构造器

关键字this指示一个方法的隐式参数。但它还有其他含义
如果构造器的第一句形如:

this(...);

这个构造器将调用同一个类的另一个构造器

public Employee(double s){
	this("Employee #"+nextId,s);//调用Employee(String,double);
	nextId++;
}

当调用new Employee(60000)时,Employee(double)构造器将调用Employee(String,double)构造器。
采用这种方式使用this关键字非常有用,这样的话,公共的构造器代码只需编写一次。

4.6.7 初始化块

前面的两种初始化数据字段的方法:

  • 在构造器中设置值
  • 在声明中赋值。

现在,第三种机制,叫做初始化块。一个类的声明中可以包含任意多个代码块。只要构造这个类的对象,这些块就会执行。

class Emlpoyee{
	private static int nextId;
	private int id;
	private String name;
	private double salary;
	{
		id=nextId;
		nextId++;
	}
	public Employee(String n,double s){
		name=n;
		aslary=s
	}
	public Employee{
		name="";
		salary=0;
	}
	...
}

这个示例中,无论使用那个构造器构造对象,id字段都会在对象初始化块中初始化。首先运行初始化块,然后才运行构造器的主体。这种机制不是必需的,也不常见。通常会直接将初始化代码放到构造器中
可以在初始化块中设置字段。即使这些字段在类后面才定义,这是合法的。(????这么牛?)但是,为了避免循环定义,不允许读取在后面初始化的字段。总体的建议还是,将初始化块放在字段定义之后。

调用构造器的具体处理步骤(重要的重要的重要的很!!!)

  1. 如果构造器的第一行调用了另一个构造器,则基于所提供的参数执行第二个构造器。
  2. 否则,(a)所有数据字段初始化为其默认值,就是0、false或者null。(b)按照在类声明中出现的顺序,执行所有字段初始化方法和初始化块。
  3. 执行构造器主体代码。

应该精心的组织好初始化代码,这样有利于其他程序员理解。
例如,如果让类的构造器依赖于数据字段声明的顺序,那就会显得很奇怪并且容易引起错误。
可以通过提供一个初始值,或者使用一个静态初始化块来初始化静态字段。前面的机制

private static int nextId=1;

如果类的静态字段需要很复杂的初始化代码,可以使用静态的初始化块。将代码放到一个块中,标记为static。

static{
	var generator=new Random();
	nextId=generator.nextInt(10000);
}

在类第一次加载的时候,将会进行静态字段的初始化。与实例字段一样,除非将静态字段现实的设置成其他值,否则默认初始值是0、false、null。所有的静态字段初始化方法以及静态初始化块都将依照类声明中出现的顺序执行。

下面的程序将展示很多特性:

  • 重载构造器
  • 用this(…)调用另一个构造器
  • 无参构造器
  • 对象初始化块
  • 静态初始化块
  • 实例字段初始化块
import java.util.*;
public class ConstructorTest {
    public static void main(String[] args) {
        var staff=new Employee[3];
        staff[0]=new Employee("harry", 40000);
        staff[1]=new Employee(60000);
        staff[2]=new Employee();

        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="";//实例字段初始化
    private double salary;


    //静态初始化块
    static {
        var generator=new Random();
        nextId=generator.nextInt(10000);
    }
    //对象初始化块
    {
        id=nextId;
        nextId++;
    }

    //三个重载的构造器
    public Employee(String n,double s){
        name=n;
        salary=s;
    }

    public Employee(double s){
        this("Employee #"+nextId,s);
    }

    //默认构造器
    public Employee(){

    }

    public String getName(){
        return name;
    }
    public double getSalary(){
        return salary;
    }
    public int getId(){
        return id;
    }

}

java.util.Random类中的一些方法:

  • Random() 构造一个新的随机数生成器
  • int nextInt(int n) 返回一个0~n-1之间的随机数

4.6.8 对象析构与finalize方法

C++中有显式的析构器方法,其中放置一些当对象不再使用时需要执行的清理代码。在析构器中,常见的操作是回收分配给对象的存储空间。Java会完成自动的垃圾回收,不需要人工回收内存,所以java不支持析构器。
当某些对象使用了内存之外的其他资源,例如,文件或者使用了系统资源的另一个对象的句柄。这种情况下,当资源不再需要时,将其回收和再利用十分重要。
如果一个资源一旦使用完就需要立即关闭,那么应当提供一个close方法来完成必要的清理工作。可以在对象使用完时调用这个close方法。第七章(异常、断言、日志)将介绍如何确保自动调用这个方法。

如果可以等到虚拟机退出,那么可以用方法Runtime.addShutdownHook增加一个“关闭钩”。
可以使用一个Cleaner类注册一个动作,当对象不可达时(就是说其他对象都无法访问这个对象,只有清洁器可以访问),就会完成给个动作。
上面说的两种情况很少见。当然我也没太懂。。。毕竟没见过实际使用。

警告:不要使用finalize方法来完成清理。这个方法原本要在垃圾回收器清理对象之前调用。不过,你并不能知道这个方法到底什么时候调用,而且这个方法已经被废弃。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值