• JAVA8特性学习笔记

    2018-12-20 11:09:35
    JAVA8也好久了,在这里记录几个比较常见也是自己常用的新特性,与大家进行分享。 Lambda表达式 Stream API Date API Optional API 接口的默认方法和静态方法 Lambda表达式 它允许我们将函数当成参数传递给...

    用JAVA8也好久了,在这里记录几个比较常见也是自己常用的新特性,与大家进行分享。

    • Lambda表达式
    • Stream API
    • Date API
    • Optional API
    • 接口的默认方法和静态方法

    Lambda表达式

    它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。最简单的Lambda表达式可由逗号分隔的参数列表->符号和语句块组成。

    • Lambda语法如下
    (parameters) -> expression
    或
    (parameters) -> {statements;}
    
    • 使用Lambda的好处

      • 不需要声明参数类型,由编译器统一识别参数值。
      • 当参数为一个的时候,不需要定义圆括号,但多个参数的时候需要定义圆括号。
      • 如果函数主体只有一个语句,就不用使用大括号。
      • 如果函数主体只有一个返回值可以不指定返回类型,由编译器自动识别。
    • 举个栗子

    public static void mian(String[] args) {
        // 传统编程,不使用Lambda
        List<String> list = Arrays.asList("a", "b", "c");
        for (String i : list) {
          System.out.println("-----不使用Lambda");
          System.out.println("i is : " + i);
        }
    
        // 使用Lambda(1)
        System.out.println("-----使用Lambda(1)");
        list.forEach(k -> System.out.println("k is : " + k));
    
        // 使用Lambda(2)
        list.forEach(k -> {
          System.out.println("-----使用Lambda(2)");
          System.out.println("k is : " + k);
        });
    
        // 使用Lambda(3),不写返回值类型,默认由编译器识别
        System.out.println("-----使用Lambda(3)");
        list.sort((e1, e2) -> e1.compareTo(e2));
    
        // 使用Lambda(4)
        System.out.println("-----使用Lambda(4)");
        list.sort((e1, e2) -> {
          int result = e1.compareTo(e2);
          return result;
        });
      }
    

    Stream API

    新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。可以通过将List、Set、Array等对象转换成流进行操作。

    • 流操作分为两种:

      • 中间操作:会返回一个全新的流对象,不会影响最初的流。
      • 最终操作:会将流进行转换或者操作,返回非流对象。
    • 中间操作

      • java Stream<T> distinct() 去除流中重复的对象,并返回一个流。
      public static void main(String[] args) {
      	List<String> list = Arrays.asList("abc", "abc", "bc", "efg", "abcd", "", "jkl");
      	System.out.println("去重前:" + list);
      	System.out.println("去重后:" + list.stream().distinct().collect(Collectors.joining(",")));
      }
      
      • java Stream<T> skip(long n) 跳过Stream中的前n个对象,将其他对象返回一个Stream。如果n超过了Stream中对象的个数,则会返回一个空的Stream。
      public static void main(String[] args) {
      	List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
      	System.out.println("skip:");
      	numbers.stream().skip(2).forEach(System.out::println);
      }
      
      • java Stream<T> limit(long maxSize) 截取Stream的前maxSize个对象,并形成一个新Stream。
      public static void main(String[] args) {
      	List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
      	System.out.println("limit:");
      	numbers.stream().limit(4).forEach(System.out::println);
      }
      
      • java Stream<T> filter(Predicate<? super T> predicate) 根据给定的predicate来过滤对象,返回满足条件的对象构成的Stream。
      public static void main(String[] args) {
      	List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
      	System.out.println("filter:");
          numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println);
      }
      
      • Stream<R> map(Function<? super T, ? extends R> mapper) 通过给定的mapper,将T类型的流转换为R类型的Stream。
      public static void main(String[] args) {
      	List<String> str = Arrays.asList("a", "b", "c", "d");
      	System.out.println("map:");
      	str.stream().map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);
      }
      
      • java Stream<T> sorted(); Stream<T> sorted(Comparator<? super T> comparator) sorted方法可以对Stream进行排序。排序的对象必须实现Comparable,如果没实现会抛出ClassCastException;不提供comparator时,则会调用compareTo方法。
      public static void main(String[] args) {
      	List<Integer> list = Arrays.asList(6, 5, 4, 3, 2, 1);
      	System.out.println("倒序:" + list);
      	System.out.println("sorted:");
      	list.stream().sorted().forEach(System.out::println);
      }
      
    • 最终操作

      • Optional<T> min(Comparator<? super T> comparator)&
        Optional<T> max(Comparator<? super T> comparator)
        • 根据给定的comparator返回Stream中的max或min。
      • long count()
        • 返回Stream中对象的个数。
      • <R, A> R collect(Collector<? super T, A, R> collector)
        • 根据给定的collector对Stream中的元素进行操作,返回复杂数据结构的对象。用于将Stream中的对象转换成我们想要的结构,如list、map、set等。
          前例中就使用collect(Collectors.toList())将Stream中的对象转换成List。
      • Object[] toArray()
        • 将Stream中的对象返回成一个Object数组。
      • void forEach(Consumer<? super T> action)
        • 顾名思义,对Stream中每个元素进行action操作,但forEach是一个最终操作,一般在结束时查看对象使用。

    Date API

    • Java8在java.time包下提供了很多新的API。以下为两个比较重要的API。
      • Local(本地) − 简化了日期时间的处理,没有时区的问题。
      • Zoned(时区) − 通过制定的时区处理日期时间。
    • 在Java8之前在处理时间这部分,通常使用java.util.Datejava.text.SimpleDateFormat
    	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    	Date d = new Date();
    	String time = sdf.format(d);
    	System.out.println("time is : " + time);
    
    • Java8本地化日期时间API(copy自网上):
    import java.time.LocalDate;
    import java.time.LocalTime;
    import java.time.LocalDateTime;
    import java.time.Month;
     
    public class Java8Tester {
       public static void main(String args[]){
          Java8Tester java8tester = new Java8Tester();
          java8tester.testLocalDateTime();
       }
        
       public void testLocalDateTime(){
        
          // 获取当前的日期时间
          LocalDateTime currentTime = LocalDateTime.now();
          System.out.println("当前时间: " + currentTime);
            
          LocalDate date1 = currentTime.toLocalDate();
          System.out.println("date1: " + date1);
            
          Month month = currentTime.getMonth();
          int day = currentTime.getDayOfMonth();
          int seconds = currentTime.getSecond();
            
          System.out.println("月: " + month +", 日: " + day +", 秒: " + seconds);
            
          LocalDateTime date2 = currentTime.withDayOfMonth(10).withYear(2012);
          System.out.println("date2: " + date2);
            
          // 12 december 2014
          LocalDate date3 = LocalDate.of(2014, Month.DECEMBER, 12);
          System.out.println("date3: " + date3);
            
          // 22 小时 15 分钟
          LocalTime date4 = LocalTime.of(22, 15);
          System.out.println("date4: " + date4);
            
          // 解析字符串
          LocalTime date5 = LocalTime.parse("20:15:30");
          System.out.println("date5: " + date5);
       }
    }
    
    • Java8使用时区的日期时间API(copy自网上):
    import java.time.ZoneId;
     
    public class Java8Tester {
       public static void main(String args[]){
          Java8Tester java8tester = new Java8Tester();
          java8tester.testZonedDateTime();
       }
        
       public void testZonedDateTime(){
        
          // 获取当前时间日期
          ZonedDateTime date1 = ZonedDateTime.parse("2015-12-03T10:15:30+05:30[Asia/Shanghai]");
          System.out.println("date1: " + date1);
            
          ZoneId id = ZoneId.of("Europe/Paris");
          System.out.println("ZoneId: " + id);
            
          ZoneId currentZone = ZoneId.systemDefault();
          System.out.println("当期时区: " + currentZone);
       }
    }
    

    Optional API

    • 首先,Optional 类的引入很好的解决空指针异常。Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
    • Optional 是个容器:它可以保存类型T的值,或者仅仅保存null。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。
    import java.util.Optional;
     
    public class Java8Tester {
       public static void main(String args[]){
       
          Java8Tester java8Tester = new Java8Tester();
          Integer value1 = null;
          Integer value2 = new Integer(10);
            
          // Optional.ofNullable - 允许传递为 null 参数
          Optional<Integer> a = Optional.ofNullable(value1);
            
          // Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
          Optional<Integer> b = Optional.of(value2);
          System.out.println(java8Tester.sum(a,b));
       }
        
       public Integer sum(Optional<Integer> a, Optional<Integer> b){
        
          // Optional.isPresent - 判断值是否存在
            
          System.out.println("第一个参数值存在: " + a.isPresent());
          System.out.println("第二个参数值存在: " + b.isPresent());
            
          // Optional.orElse - 如果值存在,返回它,否则返回默认值
          Integer value1 = a.orElse(new Integer(0));
            
          //Optional.get - 获取值,值需要存在
          Integer value2 = b.get();
          return value1 + value2;
       }
    }
    

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

    这个我平时用的比较少,就简单介绍一下用法。

    public interface test {
       default void print(){
          System.out.println("默认方法");
       }
       
       static void staticVoid(){
          System.out.println("静态方法");
       }
    }
    

    最后附上Collectors类的静态工厂方法。

    工厂方法 返回类型 作用
    toList List<T> 把流中所有项目收集到一个 List
    toSet Set<T> 把流中所有项目收集到一个 Set,删除重复项
    toCollection Collection<T> 把流中所有项目收集到给定的供应源创建的集合menuStream.collect(toCollection(), ArrayList::new)
    counting Long 计算流中元素的个数
    sumInt Integer 对流中项目的一个整数属性求和
    averagingInt Double 计算流中项目 Integer 属性的平均值
    summarizingInt IntSummaryStatistics 收集关于流中项目 Integer 属性的统计值,例如最大、最小、 总和与平均值
    joining String 连接对流中每个项目调用 toString 方法所生成的字符串collect(joining(", "))
    maxBy Optional<T> 一个包裹了流中按照给定比较器选出的最大元素的 Optional, 或如果流为空则为 Optional.empty()
    minBy Optional<T> 一个包裹了流中按照给定比较器选出的最小元素的 Optional, 或如果流为空则为 Optional.empty()
    reducing 归约操作产生的类型 从一个作为累加器的初始值开始,利用 BinaryOperator 与流 中的元素逐个结合,从而将流归约为单个值累加int totalCalories = menuStream.collect(reducing(0, Dish::getCalories, Integer::sum));
    collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结果应用转换函数int howManyDishes = menuStream.collect(collectingAndThen(toList(), List::size))
    groupingBy Map<K, List<T>> 根据项目的一个属性的值对流中的项目作问组,并将属性值作 为结果 Map 的键
    partitioningBy Map<Boolean,List<T>> 根据对流中每个项目应用谓词的结果来对项目进行分区
    展开全文
  • Java8特性学习总结

    2017-05-21 22:40:02
    Java8特性

    Lambda 表达式

    Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
    Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:
    - 左侧:指定了 Lambda 表达式需要的所有参数
    - 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能。

    Lambda 表达式语法

    语法格式一:无参,无返回值,Lambda 体只需一条语句

    Runnable runnable = () -> System.out.println("hello Lambda");
    

    语法格式二:Lambda 需要一个参数

    Consumer<String> con = (t) -> System.out.println(t);
    

    语法格式三:Lambda 只需要一个参数时,参数的小括号可以省略

    Consumer<String> con = t -> System.out.println(t);
    

    语法格式四:Lambda 需要两个参数,并且有返回值

    Comparator<Integer> comparator = (x,y) -> {
            System.out.println("相加结果是:"+(x+y));
            return Integer.compare(x,y);
        };
    

    语法格式五:当 Lambda 体只有一条语句时,return 与大括号可以省略

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

    语法格式六:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

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

    类型推断

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

        /**
         * Lambda表达式基础语法,Java8中引入新的操作符"->"
         * Lambda表达式分为左右两部分:
         *  左:Lambda表达式的参数列表
         *  右:Lambda表达式中所需执行的功能,即Lambda体
         */
        public class LambdaDemo {
    
            @Test
            public void test1(){
                Runnable r = new Runnable() {
    
                    @Override
                    public void run() {
                        System.out.println("hello world");
                    }
                };
                r.run();
                System.out.println("------------");
    
                //Lambda表达式
                Runnable runnable = () -> System.out.println("hello Lambda");
                runnable.run();
            }
    
            @Test
            public void test2(){
                Consumer<String> consumer = new Consumer<String>() {
    
                    @Override
                    public void accept(String t) {
                        System.out.println(t);
                    }
                };
                consumer.accept("hello consumer !");
                System.out.println("---------------");
    
                //Lambda表达式
        //      Consumer<String> con = (t) -> System.out.println(t);
                Consumer<String> con = t -> System.out.println(t);
                con.accept("hello Lambda !");
            }
    
            @Test
            public void test3(){
                Comparator<Integer> comparator = (x,y) -> {
                    System.out.println("相加结果是:"+(x+y));
                    return Integer.compare(x,y);
                };
    
                int compare = comparator.compare(1, 2);
                System.out.println(compare);
            }
    
            @Test
            public void test4(){
                Comparator<Integer> comparator = (x,y) -> Integer.compare(x,y);
    
                int compare = comparator.compare(1, 2);
                System.out.println(compare);
            }
    
            @Test
            public void test5(){
                show(new HashMap<>());
            }
    
            public void show(Map<String,Integer> map){
    
            }
        }
    

    函数式接口

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

    Java 内置四大核心函数式接口

        /**
         * 函数式接口
         */
        public class TestFunction {
    
            //Consumer<T>消费型接口
            @Test
            public void test1(){
                cost(8888, (m) -> System.out.println("共消费:" + m + "元"));
            }
    
            public void cost(double money,Consumer<Double> con){
                con.accept(money);
            }
    
            //Supplier<T> 供给型接口
            @Test
            public void test2(){
                List<Integer> list = getNumList(8, () -> (int)(Math.random() * 100));
                for (Integer integer : list) {
                    System.out.println(integer);
                }
            }
    
            //产生指定数量的整数,放入集合中
            public List<Integer> getNumList(int num,Supplier<Integer> sup){
                List<Integer> list = new ArrayList<Integer>();
    
                for (int i = 0; i < num; i++) {
                    Integer n = sup.get();
                    list.add(n);
                }
    
                return list;
            }
    
            //Function<T,R> 函数型接口
            @Test
            public void test3(){
                String string = strHandler(" 函数型接口测试 ", (str) -> str.trim().substring(0, 5));
                System.out.println(string);
            }
    
            //用于处理字符串
            public String strHandler(String str,Function<String, String> fun){
                return fun.apply(str);
            }
    
            //Predicate<T> 断言型接口
            @Test
            public void test4(){
                List<String> list = Arrays.asList("hello","Lambda","ok");
                List<String> strList = filterStr(list, (s) -> s.length() > 3);
                for (String string : strList) {
                    System.out.println(string);
                }
            }
    
            //将满足条件的字符串,放入集合中
            public 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;
            }
        }
    

    其他接口

    方法引用与构造器引用

    方法引用

    当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)方法引用:使用操作符 “::” 将方法名和对象或类的名字分隔开来。

    如下三种主要使用情况:
    - 对象::实例方法
    - 类::静态方法
    - 类::实例方法

    使用注意事项:
    * 1.Lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致。
    * 2.若Lambda 参数列表中第一个参数是实例方法调用者,第二个参数是实例方法的参数 可以使用 ClassName :: method

    构造器引用

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

    数组引用

    格式: type[] :: new

        /**
         * 方法引用:若Lambda 体中的内容有方法已经实现了,我们可以使用"方法引用"
         */
        public class TestMethodRef {
    
            @Test
            public void test1(){
                Consumer<String> con = (x) -> System.out.println(x);
                con.accept("shuai");
    
                //方法引用,对象::实例方法名
                Consumer<String> consumer = System.out::println;
                consumer.accept("test");
            }
    
            @Test
            public void test2(){
                Person person = new Person();
                Supplier<String> supplier = () -> person.getName();
                String str = supplier.get();
                System.err.println(str);
    
                //方法引用,对象::实例方法名
                Supplier<Integer> sup = person::getAge;
                Integer age = sup.get();
                System.out.println(age);
            }
    
            //类::静态方法名
            @Test
            public void test3(){
                Comparator<Integer> com = (x,y) -> Integer.compare(x, y);
                //使用前提,compare的参数和返回值与Comparator一致
                Comparator<Integer> comparator = Integer :: compare;
            }
    
            //类::实例方法名
            @Test
            public void test4(){
                BiPredicate<String, String> bp = (x,y) -> x.equals(y);
                //使用条件:第一个参数是实例方法调用者,第二个参数是实例方法的参数
                BiPredicate<String, String> biPredicate = String :: equals;
            }
    
            //构造器引用
            @Test
            public void test5(){
                Supplier<Person> sup = () -> new Person();
    
                //构造器引用方式
                Supplier<Person> supplier = Person :: new;
                Person person = supplier.get();
                System.out.println(person);
            }
    
            //构造器引用
            @Test
            public void test6(){
                Function<Integer, Person> fun = (x) -> new Person(x);
    
                Function<Integer, Person> function = Person :: new;
                Person person = function.apply(2);
                System.out.println(person);
                System.out.println("--------------------");
    
                BiFunction<String, Integer, Person> biFunction = Person :: new;
                Person person2 = biFunction.apply("张三", 23);
                System.out.println(person2);
            }
    
            //数组引用
            @Test
            public void test7(){
                Function<Integer, String[]> fun = (x) -> new String[x]; 
                String[] strs = fun.apply(8);
                System.out.println(strs.length);
    
                Function<Integer, String[]> function = String[] :: new;
                String[] strArray = function.apply(6);
                System.out.println(strArray.length);
            }
        }
    

    Stream API

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

    Stream

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

    Stream 的操作三个步骤

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

    创建 Stream

    Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
    - default Stream stream() : 返回一个顺序流
    - default Stream parallelStream() : 返回一个并行流

    Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
    - static Stream stream(T[] array): 返回一个流

    可以使用静态方法 Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数。
    - public static Stream of(T… values) : 返回一个流

    可以使用静态方法 Stream.iterate() 和Stream.generate(), 创建无限流。
    - 迭代
    public static Stream iterate(final T seed, final UnaryOperator f)
    - 生成
    public static Stream generate(Supplier s) :

        //创建Stream
        @Test
        public void test1(){
            //1.可以通过Collection系列集合提供的stream() 或parallelStream()
            List<String> list = new ArrayList<>();
            Stream<String> stream = list.stream();
    
            //2.通过Arrays中静态方法 stream() 获取数组流
            Person[] persons = new Person[10];
            Stream<Person> stream2 = Arrays.stream(persons);
    
            //3.通过Stream类中的静态方法 of()
            Stream<String> stream3 = Stream.of("a","b","c");
    
            //4.创建无限流
            //迭代
            Stream<Integer> stream4 = Stream.iterate(0, (x) -> x + 2);
            stream4.limit(8).forEach(System.out :: println);
    
            //生成
            Stream.generate(() -> Math.random()).limit(6)
                .forEach(System.out :: println);
        }       
    

    Stream 的中间操作

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

    筛选与切片

    映射

    排序

        /**
         * Stream API的中间操作
         */
        public class TestSteamAPI2 {
    
            List<Person> persons = Arrays.asList(
                    new Person(2, "钱四", 24),
                    new Person(1, "张三", 33),
                    new Person(2, "李四", 24),
                    new Person(3, "王五", 65),
                    new Person(4, "赵六", 26),
                    new Person(4, "赵六", 26),
                    new Person(5, "陈七", 27)
            );
    
            //内部迭代,由Stream API完成
            @Test
            public void test1(){
                //中间操作,不会执行任何操作
                Stream<Person> stream = persons.stream()
                                            .filter((e) -> {
                                                System.out.println("Stream的中间操作");
                                                return e.getAge() > 25;
                                            });
                //终止操作,一次性执行全部内容,即"惰性求值"
                stream.forEach(System.out :: println);
            }
    
            //外部迭代
            @Test
            public void test2(){
                Iterator<Person> iterator = persons.iterator();
                while (iterator.hasNext()) {
                    System.out.println(iterator.next());
                }
            }
    
            //limit,截断
            @Test
            public void test3(){
                persons.stream()
                    .filter((e) -> {
                        System.out.println("迭代操作"); //短路
                        return e.getAge() > 24;
                    })
                    .limit(2)
                    .forEach(System.out :: println);
            }
    
            //跳过skip,distinct去重(要重写equals和hashcode)
            @Test
            public void test4(){
                persons.stream()
                        .filter((e) -> e.getAge() > 23)
                        .skip(2)
                        .distinct()
                        .forEach(System.out :: println);
            }
    
            //映射
            @Test
            public void test5(){
                List<String> list = Arrays.asList("a","bb","c","d","e");
    
                list.stream().map((str) -> str.toUpperCase())
                    .forEach(System.out :: println);
                System.out.println("---------------");
    
                persons.stream().map((Person :: getName)).forEach(System.out :: println);
                System.out.println("---------------");
    
                Stream<Stream<Character>> stream = list.stream()
                    .map(TestSteamAPI2 :: filterCharacter);
    
                stream.forEach((s) -> {
                    s.forEach(System.out :: println);
                });
                System.out.println("-----------------");
    
                //flatMap
                Stream<Character> stream2 = list.stream()
                    .flatMap(TestSteamAPI2 :: filterCharacter);
                stream2.forEach(System.out :: println);
            }
    
            //处理字符串
            public static Stream<Character> filterCharacter(String str){
                List<Character> list = new ArrayList<>();
    
                for (Character character : str.toCharArray()) {
                    list.add(character);
                }
                return list.stream();
            }
    
            //排序
            @Test
            public void test6(){
                List<String> list = Arrays.asList("bb","c","aa","ee","ddd");
    
                list.stream()
                    .sorted() //自然排序
                    .forEach(System.out :: println);
                System.out.println("------------");
    
                persons.stream()
                        .sorted((p1,p2) -> {
                            if (p1.getAge() == p2.getAge()) {
                                return p1.getName().compareTo(p2.getName());
                            } else {
                                return p1.getAge() - p2.getAge();
                            }
                        }).forEach(System.out :: println);
    
            }
    
        }
    

    Stream 的终止操作

    终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。

    查找与匹配

    归约



    map 和 reduce 的连接通常称为 map-reduce 模式。

    收集


    Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例。

        /**
         * Stream API的终止操作
         */
        public class TestSteamAPI3 {
    
            List<Person> persons = Arrays.asList(
                    new Person(2, "钱四", 24,Status.YOUNG),
                    new Person(1, "张三", 23,Status.YOUNG),
                    new Person(2, "李四", 24,Status.YOUNG),
                    new Person(3, "王五", 65,Status.OLD),
                    new Person(4, "赵六", 26,Status.MIDDLE),
                    new Person(4, "赵六", 56,Status.OLD),
                    new Person(5, "陈七", 27,Status.MIDDLE)
            );
    
            //查找与匹配
            @Test
            public void test1(){
                boolean b = persons.stream()
                                    .allMatch((e) -> e.getStatus().equals(Status.YOUNG));
                System.out.println(b);
    
                boolean b2 = persons.stream()
                        .anyMatch((e) -> e.getStatus().equals(Status.YOUNG));
                System.out.println(b2);
    
                boolean b3 = persons.stream()
                        .noneMatch((e) -> e.getStatus().equals(Status.MIDDLE));
                System.out.println(b3);
    
                Optional<Person> op = persons.stream()
                        .sorted((e1,e2) -> Integer.compare(e1.getAge(), e2.getAge()))
                        .findFirst();
                System.out.println(op.get());
    
                Optional<Person> optional = persons.stream()
                        .filter((e) -> e.getStatus().equals(Status.OLD))
                        .findAny();
                System.out.println(optional.get());
            }
    
            //最大,最小
            @Test
            public void test2(){
                long count = persons.stream()
                        .count();
                System.out.println(count);
    
                Optional<Person> optional = persons.stream()
                        .max((e1,e2) -> Integer.compare(e1.getId(), e2.getId()));
                System.out.println(optional.get());
    
                Optional<Integer> op = persons.stream()
                        .map(Person :: getAge)
                        .min(Integer :: compare);
                System.out.println(op.get());
            }
    
            //归约
            @Test
            public void test3(){
                List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8);
    
                Integer sum = list.stream()
                    .reduce(0, (x,y) -> x + y);
                System.out.println(sum);
                System.out.println("------------");
    
                Optional<Integer> optional = persons.stream()
                        .map(Person :: getAge)
                        .reduce(Integer :: sum);
                System.out.println(optional.get());
            }
    
            //收集
            @Test
            public void test4(){
                List<String> list = persons.stream()
                        .map(Person :: getName)
                        .collect(Collectors.toList());
                list.forEach(System.out :: println);
                System.out.println("------------");
    
                Set<String> set = persons.stream()
                        .map(Person :: getName)
                        .collect(Collectors.toSet());
                set.forEach(System.out :: println);
                System.out.println("------------");
    
                HashSet<String> hashSet = persons.stream()
                        .map(Person :: getName)
                        .collect(Collectors.toCollection(HashSet :: new));
                hashSet.forEach(System.out :: println);
            }
    
            @Test
            public void test5(){
                Long count = persons.stream()
                        .collect(Collectors.counting());
                System.out.println("总人数="+count);
                System.out.println("----------------");
    
                //平均值
                Double avg = persons.stream()
                        .collect(Collectors.averagingInt(Person :: getAge));
                System.out.println("平均年龄="+avg);
                System.out.println("---------------");
    
                //总和
                Integer sum = persons.stream()
                        .collect(Collectors.summingInt(Person :: getAge));
                System.out.println("年龄总和="+sum);
                System.out.println("----------------");
    
                //最大值
                Optional<Person> max = persons.stream()
                        .collect(Collectors.maxBy((e1,e2) -> Integer.compare(e1.getAge(), e2.getAge())));
                System.out.println("最大年龄是"+max.get());
                System.out.println("----------------");
    
                //最小值
                Optional<Person> min = persons.stream()
                        .collect(Collectors.minBy((e1,e2) -> Integer.compare(e1.getAge(), e2.getAge())));
                System.out.println("最小年龄是"+min.get());
            }
    
            //分组
            @Test
            public void test6(){
                Map<Status, List<Person>> map = persons.stream()
                        .collect(Collectors.groupingBy(Person :: getStatus));//根据年龄层分组
                System.out.println(map);
            }
    
            //多级分组
            @Test
            public void test7(){
                Map<Status, Map<String, List<Person>>> map = persons.stream()
                        .collect(Collectors.groupingBy(Person :: getStatus ,Collectors.groupingBy((e) -> {
                            if (e.getId()%2 == 1) {
                                return "单号";
                            } else {
                                return "双号";
                            } 
                        })));
                System.out.println(map);
            }
    
            //分区
            @Test
            public void test8(){
                Map<Boolean, List<Person>> map = persons.stream()
                        .collect(Collectors.partitioningBy((e) -> e.getAge() > 30));
                System.out.println(map);
            }
    
            //IntSummaryStatistics
            @Test
            public void test9(){
                IntSummaryStatistics iss = persons.stream()
                        .collect(Collectors.summarizingInt(Person :: getAge));
                System.out.println(iss.getSum());
                System.out.println(iss.getAverage());
                System.out.println(iss.getMax());
            }
    
            @Test
            public void test10(){
                String str = persons.stream()
                        .map(Person :: getName)
                        .collect(Collectors.joining(",","人员名单:","等"));
                System.out.println(str);
            }
        }
    

    并行流与串行流

    并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
    Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换。

        /**
         * FrokJoin框架
         */
        public class ForkJoinCalculate extends RecursiveTask<Long>{
            private static final long serialVersionUID = 1L;
    
            private long start;
            private long end;
    
            private static final long THRESHOLD = 10000;
    
            public ForkJoinCalculate() {
    
            }
    
            public ForkJoinCalculate(long start, long end) {
                this.start = start;
                this.end = end;
            }
    
    
            @Override
            protected Long compute() {
                long length = end - start ;
                if (length <= THRESHOLD) {
                    long sum = 0;
                    for (long i = start; i <= end; i++) {
                        sum += i;
                    }
                    return sum;
                }else {
                    long middle = (start + end) / 2; 
                    ForkJoinCalculate left = new ForkJoinCalculate();
                    left.fork();//拆分子任务,同时压入线程队列
    
                    ForkJoinCalculate right = new ForkJoinCalculate();
                    right.fork();
                    return left.join() + right.join();
                }
            }
    
        }
    

    测试并行流

        public class TestForkJoin {
    
            /**
             * FrokJoin框架
             */
            @Test
            public void test1(){
                Instant start = Instant.now();
    
                ForkJoinPool pool = new ForkJoinPool();
                ForkJoinTask<Long> task = new ForkJoinCalculate(0,10000000000L);
                Long sum = pool.invoke(task);
                System.out.println(sum);
    
                Instant end = Instant.now();
                System.out.println(Duration.between(start,end).toMillis());
            }
    
            /**
             * for循环
             */
            @Test
            public void test2(){
                Instant start = Instant.now();
                long sum = 0L;
    
                for (long i = 0; i <= 10000000000L; i++) {
                    sum += i;
                }
                System.out.println(sum);
    
                Instant end = Instant.now();
                System.out.println(Duration.between(start, end).toMillis());
            }
    
            /**
             * Java8并行流
             */
            @Test
            public void test3(){
                Instant start = Instant.now();
                LongStream.rangeClosed(0, 10000000000L)
                            .parallel()
                            .reduce(0,Long :: sum);
                Instant end = Instant.now();
                System.out.println(Duration.between(start, end).toMillis());
            }
        }
    

    接口中的默认方法与静态方法

    接口中的默认方法

    Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default 关键字修饰。
    接口默认方法的”类优先”原则
    若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时
    - 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
    - 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突。

    接口中的静态方法

    Java8 中,接口中允许添加静态方法。

        public interface MyInterface {
    
            default String getName(){
                return "接口测试";
            }
    
            public static void show(){
                System.out.println("接口中的静态方法");
            }
        }
    

    新时间日期 API

    LocalDate、LocalTime、LocalDateTime

    LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。

    传统写法:

        public class DateFormatThreadLocal {
    
            private static final ThreadLocal<DateFormat> tl = new ThreadLocal<DateFormat>(){
                protected DateFormat initialValue() {
                    return new SimpleDateFormat("yyyyMMdd");
                }
            };
    
            public static Date convert(String source) throws ParseException{
                return tl.get().parse(source);
            }
        }       
    
        public static void main(String[] args) throws InterruptedException, ExecutionException {
    //      SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    
            Callable<Date> callable = new Callable<Date>() {
    
                @Override
                public Date call() throws Exception {
    //              return sdf.parse("20170521");
                    return DateFormatThreadLocal.convert("20170521");
                }
    
            };
            ExecutorService pool = Executors.newFixedThreadPool(10);
            List<Future<Date>> results = new ArrayList<>();
            for (int i = 0; i < 8; i++) {
                results.add(pool.submit(callable));
            }
            for (Future<Date> future : results) {
                System.out.println(future.get());
            }
    
            //关闭资源
            pool.shutdown();
        }
    

    新特性:

        public static void main(String[] args) throws InterruptedException, ExecutionException {
            DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");
            Callable<LocalDate> callable = new Callable<LocalDate>() {
    
                @Override
                public LocalDate call() throws Exception {
                    return LocalDate.parse("20170521",dtf);
                }
    
            };
            ExecutorService pool = Executors.newFixedThreadPool(10);
            List<Future<LocalDate>> results = new ArrayList<>();
            for (int i = 0; i < 8; i++) {
                results.add(pool.submit(callable));
            }
            for (Future<LocalDate> future : results) {
                System.out.println(future.get());
            }
    
            //关闭资源
            pool.shutdown();
    }
    

    LocalDate LocalTime LocalDateTime

        //1.LocalDate LocalTime LocalDateTime
        @Test
        public void test1(){
            LocalDateTime ldt = LocalDateTime.now();
            System.out.println(ldt);
    
            LocalDateTime ldt2 = LocalDateTime.of(2017, 05, 21, 21, 43, 55, 33);
            System.out.println(ldt2);
    
            LocalDateTime ldt3 = ldt.plusYears(3);
            System.out.println(ldt3);
    
            LocalDateTime ldt4 = ldt.minusMonths(5);
            System.out.println(ldt4);
        }
    

    Instant 时间戳

    用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的描述进行运算。

        //2.Instant
        @Test
        public void test2(){
            Instant now = Instant.now();
            System.out.println(now);
    
            OffsetDateTime atOffset = now.atOffset(ZoneOffset.ofHours(6));
            System.out.println(atOffset);
    
            Instant ins = Instant.ofEpochSecond(60);
            System.out.println(ins);
        }
    

    Duration 和 Period

    • Duration:用于计算两个“时间”间隔
    • Period:用于计算两个“日期”间隔

      //3.Duration 
      @Test
      public void test3(){
          Instant now = Instant.now();
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          Instant now2 = Instant.now();
          //计算时间差
          Duration duration = Duration.between(now, now2);
          System.out.println(duration.getSeconds());
          System.out.println("----------------");
      
          LocalTime lt1 = LocalTime.now();
          try {
              Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          LocalTime lt2 = LocalTime.now();
          System.out.println(Duration.between(lt1, lt2).toMillis());
      }
      
      //4.Period
      @Test
      public void test4(){
          LocalDate ld1 = LocalDate.of(2017, 1, 1);
          LocalDate ld2 = LocalDate.now();
          Period period = Period.between(ld1, ld2);
          System.out.println(period.getYears());
          System.out.println(period.getMonths());
          System.out.println(period.getDays());
      }
      

    日期的操作

    • TemporalAdjuster : 时间校正器。有时我们可能需要获取例如:将日期调整到“下个周日”等操作。
    • TemporalAdjusters : 该类通过静态方法提供了大量的常用 TemporalAdjuster 的实现。

      //5.TemporalAdjuster:时间校正器
      @Test
      public void test5(){
          LocalDateTime ldt = LocalDateTime.now();
          System.out.println(ldt);
      
          LocalDateTime ldt2 = ldt.withDayOfMonth(8);
          System.out.println(ldt2);
      
          LocalDateTime ldt3 = ldt.with(TemporalAdjusters.next(DayOfWeek.SATURDAY));
          System.out.println(ldt3);
      
          //自定义
          LocalDateTime ldt5 =  ldt.with((l) -> {
              LocalDateTime ldt4 = (LocalDateTime) l;
              DayOfWeek dow = ldt4.getDayOfWeek();
              if (dow.equals(DayOfWeek.FRIDAY)) {
                  return ldt4.plusDays(3);
              }else if (dow.equals(DayOfWeek.SATURDAY)) {
                  return ldt4.plusDays(2);
              } else {
                  return ldt4.plusDays(1);
              }
          });
          //下个工作日
          System.out.println(ldt5);
      }
      

    解析与格式化

    java.time.format.DateTimeFormatter 类
    该类提供了三种格式化方法:
    - 预定义的标准格式
    - 语言环境相关的格式
    - 自定义的格式

        //DateTimeFormatter:格式化时间/日期
        @Test
        public void test6(){
            DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE_TIME;
            LocalDateTime ldt = LocalDateTime.now();
    
            System.out.println(ldt);
            String format = ldt.format(dtf);
            System.out.println(format);
            System.out.println("------------");
            DateTimeFormatter dtf2 = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
            String format2 = dtf2.format(ldt);
            System.out.println(format2);
    
            LocalDateTime ldt2 = ldt.parse(format2,dtf2);
            System.out.println(ldt2);
        }
    

    时区的处理

    Java8 中加入了对时区的支持,带时区的时间为分别为:
    ZonedDate、ZonedTime、ZonedDateTime
    其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式
    例如 :Asia/Shanghai 等
    ZoneId:该类中包含了所有的时区信息
    - getAvailableZoneIds() : 可以获取所有时区时区信息
    - of(id) : 用指定的时区信息获取 ZoneId 对象

        //ZonedDate ZoneTime ZoneDateTime
        @Test
        public void test7(){
            LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
            System.out.println(ldt);
    
            LocalDateTime ldt2 = LocalDateTime.now(ZoneId.of("Europe/Tallinn"));
            ZonedDateTime zdt = ldt2.atZone(ZoneId.of("Asia/Shanghai"));
            System.out.println(zdt);
        }
    

    与传统日期处理的转换

    其他新特性

    HashMap

    HashMap:减少碰撞,位置相同时,条件达到链表上超过8个,总数超过64个时,数据结构改为红黑树。

    ConcurrentHashMap:取消锁分段,与HashMap相同,达到条件时,数据结构改为红黑树。

    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

        /**
         * Optional类
         */
        public class TestOptional {
    
            @Test
            public void test1(){
                //参数不能为空
                Optional<Person> op = Optional.of(new Person());
    
                Person person = op.get();
                System.out.println(person);
            }
    
            @Test
            public void test2(){
                //构建空optional
                Optional<Person> op = Optional.empty();
                System.out.println(op.get());
            }
    
            @Test
            public void test3(){
                //如果为null,调用empty,如果不为null,调用of
                Optional<Person> op = Optional.ofNullable(null);
        //      Optional<Person> op = Optional.ofNullable(new Person());
                if (op.isPresent()) {
                    System.out.println(op.get());
                }
    
                //有值就用值,没值就替代
                Person person = op.orElse(new Person("张三", 23));
                System.out.println(person);
            }
        }
    

    重复注解与类型注解

    Java 8对注解处理提供了两点改进:可重复的注解及可用于类型的注解。

        @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface MyAnnotations {
    
            MyAnnotation[] value();
        }
    
        @Repeatable(MyAnnotations.class)
        @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, ElementType.TYPE_PARAMETER})
        @Retention(RetentionPolicy.RUNTIME)
        public @interface MyAnnotation {
    
            String value() default "aric";
        }
    
        /**
         * 重复注解与类型注解
         */
        public class TestAnnotation {
    
            @MyAnnotation("hello")
            @MyAnnotation("test")
            public void show(@MyAnnotation("a") String str){
                System.out.println(str);
            }
        }
    
    展开全文
  • Java8特性学习

    2018-06-05 18:32:23
    Java8中允许接口中包含具有具体实现的方法,这种方法被称为“默认方法”,使用default关键字修饰。 如: public interface MyInterface { String notDefault(); default String testDefault() { return &...

    1、接口中的默认方法

    Java8中允许接口中包含具有具体实现的方法,这种方法被称为“默认方法”,使用default关键字修饰。
    如:

    public interface MyInterface {
    
        String notDefault();
    
        default String testDefault() {
            return "Hello Default Method";
        }
    }

    该接口的实现类中只需要实现抽象方法即可,默认方法可以直接使用。

    MyInterface myInterface = new MyInterface() {
        @Override
        public String notDefault() {
            return "这不是一个default方法,实现类需要重写这个方法";
        }
    };
    
    System.out.println(myInterface.notDefault());
    System.out.println(myInterface.testDefault());

    接口中默认方法的“类优先”原则:

    若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时

    • 选择父类中的方法。如果一个父类中提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。 测试代码:
    public  class MySuperClazz {
    
        public String testDefault() {
            return "This is default method";
        }
    }
    
    public interface MyInterface {
    
        default String testDefault() {
            return "Hello Default Method";
        }
    }
    
    public class MyClazz extends MySuperClazz implements MyInterface{
    
    }
    MyClazz myClazz = new MyClazz();
    System.out.println(myClazz.testDefault());

    输出结果:

    This is default method
    • 接口冲突,如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么实现类必须覆盖该方法来解决冲突。

    测试代码:

    public class MyClazz /*extends MySuperClazz */implements MyInterface,MyInterface2{
    
        @Override
        public String testDefault() {
            return "测试两个接口中同名方法";
        }
    }
    
    public interface MyInterface2 {
    
        default String testDefault() {
            return "Hello Default Method2";
        }
    }
    
    public interface MyInterface {
    
        default String testDefault() {
            return "Hello Default Method";
        }
    }

    测试结果:

    测试两个接口中同名方法

    Java8的接口中也可以声明静态方法

    public interface MyInterface {
    
        static void testStatic() {
            System.out.println("静态方法");
        }
    }

    2、Lambda表达式

    上面提到的代码

    MyInterface myInterface = new MyInterface() {
        @Override
        public String notDefault() {
            return "这不是一个default方法,实现类需要重写这个方法";
        }
    };

    可以通过Lambda表达式改写成如下方式

    MyInterface myInterface = () -> "这不是一个default方法,实现类需要重写这个方法";

    这种形式,便是java8提供的更加简洁的方式,即Lambda表达式。相比于jdk1.7之前的代码,使用Lambda表达式更加的简短易读。

    Lambda表达式是一种“语法糖”,需要函数式接口的支持。
    Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器可以通过上下文推断出数据类型,即:类型推断。Java的编译器能够自动识别参数的类型,所以可以省略类型不写。

    Lambda表达式可以分为两部分:
    左侧:指定了Lambda表达式需要的所有参数
    右侧:指定了Lambda体,即lambda表达式要执行的功能

    Lambda表达式有以下几种格式:

    • 语法格式一:无参数,无返回值
    格式:
        () -> System.out.println("Hello Lambda");
    例:
        Runnable r1 = () -> System.out.println("Hello Lambda");
    • 语法格式二:有一个参数,并且无返回值
    格式:
         (x) -> System.out.println(x)
         只有一个参数小括号可以不写
         x -> System.out.println(x)
    例:
        Consumer<String> con = (x) -> System.out.println(x);
        Consumer<String> con2 = x -> System.out.println(x);
        con.accept("zhw");
        con2.accept("nb");
    • 语法格式三:有两个参数以上,并且lambda体中有多条语句,并且有返回值,如果有多条语句必须使用{}
    例:
        Comparator<Integer> com = (x,y) -> {
            System.out.println("语法格式三");
            return Integer.compare(x,y);
        };
    • 语法格式四:lambda体重只有一条语句,并有返回值,大括号可以省略,return可以省略
    Comparator<Integer> com = (x,y) -> Integer.compare(x,y);

    Lambda表达式的范围:

    对于lambdab表达式外部的变量,其访问权限的粒度与匿名对象的方式非常类似。
    你能够访问局部对应的外部区域的局部final变量,以及成员变量和静态变量。
    注意:final关键字可以省略,但是编译的时候依然被隐式的当成final变量来处理。

    方法引用:

    若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”,(可以理解为方法引用是Lambda表达式的另一种表现形式)

    注意:
    1、 Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的参数列表和返回值类型保持一致
    2、若lambda参数列表中的第一个参数是 实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method的形式

    方法引用的三种语法格式:

    • 对象::实例方法名

    • 类::静态方法名

    • 类::实例方法名

    构造器引用

    格式:

    • ClassName::new

    注意:
    需要调用的构造器的参数列表要与函数式接口中抽象方法的参数保持一致

    3、函数式接口

    上文提到的Lambda表达式需要函数式接口的支持那什么是函数式接口呢?所谓的函数式接口就是必须有且仅有一个抽象方法声明的接口,Lambda表达式必须要与抽象方法的声明相匹配,由于默认方法不是抽象的,所以可以在函数式接口中任意添加默认方法。

    定义函数式接口的时候可以使用@FunctionalInterface注解,编译器会判断定义的接口是否满足函数是接口的条件,如果不满足,编译器会抛出异常。

    @FunctionalInterface
    public interface MyFunction<T,R> {
    
        R get(T t1,T t2);
    }

    Java8内置的函数式接口:

    Consumers

    Consumer<T> : 消费型接口,Consumer代表了在一个输入参数上需要进行的操作。
        void accept(T t); 
    
    Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
    greeter.accept(new Person("Luke", "Skywalker"));

    Suppliers

    Supplier<T>  : 供给型接口,口产生一个给定类型的结果。与Function不同的是,Supplier没有输入参数。
    
    Supplier<Person> personSupplier = Person::new;
    personSupplier.get(); // new Person

    Functions

    Function<T,R>  : 函数型接口,接受一个参数(T),并返回单一的结果(R)。默认方法可以将多个函数串在一起(compse, andThen)。
    
    Function<String, Integer> toInteger = Integer::valueOf;
    Function<String, String> backToString = toInteger.andThen(String::valueOf);
    backToString.apply("123"); // "123"

    Predicates

    Predicate<T>  : 段言型接口,是一个布尔类型的函数,该函数只有一个参数。Predicate接口包含了多种默认方法,用于处理复杂的逻辑动词(and, or,negate)
    
    Predicate<String> predicate = (s) -> s.length() > 0;
    predicate.test("foo"); // true
    predicate.negate().test("foo"); // false
    Predicate<Boolean> nonNull = Objects::nonNull;
    Predicate<Boolean> isNull = Objects::isNull;
    Predicate<String> isEmpty = String::isEmpty;
    Predicate<String> isNotEmpty = isEmpty.negate();

    4、Stream 数据流

    数据流表示元素的序列,并支持不同种类的操作来执行元素上的计算。数据流是单体(Monad),并且在Java8函数式编程中起到重要作用。

    流(Stream)到底是什么:是数据渠道,用于操作数据源(集合,数组等)所生成的元素序列“集合讲的是数据,流讲的是计算”。

    注意:

    1. Stream自己不会存储元素
    2. Stream不会改变源对象。相反,他们会返回一个持有结果的新的Stream
    3. Stream操作是延迟执行的。这意味着他们会等到需要结果的时候才会执行。

    Stream操作的三个步骤:

    • 创建数据流
    • 衔接操作
    • 终止操作

    Stream数据流的操作过程图:
    这里写图片描述

    数据流操作要么是衔接操作(创建过程也可以看成衔接操作),要么是终止操作,数据流的操作大多数是无状态的而且是无干扰的,即当一个函数不修改数据流的底层数据源时,它就是无干扰的;当一个函数的操作不依赖于外部作用域中的任何操作过程中的可变的变量或状态时,它就是无状态的。

    创建数据流:

    创建Stream的五种方式:
    1、 可以通过Collection系列集合中的stream()或者parallelStream()方法获取串行流或者并行流。

    List<String> list = new ArrayList<>();
    Stream<String> stream = list.stream();

    2、 通过Arrays中的静态方法stream()方法获取数组流

     Arrays.asList("a1", "a2", "a3").stream();

    3、 通过Stream类中的静态方法of()方法获取流,可以从一系列对象引用中创建数据流

    Stream<String> stream = Stream.of(“aa”,”bb”,”cc”);

    4、 创建无限流

    //迭代
    Stream<Integer> stream = Stream.iterate(0,(x) -> x+ 2);
    Stream.limit(10).forEach(System.out::println)
    //生成
    Stream.generate(()->Math.random()).forEach(System.out::println);

    5、java8提供了一些特殊种类的流,用于处理基本数据类型int,long,double。分别为IntStream,LongStream,DoubleStream

    前四种方式创建的数据流称为“对象数据流”,第五种方式创建的流称为“基本数据流”,基本数据流和对象数据流用法相似,但有一些不同,基本数据流使用的是特殊的lambda表达式,如:IntFunction而不是Function,IntPredicate而不是Predicate。同时基本数据流还支持一些额外的聚合终止操作sum()和average()等。有些时候需要进行对象数据流和基本数据流的转换。转换方式如下:

    对象数据流转换成基本数据流:

    mapToInt() 、 mapToLong() 和mapToDouble()
    
    例:
    Stream.of("a1", "a2", "a3")
            .map(s -> s.substring(1))
            .mapToInt(Integer::parseInt)
            .max()
            .ifPresent(System.out::println); 

    基本数据流转换成对象数据流:

    mapToObj()
    
    例:
    IntStream.range(1, 4)
            .mapToObj(i -> "a" + i)
            .forEach(System.out::println);

    衔接操作:

    多个衔接操作可以连接起来形成 一个流水线,除非流水线上出发终止操作,否则衔接操作不会执行任何处理,而在终止操作是一次性全部处理,称为“惰性求值”或“延迟执行”,延迟性是衔接操作的一个重要特性。

    例:
    Stream.of("d2", "a2", "b1", "b3", "c")
            .filter(s -> {
                System.out.println("filter: " + s);
                return true;
            });

    上面这段代码并不会向控制台打印任何东西,因为并没有终止操作,衔接操作只有在终止操作调用时才会被执行。

    衔接操作主要有以下几种方式:

    筛选与切片

    filter—接收Lambda,从六中排除某些元素
    limit—截断流,使其元素不超过给定数量
    skip(n)—跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit互补
    distinct—筛选,通过流所生成元素的hashCode()equals()去除重复元素
    
    内部迭代:迭代操作有Stream API完成
    外部迭代:自己编写迭代操作

    映射

    map—接收Lambda,将元素转换成其他形式或提取信息。接收一个函数操作参数,该函数会应用到每个元素上,并将其映射成一个新的元素。
    
    flatMap—接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流链接成一个流

    排序

    sorted()—自然排序
    sorted(Comparator com)—定制排序

    终止操作:

    在数据流中,只有执行了终止操作时,衔接操作才会一次性执行。终止操作主要有以下几种方式:

    查找与匹配

    allMatch—检查是否匹配所有元素
    anyMatch—检查是否至少匹配一个元素
    noneMatch—检查是否没有匹配所有元素
    findFirst—返回当前流中的第一个元素
    findAny—返回流中元素的任意元素
    count—返回流中元素的总个数
    max—返回流中的最大值
    min—返回流中的最小值

    归约

    ruduce(T identity,BinaryOperator) / ruduce(BinaryOperator) –可以将流中元素反复结合起来,组合为单一结果。

    收集

    collect—将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法,将流中的元素存放在不同类型的结果中。Java8通过内置的 Collectors 类支持多种内置的收集器。
    
    List<Person> filtered = persons.stream()
                                    .filter(p -> p.name.startsWith("P"))
                                    .collect(Collectors.toList());
    System.out.println(filtered); // [Peter, Pamela]

    5、并行流与串行流

    串行流:在一个线程上执行一个内容

    Arrays.asList("a1", "a2", "a3")
            .stream()
            .findFirst()
            .ifPresent(System.out::println);

    并行流:就是把一个内容分成多个数据快,并用不同的线程分别处理每个数据块的流。并行流能够在多个线程上执行操作。主要是为了解决处理大量元素时的性能问题。

    Arrays.asList("a1", "a2", "a3")
            .stream()
            .findFirst()
            .parallel()
            .ifPresent(System.out::println);
    --------------------------------------------------------------------
    Arrays.asList("a1", "a2", "a3")
            .parallelStream()
            .findFirst()
            .ifPresent(System.out::println);

    Stream API 可以声明性的通过parallel()与sequential()在并行流与串行流之间进行切换。
    在一个串行流中使用parallel()切换为并行流,在一个并行流中使用sequential()切换为串行流。

    Java8对并行流有了更好的支持
    并行流使用公共的 ForkJoinPool ,由 ForkJoinPool.commonPool() 方法提供。底层线程池的大小最大为五个线程 – 取决于CPU的物理核数。

    Fork/Join框架:在必要的情况下,讲一个大任务,进行拆分(fork)成若干个小任务(拆分到不可拆分时),再将一个个小人物运算的结果进行汇总(join)。
    这里写图片描述

    采用“工作窃取”算法(work-stealing),Java8之前这种框架使用的并不多,主要是因为这种方法实现起来过于麻烦,而Java8将实现的方法变得非常简单。

    “工作窃取”算法:算法是指某个线程从其他队列里窃取任务来执行

    对于常见的一个大型任务,我们可以把这个大的任务切割成很多个小任务,然后这些小任务会放在不同的队列中,每一个队列都有一个相应的的工作执行线程来执行,当一个线程所需要执行的队列中,任务执行完之后,这个线程就会被闲置,为了提高线程的利用率,这些空闲的线程可以从其他的任务队列中窃取一些任务,来避免使自身资源浪费,这种在自己线程闲置,同时窃取其他任务队列中任务执行的算法,就是工作窃取算法

    6、Optional类

    Optional类是一个容器类,代表一个值存在或不存在,可以避免空指针异常。Optional不是一个函数式接口,而是一个精巧的工具接口,用来方式NullPointException异常。Optional是一个简单的值容器,这个值可以是null,也可以是non-null,为了不直接返回null,我们在Java 8中就返回一个Optional类,使用Optional类可以避免使用if进行null检查,这种检查的过程交由底层自动处理。主要有如下一些方法:

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

    7、JAVA8全新的时间包

    Java 8 包含了全新的时间日期API,这些功能都放在了java.time包下。主要有如下一些方法:

    LocalDate,LocalTime,LocalDateTime : 时间
    Instant : 时间戳(以Unix元年:19701月一日 00:00:00到某个时间时间的毫秒值)
    Duration : 计算两个“时间”之间的间隔
    Period : 计算两个“日期”之间的间隔
    TemporalAdjust :时间校正器
    DateTimeFormatter :格式化时间/日期
    ZonedDate,ZonedTime,ZonedDateTime : 时区
    展开全文
  • 基于此,接下来将相对系统的去学习Java8的一些新特性,也算是用自燃的方式来点燃各位想要掌握新知识的同学,一起进步! 新特性 Oracle在2014年就发布了Java8Java8Java语言的一个重要的版本。Java8支持...

    背景

    随着工作的深入,用到的知识是越来越多,代码中用到Java8的特性也越来越多,如果只是知道几个简单的用法而不从本质上去知道其背景、实现原理,理解代码是一件相对困难的事情。基于此,接下来将相对系统的去学习Java8的一些新特性,也算是用自燃的方式来点燃各位想要掌握新知识的同学,一起进步!

    新特性

    Oracle在2014年就发布了Java8,Java8是Java语言的一个重要的版本。Java8支持函数式编程,新特性主要包括Lambda表达式,Optional、Stream、Date Time API,接口默认方法等。接下来两周时间将逐一分析。

    举例说明演变过程

    函数式编程
    其实Java语言本身就支持函数式编程,只是我们平常的编写中没有这个意识。举例有个Student类

    • 非函数式编程写法
    public class Student {
        private String name;
        private Integer age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
        
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    • 非函数式编程设置方式
    	Student student = new Student();
        student.setName("codingtu");
        student.setAge(18);
        System.out.println(student.toString());
    
    • 函数式编程
    public class Student {
        private String name;
        private Integer age;
    
        public String getName() {
            return name;
        }
    
        public Student setName(String name) {
            this.name = name;
            return this;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public Student setAge(Integer age) {
            this.age = age;
            return this;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    • 函数式编程设置方式
    System.out.println(new Student().setName("codingtu").setAge(18).toString());
    

    Lambda表达式演变
    下面使用集合排序来演示java7->java8写法的变化过程。假设有如下集合。

    List<Integer> integerList = new ArrayList<>();
    integerList.add(3);
    integerList.add(1);
    integerList.add(2);
    
    • Java7写法
            integerList.sort(new Comparator<Integer>() {
    
                @Override
                public int compare(Integer o1, Integer o2) {
                    return o1 - o2;
                }
            });
    

    由于List类提供的sort方法传递的是一个Comparator参数,而Comparator是一个接口,所以我们需要创建接口的实现类,如上,并实现接口的方法compare,这就是典型的java7的写法。

    • Java8写法
      上面,其实我们明白正在的逻辑其实就只有o1-o2。把上面简化为:
            integerList.sort((Integer o1, Integer o2) -> {
                return o1 - o2;
            });
    

    可以看到前面的(Integer o1, Integer o2)类似于方法的参数,中间一个“->”符号,连接后面的代码块是具体实现,由于已经知道o1和o2的类型,所以代码块直接放回逻辑部分“return o1 - o2;”。下面继续简化:

            integerList.sort((o1, o2) -> {
                return o1-o2;
            });
    

    因为我们知道integerList的类型,所以Integer算是多余的,可将其去掉。观察java8中Comparator的实现的方法,可直接使用如下方式完成排序编码。

    integerList.sort(Comparator.comparing(Integer::intValue));
    

    总结

    上面大体先简单介绍一下java8的一些演变方式,可以看到的是我们的代码更加的简洁,而且只要稍微懂一点阅读方式,代码并不难理解。从上面可以看出,lambda表达式其实就是将接口的实现方式变得简洁更简洁,而函数式编程则将方法连起来,避免多行代码不易读。欢迎留言与我讨论!

    展开全文
  • JAVA8 十大新特性详解

    2018-06-28 15:44:25
    前言: Java 8 已经发布很...在Java Code Geeks上已经有很多介绍Java 8特性的文章,例如Playing with Java 8 – Lambdas and Concurrency、Java 8 Date Time API Tutorial : LocalDateTime和Abstract Class Versus

    前言: Java 8 已经发布很久了,很多报道表明Java 8 是一次重大的版本升级。在Java Code Geeks上已经有很多介绍Java 8新特性的文章,例如Playing with Java 8 – Lambdas and ConcurrencyJava 8 Date Time API Tutorial : LocalDateTimeAbstract Class Versus Interface in the JDK 8 Era。本文还参考了一些其他资料,例如:15 Must Read Java 8 TutorialsThe Dark Side of Java 8。本文综合了上述资料,整理成一份关于Java 8新特性的参考教材,希望你有所收获。

    1. 简介

    毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。在本文中我们将学习这些新特性,并用实际的例子说明在什么场景下适合使用。

    这个教程包含Java开发者经常面对的几类问题:

    • 语言
    • 编译器
    • 工具
    • 运行时(JVM)

    2. Java语言的新特性

    Java 8是Java的一个重大版本,有人认为,虽然这些新特性领Java开发人员十分期待,但同时也需要花不少精力去学习。在这一小节中,我们将介绍Java 8的大部分新特性。

    2.1 Lambda表达式和函数式接口

    Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。

    Lambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成,例如:

    Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );

    在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:

    Arrays.asList( "a", "b", "d" ).forEach( ( String e ) -> System.out.println( e ) );

    如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:

    Arrays.asList( "a", "b", "d" ).forEach( e -> {
        System.out.print( e );
        System.out.print( e );
    } );

    Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final的),例如下列两个代码块的效果完全相同:

    String separator = ",";
    Arrays.asList( "a", "b", "d" ).forEach( 
        ( String e ) -> System.out.print( e + separator ) );

    final String separator = ",";
    Arrays.asList( "a", "b", "d" ).forEach( 
        ( String e ) -> System.out.print( e + separator ) );

    Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:

    Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

    Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
        int result = e1.compareTo( e2 );
        return result;
    } );

    Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了),举个简单的函数式接口的定义:

    @FunctionalInterface
    public interface Functional {
        void method();
    }

    不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

    @FunctionalInterface
    public interface FunctionalDefaultMethods {
        void method();
    
        default void defaultMethod() {            
        }        
    }

    Lambda表达式作为Java 8的最大卖点,它有潜力吸引更多的开发者加入到JVM平台,并在纯Java编程中使用函数式编程的概念。如果你需要了解更多Lambda表达式的细节,可以参考官方文档

    2.2 接口的默认方法和静态方法

    Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点类似traits,不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。

    默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:

    private interface Defaulable {
        // Interfaces now allow default methods, the implementer may or 
        // may not implement (override) them.
        default String notRequired() { 
            return "Default implementation"; 
        }        
    }
    
    private static class DefaultableImpl implements Defaulable {
    }
    
    private static class OverridableImpl implements Defaulable {
        @Override
        public String notRequired() {
            return "Overridden implementation";
        }
    }

    Defaulable接口使用关键字default定义了一个默认方法notRequired()DefaultableImpl类实现了这个接口,同时默认继承了这个接口中的默认方法;OverridableImpl类也实现了这个接口,但覆写了该接口的默认方法,并提供了一个不同的实现。

    Java 8带来的另一个有趣的特性是在接口中可以定义静态方法,例子代码如下:

    private interface DefaulableFactory {
        // Interfaces now allow static methods
        static Defaulable create( Supplier< Defaulable > supplier ) {
            return supplier.get();
        }
    }

    下面的代码片段整合了默认方法和静态方法的使用场景:

    public static void main( String[] args ) {
        Defaulable defaulable = DefaulableFactory.create( DefaultableImpl::new );
        System.out.println( defaulable.notRequired() );
    
        defaulable = DefaulableFactory.create( OverridableImpl::new );
        System.out.println( defaulable.notRequired() );
    }

    这段代码的输出结果如下:

    Default implementation
    Overridden implementation

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

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

    2.3 方法引用

    方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。

    西门的例子中,Car类是不同方法引用的例子,可以帮助读者区分四种类型的方法引用。

    public static class Car {
        public static Car create( final Supplier< Car > supplier ) {
            return supplier.get();
        }              
    
        public static void collide( final Car car ) {
            System.out.println( "Collided " + car.toString() );
        }
    
        public void follow( final Car another ) {
            System.out.println( "Following the " + another.toString() );
        }
    
        public void repair() {   
            System.out.println( "Repaired " + this.toString() );
        }
    }

    第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class<T>::new。注意:这个构造器没有参数。

    final Car car = Car.create( Car::new );
    final List< Car > cars = Arrays.asList( car );

    第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。

    cars.forEach( Car::collide );

    第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:

    cars.forEach( Car::repair );

    第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:

    final Car police = Car.create( Car::new );
    cars.forEach( police::follow );

    运行上述例子,可以在控制台看到如下输出(Car实例可能不同):

    Collided com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
    Repaired com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d
    Following the com.javacodegeeks.java8.method.references.MethodReferences$Car@7a81197d

    如果想了解和学习更详细的内容,可以参考官方文档

    2.4 重复注解

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

    在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。可以利用下面的代码说明:

    package com.javacodegeeks.java8.repeatable.annotations;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Repeatable;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    public class RepeatingAnnotations {
        @Target( ElementType.TYPE )
        @Retention( RetentionPolicy.RUNTIME )
        public @interface Filters {
            Filter[] value();
        }
    
        @Target( ElementType.TYPE )
        @Retention( RetentionPolicy.RUNTIME )
        @Repeatable( Filters.class )
        public @interface Filter {
            String value();
        };
    
        @Filter( "filter1" )
        @Filter( "filter2" )
        public interface Filterable {        
        }
    
        public static void main(String[] args) {
            for( Filter filter: Filterable.class.getAnnotationsByType( Filter.class ) ) {
                System.out.println( filter.value() );
            }
        }
    }

    正如我们所见,这里的Filter类使用@Repeatable(Filters.class)注解修饰,而Filters是存放Filter注解的容器,编译器尽量对开发者屏蔽这些细节。这样,Filterable接口可以用两个Filter注解注释(这里并没有提到任何关于Filters的信息)。

    另外,反射API提供了一个新的方法:getAnnotationsByType(),可以返回某个类型的重复注解,例如Filterable.class.getAnnoation(Filters.class)将返回两个Filter实例,输出到控制台的内容如下所示:

    filter1
    filter2

    如果你希望了解更多内容,可以参考官方文档

    2.5 更好的类型推断

    Java 8编译器在类型推断方面有很大的提升,在很多场景下编译器可以推导出某个参数的数据类型,从而使得代码更为简洁。例子代码如下:

    package com.javacodegeeks.java8.type.inference;
    
    public class Value< T > {
        public static< T > T defaultValue() { 
            return null; 
        }
    
        public T getOrDefault( T value, T defaultValue ) {
            return ( value != null ) ? value : defaultValue;
        }
    }

    下列代码是Value<String>类型的应用:

    package com.javacodegeeks.java8.type.inference;
    
    public class TypeInference {
        public static void main(String[] args) {
            final Value< String > value = new Value<>();
            value.getOrDefault( "22", Value.defaultValue() );
        }
    }

    参数Value.defaultValue()的类型由编译器推导得出,不需要显式指明。在Java 7中这段代码会有编译错误,除非使用Value.<String>defaultValue()

    2.6 拓宽注解的应用场景

    Java 8拓宽了注解的应用场景。现在,注解几乎可以使用在任何元素上:局部变量、接口类型、超类和接口实现类,甚至可以用在函数的异常定义上。下面是一些例子:

    package com.javacodegeeks.java8.annotations;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    import java.util.ArrayList;
    import java.util.Collection;
    
    public class Annotations {
        @Retention( RetentionPolicy.RUNTIME )
        @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )
        public @interface NonEmpty {        
        }
    
        public static class Holder< @NonEmpty T > extends @NonEmpty Object {
            public void method() throws @NonEmpty Exception {            
            }
        }
    
        @SuppressWarnings( "unused" )
        public static void main(String[] args) {
            final Holder< String > holder = new @NonEmpty Holder< String >();        
            @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();        
        }
    }

    ElementType.TYPE_USERElementType.TYPE_PARAMETER是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。

    3. Java编译器的新特性

    3.1 参数名称

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

    package com.javacodegeeks.java8.parameter.names;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    
    public class ParameterNames {
        public static void main(String[] args) throws Exception {
            Method method = ParameterNames.class.getMethod( "main", String[].class );
            for( final Parameter parameter: method.getParameters() ) {
                System.out.println( "Parameter: " + parameter.getName() );
            }
        }
    }

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

    Parameter: arg0

    如果带-parameters参数,则会输出如下结果(正确的结果):

    Parameter: args

    如果你使用Maven进行项目管理,则可以在maven-compiler-plugin编译器的配置项中配置-parameters参数:

    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
            <compilerArgument>-parameters</compilerArgument>
            <source>1.8</source>
            <target>1.8</target>
        </configuration>
    </plugin>

    4. Java官方库的新特性

    Java 8增加了很多新的工具类(date/time类),并扩展了现存的工具类,以支持现代的并发编程、函数式编程等。

    4.1 Optional

    Java应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。

    Optional仅仅是一个容易:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查,可以参考Java 8官方文档了解更多细节。

    接下来看一点使用Optional的例子:可能为空的值或者某个类型的值:

    Optional< String > fullName = Optional.ofNullable( null );
    System.out.println( "Full Name is set? " + fullName.isPresent() );        
    System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) ); 
    System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

    如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。

    上述代码的输出结果如下:

    Full Name is set? false
    Full Name: [none]
    Hey Stranger!

    再看下另一个简单的例子:

    Optional< String > firstName = Optional.of( "Tom" );
    System.out.println( "First Name is set? " + firstName.isPresent() );        
    System.out.println( "First Name: " + firstName.orElseGet( () -> "[none]" ) ); 
    System.out.println( firstName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );
    System.out.println();

    这个例子的输出是:

    First Name is set? true
    First Name: Tom
    Hey Tom!

    如果想了解更多的细节,请参考官方文档

    4.2 Streams

    新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。

    Steam API极大得简化了集合操作(后面我们会看到不止是集合),首先看下这个叫Task的类:

    public class Streams  {
        private enum Status {
            OPEN, CLOSED
        };
    
        private static final class Task {
            private final Status status;
            private final Integer points;
    
            Task( final Status status, final Integer points ) {
                this.status = status;
                this.points = points;
            }
    
            public Integer getPoints() {
                return points;
            }
    
            public Status getStatus() {
                return status;
            }
    
            @Override
            public String toString() {
                return String.format( "[%s, %d]", status, points );
            }
        }
    }

    Task类有一个分数(或伪复杂度)的概念,另外还有两种状态:OPEN或者CLOSED。现在假设有一个task集合:

    final Collection< Task > tasks = Arrays.asList(
        new Task( Status.OPEN, 5 ),
        new Task( Status.OPEN, 13 ),
        new Task( Status.CLOSED, 8 ) 
    );

    首先看一个问题:在这个task集合中一共有多少个OPEN状态的点?在Java 8之前,要解决这个问题,则需要使用foreach循环遍历task集合;但是在Java 8中可以利用steams解决:包括一系列元素的列表,并且支持顺序和并行处理。

    // Calculate total points of all active tasks using sum()
    final long totalPointsOfOpenTasks = tasks
        .stream()
        .filter( task -> task.getStatus() == Status.OPEN )
        .mapToInt( Task::getPoints )
        .sum();
    
    System.out.println( "Total points: " + totalPointsOfOpenTasks );

    运行这个方法的控制台输出是:

    Total points: 18

    这里有很多知识点值得说。首先,tasks集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有CLOSED的task;第三,mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。

    在学习下一个例子之前,还需要记住一些steams(点此更多细节)的知识点。Steam之上的操作可分为中间操作和晚期操作。

    中间操作会返回一个新的steam——执行一个中间操作(例如filter)并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。

    晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。

    steam的另一个价值是创造性地支持并行处理(parallel processing)。对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和:

    // Calculate total points of all tasks
    final double totalPoints = tasks
       .stream()
       .parallel()
       .map( task -> task.getPoints() ) // or map( Task::getPoints ) 
       .reduce( 0, Integer::sum );
    
    System.out.println( "Total points (all tasks): " + totalPoints );

    这里我们使用parallel方法并行处理所有的task,并使用reduce方法计算最终的结果。控制台输出如下:

    Total points(all tasks): 26.0

    对于一个集合,经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务,代码如下:

    // Group tasks by their status
    final Map< Status, List< Task > > map = tasks
        .stream()
        .collect( Collectors.groupingBy( Task::getStatus ) );
    System.out.println( map );

    控制台的输出如下:

    {CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

    最后一个关于tasks集合的例子问题是:如何计算集合中每个任务的点数在集合中所占的比重,具体处理的代码如下:

    // Calculate the weight of each tasks (as percent of total points) 
    final Collection< String > result = tasks
        .stream()                                        // Stream< String >
        .mapToInt( Task::getPoints )                     // IntStream
        .asLongStream()                                  // LongStream
        .mapToDouble( points -> points / totalPoints )   // DoubleStream
        .boxed()                                         // Stream< Double >
        .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
        .mapToObj( percentage -> percentage + "%" )      // Stream< String> 
        .collect( Collectors.toList() );                 // List< String > 
    
    System.out.println( result );

    控制台输出结果如下:

    [19%, 50%, 30%]

    最后,正如之前所说,Steam API不仅可以作用于Java集合,传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理,这里有一个小例子:

    final Path path = new File( filename ).toPath();
    try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {
        lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
    }

    Stream的方法onClose 返回一个等价的有额外句柄的Stream,当Stream的close()方法被调用的时候这个句柄会被执行。Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用,是Java 8对软件开发的现代范式的响应。

    4.3 Date/Time API(JSR 310)

    Java 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。

    因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。

    我们接下来看看java.time包中的关键类和各自的使用例子。首先,Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()TimeZone.getDefault()

    // Get the system clock as UTC offset 
    final Clock clock = Clock.systemUTC();
    System.out.println( clock.instant() );
    System.out.println( clock.millis() );

    这个例子的输出结果是:

    2014-04-12T15:19:29.282Z
    1397315969360

    第二,关注下LocalDateLocalTime类。LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。

    // Get the local date and local time
    final LocalDate date = LocalDate.now();
    final LocalDate dateFromClock = LocalDate.now( clock );
    
    System.out.println( date );
    System.out.println( dateFromClock );
    
    // Get the local date and local time
    final LocalTime time = LocalTime.now();
    final LocalTime timeFromClock = LocalTime.now( clock );
    
    System.out.println( time );
    System.out.println( timeFromClock );

    上述例子的输出结果如下:

    2014-04-12
    2014-04-12
    11:25:54.568
    15:25:54.568

    LocalDateTime类包含了LocalDate和LocalTime的信息,但是不包含ISO-8601日历系统中的时区信息。这里有一些关于LocalDate和LocalTime的例子

    // Get the local date/time
    final LocalDateTime datetime = LocalDateTime.now();
    final LocalDateTime datetimeFromClock = LocalDateTime.now( clock );
    
    System.out.println( datetime );
    System.out.println( datetimeFromClock );

    上述这个例子的输出结果如下:

    2014-04-12T11:37:52.309
    2014-04-12T15:37:52.309

    如果你需要特定时区的data/time信息,则可以使用ZoneDateTime,它保存有ISO-8601日期系统的日期和时间,而且有时区信息。下面是一些使用不同时区的例子:

    // Get the zoned date/time
    final ZonedDateTime zonedDatetime = ZonedDateTime.now();
    final ZonedDateTime zonedDatetimeFromClock = ZonedDateTime.now( clock );
    final ZonedDateTime zonedDatetimeFromZone = ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) );
    
    System.out.println( zonedDatetime );
    System.out.println( zonedDatetimeFromClock );
    System.out.println( zonedDatetimeFromZone );

    这个例子的输出结果是:

    2014-04-12T11:47:01.017-04:00[America/New_York]
    2014-04-12T15:47:01.017Z
    2014-04-12T08:47:01.017-07:00[America/Los_Angeles]

    最后看下Duration类,它持有的时间精确到秒和纳秒。这使得我们可以很容易得计算两个日期之间的不同,例子代码如下:

    // Get duration between two dates
    final LocalDateTime from = LocalDateTime.of( 2014, Month.APRIL, 16, 0, 0, 0 );
    final LocalDateTime to = LocalDateTime.of( 2015, Month.APRIL, 16, 23, 59, 59 );
    
    final Duration duration = Duration.between( from, to );
    System.out.println( "Duration in days: " + duration.toDays() );
    System.out.println( "Duration in hours: " + duration.toHours() );

    这个例子用于计算2014年4月16日和2015年4月16日之间的天数和小时数,输出结果如下:

    Duration in days: 365
    Duration in hours: 8783

    对于Java 8的新日期时间的总体印象还是比较积极的,一部分是因为Joda-Time的积极影响,另一部分是因为官方终于听取了开发人员的需求。如果希望了解更多细节,可以参考官方文档

    4.4 Nashorn JavaScript引擎

    Java 8提供了新的Nashorn JavaScript引擎,使得我们可以在JVM上开发和运行JS应用。Nashorn JavaScript引擎是javax.script.ScriptEngine的另一个实现版本,这类Script引擎遵循相同的规则,允许Java和JavaScript交互使用,例子代码如下:

    ScriptEngineManager manager = new ScriptEngineManager();
    ScriptEngine engine = manager.getEngineByName( "JavaScript" );
    
    System.out.println( engine.getClass().getName() );
    System.out.println( "Result:" + engine.eval( "function f() { return 1; }; f() + 1;" ) );

    这个代码的输出结果如下:

    jdk.nashorn.api.scripting.NashornScriptEngine
    Result: 2

    4.5 Base64

    对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下:

    package com.javacodegeeks.java8.base64;
    
    import java.nio.charset.StandardCharsets;
    import java.util.Base64;
    
    public class Base64s {
        public static void main(String[] args) {
            final String text = "Base64 finally in Java 8!";
    
            final String encoded = Base64
                .getEncoder()
                .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );
            System.out.println( encoded );
    
            final String decoded = new String( 
                Base64.getDecoder().decode( encoded ),
                StandardCharsets.UTF_8 );
            System.out.println( decoded );
        }
    }

    这个例子的输出结果如下:

    QmFzZTY0IGZpbmFsbHkgaW4gSmF2YSA4IQ==
    Base64 finally in Java 8!

    新的Base64API也支持URL和MINE的编码解码。
    (Base64.getUrlEncoder() / Base64.getUrlDecoder()Base64.getMimeEncoder() / Base64.getMimeDecoder())。

    4.6 并行数组

    Java8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法:

    package com.javacodegeeks.java8.parallel.arrays;
    
    import java.util.Arrays;
    import java.util.concurrent.ThreadLocalRandom;
    
    public class ParallelArrays {
        public static void main( String[] args ) {
            long[] arrayOfLong = new long [ 20000 ];        
    
            Arrays.parallelSetAll( arrayOfLong, 
                index -> ThreadLocalRandom.current().nextInt( 1000000 ) );
            Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
                i -> System.out.print( i + " " ) );
            System.out.println();
    
            Arrays.parallelSort( arrayOfLong );        
            Arrays.stream( arrayOfLong ).limit( 10 ).forEach( 
                i -> System.out.print( i + " " ) );
            System.out.println();
        }
    }

    上述这些代码使用parallelSetAll()方法生成20000个随机数,然后使用parallelSort()方法进行排序。这个程序会输出乱序数组和排序数组的前10个元素。上述例子的代码输出的结果是:

    Unsorted: 591217 891976 443951 424479 766825 351964 242997 642839 119108 552378 
    Sorted: 39 220 263 268 325 607 655 678 723 793

    4.7 并发性

    基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作(更多内容可以参考我们的并发编程课程)。

    Java 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)。

    java.util.concurrent.atomic包中也新增了不少工具类,列举如下:

    • DoubleAccumulator
    • DoubleAdder
    • LongAccumulator
    • LongAdder

    5. 新的Java工具

    Java 8提供了一些新的命令行工具,这部分会讲解一些对开发者最有用的工具。

    5.1 Nashorn引擎:jjs

    jjs是一个基于标准Nashorn引擎的命令行工具,可以接受js源码并执行。例如,我们写一个func.js文件,内容如下:

    function f() { 
         return 1; 
    }; 
    
    print( f() + 1 );

    可以在命令行中执行这个命令:jjs func.js,控制台输出结果是:

    2

    如果需要了解细节,可以参考官方文档

    5.2 类依赖分析器:jdeps

    jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。

    我们可以利用jedps分析下Spring Framework库,为了让结果少一点,仅仅分析一个JAR文件:org.springframework.core-3.0.5.RELEASE.jar

    jdeps org.springframework.core-3.0.5.RELEASE.jar

    这个命令会输出很多结果,我们仅看下其中的一部分:依赖关系按照包分组,如果在classpath上找不到依赖,则显示"not found".

    org.springframework.core-3.0.5.RELEASE.jar -> C:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar
       org.springframework.core (org.springframework.core-3.0.5.RELEASE.jar)
          -> java.io                                            
          -> java.lang                                          
          -> java.lang.annotation                               
          -> java.lang.ref                                      
          -> java.lang.reflect                                  
          -> java.util                                          
          -> java.util.concurrent                               
          -> org.apache.commons.logging                         not found
          -> org.springframework.asm                            not found
          -> org.springframework.asm.commons                    not found
       org.springframework.core.annotation (org.springframework.core-3.0.5.RELEASE.jar)
          -> java.lang                                          
          -> java.lang.annotation                               
          -> java.lang.reflect                                  
          -> java.util

    更多的细节可以参考官方文档

    6. JVM的新特性

    使用MetaspaceJEP 122)代替持久代(PermGen space)。在JVM参数方面,使用-XX:MetaSpaceSize-XX:MaxMetaspaceSize代替原来的-XX:PermSize-XX:MaxPermSize

    7. 结论

    通过为开发者提供很多能够提高生产力的特性,Java 8使得Java平台前进了一大步。现在还不太适合将Java 8应用在生产系统中,但是在之后的几个月中Java 8的应用率一定会逐步提高(PS:原文时间是2014年5月9日,现在在很多公司Java 8已经成为主流,我司由于体量太大,现在也在一点点上Java 8,虽然慢但是好歹在升级了)。作为开发者,现在应该学习一些Java 8的知识,为升级做好准备。

    关于Spring:对于企业级开发,我们也应该关注Spring社区对Java 8的支持,可以参考这篇文章——Spring 4支持的Java 8新特性一览

    8. 参考资料

    展开全文
  • 越来越多的项目已经使用 Java 8 了,毫无疑问,Java 8JavaJava 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和 JVM 等方面的十多个新特性。在本文中我们将学习这些新特性,并用...
  • Java8特性学习

    2014-03-30 15:49:09
    Java8刚正式发布没几天,今天特地体验了一下,看了一下官方文档和牛人的点评,最让我印象深刻有两个:Lambda表达式和MetaSpace! Lambda表达式 Lambda表达式可以说是千呼万唤始出来,不记得从什么时候开始,Java...
  • 以最近为例,Java 8 以 lambda 表达式和流的形式引入了函数式编程,Java 9 引入了模块化 Java 系统。每个新版本都被热切期待,而一些小的修复补丁则常常被搁在一边,等待更大组件的最终确定。Java 的演变落后于其他...
  • 初次学习这部分会感觉很难,主要是概念难于理解,最好是多看看例子,多练习。下面是个人的总结 一、集合框架及泛型1、集合框架是一套性能优良、使用方便的接口和类(位于java.util包中)解决数组在存储上不能很好...
  • JAVA8十大新特性详解

    2019-08-14 10:34:24
    前言: Java ...在Java Code Geeks上已经有很多介绍Java 8特性的文章, 例如Playing with Java 8 – Lambdas and Concurrency、Java 8 Date Time API Tutorial : LocalDateTime和Abstract C...
  • Java 8Java语言、编译器、类库、开发工具与JVM(Java虚拟机)带来了大量新特性。在这篇教程中,我们将一一探索这些变化,并用真实的例子说明它们适用的场景。 本文由以下几部分组成,它们分别涉及到Java平台某一...
  • 前言: Java 8 已经发布...在Java Code Geeks上已经有很多介绍Java 8特性的文章,例如Playing with Java 8 – Lambdas and Concurrency、Java 8 Date Time API Tutorial : LocalDateTime和Abstract Class Versus
  • Java 8特性见这里:Java8特性最佳指南 。 Guide 哥:别人家的特性都用了几年了,我 Java 才出来,哈哈!真实! Java9 发布于 2017 年 9 月 21 日 。作为 Java8 之后 3 年半才发布的新版本,Java 9 带 来了很多...
  • 前言: Java 8 已经...在Java Code Geeks上已经有很多介绍Java 8特性的文章,例如Playing with Java 8 – Lambdas and Concurrency、Java 8 Date Time API Tutorial : LocalDateTime和Abstract Class Versu
  • (本博客的代码根据 java8特性教程 学习整理,加上个人的理解而成,关于某个新特性的介绍代码里的注释已经阐述清楚,故不再写文字介绍,直接看代码吧!)  本篇介绍java8的新特性之一:方法和构造函数...
  • 1. java8特性 1.1 新特性介绍: 简而言之,java8的新特性就是:Lamdba函数(匿名函数),流,默认方法。 Java8 的灵活使用,会使得代码可读性更好(前提是你的同事也使用,别人不会,你强行使用,会被喷的!!!)...
  • 最近,很多读者出去面试都在Java8上栽了跟头,事后自己分析,确实对Java8的新特性一知半解。然而,却在简历显眼的技能部分写着:熟练掌握Java8的各种新特性,能够迅速使用Java8开发高并发应用!这不,又一名读者因为...
  • java8特性 lambda表达式详解
  • (本博客的代码根据 java8特性教程 学习整理,加上个人的理解而成,关于某个新特性的介绍代码里的注释已经阐述清楚,故不再写文字介绍,直接看代码吧!)  本篇介绍java8的新特性之一:内建的功能性...
  • Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。 Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、...
1 2 3 4 5 ... 20
收藏数 235,320
精华内容 94,128