- 阻止继承:final类和方法
- 强制类型转换
- 抽象类
有时候,我们可能希望阻止人们利用某个类定义子类。不允许扩展的类叫做final类。如果在定义类的时候使用了final修饰符就表示这个类是final类。例如,假设希望阻止人们派生Executive类的子类,就可以在声明这个类的时候使用修饰符。 声明格式如下所示:
public final class Executive extends Manager
{
....
}
类中的某个特定方法也可以被声明为final。如果这样做,子类就不能覆盖这个方法。例如:
public class Employee
{
...
public final String getName()
{
return name;
}
}
将方法或者类声明为final类的主要原因是:确保他们在子类中不会改变语义。例如Calender类与getTime和setTime方法都声明final。这表明Calender类的设计者负责实现Date类与日历状态之间的转换,而不允许子类来添乱。同样的,String类也是final类,这是意味着不允许任何人定义String的子类。换言之,如果有一个String引用,它引用的一定是一个String对象,而不可能是其他类的对象。
强制类型转换我们知道,将一个类型强制转化为另一个类型的过程叫做强制类型转化。Java语言为强制类型转换提供了一种特殊表示法:
double x = 3.405; int nx = int(x);
将表达式x的值强行转化为整数类型,去掉了小数部分。
正像有时候需要将浮点数转化为整数一样,有时候也可能将某个类的对象引用转化成另一个类的对像引用。要完成对象引用的强制类型转换,转换语法与数值表达式的强制类型转换类似,仅仅需要将目标对象用圆括号括起来,并放置在需要转换的对象引用之前就可以了。例如:
Manager boss = (Manager) staff[0];
进行强制类型转换的唯一原因是:要在暂时忽视对象的实际类型之后使用对象的全部功能。例如在managerTest类中,由于某些元素是普通员工,所以staff数组必须是Employee对象的数组。我们需要将数组中引用经理的元素复原成一个Manager对象,以便能够访问新增加的所有变量。
我们都知道Java中,每个对象都必须有一个类型。类型描述了这个变量所引用的以及能够引用的对象类型。例如,staff[i]引用一个Employee对象。
将一个值存入变量时,编译器将检查你是否承诺过多。如果将一个子类的引用赋给一个超类变量,编译器是允许的。但将一个超类变量赋予子类时,就承诺过多了。必须进行强制类型转换,这样才能通过检查。
如果试图在继承链上进行向下的强制类型转换,并“谎报”对象包含的内容,会发生什么呢?
Manager boss = (Manager) staff[1];//错误
运行这个程序时,Java运行时系统将注意到你的承诺不符,并产生一个ClassCastException异常。如果没有捕获这个异常,那么程序就会终止。因此,应该养成这样一个良好的程序设计习惯:要进行强制转换之前,先查看是否能成功转换。为此只需要使用instanceof操作符就能实现。例如:
if (staff[1] instanceof Manager)
{
boss = (Manager) staff[1];
...
}
最后,如果这个类型转换不肯成功,编译器就不会让你完成这个转换。
综上所述:
- 只能在继承层次内进行强制类型转换。
- 在将超类强制转化为子类之前,应该使用instanceof进行检查。
实际上,通过强制转化来转化对象类型不是一个好方法。在我们的程序中,大多数情况不需要将Employee对象强制转化为Manager对象,而两个类的对象都能正确调用getSalary方法,这是因为实现多态性的动态绑定机制能够自动的找到正确的方法。
只有在使用Manager类中的特有方法才需要进行强制转换。但是重新设计超类方法才是合适的方法
如果自下而上在类的继承层次结构中上移,位于更上层的类更具有一般性,可能更加抽象。从某种角度来看,祖先类更具有一般性,人们只将它作为派生其他类的基类,而不是用来制造你想实用的特例的对象。例如考虑拓展Employee类的层次结构。员工是一个人,学生也是一个人。下面扩展我们的类层次结构来加入人类Person和类Student。
为什么要那么麻烦提供这样一个高层次的抽象呢?每个人都有一些属性,例如姓名。学生跟员工都有姓名属性,因此通过引入一个公共的超类,我们就可以把getName方法放在继承层次结构中更高的一层。
现在,在增加一个getDescription方法,它可以返回一个人的简短描述。例如:
一个员工,工资5000
一个学生,计算机专业
在Employee类和Student类中实现这个方法很容易。但是在Person类中应该提供什么内容呢?除了姓名之外,Person类对这个人一无所知。当然可以让Person.getDescription()返回一个空字符串。不过还有一个更好的方法,就是使用abstract关键字,这样就完全不需要实现这个方法了。
为了提高程序的清晰度,包含一个或多个抽象方法的类本身必须被声明为抽象的。
pubilc abstract class Person
{
...
public abstract String getDescription();
}
除了抽象方法之外,抽象类还可以包含字段和具体方法。例如,Person类还保存着一个人的姓名,另外有一个返回姓名的具体方法。
public abstract class Person
{
private String name;
public Person(String name)
{
this.name=name;
}
public abstract String getDescription();
public String getName()
{
return name;
}
}
抽象方法充当着占位方法的角色,它们在子类中的具体实现。扩展抽象类可以有两种选择。一种是在子类中保留抽象类中的部分或所有抽象方法仍未定义,这样就必须将子类也定义为抽象类;另一种是定义所有抽象类中的方法,这样一来,子类就不是抽象类了。
例如,通过扩展抽象类Person类,并实现getDescription方法来定义Student类。由于Student类中不在含有抽象方法,所以不需要声明这个类为抽象类。
即使不含抽象方法,也可以将类声明为抽象类。
抽象类不能实例化。也就是说,如果将一个类声明为abstract,就不能创造这个类的对象。例如表达式new Person("Vince vu") 是错误的,但可以创建一个具体子类的对象。
需要注意的是,可以定义一个抽象类的对象变量,但是这样一个变量只能引用非抽象子类的对象。例如:
Person p = new Student("Vince vu","Economics");
这里的p是一个抽象类型是Person的变量,他应用了一个非抽象子类Student的实例。
下面定义一个扩展抽象类Person的具体子类Student:
public class Student extends Person
{
private String major;
public Student (String name,String major)
{
super(name);
this.major=major;
}
public String getDescription()
{
return "a student majoring in"+ major;
}
}
Student类定义了getDescription()方法。因此在Student类中全部的方法都是具体的,这个类不再是抽象类。
程序清单4的程序定义了抽象超类Person(见程序清单5)和两个具体子类Employee类(程序清单6)和Student类(程序清单7)
在Java中抽象是一个重要概念。接口会有更多的抽象方法,之后会详细讲解。
程序清单4
package 抽象继承类;
public class PersonTest {
public static void main(String[] args) {
var people = new Person[2];
people[0] = new Employee( "Harry Hacker", 50000, 1990, 10, 1);
people[1] = new Student ("Water Corleone","computer science");
for(Person p:people){
System.out.println(p.getName()+","+p.getDescription());
}
}
}
程序清单5
package 抽象继承类;
public abstract class Person {
private String name;
public Person(String name)
{
this.name=name;
}
public abstract String getDescription();
public String getName()
{
return name;
}
}
程序清单6
package 抽象继承类;
import java.time.*;
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;
hireDay = LocalDate.of(year,month,day);
}
public double getSalary()
{
return salary;
}
public LocalDate getHireDay()
{
return hireDay;
}
public String getDescription() {
return String.format("an employee with a salary of $%.2f",salary);
}
public void raiseSalary(double byPercent){
double raise =salary*byPercent/100;
salary+= raise;
}
}
程序清单7
package 抽象继承类;
public class Student extends Person
{
private String major;
public Student (String name,String major)
{
super(name);
this.major=major;
}
public String getDescription()
{
return "a student majoring in"+ major;
}
}



