Java核心技术——第5章
类、超类和自类
定义子类
由继承Employee类来定义manager类
public class Manager extends Employee//使用extends来定义继承
{
...
}
Employee类称为超类、基类、父类
manager类称为子类、派生类、孩子类
子类可使用超类的方法、域
应该将通用的方法放在超类中,而特殊用途的方法放在子类中
覆盖方法
在子类中添加一个与超类中同名的方法即可覆盖超类的方法
在子类中不能直接访问超类的私有域,需要通过超类中的公有接口,如getSalary()
子类想调用超类中同名的方法时,需要借助super关键字
public double getSalary()
{
return baseSalary + super.getSalary();
}
注:super和this关键字由区别,super不是一个对象的引用,只能访问类中的方法,不能访问对象中的域,不能赋给一个对象变量,而this可以,例如,不能通过super.salary
访问超类中的私有域
子类构造器
在子类中想使用超类的构造器,使用super关键字
public Manager(String name,double salary,int year,int month,int day)
{
super(name,salary,year,month,day);
bonus=0;
}
super调用超类构造器的语句必须在子类构造器的第一句
子类没有显式调用超类构造器,则自动调用超类默认(没有参数)的构造器
如果超类中没有不带参数的构造器,并且在子类构造器中没有显式调用超类其他构造器,则编译器报错
继承层次
由一个公共超类派生出来的所有类的集合被称为继承层次
多态
定义:一个对象可以指示多种实际类型的现象
置换法则:程序中出现超类对象的任何地方都可以用子类对象置换
对象变量是多态的,一个超类变量可以引用其任何的子类对象,但反过来就不成立,子类变量不能引用超类对象
理解方法调用
动态绑定:在运行时自动选择调用哪个方法的现象
例:调用类C的对象x的方法,x.f(args)
-
编译器列出类C中所有名为f的方法,和其超类中访问属性为
public
且名为f的方法(超类的私有方法不可访问) -
编译器查看调用方法时提供的参数类型
如果找不到相应参数类型,则找可以类型转换之后的方法
如果还是找不到,或者转换后的有多个方法,则报错
方法名和参数类型为方法的签名,返回类型不是签名的一部分。可以允许返回子类型,即必须保证兼容性
-
如果是private方法、static方法、final方法或者构造器,则编译器可以准确知道应该调用哪个方法,称为静态绑定
阻止继承:final类和方法
对类使用final
修饰符,表明该类不允许拓展
public final class Executive extends Manager
{
...
}
类中的特定方法被声明为final
后,子类不能覆盖这个方法
public class Employee
{
public final String getName()
{
...
}
}
将类边设置为final之后,其中的方法都变成final,但是域不会变成final,final域表示构造对象之后就不能改变其值
强制类型转换
只能在继承层次内进行类型转换
在将超类转换成子类之前,应该使用instanceof
进行检查
if(staff[1] instanceof Manager)
{
boss = (Manager) staff[1];
}
应尽量避免使用类型转换和instanof运算法,往往强转是为了使用子类的方法,这说明超类设计不合理,应该重新设计超类
抽象类
关键字abstract
为何要设置抽象类:将一些通用的方法放在继承通用超类中
包含抽象方法的类必须被声明为抽象类
抽象方法充当占位的作用,具体的实现放在子类中
抽象类不能创建对象,但可以创建其子类的对象
受保护类
关键字protected
超类中的某些方法或者某个域允许被子类访问
访问修饰符总结
- private——仅对本类可见
- public——对所有类可见
- protected——对本包和所有子类可见
- 无修饰符——对本包可见
Object:所有类的超类
可以使用Object类型变量引用任何类型的对象,但是这个变量不能使用其中的域和方法,必须通过类型转换后才能
Java中只有基本类型不是对象,所有数组类型都扩展了Object类
equals方法
Object类的equals方法用于检测两个对象是否具有相同的引用,通常没有意义
相等测试与继承
equals方法应具有的特性:
- 自反性:任何非空引用想,x.equals(x)为true
- 对称性:任何引用xy,x.equals(y)和y.equals(x)相等
- 传递性:任何引用xyz,若x.equals(y)为true,y.equals(z)为true,则x.equals(z)为true
- 一致性:如果xy引用的对象没有变化,则反复调用x.equals(y)应返回同样的结果
- 对于任意非空引用x,x.equals(null)为false
常见错误:
没有覆盖Object类中的equals方法,显式参数类型必须是Object
@Override public boolean equals(Object other)
可以在方法前添加标记,如果没有重写,则会报错
hashCode方法
散列码(hash code)是由对象导出的一个整数值。
如果重定义equals方法,就必须重新定义hashCode方法,以便用户可以将对象插入散列表中
toString方法
用于返回对象值的字符串
最好通过getClass().getName()
获得类名的字符串,而不要将类名硬加到toString方法中,好处是,它的子类可以通过super来调用这个方法
Object类定义了toString方法,默认打印对象所属的类名和散列码
注:toString方法是一种非常有用的调试工具,每个类都应该增加toString方法
泛型数组列表
数组:
Java中的数组允许在运行时确定数组大小
int actualSize = ...;
Employee[] staff = new Employee[actualSize];
ArrayList:
是一个采用类型参数的泛型类,具有自动调节数组容量的功能
ArrayList<Employee> staff = new ArrayList<Employee>();
ArrayList<Employee> staff = new ArrayList<>();//new后的类型可省略,检查前面的类型,然后放在后面
使用add方法添加元素到数组列表
staff.add(new Employee("Harry Hacker",...));
如果add调用add且内部数组已满,则数组列表自动创建一个更大的数组,并把所有的对象拷到较大的数组中
如果已经清楚数组可能存储的元素数量,则可以用ensureCapacity方法
staff.ensureCapacity(100);
可以在初始化时给出列表容量,并不是只能放这么多的元素,而是超出容量后会重新分配空间
ArrayList<Employee> staff = new ArrayList<>(100);
size方法返回数组列表包含的实际元素数量,相当于数组的length方法
staff.size()
一旦能确定数组列表大小不变,则使用trimToSize方法整理列表,但是之后再添加就要花时间移动存储块
访问数组列表元素
staff.set(i,harry);//相当于a[i]=harry;
Employuee e = staff.get(i);//相当于Employee e = a[i];
插入元素
int n = staff.size()/2;
staff.add(n,e);//位于n之后的元素都要向后移动一个位置,如果超出容量,则要重新分配空间
可以使用for-each来遍历数组列表
for(Employee e : staff)
{
...
}
//等价于
for(int i=0;i<staff.size();i++){
Employee e = staff.get(i);
}
类型化与原始数组列表的兼容性
public void update(ArrayList list){...}
public ArrayList find(String query){...}
可以将类型化的数组列表传给update,如ArrayList<Employee>
将原始类型的数组列表传给类型化的数组列表得到警告,但是不必做什么,在确定了转换没有问题后,可以用@SuppressWarnings("unchecked")
来标注这个类型转换
对象包装器与自动装箱
将基本类型int放入ArrayList中,需要先包装成对象:Integer
ArrayList<Integer> list = new ArrayList<>();
对象包装器类是不可变的,一旦构造了包装器,则不能改变包装在其中的值
对象包装器是final,不能创建子类
自动装箱:
自动将基本数据类型包装成相应的对象,放入数组列表中
ArrayList<Integer> list = new ArrayList<>();
list.add(3);//自动包装成Integer
自动拆箱:
int n = list.get(i);
算数表达式中也能自动装箱自动拆箱
Integer n = 3;
n++;
如果混合使用Integer和Double类型,Integer会自动拆箱,提升为Double,在装箱为Double
还能将某些基本方法(静态方法)放置在包装器中,例如,将一个数字字符串转换成数值
参数数量可变的方法
printf方法的定义:
public PrintStream printf(String fmt,Object... args){ return formot(fmt,args); }
其中省略号表明该方法可以接收任意数量的对象,相当于Object[] args