JDK 1.8 新特新

    科技2025-09-07  43

    JDK 1.8 新特性

    函数式接口

    定义:所谓的函数式接口,实际上就是接口里面只能有一个抽象方法的接口。Comparator接口、Runanle接口都是典型的函数式接口。

    特点:
    接口有且仅有一个抽象方法,如抽象方法compare允许定义静态非抽象方法。允许定义默认defalut非抽象方法(default方法也是java8才有的)允许java.lang.Object中的public方法,如方法equals。FunctionInterface注解不是必须的,如果一个接口符合"函数式接口"定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错
    default关键字:
    1.8之前接口是不能存在方法的实现的(只能有抽象方法),在1.8后出现default关键字,它可以存在方法体。接口实现类可以不去实现default方法,并且可以使用default方法

    ‘ :: ’ 关键字的用法

    引用静态方法引用特定对象的实例方法引用特定类型的任意对象的实例方法引用构造函数 public class User { private Integer id; private Integer age; private String name; public static int comparator(User user1, User user2){ return user1.getAge() - user2.getAge(); } } User user1 = new User(1,22,"小王"); User user2 = new User(2,20,"小李"); User user3 = new User(3,18,"小黄"); List<User> list = Arrays.asList(user1, user2, user3); //引用静态方法: Method :: 静态方法名 list.sort(User::comparator); //引用特定对象的实例方法:out是一个对象,引用对象的方法 list.forEach(System.out::println); list.sort(user::compareByName); //引用特定类型的任意对象的实例方法 //方法参考的等效lambda表达式String::compareToIgnoreCase将具有形式参数列表(String a, String b),其中a和b是用于更好地描述此示例的任意名称。方法引用将调用该方法a.compareToIgnoreCase(b)。 String[] stringArray = { "Barbara", "James", "Mary", "John", "Patricia", "Robert", "Michael", "Linda" }; Arrays.sort(stringArray, String::compareToIgnoreCase); //引用构造函数 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`包含一个`get`不带任何参数并返回一个对象的方法。因此,您可以`transferElements`使用`lambda`表达式调用该方法,如下所示: Set<Person> rosterSetLambda = transferElements(roster, () -> { return new HashSet<>(); }); //您可以使用构造函数引用代替`lambda`表达式,如下所示: Set<Person> rosterSet = transferElements(roster, HashSet::new); //`Java`编译器推断您要创建一个`HashSet`包含`type`元素的集合`Person`。或者,您可以指定以下内容: Set<Person> rosterSet = transferElements(roster, HashSet<Person>::new); //实现函数 @FunctionalInterface public interface TestInterface2 { User creat(); } TestInterface2 testInterface2 = User::new;

    Lambda表达式

    lambda表达式表达的是接口函数,箭头左侧是函数参数,箭头右侧是函数体。函数的参数类型和返回值类型都可以省略,程序会根据接口定义的上下文自动确定数据类型。Lambda表达式是不能操作外部对象的,因为Lambda 实质上是接口的子对象,只能访问静态资源和本身的内部变量。

    定义:Lambda表达式是Java 8最流行最常用的功能特性。它将函数式编程概念引入Java,函数式编程的好处在于可以帮助我们节省大量的代码,方便易用,能够大幅度的提高我们的编码效率。

    (param1, param2, param3, param4…)->{ doing……}//1.无参数形式 () -> System.out.println("Hello Lambda"); //2.一个参数形式 number1 -> int a = number1 * 2; //3.两个参数形式 (number1, number2) -> int a = number1 + number2; //4.两个参数多行形式 (number1, number2) -> { int a = number1 + number2; System.out.println(a); }

    Stream API


    Stream 管道流

    通过前面章节的学习,我们应该明白了Stream管道流的基本操作。

    源操作:可以将数组、集合类、行文本文件转换成管道流Stream进行数据处理中间操作:对Stream流中的数据进行处理,比如:过滤、数据转换等等终端操作:作用就是将Stream管道流转换为其他的数据类型。


    Stream 的获取

    例子一:Stream API 代表 for 循环

    List<String> list = nameStrs.stream() .filter(s -> s.startsWith("L")) .map(String::toUpperCase) .sorted() .collect(toList()); //首先,我们使用Stream()函数,将一个List转换为管道流 //调用filter函数过滤数组元素,过滤方法使用lambda表达式,以L开头的元素返回true被保留,其他的List元素被过滤掉 //然后调用Map函数对管道流中每个元素进行处理,字母全部转换为大写 //然后调用sort函数,对管道流中数据进行排序 //最后调用collect函数toList,将管道流转换为List返回

    例子二:将数组转换为管道流

    使用Stream.of()方法,将数组转换为管道流。 String[] array = {"Monkey", "Lion", "Giraffe", "Lemur"}; Stream<String> nameStrs2 = Stream.of(array); Stream<String> nameStrs3 = Stream.of("Monkey", "Lion", "Giraffe", "Lemur");

    例子三:将集合类对象转换为管道流

    List<String> list = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur"); Stream<String> streamFromList = list.stream(); Set<String> set = new HashSet<>(list); Stream<String> streamFromSet = set.stream();

    例子四:将文本文件转换为管道流

    Stream<String> lines = Files.lines(Paths.get("file.txt"));

    中间操作

    Filter 与 谓语逻辑

    谓语逻辑:Predicate(也是一个函数式接口)
    通常情况下,filter函数中lambda表达式为一次性使用的谓词逻辑。如果我们的谓词逻辑需要被多处、多场景、多代码中使用,通常将它抽取出来单独定义到它所限定的主语实体中。

    谓词逻辑的复用
    and语法(并)or语法(交集)negate(取反) public static Predicate<Employee> ageGreaterThan70 = x -> x.getAge() >70; public static Predicate<Employee> genderM = x -> x.getGender().equals("M"); List<Employee> filtered = employees.stream() .filter(Employee.ageGreaterThan70.and(Employee.genderM)) .collect(Collectors.toList());

    map 的基础用法

    map的基础用法 List<String> alpha = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur"); //计算字符长度 List<Integer> lengths = alpha.stream() .map(String::length) .collect(Collectors.toList()); //输入:6 4 7 5 peek用法:由于map的参数e就是返回值,所以可以用peek函数。peek函数是一种特殊的map函数,当函数没有返回值或者参数就是返回值的时候可以使用peek函数。 List<Employee> maped = employees.stream() .peek(e -> { e.setAge(e.getAge() + 1); e.setGender(e.getGender().equals("M")?"male":"female"); }).collect(Collectors.toList()); flatMap:map只能针对一维数组进行操作,数组里面还有数组,管道里面还有管道,它是处理不了每一个元素的。flatMap可以理解为将若干个子管道中的数据全都,平面展开到父管道中进行处理。 words.stream() .flatMap(w -> Arrays.stream(w.split(""))) // [h,e,l,l,o,w,o,r,l,d] .forEach(System.out::println);

    Limit与Skip管道数据截取

    List<String> limitN = Stream.of("Monkey", "Lion", "Giraffe", "Lemur") .limit(2) .collect(Collectors.toList()); List<String> skipN = Stream.of("Monkey", "Lion", "Giraffe", "Lemur") .skip(2) .collect(Collectors.toList()); limt方法传入一个整数n,用于截取管道中的前n个元素。经过管道处理之后的数据是:[Monkey, Lion]。skip方法与limit方法的使用相反,用于跳过前n个元素,截取从n到末尾的元素。经过管道处理之后的数据是: [Giraffe, Lemur]

    Distinct元素去重

    我们还可以使用distinct方法对管道中的元素去重,涉及到去重就一定涉及到元素之间的比较,distinct方法时调用Object的equals方法进行对象的比较的,如果你有自己的比较规则,可以重写equals方法。

    List<String> uniqueAnimals = Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion") .distinct() .collect(Collectors.toList()); //["Monkey", "Lion", "Giraffe", "Lemur"]

    Sorted排序

    默认的情况下,sorted是按照字母的自然顺序进行排序。

    List<String> alphabeticOrder = Stream.of("Monkey", "Lion", "Giraffe", "Lemur") .sorted() .collect(Collectors.toList()); //[Giraffe, Lemur, Lion, Monkey]

    串行、并行与顺序

    串行的好处是可以保证顺序,但是通常情况下处理速度慢一些

    并行的好处是对于元素的处理速度快一些(通常情况下),但是顺序无法保证。这可能会导致进行一些有状态操作的时候,最后得到的不是你想要的结果。

    parallel()函数表示对管道中的元素进行并行处理,而不是串行处理。但是这样就有可能导致管道流中后面的元素先处理,前面的元素后处理,也就是元素的顺序无法保证。

    当然,并不是并行的性能就比串行的性能好

    说明并行操作的适用场景:

    数据源易拆分:从处理性能的角度,parallel()更适合处理ArrayList,而不是LinkedList。因为ArrayList从数据结构上讲是基于数组的,可以根据索引很容易的拆分为多个。

    适用于无状态操作:每个元素的计算都不得依赖或影响任何其他元素的计算,的运算场景。

    基础数据源无变化:从文本文件里面边读边处理的场景,不适合parallel()并行处理。parallel()一开始就容量固定的集合,这样能够平均的拆分、同步处理。


    比较器(Comparator)

    字符串List排序:
    String.CASE_INSENSITIVE_ORDER:当使用sort方法,按照String.CASE_INSENSITIVE_ORDER(字母大小写不敏感)的规则排序 cities.sort(String.CASE_INSENSITIVE_ORDER);
    整数类型List排序
    Comparator.naturalOrder():字母自然顺序排序Comparator.reverseOrder():倒叙排序 List<Integer> numbers = Arrays.asList(6, 2, 1, 4, 9); System.out.println(numbers); //[6, 2, 1, 4, 9] numbers.sort(Comparator.naturalOrder()); //自然排序 System.out.println(numbers); //[1, 2, 4, 6, 9] numbers.sort(Comparator.reverseOrder()); //倒序排序 System.out.println(numbers); //[9, 6, 4, 2, 1]
    按对象字段对List<Object>排序
    Employee e1 = new Employee(1,23,"M","Rick","Beethovan"); Employee e2 = new Employee(2,13,"F","Martina","Hengis"); Employee e3 = new Employee(3,43,"M","Ricky","Martin"); Employee e4 = new Employee(4,26,"M","Jon","Lowman"); Employee e5 = new Employee(5,19,"F","Cristine","Maria"); Employee e6 = new Employee(6,15,"M","David","Feezor"); Employee e7 = new Employee(7,68,"F","Melissa","Roy"); Employee e8 = new Employee(8,79,"M","Alex","Gussin"); Employee e9 = new Employee(9,15,"F","Neetu","Singh"); Employee e10 = new Employee(10,45,"M","Naveen","Jain"); List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); employees.sort(Comparator.comparing(Employee::getAge));
    Comparator链对List<Object>排序
    //下面这段代码先是按性别的倒序排序,再按照年龄的倒序排序。 employees.sort( Comparator.comparing(Employee::getGender) .thenComparing(Employee::getAge) .reversed() ); employees.forEach(System.out::println); //即 reversed 会把前面所有条件都取反 //都是正序 ,不加reversed //都是倒序,最后面加一个reserved //先是倒序(加reserved),然后正序 //先是正序(加reserved),然后倒序(加reserved)

    判断是否存在

    anyMatch:判断是否有 //谓语形式 boolean isExistAgeThan70 = employees.stream().anyMatch(Employee.ageGreaterThan70); //lambda表达式 boolean isExistAgeThan72 = employees.stream().anyMatch(e -> e.getAge() > 72); allMatch 和 noneMatch:都,前者表示全部都,后者表示全部都不 boolean isExistAgeThan10 = employees.stream().allMatch(e -> e.getAge() > 10); boolean isExistAgeLess18 = employees.stream().noneMatch(e -> e.getAge() < 18);

    元素查找与Optional

    Optional类代表一个值存在或者不存在。在java8中引入,这样就不用返回null了。

    isPresent() 将在 Optional 包含值的时候返回 true , 否则返回 false 。ifPresent(Consumer block) 会在值存在的时候执行给定的代码块。 Consumer 函数式接口;它让你传递一个接收 T 类型参数,并返回 void 的Lambda表达式。T get() 会在值存在时返回值,否则?出一个 NoSuchElement 异常。T orElse(T other) 会在值存在时返回值,否则返回一个默认值。findFirst用于查找第一个符合“匹配规则”的元素,返回值为OptionalfindAny用于查找任意一个符合“匹配规则”的元素,返回值为Optional Optional<Employee> employeeOptional = employees.stream().filter(e -> e.getAge() > 40).findFirst(); System.out.println(employeeOptional.get());

    Stream集合元素归约

    Stream API为我们提供了Stream.reduce用来实现集合元素的归约。reduce函数有三个参数:
    Identity标识:一个元素,它是归约操作的初始值,如果流为空,则为默认结果。Accumulator累加器:具有两个参数的函数:归约运算的部分结果和流的下一个元素。Combiner合并器(可选):当归约并行化时,或当累加器参数的类型与累加器实现的类型不匹配时,用于合并归约操作的部分结果的函数。

    Integer类型归约
    //对List数组进行累加 //第一个参数是初始值,后面 subtotal:阶段性的值, element:新元素的值 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6); int result = numbers .stream() .reduce(0, (subtotal, element) -> subtotal + element); System.out.println(result); //21 int result = numbers .stream() .reduce(0, Integer::sum); System.out.println(result); //21
    String类型归约
    //第一个参数是初始值,后面 subtotal:阶段性的值, element:新元素的值 List<String> letters = Arrays.asList("a", "b", "c", "d", "e"); String result = letters .stream() .reduce("", (partialString, element) -> partialString + element); System.out.println(result); //abcde String result = letters .stream() .reduce("", String::concat); System.out.println(result); //abcde
    复杂对象的归约
    Employee e1 = new Employee(1,23,"M","Rick","Beethovan"); Employee e2 = new Employee(2,13,"F","Martina","Hengis"); Employee e3 = new Employee(3,43,"M","Ricky","Martin"); Employee e4 = new Employee(4,26,"M","Jon","Lowman"); Employee e5 = new Employee(5,19,"F","Cristine","Maria"); Employee e6 = new Employee(6,15,"M","David","Feezor"); Employee e7 = new Employee(7,68,"F","Melissa","Roy"); Employee e8 = new Employee(8,79,"M","Alex","Gussin"); Employee e9 = new Employee(9,15,"F","Neetu","Singh"); Employee e10 = new Employee(10,45,"M","Naveen","Jain"); List<Employee> employees = Arrays.asList(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10); Integer total = employees.stream().map(Employee::getAge).reduce(0,Integer::sum); System.out.println(total); //346 先用map将Stream流中的元素由Employee类型处理为Integer类型(age)。然后对Stream流中的Integer类型进行归约
    Combiner合并器的使用
    除了使用map函数实现类型转换后的集合归约,我们还可以用Combiner合并器来实现,这里第一次使用到了Combiner合并器。因为Stream流中的元素是Employee,累加器的返回值是Integer,所以二者的类型不匹配。这种情况下可以使用Combiner合并器对累加器的结果进行二次归约,相当于做了类型转换。 Integer total3 = employees.stream() .reduce(0,(totalAge,emp) -> totalAge + emp.getAge(),Integer::sum); //注意这里reduce方法有三个参数 System.out.println(total); //346 Integer total2 = employees .parallelStream() .map(Employee::getAge) .reduce(0,Integer::sum,Integer::sum); //注意这里reduce方法有三个参数 System.out.println(total); //346

    终端操作

    ForEach和ForEachOrdered
    parallel()函数表示对管道中的元素进行并行处理,而不是串行处理,这样处理速度更快。但是这样就有可能导致管道流中后面的元素先处理,前面的元素后处理,也就是元素的顺序无法保证forEachOrdered从名字上看就可以理解,虽然在数据处理顺序上可能无法保障,但是forEachOrdered方法可以在元素输出的顺序上保证与元素进入管道流的顺序一致。也就是下面的样子(forEach方法则无法保证这个顺序): Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion") .parallel() .forEach(System.out::println); Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion") .parallel() .forEachOrdered(System.out::println); /** Monkey Lion Giraffe Lemur Lion */
    元素的收集Collect
    通用的收集方式:自定义容器 LinkedList<String> collectToCollection = Stream.of( "Monkey", "Lion", "Giraffe", "Lemur", "Lion" ).collect(Collectors.toCollection(LinkedList::new)); //最终collectToCollection中的元素是: [Monkey, Lion, Giraffe, Lemur, Lion] 收集为Set Set<String> collectToSet = Stream.of( "Monkey", "Lion", "Giraffe", "Lemur", "Lion" ) .collect(Collectors.toSet()); //最终collectToSet 中的元素是:[Monkey, Lion, Giraffe, Lemur],注意Set会去重。 收集为List List<String> collectToList = Stream.of( "Monkey", "Lion", "Giraffe", "Lemur", "Lion" ).collect(Collectors.toList()); // 最终collectToList中的元素是: [Monkey, Lion, Giraffe, Lemur, Lion] 收集为Array(数组) String[] toArray = Stream.of( "Monkey", "Lion", "Giraffe", "Lemur", "Lion" ) .toArray(String[]::new); //最终toArray字符串数组中的元素是: [Monkey, Lion, Giraffe, Lemur, Lion] 收集倒Map

    使用Collectors.toMap()方法将数据元素收集到Map里面,但是出现一个问题:那就是管道中的元素是作为key,还是作为value。我们用到了一个Function.identity()方法,该方法很简单就是返回一个“ t -> t ”(输入就是输出的lambda表达式)。另外使用管道流处理函数distinct()来确保Map键值的唯一性。

    Map<String, Integer> toMap = Stream.of( "Monkey", "Lion", "Giraffe", "Lemur", "Lion" ) .distinct()//去重 .collect(Collectors.toMap( Function.identity(), //元素输入就是输出,作为key s -> (int) s.chars().distinct().count()// 输入元素的不同的字母个数,作为value )); // 最终toMap的结果是: {Monkey=6, Lion=4, Lemur=5, Giraffe=6} 分组收集groupingBy //Collectors.groupingBy用来实现元素的分组收集,下面的代码演示如何根据首字母将不同的数据元素收集到不同的List,并封装为Map。 Map<Character, List<String>> groupingByList = Stream.of( "Monkey", "Lion", "Giraffe", "Lemur", "Lion" ) .collect(Collectors.groupingBy( s -> s.charAt(0) , //根据元素首字母分组,相同的在一组 // counting() // 加上这一行代码可以实现分组统计 )); // 最终groupingByList内的元素: {G=[Giraffe], L=[Lion, Lemur, Lion], M=[Monkey]} //如果加上counting() ,结果是: {G=1, L=3, M=1} 其他方法 boolean containsTwo = IntStream.of(1, 2, 3).anyMatch(i -> i == 2); // 判断管道中是否包含2,结果是: true long nrOfAnimals = Stream.of( "Monkey", "Lion", "Giraffe", "Lemur" ).count(); // 管道中元素数据总计结果nrOfAnimals: 4 int sum = IntStream.of(1, 2, 3).sum(); // 管道中元素数据累加结果sum: 6 OptionalDouble average = IntStream.of(1, 2, 3).average(); //管道中元素数据平均值average: OptionalDouble[2.0] int max = IntStream.of(1, 2, 3).max().orElse(0); //管道中元素数据最大值max: 3 IntSummaryStatistics statistics = IntStream.of(1, 2, 3).summaryStatistics(); // 全面的统计结果statistics: IntSummaryStatistics{count=3, sum=6, min=1, average=2.000000, max=3}

    Map处理

    HashMap的merge()函数

    看下面一段代码,我们首先创建了一个HashMap,并往里面放入了一个键值为k:1的元素。当我们调用merge函数,往map里面放入k:2键值对的时候,k键发生重复,就执行后面的lambda表达式。表达式的含义是:返回旧值oldVal加上新值newVal(1+2),现在map里面只有一项元素那就是k:3。

    String k = "key"; HashMap<String, Integer> map = new HashMap<String, Integer>() {{ put(k, 1); }}; map.merge(k, 2, (oldVal, newVal) -> oldVal + newVal); //其实lambda表达式很简单:表示匿名函数,箭头左侧是参数,箭头右侧是函数体。函数的参数类型和返回值,由代码上下文来确定。
    按Map的键排序
    // 创建一个Map,并填入数据 Map<String, Integer> codes = new HashMap<>(); codes.put("United States", 1); codes.put("Germany", 49); codes.put("France", 33); codes.put("China", 86); codes.put("Pakistan", 92); // 按照Map的键进行排序 Map<String, Integer> sortedMap = codes.entrySet().stream() .sorted(Map.Entry.comparingByKey()) .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> oldVal, LinkedHashMap::new ) ); // 将排序后的Map打印 sortedMap.entrySet().forEach(System.out::println); 首先使用entrySet().stream() 将Map类型转换为Stream流类型。然后使用sorted方法排序,排序的依据是Map.Entry.comparingByKey(),也就是按照Map的键排序最后用collect方法将Stream流转成LinkedHashMap。 其他参数都好说,重点看第三个参数,就是一个merge规则的lambda表达式,与merge方法的第三个参数的用法一致。由于本例中没有重复的key,所以新值旧值随便返回一个即可。
    按Map的值排序
    Map<String, Integer> sortedMap2 = codes.entrySet().stream() .sorted(Map.Entry.comparingByValue()) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (oldVal, newVal) -> oldVal, LinkedHashMap::new)); sortedMap2.entrySet().forEach(System.out::println);
    使用TreeMap按键排序
    // 将 `HashMap` 转为 `TreeMap` Map<String, Integer> sorted = new TreeMap<>(codes);

    Java 8读取文件过滤

    Path filePath = Paths.get("c:/temp", "data.txt"); try (Stream<String> lines = Files.lines(filePath)){ List<String> filteredLines = lines .filter(s -> s.contains("password")) .collect(Collectors.toList()); filteredLines.forEach(System.out::println); } catch (IOException e) { e.printStackTrace();//只是测试用例,生产环境下不要这样做异常处理 }
    Processed: 0.012, SQL: 8