Java8函数式接口/Lambda表达式/接口默认方法/接口静态方法/接口冲突方法重写/lambda表达式指定泛型类型等

时间:2021-03-11 06:23:33

一:函数式接口

1.函数式接口的概念就是此接口必须有且只能有一个抽象方法,可以通过@FunctionalInterface来显示规定(类似@Override),但是没有此注解的但是只有一个抽象方法的接口也是函数式接口;(接口也和类一样有包访问权限,但是内部的方法则默认是public)

@FunctionalInterface
public interface IFoo{
void print();
}  // 就是一个最简单的函数式接口,但是如果再有个如void print2()抽象方法则这个接口不是函数式接口会在编译时便报错(因为有@FunctionalInterface)

2.接口里可以有静态方法(JDK8的新特性)

@FunctionalInterface
public interface IFoo{
void print();
static void sTest(){
System.out.println("msg");
}
}

注意,接口里的静态方法默认且只能是public的,就像接口里的字段默认且只能是public static的;调用时可以直接IFoo.sTest();

3.接口里也可以有default方法(JDK8新特性)

@FunctionalInterface
public interface IFoo{
void print();
default void sTest(){
System.out.println("msg");
}
}

与静态方法不同的是default方法本质上是实例方法,因此仍然需要创建IFoo的实现类对象才能调用此方法,如(new IFoo(){...}).sTest();是可以的但是IFoo.sTest()则错误

4.当一个接口成为了函数式接口后且也是以函数式接口来使用,它就类似C#里的委托,即接口和其里面的抽象方法是一一对应的,故泛型的声明要在接口名里而非抽象方法里

@FunctionalInterface
public interface IFoo<T extends Serializable>{
T print(T arg); // 写成<T> T print(T);对于将IFoo看成委托而言是错误的写法;注意<T extends B> T print。。在普通接口里也是可以的;
static void sTest(){
System.out.println("msg");
}
}

5.函数式接口对象和其Lambda表达式的关系

lambda表达式不会产生里面类的.class文件,而如new Predicate(){...}是会产生的,因此如果需要将lambda表达式转换为接口对象是需要强制转换的,如:((Predicate) o -> false).and(o -> false)

上面的(o -> false)是一个”函数对象“,它和new Predicate(){@Override test...}是不一样的,前者并没有产生Predicate的实现类,因此需要做一个强制类型转换将lambda表达式产生的结果转换为接口实现类对象;

6.将函数式接口当成委托使用

public interface Println<T>{
void println(T msg);
}

可以Println<String> println = System.out::println;来赋值;

7.实现多个接口但是又同名的default方法时必须重写/重定义该方法(如果是抽象方法则无所谓,当子类实现IC时实现print那么实际上即是实现IA的也是IB的不存在要重写两次的说法)

public interface IA{
default void print(){}
}
public interface IB{
default void print(){}
}
public interface IC extends IA, IB{
@Override
default void print(){IA.super.print();} // TODO 用于明确指定用IA的print,注意在JDK8以前由于没有default且只能继承一个类,因此只需要super.print()即可
}

8.lambda表达式的类型推导

如果是非泛型自然不需要,但是如果参数是泛型,但又无法隐式推导出时可以通过(String s) ->{..}来指定泛型的类型;

9.接口里可以都是default方法,此时这个接口可以直接new ITest{}就可以产生对象,但也可以重写default方法

10.可以在子接口里重写父接口的default方法令其在子接口里成为抽象方法;

11.lambda表达式和匿名内部类的区别

lambda表达式是很特殊的存在,它不会生成.class文件,而且它也不是接口的实现类的对象,应该这么理解IFoo foo = ()->true;是IFoo能够匹配右边的lambda表达式,而不是说这个正则表达式是一个IFoo的匿名实现类的对象;

注意上面存在一个隐式的转换即IFoo foo = (IFoo) ()->true;

12.将方法存储到“委托”里

ITest del = System::getenv;  // 此时通过System.getenv方法实现了ITest的抽象方法;

del.method("string");注意,这里有几个重点,1)是ITest必须是函数式接口;2)ITest中的抽象方法名字任意且可以有泛型,但是必须能匹配System.getenv(..)这个方法的返回值和参数列表;3)del和C#的委托变量还是有些不一样的不能直接del(..)来调用,而是要del.method(..)来调用System.getenv方法;

n)还可以用ITest t = String::new;表示String的构造方法;那么t.func(..)方法的就是new String(..)而产生的对象;(构造方法某种程度上可以理解为类的静态方法)

n)IFoo2<String> ss = sbs::concat;,sbs是一个对象,因此ss.func(..)实际是是调用sbs.concat(..)方法

n)对于如果方法test是类A的实例方法,则ITest del = A::test;是错误的,IDE提示没有static的test方法,因为当del.func(..)调用时将不知道要调用哪个对象的test方法,因为test的调用必须依赖某个A对象;

补充:

1.lambda表达式里是可以将隐藏的this参数也作为参数的,比如接口里有唯一的抽象方法String test(String str);,那么lambda表达式里可以用Object::toString作为它的参数的(toString是实例方法obj.toString(),它显示里没有参数但是实际上有个this参数);

2.当存在方法:public <T> T execute(RedisCallback<T> action)时,用lambda表达式作为它的参数必须用(RedisCallback<Boolean>) connection -> {..}这种形式转换,这个特点是RedisCallback的T和execute的T进行了关联;

这里之所以必须强制转换,是因为java只允许从调用处开始往下一级一级的推算泛型,比如调用处是 a,然后它可以推算出a调用的b的泛型,然后再推算出b调用的c的泛型,而不能从a里先推算出c的泛型然后再网上递推出b处的泛型;