JAVA基础学习-泛型回顾

本文最后更新于:October 23, 2022 pm

Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。

目录

泛型类

泛型类,在创建对象的时候才指定(或确定)其具体类型。

常用泛型标识:T(type。常用与类、接口)、E(element。常用与Collection集合)、K、V(K和V通常配合使用,常用语KV键值对类型)。

⚠️注意:这只是一种标识,就和方法参数一样。所以,任意字母都是可以的,只是习惯性用这几种来表示而已。

定义语法

1
2
3
class 类名称 <泛型标识,泛型标识,...>{
private 泛型标识 变量名;
}

示例

这里特意将Setter/Getter都显示的写了出来。

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
package com.tothefor.motorcode.Dro;

import com.alibaba.fastjson.JSON;

/**
* @Author DragonOne
* @Date 2022/10/19 21:15
* @Title
* @Description
*/
public class JavaGeneric<T> {
private T data;

public JavaGeneric(T data) {
this.data = data;
}

public T getData() {
return data;
}

public void setData(T data) {
this.data = data;
}

/**
* 自定义方法
*
* @param data
*/
public void showData(T data) {
System.out.println(JSON.toJSONString(this.data));
System.out.println(JSON.toJSONString(data));
}
}

测试使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.tothefor.motorcode.Dro;

/**
* @Author DragonOne
* @Date 2022/10/19 21:49
* @Title
* @Description
*/
public class TestDro {
public static void main(String[] args) {
// 这里的String可以为其他引用类型
JavaGeneric<String> dro = new JavaGeneric<>("墨水记忆");
System.out.println(dro.getData());
dro.showData("www.tothefor.com");
}
}

输出

1
2
3
墨水记忆
"墨水记忆"
"www.tothefor.com"

特殊

也可以有多个类型:public class JavaGeneric< T,K >。
特殊情况:当泛型类没有指定具体类型时,将按照Object类型进行。
不支持基本数据类型,只支持引用类型。因为基本数据类型不是继承自Object。而所有的引用类型都是继承自Object。

重要知识点

泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型。
即:同一泛型类,根据不同的数据类型创建的对象,本质上是同一类型,均为泛型类。

如:public class JavaGeneric< T >,则不管是创建的 JavaGeneric<String> 对象,还是创建的 JavaGeneric<Integer> 对象,他们本质上都是 JavaGeneric 类型。如下证明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.tothefor.motorcode.Dro;

/**
* @Author DragonOne
* @Date 2022/10/19 21:49
* @Title
* @Description
*/
public class TestDro {
public static void main(String[] args) {
JavaGeneric<String> dro = new JavaGeneric<>("墨水记忆");
JavaGeneric<Integer> droI = new JavaGeneric<>(777);
System.out.println(dro.getClass());
System.out.println(droI.getClass());
System.out.println(dro.getClass() == droI.getClass());
}
}

输出结果为:

1
2
3
class com.tothefor.motorcode.Dro.JavaGeneric
class com.tothefor.motorcode.Dro.JavaGeneric
true

子类继承

分为两种情况:子类也是泛型类、子类不是泛型类。

  • 子类也是泛型类,则子类和父类的泛型类型要一致。因为创建子类前一定会创建父类。(调用父类的构造方法)
1
class childGeneric<T> extends Generic<T>

示例:

JavaGeneric(父类):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.tothefor.motorcode.Dro;

import com.alibaba.fastjson.JSON;

/**
* @Author DragonOne
* @Date 2022/10/19 21:15
* @Title
* @Description
*/
public class JavaGeneric<E> {
private E data;

public E getData() {
return data;
}

public void setData(E data) {
this.data = data;
}
}

SonJavaGeneric(子类):

当子类有类型,父类没有类型,则默认父类为Object。可以重写父类的Getter方法查看返回值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 不指定父类类型
public class SonJavaGeneric<T> extends JavaGeneric{
@Override
public Object getData() {
return super.getData();
}
}

// 指定父类类型,而且必须指定为T,否则会报错
public class SonJavaGeneric<T> extends JavaGeneric<T>{
@Override
public T getData() {
return super.getData();
}
}

子类多类型,如:public class SonJavaGeneric< T,K,V > extends JavaGeneric< T > ,这样也是可以的,在子类中就可以使用多种类型。但是,必须要保证有一个和父类的类型保持一致。

  • 子类不是泛型类,则父类要明确泛型的数据类型。这时,子类就和正常的类没有什么区别。
1
class childGeneric extends Generic<String>

泛型接口

和泛型类类似。

定义语法

1
2
3
interface 接口名称 <泛型标识,泛型标识, ...>{
泛型标识 方法名();
}

泛型接口的使用

Generic泛型接口:

1
2
3
4
5
6
7
8
9
10
11
12
package com.tothefor.motorcode.Dro;

/**
* @Author DragonOne
* @Date 2022/10/19 23:39
* @Title
* @Description
*/
public interface Generic<T> {
T getKey();
}

有两种情况:实现类也是泛型类、实现类不是泛型类。

  • 实现类也是泛型类,实现类和接口的泛型类型要一致。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.tothefor.motorcode.Dro;

/**
* @Author DragonOne
* @Date 2022/10/19 23:43
* @Title
* @Description
*/
public class GenericImpl<T> implements Generic<T>{
@Override
public T getKey() {
return null;
}
}

使用类似泛型类,比如多类型。

  • 实现类不是泛型类,接口要明确数据类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.tothefor.motorcode.Dro;

/**
* @Author DragonOne
* @Date 2022/10/19 23:43
* @Title
* @Description
*/
public class GenericImpl implements Generic<String>{
@Override
public String getKey() {
return null;
}
}

通泛型类一样,如果不写泛型接口的类型,则默认为Object。

泛型方法

定义语法

1
2
3
4
5
6
7
8
修饰符 <T,E, ...> 返回值 方法名称(形参列表){
...
}

// 如下;
public <T> void show(T data){
...
}
  • public 与返回值中间的 < T > 非常重要。可以理解为是声明此方法为泛型方法。
  • < T > 表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
  • 只有声明了 < T > 的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。

类型通配符

类型通配符:一般是使用问号(?)代替具体的类型实参。所以,类型通配符是类型实参,而不是类型形参。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.tothefor.motorcode.Dro;

/**
* @Author DragonOne
* @Date 2022/10/20 06:24
* @Title
* @Description
*/
public class Box<T> {
private T first;

public T getFirst() {
return first;
}

public void setFirst(T first) {
this.first = first;
}
}

使用:

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
package com.tothefor.motorcode.Dro;

import com.alibaba.fastjson.JSON;

/**
* @Author DragonOne
* @Date 2022/10/19 21:49
* @Title
* @Description
*/
public class TestDro {
public static void main(String[] args) {
Box<Number> boxStr = new Box<>();
boxStr.setFirst(111);
showBox(boxStr);

Box<Integer> boxInt = new Box<>(); // Integer继承自Number
boxInt.setFirst(777);
showBox(boxInt); // 报错
}

public static void showBox(Box<Number> box) {
Number first = box.getFirst();
System.out.println(first);
}
}

可能有人为了解决这个报错问题,会想通过重载再定义一个showBox(Box<Number> box)方法。但是,这是错误的。前面就已经说过了泛型类不同的参数类型,但是本质还是泛型类的类型,所以,都是Box类型。

解决办法:

1
2
3
4
public static void showBox(Box<?> box) {
Object first = box.getFirst();
System.out.println(first);
}

问号(?)表示可以传入任何类型。也因为没有指定类型,所有默认为Object。

类型通配符的上限

即规定传入类型的上限,有一个规定的范围。而不再是任何类型。

语法

1
类/接口<? extends 实参类型>

要求该泛型的类型,只能是实参类型,或实参类型的子类类型。即实参类型为最大父类型,传入的可以是此类型以及此类型的子类。

上面的解决办法就可以改为:

1
2
3
4
public static void showBox(Box<? extends Number> box) {
Number first = box.getFirst();
System.out.println(first);
}

不知道这两种解决办法你看出来区别没有。可以看见,类型不再是Object了,而是我们规定的上限类型。这里又有点多态的感觉:父类引用指向子类对象。

类型通配符的下限

即规定传入类型的下限,有一个规定的范围。而不再是任何类型。

语法

1
类/接口<? super 实参类型>

要求该泛型的类型,只能是实参类型,或实参类型的父类类型。刚好和上限反过来了。

类型擦除

类型下限同理。

泛型与数组

泛型数组的创建

  • 可以声明带泛型的数组引用,但是不能直接创建带泛型的数组对象。如下所示。
1
2
ArrayList<String>[] listArr = new ArrayList[2]; // 编译通过
ArrayList<String>[] listArr2 = new ArrayList<String>[2]; // 编译不通过
  • 可以通过java.lang.reflect.Array的newInstance(Class<T>,int)创建T[] 数组。

泛型与反射

反射常用的泛型类:Class<T>、Constructor<T>。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.tothefor.motorcode.Dro;

import com.alibaba.fastjson.JSON;

import java.lang.reflect.Constructor;
import java.util.ArrayList;

/**
* @Author DragonOne
* @Date 2022/10/19 21:49
* @Title
* @Description
*/
public class TestDro {
public static void main(String[] args) throws Exception{
Class<Box> boxClass = Box.class;
Constructor<Box> constructor = boxClass.getConstructor();
Box box = constructor.newInstance();
}
}


本文作者: 墨水记忆
本文链接: https://tothefor.com/DragonOne/f4e2ad37.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!