个人博客
个人博客: https://www.crystalblog.xyz/
备用地址: https://wang-qz.gitee.io/crystal-blog/
1. 简介
JDK8是官方发布的一个大版本, 提供了很多新特性功能给开发者使用, 包含语言、编译器、库、工具和JVM
等方面的十多个新特性。 本文将介绍编码过程中常用的一些新特性. JDK8帮助文档
- Lambda表达式
- 函数式接口
- 方法引用和构造器调用
- Stream API
- 接口中的默认方法和静态方法
- 新的日期时间API
- Optional
2. Lambda表达式
2.1 介绍
lambda
表达式 (也称为闭包), 本质上是一段匿名内部类或一段可以传递的代码 . 可以理解为使用更加方便的“语法糖”,但原理不变的编写语法。 它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理 .
2.2 使用
Lambda表达式由逗号分隔的参数列表
、->符号
和语句块
三部分组成 .
// jdk8之前需要循环集合进行遍历输出
Arrays.asList( "a", "b", "d" ).forEach( (String s) -> {
System.out.print( s );
} );
如果语句块只有一句, 可以省略大括号; 参数列表的类型也可以省略, 如果只有一个参数, 参数的括号也可以省略.
Arrays.asList( "a", "b", "d" ).forEach( s -> System.out.print( s ) );
如果有语句块中有返回值
// jdk8之前,sort(Comparator c)需要写一个匿名内部类实现
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
int result = e1.compareTo( e2 );
return result;
} );
可以继续简化, 只有一个返回值语句, 大括号和return都可以省略.
Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
2.3 案例
需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.
传统方式编写, 每次筛选都要对集合进行循环遍历判断, 这些都是重复的操作.
/**
* 需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.
*/
public class LambdaTest {
@Test
public void testFilterProducts() {
List<Product> products = getProducts();
products = this.filterProductByColor(products);
products = this.filterProductByPrice(products);
products.forEach(System.out::println);
}
// 筛选颜色
public List<Product> filterProductByColor(List<Product> list) {
List<Product> prods = new ArrayList<>();
for (Product product : list) {
if ("红色".equals(product.getColor())) {
prods.add(product);
}
}
return prods;
}
// 筛选价格
public List<Product> filterProductByPrice(List<Product> list) {
List<Product> prods = new ArrayList<>();
for (Product product : list) {
if (product.getPrice() < 8000) {
prods.add(product);
}
}
return prods;
}
// 添加商品数据
private List<Product> getProducts() {
List<Product> products = new ArrayList<>();
for (int i = 1; i <= 10; i++) {
Product pro = Product.builder()
.color(i % 2 == 0 ? "红色" : "绿色")
.price(i * 1000)
.build();
products.add(pro);
}
return products;
}
}
使用lambda表达式+函数接口进行过滤
package cn.itcast.test.newf;
import cn.itcast.pojo.Product;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.function.Predicate;
/**
* 需求: 商城浏览商品信息, 筛选出颜色为红色, 价格小于8000的商品.
*/
public class LambdaTest {
// lambda表达式过滤
@Test
public void testFilterProducts2() {
List<Product> products = getProducts();
products = filterProductByPredicate(products, (product -> Objects.equals("红色", product.getColor())));
products = filterProductByPredicate(products, (product -> Double.compare(8000, product.getPrice()) > 0));
products.forEach(System.out::println);
}
// 使用lambda表达式, Predicate为断言的函数接口, 接口方法返回boolean类型
public List<Product> filterProductByPredicate(List<Product> list, Predicate<Product> p) {
List<Product> prods = new ArrayList<>();
for (Product product : list) {
if (p.test(product)) {
prods.add(product);
}
}
return prods;
}
}
不使用函数接口, 单纯使用lambda表达式实现
@Test
public void testFilterProducts3() {
List<Product> products = getProducts();
products.stream()
.filter((product -> Objects.equals("红色", product.getColor())))
.filter(product -> Double.compare(8000, product.getPrice()) > 0)
.forEach(System.out::println);
}
测试结果
Product(color=红色, price=2000.0)
Product(color=红色, price=4000.0)
Product(color=红色, price=6000.0)
2.4 总结
Lmabda表达式的语法总结: () -> ();
场景 | 语法 |
---|---|
无参数无返回值 | () -> System.out.println(“Hello WOrld”) |
有一个参数无返回值 | (x) -> System.out.println(x) |
有且只有一个参数无返回值 | x -> System.out.println(x) |
有多个参数,有返回值,有多条lambda体语句 | (x,y) -> {System.out.println(“xxx”);return xxxx;}; |
有多个参数,有返回值,只有一条lambda体语句 | (x,y) -> xxxx |
3. 函数式接口
函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。
3.1 简介
函数接口
指的是只有一个抽象方法的接口,这样的接口可以隐式转换为Lambda表达式。 当然接口中可以包含其他的方法(默认,静态,私有) .
java.lang.Runnable和java.util.concurrent.Callable是函数式接口的最佳例子。函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解**@FunctionalInterface** , 所以可以通过@FunctionalInterface
注解检测接口是否为一个函数式接口 .
@FunctionalInterface
public interface FunctionalDefaultMethods {
void method();
// 默认方法
default void defaultMethod() {}
// 静态方法
static void staticMethod(){}
}
下面学习JDK8提供的常用函数式接口API, 主要有Consumer, Supplier , Predicate , Function.
3.2 Consumer
3.2.1 介绍
消费型接口,有参无返回值 , 参数类型由泛型决定 .
@FunctionalInterface
public interface Consumer<T> {
//....
}
3.2.2 accept方法
表示消费一个指定泛型类型的数据 .
void accept(T t);
accept使用
/**
* Consumer<T>:消费型接口,有参无返回值
*/
public class ConsumerTest {
//定义一个方法
//方法的参数传递一个字符串的姓名
//方法的参数传递Consumer接口,泛型使用String
//可以使用Consumer接口消费字符串的姓名
private void accept(String name, Consumer<String> consumer) {
consumer.accept(name);
}
@Test
public void test() {
//对传递的字符串进行消费
//消费方式,把字符串进行反转输出
accept("admin", (name) -> {
String reName = new StringBuilder(name).reverse().toString();
System.out.println(reName);
});
}
}
3.2.3 andThen方法
Consumer接口的默认方法andThen , 可以把两个Consumer接口组合到一起,再对数据进行消费 ( 谁写前边,谁先消费 ).
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
andThen方法使用
/**
* Consumer<T>:消费型接口,有参无返回值
*/
public class ConsumerTest {
//定义一个方法,方法的参数传递一个字符串和两个Consumer接口,Consumer接口的泛型使用字符串
private void andThen(String s, Consumer<String> c1,
Consumer<String> c2) {
//使用andThen方法,把两个Consumer接口连接到一起,先消费c1再消费c2
c1.andThen(c2).accept(s);
}
@Test
public void test2() {
andThen("admin",
(name) -> {
System.out.println(name);
},
(name) -> {
//消费方式,把字符串转换为大写输出
System.out.println(name.toUpperCase(Locale.ROOT));
});
}
}
3.2.4 案例
需求: 格式化打印信息 , 字符串数组中存有多条信息,请按照格式,“姓名:xx,性别:xx“的格式信息打印出来 .
package cn.itcast.test.jdk8;
import org.junit.Test;
import java.util.Locale;
import java.util.function.Consumer;
/**
* Consumer<T>:消费型接口,有参无返回值
*/
public class ConsumerTest {
//定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
// 用两个Consumer接口消费字符串
private void printInfo(String[] arr, Consumer<String> consumer, Consumer<String> consumer2) {
//遍历字符串数组
for (String s : arr) {
consumer.andThen(consumer2).accept(s);
}
}
@Test
public void test3() {
//定义一个字符串类型的数据
String[] arr = {"刘辣子,女", "杨哈哈,男", "赵屌屌,男"};
//调用printInfo方法,传递一个字符串数组,和两个Lambda表达式
printInfo(arr,
(s) -> {
String name = s.split(",")[0];
System.out.print("姓名:" + name);
},
(s) -> {
String gender = s.split(",")[1];
System.out.println("、性别:" + gender + ".");
});
}
}
输出结果
姓名:刘辣子、性别:女.
姓名:杨哈哈、性别:男.
姓名:赵屌屌、性别:男.
3.3 Supplier
3.3.1 介绍
供给型接口,无参有返回值 .
@FunctionalInterface
public interface Supplier<T> {
T get();
}
3.3.2 get方法
Supplier接口仅包含一个无参的方法:T get() .
用来获取一个泛型参数指定类型的对象数据。意味着对应的Lambda表达式需要"对外提供"一个符合泛型类型的对象数据。
/**
* Supplier<T>:供给型接口,无参有返回值
*/
public class SupplierTest {
public String getString(Supplier<String> supplier) {
return supplier.get();
}
@Test
public void test() {
//定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String
String s = getString(() -> "人才");
System.out.println(s);
}
private Integer getMax(Supplier<Integer> supplier) {
//定义一个方法,用于获取int类型数组中元素的最大值,方法的参数传递Supplier接口,泛型使用包装类Integer
return supplier.get();
}
@Test
public void test2() {
//定义一个int类型的数组,并赋值
int[] arr = {100, 78, -887, 66, 90};
//调用getMax方法,方法参数Supplier是一个函数式接口,可以传递Lambda表达式
int maxValue = getMax(() -> {
int max = arr[0];
for (int a : arr) {
if (a > max) {
max = a;
}
}
return max;
});
System.out.println(maxValue);
}
}
3.4 Predicate
3.4.1 介绍
断言型接口,有参有返回值, 对某种类型的数据进行判断 , 返回boolean类型 .
@FunctionalInterface
public interface Predicate<T> {
//...
}
3.4.2 test方法
boolean test(T t):用于对指定类型数据进行判断, 符合条件返回true,不符合条件返回false
boolean test(T t);
test方法的使用
/**
* Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
*/
public class PredicateTest {
//定义一个方法
//参数传递一个String类型的字符串
//传递一个Predicate接口,泛型使用String
//使用predicate中的方法test对字符串进行判断,并把判断的结果返回
public boolean checkString(String s, Predicate<String> predicate) {
// 对某种数据类型的数据进行判断,结果返回一个boolean值
return predicate.test(s);
}
@Test
public void test() {
//定义一个字符串
String s = "abcdef";
//调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式
//对参数传递的字符串进行判断,判断字符串的长度是否大于5,并把判断的结果返回
boolean b = checkString(s, str -> str.length() > 5);
System.out.println(b);
}
}
3.4.3 and方法
逻辑表达式:可以连接多个判断的条件 .
&&:与运算符. 连接的多个判断条件都为true才返回true, 否则false.
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
需求:判断一个字符串,有两个判断的条件
1、判断字符串的长度是否大于5
2、判断字符串中是否包含a
两个条件必须同时满足,使用&&运算符连接两个条件 .
/**
* Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
*/
public class PredicateTest {
//定义一个方法,方法的参数,传递一个字符串
//传递俩个Predicate接口
//1、判断字符串的长度是否大于5 2、判断字符串中是否包含a 两个条件必须同时满足,使用&&运算符连接两个条件
private boolean checkString(String s, Predicate<String> predicate, Predicate<String> predicate2) {
//等价于return pre1.test(s)&&pre2.test(s);
return predicate.and(predicate2).test(s);
}
@Test
public void test2() {
//定义一个字符串
String s = "asdfgi";
//调用checkString方法,参数传递字符串和两个Lambda表达式
boolean b = checkString(s,
str -> !StringUtils.isEmpty(str) && str.length() > 5,
str -> !StringUtils.isEmpty(str) && str.contains("a"));
System.out.println(b);
}
}
3.4.4 or方法
||:或运算符,连接的多个判断条件有一个为true就返回true, 都为false才返回false.
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
需求:判断一个字符串,有两个判断的条件
1、判断字符串的长度是否大于5
2、判断字符串中是否包含a
两个条件满足一个即可,使用||运算符连接两个条件 .
/**
* Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
*/
public class PredicateTest {
@Test
public void test2() {
//定义一个字符串
String s = "asdfgi";
//调用checkStringOr方法,参数传递字符串和两个Lambda表达式
boolean b = checkStringOr(s,
str -> !StringUtils.isEmpty(str) && str.length() > 5,
str -> !StringUtils.isEmpty(str) && str.contains("a")
);
System.out.println(b);
}
//定义一个方法,方法的参数,传递一个字符串
//传递俩个Predicate接口
//1、判断字符串的长度是否大于5 2、判断字符串中是否包含a 满足一个条件即可,使用||运算符连接两个条件
private boolean checkStringOr(String s, Predicate<String> predicate, Predicate<String> predicate2) {
return predicate.or(predicate2).test(s);
}
}
3.4.5 negate方法
!:非(取反):非真则假,非假则真 .
default Predicate<T> negate() {
return (t) -> !test(t);
}
需求:判断一个字符串长度是否大于5
如果字符串的长度大于5,返回false
如果字符串的长度小于5,返回true
使用取反符号!对判断的结果进行取反 .
/**
* Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
*/
public class PredicateTest {
//定义一个方法,方法的参数,传递一个字符串
public boolean checkStringNegate(String s, Predicate<String> predicate) {
//使用Predicate接口判断字符串的长度是否大于5
//return !pre.test(s);
return predicate.negate().test(s);
}
@Test
public void testNegate() {
//定义一个字符串
String s = "asdfg";
//调用checkString方法,参数传递字符串和Lambda表达式
boolean b = checkStringNegate(s, str -> str.length() > 5);
System.out.println(b);
}
}
3.4.6 案例
需求: 集合信息筛选
数组当中有多条“姓名+性别”的信息如下:
String[] array = {"刘小甜甜,女","杨哈哈,男","赵屌屌,男"}
通过Predicate接口的拼装将符合要求的字符串筛选到ArrayList集合中
需求同时满足两个条件:必须为女生,姓名为4个字
1、有两个判断条件,需要使用两个Predicate接口,对条件进行判断
2、必须同时满足两个条件,可以使用and方法连接两个判断条件
/**
* Predicate<T>: 断言型接口,有参有返回值,返回值是boolean类型
*/
public class PredicateTest {
// 定义一个方法, 两个Predicate接口参数
public ArrayList<String> filter(String[] arr, Predicate<String> predicate, Predicate<String> predicate2) {
ArrayList<String> list = new ArrayList<>();
for (String s : arr) {
if (predicate.and(predicate2).test(s)) {
list.add(s);
}
}
return list;
}
@Test
public void test4() {
//定义一个存储字符串的数组
String[] array = {"刘小甜甜,女", "杨哈哈,男", "赵屌屌,男"};
//调用filter方法,传递字符串数组和两个Lambda表达式
ArrayList<String> list = filter(array,
str -> !StringUtils.isEmpty(str) && str.split(",")[0].length() == 4,
str -> !StringUtils.isEmpty(str) && str.split(",")[1].equals("女")
);
list.forEach(System.out::println);
}
}
3.5 Function
3.5.1 介绍
函数式接口,有参有返回值 . java.util.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据.
前者称为前置条件,后者称为后置条件 .
@FunctionalInterface
public interface Function<T, R> {
// ...
}
3.5.2 apply方法
3.5.3 andThen方法
3.6 自定义函数式接口
定义函数式接口
@FunctionalInterface
public interface MyFunctionalInterface {
//定义一个抽象方法, public abstract可以省略
public abstract void method();
}
接口实现类方式实现接口方法
public class MyFunctionalInterfaceImpl implements MyFunctionalInterface {
@Override
public void method() {
System.out.println(">>>MyFunctionalInterfaceImpl");
}
}
函数式接口的使用
/**
* 函数式接口的使用测试
*/
public class MyFunctionalInterfaceTest {
@Test
public void test() {
// 调用show方法,方法的参数是一个接口,所以可以传递接口的实现类对象
show(new MyFunctionalInterfaceImpl());
// 调用show方法,方法的参数是一个接口,所以可以传递接口的匿名内部类
show(new MyFunctionalInterface() {
@Override
public void method() {
System.out.println(">>>MyFunctionalInterface的匿名内部类实现");
}
});
// 调用show方法,方法的参数是一个函数式接口,可以传递Lambda表达式
show(() -> System.out.println(">>>MyFunctionalInterface的lambda表达式实现"));
}
// 定义一个方法,参数使用函数式接口MyFunctionalInterInterface
public void show(MyFunctionalInterface myFunctionalInterface) {
myFunctionalInterface.method();
}
}
测试结果
>>>MyFunctionalInterfaceImpl
>>>MyFunctionalInterface的匿名内部类实现
>>>MyFunctionalInterface的lambda表达式实现
3.7 Lambda的延迟加载
使用函数式接口, 应用Lambda表达式方式实现可以达到延迟加载的目的, 优化提升系统性能.
场景: 程序运行根据日志级别输出日志信息.
实现方式1
/**
* 根据不同日志级别输出日志信息
*/
public class LogPrintTest {
@Test
public void test() {
//定义三个日志信息
String msg1 = "Hello ";
String msg2 = "World ";
String msg3 = "Java";
//调用showLog方法,传递日志级别和日志信息
printLog(2, msg1 + msg2 + msg3);
}
//定义一个根据日志的级别,显示日志信息的方法
public void printLog(int level, String message) {
//对日志的等级进行判断,如果式1级别,那么输出日志信息
if (level == 1) {
System.out.println(message);
}
}
}
实现方式2: Lamdba表达式优化
/**
* 根据不同日志级别输出日志信息
*/
public class LogPrintTest {
@Test
public void test2() {
//定义三个日志信息
String msg1 = "Hello ";
String msg2 = "World ";
String msg3 = "Java";
//调用showLog方法,参数MessageBulider是一个函数式接口,所以可以传递Lambda表达式
showLog(1, () -> msg1 + msg2 + msg3);
}
//定义一个显示日志的方法,方法的参数传递日志的等级和Message接口
public void showLog(int level, MessageBulider mb) {
//对日志的等级进行判断,如果是1级,则调用MessageBulider接口中的BuilderMessage方法
if (level == 1) {
System.out.println(mb.builderMessage());
}
}
// 定义一个函数式接口
@FunctionalInterface
public interface MessageBulider {
//定义一个拼接消息的抽象方法,返回拼接的消息
String builderMessage();
}
}
3.8 函数式接口作为方法的参数
Java中的Lambda表达式可以被当作是匿名内部类的替代品。如果方法的参数
是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。
例如, java.lang.Runnable 接口就是一个函数式接口,假设有一个startThread方法使用该接口作为参数,那么就可以使用Lambda进行传参。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
lambda表达式作为方法的参数使用
/**
* lambda表达式作为方法的参数
*/
public class LambdaTest2 {
@Test
public void test() {
startThread(() -> System.out.println(">>>lambda表达式实现线程任务执行!"));
startThread(new Runnable() {
@Override
public void run() {
System.out.println(">>>匿名内部类方式实现线程任务执行!");
}
});
}
//定义一个方法startThread,方法的参数使用函数式接口Runnable
private void startThread(Runnable task) {
//开启多线程
new Thread(task).start();
}
}
3.9 函数式接口作为方法的返回值
类似地,如果一个方法的返回值类型
是一个函数式接口,那么就可以直接返回一个Lambda表达式。
例如, 当需要通过一个方法来获取一个java.util.Comparator 接口类型的对象作为排序器时, 就可以调该方法获取。
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
// .....
}
lambda表达式作为方法的返回值使用
/**
* lambda表达式作为方法的返回值
*/
public class LambdaTest3 {
@Test
public void test() {
String[] array = {"aaa", "bbb", "ccc"};
System.out.println(Arrays.toString(array));
// lambda表达式实现 Comparator#compare方法返回值
Arrays.sort(array, (o1, o2) -> o2.length() - o1.length());
System.out.println(Arrays.toString(array));
// 匿名内部类方式实现 Comparator#compare方法返回值
Arrays.sort(array, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return o2.length() - o1.length();
}
});
}
}