Java入门part6--继承和多态
继承
// Animal.java
public class Animal {public String name;public Animal(String name) {this.name = name;}public void eat(String food) {System.out.println(this.name + "is eating" + food);}
}
// Cat.java
class Cat {public String name;public Cat(String name) {this.name = name;}public void eat(String food) {System.out.println(this.name + "is eating" + food);}public void jump() {System.out.println(this.name + "is jumping");}
}
// Bird.java
class Bird {public String name;public Bird(String name) {this.name = name;}public void eat(String food) {System.out.println(this.name + "is eating" + food);}public void fly() {System.out.println(this.name + "is flying");}
}
观察可见:
- Animal,Cat,Bird三个类都有相同的方法eat(),且实现的功能一样
- 三个类都具有同样的属性name
- 从逻辑上讲,Cat,Bird都是Animal的一种(他们之间是is-a关系)
所以我们可以让Cat,Bird继承Animal类,实现代码复用。本质上来讲继承就是为了代码的复用
继承的语法规则
extends
用关键字extends来实现继承,
class 子类/派生类 extends 父类/基类/超类{}
(像Cat,Bird这种类就叫做子类/派生类,而Animal这种被继承的类叫做父类/基类/超类)
注意:
- 使用 extends 指定父类.
- Java 是单继承,也就是说一个子类只能继承一个父类 (而C++/Python等语言支持多继承).
- 子类会继承父类的所有 public 的字段和方法.
- 对于父类的 private 的字段和方法, 子类中是无法访问的.
- 子类的实例中, 也包含着父类的实例. 可以使用 super 关键字得到父类实例的引用.
super
super();//调用父类的构造方法 必须放在第一行 因为要构造子类要先构造父类
super.func();//调用父类的方法func()
super.data; //调用父类的数据成员data
父类只能访问自己的成员 或者是方法
但子类可以通过关键字super访问父类的成员和方法
所以我们可以将Animal,Cat,Bird的代码优化如下:
// Animal.java
public class Animal {public String name;public Animal(String name) {this.name = name;}public void eat(String food) {System.out.println(this.name + "is eating" + food);}
}
// Cat.java
class Cat extends Animal{public Cat(String name) {super(name);}public void jump() {System.out.println(this.name + "is jumping");}
}
// Bird.java
class Bird extends Animal{public Bird(String name) {super(name);}public void fly() {System.out.println(this.name + "is flying");}
}
此时只需将Cat,Bird类中Animal有的字段和方法删除即可
注意:
在子类的构造方法中一定要先用super()构造父类,构造完后再构造子类自己
但是此时如果想要实现封装,将父类的name属性变为private,那么编译就会出错,因为子类无法访问private修饰的方法和字段,此时应该如何实现封装呢?
protected关键字
使用protected关键字就很好的解决了这个问题:
- 对于类的调用者来说,并不能访问protected修饰的字段和方法
- 但对于子类来说,protected所修饰的字段和方法是可以访问的
此处拓展几个权限修饰关键字的修饰范围:(default是什么关键字都不加)
范围 | private | default | protected | public |
---|---|---|---|---|
同一包中同一类 | √ | √ | √ | √ |
同一包中不同类 | × | √ | √ | √ |
不同包中的子类 | × | × | √ | √ |
不同包中非子类 | × | × | × | √ |
注意:
final所修饰的类不可被继承
注意区分继承和组合
- 继承是一种
is-a
关系 - 组合是一种
has-a
关系,是一种包含关系
组合并没有涉及到特殊的语法(诸如 extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.比如:
class Student{...
}
class Teacher{...
}
class School{public Student[] students;public Teacher[] teacher;...
}
多态
向上转型
即将子类的值赋值给父类 /父类引用子类对象,比如:
Cat cat = new Cat("大不妞");
可以写成
Animal cat = new Cat("大不妞");
此时 cat 是父类 (Animal) 的引用, 指向子类 (Cat) 的实例. 这种写法称为 向上转型
向上转型发生的时机 :
- 直接赋值
- 方法传参
- 方法返回
上面列举的是直接赋值,方法传参和方法返回见下:
方法传参的形式()
public class Test {public static void main(String[] args) {Cat cat = new Cat("大不妞");feed(cat);}public static void feed(Animal animal) {animal.eat(" fish");}
}
方法返回
public class Test {public static void main(String[] args) {Animal animal = findMyAnimal();}public static Animal findMyAnimal() {Cat cat = new Cat("大不妞");return cat;}
}
动态绑定
将前面示例的代码稍作改动,让子类Cat和Animal有一个同名但实现功能不同的方法eat();
// Animal.java
public class Animal {public String name;public Animal(String name) {this.name = name;}public void eat(String food) {System.out.println("我是一只小动物");System.out.println(this.name + "is eating" + food);}
}
// Cat.java
class Cat extends Animal{public Cat(String name) {super(name);}public void eat(String food) {System.out.println("我是一只小猫咪");System.out.println(this.name + "is eating" + food);}
}class Demo0223 {public static void main(String[] args) {Animal animal1 = new Animal("大不妞");animal1.eat(" fish");Animal animal2 = new Cat("大不妞");animal2.eat(" fish");}
}
执行结果:
此时, 我们发现:
- animal1 和 animal2 虽然都是 Animal 类型的引用, 但是 animal1 指向 Animal 类型的实例, 而animal2 指向的是Cat 类型的实例.
- animal1 和 animal2 分别调用 eat 方法, 发现 animal1.eat() 实际调用了父类的方法, 而animal2.eat() 实际调用了子类的方法.
由此可得:
在 Java 中, 子类和父类拥有同名方法,此时调用该方法时究竟执行的是子类的方法还是父类的方法 , 要看究竟这个引用指向的是子类对象还是父类对象. 这个过程是程序运行时决定的(而不是编译期), 因此称为 动态绑定
反汇编发现程序编译时确实调用的是父类的方法 但是运行时却调用的子类的方法 这就是运行时绑定(也叫动态绑定),这就是所谓的 编译看左,运行看右
方法重写(override)
像是上述代码当中的eat():
子类实现父类的同名方法, 并且参数的类型和个数完全相同, 这种情况称为 方法覆写/重写/覆盖(Override)
方法重写的注意事项:
- 普通方法可以重写, static 修饰的静态方法不能重写
- 重写中子类的方法的访问权限不能低于父类的方法访问权限(也就是说如果父类方法是用protected修饰,那么子类方法肯定不能是public修饰)
对于重写的方法可以显示的给一个注解@override
class Cat extends Animal{public Cat(String name) {super(name);}@overridepublic void eat(String food) {System.out.println("我是一只小猫咪");System.out.println(this.name + "is eating" + food);}
}
这样做的好处在于这个注解能帮我们进行一些合法性校验.
例如不小心将方法名字拼写错了 (比如写成 aet), 那么此时编译器就会发现父类中没有 aet 方法, 就会编译报错, 提示无法构成重写.
重写和重载的区别:
方法重写 | 方法重载 | |
---|---|---|
方法名 | 相同 | 相同 |
参数列表 | 相同 | 不同 |
返回值 | 相同 | 不做要求 |
范围 | 继承 | 同一个类 |
限制 | 被重写的方法不能拥有比父类更严格的访问控制权限 | 没有访问控制权限要求 |
发生多态要满足两个条件:(这个多态叫做运行时多态)
- 父类需要引用子类对象(即向上转型)
- 通过父类的引用调用子类和父类同名的覆盖方法
class对象存储位置在方法区
反射: 获取class对象(用三种方法会发现class对象地址一样==》class对象只有一个)
向下转型
向下转型是将子类对象转给父类,一般不太常见,下面将介绍他的作用
还是刚刚这段代码
// Animal.java
public class Animal {public String name;public Animal(String name) {this.name = name;}public void eat(String food) {System.out.println("我是一只小动物");System.out.println(this.name + "is eating" + food);}
}
// Cat.java
class Cat extends Animal{public Cat(String name) {super(name);}@overridepublic void eat(String food) {System.out.println("我是一只小猫咪");System.out.println(this.name + "is eating" + food);}public void jump() {System.out.println(this.name + "is jumping");}
}
让猫咪吃东西
Animal animal = new Cat("大不妞");animal.eat(" fish");
//执行结果
//大不妞 is eating fish
如果我们想让猫咪跑起来
animal.jump();
此时编译出错,找不到jump();
方法
因为编译看左,运行看右,编译时期编译器先在Animal类中看有没有jump方法,没有所以直接编译出现错误
那如果想要让猫咪跑起来就只能
Animal animal = new Cat("大不妞");Cat cat = (Cat)animal;animal.jump();
这种就是向下转型,但是向下转型存在风险,比如:
Animal animal = new Bird("啾啾");Cat cat = (Cat)animal;animal.jump();
//此时执行会抛出类型转换异常 java.lang.ClassCastException
因为本质上animal是一个Bird类型的,和Cat直接没有关系,所以就会出现类型转换异常
==》要发生向下转型最好先判断是否是一个实例
instanseof
instanseof可以判定一个引用是否是某个类的实例
if(Animal instanseof Cat){Cat cat=(Cat)animal;cat.jump();
}
构造方法内是否可以发生运行时绑定?
答案是可以,例子见下
class A {public A() {func();}public void func() {System.out.println("A.func()");}
}class B extends A {private int num = 1;@Overridepublic void func() {System.out.println("B.func() " + num);}
}
public class Test {public static void main(String[] args) {B b = new B(); }
}
// 执行结果
B.func() 0
为什么执行出来的num会是0?
构造子类对象前要先构造父类
所以构造 B 对象的同时, 会调用 A 的构造方法.
A 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 B 中的 func
此时 B 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0.
使用多态的好处是什么?
类调用者对类的使用成本进一步降低.
封装 是让类的调用者不需要知道类的实现细节.
多态 能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可.
因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低.能够降低代码的 “圈复杂度”(一段代码中的分支和循环语句越多,圈复杂度越高), 避免使用大量的 if - else
可扩展能力更强,使用多态的方式代码改动成本也比较低.
Java入门part6--继承和多态相关推荐
- JAVA入门_继承与重载_饲养员喂养动物
JAVA入门_继承与重载_饲养员喂养动物 实验要求 Tiger类 Feeder类 MainClass 运行结果 实验要求 本实验要求:本实验以饲养员喂养老虎为业务背景,体验"函数重载&quo ...
- Java实验3继承、多态
继承.多态(接口和包) 实验目的 (1) 掌握Java语言中继承和多态的相关概念 (2) 掌握Java程序设计中继承和多态机制的使用 (3) 掌握Java语言中接口和包的概念及使用 实验内容及要求 仿 ...
- Java中的继承 与 多态(中)
先导: 我们在<Java中的继承 与 多态(上)>当中讲解了如下几个问题, 1.继承是什么 2.super关键字 3.特殊考点-父子类中不同代码块的实现顺序 所以现在我们对于继承 ...
- Java基础:继承、多态、抽象、接口
第一讲 继承 一.继承概述 1.多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可. 2.通过extends关键字可以实现类与类的 ...
- Java基础篇--继承(inherit),多态(Polymorphism)
Java基础篇--继承(inherit),多态(Polymorphism) 1. 继承概述 1.1 什么是继承 1.2 为什么要使用继承 1.3 继承的特点 1.4 继承的优点 2. 组合设计模式 2 ...
- Java回顾 封装 继承和多态
封装 什么是封装 封装:就是隐藏对象的属性和实现细节,仅对外提供公共访问方式. 封装时的权限控制符区别如下: 封装的意义 对于封装而言,一个对象它所封装的是自己的属性和方法,所以它是不需要依赖其他对象 ...
- 【java笔记】继承与多态
多态性的前提:extends继承或者implements实现 继承与多态: 定义:同一个操作被不同类型对象调用时可能产生不同 的行为 解释:如果一个类有很多子类,并且这些子类都重写了父类中的某个方法, ...
- Java分别采用继承、多态、抽象类、接口实现猫和狗的入门案例
目录 采用继承的思想实现猫和狗的案例 采用多态的思想实现猫和狗的案例 采用抽象类的思想实现猫和狗的案例 采用接口的思想实现猫和狗的案例 采用继承的思想实现猫和狗的案例 分析: ①猫: 成员变量:姓名, ...
- java中的多态与继承_【Java学习笔记之十六】浅谈Java中的继承与多态
1. 什么是继承,继承的特点? 子类继承父类的特征和行为,使得子类具有父类的各种属性和方法.或子类从父类继承方法,使得子类具有父类相同的行为. 特点:在继承关系中,父类更通用.子类更具体.父类具有更 ...
最新文章
- 使用netty实现一个http挡板,轻量又实用。收藏起来吧
- ipad xcode连接不了iPad的。
- SqoopFlume、Flume、HDFS之间比较
- 【新手教程】CE找武林外传基址方法
- Python is同一性运算符和==相等运算符区别
- Docker上部署GitLab服务器
- ios 画带有箭头的线_ios纯色箭头与渐变色箭头的实现
- eclipse android 服务端,Eclipse搭建服务器,实现与Android的简单通信
- ERP实施过程中的沟通管理研究
- ThinkPHP学生管理系统
- vs2010断点调试详细教程
- 手工编译Flex SDK 多国语言包
- 国产低代码开发平台,这5个值得一试
- 第3章【思考与练习4】数据清洗,从studentsInfo.xlsx 文件的“Group1”表单中读取数据。数据填充,使用习题1的数据,使用列的平均值填充“体重”和“成绩”列的NaN数据。
- @Autowired和@Resource的区别
- 如何在outlook里面撤回邮件?邮件撤回成功后对方还能不能看到?
- Linux这些年经历了什么?
- css 将标签固定在底部
- cadworx可以画设备流程图吗,CADWORX中心线法绘制管道
- 【中医百科app——中药、方剂、针灸】养生必备,随时随地查看症状
热门文章
- python 仪表盘监控_Python 全栈开发 -- 监控篇
- 概率与数理统计——大数定律
- 常用眼底图像数据集简介及下载--糖尿病视网膜病变 EyePacs,APTOS2019,STARE数据集
- ros实验操作——订阅者Subscriber的编程实现
- 小米商城抢购脚本_小米10系列MiCare保障服务上线:免费2次换屏 549元起
- JMockit 指南 翻译
- ESP8266(基于arduino平台)每篇一个知识点—1.wifi STA模式连接指定wifi
- Python软件编程等级考试三级——20210905
- python爬虫网易云_Python爬虫网易云音乐Top50热门歌单
- 【python爬虫】爬取Bing词典的单词存到SQLite数据库(加了pyqt5界面显示)