JavaSE 对象与类(二)

6、对象构造

重载:如果有多个方法(比如,StringBuilder构造器方法)有相同的名字、不同的参数、便产生了重载。

重载解析:编译器通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。如果编译器找不到匹配的参数, 就会产生编译时错误,因为根本不存在匹配, 或者没有一个比其他的更好。

签名(signature):Java允许重载任何方法,因此,要完整的描述一个方法需要指出方法名以及参数类型。

返回类型不是签名的一部分。也就是说,不能有两个名字相同、参数类型也相同的却返回不同类型值的方法。

默认域初始化:如果在构造器中没有显式地给域赋予初值,那么就会被自动地赋为默认值:数值为0、布尔值为false、对象引用为null

无参数的构造器:顾名思义,没有参数的构造器。假如一个类中没有任何构造器,那么系统会默认提供一个无参数构造器。这个构造器将所有的实例域设置为默认值,即数值为0、布尔值为false、对象引用为null。如果一个类中提供了构造器,但是没有提供无参数的构造器,则在构造对象时如果没有提供参数就会被视为不合法。

public Employee()
{}

显式域初始化:显式地直接赋一个值,确保不管怎样调用构造器每个实例域都可以被设置为一个有意义的初值。好的设计习惯。适用于当一个类的所有构造器都希望把相同的值赋给某个特定的实例域时。

public class Employee {
    // instance field 实例域
    private String name = "";
    private double salary = 0.0;
    private LocalDate hireDay = new LocalDate(); // 也可以调用函数
}

参数名:参数变量用同样的名字将实例域屏蔽起来。可以采用 this.salary 的形式访问实例域。this 指示隐式参数, 也就是所构造的对象。

public Employee(String name, double salary)
{
    this.name = name; // 如果用name = name,就不会把参数变量传递给实例域。因为局部变量与实例域同名会屏蔽实例域
    this.salary = salary;
}

调用另一个构造器:如果一个构造器的第一个语句如this(...),这个构造器将调用同一个类的另一个构造器。关键字this有两个含义,一个是引用方法的隐式参数,另一个是加上括号后表示类的构造器方法。

public Employee(String name, double salary)
{
    this.name = name; 
    this.salary = salary;
}
public Employee(double s)
{
    // calls Employee (String, double)
    this("Bob", s);
}
// 采用这种方法使用this关键字非常有用,这样对公共的构造器代码部分只编写一次即可。

初始化块:在一个类的声明中,可以包含多个代码块。只要构造类的对象,这些块就会被执行。

这种机制不是必须的,也不常见。通常会直接将初始化代码放在构造器中。即使在类的后面定义,仍然可以初始化块中的设置域,但是推荐将初始化块放在域定义之后。

public class Test6 {
    private static int nextId;
    
    private int id;
    private String name;
    private double salary;
    
    // 对象初始化块 大括号包裹的语句 在执行构造函数之前执行
    {
        id = nextId;
        nextId ++;
    }
    
    public Employee(String n, double s)
    {
        name = n;
        salary = s;
    }
    public Employee()
    {
        name = "";
        salary = 0;
    }
    // ...
}

静态的初始化块:对类的静态域进行初始化的代码比较复杂。

static 
{
   Random generator = new Random();
   nextId = generator.nextInt(1000); // 生成一个随机数[0~999] int
}

实际应用

import java.util.Random;

public class ConstructTest {
    public static void main(String[] args) {
        // fill the staff array with three Employee objects
        Employee1[] staff = new Employee1[3];

        staff[0] = new Employee1("Harry", 40000);
        staff[1] = new Employee1(60000);
        staff[2] = new Employee1();

        // print out information about all Employee objects
        for(Employee1 e : staff)
        {
            System.out.println("name=" + e.getName() + ",id=" + e.getId() + ", salary=" + e.getSalary());
        }
    }
}
class Employee1
{
    private static int nextId;

    private int id;
    private String name = ""; // 实例域初始化
    private double salary;

    // 静态初始化块
    static
    {
        Random generator = new Random();
        nextId = generator.nextInt(1000); // 生成一个随机数[0~999]
    }
	// 初始化块
    {
        id = nextId;
        nextId++;
    }
    // 重载构造器
    public Employee1(String n, double s)
    {
        name = n;
        salary = s;
    }
    
    public Employee1(double s)
    {
        this("Employee #" + nextId, s);
    }
	// 默认构造器
    public Employee1()
    {
    }
    public String getName()
    {
        return name;
    }
    public double getSalary()
    {
        return salary;
    }
    public int getId()
    {
        return id;
    }
}
/*
name=Harry,id=391, salary=40000.0
name=Employee #392,id=392, salary=60000.0
name=,id=393, salary=0.0
 */

对象析构与finalize方法:

  • 由于有自动的垃圾回收器,所以Java不支持析构器。

  • 可以为任何一个类添加finalize方法。finalize方法将在垃圾回收器清除对象之前调用。在实际应用中,不要依赖于使用finalize方法回收任何短缺的资源,这是因为很难知道这个方法什么时候才能够调用。

总结

  1. 调用构造器的具体处理步骤:
    1. 所有数据域被初始化为默认值(0,false,null)
    2. 按照类声明中出现的次序,依次执行所有域初始化语句初始化块
    3. 如果构造器第一行调用了第二个构造器,则执行第二个构造器主体。
    4. 执行这个构造器的主体。
  2. 三种初始化数据域的方法:
  • 在构造器中设置值
  • 在声明中赋值
  • 初始化块赋值

7、包

包:方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。

使用包的主要原因是确保类名的唯一性。

为了保证包名的绝对唯一性,Sun公司建议将公司的因特网域名(这显然是独一无二的)以逆序对的形式作为包名,并且对于不同的项目使用不同的子包。

类的导入:一个类可以使用所属包中的所有类,以及其他包中的公有类(public class)

访问其他包中的共有类的两个方法:

  • 在每个类名之前添加完整的包名

    java.time.LocalDate today = java.time.LocalDate.now();
    
  • 使用import语句

    import java.util.*; // 导入全部类,对代码的大小也没有任何负面影响
    LocalDate today = LocalDate.now();
    
    import java.util.LocalDate; // 或者导入特定类
    

静态导入:import 语句不仅可以导入类,还增加了导入静态方法和静态域的功能。

import static java.lang.System.*; // 导入静态方法和静态域,使用时不用加类名前缀
out.println("123"); // System.out.println()
exit(0); // System.out.exit()

将类放入包中

要想将一个类放入包中,就必须将包的名字放在源文件的开头,包中定义类的代码之前。

package com.horstmann.corejava;
public class Employee
{
	...
}

如果没有在源文件中放置package语句,这个源文件中的类就被放置在一个默认包defaulf package)中。默认包是一个没有名字的包。在此之前,我们定义的所有类都在默认包中。

如果包与目录不匹配,虚拟机就找不到类。

包作用域

修饰符可以访问的位置

public公开:任何位置可以访问
protected保护:同包、子类、本类可以访问
缺省:本类、同包可访问
private私有:本类中可访问

8、类路径

类存储在文件系统的子目录中。类路径必须与包路径匹配。

类文件也可以存储在JAR文件中。JAR文件使用ZIP格式组织文件和子目录。可以使用所有ZIP实用程序查看内部的rt.jar以及其他的JAR文件。

为了使类能够被多个程序共享,需要做到以下几点

  1. 把类放到一个目录中,例如/home/user/classdir。需要注意,这个目录是包树状结构的基目录。如果希望将qwe/wer/ert/ConstructTest类添加到其中,这个class类文件就必须位于子目录中/home/user/classdir/qwe/wer/ert中。
  2. 将JAR文件放在一个目录中,例如/home/user/classdir
  3. 设置类路径(class path)。类路径是所有包含类文件的路径的集合。

9、文档注释

JDK包含一个很有用的工具,叫做javadoc,它可以由源文件生成一个HTML文档。

使用:在源代码中添加专用的定界符/**开始的注释,会自动生成文档。

posted @ 2021-10-05 19:09  永恒&  阅读(38)  评论(0编辑  收藏  举报