RSS订阅欢迎来到Java程序员学习天地!
你的位置:首页 » JAVA » 正文

Java8新特性-Lambda与函数式接口

选择字号: 超大 标准 发布时间:2017年05月19日 | 作者:amour505 | 0个评论 | 108人浏览

引言

       Java8已经发布很多年了,出于稳定性等方面的考虑一直没有使用,随着版本不断更新,Java8也愈加趋于成熟,接受程度也越来越高。为了跟上时代发展得脚步,现公司要求使用Java8。编码过程中偶然发现,虽然jdk版本使用的是jdk8,但是基本上都还是在使用之前的版本在编码,根本没有使用到jdk8带来的一些新特性。对于jdk8,我认为吸引人的地方莫过于为Java带来了闭包的概念和面向函数式编程。

Lambda特性

Lambda语法

Lambda语法包含三个部分。

一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;

一个箭头符号:->;

方法体,可以是表达式和代码块,方法体函数式接口里面方法的实现,如果是代码块,则必须用{}来包裹起来,且需要一个return 返回值,但有个例外,若函数式接口里面方法返回值是void,则无需{}, (parameters) -> ex pression 或者 (parameters) -> { statements; } 。

下面是Java lambda表达式的简单例子:

代码如下:
// 1. 不需要参数,返回值为 5
() -> 5

// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x

// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y

// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y

// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

Lambda表达式是Java SE 8中一个重要的新特性。lambda表达式允许你通过表达式来代替功能接口。 lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。

Lambda表达式还增强了集合库。 Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及 java.util.stream 包。 流(stream)就如同迭代器(iterator),但附加了许多额外的功能。 总的来说,lambda表达式和 stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。

基本的Lambda例子

现在,我们已经知道什么是lambda表达式,让我们先从一些基本的例子开始。 在本节中,我们将看到lambda表达式如何影响我们编码的方式。 假设有一个玩家List ,程序员可以使用 for 语句 ("for 循环")来遍历,在Java SE 8中可以转换为另一种形式:

代码如下:


String[] atp = {"Rafael Nadal", "Novak Djokovic",
       "Stanislas Wawrinka",
       "David Ferrer","Roger Federer",
       "Andy Murray","Tomas Berdych",
       "Juan Martin Del Potro"};
List<String> pla yers =  Arrays.asList(atp);

// 以前的循环方式
for (String pla yer : pla yers) {
     System.out.print(pla yer + "; ");
}

// 使用 lambda 表达式以及函数操作(functional operation)
pla yers.forEach((pla yer) -> System.out.print(pla yer + "; "));

// 在 Java 8 中使用双冒号操作符(double colon operator)
pla yers.forEach(System.out::println);

可以看出,使用lambda表达式设计的代码会更加简洁,而且还可读。

方法引用

其实是lambda表达式的一个简化写法,所引用的方法其实是lambda表达式的方法体实现,语法也很简单,左边是容器(可以是类名,实例名),中间是"::",右边是相应的方法名。如下所示:

1.    ObjectReference::methodName 

一般方法的引用格式是

  1. 如果是静态方法,则是ClassName::methodName。如 Object ::equals

  2. 如果是实例方法,则是Instance::methodName。如Object obj=new Object();obj::equals;

  3. 构造函数.则是ClassName::new

再来看一个完整的例子,方便理解

1.    import java.awt.FlowLayout; 

2.    import java.awt.event.ActionEvent; 

3.    import javax.swing.JButton; 

4.    import javax.swing.Jfr ame; 

5.     

6.    /** 

7.     * 

8.     * @author benhail 

9.     */ 

10. public class TestMethodReference { 

11.  

12.     public static void main(String[] args) { 

13.  

14.         Jfr ame fr ame = new Jfr ame(); 

15.         fr ame.setLayout(new FlowLayout()); 

16.         fr ame.setVisible(true); 

17.          

18.         JButton button1 = new JButton("点我!"); 

19.         JButton button2 = new JButton("也点我!"); 

20.          

21.         fr ame.getContentPane().add(button1); 

22.         fr ame.getContentPane().add(button2); 

23.         //这里addActionListener方法的参数是ActionListener,是一个函数式接口 

24.         //使用lambda表达式方式 

25.         button1.addActionListener(e -> { System.out.println("这里是Lambda实现方式"); }); 

26.         //使用方法引用方式 

27.         button2.addActionListener(TestMethodReference::doSomething); 

28.          

29.     } 

30.     /** 

31.      * 这里是函数式接口ActionListener的实现方法 

32.      * @param e  

33.      */ 

34.     public static void doSomething(ActionEvent e) { 

35.          

36.         System.out.println("这里是方法引用实现方式"); 

37.          

38.     } 

39. 

可以看出,doSomething方法就是lambda表达式的实现,这样的好处就是,如果你觉得lambda的方法体会很长,影响代码可读性,方法引用就是个解决办法

函数式接口

       Lambda表达式是如何在java的类型系统中表示的呢?每一个lambda表达式都对应一个类型,通常是接口类型。而“函数式接口”是指仅仅只包含一个抽象方法的接口,每一个该类型的lambda表达式都会被匹配到这个抽象方法。因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。

我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的。

代码如下:


@FunctionalInterface
interface Converter<F, T> {
    T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted);    // 123


需要注意如果@FunctionalInterface如果没有指定,上面的代码也是对的。

将lambda表达式映射到一个单方法的接口上,这种做法在Java 8之前就有别的语言实现,比如Rhino ja vascript解释器,如果一个函数参数接收一个单方法的接口而你传递的是一个function,Rhino 解释器会自动做一个单接口的实例到function的适配器,典型的应用场景有 org.w3c.dom.events.EventTarget 的addEventListener 第二个参数 EventListener。

 

Lambda 作用

访问局部变量

我们可以直接在lambda表达式中访问外层的局部变量:

代码如下:


final int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3


但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:

代码如下:


int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);

stringConverter.convert(2);     // 3


不过这里的num必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:

代码如下:


int num = 1;
Converter<Integer, String> stringConverter =
        (from) -> String.valueOf(from + num);
num = 3;


在lambda表达式中试图修改num同样是不允许的。

访问对象字段与静态变量

和本地变量不同的是,lambda内部对于实例的字段以及静态变量是即可读又可写。该行为和匿名对象是一致的:

代码如下:

class Lambda4 {
    static int outerStaticNum;
    int outerNum;

    void testScopes() {
        Converter<Integer, String> stringConverter1 = (from) -> {
            outerNum = 23;
            return String.valueOf(from);
        };

        Converter<Integer, String> stringConverter2 = (from) -> {
            outerStaticNum = 72;
            return String.valueOf(from);
        };
    }
}

总结

关于Java 8的lambda和函数式接口新特性就写到这了,lambda的引入使代码会更加简洁,但只是代码简洁了这个好处的话,并不能打动很多观众,java 8也不会这么令人期待,其实java 8引入lambda迫切需求是因为lambda 表达式能简化集合上数据的多线程或者多核的处理,提供更快的集合处理速度。


标签:Java基础

额 本文暂时没人评论 来添加一个吧

发表评论

必填

选填

选填

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

控制面板
您好,欢迎到访网站!
随机文章
热门文章
热评文章
最近发表