java8新特性之函数式接口、lambda表达式、接口的默认方法、方法和构造函数的引用

函数式接口
     当接口里只有一个抽象方法的时候,就是函数式接口,可以使用注解(@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);
}


  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值