java generics implicit cast_Java Generics And Collections笔记(Part I)

写在前面

泛型,反射,数组这些和类型纠缠在一起感觉很混乱,通过这本书梳理记录一下。作者之一 Philip Walder 也是一位大牛,参与过Java 5的泛型设计,也参与过Haskell的设计,我也是通过他的talk查到主页看到这本书。他在一次talk(Category Theory for the Working Hacker)说过(大意是) come up with a great idea, find other great people to work with, then others will ignore you, don't worry about it, just stick with it for 30 years,感同身受,希望自己能沉下心努力提高自己。

Introduction

1.1 Generics

泛型可以替代legacy code里的类型转换,且可以保证在编译器没有发出unchecked警告的时候类型转换不会失败。原文如下:

Cast-iron guarantee: the implicit casts added by the compilation of generics never fail.

It applies only when no unchecked warnings have been issued by the compiler.

基于类型擦除(erasure)的泛型实现有如下特点:

保持简单,泛型没有增加新的特性。

keep things small,泛型没有为每个类型增加一份实现代码。

it eases evolution,保证了对早期版本代码的兼容性,不必分别维护Java 1.4无泛型的类库和Java 5之后的有泛型的相同功能的类库。字节码层面,泛型代码和没有使用泛型的代码是差不多的。

At the bytecode level, code that doesn’t use generics looks just like code that does.

Java具体化(reify)数组的Component type,但是并没有具体化泛型类型,比如String[]保存了指示数组Component type的信息,但是ArrayList并有保存元素的类型信息。

和C++模板语法(syntax)相似,但是语义(semantics)不同。C++模板会为每一个类型生成一份代码,可能会导致代码膨胀(code bloat),但是优化空间大,Java只有一份代码。

1.2 Boxing and Unboxing

装箱值缓存,从-128到127。注意相等判断==,equals的区别。

1.3 Foreach

对实现了接口Iterable的类可以使用foreach。

1.4 Generic Methods and Varargs

变长参数在run time会被打包成数组传给方法,或者可以显式的传递一个数组给方法。

public static void addAll(List list, T... arr) {

for (T elt : arr) list.add(elt);

}

List ints = new ArrayList<>();

addAll(ints, 1, 2);

addAll(ints, new Integer[] { 3, 4 });

assert ints.toString().equals("[1, 2, 3, 4]");

创建包含泛型类型的数组会收到unchecked的警告,变长参数应该避免在参数是泛型类型的时候使用。

Subtyping and WildCards

2.1 Subtyping and the Substitution Principle

里氏替换原则,子类可以用在需要父类的地方。

Substitution Principle: a variable of a given type may be assigned a value of any subtype of that type, and a method with a parameter of a given type may be invoked with an argument of any subtype of that type.

List是invariant,不是covariant,因此List不是List的子类,参考如下错误代码段(2.1-1):

List ints = new ArrayList();

ints.add(1);

ints.add(2);

List nums = ints; // 编译错误

nums.add(3.14);

assert ints.toString().equals("[1, 2, 3.14]"); // uh oh!

数组是covariant,Integer[]是Number[]的子类。

如果需要集合类型表现出covariant类似的特性,选择wildcards。

2.2 WildCards with extends

修改2.1-1的错误代码如下

List ints = new ArrayList();

ints.add(1);

ints.add(2);

List extends Number> nums = ints; // 编译OK

nums.add(3.14); // 编译错误

assert ints.toString().equals("[1, 2, 3.14]"); // uh oh!

nums的类型可能是Number子类的list,元素类型不一定是Double的父类,参考如下解释:

In general, if a structure contains elements with a type of the form ? extends E, we can get elements out of the structure, but we cannot put elements into the structure.

2.3 WildCards with super

参考Collections中的copy方法代码(2.3-1,源码精简版):

public static void copy(List super T> dst, List extends T> src) {

for (int i = 0; i < src.size(); i++) {

dst.set(i, src.get(i));

}

}

List objs = Arrays.asList(2, 3.14, "four");

List ints = Arrays.asList(5, 6);

Collections.copy(objs, ints);

assert objs.toString().equals("[5, 6, four]");

// 第9行代码也可以写成如下,都是类型正确的

Collections.copy(objs, ints);

Collections.copy(objs, ints);

Collections.copy(objs, ints);

2.4 The Get and Put Principle

在其他地方也被称为PECS(provider extends, consumer supers),当一个集合作为提供方,即元素会被拿出来操作,用extends,当一个集合作为消费方,即需要插入外部元素,用super。参考2.3-1。

The Get and Put Principle: use an extends wildcard when you only get values out of a structure, use a super wildcard when you only put values into a structure, and don’t use a wildcard when you both get and put.

The Get and Put Principle反过来说一般来说也是成立的。如果有extends,一般只能get,例外是可以put null,因为null是引用类型的子类。如果有super,一般只能put,例外是可以get Object,因为Object是引用类型的父类。参考如下代码(2.4-1, 2.4-2):

List ints = new ArrayList<>();

ints.add(1);

ints.add(2);

List extends Number> nums = ints;

nums.add(null); // ok

assert nums.toString().equals("[1, 2, null]");

List objs = Arrays.asList(1,"two");

List super Integer> ints = objs;

String str = "";

for (Object obj : ints)

str += obj.toString();

assert str.equals("1two");

一个反直觉的例子

Because String is final and can have no subtypes, you might expect that List is the same type as List extends String>. But in fact the former is a subtype of the latter, but not the same type, as can be seen by an application of our principles. The Substitution Principle tells us it is a subtype, because it is fine to pass a value of the former type where the latter is expected. The Get and Put Principle tells us that it is not the same type, because we can add a string to a value of the former type but not the latter.

2.5 Arrays

数组是协变的(covariant),意味着S是T的子类,S[]也是T[]的子类。

Integer[] ints = new Integer[] { 1, 2, 3 };

Number[] nums = ints;

nums[2] = 3.14; //raise ArrayStoreException

assert Arrays.toString(ints).equals("[1, 2, 3.14]"); //fails

上面代码(2.5-1)编译通过但是运行异常的原因:

数组携带者具体化类型信息(reified type),数组的component type的run time表达,在这个例子中,数组的reified type是Integer,赋值为3.14(Double)会导致ArrayStoreException。

与此对比,wildcards为泛型引入了协变的性质:如果S是T的子类,List是List extends T>的子类,参考如下代码(2.5-2)

List ints = Arrays.asList(1,2,3);

List extends Number> nums = ints;

nums.set(2, 3.14); // compile-time error

assert ints.toString().equals("[1, 2, 3.14]"); // uh oh!

上面代码编译不通过,原因是nums的类型中有extends,除了null,不能放其他类型。

同时,wildcards为泛型引入了逆变的性质:如果S是T的父类,List是List super T>的子类。

为了使数组具有协变的性质,被迫在run time给数组检测一些错误,这主要是在泛型出现之前,为了复用一些工具方法,使之适用于更多的类型。如下:

public static void sort(Object[] a);

public static void fill(Object[] a, Object val);

有了泛型,上述代码就可以写成如下:

public static void sort(T[] a);

public static fill(T[] a, T val);

某种意义而言,协变的数组是Java早期没有泛型的结果,有了泛型,协变的数组就可能是错误的设计了,Java为了向前的兼容性保留了协变的数组。

2.6 Wildcards Versus Type Parameters

两个例子,前者用wildcards,后者用type parameters(类型参数),如下:

interface Collection {

boolean contains(Object o);

boolean containsAll(Collection> c);

}

interface MyCollection {

boolean contains(E o);

boolean containsAll(Collection extends E> c);

}

Java为了保证向前兼容性,选择了前者实现,但是后者可以在编译器检查出更多问题。

2.7 Wildcard Capture

When a generic method is invoked, the type parameter may be chosen to match the unknown type represented by a wildcard. This is called wildcard capture.

仍然两个例子,前者用wildcards,后者用type parameters(类型参数),如下:

public static void reverse(List> list) {

List tmp = new ArrayList<>(list);

for (int i = 0; i < list.size(); i++)

list.set(i, tmp.get(list.size() - i - 1)); //编译错误

}

public static void rev(List list) {

List tmp = new ArrayList(list);

for (int i = 0; i < list.size(); i++)

list.set(i, tmp.get(list.size() - i - 1));

}

代码产生编译错误的原因是,尝试将Object写入位置类型的list中,List改成List>也不能解决问题,因为,tmp和list可能是元素类型不同的两种list。使用类型参数是类型正确的(type check)。

正确的实现方法如下:

public static void reverse(List> list) {

rev(list); // 复用类型参数实现的rev

}

可以说类型T捕获了wildcard。

2.8 Restrictions on Wildcards

Wildcards may not appear at the top level in class instance creation expressions (new), in explicit type parameters in generic method calls, or in supertypes (extends and implements).

Instance Creation

实例创建需要一个普通的类型,不能是wildcard,需要put, get,所以不能extends, super。

Generic Method Calls

Supertypes

当有类被实例化,会调用父类的初始化,所以父类不能有wildcard

以上都是对顶层wildcard的限制,嵌套的wildcard是允许的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值