• java8新特性之lambda表达式--超级详细版本


    1.什么是lambda表达式?为什么用它?

      lambda表达式又称闭包。它是推动java8发布的最重要新特性。lambda允许把函数作为方法的参数(函数作为参数传递进方法中)。使用lambda可以使代码更加简洁。
    例如:我们要创建一个线程输出一句话
    不用lambda:
    我们需要写一个类实现Runnable接口,然后实现他的run方法

    //实现Runnable接口的类
    public class RunTemp implements Runnable{
        @Override
        public void run() {
            System.out.println("我是实现的Runnable方法");
        }
    }
    
    //使用
    RunTemp runTemp = new RunTemp();
    new Thread(runTemp).start();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    结果:
    在这里插入图片描述
    使用lambda:

    new Thread(()-> System.out.println("lambdaYYDS")).start();
    
    • 1

    结果:在这里插入图片描述
    一对比我们就可以看出,lambda为我们节省了好多代码。但实现的功能却是相同的。

    2. 为什么Java需要lambda表达式?

      lambda表达式为java提供了缺失的函数式编程特点。使我们能将函数当作一等公民来看待。在一个支持函数的语言中,lambda表达式的类型应该是函数,但是在Java中它是一种对象,它必须依附于一种特殊的对象类型:函数式接口(functional interface)

    3. lambda表达式的语法

      lambda表达式在Java语言中引入了一个新的操作符“->”,该操作符被称为lambda操作符或者箭头操作符。它将lambda表达式分为了两部分:
    左侧:lambda所需参数
    右侧:lambda要执行的操作。

    (type param1,type param2,...) -> {body}
    (param1,param2,...) -> {body}
    //例如:
    (String param1,Interge param2,...) -> {return param1+param2;}
    
    • 1
    • 2
    • 3
    • 4

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

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

    举例:

    1. 无参,无返回值,lambda体只有一句话:
    public class Test1 {
        public static void main(String[] args) {
            Runnable runnable = ()->{ System.out.println("无参,无返回值,一句话"); };
            runnable.run();
        }
    }
    //一语句可以省略大括号:
    public static void main(String[] args) {
            Runnable runnable = ()-> System.out.println("无参,无返回值,一句话。可以省略大括号");
            runnable.run();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 一参,无返回值,lambda体只有一句话:
    public static void main(String[] args) {
        Consumer<String> consumer = (t)->{System.out.println("一参:"+t+",无返回值,一句话"); };
        consumer.accept("我是参数");
    }
    //一参可以省略小括号:
    public static void main(String[] args) {
        Consumer<String> consumer = t->{ System.out.println("一参:"+t+",无返回值,一句话。一参可以省略小括号"); };
        consumer.accept("我是参数");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    1. 两个参数,一条语句,有返回值
    public static void main(String[] args) {
        Comparator<Integer> comparable = (a, b)->{return Integer.compare(a,b);};
        System.out.println(comparable.compare(3,4));
    }
    /**
    * 一条语句有返回值时,return和大括号都可不写
    **/
    public static void main(String[] args) {
        Comparator<Integer> comparable = (a, b)-> Integer.compare(a,b);
        System.out.println(comparable.compare(3,4));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 两个参数,多条语句,有返回值
    public static void main(String[] args) {
        Comparator<Integer> comparable = (a, b)->{
            System.out.println("我是另一条语句");
            return Integer.compare(a,b);
        };
        System.out.println(comparable.compare(3,4));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.函数式接口

    4.1 什么是函数式接口

      只包含一个抽象方法的接口就是函数式接口。我们可以通过lambda表达式来创建该接口的实现对象。我们可以在任意函数式接口上使用@Functionallnterface注解,这样做可以用于检测它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。

    4.2 自定义函数式接口

    /**
     * 自定义函数式接口
     * @author wangdawei
     */
    @FunctionalInterface
    public interface WorkerInterface {
        /**
         * 一个抽象方法
         */
        public void doSomeWork();
    }
    
    
    //使用
    public class Test {
        public static void main(String[] args) {
            Test test = new Test();
            test.show(()-> System.out.println("我是最简单的lambda表达式"),"你号");
    	}
    	//自定义一个方法,将函数式接口作为参数
    	private void show(WorkerInterface worker,String str) {
    	        System.out.println(str);
    	        worker.doSomeWork();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    4.3 内置函数式接口

    四大内置函数式接口:
    在这里插入图片描述
    使用示例:

    1 Consumer:
    
    • 1
    	
        //使用
        public class Test {
    	    public static void main(String[] args) {
    	        Test test = new Test();
    	        test.markMoney(10,(x)-> System.out.println("今天花费了"+x+"元钱"));
    	    }
    	
    		/**
    	     * Consumer<T>
    	     */
    	    private void markMoney(Integer money, Consumer<Integer> consumer){
    	        consumer.accept(money);
    	    }
        }
    //结果:今天花费了10元钱
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    2 Supplier:
    
    • 1
    
        public class Test {
    	    public static void main(String[] args) {
    	        Test test = new Test();
    	        List<Integer> list = test.addNumInList(10,()->(int)(Math.random()*100));
            	list.forEach(t-> System.out.print(t+","));
    	    }
    		 /**
    	     * Supplier<T>
    	     */
    	    private List<Integer> addNumInList(int size, Supplier<Integer> supplier){
    	        List<Integer> list=new ArrayList<>();
    	        for (int i = 0; i < size; i++) {
    	            list.add(supplier.get());
    	        }
    	        return list;
    	    }
        }
        //结果:92,40,77,48,95,86,40,51,52,27,
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    3 Function<T,R>:
    
    • 1
    public class Test {
        public static void main(String[] args) {
            Test test = new Test();
            Function<Integer,String > function = (b)->{
                System.out.println("Function");
                int data = b*b+b;
                return "b*b+b的结果是:"+data;
            };
            function.apply(11);
    	}
    }
    //结果:
    //Function
    //b*b+b的结果是:132
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    4 Predicate<T>:
    
    • 1
    //判断数字大小
    public class Test {
        public static void main(String[] args) {
            int data = 11;
            Predicate<Integer> predicate = (a)->{
                if (a>10){
                    return true;
                }
                return false;
            };
            if (predicate.test(data)){
                System.out.println("data大于10");
            }else{
                System.out.println("data小于等于10");
            }
         }
    }
    //结果:data大于10
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    其它接口:
    在这里插入图片描述

    5. 方法引用

      当要传递给Lambda体的操作,已经有实现的方法了,就可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用的参数列表一致,方法的返回值也必须一致,即方法的签名一致)。可以理解为方法引用是Lambda表达式的另外一种表现形式。
    语法:使用操作符"::"将对象或类与方法名分隔开。
    使用方法:

    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名
    • 类::new
    • type[]::new

    5.1. 对象::实例方法名

    public class Test {
        public static void main(String[] args) {
            PrintStream stream = System.out;
            Consumer<String> consumer = stream::println;
            consumer.accept("对象::实例方法名");
        }
    }
    //结果:对象::实例方法名
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5.2. 类::静态方法名

    public class Test {
        public static void main(String[] args) {
            PrintStream stream = System.out;
            Consumer<String> consumer = Test::show;
            consumer.accept("类::静态方法名");
        }
        /**
         * 方法引用
         */
        static void show(String s){
            System.out.println(s);
        }
    }
    //结果:类::静态方法名
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.3. 类::实例方法名

    public class Test {
        public static void main(String[] args) {
        	BiPredicate<String,String> biPredicate = String::equals;
        	biPredicate.test("t","t");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意:此方式有要求。

       第一点:接口方法的参数比引用方法的参数多一个
       第二点:接口方法的第一个参数恰巧是调用引用方法的对象(其引用方法所在类或其父类的实例)
    
    • 1
    • 2

    以上面的例子为例:

    BiPredicate<String,String> biPredicate = String::equals;
    //首先,BigPredicate接口中的test方法规定要传两个参数:第一个参数规定第一个参数必须是String类型的实例,第二个参数是String类型参数。
    biPredicate.test("t","t");
    //第一个参数”t“是String类型的实例,流程可以理解为:"第一个参数".(test/equals)(第二个参数)
    //这也是为什么第一个参数必须是String类型的实例的原因。如果不是那就无法调用.equals方法。
    
    • 1
    • 2
    • 3
    • 4
    • 5

    例二:

    public class Test {
        public static void main(String[] args) {
                BiPredicate<MyFinalClass,String> biPredicate = MyFinalClass::showString;
                biPredicate.test(new MyFinalClass(),"例二结果输出");
        }
    }
    
    public class MyFinalClass{
        public Boolean showString(Object s){
            System.out.println("给你展示:"+s);
            return true;
        }
    }
    //BiPredicate<MyFinalClass,String> 规定:第一个参数必须是“MyFinalClass”类型的实例。第二个参数是String类型的实例。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.4. 类::new

    public class Test {
        public static void main(String[] args) {
            //无参
            Supplier<Entity> supplier = Entity::new;
            supplier.get();
            //一参
            Function<String,Entity> function = Entity::new;
            System.out.println(function.apply("王大伟").getName());
            //两参
            BiFunction<String,String,Entity> biPredicate = Entity::new;
            Entity entity = biPredicate.apply("1234232","王大伟");
            System.out.println("id:"+entity.getId()+";name:"+entity.getName());
            //三参
            NewEntity newEntity = Entity::new;
            Entity entity1 = newEntity.newEntity("372929","王大伟",20);
            System.out.println("id:"+entity.getId()+";name:"+entity.getName()+";age:"+entity1.getAge());
        }
    }
           
    /**
    *	自定义一个函数式接口
    **/
    @FunctionalInterface
    public interface NewEntity {
        /**
         * 三个参数的初始化
         * @param id
         * @param name
         * @param age
         * @return
         */
        public Entity newEntity(String id,String name,Integer age);
    }
    
    public class Entity {
        private String id;
        private String name;
        private Integer age;
    
        public Entity() {
        }
    
        public Entity(String name) {
            this.name = name;
        }
    
        public Entity(String id, String name, Integer age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
    
        public Entity(String id, String name) {
            this.id = id;
            this.name = name;
        }
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81

    5.5 数组引用

    数组引用格式:type[]:new
    示例:

      @Test
      public void test02(){
        Function<Integer,String[]> function=String[]::new;
        String[] apply = function.apply(10);
        System.out.println(apply.length);//结果:10
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    6 lambda表达式的作用域

      Lambda表达式可以看作式匿名内部类实例化的对象,Lambda表达式对变量的访问限制和匿名内部类一样,因此Lambda表达式可以访问局部变量,局部引用,静态变量,实例变量。

    6.1 访问局部变量

      在Lambda表达式中规定只能引用标记了final的外层局部变量。我们不能在lambda内部修改定义在域外的局部变量,否则会编译错误。
    例如:

    public class Test {
        public static void main(String[] args) {
            //声明局部变量
            int t = 0;
            WorkerInterface workerInterface = (a,b)->{
                System.out.println("a:"+a+"b:"+b);
                //修改局部变量
                t = t+1;
                System.out.println("t:"+t);
            };
            workerInterface.doSomeWork(3,4);
        }
    }
    /**
     * 自定义函数式接口
     * @author wangdawei
     */
    @FunctionalInterface
    public interface WorkerInterface {
        /**
         * 一个抽象方法
         */
        public void doSomeWork(int a,int b);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    在这里插入图片描述
    特殊情况下,局部变量也可以不用声明为final,但是必须保证它不可被后面的代码修改,(即隐形的具有final语义)
    例:以上面代码举例:

    public class Test {
        public static void main(String[] args) {
            //声明局部变量
            //final int t = 0;
            int t = 0;
            WorkerInterface workerInterface = (a,b)->{
                System.out.println("a:"+a+"b:"+b);
                //修改局部变量
                int c = t+1;
                System.out.println("c:"+c);
            };
            workerInterface.doSomeWork(3,4);
            System.out.println("我这里可不敢改被lambda用过的局部变量");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    上面代码就可以成功了。

    6.2 访问局部引用,静态变量,实例变量

      Lambda表达式不限制访问局部引用变量,静态变量,实例变量。
    例如:

    //访问局部引用变量。
    public class Test {
        public static void main(String[] args) {
            int t = 0;
            //创建一个局部引用变量
            List<String> list = new ArrayList<>();
            list.add("增加了一个数据。");
            WorkerInterface workerInterface = (a,b)->{
                //获取了一个局部引用变量,
                System.out.println(list.get(0));
                //我往里面加一个数据
                list.add("我往里面加一个数据");
            };
            workerInterface.doSomeWork(3,4);
            list.add("我再往里面加一个数据,也不会报错");
        }
    }
    
    //访问静态变量
    public class Test {
        static String staticStr = "静态变量";
        public static void main(String[] args) {
            WorkerInterface staticWorker = (a,b)->{
                System.out.println("看好了,我要改静态变量:"+staticStr);
                staticStr = staticStr+";;我改了,看见了吗?";
                System.out.println(staticStr);
            };
            staticWorker.doSomeWork(1,1);
         }
    }
    //结果:看好了,我要改静态变量:静态变量
    //静态变量;;我改了,看见了吗?
    
    //访问实例变量
    public class Test {
        static String staticStr = "静态变量";
        String instanceStr = "实例变量";
        public static void main(String[] args) {
            Test test = new Test();
            WorkerInterface instanceWorker = (a,b)->{
                System.out.println("看我改实例变量:"+test.instanceStr);
                test.instanceStr = test.instanceStr+",我改了";
                System.out.println(test.instanceStr);
            };
        }
    }
    //结果:
    //看我改实例变量:实例变量
    //实例变量,我改了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    6.3 Lambda表达式访问局部变量做限制的原因。

      因为实例变量存在堆中,而局部变量是在栈上分配,存在于虚拟机栈的局部变量表中,Lambda表达式(匿名类)有可能会在另一个线程中执行。如果在线程中要直接访问一个局部变量,可能线程执行时该局部变量已经被销毁了,而final类型的局部变量在Lambda表达式(匿名类)中其实是局部变量的拷贝。
      基于上述,对于引用类型的局部变量,因为Java是值传递,又因为引用类型的指向内容是保存在堆中,是线程共享的,因此Lambda表达式中可以修改引用类型的局部变量的内容,而不能修改该变量的引用。
      对于基本数据类型的变量,在Lambda表达式中只是获取到该变量的副本,且局部变量是线程私有的。因此无法知道其它线程对该变量的修改。如果该变量不做final修饰,会造成数据不同步的问题。
      但是实例变量,静态变量不做限制,因为他两个保存在堆中(Java8之后),而堆是线程共享的。在Lambda表达式内部是可以知道实例变量,静态变量的变化。

    参考:
    Lambda表达式超详细总结
    【Java 8系列】Lambda 表达式,一看就废
    Lambda表达式使用局部变量的限制
    lambda表达式——类名::实例方法

  • 相关阅读:
    带头双向循环链表讲解-思路清晰+画图+代码实现
    leetcode刷题集:单调栈(python代码)
    Linux:查看进程。
    el-form自定义规则后表单验证validate不生效的问题
    XML快速入门
    Python基础(八):循环深入讲解
    Spring Boot 在进行依赖注入时,使用了反射机制,类加载器-启动类拓展类-应用类加载器
    2023年中国半导体检测仪器设备销售收入、产值及市场规模分析[图]
    基于Python Django 的微博舆论、微博情感分析可视化系统(V2.0)
    巨细靡遗流程控制,Go lang1.18入门精炼教程,由白丁入鸿儒,Go lang流程结构详解EP09
  • 原文地址:https://blog.csdn.net/wangdawei_/article/details/125570641