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表达式——类名::实例方法

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值