[TOC]
面向对象有三大特征:
其中封装我们之前已经讲过了,利用class,我们将属性和操作封装了起来。
现在我们将要讲解继承
想一想,之前我们已经写好了一个计算三角形面积的类,
假如有一天,我们要写一个计算直角三角形的类,或者要写一个计算圆形、正方形的类,难道我们要重新再一次次把这些类写出来吗?
这时候我们就需要——继承
现在让我们来看继承的第一个例子:
类继承的格式如下:
修饰符 class 子类名 extends 父类名 {
类成员
}
下面来看一个例子:
这个例子的目录结构为:
src
|--People.java
|--Student.java
|--Main.java
其中,People.java内容如下:
public class People {
//人的一般属性
private String pId; //身份证号
private String name;
private boolean gender;
private int age;
public People() {
}
public People(String pId, String name, boolean gender, int age) {
this.pId = pId;
this.name = name;
this.gender = gender;
this.age = age;
}
public String getpId() {
return pId;
}
public void setpId(String pId) {
this.pId = pId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isGender() {
return gender;
}
public void setGender(boolean gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void showGender() {
if(this.gender) {
System.out.println("I am a boy");
}
else {
System.out.println("I am a girl");
}
}
}
而Student.java的内容如下:
public class Student extends People{
//学生特有的属性
private String stuId;
private String school;
private String major; //专业
public Student(String pId, String name, boolean gender, int age,
String stuId, String school, String major) {
super(pId, name, gender, age); //调用父类的构造器
this.stuId = stuId;
this.school = school;
this.major = major;
}
public String getStuId() {
return stuId;
}
public void setStuId(String stuId) {
this.stuId = stuId;
}
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
public String getMajor() {
return major;
}
public void setMajor(String major) {
this.major = major;
}
public void study() {
System.out.println("I majored in " + this.major);
}
}
super引用的还是当前实例,但是属性和方法绑定到父类的定义,有两种用途:
继承机制对变量的处理,会造成严重的混淆。下例中,父类和子类定义了同名但类型不同的变量,如果实例化子类对象,外界会访问到子类的列表,但是对象内有父类定义的和子类定义的两个列表,如果直接继承父类的添加元素方法,就会导致来自父类的列表和来自子类的列表,不同步。
// 省略构造函数等部分
class Father {
List<String> hobby;
public void putHobby() {
System.out.println(hobby);
}
public void setHobby(String hobby) {
hobby.append(hobby)
}
}
enum Hobbies{
public static Hobbies getHobby(String str) {/*... */}
// ...
}
class Child {
List<Hobbies> hobby;
@Override public void putHobby() {
System.out.println(hobby);
}
@Override public void setHobby(String hobby) {
hobby.append(Hobbies.getHobby(hobby))
}
}
为了避免这一危害,现在的类型结构中,尽可能地会避免继承,而改用接口和JavaBean来实现。
当我们使用new语句来创建子类的对象的时候,在默认情况下,会先执行父类的构造器,然后再执行子类的构造器。
在构造函数的第一条语句,可以使用super
关键字来调用父类的构造函数,或者使用this
关键字来调用本类构造函数的其他重载。如果第一条语句不是上述两种情况,就会调用父类的无参构造函数。无论如何,子类的构造函数中总有一个会调用父类的构造函数。
class Grandpa {
public Grandpa() {
System.out.println("grandpa");
}
}
class Father extends Grandpa {
public Father() {
System.out.println("father");
}
}
class Son extends Father {
public Son() {
System.out.println("son");
}
}
public class Main {
public static void main(String[] args) {
Son s = new Son();
}
}
这里大家应该已经了解,可以使用两种方式定义类
如果一个类声明的时候使用了public class
进行了声明,则类名称必须与文件名称完全一致
在一个*.java文件中,只能有单个public class
声明,但是允许有多个class的声明
继承并不是简单的拥有所有父类的成员。实际上,子类能享用的父类的成员存在严格的限制,权限符限制了它们可以继承哪些东西。
权限 | 本类 | 同包 | 不同包的子类 |
---|---|---|---|
public | 允许 | 允许 | 允许 |
protected | 允许 | 允许 | 允许 |
默认 | 允许 | 允许 | 不允许 |
private | 允许 | 不允许 | 不允许 |
public和默认修饰符可以修饰类
protected和private只能修饰成员
目录结构
src
|--a
|---Father
|---Son
|---Other
Father的内容:
package a;
class Father { //默认权限类
int x; //默认权限成员
protected int y; //保护成员
public Father(){};
public Father(int x, int y) {
this.x = x;
this.y = y;
}
}
Son的内容:
package a;
public class Son extends Father{ //继承默认权限类
public Son(){};
public Son(int x, int y) {
this.x = x; //this.x是继承A默认权限的成员x
this.y = y; //this.y是继承A的保护成员y
}
public void print() {
System.out.println("x = " + x);
System.out.println("y = " + y);
}
}
Other的内容:
package a;
public class Other {
public static void main(String[] args) {
//可以访问同包的友类
Father f = new Father(1,2);
//可以访问同包中某类的默认成员
System.out.println(f.x);
//可以访问同包中某类的保护成员
System.out.println(f.y);
//同一包中,Son继承Father的默认成员和保护成员
Son s = new Son(1,2);
s.print(); //测试继承权限
}
}
当父类与子类在不同包的时候,
父类的protected成员可以被子类继承,而默认权限的成员不能被子类继承
可以这样理解:只要能能证明儿子S是爸爸F的儿子,即便S是私生子(不再同一个包里),他就有权继承受法律保护的财产(受protected保护的变量)
但是私生子得到的还是不如和父亲一直生活在一起的儿子(在同一个包里面的,还能继承默认权限的财产)
当继承关系越来越复杂时,我们可能需要对某些对象进行“亲子鉴定”。
这时候就需要用到 instanceof
instanceof运算符是Java语言特有的运算符,用来在运行时判定某对象是否为特定类或该特定类子类的一个实例
class A{}
class B extends A{}
public class Other{
public static void main(String[] args) {
A a = null;
B b = null;
boolean flag;
flag = a instanceof A;
System.out.println("a instanceof A: " + flag);
flag = b instanceof B;
System.out.println("b instanceof B: " + flag);
a = new B();
b = new B();
flag = a instanceof A;
System.out.println("a instanceof A: " + flag);
flag = b instanceof B;
System.out.println("b instanceof B: " + flag);
}
}
一般而言,子类以父类为基础构建类体,增加新的属性和方法。特别的,在子类中声明与父类同名的类成员即可屏蔽父类中的成员。
对于属性的屏蔽称为属性隐藏。
对于方法的屏蔽称为方法覆盖或者方法重写(Override)
@Override是注解,表示该方法必须覆盖父类的同签名方法。这个注解可以省略,有如下好处:
由于子类中有方法覆盖,假如我们不想让方法被覆盖应该怎么办呢?这时候就需要用到final
final在基础语法的时候我们讲到,它是常量的写法
而在面向对象中,一般会把静态常量定义为public static final
final修饰的方法称为最终方法。不允许子类重写父类的final方法。
使用final有两个优点:
此外,类的private方法被隐式地指定为是final
值得一提的是,如果一个类被final修饰,它不能被继承。