标签:des style http color java os 使用 io for
最近,我在准备Oracle Java SE 7的开发人员专业认证,偶然看到一些关于Java泛型很奇怪的用法。当然,我也看到了一些优雅灵巧的代码。我觉得这些例子很值得分享,不仅因为它们可以让 你的设计选择变得简单,还会使代码具有更好的健壮性和可重用性。如果对泛型不熟悉,其中的一些例子会不容易理解。我决定把这篇文章分成四部分,这与学习和 工作中对泛型所积累的经验可以很好地吻合。
注意一下我们能看到的(代码),就会发现在很多Java框架中,泛型是非常常见的。从Web框架到Java自身的集合类型,都能看到它的身影。由于 在我之前已经有很多人讲解过这个主题,这里我首先只列出我觉得有价值的资源,之后重点讨论一些很少被提及或讲解不清的内容(主要是指网上的笔记或文章)。 因此,如果你缺少对泛型核心概念的理解,可以参考以下资料:
<>
”操作符。假设你了解泛型,也想学到更多,那让我们先知道什么不能做。出人意料的是,很多东西不能在泛型中使用。我选择了下面6个在使用泛型时需要避免的例子。
<T>
类型的静态成员很多没有经验的开发者常犯的错误就是尝试声明静态成员。如下所示,这样做会导致编译错误:Cannot make a static reference to the non-static type T
。
1
2
3
4
|
public class StaticMember<T> { // causes compiler error static T member; } |
<T>
另一个错误就是使用new操作符实例化泛型类型。这样做会导致编译错误:Cannot instantiate the type T
。
1
2
3
4
5
6
7
|
public class GenericInstance<T> { public GenericInstance() { // causes compiler error new T(); } } |
使用泛型的一个最大的限制似乎就是与基本类型不兼容。虽然你不能在声明中直接使用基本类型,但是你可以使用合适包装器(Wrapper
)类型作为替代,并且运行良好。下面的这个例子说明了这种情形:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Primitives<T> { public final List<T> list = new ArrayList<>(); public static void main(String[] args) { final int i = 1 ; // causes compiler error // final Primitives<int> prim = new Primitives<>(); final Primitives<Integer> prim = new Primitives<>(); prim.list.add(i); } } |
第一次实例化Primitives
类会产生编译错误,提示信息类似于:Syntax error on token "int", Dimensions expected after this token.
。这个问题可以通过包装器和自动打包技术来绕过。
<T>
类型的数组使用泛型的另一个显著的限制就是不能实例化参数类型的数组。考虑到数组类型的特点,原因是很明显的——数组类型在运行中维护着自己的类型信息。如果在运行时,它的类型完整性被破坏,就会抛出运行时异常ArrayStoreException
。
1
2
3
4
5
6
7
|
public class GenericArray<T> { // this one is fine public T[] notYetInstantiatedArray; // causes compiler error public T[] array = new T[ 5 ]; } |
但如果你想直接实例化一个泛型数组,就会产生编译错误:Cannot create a generic array of T
。
有时,开发者想要在抛出异常时,传入一个泛型实例。在Java中是行不通的。下面的例子就是这么做的。
1
2
|
// causes compiler error public class GenericException<T> extends Exception {} |
当你尝试创建一个这样的异常时,你会得到这样的错误信息:The generic class GenericException<T> may not subclass java.lang.Throwable.
super
和extends
的另一种含义最后值得一提的,尤其对初学者来说,就是super和extends在泛型中的含义。为了写出设计良好的泛型代码,知道他们的意义很重要。
<? extends T>
<? super T>
我非常喜欢Java的一个特性就是它的强类型。众所周知,泛型是在Java 5时引入的,它可以让我们更容易地使用集合类(除了集合类,泛型在其他地方应用也十分广泛,但这是设计泛型的主要原因)。尽管泛型只提供编译时保护,不会 进入字节码,但它的安全类型保护方式非常高效。下面是关于泛型的几个不错特性或者用例。
这可能并不意外,接口和泛型可以很好地兼容。尽管接口和泛型一起使用很常见,但我还是觉得这是一个很不错的特性。这允许开发者创建具有类型安全且考虑到重用的高效代码。比如下面的例子,这是java.lang
包的Comparable
接口:
1
2
3
|
public interface Comparable<T> { public int compareTo(T o); } |
泛型的简单引入,忽略了compareTo
方法的实例检查,使得代码更具连贯性和可读性。通常,泛型以及参数类型顺序的引入,使代码更容易阅读与理解。
谈到边界通配符,Collections
类包中有一个很好的例子。这个类中声明了copy
方法,使用边界通配符来保证列表复制操作的类型安全。以下是它的定义。
public static <T> void copy(List<? super T> dest, List<? extends T> src) { ... }
让我们仔细分析下。copy
方法是一个静态泛型方法,返回类型为void
。它接收两个参数——目的列表和源列表(都有边界)。目的列表被限制为只能存储T类型父类或T类型的实例。相反,源列表存储T类型子类或T类型的实例。这两个限制保证了两个集合以及他们之间复制操作的类型安全。数组中的类型安全被破坏时,会抛出前面提到的ArrayStoreException
异常,所以我们不必关注数组的类型安全问题。
不难想象,有人想在一个简单的边界条件中使用多个边界。实际上,这很容易做到。考虑下面一个例子:我需要创建一个方法,它接收的参数是一个可以比较(实现Comparable
接口)的数字List
。在没有泛型的时代,为了完成上述需求,开发者不得不创建一个不必要的接口ComparableList
。
1
2
3
4
5
6
7
8
9
10
11
|
public class BoundsTest { interface ComparableList extends List, Comparable {} class MyList implements ComparableList { ... } public static void doStuff( final ComparableList comparableList) {} public static void main( final String[] args) { BoundsTest.doStuff( new BoundsTest(). new MyList()); } } |
在下面的尝试中,我们忽视了(不使用泛型的)限制。使用泛型允许我们创建具体的类来满足需求,还可以让doStuff
方法足够开放。我发现的唯一缺点就是语法冗长。但代码还是容易阅读与理解,可以忽略这个缺点。
1
2
3
4
5
6
7
8
9
10
|
public class BoundsTest { class MyList<T> implements List<T>, Comparable<T> { ... } public static <T, U extends List<T> & Comparable<T>> void doStuff( final U comparableList) {} public static void main( final String[] args) { BoundsTest.doStuff( new BoundsTest(). new MyList<String>()); } } |
我决定在本文的最后一章描述我遇到的两个(泛型)最奇怪的概念或用法。你可能永远都不会看到这样的代码,但我觉得提下它应该很有意思。那么没有其他烦扰的事,让我们看看这奇怪的东西。
正如其他语言的语法,你可能会见到一些看起来相当古怪的代码。我想知道最最奇怪的代码是什么样的,它是否甚至能通过编译。我所能想到的就只有下面的了。你猜猜这段代码能否通过编译?
1
2
3
4
5
|
public class AwkwardCode<T> { public static <T> T T(T T) { return T; } } |
即使例子中的代码很糟糕,但它能通过编译并且运行起来也没有任何问题。第一行声明泛型类AwkwardCode
,第二行声明泛型方法T
。方法T
是一个返回类型T
的泛型方法。它接收的参数是T类型的,很不幸参数的名称也是T。这个参数从方法体中返回。
最后一个例子展示引用类型和泛型如何一起工作。我偶然发现了这个问题,有一段代码在调用(泛型)方法的时并没有包含类型参数签名,但通过了编译。一个人只要有一点泛型的编程经验,第一眼看到这样的代码可能会比较吃惊。你能解释一下下面代码的行为吗?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public class GenericMethodInvocation { public static void main( final String[] args) { // 1. returns true System.out.println(Compare.<String> genericCompare( "1" , "1" )); // 2. compilation error System.out.println(Compare.<String> genericCompare( "1" , new Long( 1 ))); // 3. returns false System.out.println(Compare.genericCompare( "1" , new Long( 1 ))); } } class Compare { public static <T> boolean genericCompare( final T object1, final T object2) { System.out.println( "Inside generic" ); return object1.equals(object2); } } |
好了,让我们分析下。第一次调用genericCompare
很直接。我标注了方法的参数类型并提供了两个同样类型的对象——这里没有什么神秘的。第二次调用genericCompare
,在编译时会失败,是因为Long
类型不是String
。最后,第三次调用genericCompare
会返回false
。这次很奇怪,因为这个方法被声明为接收两个相同类型的参数,但这里传入一个String
字面量和一个Long
对象却完全没有问题。这是由编译时的类型擦除过程导致的。由于这次的方法调用没有使用泛型的类型参数<String>
,编译器没办法告诉你传入了两个不同类型的参数。永远记住这一点,(两个参数)最近的共享父类被用来搜索匹配的方法。也就是说,当genericCompare
接受了参数object1
和object2
时,他们被转化为Object类型,但是运行时的多态机制会将他们按照String
和Long
实例来比较——所以这个方法返回false
。现在让我们稍微改一下这段代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public class GenericMethodInvocation { public static void main( final String[] args) { // 1. returns true System.out.println(Compare.<String> genericCompare( "1" , "1" )); // 2. compilation error System.out.println(Compare.<String> genericCompare( "1" , new Long( 1 ))); // 3. returns false System.out.println(Compare.genericCompare( "1" , new Long( 1 ))); // compilation error Compare.<? extends Number> randomMethod(); // runs fine Compare.<Number> randomMethod(); } } class Compare { public static <T> boolean genericCompare( final T object1, final T object2) { System.out.println( "Inside generic" ); return object1.equals(object2); } public static boolean genericCompare( final String object1, final Long object2) { System.out.println( "Inside non-generic" ); return object1.equals(object2); } public static void randomMethod() {} } |
新代码在Compare
类中增加了一个非泛型版本的genericCompare
方法,定义了一个新的randomMethod
方法。randomMethod
方法什么都不做,被GenericMethodInvocation
类的主函数调用了两次。这次的代码可以在第二次调用genericCompare
方法,这是因为我提供了新的方法可以匹配调用。但这又是一种奇怪的用法,针对它,可以提出了一个问题——第二次调用是泛型吗?事实证明——不是。但它仍然可以使用泛型的语法——<String>
。为了更清楚地证明这种用法,我使用泛型语法来调用randomMethod
方法。确实可行,这再一次归功于类型擦除过程——擦除了泛型标记。
但是,在这里使用边界通配符时,情况发生了变化。编译器发出一条明确的编译错误信息:Wildcard is not allowed at this location
,使代码编译失败。要想让代码编译并执行,你必须注释掉第12行。代码更改后,输出结果如下:
1
2
3
4
5
6
|
Inside generic true Inside non-generic false Inside non-generic false |
标签:des style http color java os 使用 io for
原文地址:http://www.cnblogs.com/Lightning-Kid/p/3933777.html