静态工厂方法
类似LocalDate和NumberFormat的类使用静态工厂方法来构造对象。
1 | NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(); |
这里的NumberFormat类不使用构造器来完成,有两个原因:
- 无法命名构造器。构造器名字必须与类相同,这里希望有两个不同名字,分别得到货币实例和百分比实例。
- 使用构造器时,无法改变所构造对象的类型,而工厂方法实际上将返回DecimalFormat类的对象,是NumberFormat的子类
Main方法
main方法也是一种静态方法。main方法不对任何对象进行操作,事实上,启动程序时还没有任何对象。静态的main方法将执行并构造程序所需要的对象。
方法参数
按值调用——表示方法接受的是调用者提供的值;
按引用调用——表示方法接收的是调用者提供的变量地址。
Java总是按值调用的。方法得到的是所有参数值的一个副本。也就是说,方法不能修改传递给它的任何参数变量的内容。
1 | double percent = 10; |
无论方法如何实现,在这个方法调用后,percent值还是10。
但是对于对象引用则不同!
1 | public static void tripleSalary(Employee x) |
当调用
1 | harry = new Employee(...); |
具体为:
- x初始化为harry值的一个副本,这里就是一个对象引用。
- raiseSalary方法应用于这个对象引用。x和salary同时引用的那个Employee对象的工资提高了200%。
- 方法结束后x不再使用,对象变量harry继续引用那个工资增至3倍的员工对象
总结Java方法参数
- 方法不能修改基本数据类型的参数
- 方法可以改变对象参数的状态
- 方法参数不能让一个对象参数引用一个新的对象
对象构造
重载——同方法、不同参数
默认字段初始化:如果构造器中没有显示地为字段设置初值,则会被自动的赋为默认值!数值为0、布尔值为false、对象引用为null
无参构造器:如果编写一个类没有无参构造,就会为你提供一个无参数的构造器,如果已经只定义了有参,再调无参则不合法。
参数名的定义:
习惯将参数名和实例字段保持一致,通过this来区分:
1 | public Employee(String name,double salary) |
this的另一用法:
this除了可以指示一个方法的隐式参数外,还可以调用同一个类的另一个构造器
1 | public Employee(double s) |
当调用new Employee(6000),Employee(double)构造器会调用Employee(String,double)构造器。
初始化块:
1 | class Employee |
之前有两种初始化数据字段的方法:
- 构造器中赋值
- 声明中赋值
另一个则是设置一个初始化块,只要构造这个类的对象,初始化块就会被执行——首先运行初始化快,然后才运行构造器的主体部分。
但是这不是必需的,通常将初始化代码放在构造器中
区分于静态字段对应的静态代码块:如果类的静态字段需要很复杂的初始化代码,那么可以使用静态的初始化块
区分初始化块和静态初始化块:
- 静态初始化块:使用static定义,当类装载到系统时执行一次.若在静态初始化块中想初始化变量,那仅能初始化类变量,即static修饰的数据成员.
- 非静态初始化块:在每个对象生成时都会被执行一次,可以初始化类的实例变量.
类设计技巧
- 保证数据私有
- 一定要对数据进行初始化
- 不要在类中使用过多的基本类型
- 不是所有字段都需要单独的字段访问器和字段更改器
- 分解有过多职责的类
- 类名和方法名要足够体现它们的职责
- 优先使用不可变的类
第五章 继承
继承的基本思想:基于已有的类创建新的类。就是复用已有类的方法,并且可以增加一些新的方法和字段
类、超类和子类
已存在的类——超类、基类、父类;新类——子类、派生类、孩子类
如Employee中的经理和和员工在薪资待遇上面存在一些差异,但也存在很多相同的地方。他们之间存在一个明显的“is-a”关系,每一个经理都是一个员工:“is-a”关系是继承的明显特征
1 | public class Manager extends Employee |
setBonus不是在Employee中定义的,所以Employee不能使用它。经理继承了name、salary、hireDay三个字段,并且新增了bonus字段。
覆盖方法:
如果要返回经理的奖金
1 | public double getSalary() |
因为salary是父类的私有字段,子类Manager的getSalary方法不能直接访问到!
如果我们想调用父类Employee的getSalary方法,而不是当前类的这个方法,可以用super.getSalary()
1 | public double getSalary() |
这里的super和this不能等同于一类,因为super不是一个对象的引用,例如,不能将值super赋给另一个对象变量,它只是一个指示编译器调用超类方法的特殊关键字。
注意:
有关子类是否继承了父类的私有字段(再理解)
如,Student类继承了Person类
Student对象里,本身就装着一个Person对象。Student对象没有继承Person对象的name字段,所以Student对象没有一个叫name的字段。但Student内部封装的Person对象还是有name字段的。
1 | public class Person { |
1 | public class Student extends Person { |
Student没有name字段,但它内部的Person对象有,而且还可以打出来看。
1 | public static void main(String[] args) { |
而且注意,我要直接打印Student的name字段 “s.name” ,报错说的是:Person类的name字段为私有,你不可以访问。而不是没有name字段。
大胆一点的话,我们还可以给Student类再加一个name字段。这时候的Student对象本身有一个name字段,内部的基类Person对象还有一个name对象。
1 | public class Student extends Person { |
输出:
1 | public static void main(String[] args) { |
注意:
- 使用super调用构造器,必须是子类构造器的第一条语句
- 子类构造器如果没有显式地调用超类的构造器,将自动地调用超类的无参数构造器,所以必须要求父类有无参构造,否则报错
多态
1 | Manager boss = new Manager("Carl Cracker",8000,1987,12,15); |
对于e来说,既可以是Manager也可以是Employee,像这种的,一个对象变量可以指示多种实际类型的现象称为多态,在运行时可以自动地选择适当的方法,称为动态绑定
例子:
1 | Manager boss = new Manager(...); |
这里面采用了多态,虽然staff[0]和boss引用同一个对象,但是编译器只将staff[0]看成是一个Employee对象,这意味着,可以这么调用:
1 | boss.setBonus(5000); //OK |
但不能这么调用:
1 | staff[0].setBonus(5000); //Error |
这是因为staff[0]的声明类型是Employee,而setBonus不是Employee的方法。setBonus是Manager特有的方法,而不是覆盖重写父类的方法
多态——当声明变量为某一种形态的变量时,编译器就将它看成某种形态。
注意
- 不能将超类的引用赋值给子类变量,如下非法:
1 | Manager m = staff[i]; //Error |
原因很清楚:不是所有的员工都是经理,如果赋值成功,m有可能引用了一个不是经理的Employee对象,而在后面有可能会调用m.setBonus,这就会发生错误。
警告:
1 | Manager[] managers = new Manager[10]; |
这样是没有问题的,因为manger[i]是一个Manager就一定是一个Employee!一定要切记:这里的staff和mangers引用的是同一个数组,就是一开始new的长度为10的数组!
1 | staff[0] = new Employee("Harry"); |
如果这么去赋值,编译器是可以接受的!但是!!staff[0]和managers[0]是相同的引用,我们把一个普通的员工Harry擅自归入到经理行列(数组)里面去了!!后面如果调用manager[0].setBonus(1000)的时候,将会试图调用一个根本不存在的实例字段,进而搅乱相邻存储空间的内容
牢记:所有数组要牢记创建时候的元素类型,并负责监督仅将类型兼容的引用存储到数组中!例如,使用new managers[10]创建数组是一个经理数组如果试图存储一个Employee类型的引用就会引发ArrayStoreException异常
方法调用
编译器查看对象的声明类型和方法名。
确定方法调用中提供的参数类型。
如果是private、static、final或者构造器,那么编译器将可以准确地知道应该调用哪个方法。——静态绑定;
动态绑定——如果调用的方法依赖于隐式参数的实际类型,则必须在运行的时候使用动态绑定。
强制类型转换
对于对象:
由于在员工列表中,一部分是纯员工,有一部分是经理(子类),在创建数组的时候申明的是Employee对象,而Employee对象无法读取到其Manager字段或方法等属性(多态),那么在实际用Manager这个对象的时候,要先强制转换成Manager类型:
1 | Manager boss = (Manager)staff[0]; |
将其复原为Manager对象,以便于访问其额外的字段,如bonus奖金。当然,前提是0号确实是Manager,如果“谎报”,则会报错ClassCastException,为了确保不会谎报,可以先判断一下:
1 | if (staff[0] instanceof Manager) |
受保护字段protected
一般来说,声明为private私有,对其他类都是不可见的,即,子类不能访问超类的私有字段。不过有时候希望限制超类中的某个方法只允许子类访问,或者希望子类的方法访问超类的某个字段。
例如,将Employee中的hireDay字段设为protected,而不是private,则Manager方法就可以访问到这个字段。
注意:
- 要谨慎使用,如果你的代码被别的程序员访问了受保护字段,那么后期维护时候,修改自身类则会影响到别人!
- 受保护的方法更具有实际意义,表明子类得到了信任,可以正确的使用这个方法,而其他类则不行
泛型类数组列表
ArrayList是一个有类型参数的泛型类。尖括号里面填写保存的元素对象类型,如ArrayList<Employee>
声明一个保存Employee对象的数组列表:
1 | ArrayList<Employee> staff = new ArrayList<Employee>(); |
也可以省略右边括号里面的类型参数
1 | ArrayList<Employee> staff = new ArrayList<>(); |
对象包装器与自动装箱
每个基本类型都有与之对应的类Integer、Long、Float、Double、Short、Byte、Character、Boolean;
<>尖括号中的类型参数不允许是基本类型
由于每个值分别包装在对象中,所以ArrayList<Integer>效率远远低于int[]
自动装箱:
1 | var list = new ArrayList<Integer>() |
此时,进行了自动装箱过程:
1 | list.add(Integer.valueOf(3)) |
自动拆箱:此时拿到的n应该是<Integer>类型
1 | int n = list.get(i) |
转换成:
1 | int n = list.get(i).intValue(); |





