0%

6_泛型

泛型是 java 1.5中添加的。在没有泛型之前,从集合中读取每一个对象都必须进行转换,如果有人不小型插入了错误的类型,那么就会在运行的时候出现类型转换异常。
泛型的作用就是告诉每个集合可以接受哪些类型,在编译阶段就可以告知是否插入了错误的对象

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型类的优点:

  • 在使用时,不用强转转换
  • 能够在编译阶段识别到 插入了错误的对象类型

泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Point<T>{// 此处可以随便写标识符号   
private T x ;
private T y ;
public void setX(T x){//作为参数
this.x = x ;
}
public void setY(T y){
this.y = y ;
}
public T getX(){//作为返回值
return this.x ;
}
public T getY(){
return this.y ;
}
};

class Test {
public static void main(String[] args){
//IntegerPoint使用
Point<Integer> p = new Point<>() ;
p.setX(new Integer(100)) ;
System.out.println(p.getX());

//FloatPoint使用
Point<Float> p = new Point<>() ;
p.setX(new Float(100.12f)) ;
System.out.println(p.getX());
}
}

泛型类在构造时,需要在类名后添加上<String>,即一对尖括号,中间写上要传入的类型。之后的使用方法就和普通类的使用差才不多了。

泛型接口

1
2
3
4
interface Info<T>{        // 在接口上定义泛型    
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
public void setVar(T x);
}

泛型方法

泛型方法与以往方法的唯一不同点就是

在返回值前加上 <T> 来表示泛型变量。

1
2
3
4
5
6
7
8
9
10
public class StaticFans {  
//静态函数
public static <T> void StaticMethod(T a){
Log.d("harvic","StaticMethod: "+a.toString());
}
//普通函数
public <T> void OtherMethod(T a){
Log.d("harvic","OtherMethod: "+a.toString());
}
}
1
2
3
4
5
6
7
8
9
10
11
12
class Test{
public static void main(String[] args){
//静态方法
StaticFans.StaticMethod("adfdsa");//使用方法一
StaticFans.<String>StaticMethod("adfdsa");//使用方法二

//常规方法
StaticFans staticFans = new StaticFans();
staticFans.OtherMethod(new Integer(123));//使用方法一
staticFans.<Integer>OtherMethod(new Integer(123));//使用方法二
}
}

方法一,可以像普通方法一样,直接传值,任何值都可以(但必须是派生自Object类的类型,比如String,Integer等),函数会在内部根据传进去的参数来识别当前T的类别。但尽量不要使用这种隐式的传递方式,代码不利于阅读和维护。因为从外观根本看不出来你调用的是一个泛型函数。
方法二,与方法一不同的地方在于,在调用方法前加了一个<String>来指定传给<T>的值,如果加了这个<String>来指定参数的值的话,那StaticMethod()函数里所有用到的T类型也就是强制指定了是String类型。这是我们建议使用的方式。

返回值为泛型:

1
2
3
4
public static <T> List<T> parseArray(String response,Class<T> object){  
List<T> modelList = JSON.parseArray(response, object);
return modelList;
}

上面的代码出现了 Class<T>。Class也是一泛型,它是传来用来装载类的class对象的。

通配符 ?

除了用 <T> 表示泛型外,还有 <?> 这种形式。`` 被称为通配符。

<T> 的局限性

1
2
3
4
5
6
7
8
9
10
class Base{

}

class Sub extends Base{

}

Sub sub = new Sub();
Base base = sub;

上面代码显示,Base 是 Sub 的父类,它们之间是继承关系,所以 Sub 的实例可以给一个 Base 引用赋值,那么

1
2
List<Sub> lsub = new ArrayList<>();
List<Base> lbase = lsub;

最后一行代码成立吗?编译会通过吗?

答案是否定的。

编译器不会让它通过的。Sub 是 Base 的子类,不代表 List<Sub>List<Base> 有继承关系。

但是,在现实编码中,确实有这样的需求,希望泛型能够处理某一范围内的数据类型,比如某个类和它的子类,对此 Java 引入了通配符这个概念。

所以,通配符的出现是为了指定泛型中的类型范围。

通配符有 3 种形式。

  • <?> 被称作无限定的通配符。
  • <? extends T> 被称作有上限的通配符。
  • <? super T> 被称作有下限的通配符。

无限定通配符

1
2
public void testWildCards(Collection<?> collection){
}

上面的代码中,方法内的参数是被无限定通配符修饰的 Collection 对象,它隐略地表达了一个意图或者可以说是限定,那就是 testWildCards() 这个方法内部无需关注 Collection 中的真实类型,因为它是未知的。
所以,你只能调用 Collection 中与类型无关的方法。

1
2
3
4
5
6
7
8
class test{
public void testWildCards(Collection<?> collection){
collection.add(123);// 编译不通过
collection.add("sdf");// 编译不通过

collection.size(); // 通过
}
}

我们可以看到,当 <?> 存在时,Collection 对象丧失了 add() 方法的功能,编译器不通过。
我们再看代码。

1
2
List<?> wildlist = new ArrayList<String>();
wildlist.add(123);// 编译不通过

有人说,<?> 提供了只读的功能,也就是它删减了增加具体类型元素的能力,只保留与具体类型无关的功能。它不管装载在这个容器内的元素是什么类型,它只关心元素的数量、容器是否为空?我想这种需求还是很常见的吧。

有同学可能会想,<?> 既然作用这么渺小,那么为什么还要引用它呢? 

个人认为,提高了代码的可读性,程序员看到这段代码时,就能够迅速对此建立极简洁的印象,能够快速推断源码作者的意图。

<? extends T>

<?> 代表着类型未知,但是我们的确需要对于类型的描述再精确一点,我们希望在一个范围内确定类别,比如类型 A 及 类型 A 的子类都可以。

1
2
3
4

public void testSub(Collection<? extends Base> para){

}

上面代码中,para 这个 Collection 接受 Base 及 Base 的子类的类型。

但是,它仍然丧失了写操作的能力。也就是说

1
2
para.add(new Sub());   // 编译不通过
para.add(new Base()); // 编译不通过

仍然编译不通过。

没有关系,我们不知道具体类型,但是我们至少清楚了类型的范围。

<? super T>

这个和 <? extends T> 相对应,代表 T 及 T 的超类。

1
2
public void testSuper(Collection<? super Sub> para){
}

<? super T> 神奇的地方在于,它拥有一定程度的写操作的能力。

1
2
3
4
public void testSuper(Collection<? super Sub> para){
para.add(new Sub());//编译通过
para.add(new Base());//编译不通过
}

通配符与类型参数的区别

一般而言,通配符能干的事情都可以用类型参数替换。
比如

public void testWildCards(Collection<?> collection){}

可以被

public void test(Collection collection){}

取代。

值得注意的是,如果用泛型方法来取代通配符,那么上面代码中 collection 是能够进行写操作的。只不过要进行强制转换。

1
2
3
4
5

public <T> void test(Collection<T> collection){
collection.add((T)new Integer(12));
collection.add((T)"123");
}

类型擦除

泛型是 Java 1.5 版本才引进的概念,在这之前是没有泛型的概念的,但显然,泛型代码能够很好地和之前版本的代码很好地兼容。
这是因为,泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。
通俗地讲,泛型类和普通类在 java 虚拟机内是没有什么特别的地方。回顾文章开始时的那段代码

1
2
3
4
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();

System.out.println(l1.getClass() == l2.getClass());

打印的结果为 true 是因为 List<String>List<Integer> 在 jvm 中的 Class 都是 List.class。

泛型信息被擦除了。

泛型类运行状态的情况

1
2
3
4
5
6
7
8
public class Erasure <T>{
T object;

public Erasure(T object) {
this.object = object;
}

}

Erasure 是一个泛型类,我们查看它在运行时的状态信息可以通过反射。

1
2
3
Erasure<String> erasure = new Erasure<String>("hello");
Class eclz = erasure.getClass();
System.out.println("erasure class is:"+eclz.getName());

打印的结果是

erasure class is:com.frank.test.Erasure

Class 的类型仍然是 Erasure 并不是 Erasure 这种形式,那我们再看看泛型类中 T 的类型在 jvm 中是什么具体类型。

1
2
3
4
Field[] fs = eclz.getDeclaredFields();
for ( Field f:fs) {
System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
}

打印结果是

Field name object type:java.lang.Object

那我们可不可以说,泛型类被类型擦除后,相应的类型就被替换成 Object 类型呢?

这种说法,不完全正确。

我们更改一下代码。

1
2
3
4
5
6
7
8
9

public class Erasure <T extends String>{
// public class Erasure <T>{
T object;

public Erasure(T object) {
this.object = object;
}
}

现在再看测试结果:

Field name object type:java.lang.String

我们现在可以下结论了,在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 <T> 则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String> 则类型参数就被替换成类型上限。

泛型中值得注意的地方

泛型类或者泛型方法中,不接受 8 种基本数据类型。
所以,你没有办法进行这样的编码。

1
2
List<int> li = new ArrayList<>();
List<boolean> li = new ArrayList<>();

需要使用它们对应的包装类。

1
2
List<Integer> li = new ArrayList<>();
List<Boolean> li1 = new ArrayList<>();

来源

https://blog.csdn.net/briblue/article/details/76736356