Chapter 5 Generics

Item 23: Don’t use raw types in new code

虽然你可以把一个List<String>传给一个List类型(raw type)的参数,但你不应该这么做(因为允许这样只是为了兼容遗留代码),举个例子:

// Uses raw type (List) - fails at runtime!
public static void main(String[] args) {List<String> strings = new ArrayList<String>();unsafeAdd(strings, new Integer(42));String s = strings.get(0); // Compiler会自动加上cast
}
private static void unsafeAdd(List list, Object o) {list.add(o);
}

以上代码说明如果你把List<String>类型转换成List类型,就可以往里面加任何东西了,编译器不会做检查,就很危险。但是你也不能把List<String>转换成List<Object>,但是你可以把List<String>转换成List<?>,读作List of some type(某种类型的List),但这时候你就不能忘里面add东西了(除了null),因为这个List包含某种类型,但你又不知道(或不关心)是具体是什么类型,那你加进去的东西很可能跟它应有的类型不匹配,所以你只能拿出来一个东西(并定义成Object类型)。如果你实在想add,可以用generic methods或bounded wildcard types。

由于在运行时“都变成了raw type”,所以instanceof后面只能用raw type:

// Legitimate use of raw type - instanceof operator
if (o instanceof Set) { // Raw typeSet<?> m = (Set<?>) o; // Wildcard type
}

这里的Cast不会造成编译器warning。

Item 24: Eliminate unchecked warnings

当写Generic的时候,通常会遇到很多warning。而你应该尽量eliminate掉每一个warning,这样到了runtime你的代码才更可能不会抛出ClassCastException。如果你没法消除这个warning,但你能证明是typesafe的,那你应该用@SuppressWarnings("unchecked")。SuppressWarnings可以用在类上,方法上,变量声明上,你应该将其用在the smallest scope possible,因为如果你比如用在整个class上,那你可能mask了一些关键性的warning,所以千万别这么做。有时候为了这个原则,你还不得不把某句语句拆成两句写,比如:

return (T[]) Arrays.copyOf(elements, size, a.getClass());

由于不能在return语句上加SuppressWarnings,所以你只能拆成两句:

@SuppressWarnings("unchecked")
T[] result = (T[]) Arrays.copyOf(elements, size, a.getClass());
return result;

每次你用@SuppressWarnings("unchecked")时,都应该注释一下你为什么要这么做。

Item 25: Prefer lists to arrays

数组是covariant的,意思就是如果Sub是Super的子类,那么Sub[]就是Super[]的“子类型”,这也就意味着你可以把String[]转成Object[],然后加一个Object对象进去,然后到runtime会报错。而泛型是invariant的,也就是对于任何的x和y,List<x>List<y>没有任何关系。
你无法new一个跟泛型有关的数组,比如以下都是错误的:new List<E>[], new List<String>[], new E[]。为什么?书上举了个例子我懒得写了,反正我个人总结下来就是:都怪擦除,因为T[]到运行时其实就相当于Object[],你可以往里面放任何东西,但按理说你应该只能放是T的东西进去。所以说不要把varargs和泛型混用,因为varargs其实就相当于创建了一个数组当成参数。由于这种无法创建数组的限制以及数组的协变性,你可以考虑用List<T>代替T[],比如可以用new ArrayList<E>(list)代替(E[]) list.toArray(),会安全得多。
总结来说,泛型提供了编译时但非运行时的type safety,而数组恰好相反。

Item 26: Favor generic types

generic types就是Generic classes and interfaces。这个item就是教你怎么把你的类变成泛型类,比如我们要把item 6中基于Object[]的Stack升级为泛型的,那我们就把所有“Object”替换成“E”,并在类声明中加上泛型参数。这样会有一个问题就是:elements = new E[DEFAULT_INITIAL_CAPACITY]这句话不能通过编译。因为你不能创建泛型数组,解决方法是:
1.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
因为elements是个private的field,不会泄露给client,而唯一给这个数组“装元素”的地方就是push方法,而push方法push进去的东西保证是E类型的(通过编译器),所以你可以安心地加上@SuppressWarnings("unchecked")。(给这句所在constructor加,因为这句是赋值不是声明,所以加不了)
2.先elements = new Object[DEFAULT_INITIAL_CAPACITY]; 然后把elements的定义改成Object[]类型的,最后E result = (E) elements[--size];就行了。
同理,因为push的时候已经确保元素肯定是E,所以这里的warning也可以suppress掉。这两种方法都可以,基本只是个人喜好问题。

Item 27: Favor generic methods

“Static utility methods”通常是泛型化的good candidates。在调用泛型方法的时候不需要显式指定泛型参数的具体类型,编译器会自己推断出来,这叫type inference。
后面这个generic singleton factory我来回看了几遍终于有那么一点似乎看懂了,首先例子代码如下:

interface UnaryFunction<T> {T apply(T arg);
}
private static UnaryFunction<Object> IDENTITY_FUNCTION = new UnaryFunction<Object>() {public Object apply(Object arg) {return arg;}
};
@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> identityFunction() {return (UnaryFunction<T>) IDENTITY_FUNCTION;
}

一开始我在想new UnaryFunction<Object>(){...}这句话是什么意思,为什么这里是Object而不能是T,后来一想:匿名类是同时声明和创建的,而创建一个泛型类的实例必须指定具体的type parameter,所以这里就相当于声明了一个 实现了UnaryFunction<T>的类,然后创建了一个它的实例(泛型参数是Object)。然后identityFunction方法是一个泛型的static factory method,会把UnaryFunction<Object>类型转换成 “调用这个泛型方法的时候被推断出来的类型 的类型的UnaryFunction”。先看一下用法:

public static void main(String[] args) {String[] strings = { "jute", "hemp", "nylon" };UnaryFunction<String> sameString = identityFunction();for (String s : strings)System.out.println(sameString.apply(s));Number[] numbers = { 1, 2.0, 3L };UnaryFunction<Number> sameNumber = identityFunction();for (Number n : numbers)System.out.println(sameNumber.apply(n));
}

第一次调用identityFunction()的时候被推断出来的类型是String,第二次是Number。然后以第一次为例,在调用sameString.apply(s)的时候,相当于编译器就会调用UnaryFunction<String>接口中的public String apply(String arg)方法,所以编译器此时会检查s这玩意儿是不是Sting,发现没问题,OK,返回的结果也会被编译器cast成String,而这里你的实际方法(return arg;)啥都没做,所cast肯定不会报错。这个例子的意思关键在于static factory method里面的那个cast: (UnaryFunction<T>) IDENTITY_FUNCTION,正是因为这个cast,所以client代码才能让 任何类型的UnaryFunction 都共享同一个实例(IDENTITY_FUNCTION )。
但是我在普通的client代码里面试了一下,无法将List<Object> cast成 List<String>,看来这个技巧也只能在泛型方法里面用了。C#的类似实现(虽然可能不是单例):

static void Main(string[] args)
{String[] strings = { "jute", "hemp", "nylon" };var sameString = IdentityFunction<String>();foreach (var s in strings){Console.WriteLine(sameString(s));}int[] ints = { 1, 2, 3 };var sameInt = IdentityFunction<int>();foreach (var s in ints){Console.WriteLine(sameInt(s));}
}
public static Func<T, T> IdentityFunction<T>()
{return arg => arg;
}

听起来很玄乎的一个概念recursive type bound,比如:<T extends Comparable<T>> may be read as “for every type T that can be compared to itself,”。
总之,generic methods和generic types都不需要client进行各种cast。

Item 28: Use bounded wildcards to increase API flexibility

为了更好的灵活性,应该在输入参数上用wildcard types。如果这个输入参数代表了一个producer就用entends,如果代表了一个consumer就用super,比如如果一个方法的参数声明是Collection<E> container,如果在方法内部只会从container读取E的instance(也就是E只能作为container方法中的返回类型),也就是container只是作为生产者,那么就应该把声明改成Collection<? extends E> container。(你可以这么记,不管返回什么,反正放到E类型的变量里肯定是安全的)同理,如果在方法内部,比如会把E的instance加到container里去,那么container就是消费者(也就是E会作为输入类型),那么就应该声明成Collection<? super E> container。如果既是生产者又是消费者,那就不能用bounded wildcards了。
注意不要在返回类型上用wildcard types,因为如果你这么做了会迫使client code里面也要受到wildcard types的限制,比如你返回了一个Set<E extends E>,那么得到这个东西的client就只能在它上面进行get操作,而不能add了。正确的用法是,你应该让client根本不用去思考wildcard types,wildcard types只是让你的API能接受更多的类型。
因为type inference的规则很复杂,有时候type inference会推理错,这时候你需要显示地指定一个type parameter,比如:Union.<Number>union(integers, doubles)
item 27中有这么一个方法:

// Returns the maximum value in a list - uses recursive type bound
public static <T extends Comparable<T>> T max(List<T> list) {Iterator<T> i = list.iterator();T result = i.next();while (i.hasNext()) {T t = i.next();if (t.compareTo(result) > 0)result = t;}return result;
}

现在我们把它增强成用wildcard type,变成这样:

public static <T extends Comparable<? super T>> T max(List<? extends T> list)

首先把list的类型改成“生产者”很好理解,因为list只返回一个Iterator<E>,而这个Iterator<E>的next方法的声明是“E next()”,E是返回类型。但为什么这里要把Comparable<T>改成Comparable<? super T>。首先看一下Comparable<T>的定义:

public interface Comparable<T>{int compareTo(T o)
}

看到了吗,T是输入参数,所以Comparable<T>的“实例”是个消费者。 比如如果你这么调用上面的那个方法:

List<Apple> apples = new...;
Apple maxApple = max(apples);

那么这里的Apple类并不一定要实现Comparable<Apple>,也可以只实现Comparable<Fruit>,当然Fruit是Apple的基类。假设Apple只知道怎么和Fruit比,然后当运行到方法体内“t.compareTo(result)”这句的时候,t是一个Apple,result也是一个Apple,但是t只知道怎么和另一个Fruit比,但是result是一个Apple当然也是一个Fruit,所以没问题。其实上面解释地这么麻烦,不如你只要记住“always use Comparable<? super T> in preference to Comparable<T>”就行了(Comparator也一样)。
最后记得还要把方法体中的Iterator<T>改成Iterator<? extends T>

下面是两种等价的方法声明:

// Two possible declarations for the swap method
public static <E> void swap(List<E> list, int i, int j);
public static void swap(List<?> list, int i, int j);

作者说第二种更好,因为更简单,且不需要考虑type parameter。如果一个type parameter在方法声明中只出现了一次,那么就应该把它替换成unbounded wildcard或bounded wildcard。为什么必须是“只出现了一次”?书上没说但我个人理解是因为如果出现了两次:public static <E> void swap(List<E> list1,List<E> list2),那么这里的list1包含的元素和list2包含的元素应该是相同的类型,如果你全都换成“?”,那么list1和list2完全可以包含不同的类型。
但是问题又来了,如果你单纯这么实现:

public static void swap(List<?> list, int i, int j) {list.set(i, list.set(j, list.get(i)));
}

会发现不行,因为不能放东西到List<?>里去,解决办法是依靠“type capture”,写个helper方法:

public static void swap(List<?> list, int i, int j) {swapHelper(list, i, j);
}
// Private helper method for wildcard capture
private static <E> void swapHelper(List<E> list, int i, int j) {list.set(i, list.set(j, list.get(i)));
}

虽然书上的解释有点莫名其妙,但我选择“信了”,感觉记住就行,只是个小技巧。我觉得可以这么理解:因为一个泛型方法被调用的时候肯定要指定泛型参数具体是什么,如果你不指定那就只能靠编译器推断,而在这里编译器就会“capture”到?代表的东西。

Item 29: Consider typesafe heterogeneous containers

这一小节就是告诉你怎么实现这么一个类,保存 你最喜欢的 某个类型的一个实例:

public class Favorites {private Map<Class<?>, Object> favorites = new HashMap<Class<?>, Object>();public <T> void putFavorite(Class<T> type, T instance) {if (type == null) throw new NullPointerException("Type is null");favorites.put(type, type.cast(instance));}public <T> T getFavorite(Class<T> type) {return type.cast(favorites.get(type));}
}

你可以用putFavorite来存一个“T类型的实例”,然后通过getFavorite来获取这个实例。
所以说这里的T和T类型的value是一一对应的,你可以放很多种不同的类型进去。你可以这样使用:

Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);

其实这里用Class<T>来作为“Key”是因为自JDK1.5来Class类就被泛型化了,比如String.class是Class<String>的类型、Integer.class是Class<Integer>的类型,当把这样一个“class literal”(应该就是指“String.class”这种写法)传给某个方法的时候,通常把它叫做type token。而你完全可以自己写一个类,比如Holder<T>来作为“Key”,但是不如Class好,因为Class有一些方法比如cast可以不用让你suppress warning(我个人认为的)。上面的type.cast方法其实就是Java’s cast operator对应的“动态版本”,它只是检查一下它的参数是不是被自己代表的类型,不是的话就抛出ClassCastException:

public class Class<T> {T cast(Object obj);
}

另外,说些没什么关联的事儿:如果你把Class<?>类型转换成,比如Class<? extends Apple>,会得到warning,那么你可以用asSubclass这个方法,比如假设你得到了一个Class<?>类型的变量apple,然后你可以apple.asSubclass(Apple.class),意思就是“把Class<?>变成Class<Apple>”(反正你就这么理解吧),如果这个apple指向的对象并不是一个“Apple对象”的Class object,那就抛出异常。

转载于:https://www.cnblogs.com/raytheweak/p/7190157.html

《Effective Java》读书笔记 - 5.泛型相关推荐

  1. Effective Java 读书笔记(七):通用程序设计

    Effective Java 读书笔记七通用程序设计 将局部变量的作用域最小化 for-each 循环优于传统的 for 循环 了解和使用类库 如果需要精确的答案请避免使用 float 和 doubl ...

  2. Effective Java读书笔记(二)

    Effective Java 读书笔记 (二) 创建和销毁对象 遇到多个构造器参数时要考虑使用构建器 创建和销毁对象 何时以及如何创建对象? 何时以及如何避免创建对象? 如何确保它们能够适时地销毁? ...

  3. Effective Java读书笔记七:泛型(部分章节需要重读)

    第23条:请不要在新代码中使用原生态类型 从java1.5发行版本开始,Java就提供了一种安全的替代方法,称作无限制的通配符类型,如果要使用范型,但是确定或者不关心实际的参数类型,就可以用一个问号代 ...

  4. Effective Java读书笔记完结啦

    Effective Java是一本经典的书, 很实用的Java进阶读物, 提供了各个方面的best practices. 最近终于做完了Effective Java的读书笔记, 发布出来与大家共享. ...

  5. Effective Java 读书笔记(一)

    前言: 开个新的坑位,<effective java>的读书笔记,之后有时间会陆陆续续的更新,读这本书真的感触满多,item01和item02就已经在公司的项目代码中看到过了.今天这篇主要 ...

  6. Effective Java读书笔记三:创建和销毁对象

    第1条:考虑用静态工厂方法代替构造器 对于类而言,为了让客服端获得它的一个实例最常用的的一个方法就是提供一个公有的构造器.还有一种方法,类可以提供一个公有的静态工厂方法(static factory ...

  7. Effective Java读书笔记六:方法

    第38条:检查参数的有效性 绝大多数方法和构造器对于传递给它们的参数值都会有些限制.比如,索引值必须大于等于0,且不能超过其最大值,对象不能为null等.这样就可以在导致错误的源头将错误捕获,从而避免 ...

  8. Effective Java读书笔记五:异常

    第57条:只针对异常的情况才使用异常 异常是为了在异常情况下使用而设计的,不要将它们用于普通的控制流,也不要编写迫使它们这么做的API. 下面部分来自:异常 如果finally块中出现了异常没有捕获或 ...

  9. Effective Java读书笔记四:通用程序设计

    第45条:将局部变量的作用域最小化 在第一次使用变量时的地方声明: 几乎每个局部变量的声明都应该包含一个初始表达式: 如果在终止循环之后不需要循环变量的内容,for循环优于while循环.(for循环 ...

  10. Effective Java读书笔记二:枚举和注解

    第30条:用enum代替int常量 当需要一组固定常量的时候,应该使用enum代替int常量,除了对于手机登资源有限的设备应该酌情考虑enum的性能弱势之外. 第31条:用实例域代替序数 枚举的ord ...

最新文章

  1. 浅析网站流量出现异常情况应怎样解决?
  2. jsp form表里的submit点击没反应
  3. 组件与组件之间的通信以及vue2.0中的变化、示例
  4. ES6的导入和导出模块
  5. Java 字符串,byte[],16进制的字符串互转
  6. BZOJ 1845三角形面积并
  7. Linux网络实时流量监测工具iftop的安装使用
  8. python第一题 引发的思考和学习
  9. echarts+php+mysql 绘图实例
  10. [Deep Learning] 神经网络基础
  11. 进销存excel_进销存管理系统excel模板
  12. echarts的tooltip提示框
  13. spring动态代理之cglib动态代理
  14. KETTLE将本地图片抽取到oracle库
  15. 【公告】CSDN 博客将进行数据库维护
  16. JavaScript打点计时器
  17. 商标注册的费用是多少钱
  18. nodejs实现分解质因数的算法
  19. 【科学的尽头是神学】祖师爷坐镇
  20. 【实验3】——目标的分辨能力

热门文章

  1. popupwindow 不抢夺焦点_央视专访“上个厕所就要3000块”的亲历者, 被“坑”的不愉快经历...
  2. 霍普分叉matlab程序,基于MATLAB_GUI的Kalman滤波程序
  3. d3js mysql_D3.js入门指南
  4. python docx库使用样例_Python docx库用法示例分析
  5. linux命令基本格式教程,Linux命令基本格式(详解版)
  6. 2021年茂名市高考成绩查询,2021年茂名高考最高分多少分,历年茂名高考状元
  7. E1倒换保护设备知识详解
  8. 【渝粤教育】电大中专Office办公软件 (2)作业 题库
  9. 【渝粤题库】陕西师范大学210010 幼儿园管理学 作业(高起专、专升本)
  10. 【渝粤教育】广东开放大学 计算机思维 形成性考核 (29)