编辑
前言
大家好,我是那个不会打拳的程序猿。今天我给大家带来的是面向对象之封装继承多态中的继承,文章通过继承的语法、父类成员的访问、super关键字、子类的构造方法、protected关键字等方面来详细讲解继承的用法。
编辑
目录
1.继承的概念
继承机制:是面向对象编程的一个重要体现,它主要是用来使一些重复的代码只用编写一份达到共用的效果。它主要解决:共性的抽取,实现代码的复用。
例如:狗和猫都是动物,他们共有的特性就是吃、睡、跑等,因此我们可以把这些特性作为一份代码,使得狗和猫这两类能共用这份代码,避免写两份代码甚至更多代码。
编辑
通过Java就能写出这样一些代码:
class Dog { String run; String eat; public void sleep() { System.out.println("睡觉"); } public void wangWang() { System.out.println("汪汪叫"); } } class Cat { String run; String eat; public void sleep() { System.out.println("睡觉"); } public void wangWang() { System.out.println("汪汪叫"); } }
编辑
我们发现,狗类和猫类中的这一部分代码是相同的,因此,我们可以把这一部分代码专门用一个类来写出来,从而能人狗和猫类公共使用这份代码,这就是继承的思想。如图:
编辑
上图展示了,Dog和Cat类可以继承Animal类中的属性,并且Dog和Cat类也有属于自己的属性。其中Animal为父类(基类/超类),Dog和Cat为子类(派生类)。那么这样的关系怎样用代码去实现呢?请看下方讲解!
1.1继承的语法
在Java中,我们如果要表示类与类之间的关系,可以通过extends关键字来区分:
修饰符 class 子类 extends 父类 { //.... }
那么对上方场景中Animal、Dog和Cat类使用继承方式设计如下:
class Animal { String run = "跑"; String eat = "吃"; public void sleep() { System.out.println("睡觉"); } } class Dog extends Animal { public void wangWang() { System.out.println("汪汪叫"); } } class Cat extends Animal { public void miaoMiao() { System.out.println("喵喵叫"); } } public class Test { public static void main(String[] args) { Dog dog = new Dog(); Cat cat = new Cat(); //dog类中并没有定义任何成员变量,run和eat是从父类Animal中继承过来的。 System.out.println(dog.run); System.out.println(dog.eat); dog.wangWang(); //dog类中并没有定义任何成员变量,run和eat是从父类Animal中继承过来的。 System.out.println(cat.run); System.out.println(cat.eat); cat.miaoMiao(); } }
编辑
上述代码,我们之间在main方法中实例化Dog和Cat这两个类,Dog和Cat这两个类都继承了run和eat这两个成员变量,并且Dog类和Cat类他们都有自己的属性汪汪叫和喵喵叫。因此子类可以继承父类中的属性,而且子类也可以有属于自己的属性。
注意:
- 子类会将父类中的成员变量或者成员方法继承到子类中了
- 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
1.2父类成员访问
在继承体系当中,子类将父类的方法和字段可以继承下来,那么在子类中能否不通过实例化对象直接访问父类中的继承下来的成员或方法呢?
1.2.1子类中访问父类的成员变量
(1)子类和父类不存在同名成员变量
class Animal { String run = "跑"; String eat = "吃"; } class Dog extends Animal { String sleep = "睡觉"; public void show() { System.out.println(run);//访问从父类中继承下来的run System.out.println(eat);//访问从父类中继承下来的eat System.out.println(sleep);//访问子类自己的sleep } } public class Test { public static void main(String[] args) { Dog dog = new Dog(); dog.show(); } }
编辑
通过以上代码我们可以看到,访问父类继承下来的成员变量是毫无争议的。子类中也有关于子类自己的属性因此访问自己的成员变量也是没有异议的。但是当子类与父类中的成员变量同名的时候,我们该如何去从呢?
(2)子类和父类成员变量同名
class Animal { String run = "跑"; String eat = "吃"; } class Dog extends Animal { String run = "跑步"; String sleep = "睡觉"; public void show() { System.out.println(run);//访问子类自己的run System.out.println(eat);//访问从父类中继承下来的eat System.out.println(sleep);//访问子类自己的sleep } } public class Test { public static void main(String[] args) { Dog dog = new Dog(); dog.show(); } }
编辑 以上代码中,我们发现父类与子类中的成员变量run相同了,因此在子类中我们会优先访问子类自己的run,并不会继承父类中run的属性。
因此,在子类方法中 或者 通过子类对象访问成员时:
- 如果访问的成员变量子类中有,优先访问自己的成员变量。
- 如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
- 如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
1.2.2子类中访问父类的成员方法
(1)成员方法名字不同
class Animal { public void funA() { System.out.println("Animal中的funA()"); } } class Dog extends Animal { public void funB() { System.out.println("Dog中的funB()"); } public void show() { funA(); funB(); } } public class Test { public static void main(String[] args) { Dog dog = new Dog(); dog.show(); } }
编辑
以上代码中,子类与父类中都没有成员方法同名的情况,因此没有报错情况。那么在子类方法中或者子类对象访问方法时,优先访问自己的,没有时再访问父类中的,如果父类中也没有则会报错!
(2)成员方法名字相同
class Animal { public void funA() { System.out.println("Animal中的funA()"); } public void funB() { System.out.println("Animal中的funB()"); } } class Dog extends Animal { public void funA(int num) { System.out.println("Dog中的funA()"); } public void funB() { System.out.println("Dog中的funB()"); } public void show() { funA();//访问了父类中的funA方法 funA(6);//因为有了参数,访问了子类中的funA方法 funB();//访问的是子类中的funb方法 } } public class Test { public static void main(String[] args) { Dog dog = new Dog(); dog.show(); } }
编辑以上代码我们可以看到,访问的优先级也是跟成员方法是一样的。子类里面有的则访问子类里面的,子类里面没有的则访问父类里面的。那如果子类与父类中的成员变量或者成员方法名相同时,我们非要访问父类里面的成员变量或成员方法那怎么办呢?我们可以加上一个super关键字。
1.3super关键字
由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作?直接访问是无法做到的,Java提供了super关键字,该关键字主要作用:在子类方法中访问父类的成员。
class Animal { String name = "赛虎"; public void funA() { System.out.println("Animal中的funA()"); } public void funB() { System.out.println("Animal中的funB()"); } } class Dog extends Animal { String name = "小米"; public void funA() { System.out.println("Dog中的funA()"); } public void funB() { System.out.println("Dog中的funB()"); } public void show() { System.out.println(super.name); super.funA(); super.funB(); } } public class Test { public static void main(String[] args) { Dog dog = new Dog(); dog.show(); } }
编辑
以上代码中,子类与父类中都有相同的成员变量名与方法名,但是在我们的show方法里使用了关键字super使得子类引用了父类的成员变量与方法,这就是super关键字的作用。
1.4子类构造方法
我们在子类里面执行构造方法时,必须要先调用父类构造方法,然后才能执行子类构造方法。
class Base { public Base() { System.out.println("这是一个无参的父类构造方法"); } } class Derived extends Base { public Derived() { //编译器会默认增加一个super();语句 System.out.println("这是一个子类的构造方法"); } } public class Test2 { public static void main(String[] args) { Derived der = new Derived(); } }
编辑以上代码,我们可以看到。我们在main方法中创建der对象后,先执行了父类中的构造方法然后再执行了子类中的构造方法。因为编译器会默认在子类的构造方法中第一行添加一条语句:super();使得我们的父类构造方法优先级最高。
class Base { public Base(int a,int b) { System.out.println("这是带两个参数的父类构造方法"); } } class Derived extends Base { public Derived() { System.out.println("这是一个子类的构造方法"); } } public class Test2 { public static void main(String[] args) { Derived der = new Derived(); } }
编辑
当我们这样去编写代码时,就会报错。那是因为我们子类的构造方法中编译器默认提供的super()语句里面没有参数,因此我们应该这样写代码:
class Base { public Base(int a,int b) { System.out.println("这是带两个参数的父类构造方法"); } } class Derived extends Base { public Derived() { super(2,3); System.out.println("这是一个子类的构造方法"); } } public class Test2 { public static void main(String[] args) { Derived der = new Derived(); } }
编辑当我们在子类里面,使用super()语句给父类中的构造方法赋初值后,子类的构造方法才能运行下去。注意,这个super语句如果我们没有人为的在子类构造方法中写入的话,编译器会默认super这个语句的存在。因此我们父类构造方法为无参的也就可以省略不写,如果是有参的就自己写上super语句并赋上值。
注意:
- 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构
- 造方法
- 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的
- 父类构造方法调用,否则编译失败。
- 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
- super(...)只能在子类构造方法中出现一次,并且不能和this同时出现
1.5super和this关键字
super和this都可以在成员方法中访问成员变量或调用成员方法,并且都可以放在构造方法的第一行,那他们直接有什么区别呢?
相同点:
- 都是Java中的关键字
- 只能在类中的非静态方法中使用,用来访问非静态成员方法和变量
- 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
class Derived extends Base { public Derived() { super(2,3); this(); System.out.println("这是一个子类的构造方法"); } }
编辑
class Derived extends Base { public Derived() { this(); super(2,3); System.out.println("这是一个子类的构造方法"); } }
编辑
我们可以看到,当super和this两个关键字同时在子类构造方法中出现时。都出现必须要在第一条语句的说法。
不同点:
- this是当前对象的引用,而super是对父类中的成员或方法的引用。
- 在构造方法中:this(...)用于调用本类构造方法,super(...)用于调用父类构造方法,两种调用不能同时在构造方法中出现
- 构造方法中一定会存在super(...)的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有
class Derived extends Base { public Derived(int c,int d) { super(2,3); this(3,4); } }
编辑当我们同时在一个构造方法中使用super(...)和this(...)就会报错,但如果我们分别使用super和this两个独特的功能时,比如this对当前对象的引用,super对父类构造方法进行传参。就不会造成冲突。
class Base { public Base(int a,int b) { System.out.println("a="+a+" b="+b); } } class Derived extends Base { int c; int d; public Derived(int c,int d) { super(2,3); this.c = c; this.d = d; System.out.println("c="+c+" d="+d); } } public class Test2 { public static void main(String[] args) { Derived der = new Derived(4,5); } }
编辑 以上代码,我们可以看到,如果super和this各自用各自独特的功能就不会发生报错。
1.6protected关键字
(1)同一个包内同一个类
public class Test { protected int num = 99; public static void main(String[] args) { Test test = new Test(); System.out.println(test.num); } }
编辑 以上代码展示了在同一个包同一个类内里面,被protected修饰的成员变量可以被使用。
(2)同一包中不同类
package Pack1; class Dog { protected int num = 88; } public class Test { public static void main(String[] args) { Dog dog = new Dog(); System.out.println(dog.num); } }
编辑 同样在同一包内,我们照样可以很清晰的访问到被protected修饰的变量。
(3)不同包中的子类
//包1中 package Pack1; public class Test1 { protected int num = 99; public static void main(String[] args) { } } //包2中 package Pack2; //导入包1中的Test1类 import Pack1.Test1; //继承包1中的Test1父类 public class Test2 extends Test1{ public static void main(String[] args) { Test1 test = new Test1(); System.out.println(test.num); } }
编辑
当我们在不同包子类访问父类的时候,按我们在同一个包内的情况我们可以直接实例化并继承父类中所有的成员和方法。但是在不同包的时候,被protected修饰的变量不能随意继承,因此我们得使用super关键字来引用被protected修饰的变量或方法。
//包1 package Pack1; public class Test1 { protected int num = 99; public static void main(String[] args) { } } //包2 package Pack2; //导入包1中的Test1类 import Pack1.Test1; //继承包1中的Test1父类 public class Test2 extends Test1{ public void show() { System.out.println(super.num); } public static void main(String[] args) { Test2 test = new Test2(); test.show(); } }
编辑 以上代码我们可以看到,通过super关键字就能很好的访问到了protected修饰的关键字了。因此有以下表格:
NO | 范围 | private | default | protected | public |
1 | 同一包中的同一类 | 可以访问 | 可以访问 | 可以访问 | 可以访问 |
2 | 同一包中不同类 | 不可以访问 | 可以访问 | 可以访问 | 可以访问 |
3 | 不同包中的子类 | 不可以访问 | 不可以访问 | 可以访问 | 可以访问 |
4 | 不同包中的非子类 | 不可以访问 | 不可以访问 | 不可以访问 | 可以访问 |
以上情况大家可以一一测试一下,体会各个关键字的不同之处。
1.7继承方式
我们的继承方式分为:单继承、多层继承、不同类继承同一类。
(1)单继承
//此类为父类 class Animal { } //此类为子类 class Dog extends Animal { }
以上代码展示了子类继承父类的情况,这是一个比较常见的继承方式。
编辑
(2)多层继承
//此类为父类 class Animal { } //Dog类继承Animal类 class Dog extends Animal { } //Cat类继承Dog类 class Cat extends Dog { }
以上代码展示了多层继承的情况,当这个层次为三层的时候,它是第一个类作为"爷爷类",第二个类作为"儿子类"继承了它的父类也就是"爷爷类",第三个类作为"孙子类"继承它的父类也就是"儿子类"。这就是一个多层继承的方式。
编辑
(3)不同类继承同一个类
//此类为父类 class Animal { } //Dog类继承Animal类 class Dog extends Animal { } //Cat类继承Animal类 class Cat extends Animal { }
以上代码展示了不同类继承同一个类的情况,也就是一个类为父类,其余的类作为子类都继承这个父类。我们称之为不同类继承同一个类。
编辑
(4)多继承是不可行的
//不能继承多个父类 class Me extends Animal,Dog { }
当我们把一个类继承多个类的时候,编译器先是通过不了的,因此我们只能依靠以上三种方式去实现继承。
编辑
1.8final关键字
final关键字会锁定被修饰的变量或者方法,当你试图修改被final修饰的变量或方法时,编译器会报错,令你无法通过编译。
(1)final修饰变量
public class Test3 { public static void main(String[] args) { final int a = 10; a = 20; } }
编辑
当我们在main方法里定义一个用final修饰的变量后,想对它进行修改。此时是不可行的,被final修饰的变量或方法是不可修改的。
(2)final修饰类
final class Father { } class Son extends Father { }
编辑
当我使用final修饰类的时候,此时不能把此类当作父类给其他类继承。
本期博客到这里就结束啦,相信大家对继承有了一定的了解,感谢你的阅读,如有收获还请三连支持。
编辑
下期预告:多态