泛型
不要在新代码中使用原始类型
泛型(generic):声明中具有一个或多个类型参数
原始类型(raw type):不带任何实际类型参数的泛型名称
格式:
类或接口的名称
<
对应于泛型形式类型参数的实际参数
>
如
List<String>
就是对应于List<E>
的实际参数为String
的参数化类型如与
List<E>
对应的原始类型是List
优点:
- 在编译时发现插入类型的错误(越早发现错误越好,编译时发现好于运行时发现)
- 不再需要手工转换类型
//JDK5之前的写法,使用的是原始类型
private static final List stringList = new ArrayList();
//有了泛型之后的写法,使用泛型
private static final List<String> stringList2 = new ArrayList<String>();
//JDK7 能将后面<>里的类型省略,被称为Diamond
private static final List<String> stringList3 = new ArrayList<>();
public static void main(String[] args) {
String str = "test";
Integer integer = 1;
stringList.add(str);
stringList.add(integer);//可通过编译,但之后报ClassCastException错误
stringList2.add(str);
// stringList2.add(integer);//无法通过编译
for(Iterator iterator = stringList.iterator();iterator.hasNext();){
String string = (String) iterator.next();
System.out.println(string);
}
for(Iterator iterator = stringList2.iterator();iterator.hasNext();){
String string = iterator.next();
System.out.println(string);
}
List
和List<Object>
之间的区别?
List
逃避了泛型检查,List<Object>
则是告知编译器,它能够持有任意类型的对象
无限制的通配符类型:
使用泛型,但不确定或者不关心实际的类型参数,可以用一个问号代替。如List<?>
泛型信息在运行时会被擦除
学习链接:
1.https://docs.oracle.com/javase/tutorial/java/generics/erasure.html
2.http://stackoverflow.com/questions/313584/what-is-the-concept-of-erasure-in-generics-in-java
下面通过一个小demo说明类型擦除
//类型擦除
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
System.out.println(stringList.getClass().toString());
System.out.println(integerList.getClass().toString());
System.out.println(stringList.getClass()==integerList.getClass());
integerList.add(100);
Method method = integerList.getClass().getMethod("add",Object.class);
method.invoke(integerList,"abc");
System.out.println(integerList);
运行结果:
一般不在代码中使用原始类型,除了两种例外情况(都是因为泛型信息在运行时会被擦除):
- 1.在类文字(class literals)中
如: List.class,String[].class,int.class都合法 List<String>.class,List<String>.class都不合法
- 2.instanceof
-
if(o instanceof Set){ //原始类型(Raw Type) Set<?> set = (Set<?>)o;//通配符类型(WildCard Type) }
下面的表格是泛型相关的术语:
下面这张图很好的介绍了无限制通配符和其他泛型符号之间的关系:
消除非受检警告
始终在尽可能小的范围内使用SuppressWarnings注解
Java源码中的ArrayList类中有个toArray方法,其中就有强转的警告:
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size)
// Make a new array of a's runtime type, but my contents:
return (T[]) Arrays.copyOf(elementData, size, a.getClass());
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
最好是将范围进一步缩小。将注解由整个方法到局部的变量声明上去。
@SuppressWarnings("unchecked")
public <T> T[] toArray(T[] a) {
if (a.length < size){
@SuppressWarnings("unchecked")
T[] result = (T[]) Arrays.copyOf(elementData, size, a.getClass());
return result;
}
System.arraycopy(elementData, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
列表优于数组
- 数组是协变的(covariant),泛型则是不可变的
Object[] objectArray = new String[1];
List<Object> objectList = new ArrayList<String>();//无法通过编译 imcompatible types
// String类是Object类的子类
//String[]是Object[]的子类
//而List<String>并不是List<String>的子类型
- 数组是具体化的(reified),在运行时才知道并检查它们的元素类型约束。而泛型通过擦除来实现的。泛型只在编译时强化类型信息,并在运行时擦除它们的元素类型信息。擦除就是使泛型可以与没有使用泛型的代码可以互用。
Object[] objectArray = new String[1];
List<String> objectList = new ArrayList<String>();
objectArray[0] = 3;//可通过编译,运行时报错
// objectList.add(1);//编译时报错
数组和泛型不能很好地混合使用。可用列表代替数组。
总结:数组是协变且可具体化的,泛型是不可变的且可被擦除的。-->数组提供了运行时类型安全而编译时类型不安全。而泛型反之。
优先考虑泛型
泛型相比于Object的优点:
- 不需要强制类型转换
- 编译时类型安全
public class SomeClazz<T> {
public Object dosthWithObj(Object obj){
return obj;
}
public T dosthWithT(T t){
return t;
}
public static void main(String[] args) {
SomeClazz<Foo> someClazz = new SomeClazz<Foo>();
Foo foo = new Foo();
Foo foo1 = (Foo) someClazz.dosthWithObj(foo);
Foo foo2 = someClazz.dosthWithT(foo);
}
}
public class Stack<E> {
private E [] elements;
private static final int MAX_SIZE = 16;
private int size = 0;
@SuppressWarnings("unchecked")
public Stack(){
elements = (E[]) new Object[MAX_SIZE];
}
public void push(E e){
ensureSize();
elements[size++]=e;
}
public E pop(){
if(size==0)
throw new EmptyStackException();
E e = elements[--size];
elements[size]=null;
return e;
}
private void ensureSize() {
if(size==elements.length){
elements= Arrays.copyOf(elements,2*size+1);
}
}
public static void main(String[] args) {
Stack<Integer> stack = new Stack<>();
for(int i =0;i<50;i++){
stack.push(i);
}
for(int i = 0;i<10;i++){
System.out.println(i+": "+stack.pop());
}
}
}
class EmptyStackException extends RuntimeException{
}
前面曾鼓励优先使用列表而不是数组。并不意味着所有的泛型中都要使用列表。况且Java并不是生来就支持列表的。
每个类型都是它自身的子类型。
如有 SomeClazz<E extends Number>
SomeClazz<Number>是合法的
优先考虑泛型方法
方法可以考虑泛型化,特别是静态工具方法。
泛型方法语法:
方法修饰语 泛型 返回值 方法名()
public static <T> T foo(T args);
/**
* 使用泛型方法
* 返回两个集合的联合
* @param s1
* @param s2
* @param <E>
* @return
*
* 局限:两个参数和返回的结果的类型必须全部相同
* 解决方法:使用有限制的通配符
*/
public static <E> Set<E> unionGeneric(Set<E> s1,Set<E> s2){
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
public static <K,V> Map<K,V> newHashMap(){
return new HashMap<K,V>();
}
泛型单例工厂:
public interface UnaryFunction<T>{
T apply(T arg);
}
private static UnaryFunction<Object> IDENTITY_FUNCTION =
new UnaryFunction<Object>() {
@Override
public Object apply(Object arg) {
return arg;
}
};
@SuppressWarnings("unchecked")
public static <T> UnaryFunction<T> identityFunction(){
return (UnaryFunction<T>) IDENTITY_FUNCTION;
}
/**
* 每次都要创建一个,很浪费,而且它是无状态的.
* 泛型被具体化了,每个类型都需要一个恒等函数,但是它们被擦除后,就只需要一个泛型单例了.
* @param <T>
* @return
*/
public static <T> UnaryFunction<T> identityFunction2(){
return new
UnaryFunction<T>() {
@Override
public T apply(T arg) {
return arg;
}
};
}
递归类型限制:
通过某个包含该类型参数本身的表达式来限制类型参数
<T extends Comparable<T>>//针对可以与自身进行比较的每个类型T
利用有限制通配符来提升API的灵活性
参数化类型是不可变的。
虽然String类是Object类的子类,但是List<String>和List<Object>无关
/**
* 栈的实现
* @param <E>
* API:
* public Stack();
* public void push(E e);
* public E pop();
* public boolean isEmpty();
*
* 新增API:
* before:
* public void pushAll(Iterable<E> i);
* public void popAll(Collection<E> c);
* after:
* 使用有限制的通配符类型(bounded wildcard type)
* public void pushAll(Iterable<? extends E> i);
* public void popAll(Collection<? super E> c);
*
*/
class Stack<E>{
private E [] elements;
private static final int INIT_CAPABILITY = 16;
private int size = 0;
@SuppressWarnings("unchecked")
public Stack(){
elements = (E[]) new Object [INIT_CAPABILITY];
}
public void push(E e){
checkCapability();
elements[size++]=e;
}
public E pop(){
if(size==0)
throw new RuntimeException("Empty Stack");
E e = elements[--size];
elements[size]=null;
return e;
}
private void checkCapability() {
if(size==elements.length)
elements = Arrays.copyOf(elements,2*elements.length-1);
}
public boolean isEmpty(){
return size==0;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
String start = super.toString();
sb.append(start);
for(int i = 0 ;i<size;i++){
String s = " ["+elements[i]+"]";
sb.append(s);
}
return sb.toString();
}
//before
// public void pushAll(Iterable<E> i){
// for(E e:i){
// push(e);
// }
// }
// public void popAll(Collection<E> c){
// while (!isEmpty()){
// c.add(pop());
// }
// }
//after
public void pushAll(Iterable<? extends E> i){
for(E e:i){
push(e);
}
}
public void popAll(Collection<? super E> c){
while(!isEmpty()){
c.add(pop());
}
}
Stack<Number> stack= new Stack<>();
Iterable<Integer> integers = Arrays.asList(1,2,3,4,5);
Collection<Object> objectCollection = new LinkedList<>();
//before
// stack.pushAll(integers);//参数类型不对
// stack.popAll(objectCollection);//参数类型不对
//after
stack.pushAll(integers);
System.out.println(stack);
stack.popAll(objectCollection);
System.out.println(stack);
从上面的Demo中我们知道,Java中提供了有限制的通配符类型
来提高API的灵活性。
如
Collection<? extends E>
Collection<? super E>
一般在表示生产者或消费者的输入参数上使用通配符类型。
PECS:Producer-extends Consumer-super
------------------ * 参数化类型 通配符类型 * T生产者 extends * T消费者 super * ------------------
原文地址:http://www.cnblogs.com/JohnTsai/p/5344733.html