Java8 通关攻略

点赞+收藏 就学会系列,文章收录在 GitHub JavaEgg ,N线互联网开发必备技能兵器谱javascript

Java8早在2014年3月就发布了,6年了,你有对它作个全面的了解吗html

本文是用我拙劣的英文和不要脸的这抄抄那抄抄,熬出来的,没有深究源码,只是对 Java8 有一个总体的认知,能够上手用起来,示例代码也都在github上java

JDK 8 有什么新功能

  • Java 编程语言( Java Programming Language)git

    • Lambda表达式:一个新的语言特性, 它们使您可以将函数视为方法参数,或将代码视为数据
    • 方法引用: 方法引用为已经有名称的方法提供易于阅读的lambda表达式
    • 默认方法:使用 default 关键字为接口定义默认方法(有实现的方法)
    • 重复注解提供了将同一注解屡次应用于同一声明或类型使用的能力
    • 类型注解提供了在使用类型的任何地方应用注解的能力,而不只仅是在声明上
    • Java8 加强了类型推断
    • 方法参数反射
    • java.util.function: 一个新的包,它包含为lambda表达式和方法引用提供目标类型的通用功能接口
  • 集合(Collections)程序员

    • java.util.stream包中新增了 Stream API ,用来支持对元素流的函数式操做
    • 改进了有键冲突问题的 HashMap
  • 精简运行时(Compact Profiles)github

  • 安全性(Security)数据库

  • JavaFXexpress

  • Tools(包含一些调用Nashorn引擎、 启动JavaFX应用程序等等 )apache

  • 国际化(Internationalization)编程

    • Unicode加强,包括对Unicode 6.2.0的支持
    • 提供了新的 Calendar 和 Locale API
  • 部署(Deployment)

  • 日期-时间 包(Date-Time Package):提供了更全面的时间和日期操做

  • 脚本(Scripting):Java 8提供了一个新的 Nashorn javascript 引擎(取代了Nashorn javascript引擎),它容许咱们在JVM上运行特定的 javascript 应用

  • 改进 IO 和 NIO

  • 改进 java.langjava.util

    • 支持数组并行排序
    • 支持Base64 的编码和解码
    • 支持 无符号运算
    • Optional 类 :最大化减小空指针异常
  • JDBC

    • JDBC-ODBC桥已被移除
    • JDBC 4.2引入了新的特性
  • Java DB(一个Java数据库)

  • 网络(Networking)

    • 新增了 java.net.URLPermission
  • 并发(Concurrency

    • CompletableFuture 加强了以前的Future
    • java.util.concurrent.ConcurrentHashMap 支持基于新添加的streams功能和lambda表达式的聚合操做
    • java.util.concurrent.atomic 提供了一组原子变量类, 对于单个变量支持无锁、线程安全操做的工具类
    • java.util.concurrent.ForkJoinPool 用于补充ExecutorService
    • java.util.concurrent.locks.StampedLock 提供了基于功能的锁,有三种模式用于控制读/写访问
  • JVM: 移除了 PermGen ,取而代之的是Metaspace

Java8 特别强大的是Lambda 表达式和Stream,经过它两新增和加强了不少包

新增: java.lang.invokejava.util.functionjava.util.stream

修改:

modify-class.png


1、Lambda表达式

能够把 Lambda 表达式理解为简洁的表示可传递的匿名函数的一种方式,Lambda表达式基于数学中的λ演算得名:它没有名称,但有参数列表、函数主体、返回类型,可能还有一个能够抛出的异常列表。

  • 匿名——匿名函数(即没有函数名的函数),不像普通的方法有一个明确的名称,“写得少,想得多”
  • 函数——Lambda函数不像方法那样属于某个特定的类,但同样有参数列表、函数主体和返回类型
  • 传递——Lambda表达式能够做为参数传递给方法或者存储在变量中
  • 简洁——无需像匿名类那样写不少模板代码

Lambda表达式使您可以封装单个行为单元并将其传递给其余代码。若是但愿对集合的每一个元素、流程完成时或流程遇到错误时执行某个操做,可使用lambda表达式。

1. 为何要使用Lambda表达式

Lambda 是一个匿名函数,咱们能够把 Lambda表达式理解为是一段能够传递的代码(将代码像数据同样进行传递——行为参数化)。能够写出更简洁、更灵活的代码。做为一种更紧凑的代码风格,使Java的语言表达能力获得了提高。

匿名类的一个问题是,若是您的匿名类的实现很是简单,例如一个接口只包含一个方法,那么匿名类的语法可能看起来很笨拙和不清楚。在这些状况下,您一般试图将功能做为参数传递给另外一个方法,例如当有人单击按钮时应该采起什么操做。Lambda表达式容许您这样作,将功能视为方法参数,或将代码视为数据。

hello-lambda

2. Lambda 表达式语法

(parameters) -> expression(parameters) ->{ statements; }

Lambda 表达式在 Java 语言中引入了一个新的语法元素和操做符。这个操做符为 -> , 该操做符被称为 Lambda 操做符或剪头操做符。它将 Lambda 分为两个部分:

  • 左侧:指定了 Lambda 表达式须要的全部参数

  • 右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能

eg(错误示范):

(Integer i) -> return "hello"+i;   //错误的Lambda,return是一个控制流语句,须要{}
(String s) -> {"hello";}    //“hello”是一个表达式,不是语句,不须要{},能够写成{return “hello”;}
复制代码

eg(正确示范):

  1. 无参,无返回值,Lambda 体只需一条语句

    Runnable runnable = () -> System.out.println("hello lambda");
    复制代码
  2. Lambda 须要一个参数

    Consumer<String> consumer = (args) -> System.out.println(args);
    复制代码

    Lambda 只须要一个参数时,参数的小括号能够省略

    Consumer<String> consumer = args -> System.out.println(args);
    复制代码
  3. Lambda 须要两个参数,而且有返回值

    BinaryOperator<Long> binaryOperator = (Long x,Long y) -> {
    	System.out.println("实现函数接口方法");
    	return x +y;
    };
    复制代码

    参数的数据类型可省略,Java8加强了类型推断,且当 Lambda 体只有一条语句时,return 与大括号能够省略

    BinaryOperator<Long> binaryOperator = (x, y) -> x + y;
    复制代码

类型推断

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然能够编译,这是由于 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。Java7中引入的菱形运算符<>),就是利用泛型从上下文推断类型。

List<String> list = new ArrayList<>();
复制代码

3. Lambda表达式实例

官方提供的示例,假设你要开发一个社交软件,那个缺打的PM整天改需求,今天要查询出成年用户的信息,明天又要查询成年女性的用户信息,后天又要按各类奇怪的搜索条件查询。

这时的程序员:从简单的用户遍历比较方法改成通用的搜索方法到后来都用上了工厂模式,等到第7天的时候,你不耐烦了,玛德,每一个条件就一句话,我写了7个类,我可不想作CtrlCV工程师,这时候Lambda表达式是你的不二之选。

行为参数化就是能够帮助你处理频繁变动的需求的一种软件开发模式。

官方提供的demo,一步步告诉你使用Java8的好处(从值参数化到行为参数化)。代码

import java.util.List;
import java.util.ArrayList;
import java.time.chrono.IsoChronology;
import java.time.LocalDate;
public class Person {
    public enum Sex {
        MALE, FEMALE
    }

    String name;
    LocalDate birthday;
    Sex gender;
    String emailAddress;

    Person(String nameArg, LocalDate birthdayArg,
           Sex genderArg, String emailArg) {
        name = nameArg;
        birthday = birthdayArg;
        gender = genderArg;
        emailAddress = emailArg;
    }

    public int getAge() {
        return birthday
                .until(IsoChronology.INSTANCE.dateNow())
                .getYears();
    }

    public void printPerson() {
        System.out.println(name + ", " + this.getAge());
    }

    public Sex getGender() {
        return gender;
    }

    public String getName() {
        return name;
    }

    public String getEmailAddress() {
        return emailAddress;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }

    public static List<Person> createRoster() {
        List<Person> roster = new ArrayList<>();
        roster.add(new Person(
                        "Fred",
                        IsoChronology.INSTANCE.date(1980, 6, 20),
                        Person.Sex.MALE,
                        "fred@example.com"));
        roster.add(new Person(
                        "Jane",
                        IsoChronology.INSTANCE.date(1990, 7, 15),
                        Person.Sex.FEMALE, "jane@example.com"));
        roster.add(new Person(
                        "George",
                        IsoChronology.INSTANCE.date(1991, 8, 13),
                        Person.Sex.MALE, "george@example.com"));
        roster.add(new Person(
                        "Bob",
                        IsoChronology.INSTANCE.date(2000, 9, 12),
                        Person.Sex.MALE, "bob@example.com"));
        return roster;
    }
}

复制代码

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

public class RosterTest {
    interface CheckPerson {
        boolean test(Person p);
    }

    /** * 1. eg:输出年龄大于20岁的成员 * 匹配符合某一特征的成员的方法 * 若是老板要年龄在某一区间的成员呢?接着换方法 */
    public static void printPersonsOlderThan(List<Person> roster, int age) {
        for (Person p : roster) {
            if (p.getAge() >= age) {
                p.printPerson();
            }
        }
    }

    /** * 2. eg:输出年龄在14到30岁之间的成员 * 更全面的匹配方法 * 若是老板只要男性成员呢? */
    public static void printPersonsWithinAgeRange( List<Person> roster, int low, int high) {
        for (Person p : roster) {
            if (low <= p.getAge() && p.getAge() < high) {
                p.printPerson();
            }
        }
    }

    /** * 3. eg:老板又提出了各类复杂的需求,不要处女座的、只要邮箱是163的,怎么搞? * 方法1:在本地类中指定搜索条件代码,经过接口方式,不一样的需求对应不一样的实现类, * 每次都要新建实现类,写大量的代码 * 方法2:在匿名类中指定搜索条件代码,不须要写各类实现,可是还要写个interface CheckPerson, * 并且匿名类写起来也挺麻烦 * 方法3:Lambda表达式是懒人的不二之选,CheckPerson是一个只包含一个抽象方法的接口, * 比较简单,Lambda能够省略其实现 */
    public static void printPersons( List<Person> roster, CheckPerson tester) {
        for (Person p : roster) {
            if (tester.test(p)) {
                p.printPerson();
            }
        }
    }

    /** * 4. eg: 搞这么久,还得写一个接口,并且是只有一个抽象方法,仍是不爽? * 你也可使用标准的函数接口来代替接口CheckPerson,从而进一步减小所需的代码量 * java.util.function包中定义了标准的函数接口 * 咱们可使用JDK8提供的 Predicate<T>接口来代替CheckPerson。 * 该接口包含方法boolean test(T t) */
    public static void printPersonsWithPredicate( List<Person> roster, Predicate<Person> tester) {
        for (Person p : roster) {
            if (tester.test(p)) {
                p.printPerson();
            }
        }
    }

    /** * 5. Lambda表达式可不仅是可以简化匿名类 * 简化 p.printPerson(), * 使用Consumer<T>接口的void accept(T t)方法,至关于入参的操做 */
    public static void processPersons( List<Person> roster, Predicate<Person> tester, Consumer<Person> block) {
        for (Person p : roster) {
            if (tester.test(p)) {
                block.accept(p);
            }
        }
    }

    /** * 6. eg: 老板说了只想看到邮箱 * Function<T,R>接口,至关于输入类型,mapper定义参数,block负责方对给定的参数进行执行 */
    public static void processPersonsWithFunction( List<Person> roster, Predicate<Person> tester, Function<Person, String> mapper, Consumer<String> block) {
        for (Person p : roster) {
            if (tester.test(p)) {
                String data = mapper.apply(p);
                block.accept(data);
            }
        }
    }

    // 7. 使用泛型
    public static <X, Y> void processElements( Iterable<X> source, Predicate<X> tester, Function<X, Y> mapper, Consumer<Y> block) {
        for (X p : source) {
            if (tester.test(p)) {
                Y data = mapper.apply(p);
                block.accept(data);
            }
        }
    }

    public static void main(String[] args) {
        List<Person> roster = Person.createRoster();

        /** * 1. 输出年龄大于20岁的成员 */
        System.out.println("Persons older than 20:");
        printPersonsOlderThan(roster, 20);
        System.out.println();

        /** * 2. 输出年龄在14到30岁之间的成员 */
        System.out.println("Persons between the ages of 14 and 30:");
        printPersonsWithinAgeRange(roster, 14, 30);
        System.out.println();

        /** * 3. 输出年龄在18到25岁的男性成员 * (在本地类中指定搜索条件) * 您可使用一个匿名类而不是一个本地类,而且没必要为每一个搜索声明一个新类 */
        System.out.println("Persons who are eligible for Selective Service:");
        class CheckPersonEligibleForSelectiveService implements CheckPerson {
            public boolean test(Person p) {
                return p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25;
            }
        }

        // 这个其实就是经过行为参数化传递代码
        printPersons(
                roster, new CheckPersonEligibleForSelectiveService());

        System.out.println();

        // 3. 在匿名类中指定搜索条件代码
        System.out.println("Persons who are eligible for Selective Service " +
                "(anonymous class):");
        printPersons(
                roster,
                new CheckPerson() {
                    public boolean test(Person p) {
                        return p.getGender() == Person.Sex.MALE
                                && p.getAge() >= 18
                                && p.getAge() <= 25;
                    }
                }
        );

        System.out.println();

        // 3: 使用Lambda表达式简化代码,一个箭头
        System.out.println("Persons who are eligible for Selective Service " +
                "(lambda expression):");

        printPersons(
                roster,
                (Person p) -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25
        );

        System.out.println();

        // 4. 使用Lambda的标准功能接口
        System.out.println("Persons who are eligible for Selective Service " +
                "(with Predicate parameter):");

        printPersonsWithPredicate(
                roster,
                p -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25
        );

        System.out.println();

        //5.使用Predicate和Consumer参数
        System.out.println("5. Persons who are eligible for Selective Service " +
                "(with Predicate and Consumer parameters):");

        processPersons(
                roster,
                p -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25,
                p -> p.printPerson()
        );

        System.out.println();

        // 6. 经过Function<T,R> 指定输出类型
        System.out.println("Persons who are eligible for Selective Service " +
                "(with Predicate, Function, and Consumer parameters):");

        processPersonsWithFunction(
                roster,
                p -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25,
                p -> p.getEmailAddress(),
                email -> System.out.println(email)
        );

        System.out.println();

        // 7. 使用泛型
        System.out.println("Persons who are eligible for Selective Service " +
                "(generic version):");

        processElements(
                roster,
                p -> p.getGender() == Person.Sex.MALE
                        && p.getAge() >= 18
                        && p.getAge() <= 25,
                p -> p.getEmailAddress(),
                email -> System.out.println(email)
        );

        System.out.println();

        // 8: 使用接受Lambda表达式的批量数据操做
        System.out.println("Persons who are eligible for Selective Service " +
                "(with bulk data operations):");

        roster.stream()
                .filter(
                        p -> p.getGender() == Person.Sex.MALE
                                && p.getAge() >= 18
                                && p.getAge() <= 25)
                .map(p -> p.getEmailAddress())
                .forEach(email -> System.out.println(email));
        System.out.println();

        /** * 9. 按年龄排序。Java 8 以前须要实现 Comparator 接口 * 接口比较器是一个功能接口。所以, * 可使用lambda表达式来代替定义并建立一个实现了Comparator的类的新实例: */
        Person[] rosterAsArray = roster.toArray(new Person[roster.size()]);

        Arrays.sort(rosterAsArray,
                (a, b) -> Person.compareByAge(a, b)
        );

        for (Person person : roster) {
            person.printPerson();
        }

        /** * 这种比较两个Person实例的出生日期的方法已经做为Person. * comparebyage存在。你能够在lambda表达式中调用这个方法 */

        Arrays.sort(rosterAsArray,
                (a, b) -> Person.compareByAge(a, b)
        );
}
复制代码

2、函数式接口

1. 什么是函数式接口

  • 只包含一个抽象方法的接口,称为函数式接口,该抽象方法也被称为函数方法。 咱们熟知的Comparator和Runnable、Callable就属于函数式接口。
  • 这样的接口这么简单,都不值得在程序中定义,因此,JDK8在 java.util.function 中定义了几个标准的函数式接口,供咱们使用。Package java.util.function
  • 能够经过 Lambda 表达式来建立该接口的对象。(若 Lambda 表达式抛出一个受检异常,那么该异常须要在目标接口的抽象方法上进行声明)。
  • 咱们能够在任意函数式接口上使用 @FunctionalInterface 注解, 这样作能够检查它是不是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

2. 自定义函数式接口

@FunctionalInterface    //@FunctionalInterface标注该接口会被设计成一个函数式接口,不然会编译错误
public interface MyFunc<T> {
    T getValue(T t);
}
复制代码
public static String toUpperString(MyFunc<String> myFunc, String str) {
    return myFunc.getValue(str);
}

public static void main(String[] args) {
    String newStr = toUpperString((str) -> str.toUpperCase(), "abc");
    System.out.println(newStr);
}
复制代码

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

函数接口为lambda表达式和方法引用提供目标类型

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

函数式接口 参数类型 返回类型 用途
Consumer<T> T void 对类型为T的对象应用操做,包含方法:void accept(T t)
Supplier<T> T 返回类型为T的对象,包 含方法:T get();
Function<T,R> T R 对类型为T的对象应用操做,并返回结果。结果是R类型的对象。包含方法:R apply(T t);
Predicate<T> T boolean 肯定类型为T的对象是否知足某约束,并返回 boolean 值。包含方法 boolean test(T t);
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

/* * Java8 内置的四大核心函数式接口 * Consumer<T> : 消费型接口 void accept(T t); * Supplier<T> : 供给型接口 T get(); * Function<T, R> : 函数型接口 R apply(T t); * Predicate<T> : 断言型接口 boolean test(T t); */
public class FunctionalInterfaceTest {

    //Predicate<T> 断言型接口:将知足条件的字符串放入集合
    public List<String> filterStr(List<String> list, Predicate<String> predicate) {
        List<String> newList = new ArrayList<>();
        for (String s : list) {
            if (predicate.test(s)) {
                newList.add(s);
            }
        }
        return newList;
    }

    @Test
    public void testPredicate() {
        List<String> list = Arrays.asList("hello", "java8", "function", "predicate");
        List<String> newList = filterStr(list, s -> s.length() > 5);
        for (String s : newList) {
            System.out.println(s);
        }
    }

    // Function<T, R> 函数型接口:处理字符串
    public String strHandler(String str, Function<String, String> function) {
        return function.apply(str);
    }

    @Test
    public void testFunction() {
        String str1 = strHandler("测试内置函数式接口", s -> s.substring(2));
        System.out.println(str1);

        String str2 = strHandler("abcdefg", s -> s.toUpperCase());
        System.out.println(str2);
    }

    //Supplier<T> 供给型接口 :产生指定个数的整数,并放入集合
    public List<Integer> getNumList(int num, Supplier<Integer> supplier) {
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            Integer n = supplier.get();
            list.add(n);
        }
        return list;
    }

    @Test
    public void testSupplier() {
        List<Integer> numList = getNumList(10, () -> (int) (Math.random() * 100));

        for (Integer num : numList) {
            System.out.println(num);
        }
    }

    //Consumer<T> 消费型接口 :修改参数
    public void modifyValue(Integer value, Consumer<Integer> consumer) {
        consumer.accept(value);
    }

    @Test
    public void testConsumer() {
        modifyValue(3, s -> System.out.println(s * 3));
    }
}
复制代码

Package java.util.function 包下还提供了不少其余的演变方法。

java8-function.png

?> Tip

Java类型要么是引用类型(Byte、Integer、Objuct、List),要么是原始类型(int、double、byte、char)。可是泛型只能绑定到引用类型。将原始类型转换为对应的引用类型,叫装箱,相反,将引用类型转换为对应的原始类型,叫拆箱。固然Java提供了自动装箱机制帮咱们执行了这一操做。

List<Integer> list = new ArrayList();
	for (int i = 0; i < 10; i++) {
	list.add(i);    //int被装箱为Integer
}
复制代码

但这在性能方面是要付出代价的。装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。所以,装箱后的值须要更多的内存,并须要额外的内存搜索来获取被包裹的原始值。

以上funciton包中的IntPredicate、DoubleConsumer、LongBinaryOperator、ToDoubleFuncation等就是避免自动装箱的操做。通常,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀。


3、方法引用

  • 方法引用是指经过方法的名字来指向一个方法

  • 当要传递给 Lambda 体的操做,已经有实现的方法了,就可使用方法引用(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)

  • 方法引用的惟一用途是支持Lambda的简写(能够理解为方法引用是lambda表达式的另外一种表现形式,快捷写法)

使用 :: 操做符将方法名对象或类的名字分隔开

1. eg

BinaryOperator<Double> binaryOperator = (x,y)->Math.pow(x,y);
//等价于
BinaryOperator<Double> binaryOperator1 = Math::pow;
复制代码

2. 方法引用类型

Java 8 提供了4种方法引用

Kind Example
静态方法引用 ContainingClass::staticMethodName
特定对象的实例方法引用 containingObject::instanceMethodName
特定类型的任意对象的实例方法引用 ContainingType::methodName
构造器引用 ClassName::new

1. 静态方法引用

//比较年龄的方法在Person.compareByAge的已经存在,因此可使用方法引用
Arrays.sort(rosterAsArray, Person::compareByAge);
//---------------------
@Test
public void test3(){
    BiFunction<Double,Double,Double> bif = (x,y)->Math.max(x,y);
    System.out.println(bif.apply(22.1,23.2));

    System.out.println("===等价于===");

    BiFunction<Double,Double,Double> bif1 = Math::max;
    System.out.println(bif1.apply(22.1,23.2));
}

@Test
public void test4(){
    Comparator<Integer> com = (x, y)->Integer.compare(x,y);
    System.out.println(com.compare(1,2));

    System.out.println("===等价于===");
    Comparator<Integer> com1 = Integer::compare;
    System.out.println(com1.compare(1,2));
}
复制代码

2. 特定对象的实例方法引用

class ComparisonProvider {
    public int compareByName(Person a, Person b) {
        return a.getName().compareTo(b.getName());
    }
        
    public int compareByAge(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}
ComparisonProvider myComparisonProvider = new ComparisonProvider();
Arrays.sort(rosterAsArray, myComparisonProvider::compareByName);
//------------------------
@Test
public void test2() {
    Person person = new Person("Tom", IsoChronology.INSTANCE.date(1995, 6, 20), Person.Sex.MALE, "tom@qq.com");

    Supplier<String> sup = () -> person.getName();
    System.out.println(sup.get());

    System.out.println("===等价于===");

    Supplier<String> sup1 = person::getName;
    System.out.println(sup1.get());
}
复制代码

3. 特定类型的任意对象的实例方法引用

String[] stringArray = { "Barbara", "James", "Mary", "John",
    "Patricia", "Robert", "Michael", "Linda" };
Arrays.sort(stringArray, String::compareToIgnoreCase);
//-------------------
@Test
public void test5(){
    BiPredicate<String,String> bp = (x,y)->x.equals(y);
    System.out.println(bp.test("Java情报局","Java情报局1"));
    System.out.println("===等价于===");

    BiPredicate<String,String> bp1 = String::equals;
    System.out.println(bp.test("Java情报局","Java情报局"));
}
复制代码

4. 构造器引用

将一个集合内元素复制到另外一个集合中。

public static <T, SOURCE extends Collection<T>, DEST extends Collection<T>>
    DEST transferElements( SOURCE sourceCollection, Supplier<DEST> collectionFactory) {
        
        DEST result = collectionFactory.get();
        for (T t : sourceCollection) {
            result.add(t);
        }
        return result;
}
复制代码

Supplier是一个函数式接口,您可使用lambda表达式调用方法TransferElements

Set<Person> rosterSetLambda =
    transferElements(roster, () -> { return new HashSet<>(); });
复制代码

使用构造器引用代替lambda表达式

Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new);
//Java编译器能够推断出要建立包含Person类型元素的HashSet集合,可简写
Set<Person> rosterSet = transferElements(roster, HashSet::new);
复制代码
Function<Integer,MyClass> fun = (n) -> new MyClass(n);
//等价于
Function<Integer,Person> fun = MyClass::new;
// 带两个参数的构造器引用就要用BiFunction,多个参数的话,还能够自定义一个这样的函数式接口
复制代码
@Test
public void test6(){
    Supplier<Person> sup = ()->new Person("Tom", IsoChronology.INSTANCE.date(1995, 6, 20), Person.Sex.MALE, "tom@qq.com");
    System.out.println(sup.get());
复制代码

构造器引用还能够建立数组

@Test
public void test7(){
    Function<Integer,String[]> fun = args -> new String[args];
    String[] strs = fun.apply(6);
    System.out.println(strs.length);
    
    System.out.println("===等价于===");
    
    Function<Integer,String[]> fun1 = String[]::new;
    String[] strs1 = fun1.apply(6);
    System.out.println(strs1.length);
}
复制代码

4、Stream——函数式数据处理

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

1. Stream是个啥

Stream(流) 是数据渠道,用于操做数据源(集合、数组等)所生成的元素序列。

集合讲的是数据,流讲的是计算!

?>tip

  • Stream 本身不会存储元素

  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream

  • Stream 操做是延迟执行的。这意味着他们会等到须要结果的时候才执行

流操做有两个重要特色

  • 流水线——不少流操做自己会返回一个流,这样多个操做就能够连接起来,造成一个大的流水线
  • 内部迭代——与迭代器显示迭代集合不一样,流的迭代操做都在背后进行

2. Stream 的操做三个步骤

  1. 建立 Stream 一个数据源(如:集合、数组),获取一个流
  2. 中间操做(一个中间操做链,对数据源的数据进行处理,造成一条流的流水线)
  3. 终止操做(一个终止操做,执行中间操做链,并产生结果)

2.1. 建立 Stream

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • default Stream<E> stream() : 返回一个顺序流

  • default Stream<E> parallelStream() : 返回一个并行流

由数组建立流

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)

由值建立流

可使用静态方法 Stream.of(), 经过显示值建立一个流。它能够接收任意数量的参数。

  • public static<T> Stream<T> of(T... values) : 返回一个流
由函数建立流:建立无限流

可使用静态方法 Stream.iterate() 和 Stream.generate(), 建立无限流。

  • 迭代

    • public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
  • 生成

    • public static<T> Stream<T> generate(Supplier<T> s) :
//建立 Stream
@Test
public void test1(){
  //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[10];
  Stream<Integer> stream1 = Arrays.stream(nums);

  //3. 经过 Stream 类中静态方法 of()
  Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);

  //4. 建立无限流
  //迭代
  Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
  stream3.forEach(System.out::println);

  //生成
  Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
  stream4.forEach(System.out::println);
}
复制代码

2.2. Stream 的中间操做

多个中间操做能够链接起来造成一个流水线,除非流水线上触发终止操做,不然中间操做不会执行任何的处理! 而在终止操做时一次性所有处理,称为“惰性求值”

2.2.1 筛选与切片
方法 描述
filter(Predicate p) 接收 Lambda , 从流中排除某些元素
distinct() 筛选,经过流所生成元素的 hashCode() 和 equals() 去除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
List<Person> persons = Person.createRoster();

//内部迭代:迭代操做 Stream API 内部完成
@Test
public void test2(){
  //全部的中间操做不会作任何的处理
  Stream<Person> stream = persons.stream()
    .filter((e) -> {
      System.out.println("测试中间操做");
      return e.getAge() <= 35;
    });

  //只有当作终止操做时,全部的中间操做会一次性的所有执行,称为“惰性求值”
  stream.forEach(System.out::println);
}

//外部迭代
@Test
public void test3(){
  Iterator<Person> it = persons.iterator();

  while(it.hasNext()){
    System.out.println(it.next());
  }
}

@Test
public void test4(){
  persons.stream()
    .filter((p) -> {
      System.out.println("大于25岁的成员:"); // && ||
      return (p.getAge()) >= 25;
    }).limit(3)
    .forEach(System.out::println);
}

@Test
public void test5(){
  persons.parallelStream()
    .filter((e) -> e.getAge() >= 20)
    .skip(2)
    .forEach(System.out::println);
}

@Test
public void test6(){
  persons.stream()
    .distinct()
    .forEach(System.out::println);
}
复制代码
2.2.2 映射
方法 描述
map(Function f) 接收一个函数做为参数,该函数会被应用到每一个元素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f) 接收一个函数做为参数,该函数会被应用到每一个元素上,产生一个新的 DoubleStream
mapToInt(ToIntFunction f) 接收一个函数做为参数,该函数会被应用到每一个元素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f) 接收一个函数做为参数,该函数会被应用到每一个元素上,产生一个新的 LongStream
flatMap(Function f) 接收一个函数做为参数,将流中的每一个值都换成另外一个流,而后把全部流链接成一个流
//映射
@Test
public void test1(){
  Stream<String> str = persons.stream()
    .map((e) -> e.getName());
  System.out.println("-------------------------------------------");
  List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
  Stream<String> stream = strList.stream()
    .map(String::toUpperCase);
  stream.forEach(System.out::println);

  System.out.println("---------------------------------------------");

  Stream<Character> stream3 = strList.stream()
    .flatMap(TestStreamAPI::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();
}
复制代码
2.2.3 排序
方法 描述
sorted() 产生一个新流,其中按天然顺序排序
sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序
@Test
public void test(){
  persons.stream()
    .map(Person::getName)
    .sorted()
    .forEach(System.out::println);

  System.out.println("------------------------------------");

  persons.stream()
    .sorted((x, y) -> {
      if(x.getAge() == y.getAge()){
        return x.getName().compareTo(y.getName());
      }else{
        return Integer.compare(x.getAge(), y.getAge());
      }
    }).forEach(System.out::println);
}
复制代码

2.3. Stream 的终止操做

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

2.3.1 查找与匹配
方法 描述
allMatch(Predicate p) 检查是否匹配全部元素
anyMatch(Predicate p) 检查是否至少匹配一个元素
noneMatch(Predicate p) 检查是否没有匹配全部元素
findFirst() 返回第一个元素
findAny() 返回当前流中的任意元素
count() 返回流中元素总数
max(Comparator c) 返回流中最大值
min(Comparator c) 返回流中最小值
forEach(Consumer c) 内部迭代(使用 Collection 接口须要用户去作迭 代,称为外部迭代。相反,Stream API 使用内部 迭代——它帮你把迭代作了)
public class TestStreamAPI2 {

	List<Person> persons = Person.createRoster();	
	//3. 终止操做
	@Test
	public void test1(){
			boolean bl = persons.stream()
				.allMatch((e) -> e.getGender().equals(Person.Sex.FEMALE));
			
			System.out.println("全部成员都为女性吗?"+bl);
			
			boolean bl1 = persons.stream()
				.anyMatch((e) -> e.getGender().equals(Person.Sex.FEMALE));
			
			System.out.println("成员中有女性吗?"+bl1);
			
			boolean bl2 = persons.stream()
				.noneMatch((e) -> e.getGender().equals(Person.Sex.FEMALE));
			
			System.out.println("成员中是否是没有女性?"+bl2);
	}
	
	@Test
	public void test2(){
		Optional<Person> op = persons.stream()
			.sorted(Comparator.comparingInt(Person::getAge))
			.findFirst();
		System.out.println("年龄最小的:"+op.get());
		
		Optional<Person> op2 = persons.parallelStream()
			.filter((e) -> e.getGender().equals(Person.Sex.MALE))
			.findAny();
		
		System.out.println("随便找个男的:"+op2.get());
	}
	
	@Test
	public void test3(){
		long count = persons.stream()
						 .filter((e) -> e.getGender().equals(Person.Sex.FEMALE))
						 .count();
		
		System.out.println("女生的人数:"+count);
		
		Optional<Integer> op = persons.stream()
			.map(Person::getAge)
			.max(Integer::compare);
		
		System.out.println("最大年龄:"+op.get());
		
		Optional<Person> op2 = persons.stream()
			.min((e1, e2) -> Integer.compare(e1.getAge(), e2.getAge()));
		
		System.out.println("最小年龄成员:"+op2.get());
	}
	
	//注意:流进行了终止操做后,不能再次使用
	@Test
	public void test4(){
		Stream<Person> stream = persons.stream()
		 .filter((e) -> e.getGender().equals(Person.Sex.FEMALE));
		
		long count = stream.count();
		
		stream.map(Person::getAge)
			.max(Integer::compare);
	}
}
复制代码
2.3.2 规约
方法 描述
reduce(T iden, BinaryOperator b) 能够将流中元素反复结合起来,获得一个值。 返回 T
reduce(BinaryOperator b) 能够将流中元素反复结合起来,获得一个值。 返回 Optional<T>

备注:map 和 reduce 的链接一般称为 map-reduce 模式,因 Google 用它来进行网络搜索而出名。

List<Person> persons = Person.createRoster();

//3. 终止操做:归约
@Test
public void test1(){
  List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
  Integer sum = list.stream()
    .reduce(0, (x, y) -> x + y);

  System.out.println(sum);
  System.out.println("----------------------------------------");

  Optional<Integer> op = persons.stream()
    .map(Person::getAge)
    .reduce(Integer::sum);
  System.out.println("全部成员的年龄和:"+op.get());
}

//需求:搜索名字中 “B” 出现的次数
@Test
public void test2(){
  Optional<Integer> sum = persons.stream()
    .map(Person::getName)
    .flatMap(TestStreamAPI1::filterCharacter)
    .map((ch) -> {
      if(ch.equals('B'))
        return 1;
      else 
        return 0;
    }).reduce(Integer::sum);

  System.out.println(sum.get());
}
复制代码
2.3.3 收集
方法 描述
collect(Collector c) 将流转换为其余形式。接收一个 Collector接口的 实现,用于给Stream中元素作汇总的方法

Collectors

Collector接口中方法的实现决定了如何对流执行收集操做(如收集到 List、Set、Map)。可是 Collectors 实用类提供了不少静态方法,能够方便地建立常见收集器实例,具体方法与实例以下表: docs.oracle.com/javase/8/do…

方法 返回类型 做用 示例
toList List 把流中元素收集到List List list= list.stream().collect(Collectors.toList());
toSet Set 把流中元素收集到Set Set set= list.stream().collect(Collectors.toSet());
toCollection Collection 把流中元素收集到建立的集合 Collectione mps=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting Long 计算流中元素的个数 long count = list.stream().collect(Collectors.counting());
summingInt Integer 对流中元素的整数属性求和 Integer sum = persons.stream() .collect(Collectors.summingInt(Person::getAge));
averagingInt Double 计算流中元素Integer属性的平均值 double avg= list.stream().collect(Collectors.averagingInt(Person::getAge));
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值。 如:平均值 IntSummaryStatistics iss= list.stream().collect(Collectors.summarizingInt(Person::getAge));
joining String 链接流中每一个字符串 String str= list.stream().map(Person::getName).collect(Collectors.joining());
maxBy Optional<T> 根据比较器选择最大值 Optionalmax= list.stream().collect(Collectors.maxBy(comparingInt(Person::getAge)));
minBy Optonal<T> 根据比较器选择最小值 Optional min = list.stream().collect(Collectors.minBy(comparingInt(Person::getAge)));
reducing 归约产生的类型 从一个做为累加器的初始值开始,利用BinaryOperator与 流中元素逐个结合,从而归 约成单个值 int total=list.stream().collect(Collectors.reducing(0, Person::getAge, Integer::sum));
collectingAndThen 转换函数返回的类型 包裹另外一个收集器,对其结果转换函数 int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map<K,List<T>> 根据某属性值对流分组,属性为K,结果为V Map<Person.Sex, List<Person>> map = persons.stream() .collect(Collectors.groupingBy(Person::getGender));
partitioningBy Map<Boolean,List<T>> 根据true或false进行分区 Map<Boolean, List<Person>> map = persons.stream() .collect(Collectors.partitioningBy((e) -> e.getAge() >= 50));
@Test
public void test3(){
  List<String> list = persons.stream()
    .map(Person::getName)
    .collect(Collectors.toList());
  list.forEach(System.out::println);
}

@Test
public void test4(){
  Optional<Integer> max = persons.stream()
    .map(Person::getAge)
    .collect(Collectors.maxBy(Integer::compare));

  System.out.println("最大年龄:"+max.get());

  Optional<Person> op = persons.stream().min(Comparator.comparingInt(Person::getAge));

  System.out.println("最小年龄的成员:"+op.get());

  Integer sum = persons.stream()
    .collect(Collectors.summingInt(Person::getAge));

  System.out.println("全部成员年龄和:"+sum);

  IntSummaryStatistics dss = persons.stream()
    .collect(Collectors.summarizingInt(Person::getAge));

  System.out.println("最大年龄:"+dss.getMax());
}

//分组
@Test
public void test5(){
  Map<Person.Sex, List<Person>> map = persons.stream()
    .collect(Collectors.groupingBy(Person::getGender));

  System.out.println("按性别分组:"+map);
}

//多级分组
@Test
public void test6(){
  Map<Person.Sex, Map<String, List<Person>>> map = persons.stream()
    .collect(Collectors.groupingBy(Person::getGender, Collectors.groupingBy((e) -> {
      if(e.getAge() >= 60)
        return "老年";
      else if(e.getAge() >= 35)
        return "中年";
      else
        return "成年";
    })));

  System.out.println(map);
}

//分区
@Test
public void test7(){
  Map<Boolean, List<Person>> map = persons.stream()
    .collect(Collectors.partitioningBy((e) -> e.getAge() >= 50));

  System.out.println(map);
}
@Test
public void test8(){
  String str = persons.stream()
    .map(Person::getName)
    .collect(Collectors.joining("," , "----", "----"));

  System.out.println(str);
}

@Test
public void test9(){
  Optional<Integer> sum = persons.stream()
    .map(Person::getAge)
    .collect(Collectors.reducing(Integer::sum));
  System.out.println(sum.get());
}
复制代码

3. 并行流与串行流

先说说并行和并发

并发是两个任务共享时间段,并行则是两个任务在同一时间发生,好比运行在多核CPU上。

lbvsVU.png

并行流就是把一个内容分红多个数据块,并用不一样的线程分别处理每一个数据块的流

Java 8 中将并行进行了优化,咱们能够很容易的对数据进行并行操做。Stream API 能够声明性地经过 parallel()sequential() 在并行流与顺序流之间进行切换。若是想从一个集合类建立一个流,调用parallerStream就能够获取一个并行流。

public static long parallelSum(long n) {
    return Stream.iterate(1L, i -> i + 1)
        .limit(n)
        .parallel()    //将流转化为并行流
        .reduce(0L, Long::sum);
}
复制代码

配置并行流使用的线程池

使用流的parallel方法,你可能会想到,并行流用的线程是从哪儿来的?有多少个?怎么自定义?

并行流内部使用了默认的ForkJoinPool(分支/合并框架),它默认的线程数量就是你的处理器数量,这个值是由Runtime.getrRuntime().acailable-Processors()获得。

你能够经过系统属性java.util.concurrent.ForkJoinPool.common.parallelism来改变线程池大小,以下

System.setProperty("java.util.concurrent.ForkJoinPool.common.parallelism","12");,这是一个全局设置,所以会影响代码中全部的并行流(目前还没法专门为某个并行流指定该值,通常而言,让ForkJoinPool的大小等于处理器数量是个不错的默认值)。

高效使用并行流

  • 并行流并非老是比顺序流快
  • 留意装箱。自动装箱和拆箱操做会大大下降性能,Java8 中有原始类型流(IntStream、LongStream...)来避免这种操做
  • 有些操做自己在并行流上的性能就比顺序流差,特别是 limit 和 findFirst 等依赖元素顺序的操做,他们在并行流上执行的代价就很是大
  • 还要考虑流的操做流水线的总计算成本
  • 对于较小的数据量,不必使用并行流
  • 要考虑流背后的数据结构是否易于分解,好比,ArrayList 的拆分效率比 LinkedList 高不少,前者无需遍历
  • 还要考虑终端操做中合并步骤的代价是大是小(好比Collector中的combiner方法)

4. Fork/Join 框架

并行流背后使用的基础框架就是 Java7 中引入的分支/合并框架

Fork/Join(分支/合并)框架的目的是以递归方式将能够并行的任务拆分(fork)成更小的任务,而后将每一个任务的结果合并 (join)起来生成总体效果。它是ExectorService接口的一个实现,把子任务分配给线程池(称为ForkJoinPool)中的工做线程。

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

fork-join.png

// 用分支/合并框架 并行求和
public class ForkJoinSumCalculator extends RecursiveTask<Long> {

    private final long[] numbers;
    private final int start;
    private final int end;

    //再也不将任务分解为子任务的数组大小
    public static long THRESHOLD = 100;

    //公共构造器用于建立主任务
    public ForkJoinSumCalculator(long[] numbers) {
        this(numbers, 0, numbers.length);
    }

    //私有构造器用于以递归方式为主任务建立子任务
    private ForkJoinSumCalculator(long[] numbers, int start, int end) {
        this.numbers = numbers;
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        int length = end - start;
        //若是大小小于等于阈值,顺序计算结果
        if (length <= THRESHOLD) {
            return computerSequntially();
        }

        ForkJoinSumCalculator leftTask = new ForkJoinSumCalculator(numbers, start, start + length / 2);

        leftTask.fork();

        ForkJoinSumCalculator rightTask = new ForkJoinSumCalculator(numbers, start + length / 2, end);

        Long rightResult = rightTask.compute();   //同步执行第二个任务,
        Long leftResult = leftTask.join(); // 读取第一个子任务的结果,若是还没有完成就等待
        return rightResult + leftResult;
    }


    // 子任务再也不可分时计算和
    private long computerSequntially() {
        long sum = 0;
        for (int i = start; i < end; i++) {
            sum += numbers[i];
        }
        return sum;
    }

    public static long forkJoimSum(long n) {
        long[] numbers = LongStream.rangeClosed(1, n).toArray();
        ForkJoinTask<Long> task = new ForkJoinSumCalculator(numbers);
        return new ForkJoinPool().invoke(task);
    }

    public static void main(String[] args) {
        System.out.println("sum:" + forkJoimSum(10000));
    }
}
复制代码

Fork/Join 框架与传统线程池的区别

采用 “工做窃取”模式(work-stealing): 当执行新的任务时它能够将其拆分红更小的任务执行,并将小任务加到线程队列中,而后再从一个随机线程的队列中偷一个并把它放在本身的队列中。

相对于通常的线程池实现,fork/join框架的优点体如今对其中包含的任务的处理方式上,在通常的线程池中,若是一个线程正在执行的任务因为某些缘由没法继续运行,那么该线程会处于等待状态,而在fork/join框架实现中,若是某个子问题因为等待另一个子问题的完成而没法继续运行,那么处理该子问题的线程会主动寻找其余还没有运行的子问题来执行,这种方式减小了线程的等待时间,提升了性能。

使用Fork/Join框架的最佳作法

  • 对一个任务调用join方法会阻塞调用方,直到该任务做出结果。所以,有必要在两个子任务的计算都开始以后再调用它
  • 不该该在 RecursiveTask 内部使用 ForkJoinPool 的 invoke 方法。相反,你应该始终直接调用 compute 或fork 方法,只有顺序代码才应该用 invoke 来启动并行计算

工做窃取

fork-join-steal.jpg

5. Spliterator

“可分迭代器”——spliterator,和Iterator同样,也用于遍历数据源中的元素,它是为了并行执行而设计。

Java8 为集合框架中包含的全部数据结构都提供了一个默认的 Spliterator 方法。集合实现了Spliterator接口,接口提供了一个Spliterator方法。

spliterator

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

传统上,Java中实现接口的类必须为接口中定义的每一个方法提供一个实现类,或者从父类中继承它的实现。但若是类库的设计者须要修改接口,加入新的方法,这种方式就会出现问题。全部使用该接口的实体类为了适配新的接口约定都须要进行修改(要是这么不兼容的话,早晚被淘汰)。因此,Java8为了解决这一问题引入了一种新的机制。Java8中的接口支持在声明方法的同时提供实现。其一,Java8容许在接口中声明静态方法。其二,Java8引入的新功能——默认方法,经过默认方法能够指定接口方法的默认实现(所以,实现接口的类若是不显式的提供该方法的具体实现,就会自动继承默认的实现,这种机制可使你平滑的进行接口的优化和升级)。

默认方法

Java 8中容许接口中包含具备具体实现的方法,该方法称为 “默认方法”,默认方法使用 default 关键字修饰。

interface MyFunc<T>{
    T func(int a);

    default String getName(){
        return "hello java8";
    }
}
复制代码
@Test
public void test1(){
    List<Integer> list = Arrays.asList(22,11,33,55,4);
    //sort是List接口中的默认方法,naturalOrder是Comparator的静态方法
    list.sort(Comparator.naturalOrder());
    for (Integer integer : list) {
        System.out.println(integer);
    }
}
复制代码

default-method.png

默认方法的”类优先”原则

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

  • 选择父类中的方法。若是一个父类提供了具体的实现,那么接口中具备相同名称和参数的默认方法会被忽略。

  • 接口冲突。若是一个父接口提供一个默认方法,而另外一个接口也提供了一个具备相同名称和参数列表的方法(无论方法是不是默认方法),那么必须覆盖该方法来解决冲突

interface MyFunc<T> {
    default String getName() {
        return "hello java8";
    }
}

interface MyFunc1 {
    default String getName() {
        return "hello Java情报局";
    }
}

class MyClass implements MyFunc, MyFunc1 {

    @Override
    public String getName() {
        return MyFunc1.super.getName();
    }
}
复制代码

JavaAPI的设计者们充分利用了默认方法,为集合接口和类新增了不少新的方法。


6、Optional 类

1. 用 Optional 取代 null

当你碰到程序中有一个NullPointerException时的第一冲动是不就是赶忙找到代码,添加一个if语句,检查下??

NullPointerException是Java程序开发中典型的异常。为了不这种异常,咱们的代码有可能充斥着一层又一层的深度嵌套的null检查,代码可读性极差。

Optional类(java.util.Optional) 是一个容器类,表明一个值存在或不存在, 原来用 null 表示一个值不存在,如今 Optional 能够更好的表达这个概念。而且能够避免空指针异常。

变量存在时,Optional类知识对类简单封装。变量不存在时,缺失的值就会被建模成一个“空”的Optional对象,由方法Optional.empty()返回。

经常使用方法:

  • 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

2. Optional 实例

2.1 建立Optional对象

@Test
public void test(){

    Optional<Person> optional = Optional.empty();  //建立一个空Optional

    Optional<Person> op = Optional.of(new Person());
    Person p = op.get();
    System.out.println(p);   //Person{name='null', birthday=null, gender=null, emailAddress='null'}

    Person person = null;
    Optional<Person> op1 = Optional.of(person); //person为null,抛出NullPointerException

    Optional<Person> op2 = Optional.ofNullable(person);   //建立容许null值得Optional对象

}
复制代码

2.2 optional 对象操做

@Test
public void test4(){
    Person person = new Person("Tom",IsoChronology.INSTANCE.date(1999, 7, 15),Person.Sex.FEMALE, "Tom@360.com")
    Optional<Person> op = Optional.ofNullable(person);

    Optional<String> op1 = op.map(Person::getName);
    System.out.println(op1.get());
    
    /** * 使用 map 从 optional 对象中提取和转换值 * 若是想提取人员姓名,以前须要判断persion !=null,Optional提供了一个map方法,对其处理 **/
    Optional<String> op2 = op.map(Person::getName);
    System.out.println(op2.get());

    //使用 flatMap 连接 optional 对象
    Optional<String> op3 = op.flatMap((e) -> Optional.of(e.getName()));
    System.out.println(op3.get());
    
    //TODO
}
复制代码

7、CompletableFuture —— 组合式异步编程

1. Future接口

Future接口在 Java 5 中被引入,设计初衷是对未来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。在 Future中触发那些潜在耗时的操做把调用线程解放出来,让它能继续执行其余有价值的工做, 再也不须要等待耗时的操做完成。打个比方,你能够把它想象成这样的场景:你拿了一袋衣服到你中意的干洗店去洗衣服。干洗店员工会给你张发票,告诉你何时你的衣服会洗好(这就 是一个Future事件)。衣服干洗的同时,你能够去作其余的事情。Future的另外一个优势是它比 更底层的Thread更易用。要使用Future,一般你只须要将耗时的操做封装在一个Callable对象中,再将它提交给ExecutorService,就能够了。下面这段代码展现了Java 8以前使用 Future的一个例子。

ExecutorService executor = Executors.newCachedThreadPool();
Future<Double> future = executor.submit(new Callable<Double>() {
    public Double call() {
        return doSomeThings();    //异步方式在新的线程中执行操做
    }
});
//doSomethingElse(); //异步操做进行的同时,能够作其余事情
try {
    //获取异步操做的结果,若是阻塞,等1秒后退出
    Double result = future.get(1, TimeUnit.SECONDS);   
} catch (ExecutionException | InterruptedException | TimeoutException e) {
}
复制代码

1.1 Future接口的局限性

虽然Future以及相关使用方法提供了异步执行任务的能力,可是对于结果的获取倒是很不方便,只能经过阻塞或者轮询的方式获得任务的结果。阻塞的方式显然和咱们的异步编程的初衷相违背,轮询的方式又会耗费无谓的CPU资源,并且也不能及时地获得计算结果,为何不能用观察者设计模式当计算结果完成及时通知监听者呢?

Java的一些框架,好比Netty,本身扩展了Java的 Future接口,提供了addListener等多个扩展方法。Google guava也提供了通用的扩展Future:ListenableFuture、SettableFuture 以及辅助类Futures等,方便异步编程。

做为正统的Java类库,是否是应该作点什么,增强一下自身库的功能呢?

在Java 8中, 新增长了一个包含50个方法左右的类: CompletableFuture,提供了很是强大的Future的扩展功能,能够帮助咱们简化异步编程的复杂性,提供了函数式编程的能力,能够经过回调的方式处理计算结果,而且提供了转换和组合CompletableFuture的方法。

好比实现下面一些例子:

  • 将两个异步计算合并为一个——这两个异步计算之间相对独立,同时第二个又依赖于第一个的结果
  • 等待Future集合中的全部任务都完成
  • 仅等待Future集合中最快结束的任务完成(有可能由于它们试图经过不一样的方式计算同 一个值),并返回它的结果
  • 经过编程方式完成一个Future任务的执行(即以手工设定异步操做结果的方式)
  • 应对Future的完成事件(即当Future的完成事件发生时会收到通知,并能使用Future 计算的结果进行下一步的操做,不仅是简单地阻塞等待操做的结果)

1.2 使用CompletableFuture 构建异步应用

public class TestCompletableFuture {
    public static CompletableFuture<Integer> compute() {
        final CompletableFuture<Integer> future = new CompletableFuture<>();
        return future;
    }
    public static void main(String[] args) throws Exception {
        final CompletableFuture<Integer> f = compute();
        class Client extends Thread {
            CompletableFuture<Integer> f;
            Client(String threadName, CompletableFuture<Integer> f) {
                super(threadName);
                this.f = f;
            }
            @Override
            public void run() {
                try {
                    System.out.println(this.getName() + ": " + f.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        }
        new Client("Client1", f).start();
        new Client("Client2", f).start();
        System.out.println("waiting");
        f.complete(100);
        System.in.read();
    }
}
复制代码

8、新时间日期 API

1. 使用 LocalDate、LocalTime、LocalDateTime

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

    @Test
    public void test1(){
        LocalDate date = LocalDate.of(2020,01,03);
        Month month = date.getMonth();
        System.out.println(month);    //JANUARY
    
        DayOfWeek dayOfWeek = date.getDayOfWeek();
        System.out.println(dayOfWeek);   //FRIDAY
    
        int len = date.lengthOfMonth();
        System.out.println(len);  //31
        //使用TemporalField(ChronoField枚举实现了该接口)读取LocalDate的值
        int year = date.get(ChronoField.YEAR);
        System.out.println(year);  //2020
    
        LocalDate ld = LocalDate.parse("2020-01-03");
        System.out.println(ld);   //2020-01-03
    
        LocalTime time = LocalTime.of(19,56,11);
        System.out.println(time);  //19:56:11
    
        LocalDateTime ldt = LocalDateTime.now();
        LocalDateTime l1 = LocalDateTime.of(2020,01,03,18,48);
        System.out.println(l1);  //2020-01-03T18:48
    
        LocalDateTime l2 = l1.plusYears(3);
        System.out.println(l2);     //2023-01-03T18:48
    
        LocalDateTime l3 = l1.minusMonths(1);
        System.out.println(l3);  //2019-12-03T18:48
        System.out.println(l3.getMinute()+","+l3.getYear());   //48,2019
    }
    复制代码

2. Instant 时间戳

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

    @Test
    public void test2(){
        Instant ins = Instant.now();  //默认使用 UTC 时区
        OffsetDateTime odt = ins.atOffset(ZoneOffset.ofHours(8));
        System.out.println(odt);
        System.out.println(ins.getNano());
        Instant ins2 = Instant.ofEpochSecond(5);
        System.out.println(ins2);
    }
    复制代码

3. Duration 和 Period

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

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

    @Test
    public void test3(){
        Instant ins1 = Instant.now();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        Instant ins2 = Instant.now();
        System.out.println("所耗费时间为:" + Duration.between(ins1, ins2));   
    
        System.out.println("----------------------------------");
        LocalDate ld1 = LocalDate.now();
        LocalDate ld2 = LocalDate.of(2019, 1, 1);
    
        Period pe = Period.between(ld2, ld1);
        System.out.println(pe.getYears());  
        System.out.println(pe.getMonths());
        System.out.println(pe.getDays());
    }
    复制代码

4. 日期的操纵

  • 经过 withXXX 方法修改 LocalDate 的属性

  • TemporalAdjuster : 时间校订器。有时咱们可能须要获取例如:将日期调整到“下个周日”等操做。

  • TemporalAdjusters : 该类经过静态方法提供了大量的常 用 TemporalAdjuster 的实现。

    @Test
    public void test(){
        LocalDate date = LocalDate.now();
        //经过withAttributer方法修改LocalDate的属性
        LocalDate date1 = date.with(ChronoField.ALIGNED_WEEK_OF_YEAR,9);
        LocalDate date2 = date.withYear(2019);
        LocalDate date3 = date.withDayOfMonth(11);  //修改成11号
        System.out.println(date1);
        //下周日
        LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
        System.out.println(nextSunday);
    }
    复制代码

5. 解析与格式化

java.time.format.DateTimeFormatter 类:该类提供了三种格式化方法:

  • 预约义的标准格式

  • 语言环境相关的格式

  • 自定义的格式

    @Test
    public void test(){
        //DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE;
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss E");
    
        LocalDateTime ldt = LocalDateTime.now();
        String strDate = ldt.format(dtf);
    
        System.out.println(strDate); //2020年01月03日 20:32:14 星期五
    
        LocalDateTime newLdt = ldt.parse(strDate, dtf);
        System.out.println(newLdt);  //2020-01-03T20:32:14
    }
    复制代码

时区的处理

  • Java8 中加入了对时区的支持,带时区的时间为分别为: ZonedDate、ZonedTime、ZonedDateTime

    其中每一个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式 例如 :Asia/Shanghai 等

    ZoneId:该类中包含了全部的时区信息

    • getAvailableZoneIds() : 能够获取全部时区时区信息
  • of(id) : 用指定的时区信息获取 ZoneId 对象

    @Test
    public void test(){
        Set<String> set = ZoneId.getAvailableZoneIds();  //遍历时区
        set.forEach(System.out::println);
        LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
        System.out.println(ldt);
    
        ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("US/Pacific"));
        System.out.println(zdt);
    }
    复制代码

9、重复注解与类型注解

注解

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

Java中注解是一种对程序元素进行配置,提供附加信息的机制(Java8以前,注解只能被用在声明上)

重复注解

Java8 以前不容许上边这样的重复注解,因此通常会经过一些惯用手法绕过这一限制。能够声明一个新的注解,它包含了你但愿重复的注解数组。

建立一个重复注解

  1. 将注解标记为**@Repeatable**
  2. 提供一个注解的容器
import java.lang.annotation.Repeatable;
@Repeatable(Authors.class)
public @interface Author {
    String name();
}
复制代码
public @interface Authors {
    Author[] value();
}
复制代码
@Author(name = "Java")
@Author(name = "Android")
public class Book {
    public static void main(String[] args) {
        Author[] authors = Book.class.getAnnotationsByType(Author.class);
        Arrays.asList(authors).forEach(s->{
            System.out.println(s.name());
        });
    }
}
复制代码

类型注解

Java8 开始,注解能够应用于任何类型。包括new操做符、类型转换、instanceof检查、范型类型参数,以及implemtnts和throws子句。

@NotNull String name = person.getName();    //getName不返回空

List<@NotNull Person> persons = new ArrayList<>();  //persons老是非空
复制代码

10、其余语言特性

原子操做

java.util.concurrent.atomic 包提供了多个对数字类型进行操做的类,好比AtomicInteger和AtomicLong,它们支持对单一变量的原子操做。这些类在Java 8中新增了更多的方法支持。

  • getAndUpdate——以原子方式用给定的方法更新当前值,并返回变动以前的值

  • updateAndGet——以原子方式用给定的方法更新当前值,并返回变动以后的值

  • getAndAccumulate——以原子方式用给定的方法对当前及给定的值进行更新,并返回变动以前的值

  • accumulateAndGet——以原子方式用给定的方法对当前及给定的值进行更新,并返回变动以后的值

Adder和Accumulator

多线程的环境中,若是多个线程须要频繁地进行更新操做,且不多有读取的动做(好比,在统计计算的上下文中),Java API文档中推荐大使用新的类LongAdder、LongAccumulator、Double-Adder以及DoubleAccumulator,尽可能避免使用它们对应的原子类型。这些新的类在设计之初就考虑了动态增加的需求,能够有效地减小线程间的竞争。

LongAddr 和 DoubleAdder 类都支持加法操做 , 而 LongAccumulator 和 DoubleAccumulator可使用给定的方法整合多个值。

ConcurrentHashMap

ConcurrentHashMap类的引入极大地提高了HashMap现代化的程度,新引入的ConcurrentHashMap对并发的支持很是友好。ConcurrentHashMap容许并发地进行新增和更新操做,由于它仅对内部数据结构的某些部分上锁。所以,和另外一种选择,即同步式的Hashtable比较起来,它具备更高的读写性能。

  1. 性能

    为了改善性能,要对ConcurrentHashMap的内部数据结构进行调整。典型状况下,map的条目会被存储在桶中,依据键生成哈希值进行访问。可是,若是大量键返回相同的哈希值,因为桶是由List实现的,它的查询复杂度为O(n),这种状况下性能会恶化。在Java 8中,当桶过于臃肿时,它们会被动态地替换为排序树(sorted tree),新的数据结构具备更好的查询性能(排序树的查询复杂度为O(log(n)))。注意,这种优化只有当键是能够比较的(好比String或者Number类)时才可能发生。

  2. 类流操做

    ConcurrentHashMap支持三种新的操做,这些操做和你以前在流中所见的很像:

  • forEach——对每一个键值对进行特定的操做

  • reduce——使用给定的􏰤简函数(reduction function),将全部的键值对整合出一个结果􏰝

  • search——对每个键值对执行一个函数,直到函数的返回值为一个非空值

    以上每一种操做都支持四种形式,接受使用键、值、Map.Entry以及键值对的函数:

  • 使用键和值的操做(forEach、reduce、search)

  • 使用键的操做(forEachKey、reduceKeys、searchKeys)

  • 使用值的操做 (forEachValue、reduceValues、searchValues)

  • 使用Map.Entry对象的操做(forEachEntry、reduceEntries、searchEntries)

注意,这些操做不会对ConcurrentHashMap的状态上锁。它们只会在运行过程当中对元素进行操做。应用到这些操做上的函数不该该对任何的顺序,或者其余对象,或在计算过程发生变化的值,有依赖。 除此以外,你须要为这些操做指定一个并发阈值。若是通过预预估当前map的大小小于设定的阈值,操做会顺序执行。使用值1开开启基于通用线程池的最大并行。使用值Long.MAX_VALUE设定程序以单线程执行操做。下面这个例子中,咱们使用reduceValues试图找出map中的最大值:

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>(); 
Optional<Integer> maxValue = Optional.of(map.reduceValues(1, Integer::max));
复制代码

注意,对int、long和double,它们的reduce操做各有不一样(好比reduceValuesToInt、reduceKeysToLong等)。

  1. 计数

    ConcurrentHashMap类提供了一个新的方法,名叫mappingCount,它以长整型long返回map中映射的数目。咱们应该尽可能使用这个新方法,而不是老的size方法,size方法返回的类型为int。这是由于映射的数量多是int没法表示的。

  2. 集合视图

    ConcurrentHashMap类还提供了一个名为KeySet的新方法,该方法以Set的形式返回ConcurrentHashMap的一个视图(对map的修改会反映在该Set中,反之亦然)。你也可使用新的静态方法newKeySet,由ConcurrentHashMap建立一个Set。

Arrays

Arrays类提供了不一样的静态方法对数组进行操做。如今,它又包括了四个新的方法(它们都有特别重载的变量)

  • parallelSort:parallelSort方法会以并发的方式对指定的数组进行排序,你可使用天然顺序,也能够

    为数组对象定义特别的Comparator

  • setAll和parallelSetAll:setAll和parallelSetAll方法能够以顺序的方式也能够用并发的方式,使用提供的函数 计算每个元素的值,对指定数组中的全部元素进行设置

  • parallelPrefix:parallelPrefix方法以并发的方式,用用户提供的二进制操做符对给定数组中的每一个元素

    进行累积计算

Number

Number类中新增方法

  • Short、Integer、Long、Float和Double类提供了静态方法sum、min和max
  • Integer和Long类提供了compareUnsigned、divideUnsigned、remainderUnsigned 和toUnsignedLong方法来处理无符号数。
  • Integer和Long类也分别提供了静态方法parseUnsignedInt和parseUnsignedLong 将字符解析为无符号int或者long类型。
  • Byte和Short类提供了toUnsignedInt和toUnsignedLong方法经过无符号转换将参数转化为 int 或 者 long 类型。相似地, Integer 类如今也提供了静态方法 toUnsignedLong。
  • Double和Float类提供了静态方法isFinite,能够检查参数是否为有限浮点数。
  • Boolean类如今提供了静态方法logicalAnd、logicalOr和logicalXor,能够在两个 boolean之间执行and、or和xor操做。
  • BigInteger 类提供了 byteValueExact 、 shortValueExact 、 intValueExact 和 longValueExact,能够将BigInteger类型的值转换为对应的基础类型。不过,若是在转换过程当中有信息的丢失,方法会抛出算术异常。

Math

若是Math中的方法在操做中出现ຼ出,Math类提供了新的方法能够抛出算术异常。支持这一异常的方法包括使用int和long参数的addExact、subtractExact、multipleExact、 incrementExact、decrementExact和negateExact。此外,Math类还新增了一个静态方法 toIntExact,能够将long值转换为int值。其余的新增内容包括静态方法floorMod、floorDiv 和nextDown。

Files

Files类最引人注目的改变是,你如今能够用文件直接产生流

  • Files.list——生成由指定目录中全部条目构成的Stream<Path>。这个列表不是递归包含的。因为流是延迟消费的,处理包含内容很是庞大的目录时,这个方法很是有用
  • Files.walk——和Files.list有些相似,它也生成包含给定目录中全部条目的 Stream<Path>。不过这个列表是递归的,你能够设定递归的深度。注意,该遍历是依照深度优先进行的
  • Files.find—— 经过递归地遍历一个目录找到符合条件的条目,并生成一个 Stream<Path> 对象

String

String类也新增􏱗了一个静态方法,名叫join。它能够用一个分隔符将多个字符串􏶘接起来。和咱们之前使用的apache提供的StringUtils.join同样。

Reflection

Reflection API的变化就是为了支持Java 8中注解机制的改变。 除此以外,Relection接口的另外一个变化是新增了能够查询方法参数信息的API,好比,你如今可使用新的java.lang.reflect.Parameter类查询方法参数的名称和修饰符。


FAQ

  • 说说你知道的Java8 有哪写新特性?

  • 什么是lambda表达式?有啥优势?

  • ConcurrentHashMap 在Java8 和 Java7的实现区别?

  • 能说说 Java 8 改进的JVM 不?

  • hashMap原理,java8作了哪些改变?

  • 外部迭代和内部迭代,你晓得吧?


参考

《Java 8实战》

《Java 8函数式编程》

Java 8官方文档

某免费视频学习网站

相关文章
相关标签/搜索