1、为什么需要泛型
package com.xiya.generic;
import java.util.ArrayList;
import java.util.List;
public class GenericTest {
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc");
list.add(111);//可以通过编译
for (Object o : list) {
String value = (String) o;//抛出ClassCastException异常(java.lang.Integer cannot be cast to java.lang.String)
System.out.println(value);
}
}
}
如上,我们向List
类型集合中加入了一个String
类型的值和一个Integer
类型的值(这样是合法的,因为此时list默认的存储类型为Object
类型)。
上面的代码会在运行时抛出ClassCastException异常,因为它尝试将一个Integer转换为String java.lang.Integer cannot be cast to java.lang.String
。接着,来看一下从java5开始,Collection的用法:
package com.xiya.generic;
import java.util.ArrayList;
import java.util.List;
public class GenericTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("abc");
list.add("def");
//list.add(111);//不可以通过编译
for (String s : list) {
System.out.println(s);//无需任何强制类型转换
}
}
}
注意到,List
的创建增加了类型参数String,因此只能向List
中添加String类型对象,添加其他对象会抛出编译异常;
同样可以注意到,foreach循环不需要再添加任何强制类型转换,也就移除了运行时的ClassCastException异常。
泛型的两个好处:
- 编译时类型检查。
- 避免强制类型转换。
2、泛型类与泛型接口
考虑以下场景:您希望开发一个用于在应用中传递对象的容器。但对象类型并不总是相同。因此,需要开发一个能够存储各种类型对象的容器。
鉴于这种情况,要实现此目标,显然最好的办法是开发一个能够存储和检索 Object 类型本身的容器,然后在将该对象用于各种类型时进行类型转换。
package com.xiya.generic;
class ObjectContainer {
private Object obj;
/**
* @return the obj
*/
public Object getObj() {
return obj;
}
/**
* @param obj the obj to set
*/
public void setObj(Object obj) {
this.obj = obj;
}
}
public class GenericTest {
public static void main(String[] args) {
ObjectContainer objectContainer = new ObjectContainer();
objectContainer.setObj("Hello World");
System.out.println(objectContainer.getObj());
}
}
原始类的定义,容易引发ClassCastException。
现在来看一下泛型类来重新定义ObjectContainer
使用<T>
指定泛型参数,如下:
package com.xiya.generic;
class ObjectContainer<T> {
private T obj;
/**
* @return the obj
*/
public T getObj() {
return obj;
}
/**
* @param obj the obj to set
*/
public void setObj(T obj) {
this.obj = obj;
}
}
public class GenericTest {
public static void main(String[] args) {
ObjectContainer<String> objectContainer = new ObjectContainer<>();
objectContainer.setObj("Hello World");
//objectContainer.setObj(111);//无法通过编译
String str = objectContainer.getObj();
System.out.println(str);
}
}
接口的泛型应用和类的泛型应用很类似,如下:
public interface List <E> {
void add(E x);
Iterator<E> iterator();
}
public interface Iterator<E> {
E next();
boolean hasNext();
}
类似的,可以将此应用到自定义的接口与类当中。
3、泛型的命名规范
为了更好地去理解泛型,我们也需要去理解java泛型的命名规范。为了与java关键字区别开来,java泛型参数只是使用一个大写字母来定义。各种常用泛型参数的意义如下:
E — Element,常用在java Collection里,如:List< E >,Iterator< E >,Set< E >
K,V — Key,Value,代表Map的键值对
N — Number,数字
T — Type,类型,如String,Integer等等
S,U,V etc. - 2nd, 3rd, 4th 类型,和T的用法一样
4、泛型方法
定义泛型方法语法格式如下:
调用泛型方法语法格式如下:
public class GenericTest {
public static <T> T getObject(Class<T> c) throws IllegalAccessException, InstantiationException {
return c.newInstance();
}
public static void main(String[] args) {
try {
Person person = getObject(Person.class);
person.setName("lgh");
person.setAge(25);
System.out.println(person);
} catch (IllegalAccessException | InstantiationException e) {
e.printStackTrace();
}
}
}
<T>
和 T
的区别?
其实说白了就是先声明后使用,和变量的使用没有本质区别。
5、泛型参数的界限
有时候,你会希望泛型类型只能是某一部分类型,比如操作数据的时候,你会希望是Number或其子类类型。这个想法其实就是给泛型参数添加一个界限。其定义形式为:
<T extends BoundingType>
此定义表示T应该是BoundingType的子类型(subtype)。T和BoundingType可以是类,也可以是接口。另外注意的是,此处的extends
表示的子类型,不等同于继承。
public class GenericTest {
public static <T extends Number> void test(List<T> list) {
System.out.println(list);
}
public static void main(String[] args) {
List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5);
List<Number> list2 = Arrays.asList(6, 7, 8, 9, 10);
List<String> list3 = Arrays.asList("111", "222");
test(list1);
test(list2);
//test(list3);//报错
}
}
此外,泛型只在编译阶段有效。运行时会进行类型擦除。
即List<String> list = new ArrayList<>();
编译后变成
List list = new ArrayList<>();
。
package com.xiya.generic;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class GenericTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("111");
list.add("222");
// list.add(333);
try {
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, 333);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
System.out.println(list);
}
}
参考:
http://peiquan.blog.51cto.com/7518552/1302898
https://www.ziwenxie.site/2017/03/01/java-generic/
http://www.oracle.com/technetwork/cn/articles/java/juneau-generics-2255374-zhs.html
http://www.infoq.com/cn/articles/cf-java-generics
https://www.ibm.com/developerworks/cn/java/j-lo-gj/index.html
http://oldratlee.com/278/tech/java/study-material-of-java-generic.html
http://blog.csdn.net/LonelyRoamer/article/category/1212099