JDK 1.8 新特新

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用于查找第一个符合“匹配规则”的元素,返回值为Optional
  • findAny用于查找任意一个符合“匹配规则”的元素,返回值为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();//只是测试用例,生产环境下不要这样做异常处理
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值