Java8新特性lambda表达式笔记

Lambda简介

JDK8之后,java支持了lambda表达式这个特性.

Lambda表达式, 也称为闭包, 他是推动Java8发布的最重要新特性
Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中)
使用 Lambda 表达式可以使代码变的更加简洁紧凑

  • lambda可以用更精简的代码创建匿名内部类.但是该匿名内部类实现的接口只能有一个抽象方法,否则无法使用!
  • lambda表达式是编译器认可的,最终会将其改为内部类编译到class文件中

Lambda表达式是JDK1.8之后的一种语法,是一个匿名函数,是对匿名函数的简写形式,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递),可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升;

先来看一下使用Lambda表达式的效果

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class MyTest {
    public static void main(String[] args) {
        Integer[] ints = {98, 243, 35, 13, 57, 243};
        List<Integer> list = Arrays.asList(ints);

        //之前的排序
        list.sort(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        System.out.println(list);
        //[243, 243, 98, 57, 35, 13]

        //使用Lambda表达式
        list.sort((o1,o2)->(o1-o2));

        System.out.println(list);
        //[13, 35, 57, 98, 243, 243]
    }
}

通过上面的对比,发现Lambda表达式式书写起来更为简洁;

Lambda语法

JDK1.8之后引入的一种语法,他的写法是使用一个->符号,箭头将Lambda表达式分为左右两部分,

  • 左边写的是实现的这个接口中的抽象方法中的形参列表,
  • 右边就是对抽象方法的处理;

lambda 表达式的语法格式如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

1.即使lambda表达式没有参数,仍然要提供空括号,类似于⽆参⽅法() -> {....}
2.如果可以推导出⼀个lambda表的参数类型,则可以忽略其类型 (s1, s2) -> {...}
3.如果⽅法只有⼀个参数,⽽且这个参数类型可以推导出,那么可以省略⼩括号a -> {....}
4.如果⼀个lambda表达式只在⼀些分⽀返回值,这是不合法的如 (int x) -> {if (x > 0) return 1;}


以下是lambda表达式的重要特征:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定表达式返回了一个数值。

Lambda表达式实例

因为Lambda表达式的核心就是实现的这个接口中的抽象方法中的形参列表 -> 抽象方法的处理,因此根据形参列表与返回值的不同,Lambda表达式的具体写法也不相同;

Lambda 表达式的简单例子:

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

无返回值有形参的抽象方法

public class MyTest1 {
    public static void main(String[] args) {
    
        MyInterface myInterface = new MyInterface() {
            @Override
            public void show(int a, int b) {
                System.out.println(a + b);
            }
        };

        myInterface.show(20, 30);//50

        //简写1:方法名可以自己推断出来
        MyInterface myInterface1 = (int a, int b) -> {
            System.out.println(a + b);
        };

        myInterface1.show(20, 40);//60

        //简写2:可以省略形参列表中的形参类型
        MyInterface myInterface2 = (a, b) -> {
            System.out.println(a + b);//70
        };

        myInterface2.show(20, 50);

        //简写3:如果抽象方法中只有一行代码,可以省略方法体的大括号,当然,如果不止一行,就不能省略
        MyInterface myInterface3 = (a, b) -> System.out.println(a + b);
        myInterface3.show(20, 60);//80
    }
}
public interface MyInterface {
    public abstract void show(int a,int b);
}
  • 可以省略方法名,IDEA会帮你自动检测方法名;
  • 可以省略方法中的形参类型;
  • 如果对抽象方法的实现逻辑只有一行,可以省略方法体的大括号,当然如果不止一行,就不能省略了

有返回值的抽象方法

public class MyTest2 {
    public static void main(String[] args) {
        MyInterface1 test1 = new MyInterface1() {
            @Override
            public int test(int a, int b) {
                return a - b;
            }
        };
        System.out.println(test1.test(90, 8));//82

        //简写1:
        MyInterface1 test2 = (int a, int b) -> {
            return a - b;
        };
        System.out.println(test2.test(20, 10));//10

        //简写2:
        MyInterface1 test3 = (a, b) -> {return a - b;};
        System.out.println(test3.test(30, 10));//20

        //简写3:这个有返回值的方法,不能直接去掉大括号,还需要去掉return关键字
        MyInterface1 test4 = (a, b) -> a - b;
        System.out.println(test4.test(40, 10));//30
    }
}
public interface MyInterface1 {
    public abstract int test(int a,int b);
}

有返回值的方法,如果要去掉大括号,还需要去掉return关键字;

有一个形参的抽象方法

public class MyTest3 {
    public static void main(String[] args) {
        MyInterface2 myInterface = a -> a-20;
        myInterface.show(20);
    }
}
public interface MyInterface2 {
    public abstract int show(int a);
}

形参列表中只有一个参数,可以去掉形参的括号;

Lambda表达式作为参数传递


Lambda表达式也可以作为参数传递;

import java.util.Arrays;
public class MyTest4 {
    public static void main(String[] args) {
        Integer[] ints = {89, 67, 23};
        Arrays.sort(ints, (o1, o2) -> o1-o2);
        System.out.println(Arrays.toString(ints));
        //[23, 67, 89]
    }
}

综合例子


在 Java8Tester.java 文件输入以下代码:
public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
        
      // 类型声明
      MathOperation addition = (int a, int b) -> a + b;
        
      // 不用类型声明
      MathOperation subtraction = (a, b) -> a - b;
        
      // 大括号中的返回语句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
        
      // 没有大括号及返回语句
      MathOperation division = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
      // 不用括号
      GreetingService greetService1 = message -> System.out.println("Hello " + message);
        
      // 用括号
      GreetingService greetService2 = (message) -> System.out.println("Hello " + message);
        
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
    
   interface MathOperation {
      int operation(int a, int b);
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
    
   private int operate(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
}

执行以上脚本,输出结果为:

$ javac Java8Tester.java
$ java Java8Tester
10 + 5 = 15
10 - 5 = 5
10 x 5 = 50
10 / 5 = 2
Hello Runoob
Hello Google

使用 Lambda 表达式需要注意以下两点:

  1. Lambda 表达式主要用来定义行内执行的方法类型接口(例如,一个简单方法接口)。在上面例子中,我们使用各种类型的 Lambda 表达式来定义 MathOperation 接口的方法,然后我们定义了 operation 的执行。
  2. Lambda 表达式免去了使用匿名方法的麻烦,并且给予 Java 简单但是强大的函数化的编程能力。

Lambda表达式注意事项

Lambda表达式不是万能的,他需要函数式接口的支持;

函数式接口的定义是: 只包含一个抽象方法的接口,称为函数式接口;
其实我们的Lambda表达式就是对函数式接口的一种简写方式,所以只有是函数式接口,我们才能用Lambda表达式;再换句话说,Lambda表达式需要函数式接口的支持,那函数式接口我们可以自己定义,当然JDK1.8也给我们提供了一些现成的函数式接口;
在这里插入图片描述

  • 可以通过 Lambda 表达式来创建该接口的对象,我们可以在任意函数式接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口;

  • 为什么只能有一个抽象方法,如果有多个抽象方法,这个接口不是函数式接口,简写的时候省略了方法名,IDEA不能知道到底重写的是哪一个方法,不能推断出来;

  • 注解写在接口声明上面,如果不报错,就不是函数式接口;

  • JDK1.8之后,提供了很多函数式接口,作为参数传递;

Java中四大函数式接口

1、函数式接口:只有一个方法的接口; 2、有且仅有一个抽象方法的接口,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。Lambda就是Java中函数式编程的体现; 3、主要分布在 java.util.function 包下,
常见的 4大原始函数 接口为:Function (函数型接口)Predicate (断定型接口)Consumer (消费型接口)Supplier (供给型接口)
在这里插入图片描述

方法引用

先来看一下什么是方法引用:

方法引用其实是Lambda表达式的另一种写法,当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用;

注意: 实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!
方法引用:使用操作符 :: 将方法名和对象或类的名字分隔开来,三种主要使用情况为:

  • 对象::实例方法
  • 类::静态方法
  • 类::实例方法

对象::实例方法

import java.util.function.Consumer;

public class MyTest {
    public static void main(String[] args) {
        Consumer<String> consumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        };
        consumer.accept("aaaaaaaaaaaaaa");
        //aaaaaaaaaaaaaa

        //简写1:
        Consumer<String> consumer1 = (String s) -> {
            System.out.println(s);
        };
        consumer1.accept("abc");
        //abc

        //简写2:
        Consumer<String> consumer2 = (s) -> System.out.println(s);
        consumer2.accept("bcd");
        //bcd

        //简写3:
        Consumer<String> consumer3 = System.out::println;
        consumer3.accept("abc");
        //abc
    }
}

为什么可以写成上述方式?
因为:System.out.println(s);与void accept(String s)一样,都是使用s作为参数,返回值是void,因此就可以简写为简写3;

类::静态方法

import java.util.function.BinaryOperator;

public class MyTest1 {
    public static void main(String[] args) {
        BinaryOperator<Double> operator = new BinaryOperator<Double>(){
            @Override
            public Double apply(Double o, Double o2) {
                return Math.max(o,o2);
            }
        };

        System.out.println(operator.apply(2.13, 3.12));//3.12

        BinaryOperator<Double> operator2 = (o, o2) -> Math.max(o,o2);
        System.out.println(operator2.apply(2.13, 3.12));//3.12

        BinaryOperator<Double> operator3 = Math::max;

        Double max = operator3.apply(5.0, 20.0);
        System.out.println(max);//20.0

    }
}

因为Math.max()所需要的参数以及返回值与重写的accpet()一样,因此可以简写为类::静态方法

import java.util.Comparator;
public class MyTest2 {
    public static void main(String[] args) {
        Comparator<Integer> comparator = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1,o2);
            }
        };

        System.out.println(comparator.compare(20, 12));//1

        Comparator<Integer> comparator1 = Integer::compareTo;
        System.out.println(comparator1.compare(20, 12));//1
    }
}

类::实例方法

import java.util.Comparator;
public class MyTest2 {
    public static void main(String[] args) {
        Comparator<String> comparator = new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.compareTo(o2);
            }
        };

        System.out.println(comparator.compare("20", "12"));//1

        Comparator<String> comparator1 = String::compareTo;
        System.out.println(comparator1.compare("20", "12"));//1
    }
}

为什么可以这样写?、
传递过来的两个参数,一个作为调用者,一个作为参数,这时候,使用类::实例方法简写;

构造引用

1、与⽅法引⽤类似,不过⽅法名为 new, Person::new这就是Person构造器的⼀个引⽤,具体哪⼀个构造器呢,取决于上下⽂
2、数组的构造器引⽤Integer[]::new

格式:ClassName::new
与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!

import java.util.function.Supplier;

public class Mytest3 {
    public static void main(String[] args) {
        Supplier<Student> supplier = new Supplier<Student>() {
            @Override
            public Student get() {
                return new Student();
            }
        };

        Student student = supplier.get();

        Supplier<Student> supplier1 = () -> new Student();
        Student student1 = supplier1.get();
    }
}

public class Mytest3 {
    public static void main(String[] args) {
        Supplier<Student> supplier = new Supplier<Student>() {
            @Override
            public Student get() {
                return new Student("张三",23);
            }
        };

        Student student = supplier.get();
        System.out.println(student);

        Supplier<Student> supplier1 = () -> new Student("李四",25);
        Student student1 = supplier1.get();
        System.out.println(student1);
    }
}

public class MyTest4 {
    public static void main(String[] args) {
        Student student = new Student("张三", 23);
        BiFunction<String, Integer, Student> function = new BiFunction<String, Integer, Student>() {
            @Override
            public Student apply(String s, Integer integer) {
                return student;
            }
        };

        BiFunction<String, Integer, Student> function1 = (s, integer) -> student;

        BiFunction<String, Integer, Student> function2 = Student::new;
    }
}

之所以可以这样简写,是因为构造方法的形参与返回值与重写的方法一样;

Lambda变量作用域

1、lambda表达式可以“捕获”外围作⽤域中变量的值,只要确保所捕获的值是明确定义的
(1)lambda表达式中,只能引⽤值不会改变的外部变量
(2)lambda表达式中捕获的变量必须是 final 变量
2、lambda表达式的与 嵌套块 有相同的作⽤域,在lambda表达式中声明⼀个与局部变量同名的参数或局部变量是不合法的
3、lambda表达式中的 this 含义与外⾯⼀致

lambda 表达式只能引用标记了final的外层局部变量,这就是说不能在 lambda 内部修改定义在域外的局部变量,否则会编译错误。
在 Java8Tester.java 文件输入以下代码:

public class Java8Tester {
 
   final static String salutation = "Hello! ";
   
   public static void main(String args[]){
      GreetingService greetService1 = message -> System.out.println(salutation + message);
      greetService1.sayMessage("Runoob");
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
}

执行以上脚本,输出结果为:

$ javac Java8Tester.java
$ java Java8Tester
Hello! Runoob




我们也可以直接在 lambda 表达式中访问外层的局部变量:

public class Java8Tester {
    public static void main(String args[]) {
        final int num = 1;
        Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
        s.convert(2);  // 输出结果为 3
    }
 
    public interface Converter<T1, T2> {
        void convert(int i);
    }
}

lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)

int num = 1;  
Converter<Integer, String> s = (param) -> System.out.println(String.valueOf(param + num));
s.convert(2);
num = 5;  
//报错信息:Local variable num defined in an enclosing scope must be final or effectively 
 final

在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

String first = "";  
Comparator<String> comparator = (first, second) -> Integer.compare(first.length(), second.length());  //编译会出错 

import java.io.File;
import java.io.FileFilter;

/**
 * lambda表达式  JDK8之后推出的新特性
 *
 * lambda可以让程序员面向函数式编程
 * 让我们用更精简的语法创建匿名内部类
 * 语法:
 * (参数列表)->{
 *     方法体
 * }
 * 当我们使用匿名内部类创建一个对象时,如果实现的接口中【只有一个抽象方法】时,该操作就可以使用lambda
 * 表达式做替换。是的代码更简洁,优雅。
 *
 */
public class LambdaDemo {
    public static void main(String[] args) {
        //匿名内部类写法创建一个文件过滤器
        FileFilter filter1 = new FileFilter() {
            public boolean accept(File f) {
                return f.length()>500;
            }
        };

        //lambda表达式写法:去掉接口和方法名部分,在参数列表和方法体大括号之间加上"->"就是lambda
        FileFilter filter2 = (File f)->{
            return f.length()>500;
        };

        //lambda表达式中,可以忽略方法参数类型(仅写参数名即可)
        FileFilter filter3 = (f)->{
            return f.length()>500;
        };
        //如果该方法的参数只有【一个】的时候,参数列表的"()"可以忽略不写
        FileFilter filter4 = f->{
            return f.length()>500;
        };

        //如果方法体中只有一句代码,那么方法体的"{}"可以忽略不写
        //注:如果有return关键字,那么在忽略"{}"的同时"return"关键字也必须一同忽略。
        FileFilter filter5 = f->f.length()>500;

        //原来匿名内部类在File当中的应用
        File dir = new File("./src/file");
        if(dir.isDirectory()){
            //匿名内部类形式
//            File[] subs = dir.listFiles(new FileFilter() {
//                public boolean accept(File file) {
//                    return file.length()>1000;
//                }
//            });
            File[] subs = dir.listFiles(f->f.length()>1000);
            for(int i=0;i<subs.length;i++){
                System.out.println(subs[i].getName()+":"+subs[i].length());
            }
        }
    }
}

参考地址:
https://www.runoob.com/java/java8-lambda-expressions.html
https://blog.csdn.net/weixin_45082647/article/details/106991685

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值