Lambda表达式
简介
lambda表达式可以用非常少的代码实现抽象方法。lambda表达式不能独立执行,必须实现函数式接口,并且会返回一个函数式接口的对象。
语法:
() -> 结果表达式
参数 -> 结果表达式
(参数1,参数2,参数3) -> {结果表达式}
实现函数式接口
实现无参函数式接口
定义一个Book类型的抽象类,类中有一个获取名称的抽象方法,并用lambda来实现
public interface Book {
String getName();
}
public static void main(String[] args) {
Book book = () -> "lambda 实现book接口";
System.out.println(book.getName());
}
实现有参函数式接口
定义一个Book类型的抽象类,该抽象类中有一折扣过的书价格方法。
public interface Book {
BigDecimal getPrice(double discount, BigDecimal price);
}
public static void main(String[] args) {
Book book = (discount, price) -> BigDecimal.valueOf(discount).multiply(price);
System.out.println("最终折扣过的价格是:"+book.getPrice(0.8, BigDecimal.valueOf(32.5)));
// 最终折扣过的价格是:26.00
}
调用外部变量
lambda表达式无法更改局部变量的值,但是却可以更改外部类的成员变量的值。
无法更改局部变量的值
public interface Book {
BigDecimal getPrice(double discount, BigDecimal price);
}
public static void main(String[] args) {
BigDecimal methodPrice = BigDecimal.ZERO;
Book book = (discount, price) -> {
BigDecimal price1 = BigDecimal.valueOf(discount).multiply(price);
methodPrice = price1; // 此处会报编译错误
return price1;
};
System.out.println("最终折扣过的价格是:"+book.getPrice(0.8, BigDecimal.valueOf(32.5)));
// 最终折扣过的价格是:26.00
}
/*
* 当然也可以使用AtomicReference来更改局部变量
**/
public static void main(String[] args) {
/**
* AtomicReference类提供了一个可以原子读写的对象引用变量。
* 原子意味着尝试更改相同AtomicReference的多个线程(例如,使用比较和交换操作)不会使AtomicReference最终达到不一致的状态。
*/
AtomicReference<BigDecimal> methodPrice = new AtomicReference<>(BigDecimal.ZERO);
Book book = (discount, price) -> {
BigDecimal price1 = BigDecimal.valueOf(discount).multiply(price);
methodPrice.set(price1);
return price1;
};
book.getPrice(0.8, BigDecimal.valueOf(32.5));
System.out.println("最终折扣过的价格是:"+ methodPrice);
// 最终折扣过的价格是:26.00
}
更改成员变量的值
static BigDecimal methodPrice = BigDecimal.ZERO;
public static void main(String[] args) {
Book book = (discount, price) -> {
BigDecimal price1 = BigDecimal.valueOf(discount).multiply(price);
methodPrice = price1;
return price1;
};
book.getPrice(0.8, BigDecimal.valueOf(32.5));
System.out.println("最终折扣过的价格是:"+ methodPrice);
// 最终折扣过的价格是:26.00
}
总结
- lambda表达式可以调用并修改类成员变量的值。
- lambda表达式只是描述了抽象方法是如何实现的,在抽象方法没有被调用前,lambda表达式中的代码并没有被执行,运行抽象方法之前类成员变量的值不会发生变化。
- 只要抽象方法被调用,就会执行lambda表达式中的代码,类成员变量的值就会被修改。
异常处理
编写抽象方法为了保证程序的安全性,会在定义时就抛出异常。lambda表达式中并没有抛出异常的语法,lambda表达式会默认抛出抽象方法原有的异常,当此方法被调用时则需要进行异常处理。
public interface Book {
boolean getNum(Object num) throws ClassCastException;
}
public static void main(String[] args) {
Book book = (num) -> {
if (!Integer.TYPE.isInstance(num)) {
throw new ClassCastException("只能接收数字类型");
}
number = Integer.parseInt(num.toString());
return true;
};
book.getNum("test");
// Exception in thread "main" java.lang.ClassCastException: 只能接收数字类型
}
方法引用
引用静态方法
lambda 表达式添加了一套新语法,用来引用方法。
语法: 类名::静态方法名
public interface Book {
// 定义抽象方法
int add(int num, int num2);
}
public class Main implements Book {
public static int number = 0;
public static void main(String[] args) {
// 调用静态方法
Book book = Main::getNumber;
// 直接调用接口返回结果
int add = book.add(1, 3);
System.out.println(add);
// 4
}
// 定义静态方法
static int getNumber(int num, int num2) {
return num + num2;
}
@Override
public int add(int num, int num2) {
return 0;
}
}
引用成员方法
与调用静态方法不同,引用成员方法 通过对象名来调用方法
语法: 对象名::成员方法名
public interface Book {
// 定义抽象方法
int add(int num, int num2);
}
public class Main implements Book {
public static int number = 0;
public static void main(String[] args) {
// 声明对象
Main main = new Main();
// 通过对象来调用方法
Book book = main::getNumber;
int add = book.add(1, 3);
System.out.println(add);
}
int getNumber(int num, int num2) {
return num + num2;
}
@Override
public int add(int num, int num2) {
return 0;
}
}
引用带泛型方法
public interface Book<T> {
// 定义抽象方法
int add(T[] t);
}
public class Main<T> implements Book<String> {
public static int number = 0;
public static void main(String[] args) {
Main main = new Main();
String [] str = {"书籍", "报表", "服务"};
Book book = main::getNum;
int result = book.add(str);
System.out.println(result);
}
public int getNum(T[] t) {
return t.length;
}
@Override
public int add(String[] t) {
return 0;
}
}
引用构造方法
lambda 表达式有三种引用构造方法的语法,
1、无参构造方法
2、有参构造方法
3、引用数组构造方法
语法:类名::new
为了区分引用静态方法的语法,使用new关键字,表示引用构造方法。
public interface Book {
Print add();
}
public class Print implements Book {
public static void main(String[] args) {
Book book = Print::new;
book.add();
// 无参构造方法
}
public Print() {
System.out.println("无参构造方法");
}
public Print(int i) {
System.out.println("有参构造方法");
}
@Override
public Print add() {
return null;
}
}
public class Print implements Book {
public static void main(String[] args) {
Book book = Print::new;
book.add(2);
// 有参构造方法
}
public Print() {
System.out.println("无参构造方法");
}
public Print(int i) {
System.out.println("有参构造方法");
}
@Override
public Print add(int num) {
return null;
}
}
fuction接口
简介
上文实例中,想要使用lambda表达式,必须先创建或者调用已有的函数式接口。
在这里,java.util.funcation包提供了预定义函数式接口。
Function<T, R> 接口
下面介绍常用接口Function<T, R>接口
T: 被操作的类型,类似于方法参数类型
R:操作结果类型,类似于方法的返回类型
方法 | 功能说明 | 方法返回值 |
---|---|---|
apply(T t) | 抽象方法,按照被子类实现的逻辑,执行函数。参数为被操作泛型对象 | R |
andThen(Function<? super R, ? extends V> after) | 先执行apply(t)方法,将执行结果作为本方法参数,再按照after函数逻辑继续执行 | (T t) -> after.apply(apply(t)) |
compose(Function<? super V, ? extends T> before) | 先按照before函数逻辑操作接口的被操作对象t,再将执行结果作为apply()方法的参数 | (V v) -> apply(before.apply(v)) |
static identity() | 此方法是静态方法。返回一个Function对象,此对象的apply()方法只会返回参数值 | t -> t |
public class Print {
// 把数组转为字符串
Function<String[], String> function = (addressArray) -> {
StringBuffer address = new StringBuffer();
for (String str : addressArray) {
address.append(str);
}
return address.toString();
};
// 按照回车返回数组
Function<String, String[]> after = (addressArray) -> addressArray.split("xx");
// 把每个数组加上回车特殊符号
Function<String[], String[]> before = (addressArray) -> {
String[] new_address = new String[addressArray.length];
for (int i = 0; i < addressArray.length; i++) {
new_address[i] = addressArray[i]+":";
}
return new_address;
};
public static void main(String[] args) {
String[] address = {"xx省", "xx市", "xx区"};
Print print = new Print();
String identity = print.getIdentity(address);
System.out.println(identity);
// 地址数组长度3
String apply = print.getApply(print, address);
System.out.println(apply);
// xx省xx市xx区
String compose = print.getCompose(print, address);
System.out.println(compose);
// xx省:xx市:xx区:
String[] strings = compose.split(":");
String andThen = print.getAndThen(print, strings);
System.out.println(andThen);
// 省市区
}
private String getApply(Print print, String[] address) {
return print.function.apply(address);
}
private String getAndThen(Print print, String[] address) {
Function<String[], String> function1 = print.function;
String[] apply = function1.andThen(print.after).apply(address);
return function1.apply(apply);
}
private String getCompose(Print print, String[] address) {
Function<String[], String> function1 = print.function;
return function1.compose(print.before).apply(address);
}
private String getIdentity(String[] address) {
Function<String, String> identity = Function.identity();
return identity.apply("地址数组长度" + address.length);
}
}
流处理
流处理可以对数据进行过滤、映射、查找和收集功能,并且代码量很少。
缺点是 代码可读性不高。
简介
流处理的接口都定义在java.util.stream包下。BaseStream接口是最基础的接口 是stream接口的子接口。
stream接口时泛型接口,可以操作任何类的对象。
类型:
终端操作,操作会消费流,操作结束之后,被操作的流对象就不能再次执行其他操作了。
中间操作,会生成一个新的流对象,被操作的流对象任然可以执行其他操作。
Collection接口新增两个可以获取流对象的方法。可以获取集合的顺序流。
Stream stream();
获取集合的并行流
Stream parallelStream;
Collectors类
Optional类为收集器类,该类实现了java.util.Collector接口,可以将Stream流对象进行各种各样的封装、归集、分组等操作。
数据过滤
filter()方法
public class Print {
public static void main(String[] args) {
List<String> books = new ArrayList<>();
books.add("123小说");
books.add("245名著");
books.add("书籍");
Stream<String> stream = books.stream();
stream = stream.filter(str -> !str.contains("书籍"));
List<String> list = stream.collect(Collectors.toList());
list.forEach(System.out::println);
// 123小说
//245名著
}
}
distinct()过滤方法
可以去除流中的重复元素
public static void main(String[] args) {
List<String> books = new ArrayList<>();
books.add("123小说");
books.add("123小说");
books.add("书籍");
Stream<String> stream = books.stream();
stream = stream.distinct();
List<String> list = stream.collect(Collectors.toList());
list.forEach(System.out::println);
//123小说
//书籍
}
limit()方法
可以获取流中前N个元素
public static void main(String[] args) {
List<String> books = new ArrayList<>();
books.add("123小说");
books.add("123小说");
books.add("书籍");
Stream<String> stream = books.stream();
stream = stream.limit(2);
List<String> list = stream.collect(Collectors.toList());
list.forEach(System.out::println);
//123小说
//123小说
}
skip()方法
可以忽略流中的前N个元素
public static void main(String[] args) {
List<String> books = new ArrayList<>();
books.add("123小说");
books.add("123小说");
books.add("书籍");
Stream<String> stream = books.stream();
stream = stream.skip(1);
List<String> list = stream.collect(Collectors.toList());
list.forEach(System.out::println);
//123小说
//书籍
}
数据映射
数据的映射和过滤概念不同:过滤是在流中找到符合条件的元素,映射是在流中获得具体的数据。
public static void main(String[] args) {
List<String> books = new ArrayList<>();
books.add("123小说");
books.add("123小说");
books.add("书籍");
Stream<String> stream = books.stream();
stream = stream.filter(s -> s.equals("123小说"));
List<String> list = stream.collect(Collectors.toList());
list.forEach(System.out::println);
//123小说
//123小说
}
数据查找
allMath()方法
判断流中的元素是否全部符合某一条件,返回结果是boolean值。全部元素符合条件true,否则false
public static void main(String[] args) {
List<Integer> books = new ArrayList<>();
books.add(123);
books.add(2);
books.add(5);
Stream<Integer> stream = books.stream();
Boolean result = stream.allMatch(integer -> integer>3);
System.out.println(result);
// false
}
anyMatcd()方法
判断流中的元素是否符合某一条件
public static void main(String[] args) {
List<Integer> books = new ArrayList<>();
books.add(123);
books.add(2);
books.add(5);
Stream<Integer> stream = books.stream();
Boolean result = stream.anyMatch(integer -> integer>3);
System.out.println(result);
// true
}
findFirst()方法
返回符合条件的第一个元素
public static void main(String[] args) {
List<String> books = new ArrayList<>();
books.add("123");
books.add("2");
books.add("444");
Stream<String> stream = books.stream();
Stream<String> integerStream = stream.filter(str -> str.contains("2"));
Optional<String> first = integerStream.findFirst();
System.out.println(first.get());
// 123
}