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

目录
相关文章
|
6月前
|
Java 编译器 API
Java 密封类:精细化控制继承关系
Java 密封类:精细化控制继承关系
402 83
|
4月前
|
安全 Java 数据建模
Java记录类:简化数据载体的新选择
Java记录类:简化数据载体的新选择
275 101
|
4月前
|
安全 Java 开发者
Java记录类:简化数据载体的新方式
Java记录类:简化数据载体的新方式
320 100
|
7月前
|
IDE Java 数据挖掘
Java 基础类从入门到精通实操指南
这份指南专注于**Java 17+**的新特性和基础类库的现代化用法,涵盖开发环境配置、数据类型增强(如文本块)、字符串与集合处理进阶、异常改进(如密封类)、IO操作及实战案例。通过具体代码示例,如CSV数据分析工具,帮助开发者掌握高效编程技巧。同时提供性能优化建议和常用第三方库推荐,适合从入门到精通的Java学习者。资源链接:[点此下载](https://pan.quark.cn/s/14fcf913bae6)。
269 36
|
5月前
|
安全 IDE Java
Java记录类型(Record):简化数据载体类
Java记录类型(Record):简化数据载体类
468 143
|
3月前
|
存储 Java 索引
用Java语言实现一个自定义的ArrayList类
自定义MyArrayList类模拟Java ArrayList核心功能,支持泛型、动态扩容(1.5倍)、增删改查及越界检查,底层用Object数组实现,适合学习动态数组原理。
162 4
|
3月前
|
IDE JavaScript Java
在Java 11中,如何处理被弃用的类或接口?
在Java 11中,如何处理被弃用的类或接口?
243 5
|
3月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
236 1
|
3月前
|
Java Go 开发工具
【Java】(8)正则表达式的使用与常用类分享
正则表达式定义了字符串的模式。正则表达式并不仅限于某一种语言,但是在每种语言中有细微的差别。
288 1
|
3月前
|
存储 Java 程序员
【Java】(6)全方面带你了解Java里的日期与时间内容,介绍 Calendar、GregorianCalendar、Date类
java.util 包提供了 Date 类来封装当前的日期和时间。Date 类提供两个构造函数来实例化 Date 对象。第一个构造函数使用当前日期和时间来初始化对象。Date( )第二个构造函数接收一个参数,该参数是从1970年1月1日起的毫秒数。
230 0