泛型
★第十五章 泛型
- 泛型是一种表示数据类型的数据类型。
泛型又称参数化类型,是
Jdk5.0
出现的新特性,用以解决数据类型的安全性问题。在类声明或实例化时只要指定好需要的具体类型即可。
Java 泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生
ClassCastException
异常。同时,代码更加简洁、健壮。泛型的作用是:可以在类声明时通过一个标识(
<E>
、<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
34
35package com.f.chapter15.generic;
/**
* @author fzy
* @date 2023/6/24 19:39
*/
public class Generic01 {
public static void main(String[] args) {
//传入 String 类型给 E,则 Person 类中所有为 E 的地方都可以看作被 String 替代
Person<String> p1 = new Person<String>("hello");
p1.show(); //class java.lang.String
//传入 Integer 类型给 E,则 Person 类中所有为 E 的地方都可以看作被 Integer 替代
Person<Integer> p2 = new Person<Integer>(10);
p2.show(); //class java.lang.Integer
}
}
class Person<E> { //这里的 E 就是泛型
//用 E 表示 s 的数据类型,该数据类型是在定义 Person 对象的时候指定的,
// 即在编译期间,就可以确定 E 是什么类型
E s;
public Person(E s) { //E 也可以是参数类型
this.s = s;
}
public E getS() { //E 也可以是返回类型
return s;
}
public void show(){
System.out.println(s.getClass());
}
}
- 使用泛型的好处:
- 编译时,检查添加元素的类型,提高了安全性。
- 减少了类型转换的次数,提高效率。
- 不再提示编译警告。
泛型的语法
泛型的声明:
interface 接口<T>{}
、class 类<K,V>{}
注意:
T、K、V
不代表值,而是表示类型。- 任何字母都可以,通常用
T
表示,T
是Type
的缩写。
泛型的实例化:要**在类名后面指定类型参数的值(即类型)**,如:
List<String> strList = new ArrayList<String>();
Iterator<Customer> iterator = customers.iterator();
HashMap<String, Student> students = new HashMap<String, Student>();
Set<Map.Entry<String, Student>> entries = students.entrySet();
泛型使用细节
interface List<T>{}
、public class HashSet<E>{}
等等。T、E
只能是引用类型,而不能是基本类型。List<Integer> list = new ArrayList<Integer>();
-> 正确的List<int> list = new ArrayList<int>();
-> 错误的在指定了泛型的具体类型后,可以传入该类型或者其子类类型。
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
35package com.f.chapter15.generic;
/**
* @author fzy
* @date 2023/6/28 20:42
*/
public class GenericDetail {
public static void main(String[] args) {
//1. 因为aDog的泛型指定为A类型,所以在构造器中传入A对象,编译通过
Dog<A> aDog = new Dog<A>(new A());
//2. 因为bDog的泛型指定为A类型,又因为B是A的子类,所以在构造器中传入B对象,编译也通过
Dog<A> bDog = new Dog<A>(new B());
//3. 因为cDog的泛型指定为A类型,但是C和A没有关系,所以在构造器中传入C对象,编译错误
//Dog<A> cDog = new Dog<A>(new C());
}
}
class A {
}
class B extends A {
}
class C {
}
class Dog<E> {
E e;
public Dog(E e) {
this.e = e;
}
}泛型使用形式:
List<Integer> list = new ArrayList<Integer>();
在实际开发中,往往简写,即用下面这种形式:
List<Integer> list = new ArrayList<>();
,因为编译器会进行类型推断,知道右边<>
中的类型就是左边<>
中的类型。List list = new ArrayList();
等价于List<Object> list = new ArrayList<Object>();
。
自定义泛型
自定义泛型类
基本语法:
1
2
3class 类名<T,R,...> { //...表示可以有多个泛型
成员
}使用细节:
普通成员可以使用泛型(属性、方法)。
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
60package com.f.chapter15.customgeneric;
/**
* @author fzy
* @date 2023/6/29 19:54
*/
public class CustomGeneric_ {
public static void main(String[] args) {
}
}
//1. Tiger后面跟着泛型,所以Tiger为自定义泛型类
//2. T、R、M为泛型标识符,一般是单个大写字母
//3. 泛型标识符可以有多个
class Tiger<T, R, M> {
String name;
T t;
R r;
M m;
public Tiger(String name, T t, R r, M m) {
this.name = name;
this.t = t;
this.r = r;
this.m = m;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public R getR() {
return r;
}
public void setR(R r) {
this.r = r;
}
public M getM() {
return m;
}
public void setM(M m) {
this.m = m;
}
}使用泛型的数组,不能初始化。-> 不确定数组的类型,就不知道该开辟多大的内存空间,所以无法初始化。
静态属性和静态方法中不能使用类的泛型。-> 因为静态是和类相关的,在类加载时,对象还没有创建,而泛型的类型是在对象创建后才确定的,所以如果静态属性和静态方法使用了泛型,就无法确定其在类加载时的具体类型,
JVM
无法完成初始化,所以不能使用。泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指定确定类型)。
如果在创建对象时,没有指定类型,默认为
Object
。
自定义泛型接口
基本语法:
1
2
3interface 接口名<T,R,...> {
}使用细节:
接口中,静态成员也不能使用泛型。
泛型接口的类型,在继承接口或者实现接口的时候确定。
- 在继承接口时,可以确定泛型类型,也可以继续沿用泛型标识符。
- 在实现接口时,必须确定泛型类型。
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
68package com.f.chapter15.customgeneric;
/**
* @author fzy
* @date 2023/6/29 21:09
*/
public class CustomGenericInterface {
public static void main(String[] args) {
}
}
//因为IA继承IUsb接口时,指定了T为String类型,R为Double类型,
// 所以在类AA实现IA接口时,就会用String替换T,用Double替换R
class AA implements IA {
public Double get(String s) {
return null;
}
public void hi(String s) {
}
public void run(String s, Double aDouble) {
}
}
//在继承接口时确定泛型类型
interface IA extends IUsb<String, Double> {
}
//在实现接口时确定泛型类型
class U implements IUsb<Integer, Boolean> {
public Boolean get(Integer integer) {
return null;
}
public void hi(Integer integer) {
}
public void run(Integer integer, Boolean aBoolean) {
}
}
interface IUsb<T, R> {
R get(T t);
void hi(T t);
void run(T t, R r);
//在jdk8中,可以在接口中使用默认方法,也是可以使用泛型的
default R method(T t) {
return null;
}
}
//在继承接口时,可以确定泛型类型,也可以继续沿用泛型标识符
interface IPhone<E, M> extends IUsb<E, M> { //标识符字母不一定要一模一样
}没有指定类型,默认为
Object
。
自定义泛型方法
基本语法:
1
2
3修饰符 <T,R,...> 返回类型 方法名(参数列表) {
}使用细节:
泛型方法,可以定义在普通类中,也可以定义在泛型类中。
当泛型方法被调用时,类型会确定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.f.chapter15.customgeneric;
/**
* @author fzy
* @date 2023/6/29 21:41
*/
public class CustomGenericMethod {
public static void main(String[] args) {
Car car = new Car();
car.fly("宾利", 100); //当调用方法时,传入参数,编译器就会确定类型
}
}
class Car {
public void drive() { //普通方法
}
public <T, R> void fly(T t, R r) { //泛型方法
System.out.println(t.getClass());
System.out.println(r.getClass());
}
}public void eat(E e) {}
,修饰符后面没有<T,R,...
,说明eat
方法不是泛型方法,而是使用了泛型。泛型方法,即可以使用类声明的泛型,也可以使用自己声明的泛型。
★泛型的继承和通配符
泛型不具备继承性。
List<Object> list = new ArrayList<String>();
-> 错误的<?>
:表示支持任意泛型类型。<? extends A>
:表示支持A
类以及A
类的子类,规定了泛型的上限。<? super A>
:表示支持A
类以及A
类的父类,不限于直接父类,规定了泛型的下限。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
74package com.f.chapter15.generic;
import java.util.ArrayList;
import java.util.List;
/**
* @author fzy
* @date 2023/6/30 13:33
*/
public class GenericExtends {
public static void main(String[] args) {
List<Object> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
List<AA> list3 = new ArrayList<>();
List<BB> list4 = new ArrayList<>();
List<CC> list5 = new ArrayList<>();
//`List<?>`:表示支持任意泛型类型。
printCollection1(list1);
printCollection1(list2);
printCollection1(list3);
printCollection1(list4);
printCollection1(list5);
//List<? extends AA>:表示支持 `AA` 类以及 `AA` 类的子类。
// 即希望传入的list中的对象的类型为 `AA` 类或 `AA` 类的子类
//printCollection2(list1); //错误的
//printCollection2(list2); //错误的
printCollection2(list3);
printCollection2(list4);
printCollection2(list5);
//List<? super AA>:表示支持 `AA` 类以及 `AA` 类的父类,不限于直接父类。
// 即希望传入的list中的对象的类型为 `AA` 类或 `AA` 类的父类,不限于直接父类
printCollection3(list1);
//printCollection3(list2); //错误的
printCollection3(list3);
//printCollection3(list4); //错误的
//printCollection3(list5); //错误的
}
//`<?>`:表示支持任意泛型类型。
public static void printCollection1(List<?> c) {
for (Object obj : c) {
System.out.println(obj);
}
}
//`<? extends A>`:表示支持 `A` 类以及 `A` 类的子类,规定了泛型的上限。
public static void printCollection2(List<? extends AA> c) {
for (AA aa : c) {
System.out.println(aa);
}
}
//`<? super A>` :表示支持 `A` 类以及 `A` 类的父类,不限于直接父类,规定了泛型的下限。
public static void printCollection3(List<? super AA> c) {
for (Object obj : c) {
System.out.println(obj);
}
}
}
class AA {
}
class BB extends AA {
}
class CC extends BB {
}
通配符用在方法的参数列表(即
()
)中,表示对传入的参数的泛型类型进行限制。另一个例子:(看注释应该就明白是什么意思了)
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
43package com.f.chapter15.generic;
import java.util.List;
/**
* @author fzy
* @date 2023/6/30 14:43
*/
public class GenericExtends2 {
public static void main(String[] args) {
P<String> p1 = new P<>(); //T会被替换成String
P<Integer> p2 = new P<>(); //T会被替换成Integer
P<List> p3 = new P<List>(); //T会被替换成List
/*
* 下面的语句 p1.set(p2) 可以这么看:传入的参数为p2,而p2使用的泛型类型为Integer,
* 对于P类的set方法,若把 <> 遮掉,则可以看出,要传入的是一个P类的对象,
* 此时再看 <> 内部,表示传入的这个P类的对象,其使用的泛型类型应为List或其子类,
* 而p2使用的泛型类型为Integer,不满足 <> 内的要求,所以下面的语句会报错。
* */
//p1.set(p2); //错误的
p1.set(p3); //正确的
}
}
class P<T> {
private T t;
public T getT() {
return t;
}
//通配符
//设置指定类型的范围,超过范围就会报错
//extends : 指定范围必须是其(这里是List)子类
public void set(P<? extends List> p) {
return;
}
//super : 指定类型必须是其(这里是List)父类
public void setSuper(P<? super List> p) {
return;
}
}
JUint
JUnit
是一个Java
语言的单元测试框架。多数Java
的开发环境都已经集成了JUnit
作为单元测试的工具。- 在要进行单元测试的函数前面加上
@Test
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23package com.f.chapter15.junit_;
import org.junit.jupiter.api.Test;
/**
* @author fzy
* @date 2023/6/30 15:00
*/
public class JUnit_ {
public static void main(String[] args) {
}
public void m1() {
System.out.println("m1()方法...");
}
public void m2() {
System.out.println("m2()方法...");
}
}- 在要进行单元测试的函数前面加上