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

目录
相关文章
|
25天前
|
存储 缓存 安全
java 中操作字符串都有哪些类,它们之间有什么区别
Java中操作字符串的类主要有String、StringBuilder和StringBuffer。String是不可变的,每次操作都会生成新对象;StringBuilder和StringBuffer都是可变的,但StringBuilder是非线程安全的,而StringBuffer是线程安全的,因此性能略低。
44 8
|
26天前
|
Java 开发者
在 Java 中,一个类可以实现多个接口吗?
这是 Java 面向对象编程的一个重要特性,它提供了极大的灵活性和扩展性。
57 1
|
1月前
|
存储 安全 Java
java.util的Collections类
Collections 类位于 java.util 包下,提供了许多有用的对象和方法,来简化java中集合的创建、处理和多线程管理。掌握此类将非常有助于提升开发效率和维护代码的简洁性,同时对于程序的稳定性和安全性有大有帮助。
63 17
|
1月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
1月前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
113 4
|
1月前
|
Java 编译器 开发者
Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面
本文探讨了Java异常处理的最佳实践,涵盖理解异常类体系、选择合适的异常类型、提供详细异常信息、合理使用try-catch和finally语句、使用try-with-resources、记录异常信息等方面,帮助开发者提高代码质量和程序的健壮性。
58 2
|
1月前
|
存储 安全 Java
如何保证 Java 类文件的安全性?
Java类文件的安全性可以通过多种方式保障,如使用数字签名验证类文件的完整性和来源,利用安全管理器和安全策略限制类文件的权限,以及通过加密技术保护类文件在传输过程中的安全。
50 4
|
1月前
|
SQL Java 数据库连接
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率
在Java应用中,数据库访问常成为性能瓶颈。连接池技术通过预建立并复用数据库连接,有效减少连接开销,提升访问效率。本文介绍了连接池的工作原理、优势及实现方法,并提供了HikariCP的示例代码。
51 3
|
1月前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
44 5
|
1月前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
76 5