Java泛型进阶详解

本文最后更新于:1 年前

List <?>

List 是列表类型,可以容纳不同的数据类型。

<> 中是列表中每个元素的类的名称。

  • 如果需要 List 容纳 String 类型,则在 List 后声明 <String>

  • 如果需要 List 容纳 Integer 类型,则在 List 后声明 <Integer>

  • 如果需要 List 容纳 Animal 类型,则在 List 后声明 <Animal>

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
// 声明了一个整型的列表 ns,列表中只能存储整型
{
List<Integer> ns = new ArrayList<>();
// 12 是 Integer 的实例
ns.add(12);
System.out.println(ns);
}

// 声明了一个字符串型的列表 ns,列表中只能存储字符串
{
List<String> ns = new ArrayList<>();
// "Timor" 是 String 的实例
ns.add("Timor");
System.out.println(ns);
}

// 声明了一个 Animal 型的列表 ns,列表中只能存储 Animal
{
List<Animal> ns = new ArrayList<>();
// animal 是 Animal 的实例
Animal animal = new Animal();
animal.setName("英短");
ns.add(animal);
System.out.println(ns);
}

List 可以接纳不同的数据的原因是什么?

之所以 List<?> 能表示不同的数据类型的集合,原因就是使用到泛型,例如 ArrayList 的定义方式:

1
public class ArrayList<E>
  • <?> 表示某个类型未知,? 仅仅是占位。
  • <E> 表示某个类型未知,E 是某个具体的类型,可以进行编程,在使用的时候需要指明 E 的具体类型是某个类对象。

类对象的表示

某个类的类对象使用什么表示?

1
2
3
4
5
6
7
8
// 字符串类型   使用的是 String 来表示
String s = "";

// Animal类型 使用的是 Animal 来表示
Animal a = new Animal();

// 类对象的类型 使用的是 Class<?> 来表示
Class<?> c = Class.forName("");

获取类对象

怎么得到类对象?

  • 通过 Class.forName() 可以得到类对象

    1
    Class.forName("java.lang.String");
  • 通过 类名.class 可以得到类对象

    1
    2
    3
    4
    5
    6
    7
    8
    // String 的类对象
    String.class;

    // Animal 的类对象
    Animal.class;

    // Integer 的类对象
    Integer.class;

判断派生类

如何判断某个类型是某个类的派生类?

通过类对象的 isAssignableFrom 方法来判断其类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class AppTest {
public static void main(String[] args) throws Exception {
// 获取类对象
Class<?> c = Animal.class;
// 构造实例
Object o = c.newInstance();
// 反射所有的字段
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Class<?> type = field.getType();
// 通过类对象的 isAssignableFrom 方法来判断其类型
if (type.isAssignableFrom(String.class)) {
String s = RandomStringUtils.randomAlphabetic(6);
field.set(o, s);
} else if (type.isAssignableFrom(Double.class)) {
Double v = RandomUtils.nextDouble();
field.set(o, v);
}
}
System.out.println(o);
}
}

可变参数

当相同类型的参数的个数不确定的情况下,采用可变参数,以接受所有的入参,其本质是一个数组:

1
2
3
4
5
6
7
8
9
public static Integer sum(Integer... ns) {
Integer total = 0;

for (int i = 0; i < ns.length; i++) {
total += ns[i];
}
return total;
}

泛型方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class AppTest {
public static void main(String[] args) {
Integer n = gen(Integer.class);
System.out.println(n);
String s = gen(String.class);
System.out.println(s);
}

public static <T> T gen(Class<T> c) {
T t = null;
if (c.isAssignableFrom(Integer.class)) {
// 说明 c 是 Integer 类的类对象
Integer i = RandomUtils.nextInt();
t = (T) i;
} else if (c.isAssignableFrom(String.class)) {
// 说明 c 是 String 类的类对象
String s = RandomStringUtils.randomAlphabetic(6);
t = (T) s;
}
return t;
}
}

泛型练习

  1. 试定义静态方法 gen 使得在 main 方法中使用如下:

    方法说明:该方法能创建指定类型的实例。

    • 代码示例(一):Integer n = gen(Integer.class);
      结果说明:每次执行后得到的 n 的值都是随机的整数
    • 代码示例(二):String s = gen(String.class);
      结果说明:每次执行后得到的 s 的值都是长度为 5 随机的字符串
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static <T> T gen(Class<T> c) {
    T t = null;
    if (c.isAssignableFrom(Integer.class)) {
    // 说明 c 是 Integer 类的类对象
    Integer i = RandomUtils.nextInt();
    t = (T) i;
    } else if (c.isAssignableFrom(String.class)) {
    // 说明 c 是 String 类的类对象
    String s = RandomStringUtils.randomAlphabetic(6);
    t = (T) s;
    }
    return t;
    }
  2. 试定义静态方法 gens 使得在 main 方法中使用如下:

    方法说明:该方法能创建指定类型的实例

    • 代码示例(一):List<Integer> ns = gens(3, Integer.class);

      结果说明:每次执行后得到的 ns 长度为 3, 集合 ns 中的值都是随机的整数

    • 代码示例(二):List<String> ss = gens(6, String.class);

      结果说明:每次执行后得到的 ss 长度为 6, 集合 ss 中的值都是长度为 5 随机字符串

    1
    2
    3
    4
    5
    6
    7
    8
    public static <T> List<T> gens(Integer num, Class<T> c) {
    List<T> ts = new ArrayList<>();
    for (int i = 0; i < num; i++) {
    T t = gen(c);
    ts.add(t);
    }
    return ts;
    }
  3. 编写 pojo 类 Human。

    • 包含 name(字符串)和 age(整型)字段
    • 为字段生成 getter、setter 访问器,并重写 toString 方法

  1. 编写 pojo 类 Goods
    • 包含 name(字符串)和 price(浮点型)字段
    • 为字段生成 getter、setter 访问器,并重写 toString 方法

  1. 试定义静态方法 genObj 使得在 main 方法中使用如下:

    方法说明:该方法能创建指定类的实例,并为实例的属性赋予随机值

    • 代码示例(一):Human u = genObj(Human.class);

      结果说明:每次执行后得到的 u 的 name 字段和 age 字段的值都是随机的

    • 代码示例(二):Goods g = genObj(Goods.class);

      结果说明:每次执行后得到的 g 的 name 字段和 price 字段的值也都是随机的

    • 代码示例(三):Integer n = genObj(Integer.class);

      结果说明:每次执行后得到的 n 的值都是随机的整数

    • 代码示例(四):String s = genObj(String.class);

      结果说明:每次执行后得到的 s 的值都是长度为 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
31
32
33
34
35
36
public static <T> T genObj(Class<T> c) throws Exception {
T t = null;
if(c.isAssignableFrom(Integer.class)) {
// 说明 c 是 Integer 类的类对象
Integer i = RandomUtils.nextInt();
t = (T) i;
} else if (c.isAssignableFrom(String.class)) {
// 说明 c 是 String 类的类对象
String s = RandomStringUtils.randomAlphabetic(6);
t = (T) s;
} else {
// 创建实例
t = c.newInstance();
// 为实例的属性赋值
Field[] fields = c.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
Class<?> type = field.getType();
if (type.isAssignableFrom(String.class)) {
// 说明字段是 String 类型
String s = RandomStringUtils.randomAlphabetic(5);
field.set(t, s);
} else if (type.isAssignableFrom(Double.class)) {
// 说明字段是 Double 类型
double v = RandomUtils.nextDouble();
field.set(t, v);
} else if (type.isAssignableFrom(Integer.class)) {
// 说明字段是 Integer 类型
int i = RandomUtils.nextInt();
field.set(t, i);
}
}
}
return t;
}
}
  1. 试定义静态方法 genObjs 使得在 main 方法中使用如下:

    方法说明:该方法能创建多个指定类的实例的集合,并为每个实例的属性赋予随机值

    • 代码示例(一):List<Human> us = genObjs(3, Human.class);

      结果说明:每次执行后得到的 us 长度为 3,

      集合 us 中的每个实例的 name 字段和 age 字段的值都是随机的

    • 代码示例(二):List<Goods> gs = genObjs(6, Goods.class);

      结果说明:每次执行后得到的 gs 长度为 6,

      集合 gs 中的每个实例的 name 字段和 price 字段的值都是随机的

    • 代码示例(三):List<Integer> ns = genObjs (3, Integer.class);

      结果说明:每次执行后得到的 ns 长度为 3, 集合 ns 中的值都是随机的整数

    • 代码示例(四):List<String> ss = genObjs (6, String.class);

      结果说明:每次执行后得到的 ss 长度为 6, 集合 ss 中的值都是长度为 5 随机字符串