X Tutup
Skip to content

Latest commit

 

History

History
1800 lines (1263 loc) · 71.1 KB

File metadata and controls

1800 lines (1263 loc) · 71.1 KB

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

Java8早在2014年3月就发布了,现在才做一个详细的总结,嗯,为时不晚,JVM、JavaFX和一些安全问题,目前用不到,就没有深究。

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

JDK 8有什么新功能

  • Java 编程语言( Java Programming Language)

  • Lambda表达式:一个新的语言特性, 它们使您能够将函数视为方法参数,或将代码视为数据

  • 方法引用: 方法引用为已经有名称的方法提供易于阅读的lambda表达式

  • 默认方法:使用 default 关键字为接口定义默认方法(有实现的方法)

  • 重复注解提供了将同一注解多次应用于同一声明或类型使用的能力

  • 类型注解提供了在使用类型的任何地方应用注解的能力,而不仅仅是在声明上

  • Java8 增强了类型推断

  • 方法参数反射

  • java.util.function: 一个新的包,它包含为lambda表达式和方法引用提供目标类型的通用功能接口

  • 集合(Collections)

  • java.util.stream包中新增了 Stream API ,用来支持对元素流的函数式操作

  • 改进了有键冲突问题的 HashMap

  • 精简运行时(Compact Profiles)

  • 安全性(Security)

  • JavaFX

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

  • 国际化(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

  • java.util.concurrent.ConcurrentHashMap 支持基于新添加的streams功能和lambda表达式的聚合操作

  • java.util.concurrent.atomic 提供了一组原子变量类, 对于单个变量支持无锁、线程安全操作的工具类

  • java.util.concurrent.ForkJoinPool 用于补充ExecutorService

  • java.util.concurrent.locks.StampedLock 提供了基于功能的锁,有三种模式用于控制读/写访问

Java8 特别强大的是Lambda 表达式和Stream,通过他两新增和增强了很多类,如下

新增类: java.lang.invoke java.util.functionjava.util.stream

修改类:

modify-class.png


Lambda表达式

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

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

Lambda表达式使您能够封装单个行为单元并将其传递给其他代码。如果希望对集合的每个元素、流程完成时或流程遇到错误时执行某个操作,可以使用lambda表达式。Lambda表达式由以下特性支持:

  • 方法引用: 对于已经有名称的方法,是紧凑的、易于阅读的lambda表达式
  • 默认方法: 使您能够向库的接口添加新功能,并确保与为这些接口的旧版本编写的代码的二进制兼容性。它们是接口方法,具有实现和方法签名开头的默认关键字。此外,您可以在接口中定义静态方法。
  • Java SE 8中利用Lambda表达式和流的新的和增强的api描述了利用Lambda表达式和流的新的和增强的类

1. 为什么要使用Lambda表达式

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

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

hello-lambda

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 需要两个参数,并且有返回值(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<>();

官方提供的示例,假设你要开发一个社交软件,那个缺打的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)
        );

        /**
         *  ===================================================================
         *  方法引用:
         * 这个lambda表达式调用现有的方法,所以您可以使用方法引用而不是lambda表达式
         *  Person::compareByAge 等同于 (a, b) -> Person.compareByAge(a, b)
         */
        Arrays.sort(rosterAsArray, Person::compareByAge);

        System.out.println();

        // Reference to an Instance Method of a Particular Object
        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);
        for (Person person : rosterAsArray) {
            person.printPerson();
        }

        System.out.println();

        /**
         * 引用特定类型的任意对象的实例方法
         * String::compareToIgnoreCase 格式化 (String a, String b) ,
         *  并去调用 a.compareToIgnoreCase(b)
         */
        String[] stringArray = { "Barbara", "James", "Mary", "John",
                "Patricia", "Robert", "Michael", "Linda" };
        Arrays.sort(stringArray, String::compareToIgnoreCase);
        for (String s : stringArray) {
            System.out.println(s);
        }


        System.out.println();

        // 通过 stream 将计算集合的和
        Integer[] intArray = {1, 2, 3, 4, 5, 6, 7, 8 };
        List<Integer> listOfIntegers =
                new ArrayList<>(Arrays.asList(intArray));
        System.out.println("Sum of integers: " +
                listOfIntegers
                        .stream()
                        .reduce(Integer::sum).get());
    }
}

函数式接口

什么是函数式接口

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

自定义函数式接口

@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表达式和方法引用提供目标类型

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

函数式接口 参数类型 返回类型 用途
Consumer T void 对类型为T的对象应用操作,包含方法:void accept(T t)
Supplier T 返回类型为T的对象,包 含方法:T get();
Function<T,R> T R 对类型为T的对象应用操作,并返回结果。结果是R类型的对象。包含方法:R apply(T t);
Predicate 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等就是避免自动装箱的操作。一般,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀。


方法引用

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

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

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

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

eg:

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

方法引用类型

Java 8 提供了4种方法引用

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

1. 静态方法引用

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);
}

方法引用实例

Person[] rosterAsArray =
    roster.toArray(new Person[roster.size()]);

//Comparator 是一个函数式接口??
class PersonAgeComparator
    implements Comparator<Person> {
    public int compare(Person a, Person b) {
        return a.getBirthday().compareTo(b.getBirthday());
    }
}

// 实现比较器方法
Arrays.sort(rosterAsArray, new PersonAgeComparator());

// 使用Lambda表达式代替创建实现Comparator的实例类
Arrays.sort(rosterAsArray,
            (Person a, Person b) -> {
                return a.getBirthday().compareTo(b.getBirthday());
            }
           );

// 比较年龄的方法在Person.compareByAge的已经存在,所以可以使用方法引用
Arrays.sort(rosterAsArray, Person::compareByAge);

// 使用方法引用 == 下边简化后的 Lambda 表达式
Arrays.sort(rosterAsArray, (a, b) -> Person.compareByAge(a, b));

// 还可以直接使用Comparator比较器的方法
Arrays.sort(rosterAsArray,Comparator.comparing(Person::getBirthday));

Stream API——函数式数据处理

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 Streamstream() : 返回一个顺序流

  • default StreamparallelStream() : 返回一个并行流

由数组创建流

Java8 中的 Arrays 的静态方法 stream() 可 以获取数组流:

  • static Streamstream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)

  • public static LongStream stream(long[] array)

  • public static DoubleStream stream(double[] array)

由值创建流

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

  • public staticStreamof(T... values) : 返回一个流
由函数创建流:创建无限流

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

  • 迭代

    • public staticStreamiterate(final T seed, final UnaryOperatorf)
  • 生成

    • public staticStreamgenerate(Suppliers) :

2.2. Stream 的中间操作

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

2.1 筛选与切片
方法 描述
filter(Predicate p) 接收 Lambda , 从流中排除某些元素
distinct() 筛选,通过流所生成元素的 hashCode() 和 equals() 去 除重复元素
limit(long maxSize) 截断流,使其元素不超过给定数量
skip(long n) 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素 不足 n 个,则返回一个空流。与 limit(n) 互补
2.2 映射
方法 描述
map(Function f) 接收一个函数作为参数,该函数会被应用到每个元 素上,并将其映射成一个新的元素
mapToDouble(ToDoubleFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 DoubleStream
mapToInt(ToIntFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 IntStream。
mapToLong(ToLongFunction f) 接收一个函数作为参数,该函数会被应用到每个元 素上,产生一个新的 LongStream
flatMap(Function f) 接收一个函数作为参数,将流中的每个值都换成另 一个流,然后把所有流连接成一个流
2.3 排序
方法 描述
sorted() 产生一个新流,其中按自然顺序排序
sorted(Comparator comp) 产生一个新流,其中按比较器顺序排序

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 使用内部 迭代——它帮你把迭代做了)
2.3.2 规约
方法 描述
reduce(T iden, BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。 返回 T
reduce(BinaryOperator b) 可以将流中元素反复结合起来,得到一个值。 返回 Optional

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

2.3.3 收集
方法 描述
collect(Collector c) 将流转换为其他形式。接收一个 Collector接口的 实现,用于给Stream中元素做汇总的方法

Collectors

Collector接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是 Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表: https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html

方法 返回类型 作用 示例
toList List 把流中元素收集到List List emps= list.stream().collect(Collectors.toList());
toSet Set 把流中元素收集到Set Set emps= list.stream().collect(Collectors.toSet());
toCollection Collection 把流中元素收集到创建的集合 Collectionemps=list.stream().collect(Collectors.toCollection(ArrayList::new));
counting Long 计算流中元素的个数 long count = list.stream().collect(Collectors.counting());
summingInt Integer 对流中元素的整数属性求和 int total=list.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingInt Double 计算流中元素Integer属性的平均 值 doubleavg= list.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingInt IntSummaryStatistics 收集流中Integer属性的统计值。 如:平均值 IntSummaryStatisticsiss= list.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joining String 连接流中每个字符串 String str= list.stream().map(Employee::getName).collect(Collectors.joining());
maxBy Optional 根据比较器选择最大值 Optionalmax= list.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minBy Optonal 根据比较器选择最小值 Optional min = list.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducing 归约产生的类型 从一个作为累加器的初始值开始,利用BinaryOperator与 流中元素逐个结合,从而归 约成单个值 int total=list.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThen 转换函数返回的类型 包裹另一个收集器,对其结 果转换函数 int how= list.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingBy Map<K,List> 根据某属性值对流分组,属 性为K,结果为V Map> map= list.stream() .collect(Collectors.groupingBy(Employee::getStatus));
partitioningBy Map<Boolean,List> 根据true或false进行分区 Map>vd= list.stream().collect(Collectors.partitioningBy(Employee::getManage));

3. Stream 使用实例

4. 并行流与串行流

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

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

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方法)

5. Fork/Join 框架

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

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

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

fork-join.png

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

采用 “工作窃取”模式(work-stealing): 当执行新的任务时它可以将其拆分分成更小的任务执行,并将小任务加到线 程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。

相对于一般的线程池实现,fork/join框架的优势体现在对其中包含的任务的处理方式上,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续运行,那么该线程会处于等待状态,而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续运行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能。

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

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

工作窃取

fork-join-steal.jpg

6. Spliterator

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

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

spliterator

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

传统上,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的设计者们充分利用了默认方法,为集合接口和类新增了很多新的方法。


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
}

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接口提供了方法来检测异步计算是否已经结束(使用 isDone方法),等待异步操作结束,以及获取计算的结果。但是这些特性还不足以让你编写简洁的并发代码。比如,我们很难表述Future结果之间的依赖性;从文字描述上这很简单,“当长时间计算任务完成时,请将该计算的结果通知到另一个长时间运行的计算任务,这两个计算任务都完成后,将计算的结果与另一个查询操作结果合并”。但是,使用Future中提供的方法完成这样的操作又是另外一回事。这也是我们需要更具描述能力的特性的原因,比如下面这些。

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

1.2 使用CompletableFuture 构建异步应用


新时间日期 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);
    }

重复注解与类型注解

注解

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 = "Tom")
@Author(name = "Jack")
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总是非空

其他语言特性

原子操作

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

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

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

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

    变更之前的值。

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

    变更之后的值。

Adder和Accumulator

多线程的环境􏵇中,如果多个线程需要频繁地进行更新操作,且很少有读取的动作(比如,在统计计算的上下文中),Java API文􏵉中推推荐大使用新的类LongAdder、LongAccumulator、Double-Adder以及DoubleAccumulator,尽量避免使用它们对应的原子类型。这些新的类在设计之􏳨就考虑了动态􏱗增长的需求,可以有效地减少线程间的􏵲􏵳竞争。

LongAddr 和 DoubleAdder 类 都支持加法操作 , 而 LongAccumulator 和 DoubleAccu- mulator可以使用给定的方法整合多个值。

ConcurrentHashMap

ConcurrentHashMap类的引入极大地提􏲰了HashMap现代化的程度,新引入的ConcurrentHashMap对并发的支持非常􏶂好。ConcurrentHashMap允许并发地进行新􏱗增和更新操作,因为它仅对内部数据结构的某些部分上􏲹锁。因此,和另一种选择,即同步式的Hashtable比较起来,它具有更高的读写性能。

  1. 􏴘􏶃性能

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

  1. 类流操作

􏲘􏰊􏴵􏵱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无法表示的。

  1. 集合视图

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

Arrays

Arrays类提供了不同的静态方法对数组进行操作。现在,它又包括了四个新的方法(它们都有特别重载􏱄的变量)

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

    为数组对象定义特别的Comparator

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

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

    进行􏶐累积计算

Number和Math

Files

Files类最引人注目的改变是,你现在可以用文件直接产生流

String

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

Aggregate Operations聚合操作

Pipelines and Streams

通过集合roster调用stream()方法创建流, filter() 操作返回一个新流,该流包含与其谓词匹配的元素

管道是聚合操作的序列(下边代码包含了filter和forEach两个聚合操作,两段代码等价)

roster
    .stream()
    .filter(e -> e.getGender() == Person.Sex.MALE)
    .forEach(e -> System.out.println(e.getName()));
for (Person p : roster) {
    if (p.getGender() == Person.Sex.MALE) {
        System.out.println(p.getName());
    }
}

管道包含以下组件:

  • 一个 source :可以是一个集合,一个数组,一个函数,或者是一个 I/O channel。
  • 0个或过个 intermediate operations :比如filter,产生一个新的流( 流是元素的序列。与集合不同,它不是存储元素的数据结构 相反,流通过管道携带来自source的值 )
  • y个 terminal operation

计算所有男性的平均年龄

double average = roster
    .stream()
    .filter(p -> p.getGender() == Person.Sex.MALE)
    .mapToInt(Person::getAge)
    .average()
    .getAsDouble();

mapToInt 返回一个新的流 IntStream

聚合操作和迭代器之间的区别

聚合操作,比如forEach,看起来就像迭代器。然而,它们有几个根本的区别

  • 内部迭代:不包含类似next的方法来指示它们处理集合的下一个元素

  • 它们处理来自流的元素 : 聚合操作只操作来自流的元素,而不是直接来自集合,所以也被叫做 stream operations.

  • 它们支持将行为作为参数 : 可以将lambda表达式指定为大多数聚合操作的参数

FAQ

ConcurrentHashMap 在Java8和7的实现区别

java8 api https://docs.oracle.com/javase/8/docs/api/

Jdk8文档 https://docs.oracle.com/javase/8/docs/

参考:

《Java 8实战》

某免费视频学习网站

《Java 8函数式编程》

X Tutup