Java 8 函数式接口

本博客关于JDK1.8新特性介绍的目录在这里,欢迎点击

前言

在前面简单介绍了一些Lambda表达式得好处与语法,我们知道使用Lambda表达式(需要了解lambda点我)是需要配合函数式接口的,java8已经为我们定义好了4类内置函数式接口,这4类接口可以解决我们开发过程中绝大部分的问题。

首先看定义

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式。
Lambda 表达式和方法引用(实际上也可认为是Lambda表达式)上。

总而言之,JDK1.8中 interface允许定义默认方法和静态方法,通过默认接口定义、Lambda表达式、方法引用、函数式接口带来了函数式编程,这些功能也必将改变java开发习惯

默认方法的语法: default关键字 methodName(参数列表) { 实现体 }
静态方法语法与类的静态方法类似,不同的是接口静态方法的修饰符只能是public。

1.默认方法

假如有一个Animal接口其中有ear()、fly()和swim()方法,有一个鸟Bird和一个dog同时实现这个接口,代码如下:
Animal 接口

public interface Animal {
	void eat();
	void fly();	
	void swim();
}

Bird 实现

class Bird implements Animal{
	
	@Override
	public void eat() {
		System.out.println("bird can eat");
	}
	@Override
	public void fly() {
		System.out.println("bird can fly");
	}
	@Override
	public void swim() {
		
	}
}

Dog实现

class Dog implements Animal{

	@Override
	public void eat() {
		System.out.println("dog can fly");
	}
	@Override
	public void fly() {
		
	}
	@Override
	public void swim() {
		System.out.println("dog can fly");
	}
}

从上代码可以看到,因为Animal中定义了三个方法,所以所有实现它的类都要覆写这三个方法,在Bird类中,鸟会飞,不会游泳,但是又必须要实现swim()方法,Dog类不会飞,但是又必须要实现fly()方法。代码出现冗余。

假如现在又有了新的需求,需要在Animal接口中再增加一个sleep()方法,那么之前所有实现了Animal接口的方法势必都在再覆写sleep()方法,整个系统中可能会有很多地方需要同步修改,而此时,default方法和静态方法就显得尤为必要了。

改写上面的例子:

Animal 接口

public interface Animal {
	default void eat() {
		System.out.println("Animal can eat");
	};
	default void fly() {
		System.out.println("bird can fly");
	};
	default void swim() {
		System.out.println("dog can swim");
	};
}

Bird实现

class Bird implements Animal{
}

Dog实现

class Dog implements Animal{
}

测试类

public static void main(String[] args) {
		Bird bird = new Bird();
		bird.fly();
		
		Dog dog = new Dog();
		dog.swim();
}

运行结果

bird can fly
dog can swim

从修改后代码可以看出,代码得到了复用,Animal实现类中也没有了冗余。

2. 静态方法

假如有一个Animal工厂接口,该接口中有一个静态方法create()专门生产不同的Animal,在JDK1.8后由于引入了Lambda表达式,使子类不用覆写该接口的create()方法也可以生产任意的Animal,代码如下:


public interface AnimalFactory {
	static Animal create(Supplier supplier) {
	         return supplier.get();
	 }
}
//注:Supplier supplier = Bird::new; 不会创建对象,
//只是通过supplier变量传递构造方法,supplier .get()才是真正去创建对象
//后续会提到这个类和 ‘::new‘这个这个语法

测试类

 public static void main(String[] args) {
		Animal dog = AnimalFactory.create(Dog::new);
		dog.swim();
		
		Animal bird = AnimalFactory.create(Bird::new);
		bird.fly();
}

运行结果

fishes can swim......
birds can fly...

如果一个接口实现类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略
假如一个类实现了两个接口,两个接口中都有同样的默认方法,则编译无法通过

3.方法引用

方法引用需要使用 :: 关键字,四种形式方法引用如下:
引用静态方法:类名称 :: static 方法名称;
引用某个对象的方法:实例化对象 :: 普通方法;
引用特定类型的方法:特定类 :: 普通方法
引用构造方法:类名称 :: new

前面提到的‘Animal bird = AnimalFactory.create(Bird::new);’ 就是属于引用构造方法,那我们再实现个几个例子,来测试一下。

引用静态方法

@FunctionalInterface
interface Inter{
    public R zhuanhuan(P p);
}

public class testmain1 {
     public static void main(String args[]){
         Inter msg1 = String::valueOf;//引用Stirng的静态方法valueOf
         String str = msg1.zhuanhuan(3000);
         System.out.println("msg1:" + str);  //msg1:3000
        
         //原始方法
        Inter msg2 = new Inter() {
	        public String zhuanhuan(Integer p) {    
	        	return  String.valueOf(p);
	        }
	    };
	    System.out.println("msg2:" + str); //msg2:3000
	 }
}
//通过 Inter msg = String::valueOf;
//让Inter的R方法拥有了valueOf的功能,将String.valueOf()方法变为了Inter接口里的R()方法

@FunctionalInterface
作用:表明该接口是函数式接口,若该接口不符合函数式接口规范,则会提示> 错误信息。若不加该注解不影响程序。
~~
函数式接口,只能有一个 abstract 方法,可以包含多个默认方法、静态方法。
函数式接口可以包含Object里public的方法,这些方法不会计入 abstract 方法> 中,虽然它们是 abstract 方法。
因为任何一个函数式接口的实现,默认都继承了Object类,包含了来自Object里> 对这些 abstract 方法的实现。

引用普通方法
引用String类的比较方法 public int compareTo(String anotherString);

interface Inter

{ public int compare(P p1,P p2); } public class Test{ public static void main(String args[]){ Inter msg = String::compareTo; System.out.println(msg.compare("A","B")); // -1 } }

4.实现自定义函数式接口和方法引用

函数式接口

@FunctionalInterface
public interface AdditiveComputation {
	V additive(N n1, N n2);
}

引用函数式接口的类

public class NumberOperation  {
	
	private N n1;
	 
	private N n2;

    public NumberOperation(N n1, N n2) {
        this.n1 = n1;
        this.n2 = n2;
    }

    public V calc(AdditiveComputation calcInterface) {
        V v = calcInterface.additive(n1, n2);
        return v;
    }
}

测试

public static void main(String[] args) {
    	AdditiveComputation calcInterface1 = (n, v) -> n + v;
    	
    	AdditiveComputation calcInterface2 = new AdditiveComputation() {
    	        @Override
    	        public Integer additive(Integer n1, Integer n2) {
    	            return n1 + n2 + 3;
    	        }
    	 };
    	 
	    AdditiveComputation calcInterface3 = (n, v) -> n * v;
	    
	    System.out.println(calcInterface1.additive(1, 2));
	    System.out.println(calcInterface2.additive(1, 2));
	    System.out.println(calcInterface3.additive(1, 2));
}

运行结果

calcInterface1:3
calcInterface2:6
calcInterface2:2

5. 常用的内置函数式接口

5.1 Predicate
断言型接口,有输出,输出为Boolean

提供一个抽象方法test, 接受一个参数, 根据这个参数进行一些判断, 返回判断结果 true / false,提供几个默认的default方法, and, or, negate 用于进行组合判断,在流中被广泛使用

 		Predicate predicate = item -> "test".equals(item);
         // 1. test 方法测试
         System.out.println("1---> " + predicate.test("test"));
         List list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10));

         // 2. Predicate 返回一个List中的偶数
         // list.stream(), 表示将List作为流进行处理, filter()方法接收一个Predicate, toArray是将流转换成数组
         Object[] result = list.stream().filter(t -> t % 2 == 0).toArray();
         System.out.println("2---> " + Arrays.toString(result));

         // 3. 测试Predicate的and方法, 打印list中大于3, 小于6的数字
         Predicate predicate1 = t -> t > 3;
         predicate1 = predicate1.and(t -> t < 6);
         result = list.stream().filter(predicate1).toArray();
         System.out.println("3---> " + Arrays.toString(result));

         // 4. 测试Predicate的or方法, 打印list中小于3或大于5的数字
         predicate1 = t -> t < 3;
         predicate1 = predicate1.or(t -> t > 5);
         result = list.stream().filter(predicate1).toArray();
         System.out.println("4---> " + Arrays.toString(result));

         // 5. 测试Predicate的negate方法, 返回list中大于等于3,小于等于5的数字, 即对场景4取反
         result = list.stream().filter(predicate1.negate()).toArray();
         System.out.println("5---> " + Arrays.toString(result));

5.2 consumer
消费型接口,有输入,但是没返回值

Stream stream = Stream.of(1,2,3,4,5);

Consumer consumer1 = (s) -> System.out.println(s); 
stream.forEach(consumer1);
consumer1.accept(1);

List lisiList = new ArrayList<>();
Consumer consumer2  = x -> {
    if (x.getName().equals("lisi")){
        lisiList.add(x);
    }
};
consumer2  = consumer2.andThen(
        x -> lisiList.removeIf(y -> y.getAge() < 23)
);
Stream.of(
        new Person(21,"zhangsan"),
        new Person(22,"lisi"),
        new Person(23,"wangwu"),
        new Person(24,"wangwu"),
        new Person(23,"lisi"),
        new Person(26,"lisi"),
        new Person(26,"zhangsan")
).forEach(consumer2);
 
System.out.println(JSON.toJSONString(lisiList));

运行结果

1
2
3
4
5
1
[{"age":22,"name":"lisi"},{"age":23,"name":"lisi"},{"age":26,"name":"lisi"}]

5.3 Functional
函数型接口,有输入,有输出

	 
    	 Function function = x -> {
    		 Person p = new Person();
    		 p.setName(x); 
    	     return p;
    	 };

	    Person p1 = function.apply("我是老王");
	    System.out.println("p1:" + p1.getName());

	     Function functionA = x -> {
	         return x.length();
	     };
	 
	     Function functionB = x -> {
	    	 Person p = new Person();
	    	 p.setAge(x);
	         return p;
	     };
	
	     Function functionC = functionB.compose(functionA);
	     Person p2 = functionC.apply("我是老李");
	     System.out.println("p2:" + p2);
    	 
	     Function functionD = functionA.andThen(functionB);
	     Person p3 = functionD.apply("我是老张");
	     System.out.println("p3:" + p3);

运行结果

p1:我是老王
p2:Person(name=null, age=4)
p3:Person(name=null, age=4)

compose先执行()里的函functionA的apply方法,然后将其返回值作为functionB的apply方法的入参,执行functionB的apply方法
andThen则相反

还有例如IntFunction、DoubleFunction、ToLongFunction等等要求入参和返回值得Function。根据函数名就能推断出具体含义。

5.4 Supplier

供给型接口,无输入,有输出
个人理解具有以下几点好处:

传递构造方法,需要时调用get()方法获取对象
Supplier bird3 = Bird::new;
有参构造可以这么写:
Supplier person= () -> new Person(“小王”,11);
> 注: 还是重复一遍,该方式只适用于构造方法传递,需要用到时再创建对象,否则和正常创建对象并无任何区别

传递方法 。比如有个一个计算函数,只返回integer类型,下面两个测试函数求数组最小和最大值,如下

  public static int Calculation(Supplier sup) {
	       return sup.get();
	  }
	  
     public static void main(String args[]){
    	 
	        int arr[] = {9, 6, 88, 5, 13, 2, 4};
	        int maxValue = Calculation(() -> {
	            int max = arr[0];
	            for (int i = 0; i < arr.length; i++) {
	                if (max < arr[i]) {
	                    max = arr[i];
	                }
	            }
	            return max;
	        });
	        
	        System.out.println(maxValue);
	        
	        int minValue = Calculation(() -> {
	            int min = arr[0];
	            for (int i = 0; i < arr.length; i++) {
	                if (min > arr[i]) {
	                	min = arr[i];
	                }
	            }
	            return min;
	        });

	        System.out.println(minValue);

函数式接口就介绍到这里了,后续还有强大的Stream API的介绍。

你可能感兴趣的:(Java,8,API)