Day25.Java8接口新特性 、Lambda表达式 、函数式接口 、Stream类

1 Java8接口新特性

  1. Java8之前接口定义:只可以定义抽象方法,不能定义非抽象方法的特殊类型

  2. Java8接口的定义:即可以定义抽象方法,也可以定义非抽象方法
    如果需要定义非抽象方法,那么当前方法需要使用static或者default修饰

  3. 改变的原因:

    改变之前:如果定义了一个接口,接口已经参与使用,后续想给接口增加一个功能,只能添加抽象方法,但是接口已经在使用,新增一个抽象方法之后,使用过该接口的类型 就报错。

    改变之后,为了保证接口可以新增功能,要保证使用过接口的类型不出问题,jdk1.8之 后接口就重新定义:接口中既可以定义抽象方法也可以定义非抽象方法。

1.1 default修饰的方法

  1. 定义方式:
    直接在在修饰符位置上加上default关键字即可
  2. 注意事项:
    (1)如果某个类型实现两个接口,两个接口中定义了两个相同方法声明的抽象方法,那么该实现类只需要重写其中一个即可。
    (2)如果某个类型实现两个接口,两个接口中定义了两个相同方法声明,不同方法实现的非抽象方法,那么该实现类必须强制重写或者只能继承其中一个,不能同时继承两个。
    强制继承方式:接口名.super.方法名();
    (3)如果某个类型即继承一个父类,又实现一个接口,父类和接口中有两个相同的方 法声明,不同的实现内容的方法;该类可以选择默认继承父类的方法,或者强制 继承接口的方法。
    强制继承方式:接口名.super.方法名();

代码

package demos;

public class InterClass extends Fu implements Inter,Inter2{

    //重写接口中定义的抽象方法
    @Override
    public void show() {
        System.out.println("show方法");
    }
    //重写接口中定义的非抽象方法
    @Override
    public void print(){
        //强制继承Inter2中的方法
//        Inter2.super.print();
        //强制继承Inter中的方法
//        Inter.super.print();
        System.out.println("实现类的print方法");
    }
    //强制继承接口中的方法
    public void eat(){
        Inter.super.eat();
    }

}

1.2 static修饰的方法

  1. 父类中定义的静态方法可以被子类继承,但是不能被重写
  2. 接口中定义的静态方法,实现类不能继承的

2 Lambda表达式

  1. 概念:
    本质上是一个对象,可以当作是匿名内部类创建的对象的简写格式。

  2. 格式:
    (参数)->{方法体}

  3. 说明:
    参数:需要重写的抽象方法中的形参列表
    ->:为了分隔前后两部分,Lambda运算符或者箭头运算符
    方法体:需要对抽象方法重写的内容

  4. 举例:
    (1)如果抽象方法没有参数,方法体只有一句:
    方法体外的大括号可以省略。
    格式:()->方法体语句;
    (2)如果抽象方法中有一个参数:
    参数的类型可以省略,参数外的小括号可以省略
    格式:参数名->{方法体语句};
    (3)如果方法的参数有多个:
    参数外的小括号不能省略,参数类型可以省略,方法体外的大括号可以省略。
    格式:(参数名称1,参数名称2)->{方法体语句};
    (4)如果方法有返回值:
    直接在方法体中的大括号中对数据进行return即可
    格式:(参数名称)->{ 方法体语句; return语句; };
    (5)如果方法需要返回值,而且方法体只有一句:
    可以将大括号和return关键字一起省略
    格式:(参数名称)->要返回的数据;

  5. Lambda表达式和匿名内部类的区别:
    (1)如果接口中只有一个抽象方法,既可以使用匿名内部类创建,也可以使用Lambda创建。
    如果接口中有多个抽象方法,那么只能使用匿名内部类创建
    (2)Lambda表达式只适用于接口的使用,不适用于类型的使用
    (3)匿名内部类会在磁盘中生成一个字节码文件,Lambda表达式则没有

3 函数式接口

  1. 概念:
    一个接口中的抽象方法只有一个,那么这个接口就是一个函数式接口。
  2. 通过注解检测一个接口是否是一个函数式接口:
    @FunctionalInterface
    在接口上直接加上注解,如果这个接口是一个函数式接口则不报错,否则编译报错

代码

package demos2_interface;

//接口中抽象方法只有一个,就是一个函数式接口
//使用注解标记接口是一个函数式接口
@FunctionalInterface
public interface Inter {
    public void show();
    public default void print(){
        System.out.println("print方法");
    }
}

3.1 内置的函数式接口

  1. 在jdk8之后,官方定义了一些常用的函数式接口,如果以后需要使用类似的接口,直接 使用即可,不需要再单独定义。
  2. 分类:
Consumer<T> :消费型接口
   void    accept(T t)
Supplier<T>  :供给型接口
   T      get()
Function<T,R> :函数型接口
   R      apply(T t)
Predicate<T>  :断言型接口
   boolean    test(T t);

3.2 消费型接口

  1. 接口名称:Consumer<T>
  2. 抽象方法:void accept(T t):消费一个参数数据
  3. 概述:该接口中的方法可以接收一个参数,接收的参数类型由泛型指定,对参数的操作方式根据该接口的实现类决定,不需要返回值。
  4. 拓展的非抽象方法:
    default Consumer<T> andThen(Consumer<? super T> after)
    返回一个组合的 Consumer ,按顺序执行该操作,然后执行 after操作。

代码

package demos2_interface;

import java.util.function.Consumer;

public class Demo01 {
    public static void main(String[] args) {

        Consumer<String> con1 = str-> System.out.println(str);
        Consumer<String> con2 = str-> System.out.println(str.length());
        //将两个消费型接口的实现类对象进行合并(两个功能合并为一个功能)
        con1.andThen(con2).accept("hello");

//        get("hello",str -> System.out.println(str));
//        get("hello",str-> System.out.println(str.length()));
    }
    //对参数做处理,打印一遍参数
    //对参数做处理,打印参数的字符个数
    //如果方法对参数的使用不明确,可以使用消费型接口
    //好处:(提高代码扩展性) 不需要反复对方法进行修改
    //    如果方法接收参数之后不知道应该怎么处理,就可以使用消费型接口来处理该参数
    //    具体如何处理:将来调用该方法时,如何定义实现类对象就如何处理
    public static void get(String str,Consumer<String> con){
        con.accept(str);
    }
}

3.3 供给型接口

  1. 接口名:Supplier<T>
  2. 抽象方法:T get():该方法不需要参数,它会按照某种逻辑,返回一个具体的数据
  3. 概述:
    该接口也被称为生产型接口,如果指定了泛型是什么类型,那类中的get方法就会返回 一个该类型的一个具体数据。返回的数据,由该接口的实现类对象决定。

代码

package demos2_interface;

import java.util.ArrayList;
import java.util.Random;
import java.util.function.Supplier;

public class Demo02 {
    public static void main(String[] args) {
        //调用getList方法获取一个集合,要求集合中的随机数是1-100
        System.out.println(getList(()->new Random().nextInt(100)+1));
        //调用getList方法获取一个集合,要求集合中的随机数范围:1-1000
        System.out.println(getList(()->new Random().nextInt(1000)+1));
    }
    //定义一个方法,该方法可以返回一个集合,集合中要求存储十个随机数
    //要求:获取的随机数范围  1-100
    //要求:获取的随机数范围  1-1000
    //如果对方法的需求不断改变,定义的方法也要不断修改,方法的扩展性太低
    //Supplier<Integer> sup = ()->new Random().nextInt(100)+1;
    //如果需要获取一个不确定的数据,可以就可以定义一个供给型接口
    //需要什么数据在方法中不需要定义,只需要通过供给型接口获取即可
    //具体供给型接口提供何种数据:根据对接口的实现类重写方式决定
    public static ArrayList<Integer> getList(Supplier<Integer> sup){
        ArrayList<Integer> list = new ArrayList<>();
        for(int i = 1;i <= 10;i++){
            list.add(sup.get());
        }
        return list;
    }
}

3.4 函数型接口

  1. 接口名:Function<T,R>
  2. 抽象方法:R apply(T):接收一个数据,操作数据之后,返回一个新的数据
  3. 概述:
    该接口可以接收一个数据,数据的类型根据泛型指定,然后通过该接口的实现类对象对 该数据进行操作,操作之后返回一个新的数据。
  4. 拓展的非抽象方法:
    default Function andThen(Function f)
    先通过调用者对象处理参数,将处理的结果再通过f对象处理,将两个处理的结果进行返回。

代码

package demos2_interface;

import java.util.function.Function;

public class Demo03 {
    public static void main(String[] args) {

        Function<String,String> fun1 = str->str+"hello";
        Function<String,Integer> fun2 = str->str.length();
        //将fun1和fun2合并到一起
        //接收一个参数之后,先通过fun1的内容处理,处理的结果再通过fun2处理
        //两个对象处理之后的最终结果,返回
        System.out.println(fun1.andThen(fun2).apply("java"));

        //使用change方法,传递一个字符串,将字符串转为int返回
        int i = change("123", s -> Integer.parseInt(s));
//        System.out.println(i);
        //使用change方法,传递一个字符串,需要获取字符串字符个数
        int i2 = change("123", s -> s.length());
//        System.out.println(i2);

    }
    //定义一个方法,该方法可以接收一个数字字符串,将字符串转为int整数返回
    //定义一个方法,接收一个数字字符串,将字符串中的字符个数返回
    //如果定义方法,不确定如何处理接收的参数,可以定义一个函数型接口
    //后续调用该方法时,传递一个对应的接口实现类对象即可
    //具体如何重写接口的实现类对象:根据需求决定
    //好处:提高方法的扩展性,不用每次对方法进行修改
    public static int change(String str, Function<String,Integer> fun){
       return fun.apply(str);
    }
}

3.5 断言型接口

  1. Predicate:
    boolean test(T t):对数据做出指定的判断
  2. 概述:
    该接口是一个判断接口,接口可以接收一个指定泛型的参数,并根据该接口的实现类 对象对该参数做出对应的判断,返回只为boolean类型
  3. 额外功能:
    and(Predicate<T> p):先将参数通过调用者判断真假,再将参数通过p判断真假,全真为真,否则为假
    or(Predicate<T> p):全假为假,否则为真
    negate():取反

代码

package demos2_interface;

import java.util.function.Predicate;

public class Demo04 {
    public static void main(String[] args) {

        Predicate<String> pre = s->s.startsWith("a");
        Predicate<String> pre2 = s->s.length()>=10;
        //判断and两边的结果:全真为真,否则为假
        System.out.println(pre.and(pre2).test("abcdefghijklmn"));
        //判断or两边的结果:全假为假,否则为真
        System.out.println(pre.or(pre2).test("abcd"));
        //negate对判断结果取反
        System.out.println(pre.negate().test("abcd"));
        
        //使用testValue方法,判断一个字符串的长度是否是大于10
//        System.out.println(testValue("abcdefg", s -> s.length() >= 10));
        //使用testValue方法,判断字符串是否是以a开头
//        System.out.println(testValue("abc",s->s.startsWith("a")));
    }
    public static boolean testValue(String str, Predicate<String> pre){
       return pre.test(str);
    }
}

3.6 方法引用

  1. 概念:
    对lambda表达式的扩展,在定义lambda表达式的内容时,如果这个内容之前已经定 义过,那么就不需要再定义一遍,直接调用即可。
  2. 格式:
    如果是一个构造方法:类名::new
    如果是一个静态方法:类名::方法名
    如果是一个非静态方法:对象名::方法名

代码

package demos2_interface;

import java.util.function.Consumer;

public class Demo05 {
    public static void main(String[] args) {

        Consumer<String> con = s-> System.out.println(s+"你真帅!!!");
        con.accept("刘功睿");

        //在定义lambda表达式的时候,如果定义的内容之前已经定义过,可以拿来调用一下,不需要重复定义
        //如果内容是在构造方法中定义的:类名::new
        Consumer<String> con2 = Person::new;
        con2.accept("王浩");
        //如果内容是在静态方法中定义的:类名::静态方法名
        Consumer<String> con3 = Person::print;
        con3.accept("王硕新");
        //如果内容是在非静态方法中定义的:对象名::方法名
        Consumer<String> con4 = new Person()::show;
        con4.accept("孙伟博");

    }
}

class Person{
    private String name;
    public Person(){}
    public Person(String name){
        System.out.println(name + "你真帅!!!");
    }

    public static void print(String name){
        System.out.println(name + "你真TM帅!!!");
    }

    public void show(String name){
        System.out.println(name + "你帅的掉渣,简直无法形容了!!!");
    }
}

4 Stream类

  1. 概述:Java8之后提供的一个新的类型。
  2. 作用:使用集合和数组操作元素时,可以使用此类代替循环和判断语句完成代码的简化

4.1 Stream类对象的获取

  1. Collection集合的获取方式:
    对象名.stream();
  2. Map集合获取方式:
    ketSet().stream();
    values().stream();
    entrySet.stream();
  3. 数组的获取方式:
    Strem.of(数组名);
  4. 注意事项:
    因为Stream类型提供的功能主要是对循环做替换,所以只有集合和数组才需要使用

代码

package demos3_stream;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;

public class Demo02 {
    public static void main(String[] args) {
        //如果操作的对象是一个单列集合
        Collection<String> c = new ArrayList<>();
        Stream<String> stream1 = c.stream();
        //如果操作的对象是一个双列集合
        Map<Integer,String> map = new HashMap<>();
        Stream<Integer> stream2 = map.keySet().stream();
        Stream<String> stream3 = map.values().stream();
        Stream<Map.Entry<Integer, String>> stream4 = map.entrySet().stream();
        //如果操作的对象是一个数组
        Integer[] arr = {1,2,3};
        Stream<Integer> stream5 = Stream.of(arr);
    }
}

4.2 Stream类的常用方法

  1. Stream类中包含了一些对数据过滤和筛选以及获取的方法,由此来代替循环和判断语句。

  2. 分类:

    延迟方法:使用完该类方法之后,返回值是一个Stream类的对象,可以继续调用类中的方法。

    终结方法:使用完该类方法之后,返回值不再是一个Stream类的对象,不能继续使用类中的方法。

  3. 方法罗列:
    (1)forEach(Consumer<? super T> action):终结方法
    该方法可以自动获取流中每一个数据,并对获取的数据操作
    数据的操作方式根据消费型接口的实现类重写。
    (2)count():返回此流中的元素数。终结方法
    (3)filter(Predicate<? super T> predicate):延迟方法
    该方法可以自动获取流中的每一个数据进行判断,如果数据判断结果为真,就在流中存储,否则不存储该数据
    (4)limit(long m):将流中的前m个元素保留,其余的删除 延迟方法
    (5)skip(long n): 跳过前n个元素,保留后面的元素 延迟方法
    (6)map(Function<? super T,? extends R> mapper): 延迟方法
    该方法自动获取流中的每一个数据,并对数据处理,处理之后返回一个新 的数据。具体的处理方式根据实现类对象确定。
    (7)concat(Stream<? extends T> a, Stream<? extends T> b):延迟方法
    将两个流中的数据进行合并,合并为一个流对象
    (8)distinct():去除流中重复的元素 延迟方法
    (9)toArray():把stream流中的数据收集到数组中
    (10)collect(Collector c):把stream流中的数据收集到指定的集合中
    Collector:参数的类型 是一个接口获取可以通过工具类Collectors的方法获取
       常用:
           获取List集合:Collectors.toList()
           获取Set集合:Collectors.toSet()

练习

有两个Arraylist集合,存储队伍中的多个成员姓名,使用Stream方式,对以下步骤进行操作
1、第一个队伍只要名字为3个字的成员姓名
2、第一个队伍只要筛选之后的前三个人
3、第二个队伍只要姓张的
4、第二个队伍筛选之后不要前两个人
5、将两个队伍合并成一个队伍
6、合并之后的队伍中的所有人的Person(自定义类型)对象,存储到一个ArrayList集合中

package demos3_stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Test {
    public static void main(String[] args) {
//        有两个Arraylist集合,存储队伍中的多个成员姓名,使用Stream方式,对以下步骤进行操作
//        1、第一个队伍只要名字为3个字的成员姓名
//        2、第一个队伍只要筛选之后的前三个人
//        3、第二个队伍只要姓张的
//        4、第二个队伍筛选之后不要前两个人
//        5、将两个队伍合并成一个队伍
//        6、合并之后的队伍中的所有人的Person(自定义类型)对象,存储到一个ArrayList集合中
//        队伍1:人名自己找
//        队伍2:
        List<String> list1 = Arrays.asList("/*填写自己找的人名,中间用、隔开*/".split("、"));
        List<String> list2 = Arrays.asList("".split("、"));
        Stream<String> team1 = list1.stream().filter(x -> x.length() == 3).limit(3);
        Stream<String> team2 = list2.stream().filter(x -> x.startsWith("张")).skip(2);
        Stream<String> team = Stream.concat(team1, team2);
//        Stream<Person> personteam = team.map(x -> new Person(x));
        Stream<Person> personteam = team.map(Person::new);
//        ArrayList<Person> list = (ArrayList<Person>)personteam.collect(Collectors.toList());
        ArrayList<Person> list = new ArrayList<>();
        personteam.forEach(list::add);
        System.out.println(list);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值