专业编程基础技术教程

网站首页 > 基础教程 正文

你知道实现一个JAVA接口有几种方式吗?配合lambda更加意想不到

ccvgpt 2024-11-30 19:18:26 基础教程 1 ℃

java8以后增加了lambda表达式,使得java编程也可以像其他语言一样有更简洁的编码风格,而且基于lambda更方便实现链式调用。

一个标准的函数式接口定义如下:

你知道实现一个JAVA接口有几种方式吗?配合lambda更加意想不到

@FunctionalInterface
public interface CharacterProcessor {
    String doProcess(String source);
}

其中@FunctionalInterface 不是必须的,它只是一个标记,配合编译器做检查的,你完全可以不用加这一个注解不会影响lambda表达式的正常使用。lambda是针对接口的,而且是只有一个抽象方法的接口(这里的接口是指interface以及抽象class)。如果一个接口中有两个抽象方法,是不能使用lambda表达式的,比如你像下面这样定义:

@FunctionalInterface
public interface CharacterProcessor {
   String doProcess(String source);
   String process(Member m);
}

如上面这样的接口定义了两个方法在编辑工具中是会报错的,因为有@FunctionalInterface做类型检查,如果你去掉这个注解虽然接口本身不报错了,但是在你引用这个接口使用lambda表达式的时候会报错。所以lambda就是只适用于单一抽象方法的接口。至于为什么只能是一个抽象方法其实很好理解,因为lambda表达式的原理就是基于推导,如果有多个抽象方法编辑器无法推导是哪一种。

定义了接口以后,在JAVA中接口是不能实例化的,必须通过它的实现类才能使用。要实现一个接口,在JAVA中有很多种方式。

第一种:直接new一个内部类,这种方式比较常见

   CharacterProcessor a = new CharacterProcessor() {
            @Override
            public String doProcess(String source) {
                return source.concat("abc");
            }
        };

第二种:新建一个类实现这个接口,然后创建这个实现类的实例,这是最常见的方式

public class CharacterProcessorImpl implements CharacterProcessor {
    @Override
    public String doProcess(String source) {
        return source.toUpperCase();
    }
}
CharacterProcessor b = new CharacterProcessorImpl();

第三种 :标准 lambda表达式,任何的函数式接口都可以使用这种方式

 CharacterProcessor c = (String v) -> {return v.toUpperCase();};

这是最常规最基本的lambda表达式书写方式,() 里面是参数列表,多个参数用逗号分隔,接着就是一个 -> 箭头符号,再后面就是{} 里面就是具体的实现主体,可以有多行代码,每行代码需要用一个分号结束,也就是平时我们正常的代码块。

第四种:简化版 lambda表达式,接口只有一个入参的时候,括号里的参数类型可以省略

CharacterProcessor d = (v) -> {return v.toUpperCase();};

第五种:更简化版 lambda表达式,只有一个参数时候,连括号都可以省略了

CharacterProcessor e = v -> {return v.toUpperCase();};

第六种;更更简化版 lambda表达式,只有一行的代码主体可以直接省略{}花括号,有返回值也不需要 return关键词

CharacterProcessor f = (String v) -> v.toUpperCase();

第七种:极简化版 lambda表达式 更简洁写法,参数类型、花括号、return 都直接省略了

CharacterProcessor g = v -> v.toUpperCase();

第八种:终极简化版lambda表达式,连参数和箭头都直接不用写了,简直颠覆了我的认知

CharacterProcessor h = String::toUpperCase;

其他几种lambda使用方式也许还好理解,这第八种是咋回事?为什么可以这样写呢?这个 :: 是双冒号语法,作用是方法引用。大致意思是,使用lambda表达式会创建匿名方法, 但有时候需要使用一个lambda表达式只调用一个已经存在的方法, 所以这才有了方法引用。

这种lambda的写法必须符合两个条件:

  1. 代码主体必须是只有一行代码
  2. 代码主体里面只是一个方法调用

比如下面的表达式已经够简洁了,但是按照 :: 语法定义还可以更简洁

CharacterProcessor g = v -> v.toUpperCase();

v入参是String类型,方法体也只是简单的调用了String对象的 toUpperCase()方法,并且只有一个方法一行代码,所以符合::语法特征,那就是可以直接使用 String::toUpperCase 这种更简化的写法。

因为lambda简洁并且有一定的模式,所以JDK种内置了四种比较通用的函数式接口

  1. 消费型 Consumer特点是 只有一个入参,没有返回值,使用场景多是用来对某个对象进行自身调用
    List<String> list=new ArrayList<>();
        list.forEach(v->{
            
        });

如上面代码就是我们经常用到的forEach方法,内部就是基于消费型函数接口,源码如下


  1. 供给型 Supplier 特点是没有入参,但是有返回值,多用于在返回随机值,因为没有参数所以和外界没有联系,在方法内部自行处理逻辑再返回结果


  1. 断言型 Predicate 特点是有一个入参,返回一个布尔值,多用于对入参进行逻辑处理返回一个布尔结果的场景
   List<String> list=new ArrayList<>();
   list.stream().filter(v->v.length()==2).collect(Collectors.toList());

如上所示,java stream种的 filter就是使用断言型函数接口,源码如下:


  1. 函数型 Function 特点是有一个入参和一个返回值,多用于对入参进行处理并返回处理结果的场景
list.stream().map(String::toUpperCase).collect(Collectors.toList())

如上所示,java stream种的 map就是使用函数型接口做数据处理的,其源码如下图:

以上是JDK源码种的四种内置函数式接口,实际中我们可以扩展出很多的函数接口,比如两个入参的,只要符合函数式接口定义就可以了。

lambda表达式可以让代码更简洁这个我非常认同,但是各种网络资料上说它更易于阅读和理解我是不认同的。因为使用了lambda一般你反而很难理解这个函数的入参是什么,出参又是什么。比如下面两种方式:

   CharacterProcessor a = new CharacterProcessor() {
            @Override
            public String doProcess(String source) {
                return source.concat("abc");
            }
        };

CharacterProcessor g = v -> v.toUpperCase();

实现上第二种确实很简洁,但是单从理解上显然第一种更容易,第二种要是没有一定的lambda基础一时半会都不知道咋回事,特别是对于新手来说理解起来特费劲。其次就是调试的时候你需要添加断点,那上面的第一种显然更方便断点调试。

最近发表
标签列表