一、lambda表达式
1、 Lambda表达式是匿名内部类的简化写法。
Lambda标准格式:
(参数类型 参数名) -> {
方法体;
return 返回值;
}
省略规则:
1. 小括号中的参数类型可以省略。
2. 如果小括号中只有一个参数,那么可以省略小括号
3. 如果大括号中只有一条语句,那么可以省略大括号,return,以及;
使用前提:
1. 必须有接口, 接口中有且仅有一个需要被重写的抽象方法。
2. 必须支持上下文推导。 要么有接口作为参数,要么有一个变量接收这个Lambda表达式.
函数式编程: 可推导就是可省略
注:此处省略了persion对象的代码
public class Demo01Lambda {
public static void main(String[] args) {
//使用Lambda表达式实现多线程
new Thread(() -> {
System.out.println("Lambda标准格式执行多线程");
}).start();
//使用Lambda省略格式实现多线程
new Thread(() -> System.out.println("Lambda省略格式执行多线程")).start();
}
}
package cn.itcast.demo01;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
/*
创建集合,保存Person,并且使用比较器排序方式对集合中的Person对象按照年龄升序排序
*/
public class Demo02Lambda {
public static void main(String[] args) {
//创建集合
ArrayList<Person> list = new ArrayList<>();
//添加Person
list.add(new Person("大幂幂", 30));
list.add(new Person("王思聪", 28));
list.add(new Person("王健林", 40));
//使用比较器排序对集合中的Person按照年龄升序排序
/*
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});
*/
Collections.sort(list, (o1, o2) -> o1.getAge() - o2.getAge());
//打印集合
System.out.println(list);
}
}
二、函数式接口
1、概念:如果一个接口中有且仅有一个需要被重写的抽象方法,那么这个接口就是函数式接口。
2、函数式接口的使用:
(1)当做一个普通接口去使用(给其他类实现)。
(2)当做 lambda表达式的使用前提去使用(使用lambda表达式必须的使用函数式接口)。
有一个注解叫做:@ Functionallnterface,真个注解可以验证是否是函数式接口,这个注解在函数的上面,如果某个接口不是函数式接口,那么加上这个注解会报错。
这个注解仅仅用来验证一个接口是否是一个函数式接口,如果没有这个注解,只要某个接口满足函数式接口的条件,那么这个接口就是一个函数式接口。
@FunctionalInterface
public interface MyInterface {
void method();
}
请定义一个函数式接口 Eatable ,内含抽象 eat 方法,没有参数或返回值。使用该接口作为方法的参数,并进而
通过Lambda来使用它
public interface Eatable {
//eat方法
void eat();
}
public class Demo01LambdaTest {
public static void main(String[] args) {
method(() -> System.out.println("使用Lambda表达式吃饭饭,香香哒"));
}
//参数是一个函数式接口。
//参数如果是函数式接口,那么可以传递Lambda表达式
public static void method(Eatable eatable) {
eatable.eat();
}
}
请定义一个函数式接口 Sumable ,内含抽象 sum 方法,可以将两个int数字相加返回int结果。使用该接口作为方法的参数,并进而通过Lambda来使用它。
@FunctionalInterface
public interface Sumable {
int sum(int a, int b);
}
public class Demo02LambdaTest {
public static void main(String[] args) {
method(new Sumable() {
@Override
public int sum(int a, int b) {
return a + b;
}
});
//调用method方法,传递Lambda表达式
method((a, b) -> a + b);
}
public static void method(Sumable sumable) {
int result = sumable.sum(10, 20);
System.out.println("result:" + result);
}
}
三、使用函数式接口打印日志,优化性能浪费问题
(1)传统方式打印日志
步骤:
1. 定义了三个字符串
2. 对这三个字符串进行了拼接,并且当做参数传递给了printLog方法。
3. 进行判断,如果日志等级满足要求,那么就打印日志信息。
如果日志等级不满足要求,那么就不打印。
如果不满足要求,那么就不会打印日志信息,但是之前已经把日志信息拼接好了,拼接好之后却没有使用
这样就产生了性能浪费。
public class Demo01Log {
public static void main(String[] args) {
//定义三个字符串
String s1 = "hello";
String s2 = "world";
String s3 = "java";
//调用printLog方法,打印日志信息
printLog(1, s1 + s2 + s3);
}
//用来打印日志信息
//参数给两个,一个是日志等级。 第二个参数是日志信息
public static void printLog(int level, String msg) {
//进行判断,如果日志等级是3,那么就打印日志信息
if (level == 3) {
System.out.println(msg);
}
}
}
(2)使用Lambda表达式解决性能浪费的问题
如果日志等级满足要求,那么就进行拼接,然后打印这个日志信息。
如果日志等级不满足要求,那么就不拼接了。
Lambda表达式是作为接口中抽象方法的内容存在的。 所以当通过接口调用抽象方法时,才会
执行对应的Lambda表达式。
public class Demo02LogLambda {
public static void main(String[] args) {
//创建三个字符串
String s1 = "hello";
String s2 = "world";
String s3 = "java";
//调用method方法,根据日志等级打印日志信息
method(3, () -> {
String msg = s1 + s2 + s3;
System.out.println(msg);
});
}
//第一个参数为日志等级,第二个参数为用来打印日志的函数式接口。
public static void method(int level, MessageBuilder messageBuilder) {
if (level == 1) {
messageBuilder.printMsg();
}
}
}
.//定义一个函数式接口
public interface MessageBuilder {
void printMsg();
}
四、Lambda表达式的六种使用方式
1、lambda表达式作为方法参数
//lambda表达式做为方法参数
public class Demo01LambdaParam {
public static void main(String[] args) {
new Thread(() -> System.out.println("使用Lambda表达式执行了线程")).start();
}
}
2、Lambda表达式作为返回值类型
/*
Lambda表达式作为返回值
*/
public class Demo02LambdaReturn {
public static void main(String[] args) {
//创建集合
ArrayList<Person> list = new ArrayList<>();
//调用list的add方法,向集合中添加Person对象
list.add(new Person("灭绝师太", 50));
list.add(new Person("金花婆婆", 20));
list.add(new Person("金毛狮王", 30));
//使用比较器排序,对学生根据年龄升序排序
Collections.sort(list, getComparator());
//打印结果
System.out.println(list);
}
public static Comparator<Person> getComparator() {
return (o1, o2) -> o1.getAge() - o2.getAge();
/*
return new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
};
*/
}
}
(1) 请自定义一个函数式接口 MySupplier ,含有无参数的抽象方法 get 得到 Object 类型的返回值。并使用该函数式
接口分别作为方法的参数和返回值。
public class Demo03LambdaTest {
public static void main(String[] args) {
//调用method方法,传递Lambda表达式
//method(() -> "abc");
method(getMySupplier());
}
public static void method(MySupplier mySupplier) {
Object obj = mySupplier.get();
System.out.println(obj);
}
//Lambda表达式作为方法的返回值.
public static MySupplier getMySupplier() {
return () -> "hello";
}
}
public interface MySupplier {
//没有参数,有一个Object类型的返回值
Object get();
}
public interface MySupplier{
return get();
}
五、方法引用
1、 Lambda表达式做的事情是拿到参数之后, 【直接】对参数进行了打印
此时Lambda表达式做的事情就和方法做的事情重叠了。
Lambda表达式拿到参数之后直接进行了打印, 这样的话其实就没有必要再写一遍参数了, 因为这个内容可以推导。
如果Lambda表达式拿到参数之后,直接去干了某些事情,那么可以写成另一种方式,另一种方式是Lambda的孪生兄弟: 方法引用
方法引用是Lambda表达式的一种简化方式
方法的使用格式 :: 两个冒号
public class Demo01Lambda {
public static void main(String[] args) {
//lambda表达式, 方法参数多余了,因为拿到参数之后直接对参数进行了打印
method(str -> System.out.println(str));
//方法引用
//含义, 拿到参数之后,直接使用System.out的println方法打印这个参数.
method(System.out::println);
}
public static void method(Printable printable) {
printable.print("hello");
}
}
public class Demo02Lambda {
public static void main(String[] args) {
//调用method方法,传递一个Lambda表达式
method(num -> System.out.println(num));
//换成方法引用
method(System.out::println);
}
public static void method(PrintNumberable printNumberable) {
printNumberable.printNumber(10);
}
}
public interface Printable {
void print(String str);
}
public interface PrintNumberable {
void printNumber(int num);
}
2、方法 引用的六种方式
对于类来说有四种:
(1)对象引用成员方法
(2)类名引用静态方法
(3)supper应用父类方法
(4)this引用本类方法
对于构造器来说有两种方式
(1)类的构造器引用
(2)数组的构造器引用
1、对象引用成员方法:
格式;对象名::方法名
public class Demo01MethodRef {
public static void main(String[] args) {
//调用method方法,传递Lambda表达式。 将参数字符串转成大写并打印
//method(str -> System.out.println(str.toUpperCase()));
MyClass myClass = new MyClass();
//method(str -> myClass.printUpperStr(str));
//换成方法引用
//拿到参数之后,直接调用myClass对象的printUpperStr方法去对这个参数进行处理。
method(myClass::printUpperStr);
}
//参数是函数式接口,调用的时候可以传递Lambda表示
public static void method(Printable printable) {
printable.print("hello");
}
}
public class Demo02MethodRef {
public static void main(String[] args) {
//调用method方法,传递Lambda表达式
//method(fileName -> System.out.println("Lambda表达式在七手八脚的处理这个文件:" + fileName));
//创建一个助手对象
Assistant assistant = new Assistant();
//method(fileName -> assistant.doFile(fileName));
//方法引用
method(assistant::doFile);
}
public static void method(Helper helper) {
helper.help("工资流水.txt");
}
}
public interface Helper {
//帮助处理文件
void help(String fileName);
}
public class MyClass {
//定义方法,接收一个字符串的参数,并且将这个字符串转成大写打印
public void printUpperStr(String str) {
System.out.println(str.toUpperCase());
}
}
public interface Printable {
void print(String str);
}
2、类名引用过成员方法
格式 类名::静态方法
public class Demo01MethodRef {
public static void main(String[] args) {
//调用method方法,传递Lambda表达式
//method(num -> num > 0 ? num : -num);
//Math数学工具类中有一个abs方法,可以直接求出一个数的绝对值
method(num -> Math.abs(num));
//拿到参数num,直接调用Math类的静态方法abs对参数num进行了处理。
method(Math::abs); //表示拿到参数之后,对参数直接通过Math类的abs方法进行处理。
}
public static void method(Calcable calcable) {
int result = calcable.abs(-10);
System.out.println(result);
}
}
/*
类名引用静态方法的练习
*/
public class Demo02MethodRef {
public static void main(String[] args) {
//调用method方法,传递Lambda表达式,检查参数字符串是否为空(如果这个字符串是null,或者是一个"",那么就是空)
//如果字符串是空,那么就返回true。不是空返回false
//method(str -> str == null || str.equals(""));
method(str -> StringUtils.isBlank(str));
//改成方法引用
method(StringUtils::isBlank); //拿到参数直接对参数使用StringUtils的静态方法isBlank进行处理。
}
public static void method(Checker checker) {
boolean flag = checker.check("");
System.out.println("flag:" + flag);
}
}
3、super引用成员方法
格式 super ::方法名
public class Demo01MethodRef {
public static void main(String[] args) {
//创建一个Student对象
Student stu = new Student();
stu.sayHello();
}
}
@FunctionalInterface
public interface Greetable {
void greet();
}
public class Person {
public void sayHello() {
System.out.println("雷猴");
}
}
public class Student extends Person{
public void sayHello() {
//method(() -> System.out.println("雷猴"));
//method(() -> super.sayHello());
//改成方法引用。 使用super调用了父类的方法。 所以格式为: super::父类方法
method(super::sayHello);
}
//定义方法,方法参数传递一个函数式接口
public void method(Greetable greetable) {
greetable.greet();
}
}
4、this引用本类方法
public class Demo01MethodRef {
public static void main(String[] args) {
Man man = new Man();
man.beHappy();
}
}
public class Man {
public void buyGift() {
System.out.println("买一个500平米的大house");
}
public void marry(Richable richable) {
richable.buy();
}
public void beHappy() {
//marry(() -> System.out.println("买了一个500克拉的大钻戒"));
//marry(() -> buyGift());
//使用this引用本类方法。 this::本类方法
marry(this::buyGift);
}
}
public interface Richable {
void buy();
}
5、类的构造器使用
格式 类名:: new
public class Demo01MethodRef {
public static void main(String[] args) {
//调用method方法。 传递Lambda表达式
//拿到参数姓名和年龄之后直接通过构造方法根据姓名和年龄创建了Person对象.所以可以换成方法引用.
method((name, age) -> new Person(name, age));
//使用类的构造器引用, 因为之前代码是在使用构造方法创建对象
method(Person::new); //含义,拿到参数之后直接使用这些参数通过构造方法创建对象
}
//函数式接口当做方法参数
public static void method(PersonBuilder personBuilder) {
Person p = personBuilder.createPerson("大幂幂", 30);
System.out.println(p);
}
}
6、数组的构造器引用。
格式:
数据类型[]::new
public class Demo02MethodRef {
public static void main(String[] args) {
//调用method方法,传递lambda表达式,参数是多少,就创建一个长度是多少的数组然后返回
//method(len -> new int[len]); //拿到参数len之后直接根据参数类创建了一个int数组。所以可以换成方法引用
method(int[]::new); //拿到参数后,直接根据参数创建了一个int数组
}
//参数传递函数式接口
public static void method(ArrayBuilder arrayBuilder) {
int[] arr = arrayBuilder.createArray(10);
System.out.println(Arrays.toString(arr));
}
}
public interface PersonBuilder {
//给我姓名和年龄,那么我就可以根据这个姓名和年龄给你一个Person对象。
Person createPerson(String name, int age);
}
public interface ArrayBuilder {
//给我长度,我就可以给你对应长度的int数组
int[] createArray(int len);
}
package cn.itcast.demo10;
public class Person {
private String name;
private int age;
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
六、Java提供的函数式接口
在jdk8之后,多了一个java.util.function包。里面提供了大量的函数式接口。
其中有一个接口,叫做Supplier,可以看成一个生产者,里面的get方法,可以获取一个对象
Supplier<T> 泛型表示要获取的对象的数据类型。
抽象方法:
T get() 获取一个对象
public class Demo01Supplier {
public static void main(String[] args) {
//调用method方法。参数是一个函数式接口,可以传递Lambda表达式
method(() -> "hello");
}
//定义一个method方法,把函数式接口Supplier当做方法的参数
public static void method(Supplier<String> supplier) {
String str = supplier.get();
System.out.println(str);
}
}
/*
使用 Supplier 接口作为方法参数类型,通过Lambda表达式求出int数组中的最大值。提示:接口的泛型请使用
java.lang.Integer 类。
*/
public class Demo02SupplierTest {
public static void main(String[] args) {
//定义一个int数组
int[] arr = {5, 10, -20, 3, 8};
method(() -> {
//在Lambda表达式大括号中,求出数组的最大值,然后返回即可
//定义参照物max用于表示每次比较的最大值。 最开始可以把数组中索引为0的元素当成最大的赋值给这个变量
int max = arr[0];
//拿数组中其他的每一个元素和这个max进行比较,如果比max大,那么就把这个值赋值给max
for(int i = 1; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
//求出结果之后,返回最终结果
return max;
});
}
public static void method(Supplier<Integer> supplier) {
//调用Supplier的get方法获取一个结果
Integer result = supplier.get();
System.out.println("result:" + result);
}
}
2、 在java中,还有一个函数式接口叫做Consumer,可以看成一个消费者,用来消费一个数据(使用这个数据去做一些事情)。
Consumer<T> 泛型T,表示要消费数据的数据类型。
抽象方法:
void accept(T t): 表示要对参数t进行消费。(拿到参数t之后去做一些事情)
public class Demo03Consumer {
public static void main(String[] args) {
//调用method方法, 传递一个Lambda表达式
//method(s -> System.out.println(s));
//方法引用的写法
method(System.out::println);
}
//定义方法,把函数式接口Consumer当做参数
public static void method(Consumer<String> consumer) {
consumer.accept("hello");
}
}
/*
在函数式接口Consumer中有一个默认方法,叫做andThen,可以将两个Consumer合并(拼接)成一个Consumer对象
default Consumer andThen(Consumer after): 将两个Consumer进行合并。比如a.andThen(b)。 是对a和b进行合并。并且有先后顺序是先a后b
*/
public class Demo04Consumer {
public static void main(String[] args) {
method(s -> System.out.println(s.toUpperCase()),
s -> System.out.println(s.toLowerCase()));
}
/*
定义方法,要两个Consumer
第一个Consumer用来将Hello全部转成大写并打印
第二个Consumer用来将Hello全部转成小写并打印
*/
public static void method(Consumer<String> one, Consumer<String> two) {
//分别消费处理Hello
//one.accept("Hello");
//two.accept("Hello");
//将one的内容和two的内容合并
//合并之后的three里面包含的one和two的内容, 先one后two。
//Consumer<String> three = one.andThen(two);
//调用three的accept方法,处理Hello
//three.accept("Hello"); //因为three中包含了one和two的内容,所以这句话相当于 one.accept("Hello") two.accept("Hello")
//将one和two合并之后直接调用accept方法处理这个字符串
one.andThen(two).accept("Hello"); //先通过one调用accept,再通过two调用accept
}
}
/*
下面的字符串数组当中存有多条信息,请按照格式“ 姓名:XX。性别:XX。 ”的格式将信息打印出来。要求将打印姓
名的动作作为第一个 Consumer 接口的Lambda实例,将打印性别的动作作为第二个 Consumer 接口的Lambda实
例,将两个 Consumer 接口按照顺序“拼接”到一起
打印数组中的内容,要使用两个Consumer进行打印, 第一个Consumer打印姓名,第二个Consumer打印性别.
还要将这两个Consumer合并成一个Consumer
打印内容
姓名:迪丽热巴。 性别:女
姓名:古力娜扎。 性别:女
姓名:马尔扎哈。 性别:男
*/
public class Demo05ConsumerTest {
public static void main(String[] args) {
String[] array = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };
//调用method方法,进行处理
method(str -> System.out.print("姓名:" + str.split(",")[0]),
str -> System.out.println("。 性别:" + str.split(",")[1]),
array);
}
/*
参数one用来处理数组中每个元素的姓名
参数two用来处理数组中每个元素的性别
参数arr表示要处理的数组
*/
public static void method(Consumer<String> one, Consumer<String> two, String[] arr) {
//遍历数组,拿到数组中的每个元素
for(String str : arr) {
//使用one处理打印姓名
//one.accept(str);
//使用two处理打印性别
//two.accept(str);
//将one和two进行合并,然后调用accept方法
one.andThen(two).accept(str);
}
}
}