《Java编程思想》学习笔记_多态
多态
多态指一个行为产生多种状态,针对父类类型可接收其子类类型,最终执行的状态由具体子类确定,其不同子类可呈现出不同状态。例如人[父类]都会跑步[行为],但小孩[子类]跑步、成年人[子类]跑步、运动员[子类]跑步呈现出来的状态是不一致的。
例如:
创建一个父类People,和对应子类Child,Adult,Athletes.
子类都各自实现了一遍父类的run方法。
class People {
public void run() {
System.out.println("run");
}
}
class Adult extends People {
public void run() {
System.out.println("Adult run,1km 4分钟");
}
}
class Child extends People {
public void run() {
System.out.println("Child run, ,1km 3分钟");
}
}
class Athletes extends People {
public void run() {
System.out.println("Athletes run, ,1km 2分钟");
}
}
创建了一个run方法,接收参数为People类型。
public class Main {
public static void main(String[] args) {
Adult adult = new Adult();
Child child = new Child();
Athletes athletes = new Athletes();
run(adult);
run(child);
run(athletes);
}
private static void run(People people){
people.run();
}
}
Adult run,1km 4分钟
Child run, ,1km 3分钟
Athletes run, ,1km 2分钟
可以看到,接收类型为父类类型,具体传递的参数类型为子类类型。执行run方法时,执行的是具体子类的run方法。上述三个子类都对父类的run方法进行了重写。所有优先执行子类重写的方法,如果子类没有重写对应方法,执行的依然是子类方法。
class Adult extends People {
}
run
Child run, ,1km 3分钟
Athletes run, ,1km 2分钟
Adult修改为不重写父类方法,执行后Adult执行的就是父类的run方法。
既然方法类型为People可接收其所有子类类型。程序可改写为
public class Main {
public static void main(String[] args) {
People adult = new Adult();
People child = new Child();
People athletes = new Athletes();
run(adult);
run(child);
run(athletes);
}
}
使用多态可以确定一个规范,规范具体的实现可能有差异,但使用该规范时,只需要将接收参数设置为该规范,就可接收所有遵循该规范的实现。避免为每一个具体实现编写一个方法。
避免如下情况:
private static void run(Adult people){
people.run();
}
private static void run(Child people){
people.run();
}
private static void run(Athletes people){
people.run();
}
绑定
方法绑定
将方法调用桶一个方法主体关联起来成为方法绑定。方法绑定主要有前期绑定,后期绑定(运行时绑定,动态绑定)。
前期绑定
方法在编译时绑定,程序一但编译完成,绑定就完成了,方法调用与主体已经进行了关联,运行过程中始终只调用与之绑定的主体上的方法。
后期绑定
方法绑定是在运行时,根据判断对象类型,调用合适的方法。
上例中接收参数类型均为People,方法执行时根据传递入方法的不同子类执行具体方法。此时就是动态绑定,运行时方法调用会判断对象类型调用对应的方法。Java中private/final/static修饰的方法都是前期绑定,其余方法都是动态绑定。
多态的问题
域
如下例:
class A {
public int i = 0;
}
class A1 extends A {
public int i = 1;
}
public class Main {
public static void main(String[] args) {
A a = new A1();
System.out.println(a.i);
}
}
0
如果按照上面多态的的逻辑分析该代码,输出应该为子类的i即1.但实际输出的是父类的i值。
域的访问操作,都会有编译器解析,因此不是多态的。换而言之,只有方法才具有多态性。
A a = new A1();
a.i;
由于访问变量i不具有多态,同时此时A1向上转型为A。
访问a.i可看做访问A1类型的父类的i,即A1的super.i。
class A {
public int i = 0;
}
class A1 extends A {
public int i = 1;
public int getI(){
return i;
}
public int getAi(){
return super.i;
}
}
public class Main {
public static void main(String[] args) {
A a = new A1();
System.out.println(a.i);
A1 a1 = new A1();
System.out.println(a1.i);
System.out.println(a1.getI());
System.out.println(a1.getAi());
}
}
0 //A a = new A1(); a.i 实质是A的i
1 //A1 a1 = new A1(); a1.i A1的i
1
0 // A1 a1 = new A1(); a1.getAi(); super.i A的i
继承时,A与A1的域使用到的是两块区域存储对应变量,可看做A区域中有一个i,A1区域中也有一个i。此时由于父类和子类都有对应i,此时访问a.i虽然真实类型时A1,但A1继承自A,A a = new A1()
访问 a.i
,由于A1被向上转型为A,可看做访问A1的父类A的i。
private/static
class F {
private void fPrivate(){
System.out.println("F.fPrivate");
}
public static void fStatic(){
System.out.println("F.fStatic");
}
public static void main(String[] args) {
F f = new F1();
f.fPrivate();
f.fStatic();
}
}
class F1 extends F{
public void fPrivate(){
System.out.println("F1.fPrivate");
}
public static void fStatic(){
System.out.println("F1.fStatic");
}
}
其中f调用private方法和static方法,访问的都是父类中的方法,并未实现多态。
实现多态是基于动态绑定,确定具体方法调用的主体,private访问权限就相当于告诉编译器,该方法不需要动态绑定。当方法不要动态绑定时,就会以当前向上转型的类型为准。所以调用的父类的private方法。
静态方法是与类关联,而非与对象关联。所以调用时是以具体类型信息为准。也是以向上转型后的父类类型信息为准,调用父类的静态方法。
构造器的多态
假设现在有这样一个问题,有两个类是继承关系。子类和父类都有f()方法,在父类初始化时调用了f()方法,此时f方法究竟是子类的还是父类的方法?。
此处有一个两难的问题:
- 首先初始化子类时,父类必须先完成初始化。父类中的f()方法是动态绑定的,是运行时确定的。
- 运行时确定意味着需要对应子类也需要完成初始化,但在父类构造过程中调用的f()方法时,子类尚未初始化完成。
class A {
A() {
System.out.println("A构造器开始执行");
f();//此处调用的子类的f()方法,但此时子类还未完成初始化。
System.out.println("A构造器结束执行");
}
void f() {
System.out.println("A.f()");
}
}
class B extends A {
private int bi = 1;
B() {
}
B(int bi) {
this.bi = bi;
}
void f() {
System.out.println("B.f() bi=" + bi);
}
}
public class Main {
public static void main(String[] args) {
B b = new B(5);
}
}
A构造器开始执行
B.f() bi=0
A构造器结束执行
可以看到A构造器中执行时,执行的是子类B的f方法,但此时B类并未完全初始化,所以执行输出bi时输出的默认的0. 一个动态绑定的方法调用会向外深入到继承层次结构内部,它可以调用导出类(子类)里的方法。(《Java编程思想》原话)暂时还不太理解
。
协变返回类型
协变返回类型是指,但某个方法对父类进行重写时,返回类型不一定需要和父类方法的返回类型完全一致。可以是父类方法返回类型的子类。例如,父类方法A f()
,子类重写父类方法应为A f()
, 但子类重写时返回类型也可为A的子类,如A1 f()
.
首先有父类A子类A1作为返回类型
class A {
public String toString() {
return "A";
}
}
class A1 extends A {
public String toString() {
return "A1";
}
}
class F {
public A f() {
return new A();
}
}
class F1 extends F {
public A1 f() {// 子类重写父类方法,返回类型不一定为父类的A,也可为父类对应返回类型A的子类A1
return new A1();
}
}
public class Main {
public static void main(String[] args) {
F f = new F();
System.out.println(f.f());
f = new F1();
System.out.println(f.f());
}
}
A
A1
《Java编程思想》学习笔记_多态的更多相关文章
- [Java编程思想-学习笔记]第3章 操作符
3.1 更简单的打印语句 学习编程语言的通许遇到的第一个程序无非打印"Hello, world"了,然而在Java中要写成 System.out.println("He ...
- Java编程思想学习笔记_2(继承和多态)
静态初始化: 静态初始化只在必要的时刻进行.(即当程序需要加载类进入内存的时候,执行静态初始化.静态变量和静态代码块的初始化顺序,按照在代码中声明的顺序老执行.例如:如果要执行某个public类,那么 ...
- Java编程思想 学习笔记1
一.对象导论 1.抽象过程 Alan Kay曾经总结了第一个成功的面向对象语言.同时也是Java所基于的语言之一的Smalltalk的五个基本特性,这些特性表现了纯粹的面向对象程序设计方式 1)万物皆 ...
- [Java编程思想-学习笔记]第1章 对象导论
1.1 抽象过程 Java是一门面向对象的语言,它的一个优点在于只针对待解问题抽象,而不用为具体的计算机结构而烦心,这使得Java有完美的移植性,也即Java的口号"Write Once, ...
- Java编程思想 学习笔记11
十一.持有对象 通常,程序总是根据运行时才知道的某些条件去创建新对象.在此之前,不会知道所需对象的数量,甚至不知道确切的类型. Java实用库还提供了一套相当完整的容器类来解决这个问题,其中基本的类 ...
- Java编程思想学习笔记——类型信息
前言 运行时类型信息(RTTI:Runtime Type Information)使得我们可以在程序运行时发现和使用类型信息. Java在运行时识别对象和类的信息的方式: (1)一种是RTTI,它假定 ...
- Java编程思想 学习笔记12
十二.通过异常处理错误 Java的基本理念是“结构不佳的代码不能运行”. Java中的异常处理的目的在于通过使用少于目前数量的代码来简化大型.可靠的程序的生成,并且通过这种方式可以使你更加自信:你的 ...
- Java编程思想 学习笔记10
十.内部类 可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性.然而必须要了解,内部类和组合是 ...
- Java编程思想 学习笔记8
八.多态 在面向对象的程序设计语言中,多态是继数据抽象和继承之后的第三种基本特征. 多态通过分离做什么和怎么做,从另一角度将接口和实现分离开来. “封装”通过合并特征和行为来创建新的数据类型.“实现 ...
- Java编程思想 学习笔记7
七.复用类 1.组合语法 在新的类中产生现有类的对象.由于新的类是由现有类的对象所组成,所以这种方法叫做组合. 类中域为基本类型时能够自动被初始化为零.对象引用被初始化为null. 编译器不是简单地为 ...
随机推荐
- 手把手教会 VS2022 设计 Winform 高DPI兼容程序 (net461 net6.0 双出)
本文主要解决两个问题 C# Winform高DPI字体模糊. 高DPI下(缩放>100%), UI设计器一直提示缩放到100%, 如果不重启到100%,设计的控件会乱飞. 建立测试程序 新建.N ...
- javascript中的Ajax基础(一)
一.手写一个ajax 1 const xhr = new xmlHttpRequest() 2 3 xhr.open(请求方式:post get, 请求地址, 同步或者异步) 4 5 xhr.onre ...
- Go汇编语法和MatrixOne使用介绍
目录 MatrixOne数据库是什么? Go汇编介绍 为什么使用Go汇编? 为什么不用CGO? Go汇编语法特点 操作数顺序 寄存器宽度标识 函数调用约定 对写Go汇编代码有帮助的工具 avo tex ...
- 【vue】$attrs的作用和使用方法
之前一直不了解$attrs的作用和使用场景,然后自己翻阅了相关资料整理了下,如有不对的地方请大家指教 $attrs: $attrs是vue版本2.40以上新增的属性: 使用场景: vue项目里面,大家 ...
- 【前端干货】别再羡慕别人的Excel啦,教你点击按钮直接打开侧边栏!
负责技术支持的葡萄又来啦. 三日不见,我们的客户又为我们发来新的问题. 这次我们需要实现的场景是在前端表格环境中,像模板按钮那样,点击之后弹出一个侧边栏,然后通过点击不同的单元格显示不同的内容. 挤接 ...
- 【已解决】Redis错误:Could not create server TCP listening socket 127.0.0.1:6379: bind: 操作成功完成。
报错:redis服务在window下启动,报错: Could not create server TCP listening socket 127.0.0.1:6379: bind: 操作成功完成. ...
- 2.SSH协议常见问题排错
一.SSH登录linux服务器密码验证很慢 现象:ssh登录服务器后,输入密码时,验证要等10秒左右,很慢.登录上去后速度正常,这种情况主要有两种可能的原因: 1. DNS反向解析的问题 OpenSS ...
- kafka基础原理
1.什么是kafka Kafka 是一个分布式的基于发布/订阅模式的消息队列 消息队列的两种模式: 点对点模式(一对一,消费者主动拉取数据,消息收到后消息清除).特点,一个消息只能被一个消费者消费 发 ...
- 定时 ——setTimeout | setInterval
使用场景,setTimeout 只调用一次,setInterval 会重复调用,直到清除或重载. <div id="countDown"></div> &l ...
- ElasticSearch7.3学习(二十五)----Doc value、query phase、fetch phase解析
1.Doc value 搜索的时候,要依靠倒排索引: 排序的时候,需要依靠正排索引,看到每个document的每个field,然后进行排序. 所谓的正排索引,其实就是doc values. 在建立索引 ...