java8新特性之函数式接口、lambda表达式、接口的默认方法、方法和构造函数的引用
2017-04-14 20:51
1131 查看
函数式接口
当接口里只有一个抽象方法的时候,就是函数式接口,可以使用注解(@FunctionalInterface)强制限定接口是函数式接口,即只能有一个抽象方法。
例如:
上面的接口只有一个抽象方法,则默认是函数式接口。
如:
接口里的静态方法
从java8开始接口里可以有静态方式,用static修饰,但是接口里的静态方法的修饰符只能是public,且默认是public。
上面的代码编译器并不会报错,可以看到该接口仍然是函数式接口。
接口的默认方法
java8里,除了可以在接口里写静态方法,还可以写非静态方法,但是必须用default修饰,且只能是public,默认也是public。
由于不是静态方法,所以必须实例化才可以调用。
默认方法可以被继承。但是要注意,如果继承了两个接口里面的默认方法一样的话,那么必须重写。
如:
类继承两个有同样默认方法的接口也是一样,必须重写。
下面的代码编译会报错
因为A或B的test方法是默认方法,修饰符为public,重写该方法修饰符必须等于或者大于它,而public已经是最大的访问修饰符,所以这里修饰符必须是public
注意:默认方法并不是抽象方法,所以下面这个接口仍然是函数式接口。
在接口里可以使用默认方法来实现父接口的抽象方法。如:
在实际使用匿名函数调用时可以重写:
可以在子接口里重写父接口的默认方法,使其成为抽象方法。
例如:
可以改为:
Lanbda表达式
可以认为是一种特殊的匿名内部类
lambda只能用于函数式接口。
lambda语法:
([形参列表,不带数据类型])-> {
//执行语句
[return..;]
}
注意:
1、如果形参列表是空的,只需要保留()即可
2、如果没有返回值。只需要在{}写执行语句即可
3、如果接口的抽象方法只有一个形参,()可以省略,只需要参数的名称即可
4、如果执行语句只有一行,可以省略{},但是如果有返回值时,情况特殊。
5、如果函数式接口的方法有返回值,必须给定返回值,如果执行语句只有一句,还可以简写,即省去大括号和return以及最后的;号。
6、形参列表的数据类型会自动推断,只需要参数名称。
可以看出,lambda表达式和匿名内部类并不完全相同
观察生成的class文件可以看出,lambda表达式并不会生成额外的.class文件,而匿名内部类会生成CloseDoor$1.class
和匿名内部类一样,如果访问局部变量,要求局部变量必须是final,如果没有加final,会自动加上。
例如:
把i当作非final变量用,则lambda表达式那行会报错。
方法的引用
引用实例方法:自动把调用方法的时候的参数,全部传给引用的方法
<函数式接口> <变量名> = <实例> :: <实例方法名>
//自动把实参传递给引用的实例方法
<变量名>.<接口方法>([实参])
引用类方法:自动把调用方法的时候的参数,全部传给引用的方法
引用类的实例方法:定义、调用接口方法的时候需要多一个参数,并且参数的类型必须和引用实例方法的类型必须一致,
把第一个参数作为引用的实例,后面的每个参数全部传递给引用的方法。
interface <函数式接口> {
<返回值> <方法名>(<类名><名称> [,其它参数...])
}
<变量名>.<方法名>(<类名的实例>[,其它参数])
具体例子参考下面代码
构造器的引用
把方法的所有参数传递给引用的构造器,根据参数的类型来推断调用的构造器。
参考下面代码
当接口里只有一个抽象方法的时候,就是函数式接口,可以使用注解(@FunctionalInterface)强制限定接口是函数式接口,即只能有一个抽象方法。
例如:
public interface Integerface1 { void test(); }
上面的接口只有一个抽象方法,则默认是函数式接口。
interface Integerface3 { void test(); void test2(); }该接口有两个抽象方法,不是函数式接口
@FunctionalInterface interface Integerface2 { }上面这样写编译会报错,因为@FunctionalInterface注解声明了该接口是函数式接口,必须且只能有一个抽象方法。
如:
@FunctionalInterface interface Integerface2 { void test(); }Lambda表达式只能针对函数式接口使用。
接口里的静态方法
从java8开始接口里可以有静态方式,用static修饰,但是接口里的静态方法的修饰符只能是public,且默认是public。
interface TestStaticMethod { static void test1() { System.out.println("接口里的静态方法!"); } }用接口类名调用静态方法:
public class Test { public static void main(String[] args) { TestStaticMethod.test1(); } }
//函数式接口 @FunctionalInterface interface TestStaticMethod { //这是一个抽象方法 void test(); //静态方法,不是抽象方法 static void test1() { System.out.println("接口里的静态方法!"); } }
上面的代码编译器并不会报错,可以看到该接口仍然是函数式接口。
接口的默认方法
java8里,除了可以在接口里写静态方法,还可以写非静态方法,但是必须用default修饰,且只能是public,默认也是public。
//非静态default方法 interface TestDefaultMethod{ default void test() { System.out.println("这个是接口里的default方法test"); } public default void test1() { System.out.println("这个是接口里的default方法test1"); } //编译报错 // private default void test2() { // System.out.println("这个是接口里的default方法"); // } }
由于不是静态方法,所以必须实例化才可以调用。
public class Test { public static void main(String[] args) { //使用匿名内部类初始化实例 TestDefaultMethod tx = new TestDefaultMethod() { }; tx.test(); tx.test1(); } }
默认方法可以被继承。但是要注意,如果继承了两个接口里面的默认方法一样的话,那么必须重写。
如:
interface A { default void test() { System.out.println("接口A的默认方法"); } } interface B { default void test() { System.out.println("接口B的默认方法"); } } interface C extends A,B { }这里接口c处会报错,因为编译器并不知道你到底继承的是A的默认方法还说B的默认方法。可以修改如下进行重写,用super明确调用哪个接口的方法:
interface C extends A,B { @Override default void test() { A.super.test(); } }测试:
public class Test { public static void main(String[] args) { C c = new C() { }; c.test(); } }
类继承两个有同样默认方法的接口也是一样,必须重写。
下面的代码编译会报错
class D implements A,B { void test() { } }
因为A或B的test方法是默认方法,修饰符为public,重写该方法修饰符必须等于或者大于它,而public已经是最大的访问修饰符,所以这里修饰符必须是public
class D implements A,B { @Override public void test() { A.super.test(); } }
public static void main(String[] args) { D d = new D(); d.test(); }
注意:默认方法并不是抽象方法,所以下面这个接口仍然是函数式接口。
@FunctionalInterface interface A { default void test() { System.out.println("接口A的默认方法"); } void test1(); }
在接口里可以使用默认方法来实现父接口的抽象方法。如:
interface C extends A,B { @Override default void test() { A.super.test(); } default void test1() { System.out.println("在子接口实现父接口的抽象方法"); } }
C c = new C() { }; c.test1();
在实际使用匿名函数调用时可以重写:
C c = new C() { @Override public void test1() { System.out.println("调用时重写"); } }; c.test1();
可以在子接口里重写父接口的默认方法,使其成为抽象方法。
例如:
interface E { default void test() { System.out.println("接口E的默认方法"); } } interface F extends E { void test(); }下面main方法里这样写不会报错
E e = new E(){ }; e.test();但如果是这样:
F f = new F(){ }; f.test();则编译报错,要求你必须实现test()方法:
可以改为:
public static void main(String[] args) { F f = new F(){ @Override public void test() { System.out.println("F接口实现"); } }; f.test(); }
Lanbda表达式
可以认为是一种特殊的匿名内部类
lambda只能用于函数式接口。
lambda语法:
([形参列表,不带数据类型])-> {
//执行语句
[return..;]
}
注意:
1、如果形参列表是空的,只需要保留()即可
2、如果没有返回值。只需要在{}写执行语句即可
3、如果接口的抽象方法只有一个形参,()可以省略,只需要参数的名称即可
4、如果执行语句只有一行,可以省略{},但是如果有返回值时,情况特殊。
5、如果函数式接口的方法有返回值,必须给定返回值,如果执行语句只有一句,还可以简写,即省去大括号和return以及最后的;号。
6、形参列表的数据类型会自动推断,只需要参数名称。
package com.Howard.test12; public class TestLambda { public static void main(String[] args) { TestLanmdaInterface1 t1 = new TestLanmdaInterface1() { @Override public void test() { System.out.println("使用匿名内部类"); } }; //与上面的匿名内部类执行效果一样 //右边的类型会自动根据左边的类型进行判断 TestLanmdaInterface1 t2 = () -> { System.out.println("使用lanbda"); }; t1.test(); t2.test(); //如果执行语句只有一行,可以省略大括号 TestLanmdaInterface1 t3 = () -> System.out.println("省略执行语句大括号,使用lanbda"); t3.test(); TestLanmdaInterface2 t4 = (s) -> System.out.println("使用lanbda表达式,带1个参数,参数为:"+s); t4.test("字符串参数1"); TestLanmdaInterface2 t5 = s -> System.out.println("使用lanbda表达式,只带1个参数,可省略参数的圆括号,参数为:"+s); t5.test("字符串参数2"); TestLanmdaInterface3 t6 = (s,i) -> System.out.println("使用lanbda表达式,带两个参数,不可以省略圆括号,参数为:"+s+" "+ i); t6.test("字符串参数3",50); } } @FunctionalInterface interface TestLanmdaInterface1 { //不带参数的抽象方法 void test(); } @FunctionalInterface interface TestLanmdaInterface2 { //带参数的抽象方法 void test(String str); } @FunctionalInterface interface TestLanmdaInterface3 { //带多个参数的抽象方法 void test(String str,int num); }
package com.Howard.test12; public class CloseDoor { public void doClose(Closeable c) { System.out.println(c); c.close(); } public static void main(String[] args) { CloseDoor cd = new CloseDoor(); cd.doClose(new Closeable() { @Override public void close() { System.out.println("使用匿名内部类实现"); } }); cd.doClose( () -> System.out.println("使用lambda表达式实现")); } } @FunctionalInterface interface Closeable { void close(); }
可以看出,lambda表达式和匿名内部类并不完全相同
观察生成的class文件可以看出,lambda表达式并不会生成额外的.class文件,而匿名内部类会生成CloseDoor$1.class
和匿名内部类一样,如果访问局部变量,要求局部变量必须是final,如果没有加final,会自动加上。
例如:
public class TestLambdaReturn { void re(LambdaReturn lr) { int i = lr.test(); System.out.println("lambda表达式返回值是:"+i); } public static void main(String[] args) { int i = 1000; tlr.re( () -> i); } } interface LambdaReturn { int test(); } 如果只是上面那样写,编译不会报错,但是如果改为: public static void main(String[] args) { int i = 1000; tlr.re( () -> i); //报错 i = 10; }
把i当作非final变量用,则lambda表达式那行会报错。
方法的引用
引用实例方法:自动把调用方法的时候的参数,全部传给引用的方法
<函数式接口> <变量名> = <实例> :: <实例方法名>
//自动把实参传递给引用的实例方法
<变量名>.<接口方法>([实参])
引用类方法:自动把调用方法的时候的参数,全部传给引用的方法
引用类的实例方法:定义、调用接口方法的时候需要多一个参数,并且参数的类型必须和引用实例方法的类型必须一致,
把第一个参数作为引用的实例,后面的每个参数全部传递给引用的方法。
interface <函数式接口> {
<返回值> <方法名>(<类名><名称> [,其它参数...])
}
<变量名>.<方法名>(<类名的实例>[,其它参数])
具体例子参考下面代码
构造器的引用
把方法的所有参数传递给引用的构造器,根据参数的类型来推断调用的构造器。
参考下面代码
package com.Howard.test12; import java.io.PrintStream; import java.util.Arrays; /** * 测试方法的引用 * @author Howard * 2017年4月14日 */ public class TestMethodRef { public static void main(String[] args) { MethodRef r1 = (s) -> System.out.println(s); r1.test("普通方式"); //使用方法的引用:实例方法的引用 //System.out是一个实例 out是PrintStream 类型,有println方法 MethodRef r2 = System.out::println; r2.test("方法引用"); //MethodRef1 r3 =(a)-> Arrays.sort(a); //引用类方法 MethodRef1 r3 = Arrays::sort; int[] a = new int[]{4,12,23,1,3}; r3.test(a); //将排序后的数组输出 r1.test(Arrays.toString(a)); //引用类的实例方法 MethodRef2 r4 = PrintStream::println; //第二个之后的参数作为引用方法的参数 r4.test(System.out, "第二个参数"); //引用构造器 MethodRef3 r5 = String::new; String test = r5.test(new char[]{'测','试','构','造','器','引','用'}); System.out.println(test); //普通情况 MethodRef3 r6 = (c) -> { return new String(c); }; String test2 = r6.test(new char[]{'测','试','构','造','器','引','用'}); System.out.println(test2); } } interface MethodRef { void test(String s); } interface MethodRef1 { void test(int[] arr); } interface MethodRef2 { void test(PrintStream out,String str); } //测试构造器引用 interface MethodRef3 { String test(char[] chars); }
相关文章推荐
- Java8特性总结(二)Lambda表达式,函数式接口,方法引用
- Java8 lambda表达式、函数式接口、方法引用
- Java 8 函数式接口、lambda表达式、方法以及构造器引用
- Java8 函数式接口 , lambda表达式,接口默认方法
- [java8] lambda表达式、函数式接口和方法引用
- java8新特性(一)之Lambda表达式和函数式接口
- Java8 新特性----函数式接口,以及和Lambda表达式的关系
- Java 8 新特性:Lambda 表达式之方法引用(Lambda 表达式补充版)
- java8新特性lambda表达式, 函数式接口以及Steam流和新的日期时间例子代码
- java8新特性总结——lambda表达式之方法引用与构造器引用
- Java Lambda(语言篇——lambda,方法引用,目标类型,默认方法,函数接口,变量捕获)
- Java 8新特性:新语法方法引用和Lambda表达式及全新的Stream API
- Java8 Lambda表达式 函数式编程 方法引用
- Java8 新特性之一---------Lambda表达式和函数式接口
- Java 8新特性:新语法方法引用和Lambda表达式及全新的Stream API
- Java8新特性Lambda表达式、函数式接口
- javaSE_8系列博客——Java语言的特性(三)--类和对象(20)--嵌套类(Lambda 表达式--VS--方法引用)
- [转]深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
- Java 8 接口里的默认方法特性