抽象类
如果自下而上在类的继承层次结构中上移,位于上层的类更具有通用性,甚至可能更加抽象。从某种角度看,父类更加通用,人们只将它作为派生其他类的基类,而不作为想使用的特定的实例类。
为什么要花费精力进行抽象?因为每个人都有一些诸如姓名这样的属性。学生与雇员都有姓名属性,因此可以将getName方法放置在位于继承关系较高层次的通用父类中。
现在,我们增加一个getDescription方法,它可以返回一个人的简短描述。我们可以看出,在Student类和Employee类中实现这个方法很容易,但在Person中,除了姓名之外,其他的一无所知。这时,我们就可以使用abstract关键字。
public abstract String getDescription();
包含一个或者多个抽象方法的类本身必须被声明为抽象的。
public abstract class Person(){
...
public abstract String getDescription();
...
}
除了抽象方法之外,抽象类还可以包含具体数据和具体方法。例如:
public abstract class Person(){
private String name;
public Person(String name){
this.name=name;
}
public abstract String getDescription();
public String getName(){
return name;
}
}
通常,在抽象类中不能包含具体方法。建议尽量将通用的域和方法放在父类中。
抽象方法充当着占位的角色,他们的具体实现在子类中。扩展抽象类有两种选择。一种是在抽象类中定义部分抽象类方法或不定义抽象类方法,这样就必须将子类也标记为抽象类;另一种是定义全部的抽象方法,这样一来,子类就不是抽象的了。
类即使不含抽象方法,也可以将类声明为抽象类。
抽象类不能被实例化。也就是说,如果将一个类声明为abstract,就不能创建这个类的对象,但可以创建一个具体子类的对象。
需要注意的是,可以定义一个抽象类的对象变量,但是它只能引用非抽象子类的对象。例如:
Person p=new Student("aaa","bbb");
这里的p是一个抽象类Person 的变量,Person 引用了一个非抽象子类Student的实例。
我们以一个例子看一下:
public class PersonTest {
public static void main(String[] args) {
Person[] p = new Person[2];
p[0]=new Employee("aaa",1000,1998,5,1);
p[1]=new Student("bbb","ppp");
for (Person person : p) {
System.out.println(person.getName()+","+ person.getDescription());
}
}
}
public abstract class Person {
public abstract String getDescription();
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class Employee extends Person {
private double salary;
private LocalDate hireday;
public Employee(String name, double salary, int year,int month,int day) {
super(name);
this.salary = salary;
this.hireday = LocalDate.of(year,month,day);
}
public void raiseSalary(double bypercent){
double raise=salary*bypercent/100;
salary+=raise;
}
@Override
public String getDescription() {
return String.format("an employee with a salary of $%.2f",salary);
}
public double getSalary() {
return salary;
}
public LocalDate getHireday() {
return hireday;
}
}
public class Student extends Person {
private String major;
public Student(String name, String major) {
super(name);
this.major = major;
}
@Override
public String getDescription() {
return "a student majoring in "+major;
}
}
受保护访问
有些时候,我们希望父类中某些方法允许被子类访问,或允许子类的方法访问父类的某个域。为此,需要将执行方法或域声明为protected。例如,如果将父类Employee中的hireDay声明为protected,而不是私有的,Manager中的方法就可以访问它。
不过,Manager类中的方法只能够访问Manager对象中的hireDay域,而不能访问其他Employee对象中的这个域。这种限制有利于避免滥用受保护机制,使得子类只能获得访问受保护域的权利。
在实际应用中,要谨慎使用protected属性。因为这违背了oop提倡的数据封装原则。受保护的方法更具有实际意义。如果需要限制每个方法的使用,就可以将它声明为protected。这表明子类得到信任,可以正确地使用这个方法,而其他类不行。
我们归纳一下Java用于控制可见性的4个访问修饰符:
- 仅对本类可见——private
- 对所以类可见——public
- 对本包和所有子类可见——protect
- 对本包可见——默认,不需要修饰符。