文章目录
1、Lambda表达式
1.1 初识lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
上述的简介是不是听着有些晕头转向,别急,下面通过一个简单的例子去说明:
需求:有一张员工表,两个字段,name和age,如果我们需要获取年龄大于18岁的员工,有多少种做法呢?
//模仿从数据库获取的数据源
List<Employee> list= Arrays.asList(
new Employee("张三","15"),
new Employee("李四","45"),
new Employee("王五","55"),
new Employee("小刘","65")
);
方案一:遍历输出。缺点:当我需要修改需求,比如查找姓名为XXX的员工信息,得修改代码,其中大多代码重复,存在代码冗余。
@Test
public void test03(){
for (Employee employee:list) {
if(Integer.parseInt(employee.getAge())>=35){
System.out.println(employee);
}
}
}
方案二:使用策略设计模式。
接口中定义一个查询方法,当我需要查询某个需求时,再定义一个实现类去实现此接口。根据不同的需求,传递的参数也不同,其相同之处在于:传递的实现类均实现了同一个方法,由于其方法名相同,故需要修改的代码很小
public interface FilterEmployee<T> {
public boolean find(T t);
}
//实现类一
public class FilterEmployeeByName implements FilterEmployee<Employee> {
@Override
public boolean find(Employee employee) {
if(employee.getName().contains("王")){
return true;
}
return false;
}
}
//实现类二
public class FilterEmployeeByAge implements FilterEmployee<Employee> {
@Override
public boolean find(Employee employee) {
if(Integer.parseInt(employee.getAge())<=35){
return true;
}
return false;
}
}
测试:
@Test
public void test04(){
List<Employee> lists = find(this.list, new FilterEmployeeByAge());
for (Employee e:lists) {
System.out.println(e);
}
//找到name中包含‘王’的员工信息
List<Employee> list2 = find(this.list, new FilterEmployeeByName());
for (Employee e:list2) {
System.out.println(e);
}
}
//根据条件对数据集进行筛选
public List<Employee> find(List<Employee>employees,FilterEmployee<Employee> t){
ArrayList<Employee> list = new ArrayList<>();
for(Employee e:employees){
if(t.find(e)){
list.add(e);
}
}
return list;
}
方案三:上述方式中每次都需要重新创建一个新的类,使用匿名内部类
@Test
public void test06(){
List<Employee> lists = find(this.list, new FilterEmployee<Employee>() {
@Override
public boolean find(Employee employee) {
if(employee.getName().contains("王")){
return true;
}
return false;
}
});
for (Employee e:lists) {
System.out.println(e);
}
}
优点:方案三相比方案二,好处在于当新的需求来到时,不需要重新创造一个对象去继承接口FilterEmployee,直接使用匿名类;相比方案一的好处在于更加灵活,代码冗余低。
缺点:可以发现其有用的代码仅仅为6-9行是业务逻辑判断。
方案四:使用lambda表达式。
@Test
public void test07(){
List<Employee> lists = find(this.list,(e)-> Integer.parseInt(e.getAge())>=20);
for (Employee e:lists) {
System.out.println(e);
}
}
//根据条件对数据集进行筛选
public List<Employee> find(List<Employee>employees,FilterEmployee<Employee> t){
ArrayList<Employee> list = new ArrayList<>();
for(Employee e:employees){
if(t.find(e)){
list.add(e);
}
}
return list;
}
小结:通过上述比较,我们可以发现lambda表达式相当于将对接口匿名类创建的一种简单的写法。
1.2、lambda表达式的规范
上述小案例讲述了lambda表达式的用法,那么其具体的语法规则的如何的呢?
/**
* lambda表达式的基础语法:Java8中引入的新的操作符:“->”,该操作符称为箭头操作符或lambda操作符
* 将lambda表达式拆分成两部分:
* 左侧:Lambda表达式的参数列表 对应接口中抽象方法的参数列表
* 右侧:Lambda表达式中所需要执行的功能,即Lambda体
* 语法格式一:
* 接口中的抽象方法,无参数,无返回值
* ()->System.out.println("world");
* 语法格式二:
* 接口中的抽象方法,有一个参数,无返回值
* 接口中有一个参数,若只有一个参数,小括号可以不写
* (t)-> System.out.println(t)
* t-> System.out.println(t)
* 语法格式三:
* 有多个参数,有返回值,并且Lambda体中有多条语句
* (x,y)->{
* System.out.println("helloworld");
* return Integer.compare(x,y);
* };
* 语法格式四:
* 有多个参数,有返回值,但是lambda体中只有一条语句
* 那么return和{}都可以省略
* (x,y)->Integer.compare(x,y);
* 语法格式五:
* Lambda表达式参数的数据类型可以省略不写,因为JVM编译器通过上下文推断出数据类型,即“类型推断”
* (Integer x,Integer y)->Integer.compare(x,y);
* 左右遇一括号省
* 左侧推断类型省
*
* Lambda表达式需要“函数式接口”的支持
* 函数式接口:接口中只有一个抽象方法,称之为函数式接口
* 使用注解@FunctionalInterface修饰,可以检查是否为函数式接口
*/
上面的子太多,看的晕头转向?别急,案例马上就到!
咱们先回顾一下上一步中案例的内容,记住一点:lambda表达式是原有的匿名内部类的简写方式,我们可以理解为新的语法糖。
注:函数式接口:接口中只有一个抽象方法,称之为函数式接口使用注解@FunctionalInterface修饰,可以检查是否为函数式接口
首先记住基础语法:“->”,该操作符称为箭头操作符或lambda操作符
将lambda表达式拆分成两部分:
左侧:Lambda表达式的参数列表 对应接口中抽象方法的参数列表
右侧:Lambda表达式中所需要执行的功能,即Lambda体
①、Lambda表达式:无参数无返回值
@Test
public void test01(){
int num=0;//在jdk1.7之前,必须是final,但是其实默认已经是final了,在内部类中无法修改其值
//传统方式
Runnable r=new Runnable() {
@Override
public void run() {
System.out.println("hello"+num);
}
};
r.run();
System.out.println("----------------");
//使用lambda表达式
Runnable r1=()-> System.out.println("world"+num);
r1.run();
}
②、Lambda表达式:有参数,有返回值
public interface Consumer<T> {
void accept(T t);
}
@Test
public void test02(){
Consumer<String> c=(t)-> System.out.println(t);
c.accept("helloworld");
}
③、Lambda表达式:有多个参数,有返回值
@Test
public void test03(){
Comparator<Integer> com=(x,y)->{
System.out.println("helloworld");
return Integer.compare(x,y);
};
}
有没有疑问:(x,y)中没有指明参数类型,那么lambda表达式是如果知道的?
上述 Lambda 表达式中的参数类型都是由编译器推断得出的。 Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。 Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断” 。
通俗一点讲,就是在Comparator《Integer》中指明了参数的类型。
④、Lambda表达式:单参数并返回值
//需求:对一个数进行运算,没有指出做何运算
@Test
public void test05(){
/**
* 单参数并返回值
*把这个lambda表达式看作匿名内部类,那等式左边部分
*相当于该类的引用变量,而lambda体中则定义出了该接口的方法实现
*/
LambdaFunc01<Integer> option=(num)->num*num;
System.out.println(option.getvalue(100));
Integer result = get(100, (num ->num+10 ));
System.out.println(result);
}
public Integer get(Integer m,LambdaFunc01<Integer> t){
return t.getvalue(m);
}
⑤、Lambda小练习
功能一:根据员工的联络进行递减排序,如果年龄相同则比较姓名(多参数且有多个返回值)
功能二:处理字符串大小写转换(单参数且返回值)
功能三:处理参数的和与积
功能1:根据员工的联络进行递减排序,如果年龄相同则比较姓名(多参数且有多个返回值)
List<Employee> list= Arrays.asList(
new Employee("张三","75"),
new Employee("张三1","85"),
new Employee("王五","55"),
new Employee("李四","55"),
new Employee("小刘","65")
);
/**
* 需求:根据年龄进行递减排序,如果年龄相同则比较姓名
* 多个参数且有返回值
*/
@Test
public void test01(){
Collections.sort(list,(x,y)->{
if(x.getAge().equals(y.getAge())){
return -x.getName().compareTo(y.getName());
}else{
return -(Integer.parseInt(x.getAge())-Integer.parseInt(y.getAge()));
}
});
list.stream().forEach(System.out::println);
}
注:Collections.sort(List list, Comparator<? super T> c),其中lambda表达式相当于是对接口Comparator中【函数式接口】的compare方法的实现。
功能二:处理字符串大小写转换(单参数且返回值)
@FunctionalInterface//标明这是一个函数式接口
public interface LambdaFunc02<T>{
public String getvalue(String str);
}
public String strHandle(String str,LambdaFunc02<String> t){
return t.getvalue(str);
}
@Test
public void test02(){
String s = strHandle(" abcdef ", (str) -> str.trim().toUpperCase());
System.out.println(s);//ABCDEF
String s1 = strHandle("abcdefghjk", (str) -> str.substring(2, 4));
System.out.println(s1);//cd
//使用内置接口
Function<String,String>fun1=(str)->str.trim().toUpperCase();
System.out.println(fun1.apply(" abcdef "));//ABCDEF
}
注:内置接口:java中内置的函数式接口,下午会讲。
功能三:处理参数的和与积
@FunctionalInterface
public interface LambdaFunc03<T,R>{
public R getvalue(T t1,T t2);
}
//处理参数的和与积
public Long op(Long l1,Long l2,LambdaFunc03<Long,Long>t){
return t.getvalue(l1,l2);
}
@Test
public void test03(){
System.out.println(op(100L,135L,(x,y)->x+y));
System.out.println(op(100L,135L,(x,y)->x*y));
//直接使用接口也可以,不需要下面的函数定义
LambdaFunc03<Long,Long> t=(x,y)->x/y;
System.out.println(t.getvalue(100L,10L));
//使用内置接口
BiFunction<Integer,Integer,Integer>fun=(x,y)->x*y;
System.out.println(fun.apply(100,100));
}
**小结:**lambda表达式是对函数式接口中的一种匿名类的实现方式,其lambda体相当于是实现了接口的方法。
1.3 Lambda内置核心接口
在上述案例中,我们有自定义下面三种接口,但是事实上这些接口的作用仅仅是定义了参数和返回值的类型、数量等, 因为我们所需要的仅仅是:这个函数式接口是否有返回值,形参有几个,具体接口是啥名字,方法是啥名字,我们不需要管因为其具体的实现内容在lambda体中实现。
@FunctionalInterface
public interface LambdaFunc01<T>{
public Integer getvalue(Integer num );
}
@FunctionalInterface
public interface LambdaFunc02<T>{
public String getvalue(String str);
}
@FunctionalInterface
public interface LambdaFunc03<T,R>{
public R getvalue(T t1,T t2);
}
在Java8中的四大内置核心函数式接口:
/**
* Consumer<T>:消费型接口
* void accept(T t) 传进去一个参数没有任何的返回结果
* Supplier<T>:共给型接口
* T get()
*
* Function<T,R> :函数型接口
* R apply(T t)
* Predicate<T>:断言型接口
* boolean test(T t)
*
*/
通过下面的例子加深理解:
需求一:把字符串去掉空格并转为大写打印。
@Test
public void test01(){
Consumer<String> con=(ch)-> {
String s = ch.trim().toUpperCase();
System.out.println(s);
};
con.accept(" abnd ");
}
需求二:生成一些数,将结果放在集合中。
public List<Integer> getNumberList(Integer num, Supplier<Integer> t){
List<Integer> list = new ArrayList<>();
for (int i=0;i<num;i++){
list.add(t.get());
}
return list;
}
需求三:处理字符串。
public String stringHandle(String str, Function<String,String>t){
return t.apply(str);
}
@Test
public void test03(){
String ch=" coderxz的博客 ";
String s = stringHandle(ch, (str) -> str.trim().toUpperCase());
System.out.println(s);
System.out.println(stringHandle(ch,(str)->str.trim().substring(2,5)));
}
需求四:将满足条件的字符串放入集合中。
public List<String> filterString(List<String> list, Predicate<String> t){
List<String> list1 = new ArrayList<>();
for(String ch:list){
if(t.test(ch)){
list1.add(ch);
}
}
return list1;
}
@Test
public void test04(){
List<String> list1 = new ArrayList<>();
list1.add("张三");
list1.add("ab");
list1.add("1234");
list1.add("12");
//将长度大于3的过滤出来
List<String> list = filterString(list1, (str) -> str.length() > 3);
System.out.println(list);
}
小结:lambda表达式相当于就是对函数式接口中方法的实现。
除了上述接口,还提供了其他多参数的接口:
1.4 Lambda方法引用(提高篇)
当要传递给Lambda体的操作,已经有实现的方法了,那么我们可以直接调用此方法的引用!【实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致】
方法引用:使用操作符 :: 将发那个发明和对象或类的名字分隔。
主要有三种语法格式:
对象::实例方法名
类::静态方法名
类::实例方法名
是不是对上述的使用前提条件感觉很绕,这样理解:lambda表达式相当于是实现接口吧,那么当我们使用方法引用作为lambda表达式的时候,这个方法引用肯定是调用了实例方法(或静态方法),那么调用的实例方法的参数、返回值必须与接口中的参数、返回值个数保持一致。还是有点难以理解?那么就看下面这段代码吧:
/**
* 对象::实例方法名
* 注意:lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
* 比如说: void accept(T t)与 void println(T t)
*/
@Test
public void test01(){
Consumer<String> con=(x)-> System.out.println(x);
con.accept("hello");
PrintStream s=System.out;
Consumer<String> con2=s::println;
con2.accept("world");
}
PrintStream中的方法: void println(String x),其参数个数、返回值类型均与接口Consumer中void accept(T t)相同。lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
如果还不能理解,再看看下面这段代码:
@Test
public void test02(){
Employee employee = new Employee("小刘", "65");
Supplier<String> sup=()-> employee.getName();
System.out.println(sup.get());
Supplier<String> sup2=employee::getAge;
System.out.println(sup2.get());
}
String getAge()与接口Supplier中的T get()。
下面的两种调用方式:
/**
* 类::静态方法名
*/
@Test
public void test03(){
Comparator<Integer>com=(x,y)->Integer.compare(x,y);
Comparator<Integer>com2=Integer::compare;
}
/**
* 类::实例方法名
*/
@Test
public void test04(){
BiPredicate<String,String>bp=(x,y)->x.equals(y);
System.out.println(bp.test("hello","hello"));
BiPredicate<String,String>bp2=String::equals;
System.out.println(bp2.test("hello","hello1"));
}
1.5 Lambda构造器引用(提高篇)
构造器引用:
ClassName :: new
注意:需要调用的构造器参数列表需要与函数式接口中的抽象方法的参数保持一致!
数组引用:type::new
对于数组的引用:
@Test
public void test04(){
Function<Integer,String[]>fun=(x)->new String[x];
String[] str = fun.apply(10);
System.out.println(str.length);
Function<Integer,String[]>fun2=String[]::new;
String[] str2 = fun.apply(10);
System.out.println(str2.length);
}
对于自定义对象的引用:
@Test
public void test01(){
Supplier<Employee> sup=()->new Employee();
Employee employee = sup.get();
//构造器引用
/**
* 疑问:此时Employee中有很多构造方法,那么调用的是哪一个呢?
* 调用的是无参构造方法。
* 因为lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致
*/
Supplier<Employee>sip2=Employee::new;
}
/**
* 此时调用的一个参数的构造方法
* lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致
*/
@Test
public void test02(){
Function<String,Employee>func1=(x)->new Employee(x);
Function<String,Employee>func=Employee::new;
Employee employee = func.apply("rxz");
System.out.println(employee);
}