Java中的重要核心知识点之继承详解
作者:Luka.lh
一、继承
1、概念
继承(inheritance)机制:是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特 性的基础上进行扩展,增加新功能,这样产生新的类,称派生类。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。
继承主要解决的问题是:共性的抽取,实现代码复用。
例如:狗和猫都是动物,分别定义一个猫类和狗类,他们都是动物,让他们来继承动物类,那么我们就可以将共性的内容进行抽取,然后采用继承的思想来达到共用。
// Dog.java public class Dog{ string name; int age; float weight; public void eat(){ System.out.println(name + "正在吃饭"); } public void sleep(){ System.out.println(name + "正在睡觉"); } void Bark(){ System.out.println(name + "汪汪汪~~~"); } } // Cat.Java public class Cat{ string name; int age; float weight; public void eat(){ System.out.println(name + "正在吃饭"); } public void sleep() { System.out.println(name + "正在睡觉"); } void mew(){ System.out.println(name + "喵喵喵~~~"); } }
2、语法
在Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:
修饰符 class 子类 extends 父类 { // ... }
那么对于猫和狗的场景,我们让猫类和狗类均继承自动物类:
public class Animal{ String name; int age; public void eat(){ System.out.println(name + "正在吃饭"); } public void sleep(){ System.out.println(name + "正在睡觉"); } } // Dog.java public class Dog extends Animal{ void bark(){ System.out.println(name + "汪汪汪~~~"); } } // Cat.Java public class Cat extends Animal{ void mew(){ System.out.println(name + "喵喵喵~~~"); } } // TestExtend.java public class TestExtend { public static void main(String[] args) { Dog dog = new Dog(); // dog类中并没有定义任何成员变量,name和age属性肯定是从父类Animal中继承下来的 System.out.println(dog.name); System.out.println(dog.age); // dog访问的eat()和sleep()方法也是从Animal中继承下来的 dog.eat(); dog.sleep(); dog.bark(); } }
注意事项:
1、子类会将父类中的成员变量或成员方法继承到子类中去。
2、子类中一定要含有与父类不同的成员方法或者成员变量,体现出与父类的不同,否则继承就失去了意义。
3、父类成员的访问
(1)子类中访问父类成员变量
第一种情况:子类和父类不存在同名成员变量
public class Base { int a; int b; } public class Derived extends Base { int c; int d; public void method(){ a=10;//访问的是从父类继承下来的成员变量a b=20;//访问的是从父类继承下来的成员变量b c=30;//访问的是子类自己的成员变量c d=40;//访问的是子类自己的成员变量d } }
第二种情况:子类和父类成员变量有同名
public class Base { int a; int b; } public class Derived extends Base { int b; int c; public void method(){ a=10; b=20;//此时访问的是父类中的b还是子类中的b? c=30; //d=40; 编译报错,因为在子类和父类中都不存在成员变量d } }
注意事项:
在子类方法中或者通过子类对象访问成员变量时:
1、如果访问的成员变量子类中有,优先访问自己的成员变量。
2、如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
3、如果访问的成员变量与父类中成员变量同名,则优先访问自己的,即:子类将父类同名成员隐藏了。
(2)子类中访问父类成员方法
第一种情况:子类和父类中不存在同名成员方法
public class Base { public void methodA(){ System.out.println("我是父类成员方法"); } } public class Derived extends Base { public void methodB(){ System.out.println("我是子类成员方法"); } public void methodC(){ methodA();//访问继承自父类成员方法 methodB();//访问子类自己的成员方法 //methodD(); 编译报错,在继承体系中没有methodD方法 } }
第二种情况:子类和父类中存在同名方法
public class Base { public void methodA(){ System.out.println("我是父类成员方法methodA"); } public void methodB(){ System.out.println("我是父类成员方法methodB"); } } public class Derived extends Base { public void methodA(int a){ System.out.println("我是子类成员方法methodA"); } public void methodB(){ System.out.println("我是子类成员方法methodB"); } public void methodC(){ methodA(1);//带参的methodA方法,访问的是子类的方法 methodA();//无参的methodA方法,访问的是父类的方法 methodB();//直接访问methodB,访问到的是子类的方法 } }
注意事项:
1、通过子类对象或方法访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
2、通过子类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同,根据调用方法时传递的参数选择合适的方法访问,如果没有则报错;如果父类和子类同名方法的原型一致,则只能访问到子类的,父类的无法通过派生类对象直接访问到。
咦????那么问题来了,如果想要访问与子类方法同名且原型一致的父类方法时该怎么访问呢?——super关键字
4、super关键字
当子类和父类中存在同名且原型一致的方法时,我们想要调用父类的该方法时发现不能直接访问,而在Java中提供了super关键字,该关键字的主要作用就是:在子类方法中访问父类成员。
public class Base { int a; int b; public void methodA(){ System.out.println("我是父类成员方法methodA"); } public void methodB(){ System.out.println("我是父类成员方法methodB"); } } public class Derived extends Base { int a;//与父类同名成员 String b;//与父类同名成员的类型不同 // 与父类中methodA()构成重载 public void methodA(int a){ System.out.println("我是子类成员方法methodA"); } //重写父类中的methodB方法 public void methodB(){ System.out.println("我是子类成员方法methodB"); } public void methodC(){ a=100; super.a=110; b="abc"; super.b=100; methodA(1);//带参的methodA方法,访问的是子类的方法 methodA();//无参的methodA方法,访问的是父类的方法 methodB();//直接访问methodB,访问到的是子类的方法 super.methodB();//通过super关键字调用的是父类的methodB } }
注意事项:
1、只能在非静态方法中使用。
2、用于在子类方法中,访问父类的成员变量和方法。
5、子类构造方法
子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。
public class Base { public Base(){ System.out.println("我是父类构造方法"); } } public class Derived extends Base { public Derived(){ // super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super() //当用户没有写时编译器自动添加 //且super()必须是第一条语句,且只能出现一次 System.out.println("我是子类构造方法"); } public static void main(String[] args) { Derived a=new Derived(); } }
/*
执行结果
我是父类构造方法
我是子类构造方法
*/
说明:在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:子类对象是一个父类对象,在构造子类对象时,先要将从父类继承下来的成员初始化完整,然后再初始化子类自己新增加的成员。
从对象模型的角度来看:
注意事项:
1、若父类显式定义无参或者默认的构造方法,编译器会给子类生成一个默认的构造方法,且在子类构造方法第一行默认有隐含的super()调用,即调用基类构造方法。
2、如果父类构造方法是带有参数的,此时编译器不会再给子类生成默认的构造方法,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
具体看如下代码:
public class Base { int a; int b; public Base(int a){ //父类中带参数的构造方法 this.a=a; } } public class Derived extends Base { public Derived(int a,int b){ //此时需要在子类中自定义构造方法 super(a);//选择合适的父类构造方法调用 this.b=b; } public static void main(String[] args) { Derived a=new Derived(10,20); System.out.println(a.a); System.out.println(a.b); } }
/*
执行结果:
10
20
*/
3、在子类构造方法中,super(…)调用父类构造时,必须是子类构造函数中第一条语句。
4、super(…)只能在子类构造方法中出现一次,并且不能和this同时出现。
6、super和this
相同点:
1、都是Java中的关键字
2、只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3、在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在,如下图所示:
不同点:
先给一段代码
public class Base { int a; int b; } public class Derived extends Base { int c; int d; public void method(){ super.a=10; super.b=20; this.c=30; this.d=40; } public static void main(String[] args) { Derived d=new Derived(); d.method(); } }
1、 this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是父类对象的引用。针对上述代码,如下图所示:
2、在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性。上图也可以进行说明。
3、this是非静态成员方法的一个隐藏参数,super不是隐藏的参数。还是针对上述代码,从字节码的角度来看,打开method方法中的局部变量表,发现只有this,而没有super。
4、成员方法中直接访问本类成员时,编译之后会将this还原,即本类非静态成员都是通过this来访问的;在子类中如果通过super访问父类成员,编译之后在字节码层面super实际是不存在的。
5、在构造方法中:this(…)用于调用本类构造方法,super(…)用于调用父类构造方法,两种调用不能同时在构造方法中出现。
6、 构造方法中一定会存在super(…)的调用,用户没有写编译器也会增加,但是this(…)用户不写则没有。
7、代码块执行顺序
第一种:在没有继承关系时的执行顺序
public class Person { int age; String name; public Person(int age,String name){ this.age=age; this.name=name; System.out.println("执行构造方法"); } { System.out.println("执行实例代码块"); } static { System.out.println("执行静态代码块"); } public static void main(String[] args) { Person a=new Person(20,"luka"); System.out.println("-----------------------"); Person b=new Person(21,"stepth"); } }
/*
执行结果:
执行静态代码块
执行实例代码块
执行构造方法
-----------------------
执行实例代码块
执行构造方法
*/
说明:
1、静态代码块先执行,并且只执行一次,在类加载阶段执行
2、 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
第二种:在存在继承关系时的执行顺序
public class Person { int age; String name; public Person(int age, String name) { this.age = age; this.name = name; System.out.println("Person:执行构造方法"); } { System.out.println("Person:执行实例代码块"); } static { System.out.println("Person:执行静态代码块"); } } public class Student extends Person{ public Student(int age,String name){ super(age,name); System.out.println("Student:执行构造方法"); } { System.out.println("Student:执行实例代码块"); } static{ System.out.println("Student:执行静态代码块"); } public static void main(String[] args) { Student a=new Student(20,"luka"); System.out.println("------------------------"); Student b=new Student(21,"stepth"); } }
/*
执行结果:
Person:执行静态代码块
Student:执行静态代码块
Person:执行实例代码块
Person:执行构造方法
Student:执行实例代码块
Student:执行构造方法
------------------------
Person:执行实例代码块
Person:执行构造方法
Student:执行实例代码块
Student:执行构造方法
*/
说明:
1、父类静态代码块优先于子类静态代码块执行,静态代码块相较于其他是最早执行。
2、父类实例代码块和父类构造方法紧接着执行。
3、子类的实例代码块和子类构造方法紧接着再执行。
4、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行(静态代码块只执行一次)。
8、父类成员在子类中的可见性
父类中不同访问权限的成员,在子类中的可见性又是什么样子的?
// extend01包中 public class B { private int a; protected int b; public int c; int d; } // extend01包中 // 同一个包中的子类 public class D extends B{ public void method(){ // super.a = 10; // 编译报错,父类private成员在相同包子类中不可见 super.b = 20; // 父类中protected成员在相同包子类中可以直接访问 super.c = 30; // 父类中public成员在相同包子类中可以直接访问 super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问 } } // extend02包中 // 不同包中的子类 public class C extends B { public void method(){ // super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见 super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问 super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问 //super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问 } } // extend02包中 // 不同包中的类 public class TestC { public static void main(String[] args) { C c = new C(); c.method(); // System.out.println(c.a); // 编译报错,父类中private成员在不同包其他类中不可见 // System.out.println(c.b); // 父类中protected成员在不同包其他类中不能直接访问 System.out.println(c.c); // 父类中public成员在不同包其他类中可以直接访问 // System.out.println(c.d); // 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问 } }
**注意:**父类中private成员变量随时在子类中不能直接访问,但是也继承到子类中了。
9、继承方式
Java中支持以下几种继承方式:
单继承:
多层继承:
不同类继承同一个类
不支持多继承
10、final关键字
final关键可以用来修饰变量、成员方法以及类。
1、修饰变量或字段,表示常量(即不能修改)。
final int a = 10; a = 20; // 编译出错
2、修饰类:表示此类不能被继承。
final public class Animal { ... } public class Bird extends Animal { ... } // 编译出错
平时使用的String字符串类,打开其源码可以看到它是被final修饰的,不可以被继承。
3.、修饰方法:表示该方法不能被重写。
11、组合
组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段。
这是继承和组合的区别。
//车闸类 class Brake{ } //车把手类 class Handle{ } //轮胎类 class Tire{ } public class Bicycle { private Brake brake; //可以复用车闸中的属性和方法 private Handle handle; //可以复用车把手中的属性和方法 private Tire tire; //可以复用轮胎中的属性和方法 }
作者:luka.lh
over!!!
到此这篇关于Java中的重要核心知识点之继承详解的文章就介绍到这了,更多相关Java 继承内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!