Java8特性总结
——恕我直言你可能真的不会java系列学习笔记
字母哥讲:恕我直言你可能真的不会java系列的学习笔记,以下是学习资料直达地址
视频:https://www.bilibili.com/video/BV1sE411P7C1
文章:https://www.kancloud.cn/hanxt/javacrazy/1568811
主讲: java8基础特性
恕我直言你可能真的不会java系列-lambda、streamAPI、文本块等特性深入讲解
学习笔记源码:https://github.com/gaogushenling/lambda-StreamAPI
01.lambda表达式会用了么
lambda表达式表达接口函数的实现
一、接口定义
1、经典OOP的实现样式
public class LambdaDemo {
//函数定义
public void printSomething(String something) {
System.out.println(something);
}
//通过创建对象调用函数
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
String something = "I am learning Lambda";
demo.printSomething(something);
}
}
2、创建功能接口,并对该接口定义抽象方法
public class LambdaDemo {
//抽象功能接口
interface Printer {
void print(String val);
}
//通过参数传递功能接口
public void printSomething(String something, Printer printer) {
printer.print(something);
}
}
二、传统的接口函数实现方式
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
String something = "I am using a Functional interface";
//实现Printer接口
Printer printer = new Printer() {
@Override
public void print(String val) {
//控制台打印
System.out.println(val);
}
};
demo.printSomething(something, printer);
}
三、lambda表示式实现方式
lambda表达式的语法:
(param1,param2,param3 ...,paramN)- > { //代码块; }
首先我们知道lambda表达式,表达的是接口函数
箭头左侧是函数的逗号分隔的形式参数列表
箭头右侧是函数体代码
public static void main(String[] args) {
LambdaDemo demo = new LambdaDemo();
String something = "I am learning Lambda";
//实现Printer接口(请关注下面这行lambda表达式代码)
Printer printer = (String toPrint)->{System.out.println(toPrint);};
//调用接口打印
demo.printSomething(something, printer);
}
例子
package main.java;
public class LambdaDemo1 {
interface Printer{
void printer(String val);
}
public void pringSomething(String something,Printer printer){
printer.printer(something);
}
public static void main(String[] args) {
LambdaDemo1 lambdaDemo1 = new LambdaDemo1();
String some = "asdasasa";
//不使用lambda表达式
Printer printer = new Printer() {
@Override
public void printer(String val) {
System.out.println(val);
}
};
lambdaDemo1.pringSomething(some,printer);
/*1、使用lambda表达式
*接口匿名实现类,简化函数
* ①参数
* ②函数体
* */
Printer printer1 = (String val) ->{
System.out.println(val);
};
//1.1、进一步简化,去掉参数类型
Printer printer2 = (val) ->{
System.out.println(val);
};
//1.2、进一步简化,去掉参数括号(前提:只有一个参数)
Printer printer3 = val ->{
System.out.println(val);
};
//1.3、进一步简化,去掉函数体大括号(前提:函数体只有一行代码)
Printer printer4 = val -> System.out.println(val);
//1.4、最后可精简为如下,会自动推断lambda传入的参数类型
lambdaDemo1.pringSomething(some,val -> System.out.println(val));
//1.5、无参数传入的话,可以简写成
// () -> System.out.println("无参");
/*
* 总结:
* 省略类型:自动推断
* 省略括号:一个参数
*
* lambda表达式表达的是接口函数,
* 箭头左侧是函数参数,箭头右侧是函数体。
* 函数的参数类型和返回值类型都可以省略,程序会根据接口定义的上下文自动确定数据类型。
* */
}
}
02.Stream代替for循环(初识Stream-API)
认识
Java Stream就是一个数据流经的管道,并且在管道中对数据进行操作,然后流入下一个管道。
管道的功能包括:Filter(过滤)、Map(映射)、sort(排序)等,集合数据通过Java Stream管道处理之后,转化为另一组集合或数据输出。
例子 Stream代替for循环
package main.java;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamDemo1 {
public static void main(String[] args) {
List<String> letters = Arrays.asList("e", "c", "cbn", "zxc", "caz", "d", "b", "a");
//不使用StreamAPI
/* for (String letter : letters) {
//转换大写
String temp = letter.toLowerCase();
//排序
//转
//总之非常麻烦
}*/
//1、集合 使用StreamAPI
//数组转换成流
List<String> lettertsNew = letters.stream()
//过滤:找出以c开头的元素
.filter(s -> s.startsWith("c"))
//将过滤后的结果全部大写,其中::为函数引用
.map(String::toUpperCase)
//排序,可写排序规则
.sorted()
//将流转换成集合
.collect(Collectors.toList());
System.out.println(lettertsNew);
//2、数组转换成流
String[] letters2 = {"e", "c", "cbn", "zxc", "caz", "d", "b", "a"};
Stream.of(letters2).filter(s -> s.startsWith("c")).map(String::toUpperCase).sorted().toArray(String[]::new);
//3、set转成流:集合类是一样的
Set<String> lettersSet = new HashSet<>(letters);
List<String> lettertsNewSet = lettersSet.stream()
//过滤:找出以c开头的元素
.filter(s -> s.startsWith("c"))
//将过滤后的结果全部大写,其中::为函数引用
.map(String::toUpperCase)
//排序,可写排序规则
.sorted()
//将流转换成集合
.collect(Collectors.toList());
//4、文本文件转换成流
Paths.get("file.text");
try {
Stream<String> stringStream = Files.lines(Paths.get("file.text"));
List<String> f = stringStream
//过滤:找出以c开头的元素
.filter(s -> s.startsWith("c"))
//将过滤后的结果全部大写,其中::为函数引用
.map(String::toUpperCase)
//排序,可写排序规则
.sorted()
//将流转换成集合
.collect(Collectors.toList());
} catch (IOException e) {
e.printStackTrace();
}
}
}
题外话 介绍双冒号操作符
双冒号运算就是Java中的[方法引用] Method
[方法引用]的格式是
类名::方法名
例如
1.表达式:
person -> person.getName();
可以替换成:
Person::getName
2.表达式:
() -> new HashMap<>();
可以替换成:
HashMap::new
方法引用的种类
方法引用有四种,分别是:
指向静态方法的引用
指向某个对象的实例方法的引用
指向某个类型的实例方法的引用
指向构造方法的引用
其实,JVM 本身并不支持指向方法引用,过去不支持,现在也不支持。
Java 8 对方法引用的支持只是编译器层面的支持,虚拟机执行引擎并不了解方法引用。编译器遇到方法引用的时候,会像上面那样自动推断出程序员的意图,将方法引用还原成接口实现对象,或者更形象地说,就是把方法引用设法包装成一个接口实现对象,这样虚拟机就可以无差别地执行字节码文件而不需要管
什么是方法引用了。
需要注意的是,方法引用是用来简化接口实现代码的,并且凡是能够用方法引用来简化的接口,都有这样的特征:有且只有一个待实现的方法。这种接口在 Java 中有个专门的名称: 函数式接口。当你用试图用方法引用替代一个非函数式接口时,会有这样的错误提示: xxx is not a functional interface。
3.Stream的filter与谓语逻辑
什么是谓词逻辑?
WHERE 和 AND 限定了主语employee是什么,那么WHERE和AND语句所代表的逻辑就是谓词逻辑
SELECT *
FROM employee
WHERE age > 70
AND gender = 'M'
谓词逻辑的复用
filter函数中lambda表达式为一次性使用的谓词逻辑。如果我们的谓词逻辑需要被多处、多场景、多代码中使用,通常将它抽取出来单独定义到它所限定的主语实体中。
比如:将下面的谓词逻辑定义在Employee实体class中。
public static Predicate<Employee> ageGreaterThan70 = x -> x.getAge() >70;
public static Predicate<Employee> genderM = x -> x.getGender().equals("M");
and语法(并集)
or语法(交集)
negate语法(取反)
例子
package model;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.function.Predicate;
@Data
@AllArgsConstructor
//员工
public class Employee {
private Integer id;
private Integer age; //年龄
private String gender; //性别
private String firstName; //名字
private String lastName; //姓氏
//Predicate接口,在英语中这个单词的意思是:谓词
//谓词逻辑的复用如下
//e -> e.getAge() > 70 && e.getGender().equals("M")
public static Predicate<Employee> ageGreaterThan70 = x -> x.getAge() >70;
public static Predicate<Employee> genderM = x -> x.getGender().equals("M");
}
package model;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamFilterPredicate {
public static void main(String[] args){
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);
List<Employee> filtered = employees.stream()
//filter(写谓词逻辑) 年龄大于70 并且是男性
.filter(e -> e.getAge() > 70 && e.getGender().equals("M"))
.collect(Collectors.toList());
//使用可复用谓词逻辑
List<Employee> filteredAnd = employees.stream()
.filter(Employee.ageGreaterThan70
.and(Employee.genderM))
.collect(Collectors.toList());
//or
List<Employee> filteredOr = employees.stream()
.filter(Employee.ageGreaterThan70
.or(Employee.genderM))
.collect(Collectors.toList());
//negate 取反
List<Employee> filteredNegate = employees.stream()
.filter(
Employee.ageGreaterThan70
.or(Employee.genderM)
.negate()
)
.collect(Collectors.toList());
System.out.println("filtered="+filtered);
System.out.println("filteredAnd="+filteredAnd);
System.out.println("filteredOr="+filteredOr);
System.out.println("filteredNegate="+filteredNegate);
}
}
4.Stream的map数据转换(Stream管道流的map操作)
map函数的作用就是针对管道流中的每一个数据元素进行转换操作。
例子
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamMap1 {
public static void main(String[] args) {
List<String> alpha = Arrays.asList("Monkey", "Lion", "Giraffe", "Lemur");
//不使用Stream管道流
List<String> alphaUpper = new ArrayList<>();
for (String s : alpha) {
alphaUpper.add(s.toUpperCase());
}
System.out.println(alphaUpper); //[MONKEY, LION, GIRAFFE, LEMUR]
// 1、Stream管道流map的基础用法:使用Stream管道流
List<String> collect = alpha.stream().map(String::toUpperCase).collect(Collectors.toList());
//上面使用了方法引用,和下面的lambda表达式语法效果是一样的
//List<String> collect = alpha.stream().map(s -> s.toUpperCase()).collect(Collectors.toList());
System.out.println(collect); //[MONKEY, LION, GIRAFFE, LEMUR]
//2、处理非字符串类型集合元素
List<Integer> lengths = alpha.stream()
.map(String::length)
.collect(Collectors.toList());
System.out.println(lengths); //[6, 4, 7, 5]
Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
//除了mapToInt。还有maoToLong,mapToDouble等等用法
.mapToInt(String::length)
.forEach(System.out::println);
}
}
import model.Employee;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class StreamMap2 {
public static void main(String[] args) {
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);
//每人涨一岁,性别换全词
/*List<Employee> maped = employees.stream()
.map(e -> {
e.setAge(e.getAge() + 1);
e.setGender(e.getGender().equals("M")?"male":"female");
return e;
}).collect(Collectors.toList());*/
List<Employee> maped = employees.stream()
.peek(e -> {
e.setAge(e.getAge() + 1);
e.setGender(e.getGender().equals("M")?"male":"female");
}).collect(Collectors.toList());
System.out.println(maped);
}
}
import java.util.Arrays;
import java.util.List;
public class StreamFlatMap {
public static void main(String[] args) {
List<String> words = Arrays.asList("hello", "word");
words.stream()
.map(w -> Arrays.stream(w.split(""))) //[[h,e,l,l,o],[w,o,r,l,d]]
.forEach(System.out::println);
//flatMap可以理解为将若干个子管道中的数据全都,平面展开到父管道中进行处理
words.stream()
.flatMap(w -> Arrays.stream(w.split(""))) // [h,e,l,l,o,w,o,r,l,d]
.forEach(System.out::println);
}
}
5.Stream的状态与并行操作
Stream管道流的基本操作
- 源操作:可以将数组、集合类、行文本文件转换成管道流Stream进行数据处理
- 中间操作:对Stream流中的数据进行处理,比如:过滤、数据转换等等
- 终端操作:作用就是将Stream管道流转换为其他的数据类型。
中间操作:有状态与无状态
状态通常代表公用数据,有状态就是有“公用数据”
Limit与Skip管道数据截取
Distinct元素去重
Sorted排序
串行、并行与顺序
例子
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class StreamState {
public static void main(String[] args) {
//1、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());
//2、Distinct元素去重
List<String> uniqueAnimals = Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.distinct()
.collect(Collectors.toList());
//3、Sorted排序
List<String> alphabeticOrder = Stream.of("Monkey", "Lion", "Giraffe", "Lemur")
.sorted()
.collect(Collectors.toList());
//4、串行、并行与顺序
//默认串行
//并行操作parallel,操作无状态操作
Stream.of("Monkey", "Lion", "Giraffe", "Lemur", "Lion")
.parallel()
.forEach(System.out::println);
}
}
结果
Giraffe
Lion
Lemur
Lion
Monkey
Process finished with exit code 0
6.Stream性能差问号 不要人云亦云
7.像使用SQL一样排序集合
一、字符串List排序
二、整数类型List排序
三、按对象字段对List