1 继 承
1.1 继承的基本概念
在编程中我们通常把继承读作“子类”继承“父类”,比如说这个世界生物包括人,动物等,那么生物就是就是人、动物的父类,人和动物的属性都有生物特征的共性。
1.2 继承的基本语法
从语法上说,继承使用关键字:extends。在定义子类的时候,可以用 extends 关键字说明这个类的父类是哪一个类。代码如下:
class Animal{
int age;
boolean sex;
public void eat(){
System.out.println("Animal eat");
}
public void sleep(){
System.out.println("sleep 8 hours");
}
}
class Dog extends Animal{
public void lookAfterHouse(){
System.out.println("look after house");
}
}
class Cat extends Animal{
public void catchMouse(){
System.out.println("catch mouse");
}
}
public class TestDog {
public static void main(String args[]){
Dog d = new Dog();
d.sex = true;
d.age = 3;
d.eat();
d.lookAfterHouse();
}
我们可以看到,在代码中,使用 extends 关键字,来表明 Cat 类和 Dog 类继承自 Animal。
由于共性在父类 Animal 中,因此,在 Cat 和 Dog 中,Animal 类中已经有的代码,没有必要进行重复。
父类中的属性和方法,被子类继承之后,相当于子类中也有了相应的属性和方法。同时,子类也能够写一些子类的特性,这样,就在父类的基础上增加一些功能,体现了面向对象的可扩展性。
1.3 什么能被继承?
父类中,无法被子类访问的属性和方法,不能被继承。只有子类能够访问的属性和方法,才能够被子类继承。
1.4 访问权限修饰符
public 这个修饰符不再赘述。
private:在本类内部访问且不能被继承
protected :用 protected 修饰符修饰的属性和方法,能够被本类内部、同包的类以及非同包的子类访问。也就是能被所有的子类继承。
default :不加任何的访问修饰符(默认)如 private、public、protected,则访问权限是 default 权限。只能被本类或者同包的其他类访问。
class Student{
int age;//default 不加任何的访问修饰符
}
修饰符 | 访问范围 | 是否能被子类继承 |
---|---|---|
private | 本类内部 | 不能被继承 |
(default ) | 本类内部+同包的其他类 | 能被同包的子类继承 |
protected | 本类内部+同包的其他类+非同包的子类 | 能被继承 |
public | 公开,能被所有类访问 | 能被继承 |
1.5 方法覆盖
子类中用一个特殊实现,来替换从父类中继承到的一般实现,这种语法叫做“方法覆盖”。
从语法上说,方法覆盖对方法声明的五个部分都有要求。具体来说:
1. 访问修饰符相同或更宽。如父类的方法如果是 protected 方法,子类如果想要覆盖这个方法,则修饰符至少是 protected,也可以是 public,但是不能是 default 或者 private 的。
2. 返回值类型相同。如果返回值类型不同,则会产生一个编译错误。
3. 方法名相同。这是必然的,如果方法名不同,则谈不到覆盖。
4. 参数表相同。注意的是如果不满足参数表不同,编译不出错,但是不构成方法覆盖。
5. 方法覆盖还对包括 static 修饰符的要求、对抛出的异常的要求,等等。
1.6 对象创建的过程
在有了继承关系之后,对象创建过程如下:
1. 分配空间。要注意的是,分配空间不光是指分配子类的空间,子类对象中包含的父类对象所需要的空间,一样在这一步统一分配。在分配空间的时候,会把所有的属性值都设为默认值。
2. 递归的构造父类对象。这一过程我们会在下面进一步介绍
3. 初始化本类属性。
4. 调用本类的构造方法。
结合一个具体的例子来介绍对象创建的过程。
假设有如下代码:
class A{
int valueA = 100;
public A(){ valueA=150; }
}
class B extends A{
int valueB = 200;
public B(){ valueB=250; }
}
class C extends B{
int valueC = 300;
public C(){ valueC=350; }
}
public class TestInherit{
public static void main(String args[]){
C c = new C();
}
}
总结一下:创建 C 对象的步骤一共有 7 步:
1. 分配空间
2. 初始化 A 类的属性
3. 调用 A 类的构造方法
4. 初始化 B 类的属性
5. 调用 B 类的构造方法
6. 初始化 C 类的属性
7. 调用 C 类的构造方法
1.7 super 关键字
在介绍对象创建的过程时,我们介绍了在创建对象时,会创建该对象的父类对象。而在创建父类对象的时候,很显然会调用父类的构造方法。但是,如果父类有多个构造方法,会调用哪一个呢?
例如下面的代码:
class Parent{
public Parent(){
System.out.println("Parent()");
}
public Parent(String str){
System.out.println("Parent(String)");
}
}
class Child extends Parent{
public Child(String str){
System.out.println("Child(String)");
}
}
public class TestInheritConstructor{
public static void main(String args[]){
Child c = new Child("Hello");
}
}
在默认情况下,创建子类对象时,都会调用父类的无参构造方法。
结果:
Parent()
Child(String)
在父类中我们定义好了带字符串参数的构造方法,要求 Java 在创建 Parent对象时,调用父类带字符串参数的构造方法。下面为大家介绍 super 关键字。super 关键字有两种不同的用法。
super 关键字用法一:super 用在构造方法上
加上一个语句:super(str):调用父类中带字符串参数的构造方法。
class Parent{
public Parent(){
System.out.println("Parent()");
}
public Parent(String str){
System.out.println("Parent(String)");
}
}
class Child extends Parent{
public Child(String str){
super(str);
System.out.println("Child(String)");
}
}
public class TestInheritConstructor{
public static void main(String args[]){
Child c = new Child("Hello");
结果为:
Parent(String)
Child(String)
}
}
要注意的是:
class Child extends Parent{
public Child(int n){
System.out.println("Child(int)");
}
public Child(String str){
this(10);
super(str);//编译出错
System.out.println("Child(String)");
}
}
1.super (参数) 指明调用父类哪个构造方法
对 super 的调用必须是构造方法中的第一个语句。
如果第一句既不是 this(参数),也不是 super(参数),编译器会自动在这个构造方法中增加一个语句:“super();”
this()和 super(),在构造方法中不能同时使用。
2. this (参数) 指明调用本类哪个构造方法
对 this()来说,这个语句也只能作为构造方法的第一个句。
掌握了在构造方法中如何使用 super 关键字之后,看下面这个例子。
class Super{
}
class Sub extends Super{
public Sub(){}
public Sub(String str){
super(str);
}//编译错误 找不到一个 Super 类中,带字符串参数的构造方法
}
如下当我们为 Super 类增加了一个字符串参数的构造方法后,编译器就不会自动生成这个无参构造方法,因此就会产生找不到 Super 类无参构造方法的错误。
class Super{
public Super(String str){}
}
class Sub extends Super{
public Sub(){}//编译出错位置
public Sub(String str){
super(str);
}
}
因此,如果想要让代码编译通过,必须为 Super 类增加两个构造方法。
class Super{
public Super(String str){}
public Super(){}
}
class Sub extends Super{
public Sub(){}//编译通过
public Sub(String str){
super(str);
}
}
super 关键字用法二:super 用作引用
super 关键字的第二种用法,就是把 super 当做是一个引用,这个引用指向父类对象。
例如创建一个 B 对象时,B 对象内部,会包含一个父类对象:A 对象。同时,B 对象内部
会有两个引用:this 和 super。其中 this 引用指向当前的 B 对象,而 super,则指向 B 对象内
部的 A 对象。内存中示意图如下:
class A{
public void m (){
}
}
class B extends A{
public void m1 (){
this.m();//this引用指向当前的B对象
}
public void m2(){
super.m();//super指向B对象内部的A对象
}
}
详细代码如下:
class Parent{
public void m(){
System.out.println(“m in Parent”);
}
}
class Child extends Parent{
public void m (){
System.out.println(“m in Child”);
}
public void m1 (){
this.m();
}
public void m2(){
super.m();
}
}
public class TestSuper{
public static void main(String args[]){
Child c = new Child();
c.m1();
c.m2();
}
}
输出结果为:
m in Child
m in Parent
super 可以调用父类被覆盖的方法,降低了代码的可读性以及可重用性。
例如,有一个 Server 类,这个类代表一个服务器。
class Server{
public void startService(){
}
}
class MyServer extends Server{
public void startService(){
super.startService();
//保存日志的功能
}
}
super 关键字除了可以用来调用方法之外,也可以用来指向属性。
class Parent{
int value = 100;
}
class Child extends Parent{
int value = 200;
public void print(){
int value = 300;
System.out.println(value);//直接打印 value 变量,则局部变量优先
System.out.println(this.value);//打印Child中的value属性
System.out.println(super.value);//打印Parent中的value属性
}
}
public class TestParent{
public static void main(String args[]){
Child c = new Child();
c.print();
}
}
1.8 单继承
Java 语言规定,每一个类只能有一个直接父类。这就是 Java 语言的“单继承”规则。
class A{}
class B{}
class C extends A,B{} // 编译出错,一个类不能同时有两个直接父类
对于多继承来说,一个典型的类继承关系如下图: