`
liugang594
  • 浏览: 977463 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Java函数式编程学习一

阅读更多

一、缺省方法

首先看一段用Java 8写的代码:

 

        //create a list, and add 3 elements
        List<String> l = new ArrayList<String>() {
            {
                add("Hello");
                add("World");
                add("Peace");
            }
        };
        //print element in l one by one
        l.forEach(System.out::println);

 

首先创建一个 List 对象,并添加了三个元素到其中,然后调用 List 的 forEach 方法将其中的元素一个一个打印出来。

但是我们都知道 List 是一个接口,其中没有 forEach 方法,而如果直接在 List 接口中添加新的方法定义,则会破坏向下兼容性,因为以前的程序在新的Java环境里因缺少对该方法的实现而报错。Java 8中解决这一问题的手段就是使用缺省方法。

 

缺省方法是在接口中定义方法实现的一种方式,并且保证所有已经存在的子类的兼容性,所以实现了接口的类默认都拥有在接口中定义的缺省方法,这有点像一个抽象类。当一个子类中没有覆盖缺省方法时,则对子类的该方法的调用将调用接口中的实现。缺省方法的声明方式如下:

 

public interface DefaultInterface {
    default int getValue(){
        return 1;
    }
}

只需要在方法前加关键字 default 即可。

 

缺省方法提供了一种在不破坏现在接口实现的情况下,给接口添加新的功能的途径。查看集合类的源码就会发现Java 8的集合类接口中添加了很多缺省的方法实现,用于提供对集合的共同的操作,如上所示的 forEach 方法。 

 

一个类可以实现多个接口,如果其中有多于一个接口有相同的缺省方法时,则子类需要定义自己的实现,否则会报编译错误:

interface A{
    default String name(){
        return "A";
    }
}
interface B{
    default String name(){
        return "B";
    }
}
class C implements A, B{
    public String name(){
        return A.super.name();
    }
}

 

二、forEach() 方法

forEach() 方法的源码如下:

    default void forEach(Consumer<? super T> action) {
        Objects.requireNonNull(action);
        for (T t : this) {
            action.accept(t);
        }
    }

这里 this 对象就是 List 对象本身,t 就是 List 中的遍历的元素。写在我们熟悉的方式就是:

        for(String s: l){
            action.accept(s);
        }

 

三、java.util.function.Consumer 接口

看上面 forEach() 方法参数,需要的是一个 Consumer 对象,这里我们传入的是一个 System.out::println 。 Consumer 是新增的函数编程接口中的一个,看它的介绍:

Represents an operation that accepts a single input argument and returns no result.

而双冒号 :: 操作符也是新增的一个操作符,表示一个函数引用。结合起来理解应该就是:一个类的任何一个方法,如果它只有一个参数,并且没有返回值,则在需要Consumer参数的地方都可以把此方法作为函数引用传入。

可以作个实验。先定义以下类:

class TestConsumer{
    public static TestConsumer INSTANCE = new TestConsumer();
    public void sayHello(String s){
        System.out.println("Hello "+s);
    }
}

然后替换 System.out::println 为 TestConsumer.INSTANCE::sayHello,如下:

        //create a list, and add 3 elements
        List<String> l = new ArrayList<String>() {
            {
                add("Hello");
                add("World");
                add("Peace");
            }
        };
        //print element in l one by one
        l.forEach(TestConsumer.INSTANCE::sayHello);

看打印结果:

Hello Hello
Hello World
Hello Peace

证明我们的猜想是对的。

再试一下静态方法,修改上面的TestConsumer类,添加一个静态方法:

    public static void greeting(String s){
        System.out.println("Greeting "+s);
    }

还是替换 forEach() 中的参数:

l.forEach(TestConsumer::greeting);

这次和普通的静态方法一样,直接通过类访问函数对象。打印结果:

Greeting Hello
Greeting World
Greeting Peace

由上证明,Consumer接口对于静态和实例方法都是适用的。     

 

另外,我们也可以实现一个方法,其他要求传入一个Consumer对象:

public class TestConsumer {
    
    public static void main(String[] args) {
        TestConsumer test = new TestConsumer();
        test.tryConsumer(System.out::println, "hello");
    }
    
    public void tryConsumer(Consumer c, String t){
        c.accept(t);
    }
    
}

仔细想来,这其实就有点类似于C/C++的传入方法指针之类的东西:Consumer可以根据传入的函数引用不用而执行不同的操作。  

 

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics