• Java泛型


    泛型

    泛型类

    • 例子:

      • class Score<T>{
            T value;
            public Score(T value){
                this.value = value;
            }
        
        }
        public class fanxing {
            public static void main(String[] args) {
                Score<String > score = new Score<>("aaa");
                System.out.println(score.value);
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
    • 泛型将数据类型的确定控制在了编译阶段

      • 在编写代码的时候就能明确泛型的类型
      • 如果类型不符合,将无法通过编译!
      • 因为是具体使用对象时才会明确具体类型,所以说静态方法中是不能用的
        • image-20220927135128332
    • 我们在方法中使用待确定类型的变量时

      • 因为此时并不明确具体是什么类型
      • 那么默认会认为这个变量是一个Object类型的变
        • 因为无论具体类型是什么,一定是Object类的子类:
          • image-20220926235642963
    • 因为泛型本身就是对某些待定类型的简单处理,如果都明确要使用什么类型了,那大可不必使用泛型。

      • 还有,不能通过这个不确定的类型变量就去直接创建对象和对应的数组
        • image-20220927134825845
    • 如果要让某个变量支持引用确定了任意类型的泛型,那么可以使用?通配符

      • Score<?> score = new Score<>("aaa");
        score = new Score<Integer>(111);
        Object object = score.value;//但是注意,如果使用通配符,那么由于类型不确定,所以说具体类型同样会变成Object
        System.out.println(object);//111
        
        • 1
        • 2
        • 3
        • 4
    • 当然,泛型变量不止可以只有一个,如果需要使用多个的话,我们也可以定义多个:

      • public class Test<A, B, C> {   //多个类型变量使用逗号隔开
            public A a;
            public B b;
            public C c;
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
    • 如果要存放基本数据类型的值,我们只能使用对应的包装类

      • image-20220926232135111
      Test<Integer> test = new Test<>();
      
      • 1
    • 当然,如果是基本类型的数组,因为数组本身是引用类型,所以说是可以的:

    public static void main(String[] args) {
        Test<int[]> test = new Test<>();
    }
    
    • 1
    • 2
    • 3

    泛型与多态

    不只是类,包括接口、抽象类,都是可以支持泛型的:

    public interface Study<T> {
        T test();
    }
    
    • 1
    • 2
    • 3
    • 当子类实现此接口时

      • 我们可以选择在实现类明确泛型类型

        • interface Study4<T>{
              T test();
          }
          class A implements Study4<Integer>{//在实现接口或是继承父类时,如果子类是一个普通类,那么可以直接明确对应类型
              @Override
              public Integer test() {
                  return null;
              }
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9
      • 或是继续使用此泛型让具体创建的对象来确定类型:

        • interface Study4<T>{
              T test();
          }
          class A<T> implements Study4<T>{//让子类继续为一个泛型类,那么可以不用明确
              @Override
              public T test() {
                  return null;
              }
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
          • 7
          • 8
          • 9

    继承也是同样的:

    static class A<T> {
        
    }
    
    static class B extends A<String> {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    泛型方法

    当然,类型变量并不是只能在泛型类中才可以使用,我们也可以定义泛型方法。

    • 当某个方法(无论是是静态方法还是成员方法)需要接受的参数类型并不确定时,我们也可以使用泛型来表示:

      • 在**返回值类型前添加<>**并填写泛型变量表示这个是一个泛型方法

        • private static <T> T test(T t){
              return t;
          }
          
          • 1
          • 2
          • 3
    • 实际上泛型方法在很多工具类中也有,比如说Arrays的排序方法:

      • Integer[] arr = {1, 4, 5, 2, 6, 3, 0, 7, 9, 8};
        Arrays.sort(arr, new Comparator<Integer>() {   
          	//通过创建泛型接口的匿名内部类,来自定义排序规则,因为匿名内部类就是接口的实现类,所以说这里就明确了类型
            @Override
            public int compare(Integer o1, Integer o2) {   //这个方法会在执行排序时被调用(别人来调用我们的实现)
                return 0;
            }
        });
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
    • Lambda表达式,像这种只有一个方法需要实现的接口,直接安排了:

      • Integer[] arr = {1,3,4,523,1,2,2};
        Arrays.sort(arr, (o1, o2) -> {
            return o1 - o2;
        });
        System.out.println(Arrays.toString(arr));
        
        • 1
        • 2
        • 3
        • 4
        • 5

    包括数组复制方法:

    public static void main(String[] args) {
        String[] arr = {"AAA", "BBB", "CCC"};
        String[] newArr = Arrays.copyOf(arr, 3);   //这里传入的类型是什么,返回的类型就是什么,也是用到了泛型
        System.out.println(Arrays.toString(newArr));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    泛型的界限

    • 现在有一个新的需求

      • 现在没有String类型的成绩了

      • 但是成绩依然可能是整数,也可能是小数,这时我们不希望用户将泛型指定为除数字类型外的其他类型,我们就需要使用到泛型的上界定义:

        • class Score<T extends Number>{ //设定类型参数上界,必须是Number或是Number的子类
              T value;
              public Score(T value){
                  this.value = value;
              }
          }
          
          • 1
          • 2
          • 3
          • 4
          • 5
          • 6
    • 只需要在泛型变量的后面添加extends关键字即可指定上界

      • 使用时,具体类型只能是我们指定的上界类型或是上界类型的子类,不得是其他类型。否则一律报错:
        • image-20231010160818952
    • 实际上就像这样:

      • img
    • 同样的,当我们在使用变量时,泛型通配符也支持泛型的界限:

      • Score score = new Score<>(666);
        
        • 1
    • 下界

      • Score score = new Score<>(666);
        
        • 1
        • image-20220927002611032
    • 只不过下界仅适用于通配符,对于类型变量来说是不支持的。下界限定就像这样:

      • 4aa52791-73f4-448f-bab3-9133ea85d850.jpg

    类型擦除

    • 编译时的类型检查

      • 可以不指定类型,直接绕过检查,使用原始 object

        • Score score = new Score(111);
          score.value = "222";
          System.out.println(score.value);//222
          
          • 1
          • 2
          • 3
    • 最终编译完 --》 有上界用上界类型,没有上界就是 object 类型

    • 实际上在Java中并不是真的有泛型类型(为了兼容之前的Java版本)因为所有的对象都是属于一个普通的类型

      • 一个泛型类型编译之后,实际上会直接使用默认的类型

      • public abstract class A {
            abstract Object test(Object t);  //默认就是Object
        }
        
        • 1
        • 2
        • 3
    • 当然,如果我们给类型变量设定了上界,那么会从默认类型变成上界定义的类型

      • public abstract class A <T extends Number>{   //设定上界为Number
            abstract T test(T t);
        }
        
        • 1
        • 2
        • 3
      • 那么编译之后:

        • public abstract class A {
              abstract Number test(Number t);  //上界Number,因为现在只可能出现Number的子类
          }
          
          • 1
          • 2
          • 3
    • 因此,泛型其实仅仅是在编译阶段进行类型检查

      • 当程序在运行时,并不会真的去检查对应类型

      • 所以说哪怕是我们不去指定类型也可以直接使用:

        • Test test = new Test(); //对于泛型类Test,不指定具体类型也是可以的,默认就是原始类型
      • 只不过此时编译器会给出警告:

        • image-20220927131226728
    • 同样的,由于类型擦除,实际上我们在使用时,编译后的代码是进行了强制类型转换的:

      • public static void main(String[] args) {
            A<String> a = new B();
            String  i = a.test("10");     //因为类型A只有返回值为原始类型Object的方法
        }
        
        • 1
        • 2
        • 3
        • 4
      • 实际上编译之后:

        • public static void main(String[] args) {
              A a = new B();
              String i = (String) a.test("10");   //依靠强制类型转换完成的
          }
          
          • 1
          • 2
          • 3
          • 4
    • 不支持创建泛型数组

      • image-20231010162938851

      • 要用只能用原始类型:

        • Score[] scores = new Score[10];
      • 只不过只是把它当做泛型类型的数组还是可以用的:

        image-20220927134335255

    函数式接口

    • 函数式接口就是JDK1.8专门为我们提供好的用于Lambda表达式的接口
      • 这些接口都可以直接使用Lambda表达式,非常方便,这里我们主要介绍一下四个主要的函数式接口:

    Supplier供给型函数式接口

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

      • @FunctionalInterface   //函数式接口都会打上这样一个注解
        public interface Supplier {
            T get();   //实现此方法,实现供给功能
        }
        
        • 1
        • 2
        • 3
        • 4
      • 比如我们要实现一个专门供给Student对象Supplier,就可以使用:

      • public class Student {
            public void hello(){
                System.out.println("我是学生!");
            }
        }
        //专门供给Student对象的Supplier
        private static final Supplier<Student> STUDENT_SUPPLIER = Student::new;
        public static void main(String[] args) {
            Student student = STUDENT_SUPPLIER.get();
            student.hello();
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11

    Consumer消费型函数式接口

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

      • @FunctionalInterface
        public 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); };
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
    • 使用起来也是很简单的:

      • //专门消费Student对象的Consumer
        private static final Consumer<Student> STUDENT_CONSUMER = student -> System.out.println(student+" 真好吃!");
        public static void main(String[] args) {
            Student student = new Student();
            STUDENT_CONSUMER.accept(student);
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
    • andThen方法继续调用:

      • public static void main(String[] args) {
            Student student = new Student();
            STUDENT_CONSUMER   //我们可以提前将消费之后的操作以同样的方式预定好
                    .andThen(stu -> System.out.println("我是吃完之后的操作!")) 
                    .andThen(stu -> System.out.println("好了好了,吃饱了!"))
                    .accept(student);   //预定好之后,再执行
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
      • 这样,就可以在消费之后进行一些其他的处理了,使用很简洁的代码就可以实现:

        • image-20220927181706365

    Function函数型函数式接口

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

      • @FunctionalInterface
        public 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;
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
        • 14
        • 15
        • 16
        • 17
        • 18
    • apply方法,这个是我们需要实现的:

      • //这里实现了一个简单的功能,将传入的int参数转换为字符串的形式
        private static final Function<Integer, String> INTEGER_STRING_FUNCTION = Object::toString;
        public static void main(String[] args) {
            String str = INTEGER_STRING_FUNCTION.apply(10);
            System.out.println(str);
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
    • compose指定函数式的结果作为当前函数式的实参:

      • public static void main(String[] args) {
            String str = INTEGER_STRING_FUNCTION
                    .compose((String s) -> s.length())   //将此函数式的返回值作为当前实现的实参
                    .apply("lbwnb");   //传入上面函数式需要的参数
            System.out.println(str);
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
    • 相反的,andThen可以将当前实现的返回值进行进一步的处理,得到其他类型的值:

      • public static void main(String[] args) {
            Boolean str = INTEGER_STRING_FUNCTION
                    .andThen(String::isEmpty)   //在执行完后,返回值作为参数执行andThen内的函数式,最后得到的结果就是最终的结果了
                    .apply(10);
            System.out.println(str);
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
    • Function中还提供了一个将传入参数原样返回的实现:

      • public static void main(String[] args) {
            Function<String, String> function = Function.identity();   //原样返回
            System.out.println(function.apply("不会吧不会吧,不会有人听到现在还是懵逼的吧"));
        }
        
        • 1
        • 2
        • 3
        • 4

    Predicate断言型函数式接口

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

      • @FunctionalInterface
        public 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);
            }
        }
        
        • 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 Student {
            public int score;
        }
        private static final Predicate<Student> STUDENT_PREDICATE = student -> student.score >= 60;
        public static void main(String[] args) {
            Student student = new Student();
            student.score = 80;
            if(STUDENT_PREDICATE.test(student)) {  //test方法的返回值是一个boolean结果
                System.out.println("及格了,真不错,今晚奖励自己一次");
            } else {
                System.out.println("不是,Java都考不及格?隔壁初中生都在打ACM了");
            }
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
        • 9
        • 10
        • 11
        • 12
        • 13
    • 我们也可以使用组合条件判断:

      • public static void main(String[] args) {
            Student student = new Student();
            student.score = 80;
            boolean b = STUDENT_PREDICATE
                    .and(stu -> stu.score > 90)   //需要同时满足这里的条件,才能返回true
                    .test(student);
            if(!b) System.out.println("Java到现在都没考到90分?你的室友都拿国家奖学金了");
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
        • 6
        • 7
        • 8
    • 同样的,这个类型提供了一个对应的实现,用于判断两个对象是否相等:

      • public static void main(String[] args) {
            Predicate<String> predicate = Predicate.isEqual("Hello World");   //这里传入的对象会和之后的进行比较
            System.out.println(predicate.test("Hello World"));
        }
        
        • 1
        • 2
        • 3
        • 4

    判空包装

    • 判空包装类Optional,这个类可以很有效的处理空指针问题。

      • private static void test(String str){
            Optional
                    .ofNullable(str)   //将传入的对象包装进Optional中
                    .ifPresent(s -> System.out.println("字符串长度为:"+s.length()));  //如果不为空,则执行这里的Consumer实现
        }
        
        • 1
        • 2
        • 3
        • 4
        • 5
      • 我们再获取时可以优雅地处理为空的情况:

        • 我们可以对于这种有可能为空的情况进行处理,如果为空,那么就返回另一个备选方案

          • private static void test(String str){
                String s = Optional.ofNullable(str).orElse("我是为null的情况备选方案");
                System.out.println(s);
            }
            
            • 1
            • 2
            • 3
            • 4
    • 将包装的类型直接转换为另一种类型:

      private static void test(String str){
          Integer i = Optional
                          .ofNullable(str)
                          .map(String::length)   //使用map来进行映射,将当前类型转换为其他类型,或者是进行处理
                          .orElse(-1);
          System.out.println(i);
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
  • 相关阅读:
    pyenv-win换国内源
    Java8 parallerStream导致Spring项目启动线程阻塞(死锁)
    学员分享丨我与誉天:RHCA证书之旅
    《动手学深度学习 Pytorch版》 4.4 模型选择、欠拟合和过拟合
    JavaSE——异常
    Azure + React + ASP.NET Core 项目笔记一:项目环境搭建(三)
    js的同步执行
    0基础女生学建模前,先看这篇文章
    代码随想录 Day-45|#139 单词拆分
    第十三章《集合》第4节:Queue集合
  • 原文地址:https://blog.csdn.net/yin_ming_hui/article/details/133971824