Java泛型

闲谈:

说到泛型,大部分人肯定都不陌生,在我学习泛型之前,对他的概念是一个T类型的对象,它可以指代各种类型。没错,可以这么理解,但是其中又有很多值得思考的问题,泛型跟Object有什么区别,泛型的用法等等,这些都是值得我们去深思的一个问题。
以前一直都想着利用泛型来好好装逼,但是因为技术不足,也可以说是人懒,对java的理解没有这么深刻,学习的过程中总觉得特别困难,一直都处于一知半解的状态,最近在公司项目开发过程中,发现框架里面有好多东西都运用了泛型优化代码,感觉也差不多是再对泛型进一步学习的时候了。

说一说我印象最深刻的几行代码,是用来简化Android控件绑定的。

1
2
3
4
protect (V extends View) myFindView(int resId)
{
return (V)findViewById(resId);
}

这个是写在了基类的Activity中作为所有Activity的父类,在子类中findViewById的写法就变成了如下:

1
TextView mTextView = myFindView(R.id.mTextView);

对比android原生自带的findViewById是不是直接取消了类型的强转,这如果在Activity很多的情况下是可以少很多代码和时间的。
直接取消掉了类型的强转,是不是简单了许多。好了,这么一个强大的东西,是时候好好学习一波了。

一、泛型的概念

泛型实现了参数化类型的概念,使代码可以应用于多种类型。在你创建参数化类型的一个实例时,编译器会为你负责转型操作,并且保证类型的正确性。

二、泛型的使用

普通泛型

有许多原因促成了泛型的出现,而最引人注目的一个原因就是为了创造容器类。基本上所有的程序运行时都要求你持有一大堆对象,所以容器类算得上最具重用性的类库之一。
接下来我们可以通过对比来裂解一下泛型作为容器类的好处

1
2
3
4
5
6
7
8
9
10
11
12
13
class Model{}
public class Holder1 {
private AModel mAModel;
public Holder1(AModel AModel)
{
this.mAModel=AModel;
}
AModel get()
{
return mAModel;
}
}

这个类可以明确指定其持有的对象类型,但他的重用性就不怎么样了。试想一下如果我们也有一个BModel同样需要放在这样一个容器持有类中。再重新写一个类代码就显得太多太杂了,毕竟要实现的功能是差不多的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Holder2 {
private Object mModel;
public Holder2(Object model)
{
this.mModel=model;
}
Object get()
{
return mModel;
}
public static void main(String[] args)
{
Holder2 h2 = new Holder2(new Model());
Model model = (Model) h2.get();
}
}

现在Holder 可以存放各种不同类型的对象了,因为所有的java类都是Object的子类,java支持向上向下转型的。所以这个代码可以作为任意类型的容器了。
有些情况下,我们确实希望容器能够同时持有多种类型的对象,但是,通常而言,我们只会使用容器来存储一种类型的对象。泛型的主要目前之一就是用来指定容器要持有什么类型的对象,而由编译器来保证类型的正确性。
因此,与其使用Object,我们更喜欢暂时不指定类型,而是稍后再决定具体使用什么类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Holder3<T> {
private T mModel;
public Holder3(T model)
{
this.mModel=model;
}
T get()
{
return mModel;
}
public static void main(String[] args)
{
Holder3<Model> h3 = new Holder3(new Model());
Model model = h3.get();
}
}

现在,当你创建Holder3对象时必须指明想持有什么类型的对象,将其置于尖括号内。然后你就能在Holder3存入该类型。
并且,在你从Holder3取出它的持有对象时,自动就是正确的类型。
现在我们来试试元组,持有多种不同类型。

1
2
3
4
5
6
7
8
9
10
public class TwoTuple<A,B>
{
public final A first;
public final B second;
public TwoTuple(A a,B b)
{
first=a;
second=b;
}
}

现在我们拥有了一个可以持有两个对象的元组了,如果我们再需要三个对象的元组。

1
2
3
4
5
6
7
8
9
public class ThreeTuple(A,B,C) exrebds TwoTuple<A,B>
{
public final C third;
public ThreeTuple(A a,B b,C c)
{
super(a,b)
third=c;
}
}

小结
不知道有没有人注意到 Holder3<Model> h3 = new Holder3(new Model());在初始化这个对象持有类的时候我们指明了我们想要的类型。然后编译器自动帮我们编译成了这个类型。也就是说编译器在执行h3.get();的时候它是已经明确知道了h3.get()的类型了,并自动进行了强转。而Model model = (Model) h2.get();这个,当编译器在执行这一句的时候他还不知道h2.get()到底是什么类型,最后我们告诉它,要强转成Model以适配model这个实例化对象,来进行存储。

接下来讲讲泛型接口

泛型接口

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl<T> implements Info<T>{ // 定义泛型接口的子类
private T var ; // 定义属性
public InfoImpl(T var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(T var){
this.var = var ;
}
public T getVar(){
return this.var ;
}
};
public class GenericsDemo24{
public static void main(String arsg[]){
Info<String> i = null; // 声明接口对象
i = new InfoImpl<String>("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
};
----------------------------------------------------------
interface Info<T>{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
class InfoImpl implements Info<String>{ // 定义泛型接口的子类
private String var ; // 定义属性
public InfoImpl(String var){ // 通过构造方法设置属性内容
this.setVar(var) ;
}
public void setVar(String var){
this.var = var ;
}
public String getVar(){
return this.var ;
}
};
public class GenericsDemo{
public static void main(String arsg[]){
Info i = null; // 声明接口对象
i = new InfoImpl("汤姆") ; // 通过子类实例化对象
System.out.println("内容:" + i.getVar()) ;
}
};

泛型也可以应用于接口,例如生成器,作为工厂方法设计模式的一种应用。

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public interface Generator<T> {//生成器接口
T next();
}
public class Coffee {//咖啡类
private static long counter = 0;
private final long id = counter++;
public String toString() {
return getClass().getSimpleName() + " " + id;
}
}
class Latte extends Coffee{}
class Mocha extends Coffee{}
class Cappuccino extends Coffee{}
class Americano extends Coffee{}
class Breve extends Coffee{}
/**
* 咖啡生成器类,随机生成咖啡
* @author T157
*
*/
public class CoffeeGenerator implements Generator<Coffee>,Iterable<Coffee>{
private Class[] types = {Latte.class,Mocha.class,Cappuccino.class,Americano.class,Breve.class};
private static Random rand = new Random(47);
private int mSize;
public CoffeeGenerator(){};
public CoffeeGenerator(int size) {
mSize=size;
}
@Override
public Iterator<Coffee> iterator() {
// TODO Auto-generated method stub
return new CoffeeInterator();
}
@Override
public Coffee next() {
try {
return (Coffee)types[rand.nextInt(types.length)].newInstance();
} catch (Exception e) {
// TODO Auto-generated catch block
throw new RuntimeException(e);
}
}
public class CoffeeInterator implements Iterator<Coffee>{
int count =mSize;
@Override
public boolean hasNext() {
return count>0;
}
@Override
public Coffee next() {
count--;
return CoffeeGenerator.this.next();
}
public void remove()
{
throw new UnsupportedOperationException();
}
}
public static void main(String[] args)
{
CoffeeGenerator gen = new CoffeeGenerator();
for(int i=0;i<5;i++)
{
System.out.println(gen.next());
}
for(Coffee c: new CoffeeGenerator(5))
{
System.out.println(c);
}
}
}

泛型方法

只需要在方法返回类型前加泛型参数就可以啦,看代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Demo{
public <T> T fun(T t){ // 可以接收任意类型的数据
return t ; // 直接把参数返回
}
};
public class GenericsDemo{
public static void main(String args[]){
Demo d = new Demo() ; // 实例化Demo对象
String str = d.fun("汤姆") ; // 传递字符串
int i = d.fun(30) ; // 传递数字,自动装箱
System.out.println(str) ; // 输出内容
System.out.println(i) ; // 输出内容
}
};

通过泛型方法返回泛型类型实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Info<T>{ // 指定上限
private T var ; // 此类型由外部决定
public T getVar(){
return this.var ;
}
public void setVar(T var){
this.var = var ;
}
public String toString(){ // 覆写Object类中的toString()方法
return this.var.toString() ;
}
};
public class GenericsDemo{
public static void main(String args[]){
Info<String> i1 = new Info<String>() ;
Info<String> i2 = new Info<String>() ;
i1.setVar("HELLO") ; // 设置内容
i2.setVar("汤姆") ; // 设置内容
add(i1,i2) ;
}
public static <T> void add(Info<T> i1,Info<T> i2){
System.out.println(i1.getVar() + " " + i2.getVar()) ;
}
};

使用泛型统一传入的参数类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Info<T extends Number>{ // 指定上限,只能是数字类型
private T var ; // 此类型由外部决定
public T getVar(){
return this.var ;
}
public void setVar(T var){
this.var = var ;
}
public String toString(){ // 覆写Object类中的toString()方法
return this.var.toString() ;
}
};
public class GenericsDemo{
public static void main(String args[]){
Info<Integer> i = fun(30) ;
System.out.println(i.getVar()) ;
}
public static <T extends Number> Info<T> fun(T param){//方法中传入或返回的泛型类型由调用方法时所设置的参数类型决定
Info<T> temp = new Info<T>() ; // 根据传入的数据类型实例化Info
temp.setVar(param) ; // 将传递的内容设置到Info对象的var属性之中
return temp ; // 返回实例化对象
}
};


学习完泛型我们可以知道,泛型并非只用一个T字幕来表达,它的命名风格基本为大写的英文字母,比如ABCDEFG... 泛型可以通过继承来定义上限。可以实现泛型的方法,泛型类,泛型接口,可以定义泛型数组泛型嵌套等一系列使用方法。

它跟Object的区别:是没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

参考自网易博客北漂的小羊
java编程思想第四版 泛型篇
By Xiaolong:You have a dream,you got to protect it!