实验5:定义任意的复合函数。
理解:一个策略选择融为另外一个策略选择的一部分。桥接和装饰模式。
数学中的函数y = f(x),其中 f 表示从x到y的映射。函数的映射方式无穷无尽,如
f(x) = x+1
f(x)= x *x
f(x) = (x+1)* (x+1) //这是一个函数,不是复合函数
对映射f的抽象(简化起见,f设为一元函数,参数为int),对参数x进行某种操作并返回一个int,由函数接口F封装,Test与F构成策略模式。函数接口F的实现类定义进行何种具体的操作,为了给不熟悉lambda表达式的读者增加依靠感,可以编写F的独立子类如Y1描述了f(x)= x *x。
package chap4.compositeFunction;
@FunctionalInterface
public interface F {//IntUnaryOperator
public int f(int x) ;
}//Y1: return x *x;
import static yqj2065.util.Print.*;
public class Test{ //FContext
//参数化F
public static int eval(F y, int x) { //函数y作用于x一次
return y.f(x);
}
public static void test() {
F y = x -> x + 1;
int r = eval(y, 2); pln(r);// r =y.f(2);
int r2 = eval(x -> x + 1, 2); pln(r2); //F对象直接作为实参
int r3 = eval(x -> x*x, y.f(2)); pln(r3);
int r4 = eval(x -> x*x, eval(x -> x + 1, 2)); pln(r4);
}
}
当定义了F对象x -> x + 1——即数学函数f(x) = x+1后,可以直接利用y.f(2)求值,也可以调用eval(y, 2)。观察求值函数eval(F, int),可以将一个数学函数的计算结果(一个int值),作为另外一个函数的第2个参数,如语句
eval(x -> x*x, eval(x -> x + 1, 2))
求值过程为:2为实参求值f(x) = x+1,以结果3为实参求值f(x) = x*x。
2.复合函数g(f(x))
1. 代入法
定义复合函数h(g(f(x))),最简单的方式是按照从内到外的顺序来进行,首先定义函数f(x)是什么,然后再使用f(x)去定义g(f(x))。这种代入的方式十分自然。例如,
public static void compsiteTest(){
F y = x -> x + 1; //函数 y = x+1
F comp = x -> y.f(x) * y.f(x);// comp =y*y
int r =comp.f(2); pln(r);
}
这段代码中定义了策略F的多个实现y和comp,前面定义的实现即y,参与到后面定义的实现即comp中。在多次使用策略F的时候,comp对象将多个策略选择串接起来。注意:将多个策略选择串接起来并不意味着获得的策略F的对象是展开后的x*x+2x+1,而是y*y(其中y = x+1)。这段代码中程序员手工将多个策略选择串接起来(将两个函数进行复合)。
2. F的复合方法
可以在F接口中定义一个默认方法用于函数复合,程序员可以先定义一系列F对象,再按照任意的顺序和次数加以复合。
public default F com(F y) {
return (int x) -> this.f(y.f(x));
}
3. 更高级的函数接口G
在F的基础上,可以定义更高级的函数接口G。F定义了函数映射int→int,而G定义了函数的复合映射F→F。
package chap4.compositeFunction;
@FunctionalInterface public interface F{ // IntUnaryOperator
public int f(int x);
public default F com(F y) {
return (int x) -> this.f(y.f(x));
}
F identity = x->x;
}
package chap4.compositeFunction;
@FunctionalInterface public interface G {
public F com(F y);
}
class Z1 implements G{
@Override public F com(F y){
return (x) -> y.f(x) * y.f(x);
}
}
G的抽象方法和F的默认方法com(F )的区别是:F的默认方法表示将本对象的运算方式用于参数对象,得到一个复合的F对象;而G的抽象方法,用于创建数学表示g(f(x))中的g对象,表示G的实现类将给出某种运算方式,并用于参数对象。
可以定义一系列F对象,使用F的默认方法com(F )按照任意的顺序加以复合;也可以定义一系列G对象,使用G对象的复合规则com(F ) 按照任意的顺序加以复合。
通常,G的实现类不会被编写成独立的类,而是使用lambda表达式创建G的实现类对象。
G z = y -> {
return (x) -> y.f(x) * y.f(x);//z = y * y
};//或者
G z = y -> x -> y.f(x) * y.f(x);
外层的λ表达式,定义了对于任意的函数y的复合方式y*y,其返回值为内层的λ表达式,即(x) -> y.f(x) * y.f(x)。
复合函数的最后一步,都需要一个F对象如作为落脚点。可以在接口F中预定义一个命名常量如恒等函数F.identity作为固定的F。
//例程 4 12 Test中的各种测试
public static void fTest(){
F y = x -> x + 1; //函数 y = x+1
F comp = x -> y.f(x) * y.f(x);//comp =y*y
int r =comp.f(2); pln(r);//9
F z = x -> x * x;
F composeF = y.com(z);//y +1,y=x * x
pln(composeF.f(2));//2*2+1
composeF = z.com(y); // x * x
pln(composeF.f(2));//2*2+1
}
public static void gTest(){
G z = new Z1();//z = y*y
F f = x -> x + 1;
F comp = z.com(f);
int r =comp.f(2);pln(r);
G h = y -> x -> y.f(x) * y.f(x) + 2 * y.f(x);//
G g = y -> x -> y.f(x) * y.f(x);
G w = y -> x -> y.f(x)+1;
comp = h.com(g.com(z.com(F.identity)));
comp = z.com(g.com(h.com(F.identity)));
pln(comp.f(2));
}
理解其计算过程,非常重要的一点是,λ表达式只是一个引用,正如创建一个String对象"hello"并不会执行"hello"..startsWith("h")一样,λ表达式的函数体并不会执行。
为了观察执行过程,建议在代码中增加打印语句:
G z = y -> {
pln("z:" + y);
return (x) -> {
pln("in z: x=" + x);
int i = y.f(x);
pln("in z: i=" + i);
return i * i;
};
};
另外,可以使用通用函数接口替代F、G。
//import java.util.function.*;
public static void main(String[] a) {
// G z = y -> x -> y.f(x) * y.f(x);
Function<IntUnaryOperator,IntUnaryOperator> g3 = y -> x -> y.applyAsInt(x) * y.applyAsInt(x);
IntUnaryOperator op =x -> x + 1;
// F y = x -> x + 1;
pln(g3.apply(op).applyAsInt(2));
}
或者:
public static void main(String[] a) {
Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * x;
pln(g.compose(f).apply(2));
}