目录
前言
现实生活中的继承,孩子继承父辈的财产,孩子可以直接拿父辈的财产来使用,Java中的继承是指在现有类的基础上定义一个新的类,现有类称为父类,新的类称为子类,子类会自动拥有父类的可继承的内容多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那一个类即可。
继承定义
就是子类继承父类的属性和行为,使得子类对象具有与父类相同的属性、相同的行为。子类可以直接访问父类中的非私有的属性和行为。
好处
提高代码的复用性。
类与类之间产生了关系,是多态的前提
继承格式
通过extends关键字,可以声明一个子类继承另外一个父类,定义格式如下:
class 父类 {
...
}
class 子类 extends 父类 {
...
}
简单实例
// 1.定义父类
public class Person {
//定义属性
String name;
int age;
//方法
public void eat() {
System.out.println(name + " 吃饭");
}
public void sleep() {
System.out.println(name + " 睡觉");
}
}
// 2.定义子类(继承父类)
public class Student extends Person {
}
// 3.定义测试类
public class Test {
public static void main(String[] args) {
//创建对象
Student s = new Student();
//调用属性并赋值
s.name = "小王";
s.age = 18;
//调用方法
s.eat();//小王吃饭
s.sleep();//小王睡觉
}
}
小节
子类继承了父类, 也将获得父类的全部成员变量和方法,但是, Java的子类不能继承父类的构造器.
父类不被继承
1.被private修饰的属性也不可被继承只能通过getter/setter方法访问父类的private成员变量
Java的子类不能继承父类的构造器.
public class Test 2 {
public class Fu {
public Fu() {
}
public int num1 = 10;
private int num2 = 20;
public void show1() {
System.out.println("show1");
}
private void show2() {
System.out.println("show2");
}
public int getNum2() {
return num2;
}
public void setNum2(int num2) {
this.num2 = num2;
}
}
class Zi extends Fu {
// 2. 构造方法不能继承,因为构造方法和类名相同,父类和子类的名称肯定不相同,无法继承(违背构造方法定义方法名要与类名一致需通过super调用父类构造)
/* public Fu() {
}*/
}
public static void main(String[] args) {
Zi z = new Zi();
System.out.println(z.num1);
// System.out.println(z.num2); // 私有的子类无法使用
// 通过getter/setter方法访问父类的private成员变量
System.out.println(z.getNum2());
z.show1();
// z.show2(); // 私有的子类无法使用
}
}
继承后的特点
成员变量–成员变量不重名
如果子类父类中出现不重名的成员变量,这时的访问是没有影响的.
class Fu {
// Fu中的成员变量
int num = 5;
}
class Zi extends Fu {
// Zi中的成员变量
int num2 = 6;
// Zi中的成员方法
public void show() {
// 访问父类中的num
System.out.println("Fu num="+num); // 继承而来,所以直接访问。
// 访问子类中的num2
System.out.println("Zi num2="+num2);
}
}
class Demo04 {
public static void main(String[] args) {
// 创建子类对象
Zi z = new Zi();
// 调用子类中的show方法
z.show();
}
}
演示结果:
Fu num = 5
Zi num2 = 6
成员变量–成员变量重名
class Fu1 {
// Fu中的成员变量。
int num = 5;
}
class Zi1 extends Fu1 {
// Zi中的成员变量
int num = 6;
public void show() {
// 访问父类中的num
System.out.println("Fu num=" + num);
//改正如下
//System.out.println("Fu num=" + super.num);
// 访问子类中的num
System.out.println("Zi num=" + num);
//改正如下
//System.out.println("Zi num=" + this.num);
}
}
public class Test1 {
public static void main(String[] args) {
// 创建子类对象
Zi1 z = new Zi1();
// 调用子类中的show方法
z.show();
}
}
演示结果:(想要能够访问使用super (父类) ,this (本类))
使用格式:super.父类成员变量名
使用格式:this.子类成员变量名
Fu num = 6
Zi num = 6
继承后的特点
成员方法–成员方法不重名
如果子类父类中出现不重名的成员方法,这时的调用是没有影响的
class Fu {
public void show() {
System.out.println("Fu类中的show方法执行");
}
}
class Zi extends Fu {
public void show2() {
System.out.println("Zi类中的show2方法执行");
}
}
public class Test{
public static void main(String[] args) {
Zi z = new Zi();
//子类中没有show方法,但是可以找到父类方法去执行
z.show();
z.show2();
}
}
成员方法–成员方法重名
如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做 方法重写 (Override) 。
方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者覆盖
class Fu {
public void show() {
System.out.println("Fu show");
}
}
class Zi extends Fu {
//子类重写了父类的show方法
public void show() {
System.out.println("Zi show");
}
}
public class Test{
public static void main(String[] args) {
Zi z = new Zi();
// 子类中有show方法,只执行重写后的show方法
z.show(); // Zi show
}
}
重写的应用
子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。
重写案例
// 老手机
class Phone {
public void sendMessage() {
System.out.println("发短信");
}
public void call() {
System.out.println("打电话");
}
public void showNum() {
System.out.println("显示来电号码");
}
}
// 智能手机类
class NewPhone extends Phone {
// 重写父类的来电显示号码功能,并增加自己的显示姓名和图片功能
public void showNum(){
// 调用父类已经存在的功能使用super
super.showNum();
// 增加自己特有显示姓名和图片功能
System.out.println("显示来电姓名");
System.out.println("显示头像");
}
}
public class Test {
public static void main(String[] args) {
// 创建子类对象
NewPhone np = new NewPhone();
// 调用父类继承而来的方法
np.call();
// 调用子类重写的方法
np.showNum();
}
}
构造方法
构造方法的定义格式和作用
1.构造方法的名字是与类名一致的。所以子类是无法继承父类构造方法的。
2.构造方法的作用是初始化成员变量的。所以子类的初始化过程中,必须先执行父类的初始化动作。子类的构造方法中默认有一个 super() ,表示调用父类的构造方法,父类成员变量初始化后,才可以给子类使用。
3.继承后子类构造方法特点:子类所有构造方法都会调用父类的无参构造
class Fu {
//变量
private int num1;
private int num2;
//无参构造
public Fu(){
}
//有参构造
public Fu(int num1,int num2){
this.num1=num1;
this.num2=num2;
}
//普通方法
public void show1() {
System.out.println("show1");
}
private void show2() {
System.out.println("show2");
}
public int getNum2() {
return num2;
}
public void setNum2(int num2) {
this.num2 = num2;
}
public void setNum1(int num1) {
this.num1 = num1;
}
public int getNum1() {
return num1;
}
}
class Zi extends Fu {
// 2. 构造方法不能继承,因为构造方法和类名相同,父类和子类的名称肯定不相同,无法继承(违背构造方法定义方法名要与类名一致需通过super调用父类构造)
/* public Fu() {
}*/
public Zi(){
super();
}
public Zi(int num1,int num2){
super(num1,num2);
}
}
public class Test {
public static void main(String[] args) {
Zi z = new Zi(10,20);
System.out.println();
System.out.println(z.getNum2());
System.out.println(z.getNum1());
z.show1();
}
}
演示结果:
20
10
show1
继承的执行顺序问题
继承体系中的构造器执行顺序
当调用子类构造器实例化子类对象时,父类构造器总是在子类构造器之前执行。
创建任何对象总是从该类所在继承树最顶层类的构造器开始执行,然后依次向下执行,最后才执行本类的构造器。如果父类通过this调用了同类中的重载构造器,就会依次执行此父类的多个构造器。
继承体系中的静态域执行顺序当调用子类构造器实例化子类对象时,父类优先于子类进行加载到内存,所以会先执行父类中的静态域
从该类所在继承树最顶层类开始加载,并执行其静态域,依次向下执行,最后执行本类。 静态域优先于main方法,优先于构造器执行
class Test {
static {
System.out.println("主类静态块");
}
public static void main(String[] args) {
Zi z = new Zi();
}
}
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}
执行结果:
主类静态块
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
执行顺序分析:
主类Test2_Extends先加载到内存,静态域优先于main方法执行,先输出了主类静态块,其中的main方法入栈执行,main方法中创建了子类对象
子类对象创建过程中,父类和子类都加载到内存中,并且Fu.class优先于Zi.class加载,父类中的静态域先执行后,再执行子类中的静态域,此时会第一个输出:静态代码块Fu,第二个输出:静态代码块Zi
创建对象时进入子类的构造器,因为Java是分层初始化的,所以会先初始化父类再初始化子类,子类构造器会自动默认先执行父类的构造器,因为构造代码块优先于构造方法执行,所以此时就会先执行父类的构造代码块后,再执行父类的构造方法。所以第三个输出:构造代码块Fu,第四个输出:构造方法Fu
Fu类初始化结束后,子类初始化,第五个输出的是:构造代码块Zi,第六个输出:构造方法Zi
super和this的用法
this.成员变量 ‐‐ 本类的
super.成员变量 ‐‐ 父类的
this.成员方法名() ‐‐ 本类的
super.成员方法名() ‐‐ 父类的
this(…) ‐‐ 本类的构造方法
super(…) ‐‐ 父类的构造方法
总结
1.继承的特点:Java只支持单继承,不支持多继承
2.子类对象在进行实例化前首先调用父类构造方法,再调用子类构造方法实例化子类对象
3.子类的每个构造方法中均有默认的super(),调用父类的空参构造。手动调用父类构造会覆盖默认的super()。super() 和 this() 都必须是在构造方法的第一行,所以不能同时出现。
4。继承后子类构造方法特点:子类所有构造方法都会调用父类的无参构造
5.继承注意点
成员变量和方法
子类只能继承父类的所有非私有的成员变量和方法。可以继承public protected 修饰的成员,不可以继承private修饰的。
但是可以通过父类中提供的public 的setter和getter方法进行间接的访问和操作private 的属性
对于子类可以继承父类中的成员变量和成员方法,如果子类中出现了和父类同名的成员变量和成员方法时,父类的成员变量会被隐藏,父类的成员方法会被覆盖。需要使用父类的成员变量和方法时,就需要使用super关键字来进行引用。
隐藏是针对成员变量和静态方法,覆盖是针对普通方法。
当创建一个子类对象时,不仅会为该类的实例变量分配内存,也会为它从父类继承得到的所有实例变量分配内存,即使子类定义了与父类中同名的实例变量。 即依然会为父类中定义的、被隐藏的变量分配内存。
如果子类中的实例变量被私有了 ,其父类中的同名实例变量没有被私有,那么子类对象就无法直接调用该变量,但可以通过先将对象变量强制向上转型为父类型,在通过该对象引用变量来访问那个实例变量,就会得到的是父类中的那个实例变量。
构造器
子类不能继承获得父类的构造方法,但是可以通过super关键字来访问父类构造方法。
在一个构造器中调用另一个重载构造器使用this调用完成,在子类构造器中调用父类构造器使用super调用来完成。
super 和 this 的调用都必须是在第一句,否则会产生编译错误,this和super只能存在一个。
不能进行递归构造器调用,即多个构造器之间互相循环调用。
如果父类有无参构造时,所有构造方法(包含任意有参构造)自动默认都会访问父类中的空参构造方法。(自带super();)
因为继承的目的是子类获取和使用父类的属性和行为,所以子类初始化之前,一定要先完成父类数据的初始化。
在Java中,每个类都会默认继承Object超类,所以每一个构造方法的第一条默认语句都是super()
如果父类没有无参构造,反而有其他的有参构造方法时,子类继承父类后,子类必须显式的创建构造器,
不论子类的构造器是否和父类构造器中参数类型是否一致,都必须在子类的构造器中显式的通过super关键字调用和父类构造器相应参数的构造方法。否则编译都通不过。
https://blog.csdn.net/hxhaaj/article/details/81174764