概念
在Java中,泛型这个概念是JDK1.5中引入的一个特性,泛型提供了编译时类型安全监测机制。在JDK1.5之前常常会因为泛型的强制转换抛出类型转换错误。比如在list中同时存在字符串和整形两个数组,那么如果在手动转换为同一类型,就容易产生该错误。
所以,该机制会允许程序员在编译时检测非法类型,也因此具有更好的安全性和可读性。
泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。
简单使用
通俗点说,泛型可以在我们不去进行强制转换,却能达到强制转换的效果。典型的就是在一些框架中可以传入各种你自定义的类的实例,且都能给接受。就像下面这张图一样。

常用的泛型可以简要划分为泛型类,泛型方法和泛型接口。
无论是泛型类,还是泛型方法等,泛型其实更多的是作为一种协调性的表现,其设计也能在较大程度上减少程序员针对不同发类型进行重复性的代码工作,简要来说泛型就是一中参数化类型,也就是所操作的数据类型被指定为一个参数。
下面是我针对三个情况写的一个简要的demo,用于了解泛型的使用。其中我们可以使用T,U,E等作为泛型的符号.
private void test(){
//传入String类型
String s="test1";
PairTest<String> pair1=new PairTest();
pair1.data=s;
pair1.get(s);
Log.e("TESTMain",pair1.test()+"\n"+pair1.data.getClass().getName());//获取当前类型的名字
//传入Integer类型
int i=22;
PairTest<Integer> pair2=new PairTest();
pair2.data=i;
pair2.get(i);
Log.e("TESTMain",pair2.test()+"\n"+pair2.data.getClass().getName());
//传入TESTC 自定义类型
TESTC testc=new TESTC();
PairTest<TESTC> pair3=new PairTest();
pair3.data=testc;
pair3.get(testc);
Log.e("TESTMain",pair3.test().test()+"\n"+pair3.data.getClass().getName());
}
class TESTC {
private String test(){
return "cool";
}
}
static class PairTest<T> implements GetHere { //泛型作为参数传入
private T data; //泛型对象
private T test(){ //泛型方法
return data;
}
private void get(T data){ //可作为参数传入
this.data=data;
}
@Override
public T next() {
return null;
}
}
public interface GetHere<U>{ //泛型接口
public U next();
}
运行test方法输出结果如下:
2020-08-26 11:08:56.504 30366-30366/com.example.study E/TESTMain: test1
java.lang.String
2020-08-26 11:08:56.504 30366-30366/com.example.study E/TESTMain: 22
java.lang.Integer
2020-08-26 11:08:56.505 30366-30366/com.example.study E/TESTMain: cool
com.example.study.MainActivity$TESTC
自限定类型
那么在有时候这样泛型可能也有局限,比如我只想传入和处理图片类型的类,但这时如果传入其他类型,内部处理自然会出现一些问题。那么如何来限制这些处理呢?
在协变中有个自限定类型,自限定类型的价值在于它们可以产生协变参数类型——方法参数类型会随子类而变化。其实自限定还可以产生协变返回类型回想一下思路,就是只有集成某个类,也就是属于这个类的范围的泛型才可以被加入,否在报错,那么我们首先先建立这几个类。
class Fruit{}//基类
class Apple extends Fruit{}//苹果,Fruit的继承类
class Orange extends Fruit{}//橘子,Fruit的继承类
class Fushishang extends Apple{}//富士山,Apple的继承类
他们之间的关系逻辑如下左图,我们将明显不属于Fruit这个类的String作为对比项。

然后如下将这些类进行添加操作,其中添加到含这个参数的类中。
private void test(){
FruitTest<Orange> fruitTest=new FruitTest<>();//可添加
FruitTest<Fruit> fruitTest2=new FruitTest<>();//可添加
FruitTest<Apple> fruitTest3=new FruitTest<>();//可添加
FruitTest<Fushishang> fruitTest4=new FruitTest<>();//可添加
FruitTest<String> fruitTest5=new FruitTest<>();//不可添加
}
class FruitTest<T extends Fruit> {
private T data;
private T test(){
return data;
}
}
在添加时就会如上注释一样,如果不是继承与Fruit类里的类都无法进行添加,那么这种在开发中比较常见的写法就是自限定了,简要说就是限定了传入参数的范围。
协变和逆变
讲完自限定,我们来简单了解下什么是协变和逆变,这里就不从源码的角度深入剖析,对于协变和逆变在这就简单对其进行的一个使用概念进行讲解,因为我发现我看这块看多了反而更乱了。
首先协变和逆变它一般是用在像列表,方法这些地方,是用来描述类型转换(type transformation)后的继承关系的
我们还是拿前面的水果作为例子。为了更好的说明,我这里就用列表来作为例子。
如下图,下有红线说明语句有错误,这里是传入参数不对。

从这个图上其实就可以很快发现协变和逆变的不同:
- <? extends Apple>:协变,传入值可为Apple,或继承Apple的类,类似自限定。
- <? super Apple>:逆变,传入值也可为Apple,与上者不同,这里只能传入父级类作为参数,当然父级的父级也是可以的。
如果说协变的参数传入是往下的,逆变就是往上。 为了更好的说明这个,我制作了下面这张图:

除了Apple本身可被协变和逆变作为方法传入,红色是协变的传入参数,黄色的是逆变传入参数,同级因为没有互相继承关系不可传入。
当然除了list,我们还常常在其他地方见到协变和逆变,比如下面的方法:
class FruitTest<T> {
private void XT(FruitTest<? extends T> fruitTest) {
System.out.println("这是协变方法");
}
private void NT(FruitTest<? super T> fruitTest) {
System.out.println("这是逆变方法");
}
private T data;
private T test() {
return data;
}
}
结合我们对上面的泛型的理解,对于这个较复杂的类的一些使用和相关参数的传入,想必已经很清楚了吧!
希望通过该简单的方式,能够让你理解泛型的使用。

2843

被折叠的 条评论
为什么被折叠?



