标签:
看一下泛型的知识,可以去这个原版的链接。以下部不是是写的,原文的链接在这里http://sumyblog.me/2015/12/15/java-generic-type-one-two-and-three/
泛型(Generic Type)是Java中重要的一部分。在使用Java标准库中的内容的时候,经常会遇到泛型。这里将知道的泛型部分内容总结一下。以后遇到新的内容还会继续补充。
什么是泛型
讨论一个内容的时候,首先会来说什么是什么。在官方的文档中说到
A generic type is a generic class or interface that is parameterized over types.
泛型又可以称作参数化类型,这是在Java SE新增添的特性。一对尖括号,中间包含类型信息。将类型独立成参数,在使用的时候才指定实际的类型。
如果没有泛型会怎么样?我们考虑以下几种情况:
- 你实现了一个存储整数类型(Integer)的列表,这时候你又需要存储字符串(String)的列表,两种列表逻辑行为完全一样,只是存储的类型不同。
- 为了保证列表的通用性,你将列表的类型改为了Object,这样就不用为类型修改代码了。但是每次从列表中取对象的时候都需要强制转换,而且很很容易出错。
有了泛型之后,可以将逻辑相同类型不同的代码独立出来,由编译器负责进行类型转换。
泛型的声明
泛型方法(Generic Method)
泛型方法是在普通方法声明上加入了泛型。
1 2 3 4 5 6 7
|
public static < E > void printArray( E[] inputArray ) { for ( E element : inputArray ){ System.out.printf( "%s ", element ); } System.out.println(); }
|
调用:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
Integer[] intArray = { 1, 2, 3, 4, 5 }; Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 }; Character[] charArray = { ‘H‘, ‘E‘, ‘L‘, ‘L‘, ‘O‘ }; System.out.println( "Array integerArray contains:" ); printArray( intArray ); System.out.println( "\nArray doubleArray contains:" ); printArray( doubleArray ); System.out.println( "\nArray characterArray contains:" ); printArray( charArray );
|
输出:
1 2 3 4 5 6 7 8
|
Array integerArray contains: 1 2 3 4 5 6 Array doubleArray contains: 1.1 2.2 3.3 4.4 Array characterArray contains: H E L L O
|
Java泛型方法的声明格式如下:
1 2
|
[权限] [修饰符] [泛型] [返回值] [方法名] ( [参数列表] ) {} public static < E > void printArray( E[] inputArray ) {}
|
泛型的声明,必须在方法的修饰符(public,static,final,abstract等)之后,返回值声明之前。可以声明多个泛型,用逗号隔开。泛型的声明要用<>
包裹。
泛型方法的使用有两种:
类型推导
以声明键值对的例子来说,通常的写法会有一长串,不免有些痛苦。
1
|
Map<String, List<String>> m = new HashMap<String, List<String>>();
|
我们可以构造一个泛型方法作为静态工厂,来完成这一操作。
1 2 3 4
|
public static <K, V> HashMap<K, V> newInstance() { return new HashMap<K, V>(); } Map<String, List<String>> m = newInstance();
|
编译器在编译代码的时候推导出了K, V
分别对应的类型。当然,编译器的推导能力也是有限的,这里也就不过多讨论了。
指定类型
泛型类(Generic Class)
泛型类和普通类的声明一样,只是在类名后面加上了类型表示。就像泛型方法,泛型类可以有一个或多个类型表示,用逗号进行分隔。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public class Box<T> { private T t; public void add(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { Box<Integer> integerBox = new Box<Integer>(); Box<String> stringBox = new Box<String>(); integerBox.add(new Integer(10)); stringBox.add(new String("Hello World")); System.out.printf("Integer Value :%d\n\n", integerBox.get()); System.out.printf("String Value :%s\n", stringBox.get()); } }
|
输出:
1 2
|
Integer Value :10 String Value :Hello World
|
在泛型类上声明的类型,可以被用到类中任何表示类型的地方。
泛型类只能通过以指定类型的方式进行使用。在之后的Java版本中,加入了类型推导功能,可以将后面的泛型类型省略,但是还是需要保留尖括号。
1 2
|
List<String> list = new ArrayList<String>(); List<String> list = new ArrayList<>();
|
泛型接口(Generic Interface)
泛型接口是在声明接口的时候指定,类在继承接口的时候需要补充泛型类型。
1 2 3
|
public interface Info<T> { public T getInfo(); }
|
然后定义一个类实现这个接口
1 2 3 4 5
|
public InfoImp implements Info<String> { public String getInfo() { return "Hello World!"; } }
|
可以发现实现接口里的方法需要使用具体的类型。
泛型接口的一般格式:
1
|
[访问权限] interface [接口名] <泛型标识> {}
|
当然,我们可以实现泛型接口的时候不指名泛型类型,这样这个类就需要定义为泛型类。
1 2 3 4 5
|
public InfoImp<T> implements Info<T> { public T getInfo() { return null; } }
|
泛型标识与泛型通配符
理论上泛型标识可以使用任意的字母或字母序列,可以考虑以下的例子,但是不推荐这样使用
1 2 3 4
|
public static <STRING> STRING sayHello(STRING word){ System.out.println("Hello " + word); return a; }
|
但是Java内部有一套自己的规范,这样在阅读JDK代码的时候会更加明确泛型标识的含义。
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型
S、U、V - 2nd、3rd、4th types
说到泛型标识符,再说一说泛型通配符。常用的泛型通配符有三种。
任意类型 - <?>
<?>
可以理解为泛型中的 Object
,为什么这么说呢?因为任意类型的通配符可以接受任意类型的泛型。下面的例子表示出了这种关系
1
|
Box<?> box = new Box<String>();
|
类似于将后面的类型转换到前面的类型。但是<?>
只能用作接收,不能用来定义,下面的例子是错误的
1 2 3
|
class Box<?> {} public static <?> void sayHello(? helloString) {} interface Box<?> {}
|
上限类型 - <? extends 类>
<? extends 类>
表示泛型只能使用这个类
或这个类
的子类。举个例子
1
|
public static <T extends String> void sayHello(T helloString) {}
|
在该方法中调用sayHello("xiaoming");
是正确的,但是调用sayHello(2333);
就是错误的。
考虑一种更通用的情况
1 2 3 4 5
|
public static void printList(List<? extends String> list){ for(String str:list){ System.out.println(str); } }
|
这个例子指定了泛型类的具体类型的范围。在JDK中经常可以看到这样的使用方法。
下限类型 - <? super 类>
同上限方法,<? super 类>
表示泛型只能使用这个类
或这个类
的父类。
这里就不再举例子了。
泛型二三事
类型擦除
泛型只在编译时有效,编译成字节码的过程中会进行类型擦除的操作。当然并不是所有的泛型类型都被擦除,有些也会保留下来。
一个简单的类型擦除的例子:
1 2 3 4 5
|
List<String> list = new ArrayList<>(); Iterator<String> it = list.iterator(); while (it.hasNext()) { String s = it.next(); }
|
我们对其编译,然后再反编译,反编译引擎用的CFR:
1 2 3 4 5
|
ArrayList arrayList = new ArrayList(); Iterator iterator = arrayList.iterator(); while (iterator.hasNext()) { String string = (String)iterator.next(); }
|
从上面的结果可以看出,泛型类上面的类型被去掉了,但是增加了一个类型强制转换。解释器默认认为里面的类型都会是String
。这是因为在编译的时候会进行类型检查,如果发现使用的类型与泛型声明类型不符,编译是不会通过的。
那能不能绕过这个检查呢?这个时候就需要使用反射来进行操作了。就上面的例子来说,ArrayList
可以放入任意类型,所以使用反射只要保证类型强制转换不出问题,程序还是可以使用的。
在Java文档中提到,类型擦除主要进行以下工作:
- 将泛型中的所有类型参数更换成类型界限,或者无界的类型替换成Object。所以生成的字节码只包含普通类、接口和方法。
- 为了确保类型安全,必要时插入强制类型转换
- 生成桥接方法保持扩展泛型类型中的多态性
可变参数
使用泛型方法可以使用可变参数:
1 2 3 4 5 6 7 8 9 10 11 12
|
public class Main { public static <T> void out(T... args) { for (T t : args) { System.out.println(t); } } public static void main(String[] args) { out("findingsea", 123, 11.11, true); } }
|
可以发现编译器很好的处理了这些,里面的具体原理还有待继续研究。
new T()?
你可能会很好奇,能不能在泛型方法(类)中创建泛型类型的实例呢?
答案是不可以的
1 2 3 4
|
public static <E> void append(List<E> list) { E elem = new E(); list.add(elem); }
|
不过可以使用反射来创建实例
1 2 3 4 5 6 7
|
public static <E> void append(List<E> list, Class<E> cls) throws Exception { E elem = cls.newInstance(); list.add(elem); } List<String> ls = new ArrayList<>(); append(ls, String.class);
|
因为类型擦除的缘故,部分类型信息会丢失,我们在运行时不会获取到相应的类型,所以也就无法将该类型实例化成对象。
泛型和数组
在Java中,直接创建泛型数组是非法的。泛型设计的初衷是为了简化程序员类型转换的操作,保证类型安全。数组是协变的,如果Sub为Super的子类型,那么数组Sub[]就是Super[]的子类型。这样做就很难保证存储上的安全。
但在实际使用过程中,往往需要创建泛型数组:
1 2 3
|
public static <E> E[] newArray(int n) { return new E[n]; }
|
这个时候运行程序,会抛出异常
1 2
|
Exception in thread "main" java.lang.Error: Unresolved compilation problem: Cannot create a generic array of E
|
在这种情况下,可以使用列表来代替数组
1 2
|
public static <E> List<E> newList() { return new ArrayList<E>();
|
但是,Java不是生来就有List的,如果遇到必须使用数组的情况该怎么办?
在这里可以参考Java里聚合类型的实现,以一个简单的例子说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
public class Stack<E> { private E[] elements; private int size = 0; public Stack() { elements = new E[10]; } public void push(E e) { elements[size++] = e; } public E pop() { E result = elements[--size]; elements[size] = null; return result; } }
|
上面的例子和java.util.Stack
还是有区别的,只是为了说明如何处理泛型数组问题。
同样运行的时候会出错
1 2
|
Exception in thread "main" java.lang.Error: Unresolved compilation problem: Cannot create a generic array of E
|
一种方法是在创建泛型数组的时候创建一个Object数组,然后转换成E数组。
1
|
elements = (E[]) new Object[10];
|
第二种方法将数组类型改为Object,在弹出元素的时候进行转换
1 2 3 4 5 6 7 8
|
public class Stack<E> { private Object[] elements; ... public E pop() { E result = (E) elements[--size]; ... } }
|
数组和泛型有着不同的规则和特性,一般来说不能很好的混用。如果混合起来的时候,请注意编译器的警告和错误,保证在类型问题上不要出现问题。
java 泛型
标签:
原文地址:http://blog.csdn.net/code_my_life/article/details/51887268