对象继承和“is a”关系
就像“万物皆对象”口头禅说的一样,Java中类层次的树形结构,跟日常我们理解和描述大千世界是一样的。我们会把动物,先划分为脊椎动物、节肢动物和软体动物。然后在脊椎动物里面又分哺乳动物,哺乳动物又可以细分猫和狗。
对于动物的描述,从大到小,由宽泛到具体。大的更一般的类看作是父类。包含在其中的,具体的类是子类。
子类与父类的关系是:子类对象“is kind of/is a” 父类对象。这也就是面向对象的继承特性,通过继承,可以增加软件的可复用性,使得代码在类之间共享。
注意:“is a”与“has a”关系是不同的。比如狗,是动物的一种,也可以说动物中包含了狗。但发动机属于汽车,却不能说发动机是汽车的一种。
extends关键字
先统一下名词:继承/派生是面向对象的特性之一。子类继承自父类,子类也称为派生类,父类也称为基类或者超类。
比如我们现在要实现一个Employee(员工)类,一个Manager(管理)类。普通员工拥有员工编号、姓名、职称、职级、生日等属性,管理则是特别的员工,额外拥有管理组的属性,以及管理的若干个员工。
- 员工类
public class Employee {
private String name, jobTitle;
private Date birthday;
private int jobGrade;
}
- 管理类
public class Manager extends Employee{
private String group;
private Employee[] subordinates;
}
由于Manager继承自Employee,也就拥有了普通员工的所有成员。增加了可维护性,子类是更个性化的存在,子类的改动也不会牵动到父类。
Object类
Object类是Java中所有类的直接或间接父类,处于类层次的最高点。
Object类提供了比如getClass()
、toString()
以及equals()
等基础方法。
关于判定对象是否相等有两种方式,一种是==运算符,判定对象引用是否相等;另一种是使用equals()
方法,判定对象的类型,对象属性值是否相等。
单继承特性
上面提到,子类用extends关键字来继承父类,那能不能继承多个父类呢?答案是不行的。
由于多重继承是网状的关系,子类和父类都是多对多的关系,而子类的多个父类一旦有同名的属性或者方法,很容易造成子类实例的混乱。而单继承关系则是树状结构,如果需要多继承能力则可以通过接口来实现。
注意:子类可以继承所能继承的方法和成员变量,但不能继承构造函数。
对象转型
子类和父类的转型规则
Employee e = new Manager()
,将子类的实例赋值给父类变量,这称为“对象转型(Casting)”。
变量可以赋值本类的实例,也可以只想子类的实例,这就是对象多态性的体现,但反过来不行。
对象引用转型的规则:
- 沿着类层次向“上”转型总是合法的。比如Manager子类,转成Employee父类;
- 向“下”转型,只能是沿着继承链的父类转子类,其他类是不允许的,且要使用显式转换;
instanceof
这时可能你会想了,变量e可以赋值Employee的实例对象,可以赋值Manager子类实例对象,我怎么知道具体是什么对象呢?我要知道是子类对象,才能访问它特有的属性和方法啊?答案就是instanceof
运算符
System.out.println(e instanceof Manager); // true
Override(方法覆盖)
当父类的方法不能满足需求时,子类可以修改父类提供的方法,这称为方法覆盖/方法重写(Override)。这也是面向对象多态的特性。
Override的规则
- 子类重写父类的方法,要使用相同的方法名以及参数列表。
- 同时子类方法不能比父类方法的访问权限更严格,比如,弗雷方法的访问权限是public,那么子类方法则不能是private的修饰。还有。
- 子类方法抛出的异常也不能比原方法多。
Override与Overload区别
如果方法名相同,但参数列表不同,则是Overload(方法重载)。Overload是发生在同一个类,而Override则发生在父类子类中。
super
如果子类想使用父类被隐藏的方法,可以通过super指针访问。调用super()
可显式的调用父类的构造函数。
理解多态
多态,是面向对象语言的重要特性。Java规定了,多态的情况下,执行的是对象真实的类型(运行时类型)的方法,而不是声明类型(静态类型)的方法。
这里解释一下静态类型和动态类型:
- 对象声明时称为静态类型,或者说引用类型。我们编写的代码编译时就确定下来的类型;
- 而代码在运行过程中,指向的对象类型,称为动态类型,这是真正的类型;
final终极类、方法和属性
关键字final
,既可以修饰类,也可以修饰类当中的属性和方法,被final修饰表示不能改变。final类不能被继承,不再拥有子类。final方法不能被重写;final变量,值不能被改变。
怎么会有要求不能被继承的类呢?有的,像Java.lang.String
类就是如此,类的结构和功能都很完美了,用就完事了,你不用再扩展和补充。这样做的目的,是为了保证程序中,如果有指向String类的引用,那它就是真的String类型,而不是被改过的String子类。
final方法也是如此,同时final方法还利于优化,提高编译运行的效率。
通过final定义常量,应该是Java程序员最熟悉的操作了。值得注意的是,如果该常量是引用类型,那么不能再篡改引用地址,但可以对原对象的属性值的修改,或者进行扩展属性。
Abstract抽象类
设计程序的时候,需要创建某个类仅仅定义基本行为和属性,但又不宜在这个类就进行具体的实现,而是希望等子类根据实际情况再去实现这些方法。这种定义了方法,但没有方法的具体实现的类,称为抽象类,通过abstract关键字定义。抽象类中通过abstract定义的方法,称为抽象方法。
抽象类是用来进行继承的,所以不能直接实例化。
抽象类可以包含常规类能包含的任何成员方法,包括构造函数,通常也会包含抽象方法,但反过来常规类中不能定义抽象方法。
- 抽象类Storage
abstract class Storage {
int objectNum = 0; // 存储了多少个对象
Object storage[] = new Object[10]; // 存储容器
abstract void put(Object obj); // 存入方法
abstract Object get(); // 获取方法
}
- 常规类Stack
// 实现栈结构
public class Stack extends Storage {
private int point = 0; // 指针
@Override
void put(Object obj) {
storage[point++] = obj;
objectNum++;
}
@Override
Object get() {
objectNum--;
return storage[point];
}
}
- 常规类Queue
// 实现队列结构
public class Queue extends Storage{
private int top = 0, bottom = 0;
@Override
void put(Object obj) {
storage[top++] = obj;
objectNum++;
}
@Override
Object get() {
objectNum--;
return storage[bottom++];
}
}
Interface接口
接口,是特殊的抽象类,可以看作是更纯粹的抽象类。它只规定了类的基本形式,接口中可以有成员变量,但系统会默认加上final和static关键字来修饰,所以必须对成员变量赋初始值,接口内的方法必须是抽象方法。
接口可以实现多继承特性,一个子类只能继承一个父类,但可以实现多个接口,使得代码具有更清晰的结构。
- CharStorage接口
public interface CharStorage {
void put(char c);
char get();
}
- CharStack类
public class CharStack implements CharStorage{
private char member[] = new char[10];
private int point = 0;
@Override
public void put(char c) {
member[point] = c;
point++;
}
@Override
public char get() {
point--;
return member[point];
}
}