Java 是个纯粹的面向对象语言,没有过程式的写法,所有操作都是基于对象。面向对象的设计可以更好的解决系统维护、扩展、和代码重用的需求,代码过多后过程式的逻辑组织会变得异常复杂,所以就发展出了面向对象式编程。而面向对象的基本特性就是封装、继承、与多态。
一、继承
extends 关键字表示继承某个已存在的类,已存在的类称为“超类”、“基类”、或“父类”,而新继承的类称为“子类”、“派生类”。子类可以方法父类中的所有非 private 域,也即子类通过继承获得了父类具有的特性和能力。
定义一个 Person 父类:
public class Person{private String name;private int age;public Person(String name, int age){this.name = name;this.age = age;}public String getName(){return name;}public int getAge(){return age;}}
定义一个 Student 子类:
public class Student extends Person{private String school;// 构造函数public Student(String name, int age, String school){super(name, age); // 调用父类构造函数this.school = school;}public String getSchool() {return school;}}
写个 Main 类测试下:
public class Main{public static void main(String[] args){Student s = new Student("小明", 12, "xx小学");System.out.println(s.getName()); // 输出:小明System.out.println(s.getAge()); // 输出:12System.out.println(s.getSchool()); // 输出:xx小学}}
从输出可以看出,子类 Student 并没有 getName、getAge 方法,但通过继承获得了父类的能力。在上一章我们知道 this、super 两个关键字,this 访问自身,super 访问父类,这里调用父类的构造函数使用 super。
继承可以获得父类的能力,但有时候获得的能力并不太理想,这时可以在子类中重写父类的方法,使其符合子类的需求。同样 Student 类:
public class Student extends Person{private String school; // 构造函数public Student(String name, int age, String school){super(name, age); // 调用父类构造函数this.school = school;}public String getSchool(){return school;}// 重写 getName 方法public String getName(){return "我是一名学生,我叫" + super.getName();}}
通过重写父类中的方法,使子类具有不同的能力。
二、继承层级
上面的例子只讲了一层继承关系,当然也可以进行多次继承。A 继承 B,B 继承 C,C 继承 D 理论上可以一直继续,但是肯定不建议嵌套太多继承关系,这会让程序变得复杂且性能降低。
Java 可以有多个继承层次,一个类也可以有多条继承链路,M 继承 A,N 继承 A。但是 Java 不支持多继承,即 M 同时继承 A 又 继承 B,Java 不允许多继承降低了复杂度,如果要实现多继承,可以通过接口 interface 形式实现。所以Java 类之间只有单一继承关系,没有多继承。问什么这样设计,要问语言设计者。
三、多态
我们通过类实现了封装,又通过继承获得了父类的功能,减少了重复代码,最后我们通过扩展子类,或者重写子类中的方法实现了多态。就好比一个母亲生了多个孩子,每个孩子性格差异各不相同。
子类生成的对象都属于父类类型。如:
注意看,所有子类实例都可以赋值给父类变量,但仅能调用父类定义的方法。因为 JVM 会把 s2 当作 Person 类实例看待,父类中不存在的则不能访问,又因为 s 和 s2 实际指向的是同一对象,所以调用 getName 方法还是子类的方法。
子类实例引用可以赋值给父类变量,但父类引用不能赋值给子类变量。因为这是一个包含关系,父类包含子类(人 Person 包含学生 Student),反之则不成立。
如何判断一个对象是某个类生成的,或通过某个类构造的,可以用 instanceof 语句。instanceof 可以判断某个对象是通过那个类(或其父类)构造的,如:
四、接口
接口不是类,它是对类的一组需求描述和行为规范。为什么是需求描述,因为接口只是定义行为方法,并不实现方法,而继承它的类规定必须实现接口定义的方法。类通过实现接口定义的方法而受接口的约束,所以说接口是类的需求描述和行为规范。接口和类是两种不同的数据类型。
接口使用 interface 关键字定义,格式如下:
public interface InterfaceName{public type funcName(type args, ...); // type 是数据类型}
继承类使用 extends,而继承接口使用 implements。
定义一个 Calculate 接口:
public interface Calculate{// 定义一个加法运算public int add(int i, int j);}
声明一个类 Calculator 继承 Calculate:
public class Calculator implements Calculate{// 实现 add 方法public int add(int i, int j){return i+j;}}
从上文可知 Java 中类不能实现多继承,但是这里类可以通过接口实现多继承,多个接口之间用 ',' 分割,继承的类必须实现所有继承接口中定义的方法。如:
public class Calculator implements Calculate1,Calculate2,Calculate3{... // 实现 Calculate1,Calculate2,Calculate3 中定义的所有方法}
五、接口与抽象类
上一章中我们还知道类可以被定义成抽象的,抽象类中可以定义属性和方法,也可以定义抽象方法。
那么什么时候使用抽象类,什么时候使用接口呢?为了准确使用我们需要需要了解下抽象类和接口的区别。
抽象类:将一组共同的行为组合在一起,作为一个抽象类。在抽象类中可以定义抽象方法,也可以实现方法。抽象类属于类不能多继承,可以用 instanceof 判断从属关系。
接口:对类的一组需求描述和行为规范。接口不能实现具体方法,但可以实现多继承。
那什么时候使用抽象类,什么时候使用接口呢?
1.如果抽象出来的一组行为,和子类具有从属关系(is-a),那么就使用抽象类。如果没有从属关系,只是希望具备某个能力,那么使用接口。
2.如果需求变更会导致父类的功能扩展,那么使用抽象类修改的代码会少些,而使用接口所有子类都需要调整。
如何使用抽象类或者接口,有时需要反复的代码练习。