什么是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类型