探索Java8——Lambda表达式

Lambda管中窥豹

可以把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
Lambda表达式由参数、箭头、主体构成

  • 参数列表:这里使用了Comparator中compare方法的参数compare(T o1, T o2);
  • 箭头
  • Lambda主体:表达式返回值。

Lambda在哪里使用呢?

函数式接口

记得之前的例子中,为了参数化filter方法而创建的Predicate<T>,它就是一个函数式接口,因为它仅仅定义了一个抽象方法。

public interface Predicate<T> {
    boolean test(T e);
}

所以,只定义了一个抽象方法的接口就是函数式接口
又比如Comparator和Runnable,我们来看看他们的源码:

public interface Comparator<T> {
	int compare(T o1, T o2);
}
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

接口现在还可以拥有默认方法(即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。


@FunctionalInterface个标注用于表示该接口会设计成一个函数式接口。如果你用@FunctionalInterface定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。


Lambda实践:环绕执行模式

资源处理(例如处理文件或数据库)时一个常见的模式就是打开一个资源,做一些处理,然后关闭资源。这个设置和清理阶段总是很类似,并且会围绕着执行处理的那些重要代码。这就是所谓的环绕执行(execute around)模式。

public static String processFile() throws IOException { 
 	try (BufferedReader br = 
		 new BufferedReader(new FileReader("data.txt"))) { 
 		return br.readLine(); 
 	} 
} 

现在这段代码是有局限的。你只能读文件的第一行。如果你想要返回头两行,甚至是返回使用最频繁的词,该怎么办呢?

你需要把processFile的行为参数化。你需要一种方法把行为传递给processFile,以便它可以利用BufferedReader执行不同的行为。

传递行为正是Lambda的拿手好戏。那要是想一次读两行,这个新的processFile方法看起来又该是什么样的呢?基本上,你需要一个接收BufferedReader并返回String的Lambda。比如这样:

 String result = processFile((BufferedReader br) ->
                br.readLine() + br.readLine());

我们前面解释过了,Lambda仅可用于上下文是函数式接口的情况。你需要创建一个能匹配BufferedReader -> String,还可以抛出IOException异常的接口。让我们把这一接口叫作BufferedReaderProcessor吧。

@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}

现在就可以把这个接口作为新的processFile方法的参数了:

public static String processFile(BufferedReaderProcessor p) throws 
 IOException {} 

任何BufferedReader -> String形式的Lambda都可以作为参数来传递,因为它们符合BufferedReaderProcessor接口中定义的process方法的签名。现在你只需要一种方法在processFile主体内执行Lambda所代表的代码。请记住,Lambda表达式允许你直接内联,为函数式接口的抽象方法提供实现,并且将整个表达式作为函数式接口的一个实例。因此,你可以在processFile主体内,对得到BufferedReaderProcessor对象调用process方法执行处理:

public class BufferLambda {
    public static String processFile(BufferedReaderProcessor b) throws IOException {
        try (BufferedReader br =
                     new BufferedReader(new FileReader("data.txt"))) {
            return b.process(br);
        }
    }
}

现在你就可以通过传递不同的Lambda重用processFile方法,并以不同的方式处理文件了。

处理一行:
String oneLine = 
 processFile((BufferedReader br) -> br.readLine()); 
处理两行:
String twoLines = 
 processFile((BufferedReader br) -> br.readLine() + br.readLine()); 




  • 实现Foreach打印
@FunctionalInterface
public interface PredicateForeach<T> {
    void foreach(T t);
}
public class PrintLambda {

    public static<T> void toPrint(List<T> list, PredicateForeach<T> predicateForeach){
        for (T l:list) {
            predicateForeach.foreach(l);
        }
    }

    public static void main(String[] args) {
        toPrint(Arrays.asList(1,2,3),(Integer i)-> {
            System.out.println("接下来的数字是:");
            System.out.println(i);
        });
    }
}

类型检查、类型推断以及限制

Lambda类型是使用Lamda的上下文推断出来的。通过目标类型,同一个Lambda表达式可以与不同的函数式接口联系起来。

Java 7中已经引入了菱形运算符(<>),利用泛型推断从上下文推断类型的思想。
List<String> listOfStrings = new ArrayList<>();
List<Integer> listOfIntegers = new ArrayList<>();


Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到。

使用局部变量的限制
Lambda表达式也允许使用自由变量。

int portNumber = 1337; 
Runnable r = () -> System.out.println(portNumber); 

Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量。但局部变量必须显式声明为final,或事实上是final。换句话说,Lambda表达式只能捕获指派给它们的局部变量一次。

下面是错误的!!!!
int portNumber = 1337; 
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337; 

为什么局部变量有这些限制
实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。
如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。

第二,这一限制不鼓励你使用改变外部变量的典型命令式编程模式(我们会在以后解释,这种模式会阻碍很容易做到的并行处理)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值