木心

毕竟几人真得鹿,不知终日梦为鱼

导航

JDK8的新特性

 本文内容:

  一、Lambda 表达式和方法引用、构造器引用和数组引用

  二、接口的默认方法和静态方法

  三、重复注解

  四、获取方法形参的名称

  五、Stream流

  六、Optional

 

 

  Lambda 表达式本质:函数式接口的实例

 

一、Lambda 表达式和方法引用、构造器引用和数组引用   <=返回目录

  如果你需要了解更多Lambda表达式的细节,可以参考官方文档

1.1、命令模式

  有这样一个需求:需要把"处理行为"作为参数传入方法,这个"处理行为"可以理解为就是一个方法(一段代码)。那么如何将"一段代码"传入方法呢?可以考虑使用一个Command接口来定义一个方法,用这个方法来封装"处理行为"。

  Command接口

public interface Command {
    // 接口里定义的process()方法用于封装"处理行为"
    void process(int[] target);
}

 

  测试类

public class CommandTest {
    public static void main(String[] args) {
        int[] target = {1, -1, 20, 12};
        // 第一次处理数组,具体行为取决于传入的Command的process()方法
        processArray(target, new Command() {
            
            @Override
            public void process(int[] target) {
                for (int temp : target) {
                    System.out.print(temp + ", ");
                }
            }
        });
        
        // 第二次处理数组,具体行为取决于传入的Command的process()方法
        processArray(target, new Command() {
            
            @Override
            public void process(int[] target) {
                int sum = 0;
                for (int temp : target) {
                    sum += temp;
                }
                System.out.println("\n" + "sum = " + sum);
            }
        });
    }
    
    // 参数command:是一个接口,接口中的方法process()定义了对数组的"处理行为"
    public static void processArray(int[] target, Command command) {
        command.process(target);
    }
}

 

  从上面的代码可以看到:我们想将"一段代码"或"一个方法"作为参数传给方法还是要通过对象。java8的Lambda表达式就是来解决这个问题(函数式编程),比如下面的例子:

@Test
public void test() {
    List<String> list = Arrays.asList("北京", "南京", "天津");
    
    // 匿名实现类
    List<String> filterList1 = filterString(list, new Predicate<String>() {

        @Override
        public boolean test(String t) {
            return t.contains("京");
        }
        
    });
    System.out.println(filterList1);
    
    // Lambda表达式
    List<String> filterList2 = filterString(list, t -> t.contains("京"));
    System.out.println(filterList2);
    
    
}

/**
 * 根据给定的规则过滤集合中的字符串, 此规则由Predicate的方法决定
 * 
 * @param list
 * @param pre
 * @return
 */
public List<String> filterString(List<String> list, Predicate<String> pre) {
    List<String> filterList = new ArrayList<>();
    for (String s : list) {
        if (pre.test(s)) {
            filterList.add(s);
        }
    }
    return filterList;
}

 

 

1.2、不必要的接口实现类

  接口

/**
 * @author oy
 * @date 2019年5月28日 下午11:21:41
 * @version 1.0.0
 */
public interface Calculator {
    int add(int a, int b);
}

  

  实现类

public class CalculatorImpl implements Calculator {
    @Override
    public int add(int a, int b) {
        return a + b;
    }
}

  

  测试

public class Demo {
    public static void main(String[] args) {
        // 使用接口的实现类 CalculatorImpl
        Calculator cal = new CalculatorImpl();
        int result1 = sum(10, 20, cal);
        System.out.println("result1 = " + result1);
    }
    
    private static int sum(int a, int b, Calculator calculator) {
        return calculator.add(a, b);
    }
}

 

  使用匿名内部类

public class Demo {
    public static void main(String[] args) {
     // 使用匿名内部类 int result2 = sum(10, 20, new Calculator() { @Override public int add(int a, int b) { return a + b; } }); System.out.println("result2 = " + result2); } private static int sum(int a, int b, Calculator calculator) { return calculator.add(a, b); } }

 

  java8 Lambda表达式

public class Demo {
    public static void main(String[] args) {
  
     // java8 Lambda表达式:允许我们将函数当成参数传递给某个方法(函数式编程) int result3 = sum(10, 20, (a, b) -> a + b); System.out.println("result3 = " + result3); } private static int sum(int a, int b, Calculator calculator) { return calculator.add(a, b); } }

 

1.3、函数式接口

  Lambda表达式使用的前提是接口必须是函数式接口。

  函数式接口:有且仅有一个抽象方法的接口,可以使用注解@FunctionalInterface进行语义化标注。

@FunctionalInterface
public interface 函数式接口名 {
    // 函数式接口:有且仅有一个抽象方法
    int add(int a, int b);
}

 

1.4、Lambda表达式的标准格式

  Lambda表达式的标准格式: (int a, int b) -> { return a+ b; }

  Lambda表达式的组成:

    1)一些参数(方法参数);

    2)一个箭头;

    3)一些代码(方法体);

 

   Lambda表达式的标准格式的案例:

public interface Calculator {
    int add(int... params);
}
public class Demo {
    public static void main(String[] args) {
        // Lambda表达式:标准格式
        int result = sum(10, 20, 30, (int... params) -> {
            int sum = 0;
            for (int temp : params) {
                sum += temp;
            }
            return sum;
        });
        System.out.println("result = " + result);
    }
    
    private static int sum(int a, int b, int c, Calculator calculator) {
        return calculator.add(a, b, c);
    }
}

 

1.5、Lambda表达式的简化格式

  简化:

    (1)Lambda表达式中参数类型可以省略;

    (2)如果参数有且只有一个,小括号可以省略;如果没有参数或有多个参数,小括号不可以省略

    (3)如果方法体的语句只有一个,大括号、return和分号可以省略

  标准格式:

(int... params) -> {
    int sum = 0;
    for (int temp : params) {
        sum += temp;
    }
    return sum;
}  

  简化格式:

params -> {
    int sum = 0;
    for (int temp : params) {
        sum += temp;
    }
    return sum;
}

 

1.6、Lambda表达式的上下文推断

  Lambda表达式要想使用,一定要有函数接口的推断环境

    1)要么通过方法的参数类型来确定是哪个函数式接口;

    2)要么通过赋值操作来确定是哪个函数式接口;

public interface Calculator {
    int add(int a, int b);
}
public class Demo {
    public static void main(String[] args) {
        // Lambda表达式的上下文推断: Lambda表达式赋值给变量
        Calculator cal = (a, b) -> a + b;
        int result1 = cal.add(1, 2);
        int result2 = sum(10, 20, cal);
        
        // Lambda表达式的上下文推断: 调用sum方法的时候,参数类型是函数式接口,所以Lambda表达式可以推断出来是哪个接口
        int result3 = sum(100, 200, (a, b) -> a + b);
        System.out.println("result1=" + result1 + ", result2=" + result2 + ", result3=" + result3);
    }

    private static int sum(int a, int b, Calculator calculator) {
        return calculator.add(a, b);
    }
}

 

1.7、方法引用

  我们知道Lambda表达式是一个封装了"一段代码"的函数(方法),用来实现一种功能,如果我已经在某个类中已经写了相同功能的方法,是否可以复用呢?

  以1.4的案例为例:如果我已经有了工具方法doSum(),它实现了若干数的累加

public class Utils {
    
    public static int doSum(int... params) {
        int sum = 0;
        for (int temp : params) {
            sum += temp;
        }
        return sum;
    }
    
    public int doSum2(int... params) {
        int sum = 0;
        for (int temp : params) {
            sum += temp;
        }
        return sum;
    }
}

 

  方法引用:类名引用静态方法 或 对象名引用实例方法 或 类名引用实例方法

public interface Calculator {
    int add(int... params);
}
public class Demo {
    public static void main(String[] args) {
        // 方法引用:类名引用静态方法
        int result1 = sum(10, 20, 30, Utils::doSum);

        // 错误:对象名引用静态方法
        // The method doSum(int[]) from the type Utils should be accessed in a static way
        // int result2 = sum(10, 20, 30, new Utils()::doSum);

        // 方法引用:对象名引用成员方法
        int result2 = sum(10, 20, 30, new Utils()::doSum2);
        
        System.out.println("result1=" + result1 + ", result2=" + result2);
    }

    private static int sum(int a, int b, int c, Calculator calculator) {
        return calculator.add(a, b, c);
    }
}

 

 1.8、方法引用之类名引用静态方法的Demo

public interface Calculator {
    int getAbs(int a);
}
public class Demo { public static void main(String[] args) { // 方法引用:类名引用静态方法 int result = sum(-10, Math::abs); System.out.println("result = " + result); } private static int sum(int a, Calculator calculator) { return calculator.getAbs(a); } }

  

1.9、方法引用之对象名引用实例方法的Demo

public interface Command {
    void process(String str);
}
public class Demo { public static void main(String[] args) { // Lambda表达式 // print("abc", str -> System.out.println(str));
// 方法引用:对象名引用成员方法 print("abc", System.out::println); } private static void print(String str, Command cmd) { cmd.process(str); } }

 

1.10、方法引用之类名引用实例方法的Demo

 

@Test
// Comparator中的 int compare(T t1,T t2)
// String中的 int t1.compare(t2)
public void test() {
    Comparator<String> com1 = (s1,s2) -> s1.compareTo(s2);
    System.out.println(com1.compare("abc", "abd"));
    
    Comparator<String> com2 = String :: compareTo;
    System.out.println(com2.compare("abc", "abd"));
}

 

@Test
//BiPredicate中的boolean test(T t1,T t2)
// String中的 boolean t1.equals(t2)
public void test() {
    BiPredicate<String, String> pre1 = (t1,t2) -> t1.equals(t2);
    System.out.println(pre1.test("abc", "abd"));
    
    BiPredicate<String, String> pre2 = String :: equals;
    System.out.println(pre2.test("abc", "abd"));
}

 

1.11、JDK自带的函数式接口
  Predicate接口
          boolean test(T t):进行判断,利用我们在外部设定的条件对于传入的参数进行校验并返回验证通过与否
       Consumer接口
          void accept(T t):接收参数并依据传递的行为应用传递的参数值
       Function接口
          R apply(T t):执行转换操作,输入类型 T 的数据,返回 R 类型的结果
  这三个是最重要的接口,其他的接口都是从这三个接口演化而来。

import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

public class Demo {
    public static void main(String[] args) throws Exception {
        // Predicate<T> 判断
        Predicate<String> stringPredicate = str -> StringUtils.isBlank(str) || "error".equalsIgnoreCase(str);

        Predicate<String> stringPredicate2 = new Predicate<String>() {

            @Override
            public boolean test(String str) {
                return StringUtils.isBlank(str) || "error".equalsIgnoreCase(str);
            }
        };

        // Consumer<T>
        Consumer<String> stringConsumer = str -> {
            if (StringUtils.isBlank(str) || "error".equalsIgnoreCase(str)) {
                System.out.println("Consumer失败");
            }
        };

        Consumer<String> stringConsumer2 = new Consumer<String>() {

            @Override
            public void accept(String str) {
                if (StringUtils.isBlank(str) || "error".equalsIgnoreCase(str)) {
                    System.out.println("Consumer失败");
                }
            }
        };

        // Function<T,R>
        Function<String, Integer> stringFunction = str -> {
            if (StringUtils.isBlank(str) || "error".equalsIgnoreCase(str)) {
                return 0;
            } else {
                return 1;
            }
        };

        Function<String, Integer> stringFunction2 = new Function<String, Integer>() {

            @Override
            public Integer apply(String str) {
                if (StringUtils.isBlank(str) || "error".equalsIgnoreCase(str)) {
                    return 0;
                } else {
                    return 1;
                }
            }
        };

        // 测试
        String in = "error";
        if (stringPredicate.test(in)) {
            System.out.println("Predicate失败");
        }
        stringConsumer.accept(in);
        System.out.println(stringFunction.apply(in));
    }
}

 

1.12、构造器引用和数组引用

  构造器引用

@Test
public void test() {
    Function<String, User> fun1 = name -> new User(name);
    System.out.println(fun1.apply("zs"));
    
    // 构造器引用
    Function<String, User> fun2 = User :: new;
    System.out.println(fun2.apply("zs"));
}

class User {
    private String name;
    public User(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User [name=" + name + "]";
    }
}

  数组引用

public void test() {
    Function<Integer, String[]> fun1 = length -> new String[length];
    String[] arr1 = fun1.apply(3);
    System.out.println(Arrays.toString(arr1));
    
    // 数组引用
    Function<Integer, String[]> fun2 = String[] :: new;
    String[] arr2 = fun2.apply(3);
    System.out.println(Arrays.toString(arr2));
}

 

二、接口的默认方法和静态方法  <=返回目录

   接口的成员可以是:

    1)抽象方法;

    2)常量;

    3)默认方法(Java8支持)

    4)静态方法(Java8支持)

    5)私有方法(Java9支持)

2.1、默认方法和静态方法

   默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

@FunctionalInterface
public interface Calculator {
    // 函数式接口:有且仅有一个抽象方法
    int add(int... params);
    
    // 默认方法(Java8支持)
    default void method1() {
        System.out.println("默认方法。。。");
    }
    
    // 静态方法(Java8支持)
    static void method2() {
        System.out.println("静态方法。。。");
    }
}

 

  由于JVM上的默认方法的实现在字节码层面提供了支持,因此效率非常高。默认方法允许在不打破现有继承体系的基础上改进接口。该特性在官方库中的应用是:给java.util.Collection接口添加新方法,如stream()parallelStream()forEach()removeIf()等等。

  尽管默认方法有这么多好处,但在实际开发中应该谨慎使用:在复杂的继承体系中,默认方法可能引起歧义和编译错误。如果你想了解更多细节,可以参考官方文档

 

2.2、使用默认方法改写适配器

 

 三、重复注解  <=返回目录

  自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。如果你希望了解更多内容,可以参考官方文档

 

 四、获取方法形参的名称  <=返回目录

   为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer liberary。Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及-parameters参数)提供支持。

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class Demo {
    public static void main(String[] args) throws Exception {
        Method method = Demo.class.getMethod("add", int.class, int.class);
        
        System.out.println("method: " + method.getName());
        System.out.println("return: " + method.getReturnType().getName());
        
        for (Parameter parameter : method.getParameters()) {
            System.out.println("Parameter: " + parameter.getName());
        }
    }

    public int add(int a, int b) {
        return a + b;
    }
}

  

  在Java 8中这个特性是默认关闭的,因此如果不带-parameters参数编译上述代码并运行,则会输出如下结果:

method: add
return: int
Parameter: arg0
Parameter: arg1

  

  在编译Demo.class类时使用-parameters,javac -parameters Demo.class

 

  

  Eclipse中开启的办法:Preferences->java->Compiler下勾选Store information about method parameters选项。
这样在使用eclipse编译java文件的时候就会将参数名称编译到class文件中。

  Idea中开启的方法:File->Settings->Build,Execution,Deployment->Java Compiler下的Additional command line parameters选项中添加-parameters。

  Maven中开启的方法:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.3</version>
    <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <compilerArgs>
            <arg>-parameters</arg>
        </compilerArgs>
    </configuration>
</plugin>

 

五、Stream流  <=返回目录

5.1、获取Stream流的常见方式

// 1、根据集合获取流
ArrayList<String> list = new ArrayList<>();
list.add("a_1");
list.add("a_2");
list.add("a_3"); Stream
<String> stream1 = list.stream(); // 根据数组获取流,数组的元素必须是引用类型 Integer[] intArray = {10, 20, 30}; Stream<Integer> stream2 = Stream.of(intArray);
  
  stream()方法是Collection接口的一个默认方法(java8新添加的):
default Stream<E> stream() {
    return StreamSupport.stream(spliterator(), false);
}

5.2、Stream流的map映射方法

// Stream流的map映射方法
Stream<String> stream = Arrays.asList("a_1", "a_2", "a_3").stream().map(str -> str.split("_")[1]);

// map方法的参数类型是函数式接口Function,重写apply()方法,该方法作用是对元素进行处理和转换
Stream<String> stream2 = Arrays.asList("a_1", "a_2", "a_3").stream().map(new Function<String, String>() {

    @Override
    public String apply(String t) {
        return t.split("_")[1];
    }
});

 

  Stream接口map()方法的定义:Stream<T>调用map()方法返回Stream<R>

public interface Stream<T> extends BaseStream<T, Stream<T>> {
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
}

 

  Function<T, R>的理解

// Function<T,R>: T=String, 则推断出Lambda表达式中str类型为String;R=Integer,则Lambda表达式返回值必须为Integer
Function<String, Integer> stringFunction = str -> {
    if (StringUtils.isBlank(str) || "error".equalsIgnoreCase(str)) {
        return 0;
    } else {
        return 1;
    }
};

Function<String, Integer> stringFunction2 = new Function<String, Integer>() {

    @Override
    public Integer apply(String str) {
        if (StringUtils.isBlank(str) || "error".equalsIgnoreCase(str)) {
            return 0;
        } else {
            return 1;
        }
    }
};

 

  Stream接口map()方法的理解

// Stream流的map映射方法
Stream<String> stream1 = Arrays.asList("a_1", "a_2", "a_3").stream();

// stream1的泛型类型T=String,可以推断出Lambda表达式中str类型为String;
// Lambda表达式返回值为String,可以推断出泛型R=String,调用map()方法后返回Stream<String>
Stream<String> stream2 = stream1.map(str -> str.split("_")[1]);

// stream2的泛型类型T=String,可以推断出Lambda表达式中str类型为String;
// Lambda表达式返回值为Integer,可以推断出泛型R=Integer,调用map()方法后返回Stream<Integer>
Stream<Integer> stream3 = stream2.map(Integer::parseInt);

 

  demo:

@Test
public void test2() {
    User u1 = new User();
    u1.setName("zs");
    User u2 = new User();
    u2.setName("zs2");
    
    List<User> userList = new ArrayList<User>();
    userList.add(u1);
    userList.add(u2);
    
    List<String> list = userList.stream().map(User::getName).collect(Collectors.toList());
    list.stream().forEach(e -> System.out.println(e));
}

 

5.3、Stream流的filter过滤方法

  如果希望对流当中的元素进行过滤,可以使用filter()方法。filter()返回true,则保留元素。filter()方法的参数类型为函数式接口Predicate,参考本篇博客1.10章节。

Stream<Integer> stream = Arrays.asList("a_1", "a_2", "a_3").stream()
    .map(str -> str.split("_")[1])
    .map(Integer::parseInt);

stream.filter(new Predicate<Integer>() {

    @Override
    public boolean test(Integer t) {
        return t >= 2;
    }
}).forEach(e -> System.out.println(e));

 

 5.4、Stream流的forEach遍历方法

   如果希望遍历流中的元素,可以使用forEach()方法。forEach(Lambda表达式):对流当中的每一个元素都要进行操作。forEach()方法的参数是函数式接口Consumer。参数Lambda表达式必须是一个能够消费一个参数,而且不产生数据结果的Lambda。

Arrays.asList("a_1", "a_2", "a_3").stream()
    .map(str -> str.split("_")[1])
    .map(Integer::parseInt)
    .filter(t -> t >= 2)
    //.forEach(e -> System.out.println(e));
    .forEach(new Consumer<Integer>() {

        @Override
        public void accept(Integer t) {
            System.out.println(t);
            
        }
    });

 

5.5、流式编程

Arrays.asList("a_1", "a_2", "a_3").stream()
    .map(str -> str.split("_")[1])
    .map(Integer::parseInt)
    .filter(t -> t >= 2)
    .forEach(System.out::println);

 

六、Optional   <=返回目录

   Optional常用方法:

of:为value创建一个Optional对象,如果value为空则 会报出NullPointerException异常。
ofNullable:为value创建一个Optional对象,但可以允许value为null值。
isPresent:判断当前value是否为null,如果不为null则返回true,否则false。
ifPresent:如果不为null值就执行函数式接口的内容。
get:返回当前的值,如果为空则报异常。
orElse:返回当前值,如果为null则返回other。
orElseGet:orElseGet和orElse类似,只是orElseGet支持函数式接口来生成other值。
orElseThrow:如果有值则返回,没有则用函数式接口抛出生成的异常。

 

  测试

@Test
public void testOf() {
    User user = new User();
    // user = null; // 如果user=null, 下面这句报错NullPointerException
    Optional<User> userOptional = Optional.of(user);

    if (userOptional.isPresent()) {
        System.out.println("user is not null");
        System.out.println(userOptional.get() == user);
        System.out.println(userOptional.orElse(user) == user);
    }

    userOptional.ifPresent(System.out::println);
}

@Test
public void testNullable() {
    User user = null;
    User john = new User("john", 18);
    User dick = new User("dick", 12);

    // user为null,orElse(john)返回other=john
    System.out.println(Optional.ofNullable(user).orElse(john)); // User [name=john, age=18];

    // dick不为null,orElse(john)返回当前值dick
    System.out.println(Optional.ofNullable(dick).orElse(john)); // User [name=dick, age=12]

    // get:返回当前的值,如果为空则报异常
    System.out.println(Optional.ofNullable(john).get()); // User [name=john, age=18]

    // orElseGet和orElse类似,只是orElseGet支持函数式接口来生成other值
    System.out.println(Optional.ofNullable(user).orElseGet(() -> john)); // User [name=john, age=18]
}

 

参考:

  (1)官网:https://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html

  (2)JAVA8 十大新特性详解

  (3)Java9都快发布了,Java8的十大新特性你了解多少呢?

  (4)菜鸟教程:Java 8 新特性

  (5)JDK8新特性之Optional

  (6)java8 Lambda Stream collect Collectors 常用详细实例

posted on 2019-05-27 18:10  wenbin_ouyang  阅读(293)  评论(0编辑  收藏  举报