Java之继承和多态

这篇具有很好参考价值的文章主要介绍了Java之继承和多态。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

继承

一 、继承相关基础

1. 为什么需要继承

先看下代码

// 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.继承概念

继承 (inheritance) 机制 :是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能 ,这样产生新的类,称 派生类 。继承呈现了面向对象程序设计的层次结构, 体现了 由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用(即通用代码重复使用)
例如:狗和猫都是动物,那么我们就可以将共性的内容进行抽取,然后采用继承的思想达到共用。
上述代码中, Dog Cat 都继承了 Animal 类,其中: Animal 类称为父类 / 基类或超类, Dog Cat 可以称为 Animal 的 子类/ 派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态 ( 后面讲)。

3. 继承的语法

Java 中如果要表示类之间的继承关系,需要借助 extends 关键字
修饰符 class 子类 extends 父类 {
// ...
}
// Animal.java
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 + " 喵喵喵 ~~~" );
}
}

4. 父类成员访问

在继承体系中,子类将父类中的方法和字段继承下来了,那在子类中能否直接访问父类中继承下来的成员呢?

4.1 子类中访问父类的成员变量
public class Base {
int a ;
int b ;
}
public class Derived extends Base {
int c ;
public void method (){
a = 10 ; // 访问从父类中继承下来的 a
b = 20 ; // 访问从父类中继承下来的 b
c = 30 ; // 访问子类自己的 c
}
}
4.2子类和父类成员变量同名
 class Base {
int a ;
char  b ;
int c ;
}
public class Derived extends Base {
int a ; // 与父类中成员 a 同名,且类型相同
int    b ; // 与父类中成员 b 同名,但类型不同
public void method (){
a = 100 ; // 访问父类继承的 a ,还是子类自己新增的 a
b='e';      //char类型只有父类中有,故访问父类的b
c = 102 ; // 子类没有 c ,访问的肯定是从父类继承下来的 c
// d = 103; // 编译失败,因为父类和子类都没有定义成员变量 b
}
}
在子类方法中 或者 通过子类对象访问成员时
先根据类型判断访问谁的变量。
如果访问的成员变量子类中有,优先访问自己的成员变量。
如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找
4.3 子类中访问父类的成员方法

同变量一样,只是当父类与子类发生方法重写(参数也一样)时访问子类,发生方法重载时根据参数判断访问谁。当父类与子类都没有该方法时,编译器报错。

二、super关键字

1.为什么要使用super关键字

如果子类中存在与父类中相同名字的成员时,这是该怎么办呢?使用 super 在子类中访问父类的变量和方法。

class Base {

int a;

int b;

public void methodB(){

System.out.println("Base中的methodB()");

}

}

public class Derived extends Base{

int a;

int b;

public void methodB() {

super.a = 200;

super.b = 201;

System.out.println("Derived中的methodB()方法");

System.out.println(a);

System.out.println(b); //子类的b

System.out.println(super.a);

System.out.println(super.b); //调用父类的b要用super

super.methodB(); //调用父类的方法要写super 

methodB();这里调用自己,形成死递归

}

}

class c {

public static void main(String[] args) {

Derived s=new Derived();

s.a=2;

s.b=5;

s.methodB();

}

}

屏幕输出结果

Derived中的methodB()方法
2
5
200
201
Base中的methodB()

以上代码我们发现一个问题,为什么不直接将主方法写在methodB这个类里面呢,这样因为父子的关系可以直接引用变量及方法,不用去实例化对象了。见以下代码

class Base {
int a;
int b;
public void methodB(){
System.out.println("Base中的methodB()");
}
}
public class Derived extends Base{
int a;
int b;
public void methodB() {
public static void main(String[] args) {
super.a = 200;
super.b = 201;
a=2;
b=5;
System.out.println("Derived中的methodB()方法");
System.out.println(a);
System.out.println(b); //子类的b
System.out.println(super.a);
System.out.println(super.b); //调用父类的b要用super
super.methodB(); //调用父类的方法要写super 
methodB();这里调用自己,形成死递归
}
}

这个代码编译器会报错

这是因为super是不能用在静态代码块中( 因为子类继承到父类的东西也属于对象),而 super可以看成父类的对象名 (与this看成对象名一样),正好main方法是一个静态方法,故只能间接访问。
总结:
1.在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可
2.super只能在非静态方法中使用(因为子类继承到父类的东西也属于对象)
super 的其他用法在后文中介绍。

2.子类构造方法

父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。

public class Base {
public Base (){
System . out . println ( "Base()" );
}
}
public class Derived extends Base {
public Derived (){
// super(); // 注意子类构造方法中默认会调用基类的无参构造方法: super(),
// 用户没有写时 , 编译器会自动添加,而且 super() 必须是子类构造方法中第一条语句(类比this在构造方法的作用)
System . out . println ( "Derived()" );
}
}
public class Test {
public static void main ( String [] args ) {
Derived d = new Derived ();
}
}
结果打印:
Base ()
Derived ()
从以上代码可以看出:先执行基类的构造方法,然后执行子类的构造方
注意:
1. 若父类显式定义无参的构造方法,在子类构造方法第一行默认有隐含的 super() 调用,即调用基类构造方法
2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用(参数要一致),否则编译失败。
3. 在子类构造方法中, super(...) 调用父类构造时,必须是子类构造函数中第一条语句
4. super(...) 只能在子类构造方法中出现一次,并且不能和 this 同时出现(因为都要第一条)

3.superthis的对比

super this 都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?
相同点
1. 都是 Java 中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点
1. this 是当前对象的引用, super是父类对象的引用。this(...) 用于调用本类构造方法, super(...) 用于调用父类构造方法,两种调用不能同时在构造方法中使用。
class Base {
int a;
public Base(){
System.out.println("Base()");
}
}
public class Derived extends Base{
int a;
public Derived(){
System.out.println("Derived()");
}
this.a=1;  //看成Derived.a=1
super.a=2;  //看成Base.a=2
this();   //调用子类构造方法看成Derived()
super()  //调用父类构造方法看成Base()
}
//该代码没有主方法,不能运行,只是为了演示方便。
2. 构造方法中一定会存在 super(...) 的调用,用户 没有写编译器也会增加 ,但是 this(...) 用户不写则没有。

4. 再谈初始化

我们还记得之前讲过的代码块吗?我们简单回顾一下几个重要的代码块:实例代码块和静态代码块。在没有继承关系时的执行顺序。
1. 静态代码块先执行,并且只执行一次,在类加载阶段执行
2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
那么问题来了, 继承关系上的执行顺序是怎样的呢?看下面代码
class Person {
public String name ;
public int age ;
public Person ( String name , int age ) {
this . name = name ;
this . age = age ;
System . out . println ( "Person :构造方法执行 " );
}
{
System . out . println ( "Person :实例代码块执行 " );
}
static {
System . out . println ( "Person :静态代码块执行 " );
}
}
class Student extends Person {
public Student ( String name , int age ) {
super ( name , age );
System . out . println ( "Student :构造方法执行 " );
}
{
System . out . println ( "Student :实例代码块执行 " );
}
static {
System . out . println ( "Student :静态代码块执行 " );
}
}
public class TestDemo4 {
public static void main ( String [] args ) {
Student student1 = new Student ( " 张三 " , 19 );
System . out . println ( "===========================" );
Student student2 = new Student ( "gaobo" , 20 );
}
public static void main1 ( String [] args ) {
Person person1 = new Person ( "bit" , 10 );
System . out . println ( "============================" );
Person person2 = new Person ( "gaobo" , 20 );
}
//执行结果
Person :静态代码块执行
Student :静态代码块执行
Person :实例代码块执行
Person :构造方法执行
Student :实例代码块执行
Student :构造方法执行
===========================
Person :实例代码块执行
Person :构造方法执行
Student :实例代码块执行
Student :构造方法执行
通过分析执行结果,得出以下结论:
1 、父类静态代码块优先于子类静态代码块执行,且是最早执行
2 、父类实例代码块和父类构造方法紧接着执行
3 、子类的实例代码块和子类构造方法紧接着再执行
4 、第二次实例化子类对象时,父类和子类的静态代码块都将不会再执行
如果涉及了变量初始化怎么办呢?

首先说下类的加载(在生成class文件创建类时就执行了)会导致静态成员变量先初始化(父类的静态变量先初始化,子类的静态变量后初始化),而后是静态代码块初始化(父类的静态代码块先执行,子类的静态代码块再执行),因为类的加载只有一次,所以它们只能执行一次。然后就按上面的顺序。

三、protected关键字

在类和对象章节中,为了实现封装特性, Java 中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。
Java之继承和多态,java,python,前端
如果有时我们想要一个变量能在一个包之外去使用,但又不会像public范围那么大,这时就出现了protected这个修饰符。 允许变量还能在另一个包的所属子类中去使用。
我们先看在同一个包的情况(同一个包的情况下基本不会报错)

public class Date3 {

    protected int a=30;

}

class Date2 extends Date3{

    public void a(){

        System.out.println(super.a);

        System.out.println(a);

//以下的部分复杂化了,在继承关系中我们可以直接使用a,这里运行没有问题

Date3 date3 = new Date3();

        System.out.println(date3.a);

    }

    public static void main(String[] args) {

        Date2 date2  =new Date2();

        Date3 date3  =new Date3();

        System.out.println(date2.a);

        System.out.println(date3.a);

        date2.a();

    }

}

我们看下不同包的情况

package cot;

public class Date1 {

    protected int a=30;

}

package com;

import cot.Date1;

class Date2 extends Date1 {

    public void a(){

        System.out.println(super.a);

        System.out.println(a);

//以下代码会报错

Date3 date3 = new Date3();

        System.out.println(date3.a);

    }

    public static void main(String[] args) {

        Date2 date2  =new Date2();

        Date1 date1  =new Date1();

        System.out.println(date2.a);

       System.out.println(date1.a); //这里会报错:a 在 cot.Date1 中是 protected 访问控制

       date2.a();

    }

}

观察上面的代码,下面这个报错可以理解,因为他不是子类,protected要求在不同包的子类中可以访问,但是上面的报错它明明在子类中啊, 这里记住:protected修饰的变量或方法在不同包子类中只能通过super访问,而super不能出现在主方法中,所以只能通过方法间接访问。

package cot;

public class Date1 {

    protected int a=30;

}

package com;

import cot.Date1;

class Date2 extends Date1 {

    public void a(){

        System.out.println(super.a);

    }

    public static void main(String[] args) {

        Date2 date2  =new Date2();

       // Date1 date1  =new Date1();

       // System.out.println(date2.a);

        //System.out.println(date1.a);

        date2.a();

    }

}

//输出结果:30

注意:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中去了。 

四、final关键字

final 关键可以用来修饰变量、成员方法以及类。
1. 修饰变量或字段,表示常量 ( 即不能修改 )
final int a = 10 ;
a = 20 ; // 编译出错
2.修饰类:表示此类不能被继承
final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error :( 3 , 27 ) java : 无法从最终 com . bit . Animal 进行继
3. 修饰方法:表示该方法不能被重写(后面介绍)

五、继承与组合

和继承类似 , 组合也是一种表达类之间关系的方式 , 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法(诸如 extends 这样的关键字 ), 仅仅是将一个类的实例作为另外一个类的字段。
继承表示对象之间是 is-a(是a) 的关系 ,比如:狗是动物,猫是动物
组合表示对象之间是 has-a(有a)的关系,比如:汽车有轮胎、发动机、方向盘、车载系统等
汽车和其轮胎、发动机、方向盘、车载系统等的关系就应该是组合,因为汽车是有这些部件组成的。
// 轮胎类
class Tire {
// ...
}
// 发动机类
class Engine {
// ...
}
// 车载系统类
class VehicleSystem {
// ...
}
class Car {
private Tire tire ; // 可以复用轮胎中的属性和方法
private Engine engine ; // 可以复用发动机中的属性和方法
private VehicleSystem vs ; // 可以复用车载系统中的属性和方法
// ...
}
// 奔驰是汽车
class Benz extend Car {
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议: 能用组合尽量用组合。

多态

待更新文章来源地址https://www.toymoban.com/news/detail-856347.html

到了这里,关于Java之继承和多态的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Java 封装 继承 多态(深入理解)

    登神长阶 第二阶 封装 继承 多态 🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀🍀 目录 🍒一.面向对象编程的三大特性 🍍二.封装 🧉1.定义及其作用  🥝2.访问限定符 🫛3.封装扩展 包(package) 🥕3.1.定义及其作用  🥦 3.2.导入包的类 🍔3.3.自定义包 🌯

    2024年03月11日
    浏览(84)
  • 【Java初阶(六)上】封装 继承 多态

    ❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ 🚚我的代码仓库: 33的代码仓库🚚 对于面向对象程序三大特性:封装、继承、多态。这篇文章将会详细讲解到如何实现封装、继承、多态,以及具体的应用。 本章重点 掌握封装的概念,如何实现封装,包的概念,继

    2024年04月09日
    浏览(37)
  • Java面向对象 - 封装、继承和多态

    目录 第1关:什么是封装,如何使用封装 第2关:什么是继承,怎样使用继承 第3关:super的使用 第4关:方法的重写与重载 第5关:抽象类 第6关:final的理解与使用 第7关:接口 第8关:什么是多态,怎么使用多态 Java_Educoder

    2024年02月07日
    浏览(69)
  • Java系列——封装、继承、多态初了解

    目录 一、前言 二、封装 1.什么是封装?   2.封装的特点 3.封装的使用 三、继承 1.什么是继承? 2.继承的特点 3.继承的优点 4.继承的使用  4.1 继承的格式  4.2 继承的演示 4.3 成员变量 4.4 成员方法 4.5 构造方法 五、多态 1.什么是多态? 2.多态的特点 3.多态的使用  4.引用类型

    2024年02月08日
    浏览(32)
  • 【Java SE】如何解读Java的继承和多态的特性?

    什么是继承和多态,为什么被称为Java三大特性,这两大特性为我们程序员带来了什么影响呢?是让复杂的代码简化了,还是为程序员写代码提供了多样性呢?那让我们一起来揭开这层神秘的面纱吧! 1.1为什么需要继承 Java中使用类对现实世界中实体来进行描述,类经过实例化

    2024年02月05日
    浏览(32)
  • java基础语法-package构造方法-继承-多态

    java中的包 - package 包的主要功能: 包的基本语法 在一个文件中,可以没有包,或者一个包。但是不能出现两个包。 包名一般小写,是为了区分类名,类名一般大写 java中存在不同包相同类的名称,我们可以使用包名进行区分 一般情况下,在使用类的情况下,我们都使用类的

    2024年02月05日
    浏览(31)
  • 1.0、Java 继承与多态 - 成员变量访问特点

    父类:Father.java 文件如下所示 - 子类:Child.java 文件如下所示 - - 父类对象只能访问父类中的成员变量; - 而子类对象既可以访问父类中的成员变量,也可以访问自己类中的成员变量;   在父子类的继承关系当中,如果父类和子类的成员变量重名时,子类对象有下面两种访问

    2023年04月08日
    浏览(29)
  • Educoder/头歌JAVA——JAVA面向对象:封装、继承和多态的综合练习

    目录 第1关:封装、继承和多态进阶(一) 相关知识 面向对象思想 封装 继承 组合和继承 构造函数 super()和this() 编程要求 第2关:封装、继承和多态进阶(二) 相关知识 重写和重载 abstract(抽象类)和interface(接口) final static static的作用 多态 编程要求 第

    2024年02月04日
    浏览(45)
  • Java SE 继承和多态 (图文搭配,万字详解!!)

    目录 1.继承 1.1 为什么需要继承 1.2 继承概念  1.3 继承的语法 1.4 父类成员访问 1.4.1 子类中访问父类的成员变量 1.4.2 子类中访问父类的成员方法  1.5 super  1.6 子类构造方法 1.7 super和this 1.8 再谈初始化 1.9 protected 1.10 继承方式  1.11 final 1.12 继承与组合 2

    2024年02月05日
    浏览(41)
  • 图书管理借阅系统【Java简易版】Java三大特征封装,继承,多态的综合运用

    前言 前几篇文章讲到了Java的基本语法规则,今天我们就用前面学到的数组,类和对象,封装,继承,多态,抽象类,接口等做一个图书管理借阅系统。 Java语言是面向对象的,所以首先要分析完成这个图书管理系统,有哪些对象: 👱使用者User 📘书Book 📲操作Operation 使用者

    2024年02月14日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包