Java 8 学习笔记
1. Java 8
1.1 生态
- Lambda 表达式
- 函数式接口
- 方法引用 / 构造器引用
- Stream API
- 接口中的默认方法 / 静态方法
- 新时间日期 API
- 其他新特性
1.2 新特性
- 速度更快
java8对于底层的数据结构做了优化,例如 HashMap:数组+链表 --> 红黑树
java8对虚拟机做了优化,例如 HotSpot JVM 将移除永久区,使用本地内存来存储类元数据信息并称之为:元空间(Metaspace)
- 代码更少
- 强大的 Stream API
- 便于并行
- 最大化减少空指针异常 Optional (Kotlin ?)
1.3 温故而知新
- Hashmap 底层结构/原理 老话题不再阐述 …
- 并发hashmap …
- Java虚拟机 …
- Java内存模型 …
2. 课前引入
2.1 问题引入
对于一个list中的数据,进行按照条件过滤,每次都要编写新的方法,而且方法中存在大量的重复代码
List<Employee> employees = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 38, 5555.99),
new Employee("王五", 50, 6666.99),
new Employee("赵六", 16, 3333.99),
new Employee("田七", 35, 7777.99)
);
@Test
public void t1(){
//年龄大于20
for (int i = 0; i < employees.size(); i++) {
if (employees.get(i).getAge()>20){
System.out.println(employees.get(i));
}
}
}
@Test
public void t2(){
//薪资大于7000
for (int i = 0; i < employees.size(); i++) {
if (employees.get(i).getSalary()>7000){
System.out.println(employees.get(i));
}
}
}
2.2 优化1:策略设计模式
策略设计模式(按照给定的策略(单独的类)进行过滤)
接口
public interface MyPredicate <T>{
boolean test(T t);
}
接口实现:age>=35
public class FilterEmployeeByAge implements MyPredicate<Employee>{
@Override
public boolean test(Employee employee) {
return employee.getAge()>=35;
}
}
接口实现:Salary()>=7000
public class FilterEmployeeBySalary implements MyPredicate<Employee>{
@Override
public boolean test(Employee employee) {
return employee.getSalary()>=7000;
}
}
方法实现:
/**
* 优化1:策略设计模式(按照给定的策略(单独的类)进行过滤)
*/
@Test
public void test01() {
//所有年龄大于35
List<Employee> list = filterEmployee(employees, new FilterEmployeeByAge());
System.out.println(list.toString());
//所有薪资大于7000
List<Employee> list1 = filterEmployee(employees, new FilterEmployeeBySalary());
System.out.println(list1.toString());
}
//实现方法,调用接口MyPredicate,主要内容 myPredicate.test()方法
public List<Employee> filterEmployee(List<Employee> list, MyPredicate<Employee> myPredicate) {
List<Employee> employees = new ArrayList<>();
for (Employee emp : list) {
if (myPredicate.test(emp)) {
employees.add(emp);
}
}
return employees;
}
2.3 优化2:匿名内部类
/**
* 优化2:匿名内部类
*/
@Test
public void test02() {
List<Employee> ans = filterEmployee(employees, new MyPredicate<Employee>() {
@Override
public boolean test(Employee emp) {
return emp.getSalary() >= 7000;
}
});
System.out.println(ans.toString());
}
2.3 优化3:Lambda
匿名函数
Lambda是一个匿名函数,可以理解为一段可以传递的代码(将代码像数据一样传递);可以写出更简洁、更灵活的代码;作为一种更紧凑的代码风格,是Java语言表达能力得到提升。
/**
* 优化3:lambda
*/
@Test
public void test03() {
List<Employee> ans = filterEmployee(this.employees, emp -> emp.getSalary() >= 7000);
ans.forEach(System.out::println);
}
存在的问题:
以上的解决方案都用到了 filterEmployee 方法,也就是 都使用了 MyPredicate接口,那么有没有不使用接口的方法,即使用stream流
2.4 优化4:Stream流
/**
* 优化4:stream api
*/
@Test
public void test04() {
//薪资>=5000
employees.stream()
.filter(emp -> emp.getSalary() >= 5000)
.forEach(System.out::println);
//获取所有姓名
employees.stream()
.map(Employee::getName)
.forEach(System.out::println);
}
演变过程:
垃圾代码 --> 策略模式 --> 匿名内部类 --> Lambda表达式--> stream流
3.lambda表达式
3.1 基础语法一览
基础语法:
- 操作符:->
- 左侧:参数列表
- 右侧:执行代码块 / Lambda 体
口诀:
- 写死小括号,拷贝右箭头,落地大括号
- 左右遇一括号省
- 左侧推断类型省
3.2 具体实现
- 无参数,无返回值:() -> sout
例如 Runnable接口:
public class Test02 {
int num = 10; //jdk 1.7以前 必须final修饰
//1.8之后,可以不写final,系统会给你加上
@Test
public void test01(){
//匿名内部类
new Runnable() {
@Override
public void run() {
//在局部类中引用同级局部变量
//只读
System.out.println("Hello World" + num);
//如果使用num++,会报错,因为num是final
}
};
}
@Test
public void test02(){
//语法糖
Runnable runnable = () -> {
System.out.println("Hello Lambda");
};
}
}
- 有一个参数,无返回值
@Test
public void test03(){
Consumer<String> consumer = (a) -> System.out.println(a);
consumer.accept("我觉得还行!");
}
- 有一个参数,无返回值 (小括号可以省略不写)
@Test
public void test03(){
Consumer<String> consumer = a -> System.out.println(a);
consumer.accept("我觉得还行!");
}
- 有两个及以上的参数,有返回值,并且 Lambda 体中有多条语句
@Test
public void test04(){
Comparator<Integer> comparator = (a, b) -> {
System.out.println("比较接口");
return Integer.compare(a, b);
};
}
- 有两个及以上的参数,有返回值,并且 Lambda 体中只有1条语句 (大括号 与 return 都可以省略不写)
@Test
public void test04(){
Comparator<Integer> comparator = (a, b) -> Integer.compare(a, b);
}
- Lambda 表达式 参数的数据类型可以省略不写 Jvm可以自动进行 “类型推断”
类型推断 也算是jdk8以后的新特性
3.3 函数式接口定义
函数式接口:
接口中只有一个抽象方法的接口,可以使用 @FunctionalIterface 来判断是不是
测试:
- 定义一个函数式接口:
@FunctionalInterface
public interface MyFun {
Integer count(Integer a, Integer b);
}
- 用一下:
@Test
public void test05(){
MyFun myFun1 = (a, b) -> a + b;
MyFun myFun2 = (a, b) -> a - b;
MyFun myFun3 = (a, b) -> a * b;
MyFun myFun4 = (a, b) -> a / b;
}
- 再用一下:
public Integer operation(Integer a, Integer b, MyFun myFun){
return myFun.count(a, b);
}
@Test
public void test06(){
Integer result = operation(1, 2, (x, y) -> x + y);
System.out.println(result);
}
3.4 课后题
**案例一:**调用 Collections.sort() 方法,通过定制排序 比较两个 Employee (先按照年龄比,年龄相同按照姓名比),使用 Lambda 表达式作为参数传递
- 定义实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private Integer id;
private String name;
private Integer age;
private Double salary;
}
- 定义 List 传入数据
List<Employee> emps = Arrays.asList(
new Employee(101, "Z3", 19, 9999.99),
new Employee(102, "L4", 20, 7777.77),
new Employee(103, "W5", 35, 6666.66),
new Employee(104, "Tom", 44, 1111.11),
new Employee(105, "Jerry", 60, 4444.44)
);
- @Test
@Test
public void test01(){
Collections.sort(emps, (e1, e2) -> {
if (e1.getAge() == e2.getAge()){
return e1.getName().compareTo(e2.getName());
} else {
return Integer.compare(e1.getAge(), e2.getAge());
}
});
for (Employee emp : emps) {
System.out.println(emp);
}
}
**案例二:**声明函数式接口,接口中声明抽象方法,String getValue(String str); 声明类 TestLambda,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值;再将一个字符串的第二个和第四个索引位置进行截取字串
**案例三:**声明一个带两个泛型的函数式接口,泛型类型为<T, R> T 为参数,R 为返回值;接口中声明对应的抽象方法;在 TestLambda 类中声明方法,使用接口作为参数,计算两个 Long 类型参数的和;在计算两个 Long 类型参数的乘积
4. 函数式接口
4.0 概念
4.1 四大核心函数式接口
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) |
4.1 消费型接口
/**
* 测试消费型接口
*/
@Test
public void test01() {
happy(1000, com -> System.out.println("yrl大保健花费" + com + "元"));
}
public void happy(double money, Consumer<Double> consumer) {
consumer.accept(money);
}
4.2 提供型接口
/**
* 测试供给型接口
*/
@Test
public void test02(){
ArrayList<Integer> suppliertest = suppliertest(20, ()->{
Random random=new Random();
return random.nextInt();
});
System.out.println(suppliertest.toString());
}
public ArrayList<Integer> suppliertest(int sum, Supplier supplier){
ArrayList<Integer> arrayList=new ArrayList<>();
for (int i = 0; i < sum; i++) {
Integer temp= (Integer) supplier.get();
arrayList.add(temp);
}
return arrayList;
}
4.3 函数型接口
/**
* 测试函数型接口
*/
@Test
public void test03(){
String newStr=FunctionTest("DadasDSaddsad",str->str.substring(0,6));
System.out.println(newStr);
}
public String FunctionTest(String str, Function<String,String> function){
return function.apply(str);
}
4.4 断言型接口
/**
* 测试断言型接口
*/
@Test
public void test04(){
List<String> strings=Arrays.asList("dasda","dawwas","ca","cdw","cewww","cderf","11sdawasda","ccc");
List<String> ans = PredicateTest(strings,pre->{
System.out.println("he");
return pre.length()>3;
});
System.out.println(ans.toString());
}
public List<String> PredicateTest(List<String> list, Predicate<String> predicate){
List<String> list1=new ArrayList<>();
for (String s : list) {
if (predicate.test(s)){
list1.add(s);
}
}
return list1;
}
4.5 其他接口
5.引用
5.1 概念一览
5.2 方法引用
**定义:**若 Lambda 表达式体中的内容已有方法实现,则我们可以使用“方法引用”
语法格式:
- 对象 :: 实例方法
- 类 :: 静态方法
- 类 :: 实例方法
对象::实例方法
/**
* 对象::实例方法名
*/
@Test
public void test01(){
PrintStream ps1=System.out;
Consumer<String> con=x->ps1.println(x);
con.accept("hello1");
PrintStream ps2=System.out;
Consumer<String> con2=ps2::println;
con2.accept("hello2");
Consumer<String> con3=System.out::println;
con3.accept("hello3");
}
/**
* 对象::实例方法名
*/
@Test
public void test02(){
Employee emp=new Employee();
Supplier<String> sup=()->emp.getName();
String str=sup.get();
System.out.println(str);
Supplier<String> sup2= emp::getName;
String str2=sup2.get();
System.out.println(str2);
}
**注意:**Lambda 表达实体中调用方法的参数列表、返回类型必须和函数式接口中抽象方法保持一致
类::静态方法
/**
* 类::静态方法名
*/
public void test03(){
Comparator<Integer> com=(x,y)->Integer.compare(x,y);
System.out.println(com.compare(10, 18));
Comparator<Integer> com2= Integer::compare;
System.out.println(com2.compare(10, 18));
}
类::实例方法
/**
* 类::实例方法名
*/
@Test
public void test04(){
BiPredicate<String,String> bp=(x,y)->x.equals(y);
BiPredicate<String,String> bp2= String::equals;
}
**条件:**Lambda 参数列表中的第一个参数是方法的调用者,第二个参数是方法的参数时,才能使用 ClassName :: Method
5.3 构造器引用
格式:
- ClassName :: new
/**
* 构造器引用
*/
@Test
public void test05(){
Supplier<Employee> sup=()->new Employee();
Supplier<Employee> sup2= Employee::new;
//无参构造
Employee employee = sup2.get();
System.out.println(employee);
}
/**
* 构造器引用
*/
public void test06(){
Function<Integer,Employee> fun=(x)->new Employee();
Function<Integer,Employee> fun2=Employee::new;
//一个参数的构造器
Employee emp=fun2.apply(100);
System.out.println(emp);
BiFunction<Integer,Integer,Employee> bf=Employee::new;
//两个参数的构造器
}
**注意:**需要调用的构造器的参数列表要与函数时接口中抽象方法的参数列表保持一致
代码补充:employee
package com.example.methodTest;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
/**
* @Author: GengKY
* @Date: 2021/8/12 19:09
*/
@Getter
@Setter
@ToString
public class Employee {
private Integer id;
private String name;
private Integer age;
private Double salary;
public Employee() {
}
public Employee(Integer id) {
this.id = id;
}
public Employee(Integer id, Integer age) {
this.id = id;
this.age=age;
}
public Employee(Integer id, String name, Integer age, Double salary) {
this.id = id;
this.name = name;
this.age = age;
this.salary = salary;
}
}
5.4 数组引用
语法:
- Type :: new
/**
* 数组引用
*/
@Test
public void test07(){
Function<Integer,String[]> fun=(x)->new String[x];
String[] strs=fun.apply(10);
System.out.println(strs.length);
Function<Integer,String[]> fun2=String[]::new;
String[] str2=fun2.apply(20);
System.out.println(str2.length);
}