当前位置:   article > 正文

JDK 8 新特性之接口详解_jdk8 supplier

jdk8 supplier

目录

一:接口新增的两个方法

接口默认方法的定义格式

接口默认方法的使用

二:接口静态方法

接口默认方法和静态方法的区别:

小结 :

三:常用内置函数式接口

四:Supplier接口

五:Consumer接口

六:Function接口

七:Predicate接口

案例:

小结:


一:接口新增的两个方法

      JDK 8以前的接口:  

  1. interface 接口名 {
  2. 静态常量;
  3. 抽象方法;
  4. }
      JDK 8 对接口的增强,接口还可以有 默认方法 静态方法

     JDK 8的接口: 

  1. interface 接口名 {
  2. 静态常量;
  3. 抽象方法;
  4. 默认方法;
  5. 静态方法;
  6. }

 

接口引入默认方法的背景
JDK 8 以前接口中只能有抽象方法。存在以下问题:
如果给接口新增抽象方法,所有实现类都必须重写这个抽象方法。不利于接口的扩展。
  1. interface A {
  2. public abstract void test1();
  3. // 接口新增抽象方法,所有实现类都需要去重写这个方法,非常不利于接口的扩展
  4. public abstract void test2();
  5. }
  6. class B implements A {
  7. @Override
  8. public void test1() {
  9. System.out.println("BB test1");
  10. }
  11. // 接口新增抽象方法,所有实现类都需要去重写这个方法
  12. @Override
  13. public void test2() {
  14. System.out.println("BB test2");
  15. }
  16. }
  17. class C implements A {
  18. @Override
  19. public void test1() {
  20. System.out.println("CC test1");
  21. }
  22. // 接口新增抽象方法,所有实现类都需要去重写这个方法
  23. @Override
  24. public void test2() {
  25. System.out.println("CC test2");
  26. }
  27. }
例如, JDK 8 时在 Map 接口中增加了 forEach 方法:
  1. public interface Map<K, V> {
  2. ...
  3. abstract void forEach(BiConsumer<? super K, ? super V> action);
  4. }
通过 API 可以查询到 Map 接口的实现类如:
  1. Interface Map<K, V>
  2. 所有已知实现类:
  3. AbstractMap,ConcurrentSkipListMap,IdentityHashMap,MapPropertyBase,Provider,Rendering Hints,SimpleMapProperty,Attributes,ReadonlyMapProperty,AuthProvider,EnumMapHashMap,
  4. clipboardContent,Hashtable,SimpleBindings,TreeMap,ConcurrentHashMap,MapProperty,
  5. ReadOnlyMapWrapper,Headers.Properties,LinkedHashMap,MultiMap Result,ScriptObjectMirror,
  6. TabularDataSupport,MapBinding,MapExpression,PrinterStateReasons,ReadonlyMapPropertyBase,UIDefaults,WeakHashMap
             如果在 Map 接口中增加一个抽象方法,所有的实现类都需要去实现这个方法,那么工程量时巨大的。
            因此,在 JDK 8 时为接口新增了默认方法,效果如下:
  1. public interface Map<K, V> {
  2. ...
  3. default void forEach(BiConsumer<? super K, ? super V> action) {
  4. ...
  5. }
  6. }
          接口中的默认方法实现类不必重写,可以直接使用,实现类也可以根据需要重写。这样就方便接口的扩展。

接口默认方法的定义格式

  1. interface 接口名 {
  2. 修饰符 default 返回值类型 方法名() {
  3. 代码;
  4. }
  5. }

接口默认方法的使用

         方式一:实现类直接调用接口默认方法
         方式二:实现类重写接口默认方法
  1. public class Demo02UserDefaultFunction {
  2. public static void main(String[] args) {
  3. BB b = new BB();
  4. // 方式一:实现类直接调用接口默认方法
  5. b.test02();
  6. CC c = new CC();
  7. // 调用实现类重写接口默认方法
  8. c.test02();
  9. }
  10. }
  11. interface AA {
  12. public abstract void test1();
  13. public default void test02() {
  14. System.out.println("AA test02");
  15. }
  16. }
  17. class BB implements AA {
  18. @Override
  19. public void test1() {
  20. System.out.println("BB test1");
  21. }
  22. }
  23. class CC implements AA {
  24. @Override
  25. public void test1() {
  26. System.out.println("CC test1");
  27. }
  28. // 方式二:实现类重写接口默认方法
  29. @Override
  30. public void test02() {
  31. System.out.println("CC实现类重写接口默认方法");
  32. }
  33. }

二:接口静态方法

     为了方便接口扩展, JDK 8 为接口新增了静态方法。
接口静态方法的定义格式
  1. interface 接口名 {
  2. 修饰符 static 返回值类型 方法名() {
  3. 代码;
  4. }
  5. }

接口静态方法的使用
直接使用接口名调用即可:接口名. 静态方法名 ();
  1. public class Demo04UseStaticFunction {
  2. public static void main(String[] args) {
  3. // 直接使用接口名调用即可:接口名.静态方法名();
  4. AAA.test01();
  5. }
  6. }
  7. interface AAA {
  8. public static void test01() {
  9. System.out.println("AAA 接口的静态方法");
  10. }
  11. }
  12. class BBB implements AAA {
  13. /* @Override 静态方法不能重写
  14. public static void test01() {
  15. System.out.println("AAA 接口的静态方法");
  16. }*/
  17. }

接口默认方法和静态方法的区别:

1. 默认方法通过实例调用,静态方法通过接口名调用。
2. 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以重写接口默认方法。
3. 静态方法不能被继承,实现类不能重写接口静态方法,只能使用接口名调用。

小结

              接口中新增的两种方法:
              默认方法和静态方法
            如何选择呢?
            如果这个方法需要被实现类继承或重写,使用默认方法,如果接口中的方法不需要被继承就使用静态方法

三:常用内置函数式接口

内置函数式接口来由来
        我们知道使用 Lambda 表达式的前提是需要有函数式接口。而 Lambda 使用时不关心接口名,抽象方法名,只关心抽象方法的参数列表和返回值类型。因此为了让我们使用Lambda 方便, JDK 提供了大量常用的函数式接口。
  1. import java.util.List;
  2. public class Demo01UserFunctionalInterface {
  3. public static void main(String[] args) {
  4. // 调用函数式接口中的方法
  5. method((arr) -> {
  6. int sum = 0;
  7. for (int n : arr) {
  8. sum += n;
  9. }
  10. return sum;
  11. });
  12. }
  13. // 使用自定义的函数式接口作为方法参数
  14. public static void method(Operator op) {
  15. int[] arr = {1, 2, 3, 4};
  16. int sum = op.getSum(arr);
  17. System.out.println("sum = " + sum);
  18. }
  19. }
  20. @FunctionalInterface
  21. interface Operator {
  22. public abstract int getSum(int[] arr);
  23. }
常用内置函数式接口介绍
    它们主要在 java.util.function 包中。下面是最常用的几个接口。
   1. Supplier 接口
  1. @FunctionalInterface
  2. public interface Supplier<T> {
  3. public abstract T get();
  4. }
 2. Consumer 接口
  1. @FunctionalInterface
  2. public interface Consumer<T> {
  3. public abstract void accept(T t);
  4. }
3. Function 接口
  1. @FunctionalInterface
  2. public interface Function<T, R> {
  3. public abstract R apply(T t);
  4. }
4. Predicate 接口
  1. @FunctionalInterface
  2. public interface Predicate<T> {
  3. public abstract boolean test(T t);
  4. }
  5. Predicate接口用于做判断,返回boolean类型的值

四:Supplier接口

            java.util.function.Supplier<T> 接口,它意味着 " 供给 " , 对应的 Lambda 表达式需要 对外提供 一个符合泛型类型的对象数据。
  1. @FunctionalInterface
  2. public interface Supplier<T> {
  3. public abstract T get();
  4. }
供给型接口,通过 Supplier 接口中的 get 方法可以得到一个值,无参有返回的接口。
使用 Lambda 表达式返回数组元素最大值
                 使用 Supplier 接口作为方法参数类型,通过 Lambda 表达式求出 int 数组中的最大值。提示:接口的泛型请使用 java.lang.Integer 类。

代码示例 :
  1. public class Demo05Supplier {
  2. public static void main(String[] args) {
  3. printMax(() -> {
  4. int[] arr = {10, 20, 100, 30, 40, 50};
  5. // 先排序,最后就是最大的
  6. Arrays.sort(arr);
  7. return arr[arr.length - 1]; // 最后就是最大的
  8. });
  9. }
  10. private static void printMax(Supplier<Integer> supplier) {
  11. int max = supplier.get();
  12. System.out.println("max = " + max);
  13. }
  14. }

五:Consumer接口

               java.util.function.Consumer<T> 接口则正好相反,它不是生产一个数据,而是 消费 一个数据,其数据类型由泛型参数决定。
  1. @FunctionalInterface
  2. public interface Consumer<T> {
  3. public abstract void accept(T t);
  4. }
      使用 Lambda 表达式将一个字符串转成大写和小写的字符串
            Consumer 消费型接口,可以拿到 accept 方法参数传递过来的数据进行处理 , 有参无返回的接口。基本使用如:
  1. import java.util.function.Consumer;
  2. public class Demo06Consumer {
  3. public static void main(String[] args) {
  4. // Lambda表达式
  5. test((String s) -> {
  6. System.out.println(s.toLowerCase());
  7. });
  8. }
  9. public static void test(Consumer<String> consumer) {
  10. consumer.accept("HelloWorld");
  11. }
  12. }
默认方法: andThen
               如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费一个数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中 default 方法 andThen 。 下面是 JDK 的源代码:
  1. default Consumer<T> andThen(Consumer<? super T> after) {
  2. Objects.requireNonNull(after);
  3. return (T t) -> { accept(t); after.accept(t); };
  4. }
              备注: java.util.Objects requireNonNull 静态方法将会在参数为 null 时主动抛出NullPointerException 异常。这省去了重复编写 if 语句和抛出空指针异常的麻烦。

           要想实现组合,需要两个或多个 Lambda 表达式即可,而 andThen 的语义正是 一步接一步 操作。例如两个步骤组合的情况:
  1. public class Demo07ConsumerAndThen {
  2. public static void main(String[] args) {
  3. // Lambda表达式
  4. test((String s) -> {
  5. System.out.println(s.toLowerCase());
  6. }, (String s) -> {
  7. System.out.println(s.toUpperCase());
  8. });
  9. // Lambda表达式简写
  10. test(s -> System.out.println(s.toLowerCase()), s ->
  11. System.out.println(s.toUpperCase()));
  12. }
  13. public static void test(Consumer<String> c1, Consumer<String > c2) {
  14. String str = "Hello World";
  15. // c1.accept(str); // 转小写
  16. // c2.accept(str); // 转大写
  17. // c1.andThen(c2).accept(str);
  18. c2.andThen(c1).accept(str);
  19. }
  20. }
        运行结果将会首先打印完全大写的 HELLO ,然后打印完全小写的 hello 。当然,通过链式写法可以实现更多步骤的组合。

六:Function接口

             java.util.function.Function<T,R> 接口用来根据一个类型的数据得到另一个类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值
  1. @FunctionalInterface
  2. public interface Function<T, R> {
  3. public abstract R apply(T t);
  4. }
使用 Lambda 表达式将字符串转成数字
               Function 转换型接口,对 apply 方法传入的 T 类型数据进行处理,返回 R 类型的结果,有参有返回的接口。使用的场景
例如:将 String 类型转换为 Integer 类型。
  1. public class Demo08Function {
  2. public static void main(String[] args) {
  3. // Lambda表达式
  4. test((String s) -> {
  5. return Integer.parseInt(s); // 10
  6. });
  7. }
  8. public static void test(Function<String, Integer> function) {
  9. Integer in = function.apply("10");
  10. System.out.println("in: " + (in + 5));
  11. }
  12. }

    默认方法: andThen
     Function 接口中有一个默认的 andThen 方法,用来进行组合操作。 JDK 源代码如:
  1. default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
  2. Objects.requireNonNull(after);
  3. return (T t) -> after.apply(apply(t));
  4. }
该方法同样用于 先做什么,再做什么 的场景,和 Consumer 中的 andThen 差不多:
  1. public class Demo09FunctionAndThen {
  2. public static void main(String[] args) {
  3. // Lambda表达式
  4. test((String s) -> {
  5. return Integer.parseInt(s);
  6. }, (Integer i) -> {
  7. return i * 10;
  8. });
  9. }
  10. public static void test(Function<String, Integer> f1, Function<Integer, Integer> f2) {
  11. // Integer in = f1.apply("66"); // 将字符串解析成为int数字
  12. // Integer in2 = f2.apply(in);// 将上一步的int数字乘以10
  13. Integer in3 = f1.andThen(f2).apply("66");
  14. System.out.println("in3: " + in3); // 660
  15. }
  16. }
        第一个操作是将字符串解析成为 int 数字,第二个操作是乘以 10 。两个操作通过 andThen 按照前后顺序组合到了一 起。
    请注意, Function 的前置条件泛型和后置条件泛型可以相同。

七:Predicate接口

             有时候我们需要对某种类型的数据进行判断,从而得到一个 boolean 值结果。这时可以使用 java.util.function.Predicate<T> 接口。
  1. @FunctionalInterface
  2. public interface Predicate<T> {
  3. public abstract boolean test(T t);
  4. }
  5. Predicate接口用于做判断,返回boolean类型的值
使用 Lambda 判断一个人名如果超过 3 个字就认为是很长的名字
        对 test 方法的参数 T 进行判断,返回 boolean 类型的结果。用于条件判断的场景:
  1. public class Demo10Predicate {
  2. public static void main(String[] args) {
  3. test(s -> s.length() > 3, "迪丽热巴");
  4. }
  5. private static void test(Predicate<String> predicate, String str) {
  6. boolean veryLong = predicate.test(str);
  7. System.out.println("名字很长吗:" + veryLong);
  8. }
  9. }
条件判断的标准是传入的 Lambda 表达式逻辑,只要名称长度大于 3 则认为很长。
默认方法: and
既然是条件判断,就会存在与、或、非三种常见的逻辑关系。其中将两个 Predicate 条件使用 逻辑连接起来实现“ 并且 的效果时,可以使用 default 方法 and 。其 JDK 源码为:
  1. default Predicate<T> and(Predicate<? super T> other) {
  2. Objects.requireNonNull(other);
  3. return (t) -> test(t) && other.test(t);
  4. }

案例:

  • 使用Lambda表达式判断一个字符串中即包含W,也包含H
  • 使用Lambda表达式判断一个字符串中包含W或者包含H
  • 使用Lambda表达式判断一个字符串中即不包含W
 如果要判断一个字符串既要包含大写 “H” ,又要包含大写 “W” ,那么:
  1. public class Demo10Predicate_And_Or_Negate {
  2. public static void main(String[] args) {
  3. // Lambda表达式
  4. test((String s) -> {
  5. return s.contains("H");
  6. }, (String s) -> {
  7. return s.contains("W");
  8. });
  9. }
  10. public static void test(Predicate<String> p1, Predicate<String> p2) {
  11. String str = "HelloWorld";
  12. boolean b1 = p1.test(str); // 判断包含大写“H”
  13. boolean b2 = p2.test(str); // 判断包含大写“W”
  14. // if (b1 && b2) {
  15. // System.out.println("即包含W,也包含H");
  16. // }
  17. boolean bb = p1.and(p2).test(str);
  18. if (bb) {
  19. System.out.println("即包含W,也包含H");
  20. }
  21. }
  22. }

默认方法: or
使用 Lambda 表达式判断一个字符串中包含 W 或者包含 H
and 类似,默认方法 or 实现逻辑关系中的 JDK 源码为:
  1. default Predicate<T> or(Predicate<? super T> other) {
  2. Objects.requireNonNull(other);
  3. return (t) -> test(t) || other.test(t);
  4. }
             如果希望实现逻辑 字符串包含大写 H 或者包含大写 W” ,那么代码只需要将 “and” 修改为 “or” 名称即可,其他都不变:
  1. public class Demo10Predicate_And_Or_Negate {
  2. public static void main(String[] args) {
  3. // Lambda表达式
  4. test((String s) -> {
  5. return s.contains("H");
  6. }, (String s) -> {
  7. return s.contains("W");
  8. });
  9. }
  10. public static void test(Predicate<String> p1, Predicate<String> p2) {
  11. String str = "HelloWorld";
  12. boolean b1 = p1.test(str); // 判断包含大写“H”
  13. boolean b2 = p2.test(str); // 判断包含大写“W”
  14. // if (b1 || b2) {
  15. // System.out.println("有H,或者W");
  16. // }
  17. boolean bbb = p1.or(p2).test(str);
  18. if (bbb) {
  19. System.out.println("有H,或者W");
  20. }
  21. }
  22. }
默认方法: negate
使用 Lambda 表达式判断一个字符串中即不包含 W
已经了解了,剩下的 (取反)也会简单。默认方法 negate JDK 源代码为:
  1. default Predicate<T> negate() {
  2. return (t) -> !test(t);
  3. }
          从实现中很容易看出,它是执行了 test 方法之后,对结果 boolean 值进行 “!” 取反而已。一定要在 test 方法调用之前调用 negate 方法,正如 and or 方法一样:
  1. import java.util.function.Predicate;
  2. public class Demo10Predicate_And_Or_Negate {
  3. public static void main(String[] args) {
  4. // Lambda表达式
  5. test((String s) -> {
  6. return s.contains("H");
  7. }, (String s) -> {
  8. return s.contains("W");
  9. });
  10. }
  11. public static void test(Predicate<String> p1, Predicate<String> p2) {
  12. String str = "HelloWorld";
  13. boolean b1 = p1.test(str); // 判断包含大写“H”
  14. boolean b2 = p2.test(str); // 判断包含大写“W”
  15. // 没有H,就打印
  16. // if (!b1) {
  17. // System.out.println("没有H");
  18. // }
  19. boolean test = p1.negate().test(str);
  20. if (test) {
  21. System.out.println("没有H");
  22. }
  23. }
  24. }

小结:

       1. Supplier 接口
  1. @FunctionalInterface
  2. public interface Supplier<T> {
  3. public abstract T get();
  4. }
       2. Consumer 接口
  1. @FunctionalInterface
  2. public interface Consumer<T> {
  3. public abstract void accept(T t);
  4. }
       3. Function 接口
  1. @FunctionalInterface
  2. public interface Function<T, R> {
  3. public abstract R apply(T t);
  4. }
       4. Predicate 接口
  1. @FunctionalInterface
  2. public interface Predicate<T> {
  3. public abstract boolean test(T t);
  4. }
  5. Predicate接口用于做判断,返回boolean类型的值
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/article/detail/53209
推荐阅读
相关标签
  

闽ICP备14008679号