重温经典《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。这意味着你无法在方法中更改参数所指向的对象。

目录
相关文章
|
2月前
|
Java 编译器 API
Java 密封类:精细化控制继承关系
Java 密封类:精细化控制继承关系
258 83
|
3月前
|
IDE Java 数据挖掘
Java 基础类从入门到精通实操指南
这份指南专注于**Java 17+**的新特性和基础类库的现代化用法,涵盖开发环境配置、数据类型增强(如文本块)、字符串与集合处理进阶、异常改进(如密封类)、IO操作及实战案例。通过具体代码示例,如CSV数据分析工具,帮助开发者掌握高效编程技巧。同时提供性能优化建议和常用第三方库推荐,适合从入门到精通的Java学习者。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
168 35
|
4月前
|
存储 安全 Java
【高薪程序员必看】万字长文拆解Java并发编程!(7):不可变类设计指南
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中Java不可变类设计指南,废话不多说让我们直接开始。
67 0
|
8天前
|
安全 IDE Java
Java记录类型(Record):简化数据载体类
Java记录类型(Record):简化数据载体类
63 0
|
6月前
|
Java 开发者
重学Java基础篇—Java类加载顺序深度解析
本文全面解析Java类的生命周期与加载顺序,涵盖从加载到卸载的七个阶段,并深入探讨初始化阶段的执行规则。通过单类、继承体系的实例分析,明确静态与实例初始化的顺序。同时,列举六种触发初始化的场景及特殊场景处理(如接口初始化)。提供类加载完整流程图与记忆口诀,助于理解复杂初始化逻辑。此外,针对空指针异常等问题提出排查方案,并给出最佳实践建议,帮助开发者优化程序设计、定位BUG及理解框架机制。最后扩展讲解类加载器层次与双亲委派机制,为深入研究奠定基础。
198 0
|
2月前
|
Java API
Java API中Math类功能全景扫描
在实际使用时,这些方法的精确度和性能得到了良好的优化。当处理复杂数学运算或高精度计算时,`Math`类通常是足够的。然而,对于非常精细或特殊的数学运算,可能需要考虑使用 `java.math`包中的 `BigDecimal`类或其他专业的数学库。
81 11
|
2月前
|
Java API
深入解析Java API中Object类的功能
了解和合理运用 Object类的这些方法,对于编写可靠和高效的Java应用程序至关重要。它们构成了Java对象行为的基础,影响着对象的创建、识别、表达和并发控制。
50 0
|
2月前
|
安全 Java
JAVA:Collections类的shuffle()方法
`Collections.shuffle()` 是 Java 中用于随机打乱列表顺序的工具方法,适用于洗牌、抽奖等场景。该方法直接修改原列表,支持自定义随机数生成器以实现可重现的打乱顺序。使用时需注意其原地修改特性及非线程安全性。
68 0
|
2月前
|
存储 Java 编译器
深入理解Java虚拟机--类文件结构
本内容介绍了Java虚拟机与Class文件的关系及其内部结构。Class文件是一种与语言无关的二进制格式,包含JVM指令集、符号表等信息。无论使用何种语言,只要能生成符合规范的Class文件,即可在JVM上运行。文章详细解析了Class文件的组成,包括魔数、版本号、常量池、访问标志、类索引、字段表、方法表和属性表等,并说明其在Java编译与运行过程中的作用。

热门文章

最新文章