java 多态判断非空_重拾JavaSE基础——多态及其实现方式
今天是比较抽象的多态,希望能给大家带来帮助
主要内容
多态
为什么使用多态
多态的形式
多态的概念
多态的劣势
多态存在的必然条件
类型转换
多态的实现原理
多态的分类
运行时多态的形式
实现原理
常量池
方法调用方式
动态绑定实现多态
写在最后
多态
先说好不钻牛角尖哈,多态Java的特性之一,先不着急说他的概念,先看看为什么要使用多态,多态给我们带来什么好处
为什么使用多态
举个例子吧,老奶奶喜欢养宠物,领养了一只加菲猫,加菲猫是只小动物,要吃饭,老奶奶每天负责喂它。Java翻译过来就是下面这样子的
// 老奶奶
public class Granny {
public static void main(String[] args) {
// 领养一只加菲猫,这里简单的new出来了
Garfield garfield = new Garfield();
// 抱起加菲猫给它喂食
feed(garfield);
}
public static void feed(Garfield garfield) {
// 加菲猫吃东西
garfield.eat();
}
}
class Garfield extends Animal{
@Override
public void eat() {
System.out.println("加菲猫吃饱了");
}
}
abstract class Animal {
public abstract void eat();
}
一切都很顺畅。但是这时候老奶奶又去领养了一只牧羊犬,牧羊犬也是小动物,也要吃饭,老奶奶也要给他喂食,这时候代码要添加一个牧羊犬类,老奶奶要添加一个给牧羊犬喂食的方法
public class Granny {
public static void main(String[] args) {
// 领养一只加菲猫,这里简单的new出来了
Garfield garfield = new Garfield();
// 抱起加菲猫给它喂食
feed(garfield);
// 领养一只牧羊犬
Shepherd shepherd = new Shepherd();
// 老奶奶给他喂食
shepherd.eat();
}
public static void feed(Garfield garfield) {
// 加菲猫吃东西
garfield.eat();
}
public static void feed(Shepherd shepherd) {
// 加菲猫吃东西
shepherd.eat();
}
}
class Shepherd extends Animal{
@Override
public void eat() {
Systen.out.println("牧羊犬吃的很开心");
}
}
// 加菲猫
class Garfield extends Animal{
// ...
}
如果老奶奶还想继续领养小动物,老奶奶又要给这只小动物创建一个新的喂食的方法。聪明的我给老奶奶指了条明路,只要把feed方法的参数范围扩大一点,不要指定是加菲猫还是牧羊犬z,只要是小动物都给他喂食,反正小动物都有吃的方法。
public class Granny {
public static void main(String[] args) {
// 领养一只加菲猫,这里简单的new出来了
Garfield garfield = new Garfield();
// 抱起加菲猫给它喂食
feed(garfield);
// 领养一只牧羊犬
Shepherd shepherd = new Shepherd();
// 老奶奶给他喂食
shepherd.eat();
}
// 扩大了范围
public static void feed(Animal animal) {
// 给动物喂食
animal.eat();
}
}
这样老奶奶就舒服了,所以多态的好处之一就是方便传参。
后来老奶奶发现自己家里的动物越来越多,受不了了决定只养一只其他的都卖了,于是老奶奶选择留下加菲猫又回到了最初的日子
public class Granny {
public static void main(String[] args) {
// 领养一只加菲猫,这里简单的new出来了
Garfield garfield = new Garfield();
// 抱起加菲猫给它喂食
feed(garfield);
}
public static void feed(Garfield garfield) {
// 加菲猫吃东西
garfield.eat();
}
}
但是养了一段时间老奶奶觉得加菲猫老在家躺着没什么意思,想念牧羊犬了,于是把加菲猫丢了换回牧羊犬,将原来Garfield garfield = new Garfield();改为
Shepherd shepherd = new Shepherd();
又过了一段时间老奶奶觉得不行,牧羊犬吃得太多了开销顶不住,还是加菲猫好,于是他又把代码改回来了,又将Shepherd shepherd = new Shepherd();改回
Garfield garfield = new Garfield();
我见老奶奶都一把年纪了,改来改去还挺麻烦的,就跟她说你要不定义一个Animal类的annimal变量代表你的宠物把,像这样
Animal animal = new Garfield();
这样换宠物只要改new后面的就行了,老奶奶一听觉得很有道理,所以多态的另一个好处就是右边的对象可以组件化切换,业务功能也会随之改变
在我们开发中也常常使用多态,大家回忆一下一个Service需要依赖其他Service,是不是这样写的
@Resource
private IUserServiceImpl userService;
总结:多态的优势可以总结成两个点:方便入参和实现组件化切换:
多态的形式
子类继承父类
父类 变量名称 = new 子类构造器
实现类实现接口
接口 变量名称 = new 实现类构造器
多态的概念
看完上面的内容,会有一种感觉,多态的风格其实是定义变量的时候把类型范围扩大,如上面的例子,老奶奶以后都会把他的宠物们定义成这样
Animal garfield = new Garfield();
Animal shepherd = new Shepherd();
定义加菲猫和牧羊犬的时候声明的都是Animal类型,但他们的eat方法是不一样的。同一种类型的对象执行同一个行为(方法)会得到不同的结果,这个就是多态的概念
多态只是一种编程风格,没有要求一定要遵循,只是使用了多态会有他的好处,多态已经成为大家公认且遵守的Java特性,顺着趋势走就OK
多态的劣势
这里有个小插曲,为什么老奶奶一开始会放弃加菲猫选择牧羊犬,因为牧羊犬可以帮忙看家,这是他的独有功能
class Shepherd extends Animal{
private Integer i = 0;
@Override
public void eat() {
Systen.out.println("牧羊犬吃的很开心");
}
public void lookDDoor() {
Systen.out.println("这是牧羊犬的超能力");
}
}
但是他发现自从用了多态后,再也无法让牧羊犬去看门了
public class Granny {
public static void main(String[] args) {
// 领养一只牧羊犬
Animal shepherd = new Shepherd();
// 看门
shepherd.lookDoor(); // 报错
}
}
大家可以先认为Animal shepherd = new Shepherd();进行了自动转型,shepherd已经没有看家的方法了,所以多态的劣势就是子类失去了独有的行为,而且连成员变量都不能直接访问(只能借助重写的方法去访问)
public static void main(String[] args) {
// 领养一只加菲猫,这里简单的new出来了
Garfield garfield = new Garfield();
garfield.i;// 报错
}
这时候需要使用强制类型转换来解决问题,至于为什么不能调用子类的方法相信看完后面你就懂啦
多态存在的必然条件
必须存在继承关系
必须是父类/接口类型变量引用子类/实现类类型变量
必须存在重写方法
类型转换
大家可以先记住语法,回头就能理解转换到底是在干嘛了
自动转换
Animal garfield = new Garfield();
子类类型会自动转换成父类类型,其实就是多态的默认写法
强制类型转换
子类 新变量名称 = (子类) 需要转换的变量名称
如
Animal garfield = new Garfield();
// garfield = (Garfield)garfield 必须用新的引用接收
Garfield garfield2 = (Garfield)garfield;
注意:必须使用新的变量去接收
强制类型转换的时候需要对类型进行判断
在老奶奶养加菲猫和牧羊犬的时候有一个小插曲,加菲猫很贪吃,一顿要吃多点,老奶奶也没办法,只能给他加餐,但是使用了多态,喂猫喂狗的方法都是同一个`feed`,有没有办法可以判断一下入参到底是加菲猫还是牧羊犬呢,那肯定是有的
public static void feed(Animal animal) {
// 判断是不是加菲猫,是的话给他加餐
if (garfield instanceof Garfield) {
System.out.println("加餐");
animal.eat();
}
}
到底是加菲猫还是牧羊犬只有代码运行的时候才知道,intanceof可以判断运行引用animal的实际类型是否为Garfield类
多态的实现原理
一个对象变量可以指向多种实际类型的现象成为多态,这导致一个对象变量调用同一个方法的时候得到了不同的结果。感觉非常抽象,看下面的例子
一只猫有两个个eat方法,一个无参一个有参
class Cat {
public void eat() {
System.out.println("猫会吃饭")
}
public void eat(Integer weight) {
System.out.println("猫会吃饭,吃了" + weight)
}
}
当主函数运行以下代码的时候
Cat cat = new Cat();
cat.eat();
cat.eat(10)
回想刚刚的概念,是不是同一个变量cat,调用同一个方法eat,但结果是不一样。这就是编译时多态,在编译成class文件的时候就可以确定,程序执行的eat方法是Cat类中的成员方法,而且根据形参也可以知道是哪个eat方法,
方法签名和返回参数相同看作同一个方法。这种形式成为方法重载(Overload)
再看下一种情况,猫类继承了动物类,重写了动物类的eat方法
ublic class Animal {
public void eat() {
System.out.println("动物可以走路");
}
}
class Cat {
@Override
public void eat() {
System.out.println("猫会走路");
}
}
现在有一个feed喂养的方法,需要传入一个动物类型
public void feed(Animal animal) {
animal.eat();
}
在编译的时候不能确定animal到底是什么类型的,可能是加菲猫可能是牧羊犬,准确点应该是计算机不知道animal实际是什么类型的,但程序员知道。这种就是我们最常用的多态,叫运行时多态,由于不确定传入的参数是什么类型的,同一个变量animal调用同一个方法eat产生的结果是不一样的
多态的分类
根据上面的例子,多态可以分为
编译时多态(静态多态)
运行时多态(动态多态)
后面所提到的多态都是运行时多态
运行时多态的形式
就是上面提到过的那两种
子类继承父类
父类 变量名称 = new 子类构造器
实现类实现接口
接口 变量名称 = new 实现类构造器
实现原理
尽量用通俗的话去解释,如果理解有误麻烦评论区告诉我
常量值
大家肯定听过,编译器把源代码编译成class文件的时候,会把一些常量信息统一放在class文件的一块区域,大家可以用字节码分析工具随便打开一个class文件就能看到c常量池了,这种写在文件里面的常量信息被称为静态常量池,当class文件被加载到虚拟机的时候,会在方法区开辟一段空间存放这些常量信息,这个区域就叫做运行时常量池
常量池存了哪些信息
可以看下图,其实很像我们的数据库,
注意:因为!class文件还没被加载,所以现在用分析工具展示的是静态常量池,里面包含一些符号引用(就是一个名字),加载到方法区后会替换成直接引用(内存地址)
CONSTANT_utf8_info
基本信息都存在CONSTANT_utf8_info,里面保存了这个类里面的成员方法的名字、我们定义的字符串常量(System.out.println(...)里面的字),引用类型类名(如Cat、Animal),变量名(如cat)等等
Length of bytes array; 6
length of String: 6
String: Animal
CONSTANT_Class_info
保存对其他类的符号引用(Class_name)和在CONSTANT_utf8_info的引用
Class_name:cp info #25
CONSTANT_NameAndType_info
保存方法和字段的类型和名称,还有描述符信息(入参和返回值)
Name: cp info #15
Descriptor: cp info #18
Name: cp info #28
Descriptor: cp info #10
里面的V表示返回值为空
CONSTANT_Methodref_info
保存方法的方法名称的索引和该方法所属的类名的索引,这个相当于中间表
Class_name: cp info #22
Name_and_type: cp info #23
CONSTANT_interfaceMethod_info
和CONSTANT_Methodref_info类似,保存了接口方法的名称和类型的索引和接口的索引
所有的表最终信息都保存在CONSTANT_utf8_info种,看上去就像我们的数据库表设计一样
方法调用方式
Java的方法调用方式有两种,静态调用和动态调用
静态调用
顾名思义,就是A类调用B类的静态成员方法,也就是说调用的时候很明确,我要调用方法区里面那个叫B类的那个静态方法,最后会把B类的静态方法的字节码地址替换运行时常量池对应的表符号引用,替换的过程称为静态绑定,调用绑定后的方法称为静态调用
StringUtils.isBlank();
类调用(invokestatic)在编译的时候计算机已经很明确要调那个方法了,只要类被加载到方法区,一切都顺利
注意:Java中只有被private、static、final修饰 的方法属于静态
动态调用
如果要调用动态成员变量的方法就比较麻烦了,必须先去堆中找到对应的对象,然后根据对象的信息找到对应的方法的字节码地址,保存到堆中,对象中为什么会有方法的字节码地址呢,这是动态绑定完成的操作,具体后面再说,调用动态绑定后的方法被称为动态调用
cat.eat();
实例调用(invokevirtual)就需要等到对象被创建的时候才能指定调用哪个方法
JVM调用方法的指令:
静态调用:invokestatic、`invokespecial
动态调用:invokeinterface、invokevirtual
实例化
这里需要说明的是,类如
Animal cat = new Cat();
这种形式对于cat来说他是Animal类型的,但在堆中开辟的是Cat类的对象空间,并由this指针指向Cat实例,所以cat的实际类型其实是Cat类
动态绑定实现多态
子类继承父类
方法表是在方法区中有一个集合,专门存放方法名称和代码指针,代码指针指向存放方法体字节码的内存地址。这里需要强调的是,如果是子类重写了父类的方法或者实现类实现了接口的方法,指针是指向重写的方法的
如下面的代码
public class Main {
public static void main(String[] args) {
Animal cat = new Cat();
cat.run();
}
}
class Animal {
public void play() {
System.out.println("父类方法");
}
public void run() {
System.out.println("父类方法");
}
public void eat() {
System.out.println("父类方法");
}
}
class Cat extends Animal {
@Override
public void run() {
System.out.println("子类方法");
}
}
对于Animal和Cat类,方法表是这样的
当调用Cat的run方法的时候,字节码为invokevirtual #15,JVM先在常量池查CONSTANT_Methodref_info -> CONSTANT_NameAndType_info -> CONSTANT_utf8_info,查出来现在需要调用的是Animal类中run方法,然后去Animal的方法表里面找run方法,记录以下偏移量offset,再调用invoke this,offset,这时候的this指针正指向的是堆中的Cat对象,Cat也有一张方法表,恰好数下来offset就是子类的run方法,于是找到Cat类的run方法的字节码地址,顺利调用。所以动态调用的核心就在于这个方法表和this指针的设计
实现类实现接口
接口可以多继承的,大家看下面的例子会发现用偏移量无法实现动态调用
interface A {
public void a1();
public void a2();
public void a3();
}
interface B {
public void b1();
}
class TestA implements A{
// 重写三个方法
}
class TestAB implements A, B {
// 从写四个方法
}
public class Main {
B testAB = new TestAB();
testAB.b1();
}
很明显接口B的b1方法的偏移量和实现类TestAB不一样,所以JVM提供了invokeinterface方法,它不再使用偏移量,而是使用搜索的方式寻找合适的方法,所以调用接口的方法会比调用子类的慢
为什么不能调用子类中非重写的方法
因为在父类的方法表压根就没与那个方法,例如上面的例子,如果run是Cat独有的方法,在父类Animal中就没有这个方法,就不能进行动态绑定了
那大家可以想一下强制类型转换到底是在干嘛
写在最后
写这篇文章之前我是完全不知道多态是怎么实现的,我也是一边查资料一边研究,希望能帮助大家理解多态
java 多态判断非空_重拾JavaSE基础——多态及其实现方式相关推荐
- java final类的写法_重拾JavaSE基础——抽象类、接口、代码块、final和枚举
今天继续回顾Java基础,有些东西用得不多,大家看看知道语法就好 主要内容 抽象类 抽象方法 抽象方法的写法 抽象方法是否可以私有化 抽象类的特征 抽象类有无构造器,能否实例化对象 抽象类的结构 抽象 ...
- java 多态判断非空_收藏Java 面试题全梳理
脚本之家 你与百万开发者在一起 来源 | Java建设者(ID:javajianshe) 作者 |cxuan 如若转载请联系原公众号 Java 基础篇 Java 有哪些特点 并发性的:你可以在其中执行 ...
- java 多态判断非空_跳槽涨薪季面试题之java基础(一)
点击上方[全栈开发者社区]→右上角[...]→[设为星标⭐] 为迎接金九银十跳槽涨薪季,小编汇总了java精编版面试题,大概从java基础.java8特性.多线程.spring.springboot. ...
- java 多态判断非空_Java 面试知识点解析基础知识
文本公众号来源: 我没有三颗心脏作者: 我没有三颗心脏 (一)Java 基础知识点 1)面向对象的特性有哪些? 答:封装.继承和多态(应要多算一个那就是抽象) 封装是指将对象的实现细节隐藏起来,然后通 ...
- java 对象验证非空_判断Bean对象指定字段非空
判断Bean对象指定字段非空. 方案: 在bean对象上增加注解,指定字段非空,返回异常信息有明确字段描述,省去不必要的if.else判断. 新建注解类 /** * 参数校验,判断字段非空.返回异常文 ...
- java 多态判断非空_Java核心技术(四):继承
本章目录: 一.类.超类和子类 1.多态 2.动态绑定 3.阻止继承:final类和方法 4.抽象类 5.访问修饰符总结 二.Object类:所有类的父类 1.equals方法 2.hashcode方 ...
- java 多态判断非空_Java多态性理解
Java中多态性的实现 什么是多态 面向对象的三大特性:封装.继承.多态.从一定角度来看,封装和继承几乎都是为多态而准备的.这是我们最后一个概念,也是最重要的知识点. 多态的定义:指允许不同类的对象对 ...
- java 多态判断非空_Java 多态
父类的属性变量(例如变量 int a)可以被继承,同时在子类中也会同时继承该变量(super.int a,继承的变量),子类中也可以再次声明一个同名(可以同类型)的变量(double a,自己声明的同 ...
- java优雅的非空判断
字符串非空判断: lang3包下的StringUtils StringUtils.isNotBlack(str) StringUtils.isNotEmpty(str) isNotEmpty判断非空不 ...
最新文章
- 利用MingW检验程序运行内存
- Spring控制反转(依赖注入)的最简单说明
- Intel Realsense D435 在C/C++中表示的frame_set就是python中的frames?【wait_for_frames()】
- 删除online日志測试及ora-600 [4194]错误的处理
- 安全——《微服务设计》读书笔记
- nginx python webpy 配置安装
- 结合 Mist 在本地测试网络上实现代币智能合约
- 阅读众包文献中一些值得mark 的小收获
- 【报告分享】2019年中国智能门锁发展与应用白皮书.pdf
- GitHubPage博客搭建学习专栏
- android 广播 飞行模式,Android 开启飞行模式的几种方式
- Python大数据处理方案
- win10怎么修复网络连接服务器失败,微软发布修复补丁修复Win10无网络连接问题...
- 解决谷歌导入Vue开发工具没反应的问题
- 电脑主机组装总结——自己动手,丰衣足食
- 选用什么的域名后缀好
- 华为的服务器固态硬盘LE系列和VE系列,02311TJY 800GB SSD FusionServer RH5885 V3华为服务器硬盘...
- VC无进程木马下载器源码(利用IE隐藏进程)
- 常用分辨率、帧率、码率
- h3c 云服务器操作系统,产品技术-H3C CloudOS云操作系统电信版-新华三集团-H3C
热门文章
- 启动Storm的nimbus和supervisor时报错: ImportError: No module named argparse
- C语言 用泰勒展开公式计算sin(x)的值
- android11原生录屏,终于把安卓这项功能实现了!iOS 11录屏功能演示:超好用
- Windows系统下上架iOSAPP
- 中兴和华为的面试经历
- 【旧文集】一生伏首拜阳明-记于2017
- TCP/IP网络编程之多进程服务端(一)
- JBoot 全网上最好用的 API 文档工具
- pyqt5 PDM下载工具 Persepolis Download Manager 记录
- 【机器学习】完整的机器学习项目演练:第一部分