您的位置:首页 > 编程语言 > Java开发

通用函数接口java.util.function.*[

2015-10-20 16:13 567 查看
【2.1.4 通用函数接口java.util.function.*   返回目录

为了配合λ表达式的使用,需要一些作为形参的函数接口。java.util.function包基本覆盖了程序员对函数接口的各种需求。

1.方法的类型签名

方法的类型签名,描述方法的形参列表类型通过本方法处理后,形成返回值类型。以如下格式描述:(形参列表类型) ->返回值类型

从方法的类型签名分析,java.util.function包的核心函数接口有4个。

Ø        函数型T ->R,完成参数类型T向结果类型R的转换。核心函数接口Function

Ø        判断型T ->boolean,核心函数接口Predicate/谓词

Ø        消费型T ->void,核心函数接口Consumer

Ø        供给型void->T,核心函数接口Supplier

其他函数接口与它们类似。各种类型签名可以拓展到二元如(T, U) -> R,三元及更多元的类型签名,难以支持。

由于Java泛型采用擦除技术,Java中不可以用同一个名字定义不同类型或不同数量的参数的泛型类,即无法定义Function<T, R>、Function<T,U, R>、Function<Integer, Integer >等,而必须取不同的类型名字。

2.避免装箱和拆箱

Java泛型仅针对引用类型,如果使用Function<Integer, Integer>,会将代码中的int进行装箱,从而在性能上付出代价。java.util.function包针对基本类型的int、double和long提供支持,当输入或/和输出为基本类型时,可以避免自动装箱的操作。

核心函数接口

简化或二元拓展

基本类型

Function<T, R> ,T ->R

共25个接口

 

IntFunction<R>,int->R

LongFunction<R>

DoubleFunction<R>

IntToDoubleFunction, int->double

IntToLongFunction

LongToDoubleFunction,

LongToIntFunction,

DoubleToIntFunction

DoubleToLongFunction

ToIntFunction<T>, T->int

ToDoubleFunction<T>,

ToLongFunction<T>

BiFunction<T,U,R> ,(T,U) ->R

ToIntBiFunction<T,U>,

ToLongBiFunction<T,U>,

ToDoubleBiFunction<T,U>

UnaryOperator<T>, T->T

IntUnaryOperator,

LongUnaryOperator,

DoubleUnaryOperator

BinaryOperator<T> (T,T) ->T 

IntBinaryOperator,

LongBinaryOperator,

DoubleBinaryOperator

Predicate<T>  T->boolean 

共5个接口

 

IntPredicate,

LongPredicate,

DoublePredicate

BiPredicate<L,R>  (L,R)->boolean

 

Consumer<T>, T->void

共8个接口

 

IntConsumer,

LongConsumer,

DoubleConsumer

BiConsumer<T,U>  (T,U)->void

ObjIntConsumer<T>,

ObjLongConsumer<T>,

ObjDoubleConsumer<T>

Supplier<T> ()->T

共5个接口

 

BooleanSupplier,

IntSupplier,

LongSupplier,

DoubleSupplier

 

3.自定义函数接口

虽然java.util.function包提供了通用函数接口,在后面的例子中仍然使用了自定义函数接口,主要考虑在演示代码中自定义函数接口比通用函数接口更容易理解。某种程度上,使用通用函数接口,如同定义各种类时,将各种类的方法全部命名为doSth()一样,会降低代码的可读性。

另一方面,通过自定义函数接口,可以掌握通用函数接口的使用,这里就不举例说明通用函数接口的用法了。

4.方法引用

函数接口主要支持λ表达式的使用,例如

ToIntFunction<String>f = s->s.length();

值得注意的是,在λ表达式中调用某个类的方法或构造器的情形,Java 8提供了λ表达式的简化版本,方法引用。

ToIntFunction<String>f = String::length;

方法引用是否会降低代码的可读性,见仁见智。

Consumer<String> c= System.out::println;

IntConsumer   c2= System.out::println;

当调用accept(),要注意c需要的参数为泛型指出的String,而c2需要的参数为类名IntConsumer暗示的int。

c.accept("hello");

c2.accept(5);

另外,yqj2065觉得冒号冒号,很丑陋。

注:在我们讨论各种函数接口时,关心类型签名,而函数名applyAsDouble等等不需要知道,yqj2065说过:lambda表达式是省略了名字的函数。通常,通用函数接口中抽象方法的名字如applyAsDouble、apply、test,应用程序员甚至不需要知道,我们使用lambda表达式时就不需要写函数名。

int –> int封装为IntUnaryOperator。类似的,对于long–> long封装为LongUnaryOperator、double–> double封装为DoubleUnaryOperator。
对于参数和返回值为基本类型的一元函数,int –> long封装为IntToLongFunction、int –> double封装为IntToDoubleFunction、long–> int封装为LongToIntFunction、long–> double封装为LongToDoubleFunction、double–> int封装为DoubleToIntFunction、double–> long封装为DoubleToLongFunction。
参数为基本类型而返回值为R的一元函数,int –> R封装为IntFunction<R>
、long–> R封装为LongFunction<R>、double–> R封装为DoubleFunction<R>;
参数为T而返回值为int等情况,T–> int封装为ToIntFunction<T>、T–> long封装为ToLongFunction<T>、T–> double封装为ToDoubleFunction<T>。
更一般的一元函数,T–> R封装为Function<T,R>。如果是T–> T,则特化为UnaryOperator<T>。
一元函数就有了17个函数接口。下面是几个例子。

public static void unaryFunction(){//一元函数
F y = x-> x + 1;
pln(y.f(2));
IntUnaryOperator y1 = x-> x + 1; //int –> int
pln(y1.applyAsInt(2));
IntToDoubleFunction y2 = x-> x + 1; //int –> double
pln(y2.applyAsDouble(2));
ToIntFunction<String> y3 = s->s.length();//T–> int
pln(y3.applyAsInt("hello"));
IntFunction<String> y4 = i->(i+"ok");//int–> R
pln(y4.apply(5));
Function<String,Integer>  y5 =  s->s.length();//String–> Integer
pln(y5.apply("hello"));
}


从一元函数扩展到二元函数,Java中不能够定义不同个数参数的泛型类,如DoubleOP<T,U>、DoubleOP<T,U,V>、DoubleOP<T,U,V,W>,必须为它们取不同的名字,真是醉了。可以从自定义的double op(double m,double n)出发,也可以从一般到特殊:

一般的二元函数,(T, U) –> R封装为BiFunction<T,U,R>。如果是(T, T)–> T,则特化为BinaryOperator<T>。
(T, U) –> int封装为ToIntBiFunction<T,U>、(T, U) –> long封装为ToLongBiFunction<T,U>、(T, U) –> double封装为ToDoubleBiFunction<T,U>。
(int, int) –> int封装为IntBinaryOperator;类似的,(long, long) –> long封装为LongBinaryOperator、(double, double) –> double封装为DoubleBinaryOperator。
(int, long) –> int这样的组合太多,不提供封装类。

 本系列中25个接口。

    public static void testFunction(){

principle.callback.lower.DoubleOP<Double,String> f = ( x, y)->{return x +y.length() ;};
double d = f.applyAsDouble(1.0,"yqj2065");
pln(d);

ToDoubleBiFunction<Double,Double> f1 = ( m, n)->{return m +2*n ;};
d = f1.applyAsDouble(1.0,3.0);        pln(d);

Function<String,Double> f2 = x->(double)x.length();
d = f2.apply("yqj2065"); pln(d);
BiFunction<String,String,Double>  f3 = (x,y)->x.length()+y.length()*1.0;
d = f3.apply("yqj","2065"); pln(d);
BinaryOperator<Double> f4 = (x,y)->x+y;
d = f4.apply(1.0,3.0); pln(d);
DoubleBinaryOperator f5 = (x,y)->x+y;
d = f5.applyAsDouble(1.0,3.0); pln(d);

}


通用函数接口最大的用途,是框架或/和类库的设计。假设下面代码中m()是库函数,应用程序test2()可以视m()为一个高阶函数,它使用函数接口作为形参,而应用程序以lambda表达式作为实参。

public static void m(Predicate<String> p, String t) {
if (p.test(t)) {
System.out.println("ok");
}
}
public static void test2(){
m(s->s.startsWith("a"),"aab");
}


2.Predicate<T> 

谓词/Predicate,针对T数据进行测试,返回boolean。lambda表达式的类型签名T→boolean。

上面m()的形参为Predicate<String>,针对String数据进行测试;通常的形参为Predicate<T>或Predicate<?
super T>——只读通配符(T 的某个父类型)

下面的例子,说明我们可以针对List<T>抽象出一般性的方法filter——换言之,过滤等函数不是什么了不起的想法,但在Stream中,它是延迟计算的,因而更给力。

    public static <T> List<T> filter(List<T> list, Predicate<T> p) {
        List<T> results = new ArrayList<>();
        /*list.stream().filter((s) -> (p.test(s))).//惰性
                forEach((s) -> {
                    results.add(s);
                });*/
        for (T s : list) {
            if (p.test(s)) {
                results.add(s);
            }
        }
        return results;
    }
Predicate提供了default方法与或非and、or、 negate()。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: