重点
1.类、超类和子类
2.对象包装器与自动装箱
3.枚举类
4.继承的设计技巧
1.类、超类和子类
-
声明为
private
的类成员不会被该类的子类继承 -
super
不是一个对象的引用,比如不能将值super
赋给另一个对象变量 -
如果构造子类对象时没有显式调用超类的构造器,则超类必须有一个无参构造器:
class B extends A{
// 因为A类没有无参构造器,所以子类B必须声明一个有参构造器,并且显式调用A的有参构造器,否则报错
public B(int a) {
super(a); // super(xxx)必须是子类构造器的第一句(this(xxx)也必须要第一句,所以两者不能共同出现)
}
}
class A{
// 因为有了有参构造器,所以默认不会创建无参构造器
public A(int a){}
}
- 多态指一个变量可以指示多种实际类型,多态的使用过程中注意事项有:
// Manager是Employee的子类,且setBonus是Manager特有的方法
Manager boss = new Manager(...);
Employee[] staff = new Employee[3];
staff[0] = boss;
boss.setBonus(100); // ok
staff[0].setBonus(100); // 报错,因为staff[0]的声明类型是Employee
Manager m = staff[1]; // 报错,因为不能将超类引用给子类变量
-
静态绑定和动态绑定:
- 静态绑定:编译器能够准确知道调用什么方法,比如
private
方法、static
方法、final
方法和构造器方法(可以理解为这些方法无法覆盖,所以就不存在多态的情况)
class B extends A{ @Override public void test4() { .... } } class A { public static void test1(){} // 不能覆盖 private void test2(){} // 不能覆盖 public final void test3(){} // 不能覆盖 public void test4(){} // 可以覆盖 }
- 动态绑定:指运行时能够自动选择适当的方法(需要额外开销),比如
x
的实际类型为D
(D
是C
的子类),如果D
中定义了方法f(String)
则会调用D
中的f(String)
,否则调用C
中的f(String)
,C
中再没有就依次类推
- 静态绑定:编译器能够准确知道调用什么方法,比如
-
子类可以覆盖和超类签名(方法名+参数列表)相同的方法:
- 允许子类将覆盖方法的返回类型该为超类返回类型的子类
class B extends A{ @Override public HashMap test() { return null; } } class A { public Map test(){ return null; } }
- 子类方法的可见性不能低于超类的:
class B extends A{ @Override public HashMap test() { // 如果是private就会报错 return null; } } class A { Map test(){ return null; } }
-
类使用
final
后则不允许被继承了(类中的方法自动变为final
,但是字段不会),方法使用final
后则不允许被子类覆盖 -
对于对象的强制类型转换要注意:
- 只能在继承层次结构内进行强转:
String c = (String)staff[0]; // 报错,String不是Employee的子类
- 超类强转为子类前,应该先使用
instanceof
检查
-
Java中的4个访问控制修饰符:
private
:仅本类可以访问public
:可由外部访问protected
:本包和所有子类可以访问- 默认(无修饰符):本包中可以访问
2.对象包装器与自动装箱
- 泛型尖括号中不允许基本类型,即不能写为
ArrayList<int>
,只能写为ArrayList<Integer>
- 自动拆/装箱适用于算术表达式:
// 编译器会自动插入指令对a对象进行拆箱,然后将结果加1,再装箱
Integer a = 3;
a++;
System.out.println(a.getClass()); // class java.lang.Integer
- 条件表达式中也会涉及自动拆/装箱:
// a会进行拆箱并提升为double类型,然后再装箱为Double
Integer a = 1;
Double b = 2.0;
System.out.println(true ? a : b); // 输出1.0
3.枚举类
- 比较枚举类型的值时,不需要使用
equals
,可直接使用==
比较 - 枚举的构造器是私有的(可省略
private
) - 枚举类型是抽象类
Enum
的子类,常见方法如下:
public class Test {
public static void main(String[] args) {
Size size = Enum.valueOf(Size.class, "SMALL"); // 返回指定名字的枚举常量
System.out.println(size);
Size[] values = Size.values(); // 返回包含所有枚举值的数组
for (Size value : values) {
System.out.println(value);
}
System.out.println(Size.SMALL.ordinal()); // 返回枚举常量在enum声明中的位置
}
}
enum Size{
SMALL("S"), LARGE("L");
private String abbereviation;
private Size(String abbereviation){
this.abbereviation = abbereviation;
}
}
4.继承的设计技巧
- 将公共操作和字段放在超类中
- 不要使用受保护的字段,因为
protected
机制并不能提供太多保护:- 只要由超类派生出子类就可以直接访问
protected
实例字段,破坏封装性 - 同一个包中的所有类都可以访问
protected
字段
- 只要由超类派生出子类就可以直接访问
- 使用多态而不要使用类型信息,因为多态方法或接口实现的代码更易于维护
- 不要滥用反射,因为使用反射时编译器无法帮助查找编程错误,直到运行时才会发现错误并导致异常
其他知识点
- Java中只有基本类型不是对象,像数组类型等都扩展
Object
类(Java中每个类都继承Obejct`)的类类型:
Object obj = new Employee("psj");
Employee[] staff = new Employee[2];
obj = staff; // 不会报错
obj = new int[10]; // 不会报错
- 子类定义
equals
方法时会先调用超类的equals
方法:- 检测失败则对象不相等
- 检测成功则继续比较子类中的字段
class A{
String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
A a = (A) o;
return Objects.equals(name, a.name);
}
}
class B extends A{
int age;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false; // 调用超类的equals方法
B b = (B) o;
return age == b.age;
}
}
- 集合中可能误用的
API
:size
方法:返回的是实际元素个数set
方法:当集合大小大于i时,才能调用xxx.set(i, x)
,但是可以调用xxx.add(i, x)
List<Integer> list = new ArrayList<>(100);
list.add(1);
System.out.println(list.size()); // 输出1不是100
list.set(1, 2); // 报错
list.add(1, 2); // 没问题
- 抽象类不能被实例化,可以创建一个抽象类的对象变量然后引用非抽象子类的对象
Person p = new Student(); // Person是抽象类,Student是其子类