JavaSE12-13学习总结(函数式接口、Stream流、方法引用)

12、函数式接口

函数式接口在Java中是指:有且仅有一个抽象方法的接口。
Java 8中专门为函数式接口引入了一个新的注解: @FunctionalInterface 。该注解可用于一个接口的定义上,编译器将会强制检查该接口是否确实有且仅有一个抽象方法,否则将会报错。
函数式编程:Lambda表达式:

将代码操作延迟到了另外一个对象当中通过调用方法来完成。而是否调用其所在方法是在条件判断之后才执行的。

常用函数式接口

1、Supplier接口(生产型接口)

java.util.function.Supplier< T > 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。

//定义一个方法,方法的参数传递Supplier<T>接口,泛型执行String,get方法就会返回一个String
Public static String getString(Supplier<String> sup){
        return sup.get();
    }

    public static void main(String[] args) {
        //调用getString方法,方法的参数Supplier是一个函数式接口,所以可以传递Lambda表达式
        String s = getString(()->{
            //生产一个字符串,并返回
            return "胡歌";
        });
        System.out.println(s);

        //优化Lambda表达式
        String s2 = getString(()->"胡歌");
        System.out.println(s2);
    }

2、Consumer接口(消费型接口)

java.util.function.Consumer< T > 接口不是生产一个数据,而是消费一个数据,其数据类型由泛型决定。接口中包含抽象方法 void accept(T t) ,泛型执行什么类型,就可以使用accept方法消费什么类型的数据。

 private static void consumeString(Consumer<String> function) {
       function.accept("Hello");   
    }
 
    public static void main(String[] args) {
        consumeString(s ‐> System.out.println(s));
    }

Consumer接口有一个默认方法:andThen

如果一个方法的参数和返回值全都是 Consumer 类型,那么就可以实现效果:消费数据的时候,首先做一个操作,然后再做一个操作,实现组合。而这个方法就是 Consumer 接口中的default方法 andThen 。

//定义一个方法,参数传递String类型的数组和两个Consumer接口,泛型使用String
    public static void printInfo(String[] arr, Consumer<String> con1,Consumer<String> con2){
        //遍历字符串数组
        for (String message : arr) {
            //使用andThen方法连接两个Consumer接口,消费字符串
            con1.andThen(con2).accept(message);
        }
    }

    public static void main(String[] args) {
        //定义一个字符串类型的数组
        String[] arr = { "迪丽热巴,女", "古力娜扎,女", "马尔扎哈,男" };

        //调用printInfo方法,传递一个字符串数组,和两个Lambda表达式
        printInfo(arr,(message)->{
            //消费方式:对message进行切割,获取姓名,按照指定的格式输出
            String name = message.split(",")[0];
            System.out.print("姓名: "+name);
        },(message)->{
            //消费方式:对message进行切割,获取年龄,按照指定的格式输出
            String age = message.split(",")[1];
            System.out.println("。年龄: "+age+"。");
        });
    }

3、Predicate接口(判断型接口)

java.util.function.Predicate< T > 接口包含一个抽象方法: boolean test(T t)。用于条件判断的场景,从而得到一个boolean值结果。

public static boolean checkString(String s, Predicate<String> pre){
        return  pre.test(s);
    }

    public static void main(String[] args) {
        //定义一个字符串
        String s = "abcdef";

        //调用checkString方法对字符串进行校验,参数传递字符串和Lambda表达式
        /*boolean b = checkString(s,(String str)->{
            return str.length()>5;
            //判断参数传递的字符串的长度是否大于5,并返回结果
        });*/

        //优化Lambda表达式
        boolean b = checkString(s,str->str.length()>5);
        System.out.println(b);
    }

Predicate接口的默认方法:and

将两个 Predicate 条件使用"与”逻辑连接起来实现“并且”的效果时,可以使用default方法 and

Predicate接口的默认方法:or

与 and 的“与”类似,默认方法 or 实现逻辑关系中的“

Predicate接口的默认方法:negate

默认方法 negate 实现逻辑关系中的““非”(取反)

//and
 private static void method(Predicate<String> one, Predicate<String> two) {
        //使用实现类a.and(实现类b)
        boolean isValid = one.and(two).test("Helloworld");
        
        System.out.println("字符串符合要求吗:" + isValid);
    }
 
    public static void main(String[] args) {
        method(s ‐> s.contains("H"), s ‐> s.contains("W"));
        //判断一个字符串既要包含大写“H”,又要包含大写“W”
    }
}

//or
private static void method(Predicate<String> one, Predicate<String> two) {
        boolean isValid = one.or(two).test("Helloworld");
        System.out.println("字符串符合要求吗:" + isValid);
    }
 
    public static void main(String[] args) {
        method(s ‐> s.contains("H"), s ‐> s.contains("W"));
        //判断字符串包含大写H或者包含大写W
    }
    
//negate
 private static void method(Predicate<String> predicate) {
        boolean veryLong = predicate.negate().test("HelloWorld");
        System.out.println("字符串很长吗:" + veryLong);
    }
 
    public static void main(String[] args) {
       method(s ‐> s.length() < 5);   
       //判断字符串长度是否大于5
    }

4、 Function接口(功能型接口)

ava.util.function.Function<T,R>接口用来根据一个类型的数据得到另一个类型的数据。
前者称为前置条件,后者称为后置条件。
Function接口中最主要的抽象方法为:R apply(T t)根据类型T的参数获取类型R的结果
使用的场景例如:将String类型转换为Integer类型。

Function接口的默认方法:andThen

用来进行组合操作

public static void main(String[] args) {
        ArrayList<Person> list = new ArrayList<>();
        list.add(new Person("qqq",12));
        list.add(new Person("www",13));
        list.add(new Person("eee",69));
//将封装为List的Person对象中的年龄后加一个0;
        ArrayList<String> list1 = P2(list, (Person person)-> {
                return person.getAge();
                //将Person类型对象转为int类型年龄
        }, (Integer integer)-> {
                return integer + "0";
                //将int类型年龄转为String类型拼接
        });
        System.out.println(list1);

    }

    private static <P,T,X> ArrayList<X> P2 (ArrayList<P> al,
     Function<P,T> fun1,Function<T,X> fun2){
     //P代表Person类型,T代表int类型,X代表String类型
        ArrayList<X> newList = new ArrayList<>();
        for (P p : al) {
        	//调用apply默认方法,传入Person类型对象,再使用andThen拼接需要进行的两步操作
            X apply = fun1.andThen(fun2).apply(p);
            newList.add(apply);
        }
        return newList;
    }

13.1 Stream流

两个基础的特征:

Pipelining: 中间操作都会返回流对象本身。 这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)。
内部迭代: 以前对集合遍历都是通过Iterator或者增强for的方式, 显式的在集合外部进行迭代, 这叫做外部迭代。 Stream提供了内部迭代的方式,流可以直接调用遍历方法

当使用一个流的时候,通常包括三个基本步骤获取一个数据源(source)→数据转换执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

java.util.stream.Stream 是Java 8新加入的最常用的流接口。

所有的 Collection 集合都可以通过 stream 默认方法获取流
Stream 接口的静态方法 of 可以获取数组对应的流。
//ArrayList
List<String> list = new ArrayList<>();
// ...
Stream<String> stream1 = list.stream();

//Map
Stream<String> keyStream = map.keySet().stream();
Stream<String> valueStream = map.values().stream();
Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();

//数组
String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
Stream<String> stream = Stream.of(array);

Stream流的常用方法:

延迟方法返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)

终结方法返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。本小节中,终结方法包括 count 和 forEach 方法。

1、逐一处理:forEach

void forEach(Consumer<? super T> action);
**Consumer接口中包含抽象方法void accept(T t)

 Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
 stream.forEach(name‐> System.out.println(name));
2、过滤:filter

Stream< T > filter(Predicate<? super T> predicate);
** Predicate接口中包含抽象方法boolean test(T t);

Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
Stream<String> result = original.filter(s ‐> s.startsWith("张"));
//会将original中的以张开头的字符串全部去除,把剩下的赋值给result
3、映射:map

将流中的元素映射到另一个流中
< R > Stream< R > map(Function<? super T, ? extends R>mapper);
** Function接口中包含抽象方法R apply(T t);

Stream<String> original = Stream.of("10", "12", "18");
Stream<Integer> result = original.map(str‐>Integer.parseInt(str));
//将origninal中字符串转为Integer类型赋值给result
4、取用前几个:limit

Stream< T > limit(long maxSize);

5、跳过前几个:skip

Stream< T > skip(long n);

6、组合:concat

static < T > Stream< T > concat(Stream<? extends T> a, Stream<? extends T> b)

Stream<String> result = Stream.concat(streamA, streamB);
//这是Stream类的静态方法
7、统计个数:count

long count();

Stream<String> stream1 = Stream.of("张三丰", "张翠山", "赵敏", "周芷若", "张无忌");
Stream<String> stream2 = Stream.of("美羊羊","喜洋洋","懒洋洋","灰太狼");

Stream.concat(stream1.limit(2),stream2.skip(2))
	.forEach(s -> System.out.println(s));
	//stream1只要前两个,stream2不要前两个,然后合并为一个流,再遍历输出

13.2 方法引用

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


public class Demo01PrintSimple {
    private static void printString(Printable data) {
       data.print("Hello, World!");   
    }
 
    public static void main(String[] args) {
       printString(s ‐> System.out.println(s));   
       //拿到参数之后经Lambda之手,继而传递给 System.out.println 方法去处理。
    }
}
使用方法引用:
public class Demo02PrintRef {
    private static void printString(Printable data) {
        data.print("Hello, World!");
    }
 
    public static void main(String[] args) {
       printString(System.out::println);   
       //直接让 System.out 中的 println 方法来取代Lambda
    }
}
"::"这种写法即为方法引用:
如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者

1、通过对象名引用成员方法

//函数式接口
@FunctionalInterface
public interface Printable {
   void print(String str);   
}

//一个类中已经存在了一个成员方法
public class MethodRefObject {
    public void printUpperCase(String str) {
       System.out.println(str.toUpperCase());   
    }
}

//当需要使用这个 printUpperCase 成员方法来替代 Printable 接口的Lambda的时候,已经具有了MethodRefObject 类的对象实例,则可以通过对象名引用成员方法

public class Demo04MethodRef {
    private static void printString(Printable lambda) {
       lambda.print("Hello");   
    }
 
    public static void main(String[] args) {
        MethodRefObject obj = new MethodRefObject();
        printString(obj::printUpperCase);
    }
}
//使用了与预期效果(变大写)一样的MethodRefObject()的对象的printUpperCase方法作为方法引用,
//通过直接使用MethodRefObject()的成员方法printUpperCase,实现了少写了一次实现语句(str.toUpperCase())

2、通过类名称引用静态方法

@FunctionalInterface
public interface Calcable {
   int calc(int num);   
}

public class Demo06MethodRef {
    private static void method(int num, Calcable lambda) {
       System.out.println(lambda.calc(num));   
    }
 
    public static void main(String[] args) {
       method(‐10, Math::abs);   
    }
}

这个例子中,
Lambda表达式: n -> Math.abs(n)
方法引用: Math::abs
两种写法是等效的

3、通过super引用成员方法

@FunctionalInterface
public interface Greetable {
   void greet();   
}

//父类
public class Human {
    public void sayHello() {
       System.out.println("Hello!");   
    }
}

 //子类
public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("大家好,我是Man!");
    }
 
    //定义方法method,参数传递Greetable接口
    public void method(Greetable g){
        g.greet();
    }
 
 //使用lambda方法:
    public void show(){
           //调用method方法,使用Lambda表达式
        //method(()‐>{
               //创建Human对象,调用sayHello方法
        //   new Human().sayHello();
        //});
           //简化Lambda
        //method(()‐>new Human().sayHello());
           //使用super关键字代替父类对象
        method(()‐>super.sayHello());
    }
}

 //使用方法引用
public class Man extends Human {
    @Override
    public void sayHello() {
        System.out.println("大家好,我是Man!");
    }
 
    //定义方法method,参数传递Greetable接口
    public void method(Greetable g){
        g.greet();
    }
 
    public void show(){
        method(super::sayHello);
    }
}

在这个例子中,下面两种写法是等效的:
Lambda表达式: () -> super.sayHello()
方法引用: super::sayHello

this引用成员方法同理

4、类的构造器引用

public interface PersonBuilder {
    Person buildPerson(String name);
}

 //lambda
public class Demo09Lambda {
    public static void printName(String name, PersonBuilder builder) {
       System.out.println(builder.buildPerson(name).getName());   
    }
 
    public static void main(String[] args) {
       printName("赵丽颖", name ‐> new Person(name));   
    }
}


//方法引用
public class Demo10ConstructorRef {
    public static void printName(String name, PersonBuilder builder) {
       System.out.println(builder.buildPerson(name).getName());   
    }
 
    public static void main(String[] args) {
       printName("赵丽颖", Person::new);   
    }
}

在这个例子中,下面两种写法是等效的:
Lambda表达式: name -> new Person(name)
方法引用: Person::new

5、数组的构造器引用

@FunctionalInterface
public interface ArrayBuilder {
   int[] buildArray(int length);   
}

 //lambda
public class Demo11ArrayInitRef {   
    private static int[] initArray(int length, ArrayBuilder builder) {
       return builder.buildArray(length);   
    }
 
    public static void main(String[] args) {
       int[] array = initArray(10, length ‐> new int[length]);   
    }
}


 //方法引用
public class Demo12ArrayInitRef {
    private static int[] initArray(int length, ArrayBuilder builder) {
       return builder.buildArray(length);   
    }
 
    public static void main(String[] args) {
       int[] array = initArray(10, int[]::new);   
    }
}

在这个例子中,下面两种写法是等效的:
Lambda表达式: length -> new int[length]
方法引用: int[]::new

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值