重温经典《Thinking in java》第四版之第七章 复用类(四十一)

简介: 重温经典《Thinking in java》第四版之第七章 复用类(四十一)

7.7 向上转型

“为新的类提供方法”并不是继承技术中最重要的方面,其最重要的方面是用来表现新类和基类之间的关系。这种关系可以用“新类是现有类的一种类型”这句话加以概括。

这种描述并非只是一种解释继承的华丽方式,这直接是由语言所支撑的。例如,假设有一个成为Instrument的代表乐器的基类和一个称为Wind的导出类。由于继承可以确保基类中所有的方法在导出类中也同样有效,所以能够向基类发送的所有信息同样也可以向导出类发送。如果Instrument类具有一个play()方法,那么wind乐器也将同样具备。这意味着我们可以准确地说Wind对象也是一种类型的Instrument。

//: reusing/Wind.java // Inheritance & upcasting. classInstrument { 
publicvoidplay() {} 
staticvoidtune(Instrumenti) { 
// ... i.play(); 
    } 
} 
// Wind objects are instruments // because they have the same interface: publicclassWindextendsInstrument { 
publicstaticvoidmain(String[] args) { 
Windflute=newWind(); 
Instrument.tune(flute); // Upcasting     } 
}

///:~

在此例中,tune()方法可以接受Instrument引用,这实在太有趣了。但是Wind.main()中,传递给tune()方法的是一个wind引用。鉴于Java对类型检查十分妍哥,接受某种类型的方法同样可以接受另外一种类型就会显得很奇怪,除非你认识到Wind对象同样也是一种Instrument对象,而且也不存在任何tune()方法是可以通过Instrument来调用,同时又不存在于Wind中。在tune()中,程序代码可以对Instrument和它所有的导出类起作用。这种将Wind引用转换为Instrument引用的动作,我们称之为向上转型。

7.7.1 为什么称为向上转型

该术语的使用有其历史原因,并且是以传统的继承图的绘制方法为基础的:将根置于页面的顶端,然后逐渐向下。于是,Wind的集成图就如下:

image.png

由导出类转型成基类,在继承图上是向上移动的,因此,一般称为向上转型。由于向上转型是从一个比较专用类型向比较通用类型转型,所以总是很安全的。也就是说,导出类是基类的一个超集。它可能比基类含有更多的方法,但是它必须至少具备基类中所含有的方法。在向上转型的过程中,类接口中唯一可能发生的事情就丢失方法,而不是获取它们。这就是为什么编译器在“未曾明确表示转型”或“未曾指定特殊标记”的情况下,仍然允许向上转型的原因。

7.7.2 再论组合与继承

在面向对象编程中,生成和使用程序代码最有可能采用的方法就是直接将数据和方法包装进一个类中,并使用该类的对象。也可以运用组合技术使用现有类来开发新的类;而继承技术其实是不太常用的。因此尽管在教授OOP的过程中我们多次强调继承,但是这并不意味着要尽可能使用它。相反,应当慎用这一技术,其使用场景仅限于你确信使用该技术确实有效的情况。到底是该使用组合还是使用继承,一个最清晰的判断办法就是问一问自己是否需要从新类向基类进行向上转型。如果必须向上转型,则继承是必要的;但是如果不需要,则应好好考虑自己是否需要继承。

7.8 final关键字

根据上下文环境,Java的关键字final的含义存在着细微的区别,但是通常它指的是“这是无法改变的。”不想做改变可能出于两种理由:设计或效率。由于这两个原因相差很远,所以关键字final有可能被误用。以下几节谈论了可能使用到final的三种情况:数据、方法和类。

7.8.1 final数据

许多编程语言都有某种方法,来向编译器告知一块数据是恒大不变的。有时数据的恒定不变是很有用的,比如:

1、一个永不改变的编译时常量

2、一个在运行时被初始化的值,而你不希望它被改变。

对于编译期常量这种情况,编译器可以将该常量值代入任何可能用到它的计算式中,也就是说,可以在编译时执行计算式,这减轻了一些运行时的负担。在Java中,这类常量必须是基本数据类型,并且以关键字final表示。在对这个常量进行定义的时候,必须对其进行赋值。

一个既是static又是final的域只占据一段不能改变的存储空间。

当对对象引用而不是基本类型运用final时,其含义会有一点令人迷惑。对于基本类型,final使数值恒定不变;而用于对象引用,final使引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它改变指向另一个对象。然后,对象其自身确是可以被修改的,Java并未提供使任何对象恒定不变的途径。这一限制同样适用数组,它也是对象。

importjava.util.*; 
importstaticnet.mindview.util.Print.*; 
classValue { 
inti; // Package access publicValue(inti) { this.i=i; } 
} 
publicclassFinalData { 
privatestaticRandomrand=newRandom(47); 
privateStringid; 
publicFinalData(Stringid) { this.id=id; } 
// Can be compile-time constants: privatefinalintvalueOne=9; 
privatestaticfinalintVALUE_TWO=99; 
// Typical public constant: publicstaticfinalintVALUE_THREE=39; 
// Cannot be compile-time constants: privatefinalinti4=rand.nextInt(20); 
staticfinalintINT_5=rand.nextInt(20); 
privateValuev1=newValue(11); 
privatefinalValuev2=newValue(22); 
privatestaticfinalValueVAL_3=newValue(33); 
// Arrays: privatefinalint[] a= { 1, 2, 3, 4, 5, 6 }; 
publicStringtoString() { 
returnid+": "+"i4 = "+i4+", INT_5 = "+INT_5; 
    } 
publicstaticvoidmain(String[] args) { 
FinalDatafd1=newFinalData("fd1"); 
//! fd1.valueOne++; // Error: can’t change value fd1.v2.i++; // Object isn’t constant! fd1.v1=newValue(9); // OK -- not final for(inti=0; i<fd1.a.length; i++) 
fd1.a[i]++; // Object isn’t constant! //! fd1.v2 = new Value(0); // Error: Can’t //! fd1.VAL_3 = new Value(1); // change reference //! fd1.a = new int[3]; print(fd1); 
print("Creating new FinalData"); 
FinalDatafd2=newFinalData("fd2"); 
print(fd1); 
print(fd2); 
    } 
}

/* Output:

fd1: i4 = 15, INT_5 = 18

Creating new FinalData

fd1: i4 = 15, INT_5 = 18

fd2: i4 = 13, INT_5 = 18

*///:~

按照惯例,既是static又是final的域将用大写表示,并使用下划线分割各个单词。

Java允许生成空白final,所谓空白final是指被声明为final但又未给定初始值的域。无论什么情况,编译器都确保空白final在使用前必须被初始化。但是,空白final在关键字final的使用上提供更大的灵活性,为此,一个类中的final域就可以做到根据对象而有所不同,却又保持其恒定不变的特性。

Java允许参数列表中以声明的方式将参数指明为final。这意味着你无法在方法中更改参数所指向的对象。

目录
打赏
0
0
1
0
97
分享
相关文章
|
11天前
|
重学Java基础篇—Java类加载顺序深度解析
本文全面解析Java类的生命周期与加载顺序,涵盖从加载到卸载的七个阶段,并深入探讨初始化阶段的执行规则。通过单类、继承体系的实例分析,明确静态与实例初始化的顺序。同时,列举六种触发初始化的场景及特殊场景处理(如接口初始化)。提供类加载完整流程图与记忆口诀,助于理解复杂初始化逻辑。此外,针对空指针异常等问题提出排查方案,并给出最佳实践建议,帮助开发者优化程序设计、定位BUG及理解框架机制。最后扩展讲解类加载器层次与双亲委派机制,为深入研究奠定基础。
34 0
|
17天前
|
《从头开始学java,一天一个知识点》之:输入与输出:Scanner与System类
你是否也经历过这些崩溃瞬间?三天教程连`i++`和`++i`都说不清,面试时`a==b`与`equals()`区别大脑空白,代码总是莫名报NPE。这个系列就是为你打造的Java「速效救心丸」!每天1分钟,地铁通勤、午休间隙即可学习。直击高频考点和实际开发中的“坑位”,拒绝冗长概念,每篇都有可运行代码示例。涵盖输入输出基础、猜数字游戏、企业编码规范、性能优化技巧、隐藏技能等。助你快速掌握Java核心知识,提升编程能力。点赞、收藏、转发,助力更多小伙伴一起成长!
43 19
重学Java基础篇—类的生命周期深度解析
本文全面解析了Java类的生命周期,涵盖加载、验证、准备、解析、初始化、使用及卸载七个关键阶段。通过分阶段执行机制详解(如加载阶段的触发条件与技术实现),结合方法调用机制、内存回收保护等使用阶段特性,以及卸载条件和特殊场景处理,帮助开发者深入理解JVM运作原理。同时,文章探讨了性能优化建议、典型异常处理及新一代JVM特性(如元空间与模块化系统)。总结中强调安全优先、延迟加载与动态扩展的设计思想,并提供开发建议与进阶方向,助力解决性能调优、内存泄漏排查及框架设计等问题。
32 5
|
18天前
|
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
46 11
|
11天前
|
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
21 1
|
24天前
|
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、&quot;+&quot;操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
课时11:综合实战:简单Java类
本次分享的主题是综合实战:简单 Java 类。主要分为两个部分: 1.简单 Java 类的含义 2.简单 Java 类的开发
|
25天前
|
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
46 5
课时37:综合实战:数据表与简单Java类映射转换
今天我分享的是数据表与简单 Java 类映射转换,主要分为以下四部分。 1. 映射关系基础 2. 映射步骤方法 3. 项目对象配置 4. 数据获取与调试
java常见的集合类有哪些
Map接口和Collection接口是所有集合框架的父接口: 1. Collection接口的子接口包括:Set接口和List接口 2. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及 Properties等 3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等 4. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等