Java 泛型

what

泛型我通俗的认为就是参数化类型,即传入类型提示。告诉它我即将要在这个类型的版本上使用你了。并且在写出大量代码时使用泛型可以写出一套模板代码。而且使用了泛型后,在编译期间泛型这个是能够检测你的代码有没有使用不规范类型的对象,在编译期间有点不好理解,但是当我们用IDE的时候,一旦你使用泛型,产生的对象,在IDE看来所给出的代码提示等检查也都是基于传入的泛型参数。但是呢,编译通过后在jvm中运行表现看来,所有用于参数类型的对象表现为Object, 这也俗称类型擦除。泛型参数不能时基本类型

how

泛型类

首先列举下我们经常用到一些关于泛型的例子:

1
HashMap<String,Integer> map = new HashMap<>();

如何自行定义:

1
2
3
4
5
Class Person<T> {
T t;
T getT(){}
void setT(T t){}
}
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
public class GenericTest {
public static void main(String[] args) {
Person<String> person = new Person<>();
person.setT("ffff");
Person<Integer> person1 = new Person<>();
person1.setT(9);
System.out.println(person.getT().getClass());
System.out.println(person1.getT().getClass());
System.out.println(person.getClass());//class learn2.GenericTest$Person
System.out.println(person1.getClass());//class learn2.GenericTest$Person
GenericTest test = new GenericTest();
test.getArray(1,23,4);
test.getArray("f", "f");
}
static class Person <T> {
private T t;

public void setT(T t) {
this.t = t;
}

public T getT() {
return t;
}
}
}

然后你就能发挥想象创造代码了呀,别看它加了尖括号和类型,其实看穿了还是一个普通类。通过Class比较你都看不到关于泛型的东西

以及后续的继承

泛型接口

1
2
3
4
interface Presenter<T> {
void attachView(T t);
void dettachView();
}

其实和泛型类没啥不同

泛型方法

1
2
3
4
5
6
7
8
9
10
11
class Test {
private <T> T[] getArray(T ...ts) {
return ts;
}

public void static main(String[] args) {
Test test = new Test();
int[] nums = test.getArray(1,2,3);
String[] strs = test.getArray("a", "b", "c");
}
}

这个例子是我比较喜欢的,通过变长的参数返回数组。看在你传入参数一刹那,其实就是相当于把类型参数弄进入了。

进阶使用

在jdk的代码中以及框架呀大量使用关于泛型的东西,说明了这玩意很好使。通过接口实现,或者类的继承中加入泛型,又能够写出可复用的代码。以及配合通配符,配合多态呀,那简直爽的不要不要的。

extends

1
2
3
public interface IPresenter<T extends IView> {
}
//说明了传入的参数类型,得要是IView子类

好吧我老是把<? extends T> 和 搞混。。

注意事项

  • 泛型参数不能是基本类型

  • 不能带泛型用于instancof关键字

  • 不可以用于泛型数组

    1
    2
    3
    4
    5
    6
    7
    8
    List<String>[] lists = new ArrayList<String>[10];//无法这样不行的
    List<String>[] lists = new ArrayList[10];//这样却是可以的

    //唯一的方式就是创建类型擦出的数组,然后再转型
    List<String>[] lists = new ArrayList[10];
    lists[0] = new ArrayList<String>();
    lists[0].add("aa");
    System.out.println(lists.getClass());
  • 类型捕获

    通同getClass来获取真实的类型咯

  • 泛型和重载

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Test<T,E> {
    //因为泛型擦初,所以二者有相同的方法签名
    public void test(List<T> list) {}
    public void test(List<E> list) {}

    //同样
    public void test(T t) {}
    public void test(E e) {}
    }
  • 自限定的类型

    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
    class C implements Comparable<C> {
    //这自限定的例子比较简单,即限定比较对象为自己,其实仔细研究还是泛型的事
    @Override
    public int compareTo(C o) {
    return 0;
    }
    }
    }

    class A<T extends A<T>> {} //这个有点抽象,但是仔细看好像陷入一个无限循环样子
    //首先我们继承它看看
    class B extends A<B> {
    //这样写是否合理,就看B是否能套入<T extentd A<T>> 中,呃呃,看了好久,是可以
    }
    //那么到底有什么用呢,这个通常用于继承关系中的限定。

    abstract class A<T extends A<T>> {
    abstract void set(T t);
    }

    class B extends A<B> {//这个泛型参数只能事继承链上的,换了其它的不可以,就是这是妙用啊

    @Override
    void set(B b) {//由于限定了这个实现的方法就必须为自己。其它不行。有意思呵呵

    }
    }
  • 异常处理中catch是不能捕获泛型类型的异常,因为再运行期间必须知道异常的确切类型

why

编写用于多种类型的代码。

通配符

前面的其实都还好理解,到了这里就有点要想想了,不然傻傻分不清楚。

1
2
3
<?>
<? extends E> //这个E一般就代表声明出来的类型参数。指代你声明<E>
<? super E>

栗子

还是看栗子把2333

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
package learn2;

import java.util.ArrayList;
import java.util.List;

public class GenericTest2 {
public static void main(String[] args) {
GenericTest2 test2 = new GenericTest2();
List<String> stringList = new ArrayList<>();
stringList.add("aaa");
stringList.add("bbb");
stringList.add("ccc");
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
integerList.add(3);

// ?任意
test2.runTest(stringList);
test2.runTest(integerList);

//上界为Number
List<Long> longList = new ArrayList<>();
test2.runTest2(longList, 2);

//下界Number
List<Object> objectList = new ArrayList<>();
test2.runTest3(objectList, 2);
}

private void runTest(List<?> list) {//可以接受任意泛型参数的List
list.add(null);//只能写null
// list.add(new Object());// error
System.out.println(list.get(3).getClass());
}

private void runTest2(List<? extends Number> list, Number number2) {//只能接受泛型参数继承Number的List
Number number = list.get(0);// 只可以读Number类型
//list.add(new Object());//写不了
//list.add(number2);//编译不通过
//list.add(number);//编译不通过
}

private void runTest3(List<? super Number> list, Number number) {//只能接受泛型参数为Number父类的List
list.add(number);//可以写Number类型
//list.add(new Object()); 不行
Object object = list.get(0);//只能捕获为Object
}
}

分析一波

  • <?>这个统配符代表,可以接受任意泛型参数,与之同时,对应的List<?> list 可以接受List<Number> List<String>对象等等,所以嘞,它也就根本不知道实际对象会是带什么泛型参数,所以只能null,我没想到的是居然写Object也不行,读还是的啊,但是只能为Object,没事啊,你可动态的instance of判断下嘛,写死转换肯定是不行的,因为它压根啥也不知道,咱也不敢问是吧。

  • <? extends E>这个通配符代表了,可以接受带上界为E泛型参数。即你的泛型参数得是E或E的子类才行

    List<? extends Number> list这个能能接受List<Int> List<Long>等等为Number的子类泛型参数 。可能有人会觉得有了上界那不为所欲为。我以前也是这么觉得的哈哈。上述实践得知,只能写为E不能读为E,当然能读为Object。因为它知道你传进来的肯定是带E后代泛型,所以呢,我里面的元素肯定能表现为E,所以读是没问题。但是写E就有问题了,比如我某一次识别的到是Long,然后你要写一个Number,你说怎么能写。

  • <? super E> 这个就刚好了上面那个相反,E为下界,可以接受E父类等泛型参数。对于这个而言,就只能写E不能读E ,你的泛型参数为超类,你压根不知到内容表现为具体是个啥东西,所以读就别想了。但是E可以表现为超类,所以写E就没问题咯。

总结下来就是:

<?> 可以匹配任何泛型参数,但是只能写null

<? extends E> 可以匹配E的子类泛型参数,只能读E,不能写E

<? super E> 可以匹配E的超类泛型参数,不能读E,只能写E

结语

其实泛型这个东西呢,以前我想的太简单了,传个类型参数就完事了,以前我也是这么认为的,但是看了《Java编程思想》,卧槽还有这么多东西,而且稍稍不深入就会被带跑偏呃呃。目前还是归纳的不够详细,以后学到再补充!✌,对了想不通的地方,没事一定要敲一敲,比瞎想强,总有好处的。