[读书笔记]Core Java: Volume I - Fundamentals Chapter 8

Title: Core Java — Volume I Fundamentals
Edition: Eleventh Edition
Author: Cay S. Horstmann
读书笔记:对原书的归纳、总结、补充和疑问。

Chapter 8 Generic Programming

Generics 的主要作用:让类和方法在面对不同的类型的时候采取不同的行为(此解读存疑) 。代码对于不同类型都能复用。
Generics 的主要目标:提供让程序员感到“自然”的类和方法。
本章的其他内容:由于 Java 设计的时候考虑到了向后兼容,所以造成了一些限制和缺点。Generics 的优点和缺点本章都会讲到

8.1 Why Generic Programming

8.1.1 The Advantage of Type Parameters

文章需要记住的点 Interesting Notes:
有同学会觉得,因为所有类都是 Object 的子类嘛,那为什么泛型不用 Object 呢?其实最开始就是这样实现的啦,但是这样有两个不好的地方:1) Downcasting,你每用一次对象就要 casting 一次 2)Error Checking 既然所有类都是Object,所以其实你可以放任何类。

8.1.2 Who Wants to Be a Generic Programmer?

文章需要记住的点 Interesting Notes:
这一段作者就是想表达,用 Generics 可能会很简单但是设计自己的 Generics 会很复杂。尤其是和 inheritance 结合在一起的时候。因为 Inheritance 直接涉及到了一个单项的从属关系。 A 是 B 但不代表 B 是 A。这样会给泛型增加很多的难度。不过本章的主要目的还是在于希望教给读者如何设计自己的 Generics。


8.2 Defining a Simple Generic Class

这个简单,加个 type parameter 然后把它当成一般的 type 使用就行了。


8.3 Generic Methods

文章需要记住的点 Interesting Notes:

  1. 就算是在普通的类里面,也可以定义 Generic methods。
  2. 有点反直觉的是,在 method 签名上,generic type 必须在返回数据类型之前声明。我觉得不太对劲因为compiler 完全可以从 参数数据类型或是返回数据类型上判断出此method 是否使用了泛型,那么之前的声明就感觉有点冗余。
public static <T> T getMiddle(T... a){
// bla bla bla
}
//这里的 <T> 就感觉有点冗余。也许 compiler 有特殊要求吧。 
//看到后面的补充:是滴,没有多余,可以用来描述 bounds
  1. 接受泛型的方法如果被传进了多个类型的数据,compiler 就会寻找一个 common superclass or common interface。然后很有可能找到多个符合条件的结果,这样就会出错。

8.4 Bounds for Type Variables

文章需要记住的点 Interesting Notes:

  1. 需求痛点是啥:我们想要复用的代码也不是说所有 object 都可以啦,最好是可以圈定某些特定的 object。
  2. 解决方案是啥:你想要圈定那就在 type parameter 圈定就好了。

8.5 Generic Code and the Virtual Machine

8.5.1 Type Erasure

文章需要记住的点 Interesting Notes:
Generic Type 在虚拟机里面是如何清除 type parameter的?有 bound 呢就换成 bound,没有呢就换成 通用的 Object。有多个 Bound 的呢就选择第一个。

8.5.2 Translating Generic Expressions

这里也简单,就一个字 Cast。

8.5.3 Translating Generic Methods

我自己的碎碎念 Whatever I wanna say:
首先上结论:Type Erasue 对 polymorphism 会有干涉作用,为了解决这一问题 compiler 会悄悄加上一种叫 bridge method 的东西。
要理解这一个概念,必须对 overloading 和 overriding 有更深入的理解。(
作为参考可以见这里:https://stackoverflow.com/questions/14676395/java-overloading-method-selection)

At compile time:

  1. compiler 会检查变量的声明类型,寻找最合适的方法。
  2. 如果在这时候有 overloading 的话,那么就选最精准的那个。
  3. 大家可能会觉得反正都有 polymorphism,这个时候选不选有什么意义。但其实这个选择会作为下一步的最初选择。

At run time:
4. 从刚刚的选择着手,检查对象是到底是什么类型。
5. 如果被调用的方法被 override 了,那就调用实际对象的方法。
6. 如果没有被 override,那么就调用刚刚选择的方法,这个时候哪怕有 overloading 也不管了。

归根结底,可以这么说:overloading 只在编译的时候决定选择什么,overriding 只在运行时决定选择什么。

结合上面的结论,就不能理解为什么 generics 对 polymorphism 有影响了。因为当 type erasure 发生之后,所有没有bound 的类型参数都被替换成了 Object,那么如果有人继承了这个 generic type,他原先 override 的方法,现在就会变成 overloading了。

//Before Type Erasure: 这里我们写代码的时候会觉得是 overriding
class Pair<T>{
	public void setSecond(T second){
	//....
	}
}

class DateInterval extends Pair<LocalDate>{
	public void setSecond(LocalDate second){
	//...
	}
}

//After Type Erasure:类型擦除之后就变成了了 overloading
class Pair{
	public void setSecond(Object second){
	//....
	}
}

class DateInterval extends Pair<LocalDate>{
	public void setSecond(LocalDate second){
	//...
	}

	//解决办法
	@override
	public void setSecond(Object second){
		return setSecond((LocalDate)second);
	}
}

//所以唯一解决办法,就是在 DateInerval 里面 override 父类的方法。

8.5.4 Calling Legacy Code

感觉没啥重要的。


8.6 Restrictions and Limitations

这几大 limitation 都是比较简短的点,我就合在一起记录了。

文章需要记住的点 Interesting Notes:
记住所有的 limitations 基本都是因为 type erasure 造成的。

  1. Type 参数不能是原始数据类型。
    Pair<double> 这样是不允许的,因为原始数据类型不是对象,不能被替换成 Object.
    (为啥 primitive types 不是对象呢?)
  2. Type 查询只能查询没有参数的那个类(raw type)。
    比如 Pair<String> sp = ...;
    如果你在这个对象使用 sp.getClass() 返回的则是 Pair。就是因为 Type Erasure 嘛,在JVM里面不管你的 Type 是啥对于虚拟机来说都是一样的。
  3. 不能构造带有 Type 参数的数组。You cannot create arrays of parameterized types.
    var table = new Pair<String>[10] // error
    我想可能是因为 Type Erasure 之后,该数组在虚拟机中将会以 Pair[] 的形式存在。那么其实不管你的 Type Parameter 是什么,该数组都会通过编译器的检查。然而其实际类型可能就不一样了。
  4. 这一条是根据上一条延伸的:Varargs Warning
    Varargs = Variable arguments = 不定数量的方法参数。
    前面几章我们提过,不定数量的方法参数其实就是把参数转化成了一个数组,但是第三点我们又说,带有 Type Parameter 的 generic type 不可以形成数组,所以我们就会面临麻烦。这里JVM没有那么严格要求,允许形成数组,只是会警告你。
  5. 不可以初始化 Type 变量。You cannot instantiate type variables。
    new T(...) //not allowed
    为啥不允许啊 Type Erasure 之后变成了 Object 类,根本不是你想要 new 的那个对象。)
    我们可以用 method reference/lambda expression 的方式来解决问题,也可以用 reflective 的方式来解决问题。
  6. 不可以构造 Type 数组。You cannot contruct a generic array.
    注意了,这一点和第三点很像,但是说的不是一个事情。
    第三点意思是:new Pair<String>[10] // 不可以
    第六点意思是:new T[10] // 不可以
    有一种 trick 在于,反正都要被 erase 掉,你干脆直接 erase 后的类型好了啦。
    比如:与其 new T[10] 不如 new Object[] 后面有需要的时候 Cast 就好了啦。但这样的坏处在于 cast back 的时候会报错。
  7. Type 变量不可以用在静态上下文当中。Type variables are not valid in static context of generic classes.
  8. 不可以抛出或是接住泛型类的实例。 You cannot throw or catch isntances of a generic class.
  9. ?
    第八点和第九点看不太懂因为涉及到了第七章的 Exception Handling,后续将会补上。
  10. Type Erasure 之后可能会带来 method 间的冲突。
    一个 class 或是 type variable T 不能同时是两个特殊 interface 的子类。怎样才算特殊的 interface 呢?如果这两个 interface 其实是一样的只是 type parameter 不一样的时候。
    因为在 type erasure 之后,compiler 会生成两个 bridge methods,这两个bridge methods 会是完全一样的。

8.7 Inheritance Rules for Generic Types

文章需要记住的点 Interesting Notes:

  1. Pair<S> and Pair<T> 没有关系,哪怕 S 和 T 有密切的关系。
    如果 S 是 T 的子类,而且我们也允许 Pair<S>Pair<T> 的子类,父类的引用里面可以存储父类和子类的对象,可能就会出现问题。这和数组是不一样的。在数组里父类的引用可以存储子类的对象,但是 virtual machine 会记住这个存储的类型,一旦你存储了不一样的东西则会报错。

Cross Reference: Section 5.1.5 Caution
因为 8.7 实际是 Generic types 和 数组 Arrays 比较大的一个区别,我先试图弄清楚 Array 在相关概念中是如何表现的。

  1. 数组在被关键字 new 创建的时候,会记住它所储存的类型,如果后期我们试图储存其他非子类的类型,则会报错 ArrayStoreException。
  2. 这样做的原因在于:数组是有继承的概念的,如果我们把创建的数组赋值给了他的父类,他们我可以很轻松的在数组中添加一个父类对象而 compiler 不会报错。这样的话数组里面储存的对象就会不一致,有些事父类对象有些是子类对象。而且该数组还有两个 reference:子类的 reference 和 父类的 reference。那么如果此时我们在子类的 reference 上调用子类对象的方法,就会出问题,因为该数组里面有父类对象,父类对象是没有子类方法的。我们会调用一个不存在的方法。

我想这里也是同样的原因。 如果我们允许 Pair<S> 继承 Pair<T>,那么父类和子类的两个引用可以用来引用同一个子类对象,你还可以在子类对象中添加父类对象。这时候子类引用就就会引用一个父类对象,到时候调用在父类对象上调用子类对象的方法就会出错了。

8.8 Wildcard Types

作者说这玩意儿弄出来为了使泛型更加灵活的,所以它会解决大部分 wildcard 的 limitations 吗?

8.8.1 The Wildcard Concepts

文章需要记住的点 Interesting Notes:

  1. 理解这一章的关键句子在于:

In a wildcard type, a type parameter is allowed to vary.

比如:Pair<? extends Employee> 可以读作 “某个带有未知 Type Parameter 的泛型,而这个未知的 Type Parameter 必须是 Employee 的子类。”
(那我就不懂了,Pair<?>Pair<T> 有什么样的区别呢?)

  1. 这样的话带有通配符的泛型就可以当做好多泛型的父类了。

我自己的碎碎念 Whatever I wanna say:
我觉得这完全是针对 8.7 的泛型限制来讲的,专门就是解决了上一个问题。
比如:
Manager 继承 Employee
Pair<Manager> 不继承 Pair<Employee>
但是 Pair<Manager> 继承 Pair<? extends Employee>

这一节有个问题我花了好多时间都没有看懂:

public static void print(Pair<? extends Employee> p){
	//blablablac
}
//Here you can pass Pair<Manager> as the argument.

public setFirst (? extends Employee){
}
//Here you cannot pass Manager as the argument.

怎么理解这个还是不太清楚
看了一下午吧,终于把这个地方搞清楚了。

  1. 首先我们要明白 Pair<? extends Employee> 代表什么。这是一个类,还可以用作是泛型的父类,我甚至觉得可以看做是泛型的泛型。所谓泛型就是说同一串代码可以反复在不同的类型上面使用。可是泛型本身却没有父类。比如你写了个代码专门处理 List<Shapes>,这个代码就不能用来处理List<Circle>即便是说 Shape/Circle 本身具有继承关系。
  2. 接着人们就想,能不能设计一个类,可以用来代表不同 Type Parameter 的各种泛型,所以就有了 wildcard。
  3. 这个地方的第二段代码不可以是因为,首先要认识到,这一段代码出现在定义 class Pair<? extends Employee> 当中,这个class 代表着拥有某个 Type Parameter 的泛型类。后续你极有可能用好多个符合条件的泛型来替代这个 wildcard,所以setFirst 方法不能随便接受参数因为 compiler 不确定你会用哪个泛型类来替换通配符。

8.8.2 Supertype Bounds for Wildcards

文章需要记住的点 Interesting Notes:
复习一下什么是 Type Variable Bounds,就是你在编写泛型类的时候,可以要求你的泛型必须继承什么什么类或是 interface。限制一下泛型的类型,告诉电脑也不是任何类型都可以的。

  1. ? super Manager 意思是任何一个 Manager 的父类,这和我们前面学的 bounds:任何一个 xxx class 的子类是相反的。
  2. 这里也是和上一节同样的,声明带有 wildcard 的泛型类时,我们不确定 client 到底会用什么泛型类。所以使用 super 的话,除了client 可能会提供任意一个父类,然后方法的参数自然会根据这个提供的父类而变化。那么自然你不能随便接受参数类型了,谁知道会变成什么样啊。
  3. 还有一种使用情况有点复杂,需要一步步发展起来。
    a) 我们现在想要设计一个泛型,但是对于 Type Parameter 有一定的限制。
    b) 什么样的限制呢?我们的限制就是这个 Type Parameter 一定要继承一些接口,而这一堆接口正好可以用一个 wildcard 来表示。(为什么是一堆而不是一个呢?因为有些 class 没有继承某个我们需要的接口,但是继承了相关的接口)。

8.8.3 Unbounded Wildcards

文章需要记住的点 Interesting Notes:
Pair<?> vs Pair
前一个是所有泛型的父类,它有一个重要的特征在于,你完全不能 setFirst,不管是什么类型的对象都不能传送给它当参数。(因为它可以轻松地被向下转型到任意一个类型,所以传什么参数都有可能会错。)

8.8.3 Wildcard Capture

文章需要记住的点 Interesting Notes:
我觉得看书的确给我一大感触就是,作者和读者的角度是不一样的。Pair<?> 以及 Pair<T> 这种明显特别让人混淆的概念作者居然不放在一开始讲,只是随便提了一下。这两个的区别在于,前者代表了一众泛型类,但是一旦应用开发者选定某个泛型类后,该 ?就已经被确定了。而后者是一个泛型方法,意味着他可以用在所有类型上。

  1. 这一章的内容在于,作为一个 library programmer,我们不知道 application programmer (client) 会具体使用什么类,所以我们用 wildcard 来代替。但是有时候我们必须要声明一个变量在暂时性的储存一下这个 wildcard,可是 “?” 又不能用来声明变量类型,我们该怎么办呢,答案是写一个泛型helper来帮我们。

我自己的碎碎念 Whatever I wanna say:

  1. 这章有个细节吧我觉得,那就是作者把 application programmer 和 library programmer 分得很清,而且作者基本是在以 library programmer的角度来写的。
  2. 震惊了!wildcard 请问通配符为什么要取这个名字:因为 wildcard 就是斗地主癞子版里的癞子牌,可以用来随意代表任何一张牌。

8.9 Reflection and Generics

8.9.1 The Generic Class Class.

这一小节就是讲了讲 Class 这个类其实也是泛型类,里面的细节都是泛型实现的。

8.9.2 Using Class<T> Parameters for Type Matching.

文章需要记住的点 Interesting Notes:
什么是 type matching:保证传过来的泛型 Type 和返回的泛型 Type 是一样的。
这个时候就可以利用 Class,因为 T 是不可以 new。如果你没有看懂,你可以自己写代码试试 type matching,你就明白了。

8.9.3 Generic Type Information in the Virtual Machine

文章需要记住的点 Interesting Notes:

  1. reflection API 可以帮我们重新构建泛型类。
    (这一部分没太看懂,或者说这一部分有什么用呢。)

8.9.4 Type Literals

文章需要记住的点 Interesting Notes:

  1. 我们遇到的问题:如何对不同的类采取不同的行为?因为在 Type Erasure 之后,ArrayList<Integer>ArrayList<String> 应该是一样的,那么我怎么怎么设计一个程序,使得改程序会对这两不同的类有不同的行为呢。
  2. 我们的解决方案:我也还是不太懂。

Dictionary

Application Programmer 应用开发者
Library Programmer 库开发者
Generics 泛型
Wildcard 通配符
Type Erasure 类型擦除
Reference 引用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Core Java Volume I–Fundamentals, 1 (11th Edition) By 作者: Cay S. Horstmann ISBN-10 书号: 0135166306 ISBN-13 书号: 9780135166307 Edition 版本: 11 出版日期: 2018-09-06 pages 页数: 928 The #1 Java Guide for Serious Programmers: Fully Updated for Java SE 9, 10 & 11 For serious programmers, Core Java, Volume I—Fundamentals, Eleventh Edition, is the definitive guide to writing robust, maintainable code. Whether you’re using Java SE 9, 10, or 11, it will help you achieve a deep and practical understanding of the language and API, and its hundreds of realistic examples reveal the most powerful and effective ways to get the job done. Cay Horstmann’s updated examples reflect Java’s long-awaited modularization, showing how to write code that’s easier to manage and evolve. You’ll learn how to use JShell’s new Read-Eval-Print Loop (REPL) for more rapid and exploratory development, and apply key improvements to the Process API, contended locking, logging, and compilation. In this first of two volumes, Horstmann offers in-depth coverage of fundamental Java and UI programming, including objects, generics, collections, lambda expressions, Swing design, concurrency, and functional programming. If you’re an experienced programmer moving to Java SE 9, 10, or 11, there’s no better source for expert insight, solutions, and code. Master foundational techniques, idioms, and best practices for writing superior Java code Leverage the power of interfaces, lambda expressions, and inner classes Harden programs through effective exception handling and debugging Write safer, more reusable code with generic programming Improve performance and efficiency with Java’s standard collections Build cross-platform GUIs with the Swing toolkit Fully utilize multicore processors with Java’s improved concurrency

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值