• 阶段二-Day18-Java新特性


    一、JDK新特征

    1. JDK版本变化

    JDK版本发布时间
    1.01996/1/23
    1.11997/2/19
    1.21998/12/4
    1.32000/5/8
    1.4.02002/2/13
    Java SE 5.0 / 1.52004/9/30
    Java SE 6.0 / 1.62006/4/1
    Java SE 7.0 / 1.72011/7/28
    Java SE 8.0 / 1.82014/3/18
    Java SE 9.02017/9/21
    Java SE 10.02018/3/21
    Java SE 11.02018/9/25
    Java SE 12.02019/3/19
    Java SE 13.02019/9/17
    Java SE 14.02020/3/17

    其中Java5,Java8是革命性的版本。Java11也将提供长期的支持。Java9,10都是“功能性的版本”,支持时间只有半年左右。

    特别是JDK8是JDK5以来最具革命性的版本。主要新特征包括Lambda表达式、函数式接口、方法引用和构造器引用、Stream API、新日期类、接口新变化等。其中的新日期类、接口新变化在前面章节已经讲解。

    总的来说,JDK8中的Lambda表达式和Stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。

    2. Lambda表达式

    Lambda适用于只有一个抽象方法的接口(函数式接口)。

    1. Lambda表达式引入
    1. 自定义接口

    1. public interface MyInterface {
    2. public void method();
    3. }
    1. 自定义实现类实现接口

    1. public class MyClass implements MyInterface{
    2. public void method() {
    3. System.out.println("使用类实现接口");
    4. }
    5. }
    1. 测试

    1. public class Test {
    2. public static void main(String[] args) {
    3. //1.使用外部类实现接口
    4. MyInterface myInterface = new MyClass();
    5. myInterface.method();
    6. //2.使用内部类
    7. MyInterface myInterface1 = new Inner();
    8. myInterface1.method();
    9. //3.使用匿名内部类
    10. MyInterface myInterface2 = new MyInterface() {
    11. @Override
    12. public void method() {
    13. System.out.println("匿名内部类");
    14. }
    15. };
    16. myInterface2.method();
    17. //4.lambda表达式
    18. MyInterface myInterface3 = () -> {
    19. System.out.println("lambda表达式");
    20. };
    21. myInterface3.method();
    22. }
    23. public static class Inner implements MyInterface{
    24. @Override
    25. public void method() {
    26. System.out.println("内部类实现接口");
    27. }
    28. }
    29. }
    2. Lambda说明
    1. Lambda表达式: Lambda表达式基于数学中的λ演算得名,对应java中的lambda抽象,是一个匿名函数,即没有函数名的函数。

    2. Lambda表达式好处: 使用 Lambda 表达式可使代码变的更加简洁紧凑。并且Lambda表达式可和Stream API等相结合,使代码更加简洁紧凑。Lambda 表达式经常用来替代部分匿名内部类。

    3. Lambda表达式的语法 (parameters) -> expression或 (parameters) ->{ statements; } 参数:要重写的方法的形参列表 -> :lambda运算符 表达式/语句体:要实现的方法的方法体

    4. Lambda表达式的本质:

      Lambda 表达式是一种匿名函数(不是匿名内部类),简单地说,它是没有声明的方法,也即没有访问修饰符、返回值声明和名字。它实质属于函数式编程的概念。

    3. Lambda表达式的使用
    3.1 无参,无返回值
    1. 接口

    1. public interface A {
    2. void test();
    3. }

         2.实现

    1. public class Test01 {
    2. public static void main(String[] args) {
    3. //lambda和匿名内部类很像,可以对照参考
    4. A a = new A() {
    5. @Override
    6. public void test() {
    7. System.out.println("匿名");
    8. }
    9. };
    10. a.test();
    11. A a1 = () -> {
    12. System.out.println("lambda");
    13. };
    14. a1.test();
    15. //简化//只有一条执行语句,可以省略{}
    16. A a2 = () -> System.out.println("lambda");
    17. a2.test();
    18. }
    19. }
    3.2 单参,无返回值
    1. 接口

    1. public interface B {
    2. void test(String name);
    3. }

          2. 实现

    1. public class Test02 {
    2. public static void main(String[] args) {
    3. B b = (String name) -> {
    4. System.out.println("你好" + name);
    5. };
    6. b.test("世界");
    7. //简化1:lambda可以简化参数类型,可以自动推断
    8. B b1 = (name) -> {
    9. System.out.println("你好" + name);
    10. };
    11. b1.test("世界");
    12. //简化2:只有一个参数,可以去掉括号
    13. B b2 = name -> {
    14. System.out.println("你好" + name);
    15. };
    16. b2.test("世界");
    17. //简化3:如果只有一句执行语句,可以省略{}
    18. B b3 = name -> System.out.println("你好" + name);
    19. b3.test("世界");
    20. }
    21. }
    3.3 多参,无返回值
    1. 接口

    1. public interface C {
    2. void test(String name, int age);
    3. }

         2.实现

    1. public class Test03 {
    2. public static void main(String[] args) {
    3. C c = (String name,int age) -> {
    4. System.out.println(name + " " + age);
    5. };
    6. c.test("zs",12);
    7. //简化,自动推断(多于一个参数,不能省略括号)
    8. C c1 = (name,age) -> {
    9. System.out.println(name + " " + age);
    10. };
    11. c.test("zs",12);
    12. //简化,只有一个执行语句
    13. C c2 = (name, age) -> System.out.println(name + " " + age);
    14. c.test("zs",12);
    15. }
    16. }
    3.4 无惨,有返回值
    1. 接口

    1. public interface D {
    2. int test();
    3. }

          2.实现

    1. public class Test04 {
    2. public static void main(String[] args) {
    3. D d = () -> {
    4. return 10;
    5. };
    6. System.out.println(d.test());
    7. //简化 只有返回值的代码 可以将{}和return都省略
    8. D d1 = () -> 10;
    9. System.out.println(d1.test());
    10. }
    11. }
    3.5 单参,有返回值
    1. 接口

    1. public interface E {
    2. String test(String name);
    3. }

            2.实现

    1. public class Test05 {
    2. public static void main(String[] args) {
    3. E e = (String name) -> {
    4. return name;
    5. };
    6. e.test("zs");
    7. //简化
    8. E e1 = (name) -> {
    9. return name;
    10. };
    11. e.test("zs");
    12. //简化
    13. E e2 = name -> {
    14. return name;
    15. };
    16. e.test("zs");
    17. //简化
    18. E e3 =name -> name;
    19. e.test("zs");
    20. //复杂的逻辑的代码,不能简化
    21. E e4 = aa -> {
    22. if(Integer.parseInt(aa) > 10){
    23. return aa;
    24. }
    25. return "0";
    26. };
    27. }
    28. }
    3.6 多参,有返回值
    1. 接口

    1. public interface F {
    2. int test(String name, int age);
    3. }

            2.实现

    1. public class Test06 {
    2. public static void main(String[] args) {
    3. /* 多个参数,有返回值*/
    4. //使用Lambda表达式
    5. F f1 = (name, age) -> {
    6. /* System.out.println(name);*/
    7. return age;
    8. };
    9. System.out.println(f1.test("ls", 20));
    10. //简化 只返回结果,无其他操作可以简化
    11. F f2 = (name, age) -> age;
    12. System.out.println(f2.test("ls", 20));
    13. }
    14. }
    3.7 注意
    1. 有返回值的Lambda表达式,如果方法体只有一条返回语句,可同时省略return和{}。

    2. 虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的抽象方法,不是规定接口中只能有一个方法,称为函数式接口。

    3.8 总结

    ->左面: ​ 参数类型可以省略不写,类型推断。 ​ 如果只有一个参数,()可以省略不写。 ​ ->右侧: ​ {}将方法体的具体内容包裹起来。 ​ 方法体中只有一条执行语句,{}可以省略不写。 ​ 如果执行语句就一条return语句,return 和 {}可以同时省略不写。

    3. 函数式接口

    1. 认识函数式接口

    函数式接口:接口中只能有一个抽象方法,其他的可以有default、static、Object里继承的方法等。

    作用:在Java中主要用在Lambda表达式和方法引用(想使用Lambda表达式,接口必须为函数式接口)。

    JDK8专门提供了@FunctionalInterface注解,用来进行编译检查。

    已经使用过的函数式接口,比如Comparator等,多线程阶段学习的函数式接口有Runnable、Callable等。

    注意:Comparable并没有被标记为函数式接口

    1. 演示

    1. @FunctionalInterface
    2. public interface MyInterface {
    3. //只有一个抽象方法
    4. public void method();
    5. //default方法不计
    6. default void method1(){}
    7. //static方法不计
    8. static void method2(){}
    9. //从Object继承的方法不计
    10. public boolean equals(Object obj);
    11. }
    2. 内置的函数式接口

    JDK 也提供了大量的内置函数式接口,使得 Lambda 表达式的运用更加方便、高效。这些内置的函数式接口已经可以解决我们开发过程中绝大部分的问题,只有一小部分比较特殊得情况需要我们自己去定义函数式接口。在这里特别介绍四个函数式接口。

    1. Consumer:消费型接口(void accept(T t))。有参数,无返回值。

    2. Supplier:供给型接口(T get())。只有返回值,没有入参。

    3. Function:函数型接口(R apply(T t))。一个输入参数,一个输出参数,两种类型不可不同、可以一致。

    4. Predicate:断言型接口(boolean test(T t))。输入一个参数,输出一个boolean类型得返回值。

    函数式接口方法名输入参数输出参数作用
    消费型接口Consumervoid accept(T t)Tvoid对类型为T的对象进行操作
    供给型接口SupplierT get()voidT返回类型为T的对象
    函数型接口FunctionR apply(T t)TR对类型为T的对象进行操作,返回类型为R的对象
    断言型接口Predicateboolean test(T t)Tboolean对类型为T的对象进行操作,返回布尔类型结果
    2.1 消费型接口
    1. public class Test {
    2. public static void main(String[] args) {
    3. ArrayList list = new ArrayList<>();
    4. //将一堆数字添加到集合中
    5. Collections.addAll(list,2,4,5,78,6);
    6. //consumer需要和forEach一起使用
    7. Consumer consumer = new Consumer() {
    8. @Override
    9. public void accept(Integer integer) {
    10. System.out.println(integer);
    11. }
    12. };
    13. list.forEach(consumer);
    14. //使用lambda表达式,自动推断类型,i前面不用带类型
    15. Consumer consumer1 = (i) -> System.out.println(i);
    16. list.forEach(consumer1);
    17. }
    18. }
    2.2 断言型接口
    1. public class Test {
    2. public static void main(String[] args) {
    3. ArrayList list = new ArrayList<>();
    4. Collections.addAll(list,34,56,89,65,87);
    5. Predicate predicate = new Predicate() {
    6. @Override
    7. public boolean test(Integer i) {
    8. if (i < 60){
    9. //返回true的删除
    10. return true;
    11. }
    12. //返回false的保留
    13. return false;
    14. }
    15. };
    16. list.removeIf(predicate);
    17. System.out.println(list);
    18. Predicate predicate1 = i -> {
    19. if (i > 80){
    20. return true;
    21. }
    22. return false;
    23. };
    24. list.removeIf(predicate1);
    25. System.out.println(list);
    26. }
    27. }

    4. 方法引用

    有时候,Lambda体中可能仅调用一个方法,而不做任何其它事,对于这种情况,通过一个方法名字来引用这个已存在的方法会更加清晰。方法引用是一个更加紧凑,易读的 Lambda 表达式,注意方法引用是一个 Lambda 表达式,方法引用操作符是双冒号 "::"。

    1. 方法引用1
    1. public class Test {
    2. public static void main(String[] args) {
    3. //使用匿名内部类实现
    4. Consumer consumer2 = new Consumer() {
    5. @Override
    6. public void accept(Integer i) {
    7. System.out.println(i);
    8. }
    9. };
    10. Consumer consumer = i -> System.out.println(i);
    11. ArrayList list = new ArrayList<>();
    12. Collections.addAll(list,34,56,89,65,87);
    13. list.forEach(consumer);
    14. //方法引用
    15. /*
    16. * 接口实现类的方法 :
    17. * void accept(Integer i)
    18. * 重写的方法中调用的方法:
    19. * System.out.println(i)
    20. * 两个都是对象.方法
    21. * 在本示例中,println()的参数类型、
    22. * 返回值类型正好和
    23. * Consumer接口的accept方法的
    24. * 参数类型、返回值类型相同,
    25. * 此时可以采用方法引用来简化语法。
    26. * */
    27. Consumer consumer1 = System.out::println;
    28. }
    29. }

    5. 流式编程

    1. 介绍

    Stream作为Java8的一大亮点,它与java.io包里的InputStream和OutputStream是完全不同的概念。

    它是对容器对象功能的增强,它专注于对容器对象进行各种非常便利、高效的聚合操作或者大批量数据操作。

    Stream API借助于同样新出现的Lambda表达式,极大的提高编程效率和程序可读性。

    同时,它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用fork/join并行方式来拆分任务和加速处理过程。所以说,Java8中首次出现的 java.util.stream是一个函数式语言+多核时代综合影响的产物。

    Stream有如下三个操作步骤:

    一、创建Stream:从一个数据源,如集合、数组中获取流。

    二、中间操作:一个操作的中间链,对数据源的数据进行操作。

    三、终止操作:一个终止操作,执行中间操作链,并产生结果。

    当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为“中间操作”。中间操作仍然会返回一个流对象,因此多个中间操作可以串连起来形成一个流水线。

    比如map (mapToInt,flatMap 等)、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered。

    当所有的中间操作完成后,若要将数据从流水线上拿下来,则需要执行终止操作。终止操作将返回一个执行结果,这就是你想要的数据。比如:forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator。

    多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何处理!而在终止操作时一次性全部处理,称作“惰性求值”。

    2. 创建Stream串行流
    1. public class Test {
    2. public static void main(String[] args) {
    3. //创建集合Stream
    4. ArrayList list = new ArrayList<>();
    5. Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
    6. //通过集合获取流对象
    7. Stream stream = list.stream();
    8. stream.forEach(System.out::println);
    9. //创建多个元素Stream
    10. System.out.println("----------------");
    11. //获取每个数字的流对象
    12. Stream stream3 = Stream.of(34,56,89,65,87,80,87,95,100,34,45);
    13. stream3.forEach(System.out::println);
    14. //创建数组Stream
    15. int [] arr = {34,56,89,65,87,80,87,95,100,34,45};
    16. IntStream stream1 = Arrays.stream(arr);
    17. stream1.forEach(System.out::println);
    18. }
    19. }
    3. 中间操作和终止操作

    1. public class Test {
    2. public static void main(String[] args) {
    3. ArrayList list = new ArrayList<>();
    4. Collections.addAll(list,11,45,88,1,3,4);
    5. Stream stream = list.stream();
    6. /*Stream stream1 = */stream.filter((i)->{
    7. if (i < 10){
    8. return true;
    9. }
    10. return false;
    11. });
    12. //jdk8中的stream流是不可复用的,使用一次后就会被关闭,二次使用必须重新创建stream流。
    13. stream.forEach(System.out::println);
    14. }
    15. }

    jdk8中的stream流是不可复用的,使用一次后就会被关闭,二次使用必须重新创建stream流。

    1. public class Test {
    2. public static void main(String[] args) {
    3. ArrayList list = new ArrayList<>();
    4. Collections.addAll(list, 5, 4, 4, 3, 2, 1);
    5. /* 中间操作 */
    6. Stream stream = list.stream()
    7. .filter(t -> { //过滤, 获取符合条件元素
    8. if (t > 1) {
    9. return true;
    10. }
    11. return false;
    12. })
    13. .map(t -> t + 1) //将元素进行相关操作
    14. .distinct() //去重
    15. .sorted() //排序
    16. .skip(1) //跳过1个
    17. .limit(2); //返回2个
    18. /* 终止操作 */
    19. //将数据收集到Set集合中
    20. //Set set = stream.collect(Collectors.toSet());
    21. //获取最大值
    22. //Optional max = stream.max(Integer::compareTo);
    23. //System.out.println(max.get());
    24. //获取最小值
    25. //Optional min = stream.min(Integer::compareTo);
    26. //System.out.println(min.get());
    27. //获取元素个数
    28. //long count = stream.count();
    29. //System.out.println(count);
    30. //变为数组
    31. //Object[] objects = stream.toArray();
    32. //获取第一个
    33. //Optional first = stream.findFirst();
    34. //System.out.println(first.get());
    35. //遍历获取每一个元素
    36. stream.forEach(System.out::println);
    37. }
    38. }
    4. 创建Stream并行流

    简单来说,并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

    Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。 Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与串行流之间进行切换 。

    4.1 Fork/Join

    将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个一个小任务的运算结果进行 join 汇总 。

    4.2 求和
    1. public class Test {
    2. //创建并行任务,原理将任务拆分成多个子任务
    3. //通过创建自定义类继承RecursiveTask
    4. //重写compute方法
    5. //内部类
    6. public static class SumTask extends RecursiveTask{
    7. private long start;
    8. private long end;
    9. private final int step = 2000;//自定义步长
    10. //定义构造方法赋值
    11. public SumTask(long start,long end){
    12. this.start = start;
    13. this.end = end;
    14. }
    15. @Override
    16. protected Long compute() {
    17. long sum = 0;
    18. //小于步长, 不再进行任务的拆分
    19. if (end - start < step){
    20. for (long i = start; i <= end ; i++) {
    21. sum = sum + i;
    22. }
    23. } else {
    24. //继续拆分,取中间值
    25. long mid = (end + start) / 2;
    26. //再次创建任务类
    27. SumTask sumTask1 = new SumTask(start, mid);
    28. SumTask sumTask2 = new SumTask(mid+1, end);
    29. //调用java封装好的方法,fork(),执行子任务
    30. sumTask1.fork();
    31. sumTask2.fork();
    32. //子任务执行完获取返回值
    33. Long join1 = sumTask1.join();
    34. Long join2 = sumTask2.join();
    35. sum = join1 + join2;
    36. }
    37. return sum;
    38. }
    39. }
    40. public static void main(String[] args) throws ExecutionException, InterruptedException {
    41. //1.普通方法
    42. long sum = 0;
    43. long l1 = System.currentTimeMillis();
    44. for (long i = 1; i <= 100000000L ; i++) {
    45. sum += i;
    46. }
    47. long l2 = System.currentTimeMillis();
    48. System.out.println(sum);
    49. System.out.println(l2 - l1);
    50. System.out.println("================");
    51. //2.使用并行流
    52. //创建并行线程池来执行并行任务提高效率
    53. ForkJoinPool forkJoinPool = new ForkJoinPool();
    54. long l3 = System.currentTimeMillis();
    55. //submit()方法中有一个需要传递一个ForkJoinTask类对象
    56. //RecursiveTask是ForkJoinTask的子类,可以使用多态
    57. SumTask sumTask = new SumTask(1, 100000000L);
    58. forkJoinPool.submit(sumTask);
    59. long l4 = System.currentTimeMillis();
    60. System.out.println(sumTask.get());
    61. System.out.println(l4 - l3);
    62. }
    63. }
    4.3 获取并行流
    1. /*
    2. * 获取并行流
    3. * */
    4. public class Test1 {
    5. public static void main(String[] args) {
    6. List list = new ArrayList<>();
    7. Collections.addAll(list,34,56,89,65,87,80,87,95,100,34,45);
    8. //获取并行流,底层采用ForkJoin框架,结果并不按照集合原有顺序输出
    9. Stream integerStream = list.parallelStream();
    10. //结果是乱序的,多个线程执行,所以没有顺序
    11. integerStream.forEach(System.out::println);
    12. }
    13. }

    6. Optional

    6.1 介绍

    Optional存在的意义就是简化为了防止空指针而进行的if..else等判断的代码。

    提供了全局value属性存储值。

    6.2 获取Optional对象
    1. 通过Optional.of()方法,传递的参数的值为null,会出现空指针异常。

    2. 通过Optional.ofNullable()方法,创建Optional对象。

    1. public class Test {
    2. public static void main(String[] args) {
    3. Test test = null;
    4. /*Optional test1 = Optional.of(test);*/
    5. //提示空指针异常,告诉你该对象为空java.lang.NullPointerException
    6. Optional test2 = Optional.ofNullable(test);
    7. //这里不会报空指针异常,是将一个Optional.empty对象给到了Optional对象
    8. System.out.println(test2);
    9. }
    10. public void aa(){
    11. }
    12. }
    6.3 Optional使用
    6.3.1 参数为null, 使用创建参数类型的对象
    1. 如果传递参数值为null, 可以使用orElse(T)或orElseGet(Supplier)进行参数的实例化。

    2. 如果传递参数值不为null, orElse()也会执行, orElseGet()不会执行。

    1. public class Demo {
    2. public static void main(String[] args) {
    3. //People people = null;
    4. People people =new People();
    5. People t1 = Optional.ofNullable(people).orElse(getPeople("orElse"));
    6. People t2 = Optional.ofNullable(people).orElseGet(()->getPeople("orElseGet"));
    7. }
    8. public static People getPeople(String str){
    9. System.out.println(str);
    10. return new People();
    11. }
    12. }
    6.3.2 获取Optional中存储的值

    可以通过get()获取到全局value对应的值。

    1. People people =new People();
    2. Optional optional = Optional.ofNullable(people);
    3. People people = optional.get();
    6.3.3 判断传递的参数是否为null

    ifPresent(Consumer consumer) 可以判断是否为null

    1. 为null, 不会执行Consumer的实现。

    2. 不为null, 执行Consumer的实现。

    1. People people =new People();
    2. Optional.ofNullable(people).ifPresent(peo -> peo.setName("zs"));
    3. System.out.println(people.getName());
    6.3.4 filter() 过滤
    1. Optional people1 = Optional.ofNullable(people).filter(x -> {
    2. if (x.getA() > 60) {
    3. return true;
    4. } else {
    5. return false;
    6. }
    7. });
    6.3.5 map()

    ​​​​​​​

    1. People people = null;
    2. // People people =new People("zs", 10);
    3. Optional.ofNullable(people).map(u -> u.getAge()).ifPresent(t -> System.out.println(t)); //对象不为null, 打印结果

  • 相关阅读:
    并发bug之源(一)-可见性
    5G NSA添加辅小区问题
    11.9树的表示方法(孩子,父亲,孩子兄弟),树、森林的遍历,一些操作,决策树,前缀树
    WPF中非递归(无后台代码)动态实现TreeView
    IDEA Plugin插件开发相关踩坑
    2022辽宁省数学建模B题原创成品
    详解http协议
    这一次,Python 真的有望告别 GIL 锁了?
    职场口才提升之道
    CSDN每日一题学习训练——Java版(二叉搜索树迭代器、二叉树中的最大路径和、按要求补齐数组)
  • 原文地址:https://blog.csdn.net/m0_59163770/article/details/132610758