什么是Java泛型?
所谓泛型,就是变量类型的参数化。
泛型是JDK1.5中一个最重要的特征。通过引入泛型,我们将获得编译时类型的安全和运行时更小的抛出ClassCastException的可能。
在JDK1.5中,你可以声明一个集合将接收/返回的对象的类型。
使用泛型时如果不指明参数类型,即泛型类没有参数化,会提示警告,此时类型为Object。
为什么要使用泛型?
让我们先看一个示例:
package cn.com.example; import java.util.ArrayList; import java.util.List; /** * Created by Jack on 2017/1/17. */ public class Test { public static void main(String[] args) { List list = new ArrayList(); list.add("jack"); list.add("rose"); list.add(24324); for (int i = 0; i < list.size(); i++) { String name = (String) list.get(i); System.out.println(name); } } }
结果:
Exception in thread "main" jack rose java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at cn.com.example.Test.main(Test.java:18) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。
为什么会出现 java.lang.ClassCastException异常?
因为 String name = (String) list.get(i); 需要进行强制类型转换 所以出现了 java.lang.ClassCastException异常。
有什么方法可以解决这个问题? 答案就是泛型
让我们修改下上面的示例:
package cn.com.example; import java.util.ArrayList; import java.util.List; /** * Created by Jack on 2017/1/17. */ public class Test { public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("jack"); list.add("rose"); // list.add(24324); // 编译出错 for (int i = 0; i < list.size(); i++) { String name = list.get(i); // 不需要强制转换 System.out.println(name); } } }
运行结果:
jack rose
采用泛型写法后,List想加入一个Integer类型的对象时会出现编译错误,通过List<String>,直接限定了list集合中只能含有String类型的元素,List get无须进行强制类型转换,因为此时,集合能够记住元素的类型信息,编译器已经能够确认它是String类型了。
下面我们看看List接口的定义:
public interface List<E> extends Collection<E> { int size(); boolean isEmpty(); boolean contains(Object o); Iterator<E> iterator(); Object[] toArray(); <T> T[] toArray(T[] a); boolean add(E e); boolean remove(Object o); boolean containsAll(Collection<?> c); boolean addAll(Collection<? extends E> c); boolean addAll(int index, Collection<? extends E> c); boolean removeAll(Collection<?> c); boolean retainAll(Collection<?> c); void clear(); boolean equals(Object o); int hashCode(); E get(int index); E set(int index, E element); void add(int index, E element); E remove(int index); int indexOf(Object o); int lastIndexOf(Object o); ListIterator<E> listIterator(); ListIterator<E> listIterator(int index); List<E> subList(int fromIndex, int toIndex); }
在List接口中采用泛型化定义之后,<E>中的E表示类型形参,可以接收具体的类型实参,并且此接口定义中,凡是出现E的地方均表示相同的接受自外部的类型实参。
我们在来看看List 实现类 ArrayList实现:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public E get(int index) { rangeCheck(index); checkForComodification(); return ArrayList.this.elementData(offset + index); } }
我们从源代码角度明白了为什么List加入Integer类型对象编译错误,且List get()到的类型直接就是String类型了。
自定义泛型接口、泛型类、泛型方法
自定义泛型接口、泛型类和泛型方法与上述Java源码中的List、ArrayList类似。
示例:
package cn.com.example; /** * Created by Jack on 2017/1/17. */ public class Test { public static void main(String[] args) { Example<String> example = new Example<String>("Jack"); System.out.println("value:" + example.getValue()); } } class Example<T> { T value; public Example() { } public Example(T value) { this.value = value; } public T getValue() { return value; } }
运行结果:
value:Jack
类型通配符
我们先来看一个示例:
package cn.com.example; /** * Created by Jack on 2017/1/17. */ public class Test { public static void main(String[] args) { Example<Number> name = new Example<Number>(99); Example<Integer> age = new Example<Integer>(712); getValue(name); // getValue(age); // 编译出错 } public static void getValue(Example<Number> example){ System.out.println("value :" + example.getValue()); } } class Example<T> { T value; public Example() { } public Example(T value) { this.value = value; } public T getValue() { return value; } }
getValue(age); // 编译出错 那怎么解决这个问题呢? 答案就是使用通配符
让我们修改上面的代码
package cn.com.example; /** * Created by Jack on 2017/1/17. */ public class Test { public static void main(String[] args) { Example<Number> name = new Example<Number>(99); Example<Integer> age = new Example<Integer>(712); getValue(name); getValue(age); } public static void getValue(Example<? extends Number> example){ System.out.println("value :" + example.getValue()); } } class Example<T> { T value; public Example() { } public Example(T value) { this.value = value; } public T getValue() { return value; } }
运行结果:
value :99 value :712
Example<? extends Number> example 这样就可以编译通过了 意思就是Number的子类都可以使用。
补充:
泛型中 T K V E 各代表的意思
T代表java类型
K V 代表java键值中的key和value
E代表Element
?代表不确定的java类型