0%

java中泛型

场景

  • 本次java 版本
1
2
3
4
C:\Users\Administrator>java -version
java version "1.8.0_381"
Java(TM) SE Runtime Environment (build 1.8.0_381-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.381-b09, mixed mode
  • 泛型的本质是为了将类型参数化,数据类型被设置为一个参数,在使用时再从外部传入一个数据类型;而一旦传入了具体的数据类型后,传入变量(实参)的数据类型如果不匹配,编译器就会直接报错

  • 在 ArrayList 集合中,可以放入所有类型的对象,假设现在需要一个只存储了 String 类型对象的 ArrayList 集合

1
2
3
4
5
6
7
ArrayList list = new ArrayList();
list.add("aaa");
list.add("bbb");
list.add("ccc");
for (int i = 0; i < list.size(); i++) {
System.out.println((String)list.get(i));
}
  • 但如果在添加 String 对象时,不小心添加了一个 Integer 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.ArrayList;

public class Main {
public static void main(String args[]) {
ArrayList list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add(111); //新增数字
for (int i = 0; i < list.size(); i++) {
System.out.println((String)list.get(i));
}
}
}



  • 上述代码在编译时没有报错,但在运行时却抛出了一个 ClassCastException 异常,其原因是 Integer 对象不能强转为 String 类型
1
2
   Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at Main.main(Main.java:11)
  • 使用泛型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.ArrayList;

public class Main {
public static void main(String args[]) {
ArrayList<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add(111); // 编译阶段,编译器会报错
for (int i = 0; i < list.size(); i++) {
System.out.println((String)list.get(i));
}
}
}

image-20231113153841330

  • 因此,当具体的数据类型确定后,泛型又提供了一种类型安全检测机制,只有数据类型相匹配的变量才能正常的赋值,否则编译器就不通过。所以说,泛型一定程度上提高了软件的安全性,防止出现低级的失误。

  • 泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

泛型类

  • 定义
1
2
3
4
5
6
7
class 类名称 <泛型标识> {
private 泛型标识 /*(成员变量类型)*/ 变量名;
.....

}
}

  • 尖括号 <> 中的 泛型标识被称作是类型参数,用于指代任何数据类型
  • 泛型标识是任意设置的(如果你想可以设置为 Hello都行),Java 常见的泛型标识以及其代表含义如下:
1
2
3
4
5
6
T :代表一般的任何类。
E :代表 Element 元素的意思,或者 Exception 异常的意思。
K :代表 Key 的意思。
V :代表 Value 的意思,通常与 K 一起配合使用。
S :代表 Subtype 的意思,文章后面部分会讲解示意。

  • 举例如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Generic<T> { 
// key 这个成员变量的数据类型为 T, T 的类型由外部传入
private T key;
// 泛型构造方法形参 key 的类型也为 T,T 的类型由外部传入
public Generic(T key) {
this.key = key;
}

// 泛型方法 getKey 的返回值类型为 T,T 的类型由外部指定
public T getKey(){
return key;
}
}

  • 泛型类中的静态方法和静态变量不可以使用泛型类所声明的类型参数
1
2
3
4
5
6
7
public class Test<T> {    
public static T one; // 编译错误
public static T show(T one){ // 编译错误
return null;
}
}

  • 而静态变量和静态方法在类加载时已经初始化,直接使用类名调用;在泛型类的类型参数未确定时,静态成员有可能被调用,因此泛型类的类型参数是不能在静态成员中使用的。
  • 泛型类不只接受一个类型参数,它还可以接受多个类型参数
1
2
3
4
5
6
7
8
9
10
11
12
13
public class MultiType <E,T> {
E value1;
T value2;

public E getValue1(){
return value1;
}

public T getValue2(){
return value2;
}
}

  • 看如下实例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Generic<T> {
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
}


public class Main {
public static void main(String args[]) {
Generic<String> generic = new Generic<>("hello");
System.out.println(generic.getKey());

// // <> 中什么都不传入,等价于 Generic<Object> generic = new Generic<>();
Generic generic1 = new Generic("word");
System.out.println(generic1.getKey());
}
}

泛型接口

  • 定义
1
2
3
4
5
6
7
8
9
public interface 接口名<类型参数> {
...
}

public interface Inter<T> {
public abstract void show(T t) ;
}


  • 在泛型接口中,静态成员也不能使用泛型接口定义的类型参数
1
2
3
4
5
6
7
8
9
10
interface IUsb<U, R> {
int n = 10;
U name;// 报错! 接口中的属性默认是静态的,因此不能使用类型参数声明
R get(U u);// 普通方法中,可以使用类型参数
void hi(R r);// 抽象方法中,可以使用类型参数
default R method(U u) {
return null;
}
}

  • 定义一个接口 IA 继承了 泛型接口 IUsb,在 接口 IA 定义时必须确定泛型接口 IUsb 中的类型参数
1
2
3
4
//IA.java

// 在继承泛型接口时,必须确定泛型接口的类型参数
interface IA extends IUsb<String, Double> { }
  • AA继承了IA的接口,而IA接口继承了IUsb并指定了特定的泛型参数,因此AA其实就是实现了IUsb接口的方法get和hi方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 当去实现 IA 接口时,因为 IA 在继承 IUsu 接口时,指定了类型参数 U 为 String,R 为 Double
// 所以在实现 IUsb 接口的方法时,使用 String 替换 U,用 Double 替换 R
class AA implements IA {
public Double get(String s) {
System.out.println("AA 的Double get=" + s);
return null;
}
public void hi(Double d) {
System.out.println("AA hi=" +d);

}
public void test() {
System.out.println("test");

}
}
  • 运行代码
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
public static void main(String args[]) {
AA aa = new AA();
aa.get("哈哈");
aa.hi(10.01);
aa.test();
}
}

// 结果
AA 的Double get=哈哈
AA hi=10.01
test
  • 当然也可以直接用:定义一个类 BB 实现了 泛型接口 IUsb,在 类 BB 定义时需要确定泛型接口 IUsb 中的类型参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 实现接口时,需要指定泛型接口的类型参数
// 给 U 指定 Integer, 给 R 指定了 Float
// 所以,当我们实现 IUsb 方法时,会使用 Integer 替换 U, 使用 Float 替换 R
class BB implements IUsb<Integer, Float> {
@Override
public Float get(Integer integer) {
System.out.println("BB Float get=" + integer);
return 10.11F;
}
@Override
public void hi(Float afloat) {
System.out.println("BB hi=" +afloat);

}
}
  • 定义一个类 CC 实现了 泛型接口 IUsb 时,若是没有确定泛型接口 IUsb 中的类型参数,则默认为 Object
1
2
3
4
5
6
7
8
9
10
11
12
13
public class CC implements IUsb{
@Override
public Object get(Object o) {
System.out.println("CC object get=" + o.toString());
return null;
}
@Override
public void hi(Object o) {
System.out.println("cc object hi=" + o.toString());

}
}

  • 定义一个类 DD 实现了 泛型接口 IUsb 时,若是没有确定泛型接口 IUsb 中的类型参数,也可以将 DD 类也定义为泛型类,其声明的类型参数必须要和接口 IUsb 中的类型参数相同
1
2
3
4
5
6
// DD 类定义为 泛型类,则不需要确定 接口的类型参数
// 但 DD 类定义的类型参数要和接口中类型参数的一致
class DD<U, R> implements IUsb<U, R> {
...
}

泛型方法

  • 当在一个方法签名中的返回值前面声明了一个 < T > 时,该方法就被声明为一个泛型方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public <类型参数> 返回类型 方法名(类型参数 变量名) {
...
}

public class Test<U> {
// 该方法只是使用了泛型类定义的类型参数,不是泛型方法
public void testMethod(U u){
System.out.println(u);
}

// <T> 真正声明了下面的方法是一个泛型方法
public <T> T testMethod1(T t){
return t;
}
}

  • 实现了普通方法和泛型方法
1
2
3
4
5
6
7
8
9
10
public class Test<T> {
// 是泛型类中的普通方法
public void testMethod(T t) {
System.out.println(t);
}
// 是一个泛型方法
public <T> T testMethod1(T t) {
return t;
}
}
  • 运行
1
2
3
4
5
6
7
8
9
public class Main {
public static void main(String args[]) {
Test test = new Test<Integer>();
test.testMethod("112");
String tt = (String) test.testMethod1("你好");
System.out.println(tt);
}
}

  • 当然泛型方法也可用到普通类中
1
2
3
4
5
6
7
8
9
10
public class Test {
// 普通方法
public void testMethod(String t) {
System.out.println(t);
}
// 是一个泛型方法
public <T> T testMethod1(T t) {
return 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
27
28
29
30
31
32
33
public class Test {

// 这是一个简单的泛型方法
public static <T> T add(T x, T y) {
return y;
}

public static void main(String[] args) {
// 一、不显式地指定类型参数
//(1)传入的两个实参都是 Integer,所以泛型方法中的<T> == <Integer>
int i = Test.add(1, 2);

//(2)传入的两个实参一个是 Integer,另一个是 Float,
// 所以<T>取共同父类的最小级,<T> == <Number>
Number f = Test.add(1, 1.2);

// 传入的两个实参一个是 Integer,另一个是 String,
// 所以<T>取共同父类的最小级,<T> == <Object>
Object o = Test.add(1, "asd");

// 二、显式地指定类型参数
//(1)指定了<T> = <Integer>,所以传入的实参只能为 Integer 对象
int a = Test.<Integer>add(1, 2);

//(2)指定了<T> = <Integer>,所以不能传入 Float 对象
int b = Test.<Integer>add(1, 2.2);// 编译错误

//(3)指定<T> = <Number>,所以可以传入 Number 对象
// Integer 和 Float 都是 Number 的子类,因此可以传入两者的对象
Number c = Test.<Number>add(1, 2.2);
}
}

泛型类,在创建类的对象的时候确定类型参数的具体类型;
泛型方法,在调用方法的时候再确定类型参数的具体类型。

总结

  • 后续还有更多高级的用法,比如类型擦除、通配符等有空继续按照此博主内容进行学习