java 函数式编程基础(一)

174 阅读4分钟

在正式进入函数式编程之前,有必要先了解一下Java 8为支持函数式编程所做的基础性的改进,这里,将简要介绍一下FunctionalInterface注释、接口默认方法和方法句柄。

1、FunctionalInterface 注释

Java 8提出了函数式接口的概念。所谓函数式接口,简单来说,就是只定义了单一抽象方法的接口。比如下面的定义:

@FunctionalInterface
public interface IntHandler {
    void handler(int i);
}

注释FunctionalInterface用于表明IntHandler接口是一个函数式接口,该接口被定义为只包含一个抽象方法handle(),因此它符合函数式接口的定义。如果一个函数满足函数式接口的定义,那么即使不标注为@FunctionalInterface,编译器依然会把它看做函数式接口。这有点像@Override注释,如果你的函数符合重载的要求,无论你是否标注了@Override,编译器都会识别这个重载函数,但一旦你进行了标注,而实际的代码不符合规范,那么就会得到一个编译错误。如图6.1所示,展示了一个不符合规范,却被标注为@FunctionalInterfacede接口。很显然,该IntHandler包含两个抽象方法,因此不符合函数式接口的要求,又因为IntHandler接口被标注为函数式接口,产生矛盾,故编译出错。 image.png不符合规范的函数式接口 ​

这里需要强调的是,函数式接口只能有一个抽象方法,而不是只能有一个方法。这分两点来说明:首先,在Java 8中,接口运行存在实例方法,其次任何被java.lang.Object实现的方法,都不能视为抽象方法,因此,下面的NonFunc接口不是函数式接口,因为equals()方法在java.lang.Object中已经实现。

public interface NoFunc {
    @Override
    boolean equals(Object object);
}

同理,下面实现的IntHandler接口符合函数式接口要求,虽然看起来它不像,但实际上它是一个完全符合规范的函数式接口。

@FunctionalInterface
public interface IntHandler {
    void handler(int i);
    @Override
    boolean equals(Object object);
}

2、接口默认方法

在Java 8之前的版本,接口只能包含抽象方法。但从Java 8之后,接口也可以包含若干个实例方法。这一改进使得Java 8拥有了类似于多继承的能力。一个对象实例,将拥有来自于多个不同接口的实例方法。 比如:对于接口IHorse

public interface IHorse{
	void eat();
    default void run(){
    	System.out.println();
    }
}

在Java 8中,使用default关键字,可以在接口内定义实例方法。注意,这个方法并非抽象方法,而是拥有特定逻辑的具体实例方法。 所有的动物都能自由呼吸,所以,这里可以再定义一个IAnimal接口,它也包含一个默认方法breath()。

public interface IAnimal {     
	default void breath(){         
		System.out.println("breath");     
	} 
}

骡是马和驴的杂交物种,因此骡(Mule)可以实现为IHorse,同时骡也是动物,因此有:

public class Mule implements IHorse,IAnimal{ 
    @Override     
    public void eat() {        
     	System.out.println("Mule eat");     
    }    
 	public static void main(String[] args) {        
        Mule m=new Mule();         
        m.run();         
        m.breath();     
	} 
} 

注意上述代码中Mule实例同时拥有来自不同接口的实现方法。这在Java 8之前是做不到的。从某种程度上说,这种模式可以弥补Java单一继承的一些不便。但同时也要知道,它也将遇到和多继承相同的问题。 ​

如果IDonkey也存在一个默认的run()方法,那么同时实现它们的Mule,就会不知所措,因为它不知道应该以哪个方法为准。

public interface IDonkey{     
    void eat();     
    default void run(){         
        System.out.println("Donkey run");     
    } 
}
graph TD
Start --> Stop

修改骡Mule的实现如下,注意它同时实现了IHorse和IDonkey:

public class Mule implements IHorse,IAnimal,IDonkey{ 
    @Override     
    public void eat() {        
     	System.out.println("Mule eat");     
    }    
 	public static void main(String[] args) {        
        Mule m=new Mule();         
        m.run();         
        m.breath();     
	} 
} 
// 此时,由于IHorse和IDonkey拥有相同的默认实例方法,故编译器会抛出一个错误:
Duplicate default methods named run with the parameters () and () are inherited from the types IDonkey and IHorse

为了让Mule同时实现IHorse和IDonkey,在这里,我们不得不重新实现一下run()方法,让编译器可以进行方法绑定。修改Mule的实现如下:

public class Mule implements IHorse,IDonkey,IAnimal{         
    @Override     
    public void run(){               
    	IHorse.super.run();     
    }     
    @Override     
    public void eat() {         
    	System.out.println("Mule eat");     
    }     
    public static void main(String[] args) {         
    	Mule m=new Mule();        
     	m.run();         
    	m.breath();     
    } 
}

```mermaid
interface IHorse {
  +eat()
  #run()
}
interface IDonkey {
  +eat()
  #run()
}
interface IAnimal {
  +breath()
}
class Mule{
	+eat()
	#run()
	+breath()
}
IHorse <|-- Mule
IAnimal <|-- Mule
IDonkey <|-- Mule
在这里,将Mule的run()方法委托给IHorse实现,当然,大家也可以有自己的实现。
​

接口默认实现对于整个函数式编程的流式表达非常重要。比如,大家熟悉的java.util.Comparator接口,它在JDK 1.2时就已经被引入,用于在排序时给出两个对象实例的具体比较逻辑。在Java 8中,Comparator接口新增了若干个默认方法,用于多个比较器的整合。其中一个常用的默认方法如下:
```java
default Comparator<T> thenComparing(Comparator<? super T> other) {    
	Objects.requireNonNull(other);     
	return (Comparator<T> & Serializable) (c1, c2) -> {    
	int res = compare(c1, c2);         
	return (res != 0) ? res : other.compare(c1, c2);     
	}; 
}


有了这个默认方法,在进行排序时,我们就可以非常方便地进行元素的多条件排序,比如,如下代码构造一个比较器,它先按照字符串长度排序,继而按照大小写不敏感的字母顺序排序。

Comparator<String> cmp = Comparator.comparingInt(String::length).thenComparing(String.CASE_INSENSITIVE_ORDER);