JavaSE第5篇泛型程序设计

基本概念

泛型顾名思义就是广泛的类型。这里的类型可以是基本数据类型的包装类(但不能是基本数据类型),可以是引用数据类型。但如果是基本类型的数组,因为数组本身是引用类型。泛型在定义时并不明确是什么类型,而是需要到使用时才会确定对应的泛型类型。泛型就是定义一种模板,例如ArrayList,然后在代码中为用到的类创建对应的ArrayList<类型>

泛型将数据类型的确定控制在了编译阶段,在编写代码的时候就能明确泛型的类型,如果类型不符合,将无法通过编译!编写泛型时,需要定义泛型类型;静态方法不能引用泛型类型,必须定义其他类型(例如)来实现静态泛型方法;泛型可以同时定义多种类型,例如Map<K, V>。

Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”

基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Score<String> score = new Score<String>("计算机网络", "EP074512", "优秀");
//因为现在有了类型变量,在使用时同样需要跟上<>并在其中填写明确要使用的类型
//这样我们就可以根据不同的类型进行选择了
String value = score.value; //一旦类型明确,那么泛型就变成对应的类型了
System.out.println(value);
}
}

class Score<T> { //泛型类需要使用<>,我们需要在里面添加1 - N个类型变量
String name;
String id;
T value; //T会根据使用时提供的类型自动变成对应类型

Score(String name, String id, T value) { //这里T可以是任何类型,但是一旦确定,那么就不能修改了
this.name = name;
this.id = id;
this.value = value;
}
}

泛型通配符案例

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
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {

Generic<Integer> intGeneric = new Generic<Integer>(123);
Generic<Number> numGeneric = new Generic<Number>(456);
showKeyValue(intGeneric);
showKeyValue(numGeneric);


Notepad<String,Integer> t = null ; // 定义两个泛型类型的对象
t = new Notepad<String,Integer>() ; // 里面的key为String,value为Integer
t.setKey("汤姆") ; // 设置第一个内容
t.setValue(20) ; // 设置第二个内容
System.out.print("姓名;" + t.getKey()) ; // 取得信息
System.out.print(",年龄;" + t.getValue()) ; // 取得信息
}

static void showKeyValue(Generic<?> obj){
System.out.println("key value is " + obj.getKey());
}
}

//有多个泛型变量的时候
class Notepad<K,V>{ // 此处指定了两个泛型类型
private K key ; // 此变量的类型由外部决定
private V value ; // 此变量的类型由外部决定
public K getKey(){
return this.key ;
}
public V getValue(){
return this.value ;
}
public void setKey(K key){
this.key = key ;
}
public void setValue(V value){
this.value = value ;
}
}


class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;

public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}

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

通配符区别

  • ? 表示不确定的 java 类型
  • T (type) 表示具体的一个java类型
  • K V (key value) 分别代表java键值中的Key Value
  • E (element) 代表Element

T和?的区别

1.T是一个确定的类型,通常用于泛型类和泛型方法的定义,?是一个不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。

1
2
3
4
5
// 可以
T t = operate();

// 不可以
? car = operate();

2.通过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
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Generic<Integer> intGeneric = new Generic<Integer>(123);
Generic<Number> numGeneric = new Generic<Number>(456);

//showKeyValueT(intGeneric,numGeneric); //会报错,T可以确保泛型参数的一致性
showKeyValueAny(intGeneric,numGeneric); //不确保参数的一致性
}

static <T extends Number> void showKeyValueT(Generic<T> dest, Generic<T> src){
System.out.println("dest key value is " + dest.getKey());
System.out.println("src key value is " + src.getKey());
}

static void showKeyValueAny(Generic<? extends Number> dest, Generic<? extends Number> src){
System.out.println("dest key value is " + dest.getKey());
System.out.println("src key value is " + src.getKey());
}
}

class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;

public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}

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

3.类型参数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
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
System.out.println(maxMum(1,2,3));
System.out.println(maxMum(1.1,2.2,3.3));

//其中Integer的定义如下
/*
public final class Integer extends Number
implements Comparable<java.lang.Integer>, Constable, ConstantDesc
*/
}

//限制类型参数T必须实现Comparable和必须是Number的子类,限制上界
public static <T extends Number & Comparable<T>> T maxMum(T x, T y, T z) {
T max = x;
if (y.compareTo(max) > 0) {
max = y;
}
if (z.compareTo(max) > 0) {
max = z;
}
return max;
}
}

4.通配符可以使用超类限定而类型参数不行

  • 类型参数T只具有 一种 类型限定方式:
    1
    T extends A
  • 但是通配符 ? 可以进行 两种限定:
    1
    2
    ? extends A
    ? super A

泛型的界限

引入原因

为了解决泛型中隐含的转换问题,Java泛型加入了类型参数的上下边界机制。<? extends A>表示该类型参数可以是A(上边界)或者A的子类类型。编译时擦除到类型A,即用A类型代替类型参数。这种方法可以解决开始遇到的问题,编译器知道类型参数的范围,如果传入的实例类型B是在这个范围内的话允许转换,这时只要一次类型转换就可以了,运行时会把对象当做A的实例看待。

  1. 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;
  2. 如果它表示一个 T 的消费者,就使用 < ? super T>;
  3. 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。
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
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
//限定上界
Score<? extends Integer> score = new Score<>("数据结构与算法", "EP074512", 60);
Number o = score.getValue(); //可以看到,此时虽然使用的是通配符,但是不再是Object类型,而是对应的上界
System.out.println(o);

//限定下界
Score<? super Number> scoreDown = new Score<>("数据结构与算法基础", "EP074512", 10);
Object o1 = scoreDown.getValue();
System.out.println(o1);
}
}

class Score<T extends Number> { //设定类型参数上界,必须是Number或是Number的子类
private final String name;
private final String id;
private final T value;

public Score(String name, String id, T value) {
this.name = name;
this.id = id;
this.value = value;
}

public T getValue() {
return value;
}
}

类型擦除

泛型信息只存在于编译前,编译后的字节码中是不包含泛型中的类型信息的。因此,编译器在编译时去掉类型参数,叫做类型擦除。

擦拭法决定了泛型

  • 不能是基本类型,例如:int;
  • 不能获取带泛型类型的Class,例如:Pair.class;
  • 不能判断带泛型类型的类型,例如:x instanceof Pair
  • 不能实例化T类型,例如:new T()。

利用反射绕过类型擦除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
List<Integer> list = new ArrayList<>();
list.add(123);
try {
// 由于List中的泛型参数没有设置上界,所以add方法可以add任何Object的子类型参数
Method method = list.getClass().getDeclaredMethod("add", Object.class);
method.invoke(list, "string");
method.invoke(list, true);
method.invoke(list, 45.6);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(list);
}
}

泛型数组

  • 部分反射API是泛型,例如:Class,Constructor

  • 可以声明带泛型的数组,但不能直接创建带泛型的数组,必须强制转型;

  • 可以通过Array.newInstance(Class, int)创建T[]数组,需要强制转型;

  • 同时使用泛型和可变参数时需要特别小心。

函数式接口

供给型函数式

这个接口是专门用于供给使用的,其中只有一个get方法用于获取需要的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
//专门供给Student对象的Supplier
private static final Supplier<Student> STUDENT_SUPPLIER = Student::new;
public static void main(String[] args) throws CloneNotSupportedException {
Student student = STUDENT_SUPPLIER.get();
student.hello();
}
}

@FunctionalInterface //函数式接口都会打上这样一个注解
interface Supplier<T> {
T get(); //实现此方法,实现供给功能
}

class Student {
public void hello(){
System.out.println("我是学生!");
}
}

消费型函数式接口

这个接口专门用于消费某个对象的

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
import java.util.Objects;
public class Main {
//专门消费Student对象的Consumer
private static final Consumer<Student> STUDENT_CONSUMER = student -> System.out.println(student+" 真好吃!");
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student();
STUDENT_CONSUMER //我们可以提前将消费之后的操作以同样的方式预定好
.andThen(stu -> System.out.println("我是吃完之后的操作!"))
.andThen(stu -> System.out.println("好了好了,吃饱了!"))
.accept(student); //预定好之后,再执行
}
}

@FunctionalInterface
interface Consumer<T> {
void accept(T t); //这个方法就是用于消费的,没有返回值

default Consumer<T> andThen(Consumer<? super T> after) { //这个方法便于我们连续使用此消费接口
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}

class Student {
public void hello(){
System.out.println("我是学生!");
}
}

函数型函数式接口

这个接口消费一个对象,然后会向外供给一个对象(前两个的融合体)

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
import java.util.Objects;
public class Main {
//这里实现了一个简单的功能,将传入的int参数转换为字符串的形式
private static final Function<Integer, String> INTEGER_STRING_FUNCTION = Object::toString;
public static void main(String[] args) throws CloneNotSupportedException {
//使用compose将指定函数式的结果作为当前函数式的实参
String str = INTEGER_STRING_FUNCTION
.compose((String s) -> s.length()) //将此函数式的返回值作为当前实现的实参
.apply("lbwnb"); //传入上面函数式需要的参数
System.out.println(str);

//andThen可以将当前实现的返回值进行进一步的处理,得到其他类型的值
Boolean strBol = INTEGER_STRING_FUNCTION
.andThen(String::isEmpty) //在执行完后,返回值作为参数执行andThen内的函数式,最后得到的结果就是最终的结果了
.apply(10);
System.out.println(strBol);

//Function中还提供了一个将传入参数原样返回的实现
Function<String, String> function = Function.identity(); //原样返回
System.out.println(function.apply("俺老孙来也"));
}
}

@FunctionalInterface
interface Function<T, R> {
R apply(T t); //这里一共有两个类型参数,其中一个是接受的参数类型,还有一个是返回的结果类型

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}

static <T> Function<T, T> identity() {
return t -> t;
}
}

断言型函数式接口

接收一个参数,然后进行自定义判断并返回一个boolean结果

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
import java.util.Objects;
public class Main {
private static final Predicate<Student> STUDENT_PREDICATE = student -> student.score >= 60;
public static void main(String[] args) throws CloneNotSupportedException {
Student student = new Student();
student.score = 80;
if(STUDENT_PREDICATE.test(student)) { //test方法的返回值是一个boolean结果
System.out.println("过了过了");
} else {
System.out.println("垃圾题,淦!");
}

//组合条件判断
student.score = 80;
boolean flag = STUDENT_PREDICATE
.and(stu -> stu.score > 90) //需要同时满足这里的条件,才能返回true
.test(student);
if(!flag) System.out.println("卷不过,卷不过");

//判断两个对象是否相等
Predicate<String> predicate = Predicate.isEqual("Hello World"); //这里传入的对象会和之后的进行比较
System.out.println(predicate.test("Hello World"));
}
}

@FunctionalInterface
interface Predicate<T> {
boolean test(T t); //这个方法就是我们要实现的

default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}

default Predicate<T> negate() {
return (t) -> !test(t);
}

default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}

static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}

class Student {
public int score;
}

判空包装

Java8还新增了一个非常重要的判空包装类Optional,这个类可以很有效的处理空指针问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.Optional;
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
String value = "Hello,World";
String nulValue = null;
isNull(value);
isNull(nulValue);
}

static void isNull(String str){
Integer i = Optional
.ofNullable(str)
.map(String::length) //使用map来进行映射,将当前类型转换为其他类型,或者是进行处理
.orElse(-1);
System.out.println(i);
}
}

参考文章

聊一聊-JAVA 泛型中的通配符 T,E,K,V,?
Java全栈知识体系


JavaSE第5篇泛型程序设计
https://www.eldpepar.com/coding/60010/
作者
EldPepar
发布于
2022年10月22日
许可协议