1.lambda表达式
1.格式:
(参数类型 参数名称) ->{
方法体;
return 返回值;
}2.省略规则
小括号中的参数类型可以省略。
如果小括号中只有一个参数,那么可以省略小括号。
如果大括号中只有一条语句,那么可以同时省略大括号、return关键字及语句分号。
eg:
new Thread(() ->{
System.out.println("Lambda表达式执行了");
}).start();
方法体:
可以是表达式也可以代码块,是函数式接口里方法的实现
如果负责运算的代码无法用表达式表示,可以使用编写方法实现
但必须用{}包围并按需明确使用 return语句
2.函数式接口分类
a.系统与定义函数接口(Comparator, Runnable)
b.用户自定义函数接口(注解必须有,表达式是直接通过参数列表来实现的,只能有一个有效方法)
@FunctionalInterface
public interface MyInterface {
String info(String tip);
}
3.公共定义的函数式接口
a.功能性接口:Function<T, R>
有输入参数,有返回值
是对接收一个T类型参数,返回R类型的结果的方法的抽象
通过调用apply方法执行内容
需求:给定一个字符串,返回字符串长度
package org.xxxx.demo01;
import java.util.function.Function;
public class Demo01 {
public static void main(String[] args) {
// 定义字符串
String str = "helloworld";
// 调用方法
// 在调用的时候写方法体,方法比较灵活
int length = testFun(str, (s) -> s.length());
System.out.println(length);
}
// 方法
/**
*
* @param str 输入参数
* @param fun 表达式 String 为输入类型,Integer为输出类型
* @return 返回字符串长度
*/
public static int testFun(String str, Function<String, Integer> fun) {
// 执行
Integer length = fun.apply(str);
return length;
}
}
b.消费型接口:Consumer<T>
有输入参数,没返回值
对应的方法类型为接收一个参数,没有返回值
一般来说使用Consumer接口往往伴随着一些期望状态的改变
或者事件的发生,典型的forEach就是使用的Consumer接口
虽然没有任何的返回值,但是向控制台输出结果
Consumer 使用accept对参数执行行为
package org.xxxx.demo01;
import java.util.function.Consumer;
public class Demo01 {
public static void main(String[] args) {
// 创建字符串
String str = "hello world";
// 调用
testCon(str, (s) -> System.out.println(s));
}
/**
*
* @param str 传入参数
* @param con
*/
public static void testCon(String str, Consumer<String> con) {
// 执行
con.accept(str);
}
}
c.供给型接口:Supplier<T>
无传入参数,有返回值
该接口对应的方法类型不接受参数,但是提供一个返回值
使用get()方法获得这个返回值
package org.xxxx.demo01;
import java.util.function.Supplier;
public class Demo01 {
public static void main(String[] args) {
// 创建字符串
String str = "hello world";
// 调用
String sup = testSup(() -> str);
System.out.println(sup);
}
/**
*
* @param sup
* @return
*/
public static String testSup(Supplier<String> sup) {
// 执行
String s = sup.get();
return s;
}
}
d.断言型接口:Predicate<T>
有传入参数,有返回值Boolean
该接口对应的方法为接收一个参数,返回一个Boolean类型值
多用于判断与过滤,使用test()方法执行这段行为
需求:输入字符串,判断长度是否大于0
package org.xxxx.demo01;
import java.util.function.Predicate;
public class Demo01 {
public static void main(String[] args) {
// 创建字符串
String str = "hello world";
// 调用
boolean flag = testPre(str, (s) -> s.length() > 0);
System.out.println(flag);
}
/**
*
* @param str
* @param pre
* @return
*/
public static boolean testPre(String str, Predicate<String> pre) {
// 执行
boolean flag = pre.test(str);
return flag;
}
}
3.Lambda 和 匿名内部类对比
1.所需的类型不一样
匿名内部类,需要的类型可以使类,抽象类,接口;
Lambda表达式,需要的类型必须是接口。
2.抽象方法的数量不一样
匿名内部类所需的接口中抽象方法的数量随意;
Lambda表达式所需的接口只能有一个抽象方法。
3.实现原理不同
匿名内部类是在编译后,会形成额外的一个 类名$0 的.class文件
Lambda 表达式实在程序运行的时候动态生成 .class 文件
4.例子
public class CollectionsLambdaDemo{
public static void main(String[] args) {
List<Person> persons = new ArrayList<>();
persons.add(new Person("刘德华",58,174));
persons.add(new Person("张学友",56,176));
persons.add(new Person("郭富城",54,171));
persons.add(new Person("黎明",53,178));
//1.匿名内部类,对集合进行排序
Collections.sort(persons, new Comparator<Person>() {
//年龄降序排序
@Override
public int compare(Person o1, Person o2) {
return o2.getAge() - o1.getAge();
}
});
for (Person person: persons) {
System.out.println(person);
}
System.out.println("-----------------------------------");
//2.Lambda表达式
Collections.sort(persons,(Person o1,Person o2) ->{
return o1.getAge() - o2.getAge();
});
for (Person person: persons) {
System.out.println(person);
}
}
}
//测试结果:
Person{name='刘德华', age=58, height=174}
Person{name='张学友', age=56, height=176}
Person{name='郭富城', age=54, height=171}
Person{name='黎明', age=53, height=178}
-----------------------------------
Person{name='黎明', age=53, height=178}
Person{name='郭富城', age=54, height=171}
Person{name='张学友', age=56, height=176}
Person{name='刘德华', age=58, height=174}
5.方法引用
5.1格式
符号表示:双冒号(::)
符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用。
应用场景:如果 Lambda 所要实现的方案,已经在其他方法存在相同方案,那么则可以使用方法引用。
5.2 常见引用方式
方法引用在 JDK8 中使用方式相当灵活,有如下几种形式:
1.instanceName::methodName 对象名::方法名
2.ClassName::staticMethodName 类名::静态方法名
3.ClassName::methodName 类名::方法名
4.ClassName::new 类名::new
5.TypeName[ ]::new 数组类型::new (String[]::new)
5.2.1 对象名::方法名
public class InstanceName_methodName_demo {
public static void main(String[] args) {
Date now = new Date();
//Lambda 普通写法(调用方法)
Supplier<Long> supplier1 = ()->{
return now.getTime();
};
Long aLong = supplier1.get();
System.out.println("普通写法:"+aLong);
//Lamdba 方法引用(对象名::成员方法名)
Supplier<Long> supplier2 = now::getTime;
Long bLong = supplier2.get();
System.out.println("方法引用写法:"+bLong);
//使用 方法引用 来修改Time
Consumer<Long> consumer = now::setTime;
consumer.accept(100000000000L);
Supplier<Long> supplier = now::getTime;
Long aLong1 = supplier.get();
System.out.println("修改后的Time:"+aLong1);
}
}
注意事项:
被引用的方法,参数要和接口中抽象方法的参数一样;(即:getTime() 方法无参,Supplier接口抽象方法get()方法也无参)
当接口抽象方法有返回值时,被引用的方法也必须有返回值。
5.2.2.类名::静态方法名
由于在 java.lang.System 类中已经存在了静态方法 currentTimeMillis(),
所以当我们需要通过 Lambda 表达式来调用该方法时,可以使用方法引用。
通过类名::静态方法名的方式来调用
public class className_abstractMethodName_demo {
public static void main(String[] args) {
//Lambda普通写法(类名.静态方法名)
Supplier<Long> supplier1 = ()->{
return System.currentTimeMillis();
};
Long aLong = supplier1.get();
System.out.println("普通调用:"+aLong);
//Lambda 方法引用
Supplier<Long> supplier2 = System::currentTimeMillis;
Long bLong = supplier2.get();
System.out.println("方法引用:"+bLong);
}
}
5.2.3类名::方法名
在Java面向对象中,类名是只能调用静态方法的。此处使用类名::方法名是有前提的,实际上是拿第一个参数作为方法的调用者。看如下代码先了解一下你就明白了。
public class className_methodName_demo {
public static void main(String[] args) {
//Lambda 表达式(使用 对象.方法名 形式调用)
Function<String,Integer> fn1 = (String str)->{
return str.length();
};
Integer aLength = fn1.apply("hello world");
System.out.println("字符长度为:"+aLength);
//Lambda 表达式(使用 方法引用【类名::方法名】方式调用)
Function<String,Integer> fn2 = String::length;
Integer bLength = fn2 .apply("hello world");
System.out.println("字符长度为:"+bLength);
}
}
5.2.4 例子
/**
* TODO 实例:自定义方法 (由繁到简)一步步分析
* 从 1.Lambda表达式 ---> 2.对象名::方法名 ---> 3.类名::方法名 形式 一步步分析
*
* @author liuzebiao
* @Date 2020-1-6 15:38
*/
public class Demo01 {
//数组求和方法
public int getSum(int[] arr){
int sum = 0;
for(int n : arr){
sum += n;
}
return sum;
}
public static void main(String[] args) {
//自定义一个数组求和方法 getSum(),本例使用【类名::方法名形式】方法引用方式
//分析:调用getSum()方法,传入一个 int[]类型参数,返回一个Integer值,此处用到 Function<int[],Integer> 函数式接口
//步骤:
//1.这种方式显然可以,但是Lambda表达式是冗余的
Demo01 demo = new Demo01();
Function<int[],Integer> f1 = (int[] arr)->{
return demo.getSum(arr);
};
//2.我们可以使用方法引用 demo::getSum;【对象名::方法名】
Function<int[],Integer> f2 = demo::getSum;
//3.上面2中使用的是 对象名::成员方法,自己写的方法怎么使用【类名::方法名】方式
// 分析:此时就得用到 BiFunction 函数式接口了。BiFunction 类似于 Function 函数式接口
// 差别在于参数个数不同:BiFunction 传递2个参数,Function传递1个参数
// 为什么需要传递两个参数呢?
// 通过刚才图片分析: 2=1.3方法。此处可以理解为【Integer =Demo01.getSum(int[])】这种格式
// 所以如果使用【类名::方法名】这种方式,第一个参数必须是当前类名,剩下才是传递的参数和返回参数类型
// 如果分析用2个参数,使用【类名::方法名】这种方式,则需要传3个参数,JDK8帮我们提供了BiFunction/BiConsumer/BiPredicate这3种
BiFunction<Demo01, int[], Integer> f3 = Demo01::getSum;
int[] arr = {11, 22, 33, 44, 55};
// 走到此处,调用apply()方法传参时,此处还得传递一个当前类的实例(new Demo01() new实例/Demo01.class.newInstance()反射等方式)
// 显然还不如直接使用 上面 2 中的【对象名::方法名】这种方式呢。所以【类名::方法名】这种方式很少会用到
// 所以 BiFunction/BiConsumer/BiPredicate这3种很少用到
Integer sum = f3.apply(new Demo01(), arr);
System.out.println(sum);
}
}
5.2.5 类名::new(构造器引用)
使用前提:
构造器参数列表要与接口中抽象方法的参数列表一致!
/**
* TODO person实体类
*
* @author liuzebiao
* @Date 2020-1-7 15:47
*/
public class Person {
private String name;
private int age;
public Person() {
System.out.println("执行无参构造方法");
}
public Person(String name, int age) {
System.out.println("执行有参构造方法,name:"+name+";age:"+age);
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
/**
* TODO 构造器引入Demo
*
* @author liuzebiao
* @Date 2020-1-7 15:49
*/
public class className_new_constructor {
public static void main(String[] args) {
//1.使用对象无参构造器,创建一个 Person 对象
//1.1 使用 Lambda 表达式
Supplier<Person> sup1 = ()->{
return new Person();
};
Person person1 = sup1.get();
System.out.println(person1);
//1.2 使用方法引用【类名::new(构造器)】方式
Supplier<Person> sup2 = Person::new;
Person person2 = sup2.get();
System.out.println(person2);
//2.使用对象有参构造器,创建一个 Person 对象
//此种情况会用到 BiFunction 函数式接口(前两个参数对应构造器参数类型,第三个参数为返回值类型)
//2.1 使用 Lambda 表达式
BiFunction<String,Integer,Person> bif1 = (String name,Integer age)->{
return new Person(name,age);
};
Person person3 = bif1.apply("Mary", 18);
System.out.println(person3);
//2.2 使用方法引用【类名::new(构造器)】方式
BiFunction<String,Integer,Person> bif2 = Person::new;
Person person4 = bif2.apply("Mary", 18); //它会根据 apply()方法传入的参数来判断调用哪个构造器
System.out.println(person4);
}
}
5.2.6 .数组类型::new(数组引入)
/**
* TODO 数组引用 创建数组
*
* @author liuzebiao
* @Date 2020-1-7 16:39
*/
public class array_new_demo {
public static void main(String[] args) {
//1.使用 Lambda 表达式,创建一个长度为10的int数组
Function<Integer,int[]> fn1 = (Integer num)->{
return new int[num];
};
int[] arr1 = fn1.apply(10);
System.out.println(Arrays.toString(arr1));
//2.使用 数组引用 方式,创建一个长度为10的int数组
Function<Integer,int[]> fn2 = int[]::new;
int[] arr2 = fn2.apply(10);
System.out.println(Arrays.toString(arr2));
}
}