当前位置: 首页 > news >正文

Java SE - 继承与多态

目录

  • 1.继承
    • 1.1 继承的语法形式
    • 1.2 super关键字
    • 1.3 父类的构造方法
  • 2.this 和 super的联系与区别
  • 3.继承中代码块的执行顺序
  • 4.final关键字
  • 5.组合
  • 6.多态
    • 6.1 多态发生的条件
      • 6.1.1 向上转型:使用父类接收子类创建的对象。
      • 6.1.2 重写父类中的方法
      • 6.1.3 使用父类的引用调用子类重写的方法
    • 6.2.多态的优缺点
      • 6.2.1 优点
      • 6.2.2 缺点
    • 6.3 向下转型

1.继承

继承的作用是将大量具有相同性质的代码进行抽离,通过继承的方式提供给子类复用,在Java中的继承是单继承,即一个子类只能有一个父类,但是一个父类可以有多个子类。

在没有继承前实现一个Frog(蛙)类和Duck类观察各自的特性。

package Demo1;public class Frog {public String name;public int age;public void eat(){System.out.println(this.name + "正在吃昆虫......");}public void sleep(){System.out.println(this.name + "这一只小青蛙正在休息中.......");}public void swim(){System.out.println(this.name + "在蛙泳....");}public void jump(){System.out.println(this.name + "正在跳....");}
}
public class Duck {public String name;public int age;public void eat(){System.out.println(this.name + "正在吃昆虫......");}public void sleep(){System.out.println(this.name + "这一只小青蛙正在休息中.......");}public void swim(){System.out.println(this.name + "在蛙泳....");}public void walk(){System.out.println(this.name + "正在路上走....");}
}

在这里插入图片描述
为了提高代码的执行效率,可以将以上大量重复的过程进行共性的抽取,Frog和Duck都是属于动物,将其的共性定义在一个Animal类中。

public class Animal {public String name;public int age;public void eat(){System.out.println(name + "正在吃饭");}public void sleep(){System.out.println(name + "正在休息");}public void swim(){System.out.println(name + "在游泳");}
}

通过继承父类Animal就可以使子类中减少大量重复出现的代码。

1.1 继承的语法形式

在子类中继承父类需要使用extends关键字,格式为:访问修饰符+class+子类名+extends+父类名

public class Duck extends Animalpublic class Frog extends Animal

继承后以上子类的程序可以修改为如下部分:

public class Duck extends Animal{public void walk(){System.out.println(this.name + "正在路上走....");}
}
public class Frog extends Animal {public void jump(){System.out.println(this.name + "正在跳....");}
}

此时在Frog类中使用main函数实例化一个Frog类型的对象,调用父类和子类中的方法。

class Animal {public String name;public int age;public void eat(){System.out.println(name + "正在吃饭");}public void sleep(){System.out.println(name + "正在休息");}public void swim(){System.out.println(name + "在游泳");}
}
class Frog extends Animal {public void jump(){System.out.println(this.name + "正在跳....");}public static void main(String[] args) {Frog frog =new Frog();frog.eat();frog.jump();}
}

输出结果如下:名字是null,表示空应用,这是应为还未对父类中的成员初始化。

在这里插入图片描述

1.2 super关键字

在子类中已经继承父类的成员变量,使用实例化的对象的引用进行访问父类成员。

class Animal {public String name;public int age;public void eat(){System.out.println(name + "正在吃饭....");}public void sleep(){System.out.println(name + "正在休息....");}public void swim(){System.out.println(name + "在游泳.....");}
}
class Frog extends Animal {public void jump(){System.out.println(this.name + "正在跳....");}public static void main(String[] args) {Frog frog =new Frog();frog.name = "哇哇";//通过引用访问到父类成员frog.age = 22;frog.eat();frog.jump();}
}

输出结果如下:可以通过子类引用访问到父类的成员变量。

在这里插入图片描述

如果在子类中存在与父类相同的成员变量,调用这一个相同的成员变量输出的结果是什么?

class Animal {public String name;public int age;public void eat(){System.out.println(name + "正在吃饭....");}public void sleep(){System.out.println(name + "正在休息....");}public void swim(){System.out.println(name + "在游泳.....");}
}
class Frog extends Animal {public String name;public void jump(){System.out.println(this.name + "正在跳....");}public static void main(String[] args) {Frog frog =new Frog();frog.name = "哇哇";frog.eat();frog.jump();}
}

输出如下:通过引用调用父类和子类同名的成员变量时,优先访问的是子类的成员变量(就近原则),所以在调用父类方法时,父类成员还未初始化,输出的name为null,而子类已经通过引用初始化成员变量,输出的name为哇哇。

在这里插入图片描述
需要访问父类的成员变量时,可以使用super关键字,格式是super + 点 + 父类变量名。

   public static void main(String[] args) {Frog frog =new Frog();super.name = "哇哇";frog.eat();}
}

在main方法中使用发现会报错,因为main方法是一个静态方法,静态方法中只能初始化静态变量,而name是一个非静态变量,因此系统会报错,无法正常初始化。

class Frog extends Animal {//编译报错super.name = "哇哇";public void jump(){System.out.println(super.name + "正在跳....");}

在这里插入图片描述
如果将super关键字单独作为一条语句,会出现编译报错,不符合语法规则,super关键字的使用需要在子类的方法中使用,出现在其它类中就会编译报错,正确的使用应该将super关键字调用父类成员变量的语句包含在子类方法中。

class Animal {public String name;public int age;public void eat(){System.out.println(name + "正在吃饭....");}public void sleep(){System.out.println(name + "正在休息....");}public void swim(){System.out.println(name + "在游泳.....");}
}
class Frog extends Animal {public void jump(){//子类方法中使用:super + 点 + 父类变量名super.name = "哇哇";System.out.println(super.name + "正在跳....");}public static void main(String[] args) {Frog frog =new Frog();frog.jump();}
}

在这里插入图片描述

1.3 父类的构造方法

在子类中使用父类的成员变量,如果父类中已经完成构造方法,在子类中就不需要帮助父类初始化,使用IDEA快速生成构造方法,鼠标点击右键找到Generate,点击找到Constructor方法,选择参数完成构造。

class Animal {public String name;public int age;//构造方法public Animal(String name, int age) {this.name = name;this.age = age;}public void eat(){System.out.println(name + "正在吃饭....");}public void sleep(){System.out.println(name + "正在休息....");}public void swim(){System.out.println(name + "在游泳.....");}
}

但是此时编译器会报错,There is no no-arg constructor available in ‘Demo1.Animal’,不允许在父类中直接使用构造方法;既然不能在父类中直接使用构造方法,那么父类成员的初始化,应该在子类中完成,可以使用super关键字,调用父类的构造方法完成初始化。

package Demo1;class Animal {public String name;public int age;//带两个参数的构造方法public Animal(String name, int age) {this.name = name;this.age = age;}public void eat(){System.out.println(name + "正在吃饭....");}public void sleep(){System.out.println(name + "正在休息....");}public void swim(){System.out.println(name + "在游泳.....");}
}
class Frog extends Animal {//构造方法public Frog(String name, int age) {//super替代的是Animal这个构造方法//等价于Animal(name,age)super(name, age);}
}

如果子类没有构造方法,默认是提供一个不带参数的构造方法,父类的构造方法也是默认是不带参数的,在子类完成构造前,必须先帮助父类构造,调用父类的构造方法super(参数)必须在子类构造方法的首行。

class Animal {public String name;public int age;//不带参数的构造方法,系统默认提供public Animal() {}//带两个参数的构造方法public Animal(String name, int age) {this.name = name;this.age = age;}public void eat(){System.out.println(name + "正在吃饭....");}public void sleep(){System.out.println(name + "正在休息....");}public void swim(){System.out.println(name + "在游泳.....");}
}
class Frog extends Animal {//不带参数的构造方法public Frog() {//super替代的是Animal这个构造方法//等价于Animal()super();}//带两个参数的构造方法public Frog(String name, int age) {//super替代的是Animal这个构造方法//等价于Animal(name,age)super(name, age);}

完成了父类成员的构造方法,在实例化对象的过程就可以同时初始化父类成员变量。

class Animal {public String name;public int age;//构造方法public Animal(String name, int age) {this.name = name;this.age = age;}public void eat(){System.out.println(name + "正在吃饭....");}public void sleep(){System.out.println(name + "正在休息....");}public void swim(){System.out.println(name + "在游泳.....");}
}
class Frog extends Animal {//构造方法public Frog(String name, int age) {//super替代的是Animal这个构造方法//等价于Animal(name,age)super(name, age);}public void jump(){System.out.println(this.name + "正在跳....");}public static void main(String[] args) {//创建的同时初始化Frog frog =new Frog("哈哈",2);frog.jump();//创建的同时初始化Frog frog1 = new Frog("歪歪",4);frog1.swim();}
}

在这里插入图片描述

2.this 和 super的联系与区别

联系:1.this 和 super都是关键字;2.只能在非静态成员方法中使用,访问字段(成员变量)和方法;
3.在构造方法中调用两者执行的方法,必须放在方法的首句,并且两者不能同时存在。

区别:1.this表示当前对象的引用,该引用指向当前实例化的对象,而super关键字是当前子类实例化对象从父类继承部分的引用,调用的是父类的成员和方法。2.子类的构造方法中一定存在super(参数)方法,帮助父类成员完成初始化,如果子类的构造方法不带参数,可以默认不写,系统会自动提供一个不带参数的构造方法,包含super(),而this()方法不一定必须存在。

class Base{//成员变量int a;int b;//构造方法public Base(int a, int b) {this.a = a;this.b = b;}
}class Device extends Base{//不带参数的构造方法public Device(){this(10,20);//首行}//带两个参数的构造方法public Device(int a,int b) {super(a,b);//首行}//普通方法void print(){System.out.println("a = " + this.a + " b = " + b);}}public class Main{public static void main(String[] args) {//使用不带参数的构造方法Device device1 = new Device();device1.print();//使用带两个参数的构造方法Device device2 = new Device(20,10);device2.print();}
}

在这里插入图片描述

3.继承中代码块的执行顺序

在继承关系中,通过实例化对象会执行子类的构造方法,子类的构造方法中第一条语句是帮助父类完成构造方法,因此在构造方法中会先执行父类的构造方法后执行子类的构造方法;代码块中包含静态代码块,存储于方法区,在执行中最先被加载,整个程序中只被加载一次;实例化代码块的执行比构造方法先执行,因此代码块的执行顺序是最先执行静态代码块,然后执行实例化代码块,最后执行构造方法。

以下程序执行的顺序是什么?

class Base{public Base() {System.out.println("Base()::构造方法被执行了........1");}{System.out.println("Base()::实例代码块被执行了........2");}static{System.out.println("Base()::静态代码块被执行了........3");}
}class Device extends Base{public Device() {System.out.println("Device()::构造方法被执行了.........4");}{System.out.println("Device()::实例化代码块被执行了........5");}static{System.out.println("Device()::静态代码块被执行了.........6");}}public class Main{public static void main(String[] args) {//实例化对象Device device = new Device();}
}

先执行父类的静态代码块,后执行子类的静态代码块,然后执行父类的实例化代码块和构造方法,最后执行子类的实例化代码块和构造方法。

在这里插入图片描述

4.final关键字

final本意有最终的,不可改变的意思,final关键字有三个作用,修饰变量,方法,和类,被final修饰的变量是不可被修改的,类似于修饰后变为常量值;被final修饰的方法不可被重写;被final修饰的类不可被继承,防止该类成为基类(父类)。

//不可继承
final class Test {//不可修改final int a = 20;a = 10;//errfinal void print(){System.out.println(this.a);}//可以重载void print(int a){System.out.println(a);}
}//final修饰的不可继承,编译报错
public class Main extends Test{@Override//不可重写,编译报错void print(){System.out.println("hhhhh");}
}

在这里插入图片描述

5.组合

组合与继承类似,都可实现将重复的部分进行归类后复用,组合是将实例化对象的引用归类,而不是通过继承,例如实现一个教室类,教室中包含学生和老师,老师和学生是属于人这一个类。

//人类
class Person{String name;int age;public Person(String name, int age) {this.name = name;this.age = age;}//....
}
//老师类
class Teacher extends Person{public Teacher(String name, int age) {super(name, age);}//....
}
//学生类
class Student extends Person{public Student(String name, int age) {super(name, age);}//....
}
//教室类
class Classroom{//组合Teacher和Student,创建引用private Teacher teacher;private  Student student;//构造方法public Classroom(Teacher teacher, Student student) {this.teacher = teacher;this.student = student;}//....
}
public class Main{public static void main(String[] args) {//实例化对象Classroom classroom = new Classroom(new Teacher("王老师",47),new Student("小李",12));}}

在这里插入图片描述
在Java只能支持单继承,但是使用组合间接的可以在类中访问到其它类,减少大量重复的程序实现,可以提高程序的运行效率,因此在能使用组合的情况下尽量使用组合,可以参考以下文章理解:
【为什么说要慎用继承,优先使用组合】

6.多态

多态简单理解就是多种形态,当同一个行为或者属性展示的对象不一样时,呈现的状态就不一样,例如:Animal作为父类,存在子类Dog和Cat,父类存在一个eat() 的成员方法,当对象是Dog时就是吃狗粮,当对象是Cat时就是吃猫粮,对象不同,呈现的状态就不一样,此时就发生了多态。

在这里插入图片描述

6.1 多态发生的条件

6.1.1 向上转型:使用父类接收子类创建的对象。

创建一个Aniaml类,继承一个Frog类和一个Duck类,使用父类接收子类创建的对象。

class Animal {public String name;public int age;//构造方法public Animal(String name, int age) {this.name = name;this.age = age;}public void eat(){System.out.println(name + "正在吃饭....");}
}
class Duck extends Animal{public Duck(String name, int age) {super(name, age);}
}class Frog extends Animal {//构造方法public Frog(String name, int age) {//super替代的是Animal这个构造方法//等价于Animal(name,age)super(name, age);}public static void main(String[] args) {//向上转型:使用父类的引用接收子类创建的对象Animal animal1 = new Duck("丫丫",3);Animal animal = new Frog("瓦瓦",3);}
}

此时有了父类的引用,接收不同子类的对象,调用父类的eat方法。

 public static void main(String[] args) {//向上转型:使用父类的引用接收子类创建的对象Animal animal1 = new Duck("丫丫",3);Animal animal = new Frog("瓦瓦",3);//eat方法animal.eat();animal1.eat();
}

在这里插入图片描述

运行时的结果动作是一样的,并没有发生多态,这是因为通过父类引用调用的是父类的eat()方法,如果想实现多态,需要在子类中重写父类的方法。

6.1.2 重写父类中的方法

重写方法的规则是,方法名相同,参数列表相同(参数个数,参数顺序,参数类型都一样),返回值相同,返回类型相同(如果是继承关系,返回类型是父类或子类而不同的,也默认是符合重写规则的,这种类型称为协变类型)。

Frog重写的eat()方法

 @Overridepublic void eat() {System.out.println(this.name + "在吃昆虫......");}

Duck重写的eat()方法

  @Overridepublic void eat() {System.out.println(this.name + "在吃小鱼儿......");}

6.1.3 使用父类的引用调用子类重写的方法

在子类完成重写后就可以使用父类的引用取调用重写的方法,此时父类调用的是子类的重写方法,而不是父类本身的方法,此时就发生了动态绑定。

class Animal {public String name;public int age;//构造方法public Animal(String name, int age) {this.name = name;this.age = age;}public void eat(){System.out.println(name + "正在吃饭....");}public void sleep(){System.out.println(name + "正在休息....");}
}
class Duck extends Animal{public Duck(String name, int age) {super(name, age);}@Overridepublic void eat() {System.out.println(this.name + "在吃小鱼儿......");}
}class Frog extends Animal {//构造方法public Frog(String name, int age) {//super替代的是Animal这个构造方法//等价于Animal(name,age)super(name, age);}@Overridepublic void eat() {System.out.println(this.name + "在吃昆虫......");}public static void main(String[] args) {//向上转型:使用父类的引用接收子类创建的对象Animal animal1 = new Duck("丫丫",3);Animal animal = new Frog("瓦瓦",3);//调用重写的eat()方法animal.eat();animal1.eat();}
}

在这里插入图片描述
在这里插入图片描述

动态绑定的原因是从程序运行时通过父类引用调用重写的方法时,引用存储的是子类重写方法的地址,调用的是子类的方法。

与动态绑定相关有静态绑定,在程序运行前通过方法的参数列表就确定调用的是哪一个函数,程序运行时直接使用该函数时,常见的静态绑定有方法重载。

6.2.多态的优缺点

6.2.1 优点

多态的使用可以减低圈复杂度,圈复杂度是指程序中使用嵌套的循环或分支的次数,一般圈复杂度不建议超过10,如下:

//形状类
class Shape {void draw(){System.out.println("画画");}
}
//正方形类
class Square extends Shape{@Overridevoid draw(){System.out.println("画出一个正方形......");}
}
//三角形类
class Triangle extends Shape{@Overridevoid draw() {System.out.println("画出一个三角形......");}
}
//圆类
class Cycle extends Shape{@Overridevoid draw() {System.out.println("画出一个圆.......");}public static void main(String[] args) {//创建对象,向上转型Shape shape1 = new Square();Shape shape2 = new Triangle();Shape shape3 = new Cycle();String[] shape = {"cycle","triangle","square","triangle","cycle"};for (int i = 0; i < shape.length; i++) {//创建一个临时变量接收String shapeT = shape[i];//判断,圈复杂度if (shapeT.equals("cycle")) {shape3.draw();//多态} else if (shapeT.equals("triangle")) {shape2.draw();//多态} else if (shapeT.equals("square")) {shape1.draw();//多态}}  }
}

当程序每多一层圈复杂度,程序的逻辑就越复杂,太多的if _ else if语句的判断,程序效率会减低,代码也不够间接,使用多态修改后如下:

 public static void main(String[] args) {//创建对象,向上转型Shape shape1 = new Square();Shape shape2 = new Triangle();Shape shape3 = new Cycle();Shape[] shape = {shape3,shape2,shape1,shape2,shape3};//多态for (Shape tem : shape) {tem.draw();}
}

减低圈复杂度后也可达到同样的运行效果,代码也比较简洁。

在这里插入图片描述

6.2.2 缺点

在构造方法中使用重写的方法,可能出现预期外的效果,例如:以下程序的运行结果是什么?

class A{//构造方法public A() {func();}void func(){System.out.println("A::func()......");}
}
class B extends A{int num = 1;public B() {}@Overridevoid func(){System.out.println("B::func()执行了......" + num);}
}
public class Main {public static void main(String[] args) {A a = new B();}
}

实例化一个子类的对象,会先完成父类的构造方法,父类中调用了func() 方法,此时父类和子类都有func方法,此时还未通过引用去调用重写的方法,一般默认使用父类的func方法,完成构造方法后会进行变量的初始化,因此num应该是1。实际上输出的结果如下:

在这里插入图片描述

执行的是子类的重写方法,因为在完成构造方法是,调用的方法是重写的,此时会发生动态绑定,实际上调用的是子类重写的方法,此时num因为构造方法还未执行完,并未完成初始化,输出的是默认值0;因此,在构造方法中使用重写的方法可能会造成一些难以察觉的错误,所以构造方法中应该避免使用重写方法。

6.3 向下转型

向下转型是用子类的引用接收父类的引用,但是向下转型是不安全,有时编译器并不会直接报错,但是在编译时报错。例如:创建一个动物类,包含子类Chicken和Pig。

class Animal{//成员变量public String name;public int age;//构造方法public Animal(String name, int age) {this.name = name;this.age = age;}//重写toString方法@Overridepublic String toString() {return "Animal{" +"name='" + name + '\'' +", age=" + age +'}';}
}
//子类Chicken
class Chicken extends Animal{//构造方法public Chicken(String name, int age) {super(name, age);}
}
//子类Pig
class Pig extends Animal{//构造方法public Pig(String name, int age) {super(name, age);}public static void main(String[] args) {//向上转型Animal animal1 = new Chicken("吉吉",3);Animal animal2 = new Pig("朱朱", 2);//向下转型Chicken chicken1 = (Chicken) animal2;//编译不报错Chicken chicken2 = (Chicken) animal1;//animal1指向的是Chicken的对象,不报错//输出System.out.println(chicken1);System.out.println(chicken2);}
}

在这里插入图片描述
在这里插入图片描述

以上程序编译器中并不会直接报错,但是在程序运行时会报错,这个错误是ClassCastException(类型转换异常),原因是class声明的Pid类不能转换为class声明的Chicken类,这种错误比较隐蔽,所以向下转型是不安全的,如果需要向下转型,可以使用instanceof判断。

  public static void main(String[] args) {//向上转型Animal animal1 = new Chicken("吉吉",3);Animal animal2 = new Pig("朱朱", 2);//向下转型:使用关键字instanceof//判断引用指向的类型是否为指定的子类//如果是ture就执行,如果是false就不执行if(animal2 instanceof Chicken){Chicken chicken1 = (Chicken) animal2;//编译不报错System.out.println(chicken1);}if(animal1 instanceof Chicken) {Chicken chicken2 = (Chicken) animal1;//animal1指向的是Chicken的对象,不报错System.out.println(chicken2);}}

使用instanceof关键字判断给向下转换提供多一个安全保障,表达式为假时,就可以不执行转换操作,防止类型转换异常,更多instanceof的解释可以参考以下网站:instanceof介绍

在这里插入图片描述

http://www.xdnf.cn/news/1010485.html

相关文章:

  • 广东省省考备考(第二十七天6.12)—言语:逻辑填空(练习)
  • Sentinel 流量控制安装与使用
  • 【游戏设计】游戏视角类型及核心特点分析
  • 脑电震动音频震动信号模拟器设计资料:758-2路32bit DA 脑电震动音频信号模拟器
  • 单连杆倾角估计:互补滤波器的 MATLAB 仿真实现
  • 【Python打卡Day35】模型可视化与推理@浙大疏锦行
  • bindService 和 startService 生命周期对比
  • JavaWeb期末速成 Servlet
  • qemu-guest-agent详解
  • 亚马逊woot常见问题第三弹
  • LevelDB介绍和内部机制
  • CC工具箱使用指南:【面要素四至】
  • 深度学习5——循环神经网络
  • 智能PDU:从单一功能到多维度升级
  • 洛谷P4555 最长双回文串
  • Ntfs!NtfsFreeRestartTableIndex函数分析
  • 图片压缩工具类
  • Photoshop 2025 性能配置全攻略:硬件选购与软件优化指南
  • 医疗器械行业系统如何提升医疗器械企业的核心竞争力?
  • JavaWeb(Servlet预习)
  • CANopen转PROFINET网关应用:西门子S7-1500主站控制台达AS系列CANopen设备
  • 【金仓数据库征文】_KingbaseES V8R6 运维最佳实践
  • 报表工具顶尖对决系列 --- 文本数据源
  • 大数据学习(139)-数仓设计
  • 量化投资中的Alpha模型与Beta模型的结合
  • 基于鹅优化算法(GOOSE)和三次样条插值的机器人路径规划MATLAB完整实现方案
  • Linux系统详解
  • LeetCode 72. 编辑距离(Edit Distance)| 动态规划详解
  • 网络调试中的难题与破解:跨平台抓包方案实战对比与技巧分享(含Sniffmaster经验)
  • mapstruct中的@Mapper注解详解