泛型简介/原理
1、从JDK1.4开始是没有泛型的概念的,那怎么做呢?如下列代码
List list = new ArrayList();
list.add("string");
list.add(11);
String name = (String)list.get(0);
Integer number = (Integer)list.get(1);
△这样子做有什么缺点呢
存的都是object,需要强转,转换时可能发生异常
2、JDK1.5以后的写法
List<String> list = new ArrayList();
list.add("string");
list.add("string2");
String cnBlogs = list.get(0);
String myWebSite = list.get(1);
总结: 泛型就是将类型参数化,其在编译时才确定具体的参数。在上面这个例子中,这个具体的类型就是 String。
可以看到我们在创建 List 集合的时候指定了 String 类型,这就意味着我们只能往 List 集合中存放 String 类型的数据。
而当我们指定泛型之后,我们去取出数据后就不再需要进行强制类型转换了,这样就减少了发生强制类型转换的风险。
这就是Sun公司为了JAVA语言更严格安全。
3、泛型的原理:
能看出这段代码的结果是什么吗?
ArrayList<String> a = new ArrayList<String>();
ArrayList<Integer> b = new ArrayList<Integer>();
Class c1 = a.getClass();
Class c2 = b.getClass();
System.out.println(c1 == c2);
△答案:是true,泛型只存在于编译阶段,而不存在于运行阶段。c1和c2获取到的都是java.util.ArrayList,Integer 只不过是 JVM在编译的时候会进行转换,编译后的文件时没有泛型概念的,也就是SUN严格规范了JAVA语言更安全。
△扩展:上面我们只是说了泛型在集合中的使用方式,但其实泛型的应用范围不仅仅只是集合,还包括类、方法、Map 接口等等
泛型的应用场景
泛型类
1、泛型类一般使用字母 T 作为泛型的标志
public class GenericClass<T> {
private T object;
public T getObject() {
return object;
}
public void setObject(T object) {
this.object = object;
}
}
△扩展: MAP类中也有标志 K-V
public class GenericMap<K, V> {
private K key;
private V value;
public void put(K key, V value) {
this.key = key;
this.value = value;
}
}
public static void main(String[] args) {
GenericMap<Integer, String> team = new GenericMap<>();
team.put(1, "YaoMin");
team.put(2, "Me");
GenericMap<String, Integer> score = new GenericMap<>();
score.put("YaoMin", 88);
score.put("Me", 80);
}
△扩展: 那么像每次定义枚举类型,都是数值什么的那么我们怎么定义中文描述
不会动的加载中...
泛型方法
1、泛型方法一般也用字母 T 作为泛型的标志
public class GenericMethod {
public static <T> T getObject(Class<T> clz) throws InstantiationException, IllegalAccessException{
T t = clz.newInstance();
return t;
}
}
public static void main(String[] args) throws Exception{
GenericMethod genericMethod = getObject(GenericMethod.class);
System.out.println("Class:" + genericMethod.getClass().getName());
}
泛型集合 拉拉最顶上↑
泛型通配符
1、除了泛型类、泛型方法之外,泛型还有更加复杂的应用,如:
List<? extends Number> list = new ArrayList();
List<? super Number> list = new ArrayList();
△上面的 extends 和 super 关键字其实就是泛型的高级应用:泛型通配符。
△但在讲泛型通配符之前,我们必须对编译时类型和运行时类型有一个基本的了解,才能更好地理解通配符的使用。
public class Tree {}
public class Fruits extends Tree{}
public class Banana extends Fruits{}
public class Plate<T> {
T t;
public T get(){
return t;
}
public void set(T t){
this.t=t;
}
}
2、来看看泛型的劣优
(1)Error 泛型不支持向上转型
/* Plate<Fruits> banana=new Plate<Banana>();*/
(2) ? extends 用法
Plate<? extends Fruits> bananaExtens=new Plate<Banana>();
/*//Error不允许写入 不管是父类还是子类
bananaExtens.set(new Fruits());*/
/**就这一步,未运行时根本不知道你实例的那个子类,那 JVM 就干脆什么都不给放,避免出错。**/
//只允许读取
bananaExtens.get();
(3) ? super 用法 声明子类,其实例对象可以是父类或是父类以上的级别,例Object也是可以的
Plate<? super Banana> bananaFruSuper=new Plate<Fruits>();
Plate<? super Banana> bananaTreSuper=new Plate<Tree>();
//可以写入,但是只能写入声明对象中同级或者以下的类 子类,子子类
//就这步,为运行时JVM既然不能知道实例对象是哪个,但是JVM能确定Banana的子类能转换成Banana,但是父类却不能转换()
bananaTreSuper.set(new Banana());
/*//都是高于声明对象级别
bananaTreSuper.set(new Tree());
bananaTreSuper.set(new Fruits());*/
/*//Error也是高于对象级别
bananaFruSuper.set(new Fruits());*/
/* //Error 能取但是,只能用Object 来接收
Banana b= bananaFruSuper.get();
Fruits f= bananaFruSuper.get();
*/
Object o= bananaFruSuper.get();
PESC原则
在泛型编程时,使用部分限定的形参时,<? super T>和<? extends T>的使用场景容易混淆,PECS原则可以帮助我们很好记住它们:
生产者(Producer)使用extends,消费者(Consumer)使用super。
留下一段代码加深印象(来自JDK 8 Collections.copy()源码)
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
参考于:
https://www.cnblogs.com/chanshuyi/p/deep_insight_java_generic.html