JAVA函數語言程式設計之函式式介面
技術標籤:java# Java8新特性
前言:
方法引用和Lambda表示式必須被賦值,同時編譯器需要識別型別資訊以確保型別正確。但是你怎麼知道傳遞給方法的引數的型別?
案例1:
x->x.toString()我們清楚這裡返回型別必須是String,但x是什麼型別呢?Lambda表示式包含型別推導(編譯器會自動推匯出型別資訊,避免了程式設計師顯式地宣告)。編譯器必須能夠以某種方式推匯出x的型別。
案例2:
(x,y)->x+y現在x和y可以是任何支援+運算子連線的資料型別,可以是兩個不同的數值型別或者是1個String加任意一種可自動轉換為String的資料型別(這包括了大多數型別)。但是,當Lambda表示式被賦值時,編譯器必須確定x和y的確切型別以生成正確的程式碼。
為了解決這個問題,Java8引入了java.util.function包。它包含一組介面,這些介面是Lambda表示式和方法引用的目標型別。每個介面只包含一個抽象方法,稱為函式式方法。在編寫介面時,可以使用@FunctionalInterface註解強制執行此“函式式方法”
一、簡單應用
@FunctionalInterface public interface Functional { String goodbye(String arg); } public interface FunctionalNoAnn { String goodbye(String arg); } @FunctionalInterface public interface NotFunctional { //@FunctionalInterface的值在NotFunctional的,定義中可見:介面中如果有多個方法則會產生編譯時錯誤訊息 //String goodbye(String arg); String hello(String arg); } public class FunctionalAnnotation { public String goodbye(String arg){ System.out.println("Goodbye," + arg); return ""; } public static void main(String[] args){ FunctionalAnnotation fa = new FunctionalAnnotation(); Functional f = fa::goodbye; FunctionalNoAnn fna = fa::goodbye; // Functional fac = fa; //Incompatible Functional fl = a -> "Goodbye," + a ; FunctionalNoAnn fnal = a->"Goodbye," + a; System.out.println(f.goodbye("FunctionalAnnotation")); System.out.println(fna.goodbye("FunctionalAnnotation")); System.out.println(fl.goodbye("Functional")); System.out.println(fnal.goodbye("FunctionalNoAnn")); } } 執行結果 --------------------------------------------------------------------------- Goodbye,FunctionalAnnotation Goodbye,FunctionalAnnotation Goodbye,Functional Goodbye,FunctionalNoAnn
- @FunctionalInterface註解是可選的;
- Java在main()中把Functional和Func-tionalNoAnn都當作函式式介面
- @FunctionalInterface的值在NotFunctional的定義中可見:介面中如果有多個方法則會產生編譯時錯誤訊息。
- 觀察一下f和fna兩個變數呼叫的是類中的方法,而並不是自己介面中的方法,但是我們的FunctionalAnnotation類並沒有實現兩個函式是介面,。Java8新特性:如果將方法引用或Lambda表示式賦值給函式式介面(型別需要匹配),Java會適配你的賦值到目標介面。編譯器會自動包裝方法引用或Lambda表示式到實現目標介面的類的例項中(大白話就是,引用匹配方法至關注的是引數類表與返回型別,不關注繼承實現引)。
- Functional fac = fa; 會編譯錯誤,因為它沒有明確地實現Functional介面。但是,Java8允許我們以簡便的語法為介面賦值函式。java.util.function包建立一組完整的目標介面,使得我們一般情況下不需再定義自己的介面。
二、使用函式介面時,名稱無關緊要
class In1{}
class In2{}
public class MethodConversion {
static void accept(In1 i1,In2 i2){
System.out.println("accept()");
}
static void someOtherName(In1 i1,In2 i2){
System.out.println("someOtherName()");
}
public static void main(String[]args){
BiConsumer<In1,In2> bic;
bic=MethodConversion::accept;
bic.accept(new In1(),new In2());
bic=MethodConversion::someOtherName;
//bic.someOtherName(newIn1(),newIn2());//Nope
bic.accept(new In1(),new In2());
}
}
-----------------------------------------------------------------
accept()
someOtherName()
- 觀察輸出結果:在使用函式介面時,名稱無關緊要——只要引數型別和返回型別相同。Java會將你的方法對映到介面方法。
三、高階函式
高階函式(Higher-orderFunction)只是一個消費或產生函式的函式。
public interface FuncSS extends Function<String,String> {
}
public class ProduceFunction {
static FuncSS produce(){
return s->s.toLowerCase();//[2]
}
public static void main(String[] args){
FuncSS f = produce();
System.out.println(f.apply("YELLING"));
}
}
--------------------------------------------------------------
yelling
- 這裡,produce()是高階函式。
四、andThen()使用
class Apple{
@Override
public String toString(){
return"Apple";
}
}
class Banana{
@Override
public String toString(){
return"Banana";
}
}
public class TransformFunction {
static Function<Apple,Banana> transform(Function<Apple,Banana> in){
return in.andThen(o->{
System.out.println(o);
return o;
});
}
public static void main(String[] args){
Function<Apple,Banana> f2 = transform(i->{
System.out.println(i);
return new Banana();
});
Banana banana = f2.apply(new Apple());
}
}
這裡使用到了Function介面中名為andThen()的預設方法,該方法專門用於操作函式。顧名思義,在呼叫in函式之後呼叫toThen()(還有個compose()方法,它在in函式之前應用新函式)。要附加一個andThen()函式,我們只需將該函式作為引數傳遞。transform()產生的是一個新函式,它將in的動作與andThen()引數的動作結合起來。
五、函式式介面替代反射
現實開發中我閱讀過好多程式碼滿篇if() else if()...... else(),最長的見過else if() 超越50個,這種程式碼初入職場寫寫也就ok,但是真的遇到過5年以上的程式設計師還是這麼寫,技術好些的會在業務上分析,或是用反射,但是大家都知道反射的效能並不是很好,如果在資料量較少,效能要求不高的情況下可以使用,java8之後我們好的場景下可以用函式式介面替代反射。
public class Function {
private String var1;
private String var2;
private String var3;
private String var4;
private String var5;
private String var6;
private String var7;
private String var8;
private String var9;
private String var10;
public String getVar1() {
return var1;
}
public void setVar1(String var1) {
this.var1 = var1;
}
public String getVar2() {
return var2;
}
public void setVar2(String var2) {
this.var2 = var2;
}
public String getVar3() {
return var3;
}
public void setVar3(String var3) {
this.var3 = var3;
}
public String getVar4() {
return var4;
}
public void setVar4(String var4) {
this.var4 = var4;
}
public String getVar5() {
return var5;
}
public void setVar5(String var5) {
this.var5 = var5;
}
public String getVar6() {
return var6;
}
public void setVar6(String var6) {
this.var6 = var6;
}
public String getVar7() {
return var7;
}
public void setVar7(String var7) {
this.var7 = var7;
}
public String getVar8() {
return var8;
}
public void setVar8(String var8) {
this.var8 = var8;
}
public String getVar9() {
return var9;
}
public void setVar9(String var9) {
this.var9 = var9;
}
public String getVar10() {
return var10;
}
public void setVar10(String var10) {
this.var10 = var10;
}
}
public class ReflectFunction {
static String[] arr = { "fun01", "fun02", "fun03", "fun04", "fun05", "fun06", "fun07", "fun08", "fun09", "fun10" };
static List<BiConsumer<Function, String>> biConsumers = Arrays.asList(Function::setVar1, Function::setVar2, Function::setVar3,
Function::setVar4, Function::setVar5, Function::setVar6, Function::setVar7, Function::setVar8, Function::setVar9, Function::setVar10);
public static void main(String[] args) {
long start = System.currentTimeMillis();
IntStream.range(0, 100000).forEach(i -> testReflect());
long end = System.currentTimeMillis();
System.out.println(end - start);
long start1 = System.currentTimeMillis();
IntStream.range(0, 100000).forEach(i -> testMethodReference());
long end1 = System.currentTimeMillis();
System.out.println(end1 - start1);
}
static void testReflect() {
Function function = new Function();
try {
Class<?> clazz = Class.forName("test.function.Function");
for (int i = 0; i < arr.length - 1; i++) {
Method method = clazz.getMethod("setVar" + (i + 1), String.class);
method.invoke(function, arr[0]);
}
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
e.printStackTrace();
}
}
static void testMethodReference() {
Function function = new Function();
for (int i = 0; i < arr.length; i++) {
biConsumers.get(i).accept(function, arr[i]);
}
}
}