【Java基础教程】(十四)面向对象篇 · 第八讲:多态性详解——向上及向下转型、关键字 final与 instanceof的作用~

news2024/11/24 15:45:01

Java基础教程之面向对象 · 第八讲

  • 本节学习目标
  • 1️⃣ final 关键字
    • 1.1 final类
    • 1.2 final方法
    • 1.3 final属性
  • 2️⃣ 多态性
    • 2.1 向上转型
    • 2.2 向下转型
    • 2.3 关键字 instanceof
  • 🌾 总结

在这里插入图片描述

本节学习目标

  • 掌握final 关键字的主要作用及使用;
  • 掌握对象多态性的概念以及对象转型的操作;
  • 掌握instanceof 关键字的主要作用及使用;

1️⃣ final 关键字

在Java 中 final称为终结器,在Java 里面可以使用 final定义类、方法和属性,用于表示不可变性

  • final类:当一个类被声明为final时,意味着该类不能被继承。换句话说,不能创建该类的子类。通常将类声明为final的主要原因是防止其他类修改或扩展该类的行为。例如,java.lang.String就是一个被声明为final的类;
  • final方法:当一个方法被声明为final时,意味着该方法不能被子类重写或覆盖。这种限制可以保护方法的实现,确保不会被修改。通常情况下,一个方法被声明为final是因为它的实现在父类中已经足够完善,不希望子类对其进行修改;
  • final变量:当一个变量被声明为final时,意味着该变量的值不能被更改。一旦给final变量赋值,就不能再修改它的值。通常将变量声明为final是为了防止其被重新分配或重新赋值,保持其不可变性。final变量可以是基本类型(如intdouble等)或引用类型(如对象、数组引用等),但引用类型的final变量指的是不能被重新分配,即不能再指向其他对象,但仍然可以修改对象的状态。

使用final关键字可以带来以下好处:

  • 提高性能:final关键字可以使编译器进行优化,因为它知道这些元素不会被改变,所以可以进行一些优化处理。
  • 增加安全性:final关键字可以防止意外修改或覆盖。当某个类、方法或变量对于某种场景下应该保持固定不变时,使用final可以提供更强的安全性和可靠性。
  • 支持设计决策:通过将类、方法或变量声明为final,可以明确表达出它们在设计中的意图和限制。

1.1 final类

使用 final 定义的类不能再有子类,即:任何类都不能继承以 final 声明的父类。

//	范例 1: 观察 final 定义的类
final class A{}       // 此类不能够有子类

class B extends A{}  	// 错误的继承

此程序中由于 A 类在定义时使用了 final关键字,这样 A 就不能再有子类了,所以当 B 类继承 A 类时会在编译时出现语法错误。

需要注意的是,只是进行应用开发的话,那么大部分情况下不需要使用 final 来定义类。而如果是一些系统架构的代码开发时,才有可能会使用到这样的代码。同时要注意一点:String也是使用 final 定义的类,所以String类不允许被继承。

1.2 final方法

使用 final 定义的方法不能被子类所覆写。 在一些时候由于父类中的某些方法具备一些重要的特征,并且这些特征不希望被子类破坏(不能够覆写), 就可以在方法的声明处加上final, 意思是子类不要去破坏这个方法的原本作用。

//	范例 2: 观察 final 定义方法
class A{
	public final void fun(){}  // 此方法不允许子类覆写
}

class B extends A{
	public void fun(){}			//错误:此处不允许覆写
}

此程序在 A 类中定义的 fun()方法上使用了final进行定义,这就意味着子类在继承 A 类后将不允许覆写 fun()方法。

1.3 final属性

使用 final定义的变量就成为了常量,常量必须在定义的时候设置好内容,并且不能修改。

//	范例 3: 定义常量
class A{
	final double GOOD = 100.0;	//GOOD级别就是100.0

	public final void fun(){
		GOOD=1.1;                          //错误:不能够修改常量
	}
}

此程序使用 final 定义了一个常量 “GOOD”, 这就相当于利用 “GOOD” 这个名称代表了“100.0” 这个数据。所以代码中定义常量的最大意义在于:使用常量可以利用字符串(常量名称)来更直观地描述数据。

大家可以发现本处定义的常量名称使用了全部字母大写的形式(final double GOOD =100.0;),这是Java中的命名规范要求,这样做的好处是可以明确地与变量名称进行区分,开发中也应该遵守。

而在定义常量中还有一个更为重要的概念 — — 全局常量,所谓全局常量指的就是使用了"public"、“static”、“final”3个关键字联合定义的常量,例如:

public static final String MSG="YOOTK";

static 修饰的数据保存在公共数据区,所以此处的常量就是一个公共常量。同时一定要记住,在定义常量时必须对其进行初始化赋值,否则将出现语法错误。

2️⃣ 多态性

前面文章中已经详细解析了面向对象的封装性、继承性两大特征,而多态是面向对象的最后一个主要特征,也是一个非常重要的特性,掌握了多态性才可以编写出更加合理的面向对象程序。而多态性在开发中可以体现在以下两个方面:

  • 方法的多态性:重载与覆写;

    • 方法重载:同一个方法名称,根据不同的参数类型及个数可以完成不同的功能;
    • 方法覆写:同一个方法,根据实例化的子类对象不同,所完成的功能也不同。
  • 对象的多态性:父子类对象的转换。

    • 向上转型:子类对象变为父类对象,格式:父类 父类对象 = 子类实例,自动类型转换;
    • 向下转型:父类对象变为子类对象,格式:子类 子类对象 = (子类) 父类实例,强制类型转换。

对于方法的多态性在之前已经有了详细地阐述,所以本节主要介绍对象多态性,有一点需要注意的是,对象多态性和方法覆写是紧密联系在一起的。

当然,要想真正理解多态性是如何去应用的,多态性更合理的解释需要结合抽象类与接口来一起讲解,下一篇文章将会为大家介绍抽象类与接口等知识,而要想充分理解这一概念也需要更多时间的了解与沉淀。

//	范例 4: 观察如下程序
class A{
	public void print(){
		System.out.println("A 、public void print(){}");
	}
}

class B extends A{
	public void print(){  //  此时子类覆写了父类中的print()方法
		System.out.println("B 、public void print(){}");
	}
}

public class TestDemo {
	public static void main(String args[]){
		B b = new B();                       //实例化的是子类对象
		b.print();                                  //调用被子类覆写过的方法
	}
}

程序执行结果:

B 、public void print(){}

此程序在方法覆写中已经讲解过了,由于现在子类B 覆写了A 类中的 print(), 并且在主方法中实例化的是子类对象,这样当调用 print()方法时调用的一定是已经被覆写过的方法。也就是说在本程序中需要观察以下两点。
(1)观察实例化的是哪个类的对象:“new B()”;
(2)观察调用的方法是否被子类所覆写,如果覆写了,则调用被覆写过的方法,否则调用父类方法。

2.1 向上转型

那么这样的概念与对象的多态性有什么关联呢?下面对上边案例的主方法进行一些变更,以观察对象的向上转型操作。

//	范例 5: 对象向上转型(自动完成)
public class TestDemo {
	public static void main(String args[]){
		A a = new B();  // 实例化的是子类对象,对象向上转型
		a.print();     	//调用被子类覆写过的方法
	}
}

程序执行结果:

B 、public void print(){}

可以看到此程序的执行结果与范例4 的程序执行结果没有任何区别,然而在本程序中发生了对象的向上转型操作 (A a = new B();),并且最终由父类对象调用了 print() 方法,但是最终的执行结果却是被子类所覆写过的方法的执行结果。

而产生这样结果的原因也很好理解,在范例4 中已经重点强调过:根据实例化对象所在类是否覆写了父类中的方法来决定最终执行,此程序实例化的是子类对象 (new B()),并且 print() 方法又被子类覆写了,那么最终所调用的一定是被覆写过的方法。

实际上通过此程序大家可以发现对象向上转型的特点,整个操作中根本不需要关心对象的声明类型,关键在于实例化新对象时所调用的是哪个子类的构造,如果方法被子类所覆写,调用的就是被覆写过的方法,否则就调用父类中定义的方法。这一点与方法覆写的执行原则是完全一样的。

2.2 向下转型

在清楚了对象的向上转型操作及特点后,下面再来观察对象的向下转型操作。

//	范例 6: 对象向下转型
public class TestDemo {
	public static void main(String args[]){
		A a = new B();		//实例化的是子类对象,对象向上转型 
		B b = (B) a;		//对象需要强制性地向下转型
		b.print();			//调用被子类覆写过的方法
	}
}

程序执行结果:

B 、public void print(){}

此程序首先利用对象的向上转型实例化了父类A 的对象,然后将此对象进行向下转型为子类 B 的对象,由于整个代码中关键字 new 调用的是子类 B 的构造 (new B()),所以调用的是被子类 B 所覆写的 print() 方法。

因为有强制性转换的操作,所以向下转型操作本身是有前提条件的,即必须发生向上转型后才可以发生向下转型。如果是两个没有关系的类对象发生强制转换,就会出现 “ClassCastException” 异常。

//	范例 7: 错误的转型操作
public class TestDemo {
	public static void main(String args[]){
		A a = new A(); 		//直接实例化对象, 此时并没有发生子类对象向上转型的操作,所以强制转型会带来安全隐患
		B b = (B)a;      	//强制向下转型,此处产生“ClassCastException”异常
		b.print();          //此语句无法执行
	}
}

程序执行结果:

Exception in thread "main" java.lang.ClassCastException: A cannot be cast to B at TestDemo.main(TestDemo.java:29)

本程序出现的异常表示的是类转换异常,指的是两个没有关系的类对象强制发生转型时所带来的异常。因为此时并没有发生向上转型,所以向下转型是会存在安全隐患的,开发中应该尽量避免此类操作。

对象多态性的本质是根据实例化对象所在的类是否覆写了父类中的指定方法来决定最终执行的方法体,那么这种向上或向下的对象转型有什么意义呢?

在实际开发中,对象向上转型的主要意义在于参数的统一,也是最为重要的用法,而对象的向下转型指的是调用子类的个性化操作方法。

//	范例 8: 对象向上转型作用分析
class A {
	public void print(){
		System.out.println("A、public void print(){}");
	}
}

class B extends A{    //定义A的子类
	public void print(){	//此时子类覆写了父类中的print()方法
		System.out.println("B、public void print(){}");
	}
}

class C extends A {    //定义A的子类
	public void print(){	//此时子类覆写了父类中的print()方法
		System.out.println("C、public void print(){}");
	}
}

public class TestDemo {
	public static void main(String args[]){
		fun(new B());    //对象向上转型,等价于:A a=new B();
		fun(new C());    //对象向上转型,等价于:A a=new C();
	}
	
	/**
	* 接收A类或其子类对象,不管传递哪个对象都要求调用print()方法
	* @param a A类实例化对象
	*/
	public static void fun(A a)(
		a.print();
	}
}

程序执行结果:

B、public void print(){}
C、public void print(){}

在此程序的 fun() 方法上只是接收了一个 A 类的实例化对象,按照对象的向上转型原则,此时的 fun() 方法可以接收 A类对象或所有A类的子类对象,这样只需要一个A类的参数类型,此方法就可以处理一切 A的子类对象 (即便A类有几百万个子类,fun() 方法依然可以接收及处理 )。 而在 fun() 方法中将统一调用 print() 方法,如果此时传递的是子类对象,并且覆写过 print()方法,就表示执行被子类所覆写过的方法。

如果说向上转型是统一调用的参数类型,那么向下转型就表示要执行子类的个性化操作方法。实际上当发生继承关系后,父类对象可以使用的方法必须在父类中明确定义,例如:此时在父类中存在一个 print() 方法,哪怕这时此方法被子类覆写过,父类对象依然可以调用。但是如果子类要扩充一个 funB()方法,这个方法父类对象并不知道, 一旦发生向 上转型,那么 funB()方法父类对象肯定无法使用。

//	范例 9: 子类扩充父类方法
class A{
	public void print(){
		System.out.println("A、public void print(){}");
	}
}

class B extends A{  //定义A的子类
	public void print(){	//此时子类覆写了父类中的print()方法
		System.out.println("B、public void print(){}");
	}
	
	/**
	* 在子类中扩充一个新的方法,但是此方法只能由子类对象调用,父类对象不能调用 
	*/
	public void funB(){
		System.out.println("B、扩充的funB()方法");
	}
}

此程序在子类B中定义的 funB() 方法在子类对象发生向上转型时( A a = new B();),父类对象将无法调用,也就是说这个方法是子类自己的特殊功能,并没有在父类中定义,如果此时要想调用子类中的方法,就必须将父类对象向下转型。

//	范例 10: 向下转型,调用子类中的特殊功能
public class TestDemo {
	public static void main(String args[]){
		fun(new B());//向上转型,只能调用父类中定义的方法
	}
	public static void fun(A a){
		B b = (B)a;  //要调用子类的特殊操作,需要向下转型
		b.funB();    //调用子类的扩充方法
	}
}

程序执行结果:

B、扩充的funB()方法

此程序如果要调用 fun()方法,则子类B的实例化对象一定要发生向上转型操作,但是这个时候父类对象无法调用子类BfunB()方法,所以需要进行向下转型才能正常调用 funB()方法。但是如果每一个子类都去大量扩充自己的新功能,这样就会严重破环开发的参数统一性,所以方法应该以父类为主,子类可以覆写父类方法,但尽量不要扩充新的方法。

通过以上所有分析可以发现如下特点:
向上转型:其目的是参数的统一,但是向上转型中,通过子类实例化后的父类对象所能调用的方法只能是父类中定义过的方法;
向下转型:其目的是父类对象要调用实例化它的子类中的特殊方法,但是向下转型是需要强制转换的,这样的操作容易带来安全隐患。

以个人经验来说,对于对象的转型,实际上 80%的情况下都只会使用向上转型,因为可以得到参数类型的统一,方便于程序设计;并且子类定义的方法大部分情况下应该以父类的方法名称为标准进行覆写,而不要过多地扩充方法。5% 的情况下会使用向下转型,目的是调用子类的特殊方法。还有15%的情况下是不转型,例如:String

2.3 关键字 instanceof

为了保证转型的顺利进行,Java 提供了一个关键字:instanceof,利用此关键字可以判断某一个对象是否是指定类的实例,使用格式如下。

//	返回boolean型
对象 instanceof

如果某个对象是某个类的实例,就返回 true, 否则返回 false

//	范例 11: 使用instanceof 判断
public class TestDemo  {
	public  static void main(String  args[]){
		A a = new B();               //对象向上转型
		System.out.println(a instanceof A);
		System.out.println(a instanceof B);
		System.out.println(null instanceof A);
	}
}

程序执行结果:

true
true
false

此程序利用 instanceof 判断了某个对象是否是指定类的实例,通过程序的执行结果可以发现 a 对象由于采用了向上转型进行实例化操作,所以 aA 类或 B类的实例化对象,而 null 在使用 instanceof 判断时返回的结果为 false

既然 instanceof 关键字可以准确地判断出实例化对象与类的关系,那么就可以在进行对象强制转换前进行判断,以保证安全可靠的向下转型操作。

从实际的开发来讲,向下转型的操作几乎是很少见到的,但是如果真的出现了,并且开发者不确定此操作是否安全时,一定要先使用instanceof 关键字判断。

//	范例 12: 使用 instanceof  判断
class A{
	public void print(){
		System.out.println("A、public void print(){}");
	}
}

class B extends A{                         //定义A 的子类
	public void print(){               //此时子类覆写了父类中的print()方法
		System.out.println("B、public void print(){}");
	}
	public void funB(){
		System.out.println("B、扩充的funB()方法");
	}
}

public class TestDemo {
	public static void main(String args[]){
		fun(new B());
	}
	public static void fun(A a){ //对象向上转型
		a.print();
		if (a instanceof B){ //如果 a 对象是B 类的实例
			B b= (B)a;		//向下转型
			b.funB();		//调用子类扩充的方法
		}
	}
}

程序执行结果:

B、public void print(){}
B、扩充的funB()方法

在此程序中为了保证安全的向下转型操作,在将父类转换为子类对象时首先使用了 instanceof 进行判断,如果当前对象是子类实例,则进行强制转换,以调用子类的扩充方法。

🌾 总结

在本文中我们学习了Java中两个重要的概念:final关键字和多态性。

首先,我们了解到final关键字可以应用于类、方法和属性。final类表示不可继承,final方法表示不可覆盖,而final属性表示不可修改。使用final关键字可以提高代码的安全性和性能,并明确表达设计意图。

其次,我们探讨了多态性的概念。多态性是面向对象编程的重要特性,其中包括向上转型、向下转型和关键字instanceof。通过向上转型,我们可以实现参数的统一和代码的灵活性。而向下转型则允许调用子类的个性化操作方法。关键字instanceof则可用于检查对象是否属于某个类或其子类的实例。

理解final关键字和多态性对于Java开发至关重要。它们能帮助我们设计更安全、灵活且易于维护的代码。合理运用final关键字,可以防止不必要的修改和继承;深入理解多态性,可以提高代码的可扩展性和可复用性。

最后,我们需要注意在使用final关键字时要谨慎,并根据具体需求进行选择。同时,熟练掌握多态性的概念和技巧,有助于编写更具有弹性和适应性的代码。


温习回顾上一篇(点击跳转)《【Java基础教程】(十三)面向对象篇 · 第七讲:继承性详解——继承概念及其限制,方法覆写和属性覆盖,关键字super的魔力~》

继续阅读下一篇(点击跳转)《【Java基础教程】(十五)面向对象篇 · 第九讲:~》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/760899.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【CSDN新星计划】初阶牛C/C++赛道——顺序程序设计(C语句)

目录 3.1 C语句的作用和分类 🍉(1)控制语句 🍉(2)函数调用语句 🍉(3)表达式语句 🍉(4)空语句 🍉(5&#…

【C++进阶之路】vector的基本使用和模拟实现

前言 作为STL的容器之一,vector的名字通常令人疑惑?在字面上,我们通常会翻译成向量,但感觉又解释不通,总觉得应该叫dynamic array翻译成动态数组/顺序表,更容易理解?那为啥呢? 我从…

【Java】微服务项目的部署

微服务项目的部署 准备Centos安装 Docker镜像加速检查加速器是否生效 下载docker-compose方法1 curl方法2 pip方法3 直接下载released 用docker-compose部署中间件导入项目安装jdk maven git设置idea内存减小jar启动占用内存增加idea可使用内存 本文参考 https://gitee.com/…

音乐怎么转换成wav格式?分享这五个方法给大家!

在音频编辑和处理过程中,将音乐文件转换为WAV格式是一种常见需求。WAV格式以其无损音质和广泛的兼容性而受到许多人的喜爱。下面介绍了五种常用的方法,帮助您将音乐文件转换为WAV格式,其中方法一使用记灵在线工具。 方法一:记灵在…

非常规自增自减

非常规自增自减 目录 一. 概述二. 例题 一. 概述 在C语言的单目操作符中有(自增)和–(自减)这两个运算符。假设有变量i,我想让变量i加上1,那么我们会写成ii1这样的形式。…

自旋锁与开关中断临界区的区别

自旋锁和开关中断临界区都是用于保护共享资源的机制,但它们的实现方式和使用场景有所不同。 自旋锁主要是用于多核CPU上的并发编程中,它通过不断地检查锁的状态来等待锁的释放,从而避免了线程的阻塞。当一个线程需要访问共享资源时&#xff…

VTK 三维模型 体绘制 关于环境光、漫反射、镜面反射

光源: 1):环境光:环境光是一种低强度的光,由光线经过周围环境表面多次反射后形成的,利用环境光可以描述一块区域的亮度,通常在场 景中,环境光的颜色是一个常量. 2):太阳光:即定向光源,特点是从无穷远出发射光线,光线是平行的,光线强度不会随着距离衰减. 3):点光源:在有限空间…

maven安装和换源

1. 安装(17条消息) maven历史版本下载和安装_beiback的博客-CSDN博客 安装 maven历史版本仓库 下载 Index of /dist/maven/maven-3 (apache.org)https://archive.apache.org/dist/maven/maven-3/ 选择对应版本-binaries/-zip 解压即可使用 2.换源 (17条消息) 手把手教你更改…

【读书笔记】从实模式到保护模式

计算机语言 x86汇编语言:从实模式到保护模式(操作系统引导课) 原书作者李忠 用电表示数据 寄存器的作用:具有记忆功能的器件。锁存可以通过下面的开关控制,平时开关为空,按下开关之后,将输入锁…

PLEX如何搭建个人局域网的视频网站

Plex是一款功能非常强大的影音媒体管理系统,最大的优势是多平台支持和界面优美,几乎可以在所有的平台上安装plex服务器和客户端,让你可以随时随地享受存储在家中的电影、照片、音乐,并且可以实现观看记录无缝衔接,手机…

VScode——NPM脚本窗口找不到

一、问题描述(NPM终端在任务栏左侧找不到) VScode(Visual Studio Code)版本:1.79.2 二、解决办法 第一步:通过设置/用户设置/扩展/MPM更改NPM默认配置,如下图所示: 第二步&#xff…

[java安全]CommonsCollections6

文章目录 【java安全】CommonsCollections6**测试环境**前言分析TiedMapEntry注意点一注意点二POC调用栈 【java安全】CommonsCollections6 测试环境 3.1-3.2.1,jdk1.7,1.8 前言 之前我们分析了CommonsCollections1 LazyMap利用链,但是在java 8u71以…

2023 年中回顾:珍惜当下,锻炼身体

文章大纲 过去几十年的经验:人类的悲喜并不相同马太效应破圈:健康的身体写博客这件事:价值导向参考文献 距离上次阶段性回顾仅仅过去半年,感觉整个IT 行业天翻地覆慨而慷了。 时光荏苒,我自己也在芯片领域深耕了365天&…

swagger不可用

swagger不可用 问题描述问题处理测试环境外网域名测试环境内网域名思考 总结 问题描述 上周swagger还没啥问题,这周一测试突然和我说,测试环境的swagger都用不了了,然后开始找原因 问题处理 测试环境外网域名 一直跳这个弹窗,百度…

学习AJAX

AJAX 🚀 HTTP请求报文响应报文 🚄 express框架🚬 express基本使用 🚒 原生AJAX🚬 GET.HTML🚬 POST.HTML🚬 JSON.HTML🚬 nodemon工具可以帮助重启服务🚬 IE缓存问题&#…

MIT 6.829 -- L0 Background: Single-Link Communication

MIT 6.829 -- L0 Background: Single-Link Communication 前言ProblemModulation(调制) & Demodulation(解调)FramingError DetectionError RecoveryARQ Shared Media Access总结 本课程为MIT 6.829 计网课程,课程对应官网链接: Computer Networks Lecture Notes 本节对应…

操作系统——虚拟内存管理

文章目录 一、虚拟内存中的几种地址1、逻辑地址2、线性地址3、逻辑地址转线性地址4、线性地址转物理地址 二、进程与内存1、内核空间和用户空间2、内存映射3、进程内存分配与回收 早期程序直接运行在物理内存上,直接操作物理内存,这种方式存在几个问题&a…

【Python】selenium项目实战:从12306网站获取特定时间段二等座有票的车次

文章目录 一、项目背景二、页面查找1、查询条件2、定位有二等座的元素3、定位有二等座的车次信息4、CtrlF检验xpath查找的车次 三、代码实现 一、项目背景 工具: pythonpycharmselenium 12306网址: https://kyfw.12306.cn/otn/leftTicket/init?linktyp…

【GESP】2023年06月图形化四级 -- 密码合规检测

密码合规检测 【题目描述】 网站注册需要有用户名和密码,默认小猫角色和白色背景,编写程序以检查用户输入密码的有效性。 (1)合法的密码只能由a-z之间26个字母(字母不区分大小写)、0-9之间10个数字以及!@#$四个特殊字母构成。 (2)密码最短长度:6个字符,密码最大长…

通用技术 自动化测试与持续集成方案

目录 前言: 传统接口测试 接口测试自动化 接口自动化的持续集成 前言: 在现代软件开发中,自动化测试和持续集成是两个不可或缺的环节。自动化测试可以提高测试效率、减少人工错误,并确保软件的质量。持续集成则可以帮助开发团…