Java-泛型相关
1.什么是泛型?
泛型,就是参数化类型。类似于函数的形参实参,泛型就是参数化类型,也就是给类型指定了一个参数,然后在使用的时候去指定类型,就可以灵活的完成一些操作。泛型,应用在类,接口和方法中,也及时泛型类,泛型接口和泛型方法。
2.为什么使用泛型?
如果没有泛型:
(套用一下别人的代码)
1 private static void genericTest() { 2 List arrayList = new ArrayList(); 3 arrayList.add("总有刁民想害朕"); 4 arrayList.add(7); 5 6 for (int i = 0; i < arrayList.size(); i++) { 7 Object item = arrayList.get(i); 8 if (item instanceof String) { 9 String str = (String) item; 10 System.out.println("泛型测试 item = " + str); 11 }else if (item instanceof Integer) 12 { 13 Integer inte = (Integer) item; 14 System.out.println("泛型测试 item = " + inte); 15 } 16 } 17 }
arrayList可以传入任意类型,但是到使用的时候,我们可能会对List里的对象进行很多特定的计算。就比如例子中,将字符串转换成字符串进行打印是没问题的,但是如果将int转换成字符串就会出问题,而这种问题在编译的时候根本检查不出来(因为语法上根本没错),只有在运行时从List里面取值传参了才会报错。要是想完成上面的操作而且不出错,我们就必须要多写几个方法,去分别判断对应的类型,然后执行各自的方法。但是类型那么多,你if-else也写不过来。所以我们用泛型来解决问题,去人为地限制参数类型。
用泛型划定范围之后,如果我们传入的不是范围中的类型,在编译阶段就会爆红。
3.泛型的使用方法
3.1泛型类
食用方法:在类的申明时指定参数。
1 public class FanXing<T> 2 { 3 public T name; 4 public void haHaha(T heihei) 5 { 6 7 } 8 public FanXing(T name) 9 { 10 this.name=name; 11 } 12 }
就是在类名后面加了个<T>,然后把里面你要用到泛型的地方用T替换。
然后在使用的时候:
FanXing<String> fan=new FanXing<>("卢本伟牛逼");
很简单吧?
3.2泛型接口
跟着泛型类基本一样。
1 public interface FanXing<T> 2 { 3 4 }
3.3泛型方法
这块最难!
(再次借用别人的代码)
1 public class Generic<T> { 2 public T name; 3 public Generic(){} 4 public Generic(T param){ 5 name=param; 6 } 7 public T m(){ 8 return name; 9 } 10 public <E> void m1(E e){ } 11 public <T> T m2(T e){ } 12 }
注意这句话
1 public <E> void m1(E e){ }
这句话就是一个泛型方法。和普通的泛型方法的区别就是,在返回值类型void前面加了泛型参数<E>,
这个也是区别泛型方法与一般方法的关键。<>内限制了这个类的类型参数,在这个类里面,这个参数(也就是那个E)可以自由使用,与一般的类型使用无异。
再看这句:
1 public <T> T m2(T e){ }
这句话中的<T>跟类声明中的Generic<T>
的T没关系,两个T。就比如下面这个例子:
1 Generic<String> str=new Generic<>("总有刁民想害朕"); 2 str.m2(123);
类里面传入的是String,方法里传的是Integer,不冲突。
4.泛型的食用方法
4.1如何继承一个泛型类
①如果父类不传入任何具体的类型,那么子类就需要置顶类型参数。
1 class Son<T> extends Generic<T>{}
②父类传参了,子类就甭写了:
1 class Son extends Generic<String>{}
4.2如何实现一个泛型接口
接口未指定具体类型:
1 class ImageGenerator<T> implements Generator<T>
指定了类型:
1 class ImageGenerator implements Generator<String>
4.3如何调用一个泛型方法:
与普通方法无二,不管是静态方法还是实例方法。
5.通配符?
5.1什么是通配符?
?代表具体类型。比如:
1 public void m3(List<?>list){ 2 for (Object o : list) { 3 System.out.println(o); 4 } 5 }
参数类型是?,那么在调用的时候就可以传入任意类型的List。
1 str.m3(Arrays.asList(1,2,3)); 2 str.m3(Arrays.asList("总有刁民","想害","朕"));
传入了Integer和String。
但是单独使用?的时候并不多,因为传入任意类型也就代表传入的都是Object类型,在使用传入的类型时就需要强制转换。这跟不用泛型有啥区别?而且Object就那么几个方法可以用。所以,下面的才是重点内容。
5.2通配符上界
通配符上界使用的是
1 <? extends T>
这种格式,也就是这个未知的类型继承了T,通俗点就是
T类型或者T类型的子类。
5.3通配符下界
通配符下界使用的是
1 <? super T>
也就是
T类型或者T类型的父类
5.4什么时候使用通配符上下界?
有一个原则叫PECS原则,也就是producer-extends,consumer-super。
换句话说,如果参数化类型表示一个生产者,就使用 <? extends T>
;如果参数化类型表示一个消费者,就使用<? super T>
。
关于PECS具体链接:https://blog.csdn.net/fangfengzhen115/article/details/78973258
我现在也不是特别懂这个原则,先放着。。
5.5泛型在静态方法中的问题
泛型类中的静态方法和静态类里面不能用泛型类所声明的泛型类型参数。
就比如:
1 public class Test<T> { 2 public static T one; //编译错误 3 public static T show(T one){ //编译错误 4 return null; 5 } 6 }
因为静态变量和静态方法都是属于类所有,先于对象的创建而存在。但是泛型的参数实例化是要依赖对象创建时来指定的,所以说不创建对象,也就没有确定的参数类型。
但是静态泛型方法是可以使用的。因为前面说过,泛型方法与泛型类的类型是两码事儿,互不影响。
6.泛型原理浅解析
Java在编译阶段就擦除了所有的泛型信息,也就是关于泛型的所有信息都不会存在于你的class文件中。换句话说,在程序运行时,Java根本没有获取到任何泛型信息。所有的类型转换操作,都在编译阶段调用相关函数完成了。
关于泛型差不多都在这呢,以后再遇到泛型的啥问题,就往上补吧=_=