Java基础知识之对象与类
本文内容参考《Java核心技术 卷Ⅰ》
用户自定义类
用var声明局部变量
首先是一个正常的变量声明,这是我们常用的声明方式。
1 | class Employee { |
在Java 10中,如果可以从变量的初始值推导出它们的类型,那么可以用var
关键字声明局部变量,这样可以避免重复写类名。使用var
时需要注意,它只能用于方法中的局部变量,参数和字段的类型必须声明。
1 | class Employee { |
使用null引用
如果对null
值应用一个方法,会产生一个NullPointerException
异常。
1 | LocalDate birthday = null; |
在定义类时,我们最好清楚的知道哪些字段可能为null
。我们有两种解决方法,第一种是“宽容型”方法,把null
参数转换为一个适当的非null
值,第二种是“严格型”方法,就是直接拒绝null
参数,抛出异常。
1 | // 宽容型 |
在Java 9中,Objects类对此提供了一个便利的方法。
1 | // 相当于上面那种 |
封装
1 | public String getName { |
这些是典型的访问器方法。由于它们只返回实例字段值,因此又称为字段访问器。有时候可能想要获得或设置实例字段的值,那么需要提供三项内容:
- 一个私有的数据字段。
- 一个公共的字段访问器方法。
- 一个公共的字段更改器方法。
这样设置的好处是,可以改变内部实现,除了该类的方法之外,这不会影响到其他代码。
注意不要编写返回可变对象引用的访问器方法,例如:
1 | class Employee { |
Date
类有一个更改器方法setTime
。由于返回的Date
和Employee
类里的hireDay
引用同一个对象,如果对返回的引用变量调用更改器方法,会将类里的变量一起改变。如果需要返回一个可变对象的引用,首先应该对他进行克隆(指存放在另一个新位置的对象副本)。
1 | class Employee { |
基于类的访问权限
一个方法可以访问所属类的所有对象
的私有数据。
1 | class Employee { |
典型的调用方法是
1 | if (harry.equals(boss)) ... |
这个方法不仅访问了harry
的私有字段,还访问了boss
的私有字段,这是合法的。因为boss
是Employee
类型的对象。而Employee
类的方法可以访问任何Employee
类型对象的私有字段。
final实例字段
可以将实例字段定义为final
,这样的字段必须在构造对象时初始化。也就是说,必须确保在每一个构造器执行之后,这个字段的值已经设置,并且以后不能再修改这个字段。final
修饰符对于类型为基本类型或者不可变类的字段非常有用。
对于可变的类,使用final
修饰符可能会造成混乱,例如
1 | private final StringBuilder evaluations; |
它在构造器中初始化为
1 | evaluations = new StringBuilder(); |
final
关键字只是表示存储在evaluations
变量中的对象引用不会再指向另一个不同的StringBuilder
对象,但这个对象还可以更改
1 | public void giveGoldStar() { |
静态字段与静态方法
静态字段
1 | class Employee { |
对于Employee
这个类来说,每个Employee
对象都有一个自己的id
字段,但这个类的所有实例将共享一个nextId
字段。静态字段属于类,而不属于任何单个的对象。
静态方法
静态方法是不在对象上执行的方法,可以认为静态方法是没有this
参数的方法(在一个非静态的方法中,this
参数指示这个方法的隐式参数)。
静态方法不能访问非静态的实例字段,因为它不能在对象上执行操作。不过静态方法可以访问静态字段,例如
1 | public static int getNextId() { |
当然,这个方法也可以省略static
关键字,但是省略就需要用过Employee
类对象的引用来调用这个方法。
使用对象调用静态方法是合法的,假如harry是一个Employee
对象,可以用harry.getNextId()
代替Employee.getNextId()
。但这种写法很容易造成混淆,所以还是建议用对象调用非静态方法,类名调用静态方法。
以下两种情况可以使用静态方法:
-
方法不需要访问对象状态,因为它需要的所有参数都是通过显示参数提供(例如:
Math.pow
)。 -
方法只需要访问类的静态字段(例如:
Employee.getNextId
)。
工厂方法
静态方法还有另一种常见的用途。类似LocalDate
和NumberFormat
的类使用静态工厂方法来构造对象。NumberFormat
类如下生成不同风格的格式化对象
1 | NumberFormat currentFormatter = NumberFormat.getCompactNumberInstance(); |
NumberFormat
不利用构造器的原因是:
-
无法命名构造器。构造器的名字必须与类名相同,但这里希望有两个不同的名字。
-
使用构造器时,无法改变所构造对象的类型。而工厂方法实际上将返回
DecimalFormat
类的对象(NumberFormat
的一个子类)。
方法参数
Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个副本。具体来讲,方法不能修改传递给它的任何参数变量内容。然而,有两种类型的方法参数:
- 基本数据类型(数字、布尔值)。
- 对象引用。
一个方法不能修改基本数据类型,但可以修改对象引用数据。因为方法得到的是对象引用的副本,原来的对象和这个副本都指向同一个对象。
有些人会误认为这是按引用传递,有一个反例,下面是一个交换Employee对象的方法
1 | public static void swap(Employee x, Employee y) { |
如果Java是按引用调用,那么这个方法就能实现交换
1 | var a = new Employee(); |
但是,这个方法并没有改变存储在变量a
和b
中的对象引用。也就是说,swap
方法其实是对x
和y
这两个副本的交换。
总结:
- 方法不能修改基本数据类型的参数。
- 方法可以改变对象参数的状态。
- 方法不能让一个对象参数引用一个新的对象。
对象构造
重载
如果有多个方法有相同的名字、不同的参数,就出现了重载,例如
1 | public int foo(); |
调用另一个构造器
关键字this
指示一个方法的隐式参数,它还有另一个含义。如果构造器的第一个语句形如this(...)
,这个构造器将调用同一个类的另一个构造器
1 | public Employee(double s) { |
当调用new Employee(100)
时,Employee(double)
将调用Employee(String, double)
构造。
初始化块
在一个类声明中,可以包含任意多个代码块。只要构造这个对象,这些块就会被执行。
1 | class Employee { |
无论使用哪个构造器,id字段都会在对象初始化块中初始化。首先运行初始化块,然后才运行构造器的主体部分。
如果静态字段需要很复杂的初始化代码,可以使用静态初始化块来初始化静态字段。
1 | static { |
在类第一次加载的时候,将会进行静态字段的初始化。
类设计技巧
-
一定要保证数据私有。
绝对不要破坏封装性。有时候可能需要编写一个访问器方法或更改器方法,但是最好还是保持实例字段的私有性。
-
一定要对数据进行初始化。
Java不会为你初始化局部变量,但是会对对象的实例字段进行初始化。最好不要依赖系统的默认值,而是应该显式地初始化所有数据。
-
不要在类中使用过多的基本类型。
用其它类来替代使用多个相关的基本类型,例如
1
2
3
4
5
6
7class Customer {
...
private String street;
private String city;
private String state;
private int zip;
...可以用一个名为
Address
的新类替换Customer
类中的这几个实例字段。这样更容易处理地址的变化。 -
不是所有的字段都需要单独的字段访问器和字段更改器。
-
分解有过多职责的类。
-
类名和方法名要能够体现它们的职责。
-
优先使用不可变的类。