# 前言

坚持学入门 Java 的第二天,前一篇记录了面向对象的内容毕竟对于我来说已经是第二次学习了还是比较轻松的,这篇记录一下泛型这块的内容。本篇中所有代码都在 Clown_java/src/main/java/JavaSE at master・clown-q/Clown_java (github.com)

# 泛型程序设计

# 泛型

泛型是在 JDK5 引入的一个特性,它提供了编译时类型安全检测机制,它可以在编译时检车到非法的类型,泛型的本质是参数类型

# 泛型类

可以就一个类定义为泛型类

public class GenericTest<T> { // 泛型类的使用 & lt;>,在尖括号中添加一个或者多个参数
    String name;
    T age;
    public GenericTest(String name, T age) {
        this.name = name;
        this.age = age;
    }
    public void echo(){
        System.out.println(this.name+":"+this.age);
    }
}

这里定义了一个泛型类

public class Test {
    public static void main(String[] args) {
        GenericTest<String> genericTest1 = new GenericTest<String>("小明","大叔");// 尖括号中的内容也可以用?,表示不确定是什么类型,可以使用任意类型
        GenericTest<Integer> genericTest2 = new GenericTest<Integer>("小明",1);
        genericTest1.echo();
        genericTest2.echo();
    }
}

上面实例化了两个对象,传入的数据类型不同,都是成功的

image-20230725084359452

泛型这个 T 是不能再 static 方法中使用

# 泛型方法

泛型也是可以在方法中使用的,当一个方法我们所需要的参数类型不确定的时候也可以用泛型来表示

public class Test {
    public static void main(String[] args) {
        String s = test("aaaa");
        Integer i = test(11);
    }
    public static <T> T test(T t){
        System.out.println(t);
        return t;
    }
}

这里可以看到 test 方法传入和返回的值都被定义为泛型

image-20230725094326718

# 泛型限制

在泛型类的学习中写了这样一个类

public class GenericTest<T> {
    String name;
    T age;
    public GenericTest(String name, T age) {
        this.name = name;
        this.age = age;
    }
    public void echo(){
        System.out.println(this.name+":"+this.age);
    }
}

image-20230725094920977

但是现在想让传入的参数只能是数字该怎么实现呢

这里可以规定一个上界限定即可

public class GenericTest<T extends Number> {// 使用 extends 关键字设置上界
    String name;
    T age;
    public GenericTest(String name, T age) {
        this.name = name;
        this.age = age;
    }
    public void echo(){
        System.out.println(this.name+":"+this.age);
    }
}

image-20230725095124009

可以看到,规定一个上界后,再想要使用 String 类型就会出现报错

这个上界其实就是限制了这个 T 只能是 Number 的子类,最高到 number

值得注意的是,泛型的通配符也是可以限定上界的

public class Test {
    public static void main(String[] args) {
        GenericTest<? extends Integer> genericTest2 = new GenericTest<>("小明",30);
        genericTest2.echo();
    }
}

同理,这里也是只能是 Integer 的子类,上界都有了,当然也有下界

public class Test {
    public static void main(String[] args) {
        GenericTest<? super Integer> genericTest2 = new GenericTest<>("小明",30);
        genericTest2.echo();
    }
}

与上界对应的是,下界限制的是传入的数据类型,只能是下界类或者是下界类的父类

下界的定义只能是在通配符的使用

# 函数式接口

jdk8 的一个特性,在我的上一篇 JavaSE(面向对象)中记录了有 Lambda 表达式这种东西,当接口只有一个待实现的抽象方法的时候可以使用 Lambda 表达式更加方便使用,而函数式接口就是专门提供这样的接口

# Supplier

image-20230725185005467

可以看到只有一个未实现的接口,满足 Lambda 表达式的需求这里 @FunctionalInterface 是函数式接口的注解,只有一个 get 接口用于获取需要的对象

public class FunctionInterface {
    public static void main(String[] args) {
        Supplier<test> testSupplier = new Supplier<test>() {
            @Override
            public test get() {
                return new test();
            }
        };
        testSupplier.get().test();
    }
    public static class test{
        public void test(){
            System.out.println("hello");
        }
    }
}

image-20230725190352658

这里定义了一个内部匿名类,获取了一个 test 类

image-20230725191622452

idea 这里其实已经提醒我可以使用 Lambda 表达式来写了

public class FunctionInterface {
    public static void main(String[] args) {
        Supplier<test> testSupplier = () -> new test();
        testSupplier.get().test();
    }
    public static class test{
        public void test(){
            System.out.println("hello");
        }
    }
}

返回值的类型,参数的数量和使用的构造方法是匹配的,可以直接改为方法引用

public class FunctionInterface {
    public static void main(String[] args) {
        Supplier<test> testSupplier = test::new;
        testSupplier.get().test();
    }
    public static class test{
        public void test(){
            System.out.println("hello");
        }
    }
}

所以当每次调用这个 get 方法的时候就可以获得一个新的 test 对象

# Consumer

image-20230725192854276

这里虽然有两个方法,accept 和 andThen 但是这个 andThen 不是需要实现的抽象方法,它已经有一个默认实现了,所以这里实际上也是满足的

# Function

image-20230725193742343

这个接口和上面那个一样也是其他方法都是有默认实现的

public class FunctionInterface {
    private static final Function<test,String> INTEGER_STRING_FUNCTION = Objects::toString;
    public static void main(String[] args) {
        String str = INTEGER_STRING_FUNCTION.apply(new test());
        System.out.println(str);
    }
    public static class test{
        public void test(){
            System.out.println("hello");
        }
    }
}

image-20230725194207518

这种类似的有很多,这里不在一一列举可以参考 Java 8 函数式接口

# 判空包装

先看下面这个例子

public class EmptyTest {
    public static void main(String[] args) {
        test(null);
    }
    public static void test(String str){
        if(!str.isEmpty()){
            System.out.println(str.length());
        }
    }
}

这里 test 方法将传入的字符串做一个判断,如果不为空就打印字符串的长度,看起来很合理,但是我们传入为 null 时

image-20230725131418710

很显然这里还需要一个判断,来防止传入 null,可以直接用 if,也可以使用判空包装

public class EmptyTest {
    public static void main(String[] args) {
        test("mmm" );
        test(null);
    }
    public static void test(String str){
        Optional
                .ofNullable(str)
                .ifPresent(a ->{
                    if (!a.isEmpty()){
                        System.out.println(a.length());
                    }
                });
    }
}

image-20230725132024818