Java8新特性总结

Java8新特性笔记
总括
Java8新特性总结

1、 Lambda表达式.

Lambda 是一个匿名函数。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
1.1、Lambda表达式的关键:从匿名类到 Lambda 的转换
示例:

 public static void main(String[] args) {
        //创建线程--匿名类写法
        Thread td = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello,Lamada!");
            }
        });
        td.start();
        
        //lamada写法--面向函数式编程(匿名函数)
        Thread td1 = new Thread(() -> System.out.println("hello,Lamada01!"));
        td1.start();
    }

1.2、Lambda表达式语法
Lambda表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称 为 Lambda 操作符或剪头操作符。它将 Lambda 分为两个部分:
左侧: 指定了 Lambda 表达式需要的所有参数
右侧: 指定了 Lambda 体,即 Lambda 表达式要执行的功能。
(1)语法格式一:无参,无返回值,Lambda 体只需一条语句
示例:Runnable r1 = () -> System.out.println(“Hello Lambda!”);
(2)语法格式二:Lambda 需要一个参数
示例:Consumer con = (x) -> System.out.println(x);
(3)语法格式三:Lambda 只需要一个参数时,参数的小括号可以省略
示例:Consumer con = x -> System.out.println(x);
(4)语法格式四:Lambda 需要两个参数,并且有返回值
示例:

 Comparator<Integer> com = (x, y) -> {   
		 System.out.println("函数式接口");
	     return Integer.compare(x, y);
 };

(5)语法格式五:当 Lambda 体只有一条语句时,return 与大括号可以省略
示例:Comparator com = (x, y) -> Integer.compare(x, y);
(6)Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
示例:

Comparator<Integer> com = (Integer x,Integer y) -> {  //Integer 类型可以省略
   System.out.println("函数式接口");
   return Integer.compare(x, y);
};

类型推断:Lambda 表达式中的参数类型都是由编译器推断 得出的。 Lambda 表达式中无需指定类型,程序依然可 以编译,这是因为 javac 根据程序的上下文,在后台 推断出了参数的类型。 Lambda 表达式的类型依赖于上 下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”

2、函数式接口

2.1、什么是函数式接口
(1)只包含一个抽象方法的接口,称为函数式接口。
(2)你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方 法上进行声明)。
(3)我们可以在任意函数式接口上使用 @FunctionalInterface 注解, 这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包 含一条声明,说明这个接口是一个函数式接口。

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

2.2、Java 内置四大核心函数式接口
Java8新特性总结

class TestConsumer implements Consumer<String> {
    @Override
    public void accept(String t) {
        System.out.println("消费型接口。。。");
    }
}

class TestSupplier implements Supplier<String> {
    @Override
    public String get() {
        return new String();
    }
}

class TestFunction implements Function<Integer, String> {
    @Override
    public String apply(Integer t) {
        return new String();
    }
}
class TestPredicate implements Predicate<String> {
    @Override
    public boolean test(String t) {
        return false;
    }
}

(1)Consumer : 消费型接口 void accept(T t);
示例:

   public static void main(String[] args) {
        happy(1100, (m) -> System.out.println("本次吃饭,共消费:" + m + "元"));
    }
    public static void happy(Integer money, Consumer<Integer> con) {
        con.accept(money);
}

(2)Supplier : 供给型接口 T get();
示例:

//Supplier<T> 供给型接口 :
     public static void main(String[] args) {
        List<Integer> numList = getNumList(5, () -> (int) (Math.random() * 100));
        for (Integer num : numList) {
            System.out.println(num);
        }
    }
    public static List<Integer> getNumList(int num, Supplier<Integer> sup) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            Integer n = sup.get();
            list.add(n);
        }
        return list;
} 

(3)Function<T, R> : 函数型接口 R apply(T t);
示例:

public static void main(String[] args) {
    String newStr = strHandler("\t\t\t 五一四天乐 ", (str) -> str.trim());
    System.out.println(newStr);
    String subStr = strHandler("我一定要做数据挖掘", (str) -> str.substring(2, 5));
    System.out.println(subStr);
}
// 需求:用于处理字符串
public static String strHandler(String str, Function<String, String> fun) {
    return fun.apply(str);
}

(4)Predicate : 断言型接口 boolean test(T t);
示例:

public static void main(String[] args) {
    List<String> list = Arrays.asList("Hello", "WWW", "Lambda", "!!", "ok");
    List<String> strList = filterStr(list, (s) -> s.length() > 3);
    for (String str : strList) {
        System.out.println(str);
    }
}
// 需求:将满足条件的字符串,放入集合中


   public static List<String> filterStr(List<String> list, Predicate<String> pre) {
        List<String> strList = new ArrayList<>();
        for (String str : list) {
            if (pre.test(str)) {
                strList.add(str);
            }
        }
        return strList;
    }

2.3、其它接口
Java8新特性总结
2.4、方法引用
当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致! )
方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。
如下三种主要使用情况:

(1)对象::实例方法
例如:

    PrintStream ps = System.out;
    Consumer<String> con = (str) -> ps.println(str);
    con.accept("Hello World!");
    System.out.println("--------------------------------");
    Consumer<String> con2 = ps::println;
    con2.accept("Hello Java8!");
    System.out.println("******************************");
    
    Consumer<String> con3 = System.out::println;
    con3.accept("Hello Hadoop!");

(2)类::静态方法
例如:

BiFunction<Double, Double, Double> fun = (x, y) -> Math.max(x, y);
System.out.println(fun.apply(1.5, 22.2));

BiFunction<Double, Double, Double> fun2 = Math::max;
System.out.println(fun2.apply(1.2, 1.5));
System.out.println("---------------------------------------------");

Comparator<Integer> com = (x, y) -> Integer.compare(y, x);

List<Integer> list  = Arrays.asList(new Integer [] {2,5,0});
Collections.sort(list);
System.out.println(list); //025

Collections.sort(list, com);
System.out.println(list); //520

Comparator<Integer> com2 = Integer::compare;
Collections.sort(list, com2);
System.out.println(list); //025(3)类::实例方法

(3)类::实例方法(目前只发现String类)
例如:

        BiPredicate<String, String> bp = (x, y) -> x.equals(y);
        System.out.println(bp.test("abde", "abcde"));

        BiPredicate<String, String> bp2 = String::equals;
        System.out.println(bp2.test("abc", "abc"));
        System.out.println("-----------------------------------------");

        Function<Employee, String> fun = e -> e.show();
        System.out.println(fun.apply(new Employee()));

        Function<Employee, String> fun2 = Employee::show;
        System.out.println(fun2.apply(new Employee())); 

注意:

  • ①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!(实际Demo测试,发现只需要方法参数列表相同即可)
  • ②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName

应用:(用foreach迭代Map集合)
default void forEach(BiConsumer<? super K, ? super V> action) {}(Producer Extends,Consumer Super

 public class FuncRef    {
    public static void main(String[] args) throws IOException
    {   Map<String, String> infoMap = new HashMap<>();
        infoMap.put("name", "Zebe");
        infoMap.put("site", "www.zebe.me");
        infoMap.put("email", "[email protected]");
        // 传统的Map迭代方式
        /*  for (Map.Entry<String, Object> entry : infoMap.entrySet()) {
            System.out.println(entry.getKey() + ":" + entry.getValue());
        }*/
        // JDK8的迭代方式//匿名函数
        infoMap.forEach((key, value) -> {   System.out.println(key + ":" + value);   });
        
        TestForEach testForEach = new TestForEach();
        // infoMap.forEach((key, value) ->TestForEach.method2fun(key, value)); // 调用静态方法
        // infoMap.forEach(TestForEach::method2fun); // 调用静态方法
        infoMap.forEach((key, value) -> testForEach.method2fun(key, value));
        infoMap.forEach(testForEach::method2fun);
    }
}
class TestForEach    {
    public void method2fun(String key, String value)        {
        System.out.println(key + "------**::**------" + value);
    }
}

2.5、构造器引用
构造器引用:构造器的参数列表,需要与函数式接口中参数列表保持一致!

格式: ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。
可以把构造器引用赋值给定义的方法,与构造器参数
列表要与接口中抽象方法的参数列表一致!

数组引用:
Java8新特性总结
3、Stream API 对集合数据进行操作
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对 集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数 据库查询。也可以使用 Stream API 来并行执行操作。简而言之, Stream API 提供了一种高效且易于使用的处理数据的方式。

3.1、流(Stream) 到底是什么呢?
流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。 ,“集合讲的是数据,流讲的是计算! ”
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
3.2、Stream的操作

  • 3.2.1、Stream操作的三个步骤:

(1)创建 Stream :一个数据源(如: 集合、数组), 获取一个流
(2)中间操作 :一个中间操作链,对数据源的数据进行处理
(3)终止操作(终端操作) :一个终止操作,执行中间操作链,并产生结果

如图:
Java8新特性总结

  • 3.2.2、创建Stream的几种方式

(1)Java8 中的 Collection 接口被扩展,提供了 两个获取流的方法:
 default Stream stream() : 返回一个顺序流
 default Stream parallelStream() : 返回一个并行流
(2)Java8 中的 Arrays 的静态方法 stream() 可 以获取数组流:
 static Stream stream(T[] array): 返回一个流
重载形式,能够处理对应基本类型的数组:
 public static IntStream stream(int[] array)
 public static LongStream stream(long[] array)
 public static DoubleStream stream(double[] array)
(3)可以使用静态方法 Stream.of(), 通过显示值 创建一个流。它可以接收任意数量的参数。
 public static Stream of(T… values) : 返回一个流
(4)可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。
 迭代
public static Stream iterate(final T seed, final UnaryOperator f)
 生成
public static Stream generate(Supplier s)

代码示例:

 public static void main(String[] args) {
      //1. Collection 提供了两个方法 stream() 与 parallelStream()
      List<String> list = new ArrayList<>();
      Stream<String> stream = list.stream(); //获取一个顺序流
      Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
      
      //2. 通过 Arrays 中的 stream() 获取一个数组流
      Integer[] nums = new Integer[5];
      Stream<Integer> stream1 = Arrays.stream(nums);
      stream1.forEach(System.out::println);// null null null null null

      
      //3. 通过 Stream 类中静态方法 of()
      Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
      stream2.forEach(System.out::println);// 1 2 3 4 5 6

      //4. 创建无限流
      //迭代
      Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(5);
      stream3.forEach(System.out::println); // 0 2 4 8 6 8 
      
      //生成
      Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
      stream4.forEach(System.out::println);
}

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

  • (1)筛选与切片

filter——接收 Lambda , 从流中排除某些元素。
limit——截断流,使其元素不超过给定数量。
skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
distinct——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素

代码示例:

 public static void main(String[] args) {
       List<Employee> emps =new ArrayList<>();
       emps.add(new Employee(25));
       emps.add(new Employee(30));
       emps.add(new Employee(36));
       
      //所有的中间操作不会做任何的处理
        Stream<Employee> stream = emps.stream()
         .filter((e) -> {
          System.out.println("测试中间操作");
          return e.getAge() <= 35;
         });
        
        //只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
        stream.forEach(System.out::println);
    }

(2)映射
Java8新特性总结

代码示例:

  public class StreamTest3 {
    	public static void main(String[] args) {
        List<Employee> emps = new ArrayList<>();
        emps.add(new Employee(25,"lxk"));
        emps.add(new Employee(30,"zax"));
        emps.add(new Employee(36,"zsr"));
        
        Stream<String> str = emps.stream().map((e) -> e.getName());
        str.forEach(System.out::println);
        System.out.println("-------------------------------------------");

        List<String> strList = Arrays.asList("aaa", "bbb", "ccc");
        Stream<String> stream = strList.stream().map(String::toUpperCase);
        stream.forEach(System.out::println);
        System.out.println("-------------------------------------------");

        Stream<Stream<Character>> stream2 = strList.stream().map(StreamTest3::filterCharacter);
        //stream2.forEach(System.out::println);
        stream2.forEach((sm) -> {sm.forEach(System.out::println);});
        System.out.println("---------------------------------------------");

        Stream<Character> stream3 = strList.stream().flatMap(StreamTest3::filterCharacter);
        stream3.forEach(System.out::println);
        }

	    public static Stream<Character> filterCharacter(String str) {
	        List<Character> list = new ArrayList<>();
	        for (Character ch : str.toCharArray()) {
	            list.add(ch);
	        }
	        return list.stream();
	    }

}

  • (3)排序

Java8新特性总结
代码示例:略

  • 3.4、Stream的终止操作

Java8新特性总结
4、Fork/Join 框架
就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个 小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行 join 汇总.
Java8新特性总结

4.1、Fork/Join 框架与传统线程池的区别
采用 “工作窃取”模式(work-stealing): 当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线 程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。 相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的 处理方式上.在一般的线程池中,如果一个线程正在执行的任务由于某些原因 无法继续运行,那么该线程会处于等待状态.而在fork/join框架实现中,如果 某个子问题由于等待另外一个子问题的完成而无法继续运行.那么处理该子 问题的线程会主动寻找其他尚未运行的子问题来执行.这种方式减少了线程 的等待时间,提高了性能.
4.2、并行流与串行流
并行流就是把一个内容分成多个数据块,并用不同的线程分 别处理每个数据块的流。

Java 8 中将并行进行了优化,我们可以很容易的对数据进行并 行操作。 Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。
5、Optional 类
Optional 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在, 原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且 可以避免空指针异常。

常用方法:
Optional.of(T t) : 创建一个 Optional 实例
Optional.empty() : 创建一个空的 Optional 实例
Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
isPresent() : 判断是否包含值
orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
flatMap(Function mapper):与 map 类似,要求返回值必须是Optional

用例:在项目中,总少不了一些非空的判断,可能大部分人还是如下的用法

if(null == user){
    //action1
}else{
    //action2
}

这时候该掏出Optional这个秘密武器了,它可以让非空校验更加优雅,间接的减少if操作。另一种方式,采用卫语句。

Optional<User> userOptional = Optional.ofNullable(user);
userOptional.map(action1).orElse(action2);

上面的代码跟第一段是等效的,通过一些新特性让代码更加紧凑。

6、新时间日期API
6.1、LocalDate、 LocalTime、 LocalDateTime
LocalDate、 LocalTime、 LocalDateTime 类的实 例是不可变的对象,分别表示使用 ISO-8601日 历系统的日期、时间、日期和时间。它们提供 了简单的日期或时间,并不包含当前的时间信 息。也不包含与时区相关的信息。
Java8新特性总结
6.2、Instant 时间戳
用于“时间戳”的运算。它是以Unix元年(传统 的设定为UTC时区1970年1月1日午夜时分)开始 所经历的描述进行运算

6.3、Duration 和 Period
Duration:用于计算两个“时间”间隔
Period:用于计算两个“日期”间隔
6.4、日期的操纵
TemporalAdjuster : 时间校正器。有时我们可能需要获 取例如:将日期调整到“下个周日”等操作。
TemporalAdjusters : 该类通过静态方法提供了大量的常 用 TemporalAdjuster 的实现。 例如获取下个周日:
6.5、解析与格式化
java.time.format.DateTimeFormatter 类:该类提供了三种
格式化方法:
 预定义的标准格式
 语言环境相关的格式
 自定义的格式
6.6、时区的处理
Java8 中加入了对时区的支持,带时区的时间为分别为: ZonedDate、 ZonedTime、 ZonedDateTime 其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式

例如 : Asia/Shanghai 等
ZoneId:该类中包含了所有的时区信息
getAvailableZoneIds() : 可以获取所有时区时区信息
of(id) : 用指定的时区信息获取 ZoneId 对象
时间api使用示例:https://blog.csdn.net/hhq12/article/details/81169536
6.7、与传统日期处理的转换
Java8新特性总结
7、其他新特性
7.1、接口中的默认方法
Java 8中允许接口中包含具有具体实现的方法,该方法称为 “默认方法”,默认方法使用 default 关键字修饰。

  • 接口默认方法的” 类优先” 原则

若一个接口中定义了一个默认方法,而另外一个父类或接口中 又定义了一个同名的方法时
<1> 选择父类中的方法。如果一个父类提供了具体的实现,那么 接口中具有相同名称和参数的默认方法会被忽略。
<2> 接口冲突。如果一个父接口提供一个默认方法,而另一个接 口也提供了一个具有相同名称和参数列表的方法(不管方法 是否是默认方法), 那么必须覆盖该方法来解决冲突
7.2、接口中的静态方法
Java8 中,接口中允许添加静态方法。
Java8新特性总结
7.3、重复注解与类型注解
Java8新特性总结