【Java】lambda表达式

一、函数式接口

上面是函数式接口?
首先说明他是lambda表达式使用的前提

Lambda的语法非常简洁,完全没有面向对象复杂的束缚。但是使用时有几个问题需要特别注意:

  1. 使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法
    无论是JDK内置的RunnableComparator接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。
  2. 使用Lambda必须具有上下文推断
    也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。

备注:有且仅有一个抽象方法的接口,称为“函数式接口”。

@FunctionalInterface

使用这个注解可以验证该接口是一个函数式接口,否则会报错
除了默认方法,Java8的接口也可以有静态方法的实现:

JDK预定义了很多函数接口以避免用户重复定义。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}


@FunctionalInterface //用来判断某项条件是否满足。经常用来进行筛滤操作
public interface Predicate<T> {
    boolean test(T t);
}

函数式编程思想概述

在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做

面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.

函数式编程思想:只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程

二、Lambda

Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。

对接口的要求

虽然使用 Lambda 表达式可以对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法

jdk 8 中有另一个新特性:default, 被 default 修饰的方法会有默认实现,不是必须被实现的方法,所以不影响 Lambda 表达式的使用。

定义测试的接口

我们这里给出六个接口,后文的全部操作都利用这六个接口来进行阐述。

  • 用@FunctionalInterface标注是一个函数式接口,只能有一个未实现的方法
/**多参数无返回*/
@FunctionalInterface
public interface NoReturnMultiParam {
    void method(int a, int b);
}

/**无参无返回值*/
@FunctionalInterface
public interface NoReturnNoParam {
    void method();
}

/**一个参数无返回*/
@FunctionalInterface
public interface NoReturnOneParam {
    void method(int a);
}

/**多个参数有返回值*/
@FunctionalInterface
public interface ReturnMultiParam {
    int method(int a, int b);
}

/*** 无参有返回*/
@FunctionalInterface
public interface ReturnNoParam {
    int method();
}

/**一个参数有返回值*/
@FunctionalInterface
public interface ReturnOneParam {
    int method(int a);
}

利用lambda表达式new实现接口的对象

我们可以用() -> {}来表示实现了函数式接口的类 且 同时new了个对应的对象,new出来的对象用接口多态接收。如下

MyInterface obj1 = ()->{};
// 因为有接收类型的限制,所以右面的lambda表达式能推断出来要实现的接口及new的对象
  • () 用来描述参数列表,多个参数用逗号分隔,参数类型可以推断所以也可以不写
  • {} 用来描述方法体,函数体只有一个语句的话可以省略{}符号
  • -> 为 lambda运算符 ,读作(goes to)。
  • 一个 Lambda 表达式可以有零个或多个参数
  • 参数的类型既可以明确声明,也可以根据上下文来推断。例如:(int a)(a)效果相同
  • 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:(a, b)(int a, int b)(String a, int b, float c)
  • 空圆括号代表参数集为空。例如:() -> 42
  • 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a*a
  • Lambda 表达式的主体可包含零条或多条语句
  • 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
  • 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空
import lambda.interfaces.*;

public class Test1 {
    public static void main(String[] args) {

        //无参无返回
        NoReturnNoParam noReturnNoParam = () -> {
            System.out.println("NoReturnNoParam");
        };
        noReturnNoParam.method();

        //一个参数无返回
        NoReturnOneParam noReturnOneParam = (int a) -> {
            System.out.println("NoReturnOneParam param:" + a);
        };
        noReturnOneParam.method(6);

        //多个参数无返回
        NoReturnMultiParam noReturnMultiParam = (int a, int b) -> {
            System.out.println("NoReturnMultiParam param:" + "{" + a +"," + + b +"}");
        };
        noReturnMultiParam.method(6, 8);

        //无参有返回值
        ReturnNoParam returnNoParam = () -> {
            System.out.print("ReturnNoParam");
            return 1;
        };

        int res = returnNoParam.method();
        System.out.println("return:" + res);

        //一个参数有返回值
        ReturnOneParam returnOneParam = (int a) -> {
            System.out.println("ReturnOneParam param:" + a);
            return 1;
        };

        int res2 = returnOneParam.method(6);
        System.out.println("return:" + res2);

        //多个参数有返回值
        ReturnMultiParam returnMultiParam = (int a, int b) -> {
            System.out.println("ReturnMultiParam param:" + "{" + a + "," + b +"}");
            return 1;
        };

        int res3 = returnMultiParam.method(6, 8);
        System.out.println("return:" + res3);
    }
}

Lambda 语法简化

在Lambda标准格式的基础上,使用省略写法的规则为:

  1. 小括号内参数的类型可以省略;
  2. 如果小括号内有且仅有一个参,则小括号可以省略;
  3. 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
    我们可以通过观察以下代码来完成代码的进一步简化,写出更加优雅的代码。
Copyimport lambda.interfaces.*;

public class Test2 {
    public static void main(String[] args) {

        //1.简化参数类型,可以不写参数类型,但是必须所有参数都不写
        NoReturnMultiParam lamdba1 = (a, b) -> {
            System.out.println("简化参数类型");
        };
        lamdba1.method(1, 2);

        //2.简化参数小括号,如果只有一个参数则可以省略参数小括号
        NoReturnOneParam lambda2 = a -> {
            System.out.println("简化参数小括号");
        };
        lambda2.method(1);

        //3.简化方法体大括号,如果方法条只有一条语句,则可以胜率方法体大括号
        NoReturnNoParam lambda3 = () -> System.out.println("简化方法体大括号");
        lambda3.method();

        //4.如果方法体只有一条语句,并且是 return 语句,则可以省略方法体大括号
        ReturnOneParam lambda4 = a -> a+3;
        System.out.println(lambda4.method(5));

        ReturnMultiParam lambda5 = (a, b) -> a+b;
        System.out.println(lambda5.method(1, 1));
    }
}

lambda引用

  • lambda 表达式引用方法

如果其他地方有个方法正好符合我们想要的逻辑,那么我们可以直接把该方法作为我们实现接口的内容

方法归属者::方法名

  • 静态方法的归属者为类名,Exe1::doubleNum;
  • 普通方法归属者为对象,exe::addTwo;
Integer::parseInt //静态方法引用
System.out::print //实例方法引用
Person::new       //构造器引用
Copypublic class Exe1 {
    public static void main(String[] args) {
        ReturnOneParam lambda1 = a -> doubleNum(a);
        System.out.println(lambda1.method(3));

        //lambda2 引用了已经实现的 doubleNum 方法
        ReturnOneParam lambda2 = Exe1::doubleNum;
        System.out.println(lambda2.method(3));

        Exe1 exe = new Exe1();

        //lambda4 引用了已经实现的 addTwo 方法
        ReturnOneParam lambda4 = exe::addTwo;
        System.out.println(lambda4.method(2));
    }

    /**
     * 要求
     * 1.参数数量和类型要与接口中定义的一致
     * 2.返回值类型要与接口中定义的一致
     */
    public static int doubleNum(int a) {
        return a * 2;
    }

    public int addTwo(int a) {
        return a + 2;
    }
}
构造方法的引用

我们还可以使用其他类的构造器作为我们的逻辑

语法:类名::new

有参无参构造都是上面的写法,因为我们只是想要逻辑,都想要逻辑了那么参数也肯定匹配了

interface ItemCreatorBlankConstruct {
    Item getItem();
}
interface ItemCreatorParamContruct {
    Item getItem(int id, String name, double price);
}

public class Exe2 {
    public static void main(String[] args) {
        ItemCreatorBlankConstruct creator = () -> new Item();
        Item item = creator.getItem();

        ItemCreatorBlankConstruct creator2 = Item::new;
        Item item2 = creator2.getItem();

        ItemCreatorParamContruct creator3 = Item::new;
        Item item3 = creator3.getItem(112, "鼠标", 135.99);
    }
}
方法引用(Method reference)

下面是一组例子,教你使用方法引用代替λ表达式:

//c1 与 c2 是一样的(静态方法引用)
Comparator<Integer> c2 = (x, y) -> Integer.compare(x, y);
Comparator<Integer> c1 = Integer::compare;
   
//下面两句是一样的(实例方法引用1)
persons.forEach(e -> System.out.println(e));
persons.forEach(System.out::println);
   
//下面两句是一样的(实例方法引用2)
persons.forEach(person -> person.eat());
persons.forEach(Person::eat);
   
//下面两句是一样的(构造器引用)
strList.stream().map(s -> new Integer(s));
strList.stream().map(Integer::new);

还有一些其它的方法引用:

super::toString //引用某个对象的父类方法
String[]::new //引用一个数组的构造器

三、Lambda表达式常用示例

1 lambda用于多线程

我们可以使用一些方法创建多线程对象,比如实现Runnable接口重写run方法,实现FutureTask重写call方法。

// 最麻烦的创建线程方式
public class Test1 {
    public static void main(String[] args) {
        // 匿名内部类
        Runnable task = new MyTask();
        new Thread(task).start(); // 启动线程
    }
}
// 
class MyTask implements Runnable{
    @Override
    public void run() { // 覆盖重写抽象方法
        System.out.println("多线程任务执行!");
    }
}

上面我们为每个任务都得写一个任务类,很冗余,此外我们可以使用匿名内部类的方式稍微简化一下上面代码。

使用匿名内部类

使用匿名内部类稍微简化一下

public class Test1 {
	public static void main(String[] args) {
    	// 匿名内部类
		Runnable task = new Runnable() {
			@Override
			public void run() { // 覆盖重写抽象方法
				System.out.println("多线程任务执行!");
			}
		};
		new Thread(task).start(); // 启动线程
	}
}
使用lambda创建线程

借助Java 8的全新语法,上述Runnable接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效:

public class Demo02LambdaRunnable {
	public static void main(String[] args) {
		new Thread(() -> System.out.println("多线程任务执行!")).start(); // 启动线程
	}
}

这段代码和刚才的执行效果是完全一样的,可以在1.8或更高的编译级别下通过。从代码的语义中可以看出:我们启动了一个线程,而线程任务的内容以一种更加简洁的形式被指定。

2 Lambda用于Comparator

需求:
    使用数组存储多个Person对象
    对数组中的Person对象使用Arrays的sort方法通过年龄进行升序排序

下面举例演示java.util.Comparator<T>接口的使用场景代码,其中的抽象方法定义为:

  • public abstract int compare(T o1, T o2);

当需要对一个对象数组进行排序时,Arrays.sort方法需要一个Comparator接口实例来指定排序的规则。假设有一个Person类,含有String nameint age两个成员变量:

public class Person { 
    private String name;
    private int age;
    
    // 省略构造器、toString方法与Getter Setter 
}

传统写法:

如果使用传统的代码对Person[]数组进行排序,写法如下:

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

public class Demo06Comparator {
    public static void main(String[] args) {
      	// 本来年龄乱序的对象数组
        Person[] array = {
        	new Person("古力娜扎", 19),
        	new Person("迪丽热巴", 18),
       		new Person("马尔扎哈", 20) };

      	// 使用匿名内部类 创建一个排序规则
        Comparator<Person> comp = new Comparator<Person>() {
            @Override // 年龄从小到大
            public int compare(Person o1, Person o2) {
                return o1.getAge() - o2.getAge();
            }
        };
        Arrays.sort(array, comp); // 第二个参数为排序规则,即Comparator接口实例
        /* lambda表达式的方式
        Arrays.sort(array, (Person a, Person b) -> {
          	return a.getAge() - b.getAge();
        });
        
        */

        for (Person person : array) {
            System.out.println(person);
        }
    }
}

使用lambda表达式后:

public interface Calculator {
    int calc(int a, int b);
}


public class Demo08InvokeCalc {
    public static void main(String[] args) {

        invokeCalc(120, 130, (int a, int b) -> {
            return a + b;
        });
    }

    private static void invokeCalc(int a, int b, Calculator calculator) {
        int result = calculator.calc(a, b);
        System.out.println("结果是:" + result);
    }
}

3 lambda用于集合

集合类(包括List)现在都有一个forEach方法,对元素进行迭代(遍历),所以我们不需要再写for循环了。forEach方法接受一个函数接口Consumer做参数,所以可以使用λ表达式。

这种内部迭代方法广泛存在于各种语言,如C++的STL算法库、python、ruby、scala等。

for(Object o: list) { // 外部迭代
    System.out.println(o);
}

可以写成:

list.forEach(o -> {System.out.println(o);}); //forEach函数实现内部迭代
list.forEach(

我们可以调用集合的 public void forEach(Consumer action) 方法遍历集合中的元素

集合的类似该方法的参数要求我们传入一个实现类。

Consumer 接口是 jdk 为我们提供的一个函数式接口,消费型有接收值没有返回值。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    //....
}
ArrayList<Integer> list = new ArrayList<>();

Collections.addAll(list, 1,2,3,4,5);

//lambda表达式 方法引用
list.forEach(System.out::println);//public final static PrintStream out 是个成员对象,该成员可以调用println方法

list.forEach(element -> {
    if (element % 2 == 0) {
        System.out.println(element);
    }
});
items.removeIf

我们通过public boolean removeIf(Predicate filter)方法来删除集合中的某个元素,Predicate 也是 jdk 为我们提供的一个函数式接口,可以简化程序的编写。

ArrayList<Item> items = new ArrayList<>();
items.add(new Item(11, "小牙刷", 12.05 ));
items.add(new Item(5, "日本马桶盖", 999.05 ));
items.add(new Item(7, "格力空调", 888.88 ));
items.add(new Item(17, "肥皂", 2.00 ));
items.add(new Item(9, "冰箱", 4200.00 ));

items.removeIf(ele -> ele.getId() == 7);

//通过 foreach 遍历,查看是否已经删除
items.forEach(System.out::println);
list.sort(

在以前我们若要为集合内的元素排序,就必须调用 sort 方法,传入比较器匿名内部类重写 compare 方法,我们现在可以使用 lambda 表达式来简化代码。

ArrayList<Item> list = new ArrayList<>();
list.add(new Item(13, "背心", 7.80));
list.add(new Item(11, "半袖", 37.80));
list.add(new Item(14, "风衣", 139.80));
list.add(new Item(12, "秋裤", 55.33));

/*
        list.sort(new Comparator<Item>() {
            @Override
            public int compare(Item o1, Item o2) {
                return o1.getId()  - o2.getId();
            }
        });
        */

list.sort((o1, o2) -> o1.getId() - o2.getId());

System.out.println(list);

四、流式计算

Java8为集合类引入了另一个重要概念:流(stream)。一个流通常以一个集合类实例为其数据源,然后在其上定义各种操作。流的API设计使用了管(pipelines)模式。对流的一次操作会返回另一个流。如同IO的API或者StringBuffer的append方法那样,从而多个不同的操作可以在一个语句里串起来。看下面的例子:

List<Shape> shapes = ...
shapes.stream()
  .filter(s -> s.getColor() == BLUE)//filter方法的参数是Predicate类型
  .forEach(s -> s.setColor(RED));//forEach方法的参数是Consumer类型
  // 上面两者都是函数式接口

首先调用stream方法,以集合类对象shapes里面的元素为数据源,生成一个流。然后在这个流上调用filter方法,挑出蓝色的,返回另一个流。最后调用forEach方法将这些蓝色的物体喷成红色。(forEach方法不再返回流,而是一个终端方法,类似于StringBuffer在调用若干append之后的那个toString)

还有一个方法叫parallelStream(),顾名思义它和stream()一样,只不过指明要并行处理,以期充分利用现代CPU的多核特性。

shapes.parallelStream(); // 或shapes.stream().parallel()

来看更多的例子。下面是典型的大数据处理方法,Filter-Map-Reduce:

//给出一个String类型的数组,找出其中所有不重复的素数
public void distinctPrimary(String... numbers) {
    List<String> l = Arrays.asList(numbers);
    
    List<Integer> r = l.stream()//传入一系列String(假设都是合法的数字),转成一个List,然后调用stream()方法生成流。
            .map(e -> new Integer(e))//调用流的map方法把每个元素由String转成Integer,得到一个新的流。map方法接受一个Function类型的参数,上面介绍了,Function是个函数接口,所以这里用λ表达式。
        
            .filter(e -> Primes.isPrime(e))//调用流的filter方法,过滤那些不是素数的数字,并得到一个新流。filter方法接受一个Predicate类型的参数,上面介绍了,Predicate是个函数接口,所以这里用λ表达式。
        
            .distinct()//调用流的distinct方法,去掉重复,并得到一个新流。这本质上是另一个filter操作。
        
            .collect(Collectors.toList());//用collect方法将最终结果收集到一个List里面去。collect方法接受一个Collector类型的参数,这个参数指明如何收集最终结果。在这个例子中,结果简单地收集到一个List中。
            // 我们也可以用Collectors.toMap(e->e, e->e)把结果收集到一个Map中,它的意思是:把结果收到一个Map,用这些素数自身既作为键又作为值。toMap方法接受两个Function类型的参数,分别用以生成键和值,Function是个函数接口,所以这里都用λ表达式。
    
    System.out.println("distinctPrimary result is: " + r);
}

你可能会觉得在这个例子里,List l被迭代了好多次,map,filter,distinct都分别是一次循环,效率会不好。实际并非如此。这些返回另一个Stream的方法都是“懒(lazy)”的,而最后返回最终结果的collect方法则是“急(eager)”的。在遇到eager方法之前,lazy的方法不会执行。

当遇到eager方法时,前面的lazy方法才会被依次执行。而且是管道贯通式执行。这意味着每一个元素依次通过这些管道。例如有个元素“3”,首先它被map成整数型3;然后通过filter,发现是素数,被保留下来;又通过distinct,如果已经有一个3了,那么就直接丢弃,如果还没有则保留。这样,3个操作其实只经过了一次循环。

除collect外其它的eager操作还有forEach,toArray,reduce等。

下面来看一下也许是最常用的收集器方法,groupingBy:

//给出一个String类型的数组,找出其中各个素数,并统计其出现次数
public void primaryOccurrence(String... numbers) {
    List<String> l = Arrays.asList(numbers);
    Map<Integer, Integer> r = l.stream()
        .map(e -> new Integer(e))
        .filter(e -> Primes.isPrime(e))
        .collect( Collectors.groupingBy(p->p, Collectors.summingInt(p->1)) );
    System.out.println("primaryOccurrence result is: " + r);
}

注意这一行:

Collectors.groupingBy(p->p, Collectors.summingInt(p->1))

它的意思是:把结果收集到一个Map中,用统计到的各个素数自身作为键,其出现次数作为值。

下面是一个reduce的例子:

//给出一个String类型的数组,求其中所有不重复素数的和
public void distinctPrimarySum(String... numbers) {
    List<String> l = Arrays.asList(numbers);
    int sum = l.stream()
        .map(e -> new Integer(e))
        .filter(e -> Primes.isPrime(e))
        .distinct()
        .reduce(0, (x,y) -> x+y); // equivalent to .sum()
    System.out.println("distinctPrimarySum result is: " + sum);
}

reduce方法用来产生单一的一个最终结果。
流有很多预定义的reduce操作,如sum(),max(),min()等。

再举个现实世界里的栗子比如:

// 统计年龄在25-35岁的男女人数、比例
public void boysAndGirls(List<Person> persons) {
    Map<Integer, Integer> result = persons.parallelStream().filter(p -> p.getAge()>=25 && p.getAge()<=35).
        collect(
            Collectors.groupingBy(p->p.getSex(), Collectors.summingInt(p->1))
    );
    System.out.print("boysAndGirls result is " + result);
    System.out.println(", ratio (male : female) is " + (float)result.get(Person.MALE)/result.get(Person.FEMALE));
}

4.4 生成器函数(Generator function)

有时候一个流的数据源不一定是一个已存在的集合对象,也可能是个“生成器函数”。一个生成器函数会产生一系列元素,供给一个流。Stream.generate(Supplier<T> s)就是一个生成器函数。其中参数Supplier是一个函数接口,里面有唯一的抽象方法 <T> get()

下面这个例子生成并打印5个随机数:

Stream.generate(Math::random).limit(5).forEach(System.out::println);

注意这个limit(5),如果没有这个调用,那么这条语句会永远地执行下去。也就是说这个生成器是无穷的。这种调用叫做终结操作,或者短路(short-circuiting)操作。

五、lambda常见错误

λ表达式可以被当做是一个Object(注意措辞)。λ表达式的类型,叫做“目标类型(target type)”。λ表达式的目标类型是“函数接口(functional interface)”,这是Java8新引入的概念。它的定义是:一个接口,如果只有一个显式声明的抽象方法,那么它就是一个函数接口。一般用@FunctionalInterface标注出来(也可以不标)。举例如下:

@FunctionalInterface
public interface Runnable { void run(); }
   
public interface Callable<V> { V call() throws Exception; }
   
public interface ActionListener { void actionPerformed(ActionEvent e); }
   
public interface Comparator<T> { int compare(T o1, T o2); boolean equals(Object obj); }

注意最后这个Comparator接口。它里面声明了两个方法,貌似不符合函数接口的定义,但它的确是函数接口。这是因为equals方法是Object的,所有的接口都会声明Object的public方法——虽然大多是隐式的。所以,Comparator显式的声明了equals不影响它依然是个函数接口。

你可以用一个λ表达式为一个函数接口赋值:

Runnable r1 = () -> {System.out.println("Hello Lambda!");};

然后再赋值给一个Object:

Object obj = r1;

但却不能这样干:

Object obj = () -> {System.out.println("Hello Lambda!");}; // ERROR! Object is not a functional interface!

必须显式的转型成一个函数接口才可以:

Object o = (Runnable) () -> { System.out.println("hi"); }; // correct

一个λ表达式只有在转型成一个函数接口后才能被当做Object使用。所以下面这句也不能编译:

System.out.println( () -> {} ); //错误! 目标类型不明

必须先转型:

System.out.println( (Runnable)() -> {} ); // 正确

Lambda 表达式中的闭包问题

这个问题我们在匿名内部类中也会存在,如果我们把注释放开会报错,告诉我 num 值是 final 不能被改变。这里我们虽然没有标识 num 类型为 final,但是在编译期间虚拟机会帮我们加上 final 修饰关键字。

Copyimport java.util.function.Consumer;
public class Main {
    public static void main(String[] args) {

        int num = 10;

        Consumer<String> consumer = ele -> {
            System.out.println(num);//因为是在类中,而又没有作为入参,所以编译时就有给前面定义处加上final
        };

        //num = num + 2;
        consumer.accept("hello");
    }
}
捕获(Capture)

捕获的概念在于解决在λ表达式中我们可以使用哪些外部变量(即除了它自己的参数和内部定义的本地变量)的问题。

答案是:与内部类非常相似,但有不同点。不同点在于内部类总是持有一个其外部类对象的引用。而λ表达式呢,除非在它内部用到了其外部类(包围类)对象的方法或者成员,否则它就不持有这个对象的引用。

在Java8以前,如果要在内部类访问外部对象的一个本地变量,那么这个变量必须声明为final才行。在Java8中,这种限制被去掉了,代之以一个新的概念,“effectively final”。它的意思是你可以声明为final,也可以不声明final但是按照final来用,也就是一次赋值永不改变。换句话说,保证它加上final前缀后不会出编译错误。

在Java8中,内部类和λ表达式都可以访问effectively final的本地变量。λ表达式的例子如下:

...   
int tmp1 = 1; //包围类的成员变量
static int tmp2 = 2; //包围类的静态成员变量
public void testCapture() {
    int tmp3 = 3; //没有声明为final,但是effectively final的本地变量
    final int tmp4 = 4; //声明为final的本地变量
    int tmp5 = 5; //普通本地变量
   
    Function<Integer, Integer> f1 = i -> i + tmp1;
    Function<Integer, Integer> f2 = i -> i + tmp2;
    Function<Integer, Integer> f3 = i -> i + tmp3;
    Function<Integer, Integer> f4 = i -> i + tmp4;
    Function<Integer, Integer> f5 = i -> {
        tmp5  += i; // 编译错!对tmp5赋值导致它不是effectively final的
        return tmp5;
    };
    ...
    tmp5 = 9; // 编译错!对tmp5赋值导致它不是effectively final的
}
...

Java要求本地变量final或者effectively final的原因是变量作用域和多线程问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值