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();

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

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

结果:在这里插入图片描述
一对比我们就可以看出,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;}

以下是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. 一参,无返回值,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. 两个参数,一条语句,有返回值
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. 两个参数,多条语句,有返回值
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));
}

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();
	}
}

4.3 内置函数式接口

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

1 Consumer:
	
    //使用
    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元钱
2 Supplier:

    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,
3 Function<T,R>:
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
4 Predicate<T>:
//判断数字大小
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

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

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("对象::实例方法名");
    }
}
//结果:对象::实例方法名

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);
    }
}
//结果:类::静态方法名

5.3. 类::实例方法名

public class Test {
    public static void main(String[] args) {
    	BiPredicate<String,String> biPredicate = String::equals;
    	biPredicate.test("t","t");
    }
}

注意:此方式有要求。

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

以上面的例子为例:

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

例二:

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类型的实例。

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;
    }
}

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
  }

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);
}

在这里插入图片描述
特殊情况下,局部变量也可以不用声明为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用过的局部变量");
    }
}

上面代码就可以成功了。

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);
        };
    }
}
//结果:
//看我改实例变量:实例变量
//实例变量,我改了

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

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

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Java 8引入了lambda表达式作为一种新的编程语言特性。lambda表达式是一种匿名函数,它可以作为参数传递给方法或存储在变量中。它可以简化代码,使代码更加易读和易维护。lambda表达式的语法非常简洁,可以用来替代匿名内部类。它可以在集合框架中使用,使代码更加简洁和易读。lambda表达式Java 8中最重要的新特性之一,它使Java编程更加现代化和高效。 ### 回答2: Lambda 表达式是 Java 8 中最重要的新增特性之一,它可以让我们以更简洁的方式来编写代码,并且能够更优雅的解决许多问题。 Lambda 表达式本身是一个匿名函数,它可以被当做对象进行传递和处理。它强调的是函数式编程思想,将函数作为一等公民,并支持灵活的函数组合。Lambda 表达式通常由左侧的参数列表、箭头符号和右侧的函数体组成。例如: ``` (x, y) -> x + y ``` 上述代码定义了一个 lambda 表达式,这个表达式接受两个参数 x 和 y,然后返回它们的和。 Lambda 表达式的用途非常广泛,它们通常用于简化代码,比如用于普通的遍历集合和数组: ``` List<String> names = Arrays.asList("alice", "bob", "charlie"); names.forEach(name -> System.out.println(name)); ``` Lambda 表达式还可以用于函数式接口,这是一种只包含一个抽象方法的接口。函数式接口可以被当做 lambda 表达式的类型,这意味着我们可以使用 lambda 表达式来创建这种类型的对象。例如: ``` interface Calculator { int calculate(int a, int b); } Calculator add = (a, b) -> a + b; Calculator sub = (a, b) -> a - b; ``` 上述代码定义了一个接口类型 Calculator,它包含一个抽象方法 calculate,并且使用 lambda 表达式来定义 add 和 sub 两个对象,它们分别代表加法和减法。 Lambda 表达式还支持方法引用,这是一种更简洁的语法形式,它允许我们使用方法名来代替 lambda 表达式的函数体。例如: ``` List<String> names = Arrays.asList("alice", "bob", "charlie"); names.forEach(System.out::println); ``` 上述代码使用了方法引用,它代替了之前的 lambda 表达式。 总之,Java 8 的 Lambda 表达式是一项非常有用的功能,它提供了一种更简单的方式来编写代码,让我们的代码更加简约且易于阅读。同时,它也是一种函数式编程思想的体现,让 Java 开发者能够更好的应用这些思想来解决实际问题。 ### 回答3: Java8的lambda表达式是一个Java编程语言的新特性,它可以方便地为函数式接口创建实例。Lambda表达式允许你直接以更加简单和精简的方式来定义内部类,并且能够省略掉那些没有用的代码。 在Java 8中,Lambda表达式通过一个箭头“->”来定义。在箭头左边是参数列表,这些参数可以是任何类型,但是Lambda表达式中只能有一个方法参数。在箭头右边是Lambda表达式的主体,也就是这个Lambda表达式所要执行的代码块。 Lambda表达式还可以访问外部作用域中的变量,这是通过捕获变量的方式来实现的。Lambda表达式在使用外部变量时会将其捕获到Lambda表达式内部,从而形成一个闭包,这使得Lambda表达式能够访问外部的变量。 Lambda表达式的使用能够使得代码更加简洁,能够将代码逻辑更加清晰地表达。在Java8中,Lambda表达式被广泛应用于集合的处理,比如通过对集合进行排序、过滤、映射等操作,能够更加简单地处理数据集合。Lambda表达式还被应用于多线程的编程中,能够使得并发编程更加方便和简单。 总之,Java 8的Lambda表达式是一个很有用的新特性。它能够使得Java代码更加简洁和易于理解,减少了冗余的结构和语法。同时,它也提供了更加灵活的编程方式,使得Java编程能够更加高效和便利。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值