在之前的文章我们从建立一个类开始,讲述了创建对象的有关知识,(点此进入了解《Java——从建立一个类开始》)
以及关于Java面向对象第一个特征封装(点此了解 《封装》 )
在这篇文章中,我们讲述关于继承的相关知识、子类与父类的种种联系以及子类的微观实现。
(关于子类构造方法以及super关键字的相关知识将会在下一文讲述)
继承
继承的概述
继承的概念
子类继承父类的特征和行为,使得子类具有父类相同的属性。
代码格式如下,
class 父类 {
}class 子类 extends 父类 {
}
为什么要继承
在不同类中会有共同的属性或者方法,我们可以将这些共同的属性和方法放在同一个父类,然后再派生出其他子类来共享。
优点
避免重复,易于维护,易于理解。
缺点:
耦合性,即父类发生改变,子类也会随即发生改变。
- 继承类型:单继承、多重继承 和 多继承一 ,不支持 一继承多。
继承的微观
子类继承父类,除了构造方法的部分,剩下的都会继承。
但是私有的属性,无法在子类中直接访问,但是可以通过间接的手段来访问(可继承,但无法访问)
继承类型
java不支持多继承。
我们写的类是现实事物的抽象,而我们真正在公司中所遇到的项目往往业务比较复杂,也会涉及到一系列复杂的概念,都需要我们用代码表示,所以在实际项目中写的类比较多,类之间的关系也十分复杂 。
但是即使如此,我们并不希望类之间的继承层次太复杂,一般我们不希望超出三层的继承关系,如果继承层数过多,就考虑对代码进行重构了。
如果想从语法上限制继承,就可以使用final关键字
Object类
若java中的某个类没有显式的继承任何类,则默认它继承Objec类。
Object
类是Java语言提供的根类(老祖宗类),也就是说所有对象都会有Object
类型中所有的特征。
访问子类对象
遵循就近原则,有则访问自己的,没有则访问父类的。
访问子类对象的成员变量
- 如果访问的成员变量,子类中没有,则访问从父类继承下来的,
倘若父类也没有,则报错。 - 同名:如果访问的成员变量与继承父类中的成员变量同名,则优先访问自己的。
即遵循就近原则。
示例代码如下,
class A {static int E = 10;static int I = 8;
}
class B extends A{static int E = 20;
}
public class Main {public static void main(String[] args) {B b = new B();System.out.println(b.E);System.out.println(b.I);}
}//输出20 和 8
这段代码定义了一个A类,其中有两个被static修饰的变量E和I值为10和8,
以及继承A类的B类,B类中也有类变量E值为20,
后通过公共Main类中的main函数来,来创建一个B类对象,并访问其中的E和I的值。
结果表明E是B类中的变量,符合第二种情况,I是A类中的变量,符合第一情况。
(这里用类名.类变量名访问变量值在《Java——从建立一个类开始》有提及)
访问子类对象的成员方法
- 方法名不同时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错
- 方法名相同时,若父类和子类方法中的参数列表不同,根据调用方法时传递的参数选择合适的方法进行访问,如果没有则报错。
- 通过子类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表、返回值都相同,则遵循就近原则,访问子类中的方法,不会访问父类中的方法。
class A {void beA() {System.out.println("This is A");}void difference(int number) {System.out.println("A is not number");}void be() {System.out.println("This is A");}
}
class B extends A{void beB() {System.out.println("This is B");}void difference(char letter) {if(letter != 'B') System.out.println("B is not " + letter);}void be() {System.out.println("This is B");}
}
public class Main {public static void main(String[] args) {B b = new B();b.beA();b.beB();b.difference(5);b.difference('C');b.be();}
}
/*输出:This is AThis is BA is not numberB is not CThis is B*/
这段代码定义了一个A类和一个继承A类的B类,
其中子类和父类分别有同名同参数的be()函数,不同名同参数的beA()函数和beB()函数,以及同名不同参数的difference()函数,
分别对应着上文第一二三种情况。
后通过公共Main类中的main函数来,来创建一个B类对象,并依次调用B类对象中的上述函数
输出依次表明上文的结果。
类的初始化
我们将一个类分为静态代码块和实例代码块两个部分。
静态代码块和实例代码块都是在Java中用来初始化类或对象的特殊代码块。
静态代码块
静态代码块是使用关键字static
定义的代码块,它在类加载的时候执行,只会执行一次。
静态代码块通常用于初始化静态变量或执行一些静态操作。
实例代码块
实例代码块是在类中定义的非静态代码块,它在创建对象的时候执行,每次创建对象都会执行一次。
实例代码块通常用于初始化实例变量或执行一些实例操作。
class A {static int E;static int I;void beA() {System.out.println("This is A");}void difference(int number) {System.out.println("A is not number");}void be() {System.out.println("This is A");}
}
比如上文的示例代码,其中A类中被static
修饰的就是静态代码块,其余部分就是实例代码块。
没有继承关系下的执行顺序
- 静态代码块先执行,且只执行一次,在类的加载阶段执行。
- 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,执行构造方法。
有继承关系下父类子类中的执行顺序
- 父类静态代码块优先于子类静态代码块执行,而且是最早执行
- 父类实例代码块和父类构造方法紧接着执行
- 子类实例代码块和子类构造方法紧接着执行
- 第二次实例化子类对象时,父类盒子类的静态代码块都将不会执行
我们可以编写以下的代码来实验一下。
class A {public A() {System.out.println("执行:A的构造函数");}{System.out.println("执行:A的实例代码块");}static {System.out.println("执行:A的静态代码块");}
}
class B extends A {public B() {System.out.println("执行:B的构造函数");}{System.out.println("执行:B的实例代码块");}static {System.out.println("执行:B的静态代码块");}
}
public class Main {public static void main(String[] args) {B b1 = new B();System.out.println("----------------");B b2 = new B();}
}
输出: 执行:A的静态代码块执行:B的静态代码块执行:A的实例代码块执行:A的构造函数执行:B的实例代码块执行:B的构造函数----------------执行:A的实例代码块执行:A的构造函数执行:B的实例代码块执行:B的构造函数
证明结论是对的。
下面是《Java面向对象三大特征》这几篇文章的参考链接:
(1) (2) (3) (4) (5)
(6) (7) (8) (9) (10)
(11) (12) (13) (14)