函数式接口:
当接口里只有一个抽象方法的时候,就是函数式接口,可以使用注解(@FunctionalInterface)强制限定接口是函数式接口,即只能有一个抽象方法。
例如:
package com.simple.lambdajava;
/**
* Created by zhubo on 2018/4/14.
* 默认是函数式接口
*/
public interface Interface1 {
void method1();
}
上面的接口只有一个抽象方法,则默认是函数式接口。
interface Integerface2 {
void test();
void test2();
}
该接口有两个抽象方法,不是函数式接口
@FunctionalInterface
interface Integerface2 {
}
上面这样写编译会报错,因为@FunctionalInterface注解声明了该接口是函数式接口,必须且只能有一个抽象方法。
@FunctionalInterface
public interface Interface1 {
void method1();
}
Lambda表达式只能针对函数式接口使用。
接口里的静态方法:
从java8开始接口里可以有静态方式,用static修饰,但是接口里的静态方法的修饰符只能是public,且默认是public。
package com.simple.lambdajava;
/**
* Created by zhubo on 2018/4/14.
* 接口里的静态方法
*/
public interface TestStaticMethod {
static void test1() {
System.out.println("接口里的静态方法!");
}
}
使用接口类名调用静态方法:
/**
* Created by zhubo on 2018/4/14.
*/
public class Test {
public static void main(String[] args) {
TestStaticMethod.test1();
}
}
/**
* Created by zhubo on 2018/4/14.
* 接口里的静态方法
* 也是函数式接口
*/
@FunctionalInterface
public 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();
}
Lambda表达式
可以认为是一种特殊的匿名内部类
lambda只能用于函数式接口。
lambda语法:
([形参列表,不带数据类型])-> {
//执行语句
[return..;]
}
注意:
1、如果形参列表是空的,只需要保留()即可
2、如果没有返回值。只需要在{}写执行语句即可
3、如果接口的抽象方法只有一个形参,()可以省略,只需要参数的名称即可
4、如果执行语句只有一行,可以省略{},但是如果有返回值时,情况特殊。
5、如果函数式接口的方法有返回值,必须给定返回值,如果执行语句只有一句,还可以简写,即省去大括号和return以及最后的;号。
6、形参列表的数据类型会自动推断,只需要参数名称。
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表达式那行会报错。