【Java学习】函数式接口、Stream流、方法引用

本文介绍了Java 8中的核心特性,包括函数式接口的概念和常见接口,如Supplier、Consumer、Predicate和Function的用法。接着详细讲解了Stream流的获取、常用方法及其在数据处理中的应用。最后,阐述了方法引用的语法和应用场景,如对象引用成员方法、类引用静态方法、引用构造方法和引用数组的构造器。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、函数式接口

1 概念

@FunctionalInterface -> JDK8
@xxx -> 注解 -> JVM看的, 会编译的
// /**/ -> 注释 -> 给程序猿看的, 不会编译

函数式接⼝在Java中是指:有且仅有⼀个抽象⽅法的接⼝。

函数式接⼝,即适⽤于函数式编程场景的接⼝。⽽Java中的函数式编程体现就是Lambda,所以函数式接⼝ 就是可以适⽤于Lambda使⽤的接⼝。只有确保接⼝中有且仅有⼀个抽象⽅法,Java中的Lambda才能顺利 地进⾏推导。

2 常用函数式接口

1 Supplier接口

仅包含⼀个⽆参的⽅法: T get()

Supplier: 生产者
T get(): 生产对象

public static Person creatPerson(Supplier<Person> supplier){
    return supplier.get();
}
public static void main(String[] args) {
    //Supplier的方法是 T get() T->Person
    String name="张三";
    int age=18;

    Person p=creatPerson(() -> new Person(name,age));
    System.out.println(p);
}

2 Consumer接口

Consumer: 消费者
void accept(T t): 接收t对象, 并且使用
andThen(Consumer after): 两个消费者, 一起消费
return (T t) -> { this.accept(t); after.accept(t); };
consumer1.andThen(consumer2).accept§;
: consumer1.accept(t); consumer2.accept(t)
new Consumer() {
void accept(T t) {
accept(t);
after.accept(t);
}
}

public static void print(Consumer<Person> consumer1,Consumer<Person> consumer2,Person p){
    consumer1.andThen(consumer2).accept(p);
}
public static void main(String[] args) {
    //Consumer是一个函数式接口,Void accept(T t)
    Person p=new Person("张三",18);

    print(t-> System.out.print(t.getName()+":")
            ,t-> System.out.println("今年"+t.getAge())
            ,p);
}

3 Predicate接口

有时候我们需要对某种类型的数据进⾏判断,从⽽得到⼀个boolean值结果。这时可以 使 java.util.function.Predicate 接⼝。

Predicate 接⼝中包含⼀个抽象⽅法: boolean test(T t)。

  1. Predicate and(Predicate other): this和other两个条件同时成立

    public static void print(Predicate<Person> predicate1,Predicate<Person> predicate2,Person[] ps){
        for(Person p:ps){
            if(predicate1.and(predicate2).test(p))
            System.out.println(p);
        }
    }
    public static void main(String[] args) {
        Person[] people={
                new Person("张三",10),
                new Person("张三丰",28)
        };
        print(t->t.getName().startsWith("张"),t->t.getAge()>18,people);
    }
    

    输出结果:

    Person{name=‘张三丰’, age=28}

  2. Predicate or(Predicate other): this和other 一个成立即可

    public static void print(Predicate<Person> predicate1, Predicate<Person> predicate2, Person[] ps){
        for(Person p:ps){
            if(predicate1.or(predicate2).test(p))
                System.out.println(p);
        }
    }
    public static void main(String[] args) {
        Person[] people={
                new Person("0张三",10),
                new Person("张三丰",28)
        };
        print(t->t.getName().startsWith("张"),t->t.getAge()>8,people);
    }
    

    输出结果:

    Person{name=‘0张三’, age=10}
    Person{name=‘张三丰’, age=28}

  3. Predicate negate(): this不成立 !(test)

    public static void print(Predicate<Person> predicate1, Person[] ps){
        for(Person p:ps){
            if(predicate1.negate().test(p))
                System.out.println(p);
        }
    }
    public static void main(String[] args) {
        Person[] people={
                new Person("0张三",10),
                new Person("张三丰",28)
        };
        print(t->t.getName().startsWith("张"),people);
    }
    

    输出结果:

    Person{name=‘0张三’, age=10}

4 Function接口

java.util.function.Function 接⼝⽤来根据⼀个类型的数据得到另⼀个类型的数据,前者称为 前置条件,后者称为后置条件。

Function<T, R> function1: 类型转换
R apply(T t): 将t对象 转换成 R 类型
Function andThen(Function<R, V> after):
function1.andThen(Function<R, V> after).apply(T t)
R r = function1.apply(T t);
V v = after.apply(R r);

类型转换:String->Date->Person

public static Person transfer(Function<String, Date> f1,Function<Date,Person> f2,String str){
    return f1.andThen(f2).apply(str);
}

public static void main(String[] args) {
    Person p=transfer(t->{
        SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            return sf.parse(t);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
        },
            t->new Person(),"2022-7-19 11:52:00");
    System.out.println(p);
}

输出结果:

Person{name=‘null’, age=0}

二、Stream流

1 获取流

java.util.stream.Stream 是Java 8新加⼊的最常⽤的流接⼝。(这并不是⼀个函数式接⼝。)
获取⼀个流⾮常简单,有以下⼏种常⽤的⽅式:

  • 所有的 Collection 集合都可以通过 stream 默认⽅法获取流;
  • Stream 接⼝的静态⽅法 of 可以获取数组对应的流。
//List获取流
List<String> list=List.of("1","2","3");
Stream<String> stream1=list.stream();

//Set获取流
Set<String> set= Set.of("1","2","3");
Stream<String> stream2=set.stream();

//Map获取流(3种方式)
Map<String,Integer> map=Map.of("1",1,"2",1,"3",1);
Stream<String> stream3=map.keySet().stream();

Stream<Integer> stream4=map.values().stream();

Stream<Map.Entry<String,Integer>> stream5=map.entrySet().stream();

//数组获取流
String[] strings={"1","2","3"};
Stream<String> stream6= Arrays.stream(strings);

//直接生成流
Stream<String> stream7=Stream.of("1","2","3");

System.out.println(stream7);

2 常用方法

这些⽅法可以被分成两种:

  • 延迟⽅法:返回值类型仍然是 Stream 接⼝⾃身类型的⽅法,因此⽀持链式调⽤。(除了终结⽅法外,其余⽅法均为延迟⽅法。)
  • 终结⽅法:返回值类型不再是 Stream 接⼝⾃身类型的⽅法,因此不再⽀持类似 StringBuilder 那样的链式调⽤。

终结方法

  1. 逐⼀处理:forEach

    List<String> list=List.of("1","12","123");
    Stream<String> stream1=list.stream();
    
    stream1.forEach(t-> System.out.println(t));
    
  2. 统计个数:count(返回值为long类型)

延迟方法

  1. 过滤:filter

  2. 转换:map

  3. 取⽤前⼏个:limit

  4. 跳过前⼏个:skip

    List<String> list=List.of("1","12","123");
    Stream<String> stream1=list.stream();
    
    //过滤留下长度小于等于2的元素
    stream1.filter(t-> t.length()<=2).forEach(t-> System.out.println(t));
    
    long len=stream1.map(t->new Person(t,18))
            //跳过第一个
            .skip(1)
            //取前面几个
            .limit(2)
            .count();//流中元素个数
    System.out.println(len);
    

3 练习

现在有两个 ArrayList 集合存储队伍当中的多个成员姓名,进⾏以下若⼲操作步骤:

  1. 第⼀个队伍只要名字为3个字的成员姓名;存储到⼀个新集合中。
  2. 第⼀个队伍筛选之后只要前3个⼈;存储到⼀个新集合中。
  3. 第⼆个队伍只要姓张的成员姓名;存储到⼀个新集合中。
  4. 第⼆个队伍筛选之后不要前2个⼈;存储到⼀个新集合中。
  5. 将两个队伍合并为⼀个队伍;存储到⼀个新集合中。
  6. 根据姓名创建 Person 对象;存储到⼀个新集合中。
  7. 打印整个队伍的Person对象信息。
//第⼀⽀队伍
    ArrayList<String> one = new ArrayList<>();
    one.add("迪丽热巴");
    one.add("宋远桥");
    one.add("苏星河");
    one.add("⽯破天");
    one.add("⽯中⽟");
    one.add("⽼⼦");
    one.add("庄⼦");
    one.add("洪七公");

    //第⼆⽀队伍
    ArrayList<String> two = new ArrayList<>();
    two.add("古⼒娜扎");
    two.add("张⽆忌");
    two.add("赵丽颖");
    two.add("张三丰");
    two.add("尼古拉斯赵四");
    two.add("张天爱");
    two.add("张⼆狗");
    // ....

    Stream<String> stream = one.stream()
            .filter(t -> t.length() == 3)
            .limit(3);

    Stream<String> stream1 = two.stream()
            .filter(t -> t.startsWith("张"))
            .skip(2);

    Stream<String> stream2=Stream
        .concat(stream,stream1);
    stream2.map(t->new Person(t))
            .forEach(t-> System.out.println(t));

运行结果:

Person{name=‘宋远桥’}
Person{name=‘苏星河’}
Person{name=‘⽯破天’}
Person{name=‘张天爱’}
Person{name=‘张⼆狗’}

三、方法引用

1 方法引用符

双冒号 :: 为引⽤运算符,⽽它所在的表达式被称为⽅法引⽤。如果Lambda要表达的函数⽅案已经存在于 某个⽅法的实现中,那么则可以通过双冒号来引⽤该⽅法作为Lambda的替代者。

语义分析
例如上例中, System.out 对象中有⼀个重载的 println(String) ⽅法恰好就是我们所需要的。那么对于 printString ⽅法的函数式接⼝参数,对⽐下⾯两种写法,完全等效:

  • Lambda表达式写法: s -> System.out.println(s);
  • 方法引用符: System.out::println

第⼀种语义是指:拿到参数之后经Lambda之⼿,继⽽传递给 System.out.println ⽅法去处理。
第⼆种等效写法的语义是指:直接让 System.out 中的 println ⽅法来取代Lambda。两种写法的执⾏效果完全⼀样,⽽第⼆种⽅法引⽤的写法复⽤了已有⽅案,更加简洁。
:Lambda 中传递的参数⼀定是⽅法引⽤中的那个⽅法可以接收的类型,否则会抛出异常。

2 引用方法

1.对象引用成员方法

定义函数式接口:

@FunctionalInterface
interface Printable {
    void print(String str);
}

定义一个类:

class Koo {
    public void printKoo(Object str) {
        System.out.println(str);
    }
}

主类:

public class Demo1 {
    // 接收一个函数式接口作为方法参数
    public static void printString(Printable pr, String str) {
        System.out.println("调用printString这个方法, 这里随意");
        pr.print(str); // 具体实现, 是在lambda表达式中定义的
    }
    
    public static void main(String[] args) {
        Koo koo = new Koo();
        // 调用printString方法, 需要传递一个Printable对象
        // Printable对象又是一个函数式接口, 所以可以使用lambda表达式
        printString((t) -> {
            // 调用对象的成员方法: 先有对象, 还要有个成员方法
            // 要求, 只有一句代码, 而且printKoo的方法参数就是lambda的方法参数
            koo.printKoo(t);
        }, "hello method Reference");
        System.out.println("--------");
        // 使用方法引用简化后的写法
        printString(koo::printKoo, "hello method Reference");
    }
}

2.类引用静态方法

定义函数式接口:

interface Caculatable{
    int calculate(int i);
}

定义一个类:

class Cal{
    static int abs(int i){
        return Math.abs(i);
    }
}

主类:

public class Demo2 {
    public static void cal(Caculatable cal){
        int count=cal.calculate(-10);
        System.out.println(count);
    }

    public static void main(String[] args) {
        //调用静态方法
        cal(i->Math.abs(i));

        //方法引用简化
        cal(Cal::abs);
    }
}

3.引用构造方法

定义函数式接口:

interface Factory {
    Person newInstance(String name);
}

主类:

public class Demo3 {
    public static void create(Factory factory, String name) {
        Person p  = factory.newInstance("张三丰");
        System.out.println(p);
    }
    public static void main(String[] args) {
        // lambda表达式, 只有一句代码
        // 这句代码: 调用构造方法, 参数就是lambda表达式的参数
        create((name) -> {
            return new Person(name);
        }, "谢逊");
        // 方法引用来简化
        create(Person::new, "");
    }
}

4.引用数组的构造器

定义函数式接口:

interface Foo {
    int[] createArr(int len);
}

主类:

public class Demo4 {
    public static void create(Foo foo) {
        int[] arr = foo.createArr(10);
        System.out.println(arr.length);
    }
    public static void main(String[] args) {
        create((len) -> {
            return new int[len];
        });
        // 方法引用简化
        create(int[]::new);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值