JavaSE-Lambada

108 阅读9分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

函数式编程

什么是Lambda表达式

可以理解为一种匿名函数的代替,lambda允许将函数作为一个方法的参数传递,从而简化代码编写。

什么是函数式接口

lambda表达式需要函数式接口的支持

所谓函数式接口,是指只有一个抽象方法的接口

在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是”那段代码“,需要是这个接口的实现。 这是我认为理解Lambda的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现。这种只有一个接口函数需要被实现的接口类型,我们叫它”函数式接口“。 为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成"非函数接口”,我们可以在这个上面加上一个声明@FunctionalInterface, 这个注解是非必须的,这样提醒别人避免在里面添加新的接口函数了。

Lambda表达式的基本语法

函数式接口  变量名  = (参数1,参数2...)->{

        方法体

}

传递行为参数

public class Demo4 {
    public static void main(String[] args) {
        Student s = new Student();
        //接口引用指向实现类对象
        TravelMode ridingMode = new RidingMode();
        //利用多态调用了实现类的方法
        //这个方法调用的本质是ridingMode.getToSchool
        s.attendSchool(ridingMode);
        
        
    }
}

class Student{
    //传递一个接口类型的参数对象,这个接口类型的参数对象只有一个方法
    public void attendSchool(TravelMode travelMode){
        travelMode.getToSchool();
    }
}

interface TravelMode{
    public abstract void getToSchool();
}

class WalkMode implements TravelMode{
    public void getToSchool(){
        System.out.println("步行");
    }
}

class RidingMode implements TravelMode{
    public void getToSchool(){
        System.out.println("骑车");
    }
}

代码简述:

目的是想在学生的上学方式的方法中执行代码时有不同的输出结果,这时候知道需要利用多态
多态可以是父类指向子类,可以是接口指向实现类,可以是抽象类指向子类
这里用了函数式接口,即接口指向实现类,定义了一个接口和三个实现类,实现不同的方式
以接口类型为传入参数类型,就可以传入不同的实现类了
注意父类接口类型只能传入实现类,或者父类本类,但接口用new 接口名的形式就是创建了匿名内部类,子类参数类型
不能传入父类对象,猫是动物,但动物不一定是猫
不同的实现类代表这不同的输出结果

课本lambda表达式引入

public class Demo4 {
    public static void main(String[] args) {
        Student s = new Student();
        //接口引用指向实现类对象
        TravelMode ridingMode = new RidingMode();
        //利用多态调用了实现类的方法
        //这个方法调用的本质是ridingMode.getToSchool
        s.attendSchool(ridingMode);

        //搭顺风车
        /*
        搭顺风车在实际中可能不经常出现,但要添加这种方式的话传统方式就需要添加一个类
        这个代码可能就执行一次,所以没有创建一个类的必要,为此可以创建匿名内部类,
        直接创建了这个接口的一个实现类,而写不用写这个实现类的类型,写了接口类型的名字
        所以叫匿名内部类,=后面的就相当于这个实现类需要实现的方法,以前是
        接口类型引用变量-实现类-实现类的方法,这种模式串到一起,现在直接将接口类型引用变量-实现类方法
        绑到一起,不用创建额外创建实现类了
         */
        TravelMode freeRide = new TravelMode() {
            @Override
            public void getToSchool() {
                System.out.println("搭顺风车");
            }
        };
        s.attendSchool(freeRide);
        //匿名内部类的代码简化->lambda表达式
        TravelMode freeRide1 = ()->{
            System.out.println("搭顺风车");
        };
        s.attendSchool(freeRide1);
        


    }
}

class Student{
    //传递一个接口类型的参数对象,这个接口类型的参数对象只有一个方法
    public void attendSchool(TravelMode travelMode){
        travelMode.getToSchool();
    }
}

interface TravelMode{
    public abstract void getToSchool();
}

class WalkMode implements TravelMode{
    public void getToSchool(){
        System.out.println("步行");
    }
}

class RidingMode implements TravelMode{
    public void getToSchool(){
        System.out.println("骑车");
    }
}

代码简述: 注意看注释中的解释,新添加了匿名内部类和lambda表达式

public class Demo4 {
    public static void main(String[] args) {
        Student s = new Student();
        //接口引用指向实现类对象
        TravelMode ridingMode = new RidingMode();
        //利用多态调用了实现类的方法
        //这个方法调用的本质是ridingMode.getToSchool
        s.attendSchool(ridingMode);

        //搭顺风车
        /*
        搭顺风车在实际中可能不经常出现,但要添加这种方式的话传统方式就需要添加一个类
        这个代码可能就执行一次,所以没有创建一个类的必要,为此可以创建匿名内部类,
        直接创建了这个接口的一个实现类,而写不用写这个实现类的类型,写了接口类型的名字
        所以叫匿名内部类,=后面的就相当于这个实现类需要实现的方法,以前是
        接口类型引用变量-实现类-实现类的方法,这种模式串到一起,现在直接将接口类型引用变量-实现类方法
        绑到一起,不用创建额外创建实现类了
         */
        TravelMode freeRide = new TravelMode() {
            @Override
            public void getToSchool() {
                System.out.println("搭顺风车");
            }
        };
        s.attendSchool(freeRide);
        //匿名内部类的代码简化->lambda表达式
        TravelMode freeRide1 = ()->{
            System.out.println("搭顺风车");
        };
        s.attendSchool(freeRide1);

        /*
        将lambda表达式横着写
        TravelMode freeRide1 = ()->System.out.println("搭顺风车");
        可以这样类比:
        函数式接口TravelMode相当于数据类型
        行为变量freeRide1相当于数据变量
        Lambda表达式()->System.out.println("搭顺风车") 相当于数据值
        因此lambda表达式除了可以作为参数值还可以作为返回值
         */
        Teacher t = new Teacher();
        TravelMode freeRide2 = t.giveConvenience();
        s.attendSchool(freeRide2);
        //或者
        s.attendSchool(t.giveConvenience());
    }
}
class Teacher{
    public TravelMode giveConvenience(){
        return ()-> System.out.println("搭顺风车");
    }
}
class Student{
    //传递一个接口类型的参数对象,这个接口类型的参数对象只有一个方法
    public void attendSchool(TravelMode travelMode){
        travelMode.getToSchool();
    }
}

interface TravelMode{
    public abstract void getToSchool();
}

class WalkMode implements TravelMode{
    public void getToSchool(){
        System.out.println("步行");
    }
}

class RidingMode implements TravelMode{
    public void getToSchool(){
        System.out.println("骑车");
    }
}

代码简述: 新添加了老师类和lambda作为返回值的方式,注意看注释解释

lambda应用实例1

public class Demo {
    public static void main(String[] args) {
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println("run");
            }
        };
        new Thread(runnable).start();

        /*
        ()里面写参数,这里替代了匿名内部类,在匿名内部类中,变化的部分只有方法体
        所以这里面就只写方法体
         */
        //第一种方式,主要内容就只有参数列表和方法体了
        Runnable runnable1 = ()->{
            System.out.println("hello");
        };
        new Thread(runnable1).start();

        //第二种方式
        new Thread(()->{
            System.out.println("hello xz");
        }).start();

        //第三种方式,还可以更省,只有一条语句时,{}也可以省去
        new Thread(()-> System.out.println("hi ")).start();
    }
}

代码简述:lambda基本语法

lambda应用实例2

public class Demo2 {
    public static void main(String[] args) {
        //传统方式用匿名内部类创建比较器
        Comparator <String>comparator = new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length()-o2.length();
            }
        };
        TreeSet<String> treeSet = new TreeSet<>(comparator);
        
        //lambda表达式,这里o1,o2也不用写类型了,写类型是多余的,因为在前面泛型里面写了String
        Comparator<String> comparator1 = (o1,o2)-> o1.length()-o2.length();
        TreeSet<String> treeSet1 = new TreeSet<>(comparator1);

        //利用lambda表达式的更简单写法
        TreeSet<String> treeSet2 = new TreeSet<>((o1,o2)->o1.length()-o2.length());

        /*
        以前
        new 接口名/抽象类名(){
        方法体
        }
        代表一个接口的实现类或者一个抽象类的子类
        
        现在
        (方法体参数)->{
        方法体
        }
        用来代表一个接口的实现类或者一个抽象类的子类,只包含方法的部分,就像直接传入了一个方法
         */
    }
}

lambda表达式的作用

Lambda结合FunctionalInterface Lib, forEach, stream(),method reference等新特性可以使代码变的更加简洁! 

lambda表达式总结:

lambda引入了新的操作符:->将表达式分为了两个部分

左侧:(参数1,参数2...)表示方法的参数列表

右侧:{}内部是方法体

1.形参列表的数据类型会自动判断,因为有泛型指明

2.如果形参列表为空,只保留()

3.如果形参只有一个,()可以省略,只需要参数的名称即可

4.如果执行语句只有一句,则无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句保证只有一句

5.lambda不会生成一个单独的内部类文件

6.lambda表达式若访问了局部变量,则局部变量必须是final的,若是局部变量没有加final关键字,系统会自动添加,此后修改局部变量会报错,这一点匿名内部类的使用规则

转载一下这一篇文章更有助于自己理解

Java8 Lambda 表达式有何用处?如何使用?_weixin_33957648的博客-CSDN博客

Java8提供的函数式接口

Predicate

用于做判断,输入一个对象,得到布尔结果,下面是一个判断考试是否通过的例子

class ExamJudgementDemo{
    public static void main(String[] args) {
        //在()定义了score,判断score是否大于60
        Predicate<Integer> passExam = (Integer score) -> score>=60;
        //通过passExam.test(),并且传递了一个参数,小于60会输出未通过,反之通过
        System.out.println("考试"+(passExam.test(59)?"通过":"未通过"));
    }
}

Consumer

用于处理数据,输入一个对象,没有返回值

借鉴一下他人文章方便复习

常用的函数式接口_Consumer_weiyoo55的博客-CSDN博客_函数式接口consumer

Supplier

用于创建对象,没有输入,返回对象

这个接口Supplier中只有一个无参的方法:T.get();也就是说传入接口的类型就是返回类型

常用的函数式接口_Supplier_weiyoo55的博客-CSDN博客

Function

用于映射数据,输入一个对象,返回另一个对象,将数据的数据类型进行转换

Function(T,R)前者称为前置条件,后者称为后置条件,将T类型转为R类型

抽象方法:R apply(T t)

常用函数式接口_Function_weiyoo55的博客-CSDN博客

流式编程

什么是流水线操作

 ​编辑

 streamAPI的特点是像流水线一样操作集合,严格区分执行顺序,前一个操作的结果是后一个操作的原料,上图中起连接作用的箭头就是流

在这里最后一个流没有箭头,流在这里终止了,根据这个特点,流水线操作被分为两大类,中间操作和终端操作

中间操作操作输出流,可以继续连接其他操作,但不能输出结果

终端操作输出结果,但流被终止了不能再继续其他操作

在Java.util.stream包中的流水线操作的接口

操作类型作用
filter中间操作筛选
sorted中间操作排序
skip,limit中间操作分页
map中间操作映射
collect终端操作转换成集合
reduce终端操作数据收集

流水线操作,学校筛选学生参加编程竞赛,熟悉stream流的操作

前提

先把集合转换成流,语法:Stream 集合流 = 集合.stream;

public class Demo7 {
    public static void main(String[] args) {
        TranscriptManager transcriptManager = new TranscriptManager();
        transcriptManager.addTranscript("1","xiaozhou",80);
        transcriptManager.addTranscript("2","xiaocheng",85);
        transcriptManager.addTranscript("3","xiaogou",75);
        transcriptManager.addTranscript("4","xiaoding",95);
        transcriptManager.addTranscript("5","xiaozhu",65);
        transcriptManager.addTranscript("6","xiaoma",70);
        //System.out.println(transcriptManager.allTranscripts);
        //System.out.println(transcriptManager.allTranscripts.size());
        System.out.println(transcriptManager.getCompetitor());
        System.out.println(transcriptManager.getAverageScoreForCompetitor());
    }
}

class Transcript{
    private String studentID;
    private String studentName;
    private Integer score;

    Transcript(String id,String name,Integer score){
        this.studentID = id;
        this.studentName = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Transcript{" +
                "studentID='" + studentID + ''' +
                ", studentName='" + studentName + ''' +
                ", score=" + score +
                '}';
    }

    public String getStudentID() {
        return studentID;
    }

    public void setStudentID(String studentID) {
        this.studentID = studentID;
    }

    public String getStudentName() {
        return studentName;
    }

    public void setStudentName(String studentName) {
        this.studentName = studentName;
    }

    public Integer getScore() {
        return score;
    }

    public void setScore(Integer score) {
        this.score = score;
    }
}

class TranscriptManager{
     Set<Transcript> allTranscripts = new HashSet<Transcript>();
    //收集成绩单
    public void addTranscript(String id,String name,Integer score) {
        //将各个学生的成绩单对象收集到集合里面
        this.allTranscripts.add(new Transcript(id, name, score));
    }

        public List<String> getCompetitor () {
            //执行:筛选,排序,分页,取出名字和学号
            //将收集各个成绩单的集合转换成流
            Stream<Transcript> transcriptStream = this.allTranscripts.stream();
            //使用filter()实现筛选
        /*
        filter在包下的定义
        Stream <T> filter(Predicate<? super T> predicate)
        传入参数的类型是predicate的函数式接口,可以传入一个lambda表达式
         */

            //transcriptStream.filter(((Transcript t)->t.getScore()>=80));

            //使用sorted实现排序
        /*
        sorted在包下的定义
        Stream<T> sorted (Comparator<? super T> comparator)
        传入参数类型是Comparator的函数式接口,可以传入一个lambda表达式
        下面对刚才进行筛选国的成绩单进行排序
         */

        /*transcriptStream
                .filter((Transcript t)->t.getScore()>=80)
                .sorted((Transcript t1,Transcript t2)->t1.getScore()-t2.getScore());*/

            //使用skip和limit实现分页
        /*
        skip()用于跳过前m个元素,limit()用于取出连续的n个元素
        它们在包中的定义
        Stream<T> skip(long n)
        Stream<T> limit(long maxsize)
        传入参数是为long的整数,分别指定要跳过和取出的元素数量
        下面取第11名到50名的成绩单
         */

        /*transcriptStream
                .filter((Transcript t)->t.getScore()>=80)
                .sorted((Transcript t1,Transcript t2)->t1.getScore()-t2.getScore())
                .skip(10).limit(40);*/

            //map()实现映射
        /*
        map()在包中的定义
        <R> Stream<R> map(Function<? super T,?extends R> mapper);
        传入参数类型是为Function的函数式接口,可以传入lambda表达式
        下面将处理完的成绩单映射成对应学生的姓名和学号
         */
        /*
        transcriptStream
                .filter((Transcript t)->t.getScore()>=80)
                .sorted((Transcript t1,Transcript t2)->t1.getScore()-t2.getScore())
                .skip(10).limit(40)
                .map((Transcript t)->t.getStudentName()+":" + t.getStudentID());
        */
            //collect()获得结果
        /*
        collect()方法将流转成集合,以获得结果,理解为加工环节完成,可以下生产线出产品了
        在包中的定义
        <R,A>R collect(Collector<? super T,A,R> collcrtor;
        传入参数类型是COllector类型的函数式接口,可以传入Collectors类的静态方法
        常用方法Collectors.toList()
        Collectors.toSet()
        Collectors.toMap()
         */
            List<String> competitorList
                    =  transcriptStream
                    .filter((Transcript t) -> t.getScore() >= 80)
                    .sorted((Transcript t1, Transcript t2) -> t1.getScore() - t2.getScore())
                    .skip(0).limit(3)
                    .map((Transcript t) -> t.getStudentName() + ":" + t.getStudentID())
                    .collect(Collectors.toList());
            return competitorList;
        }

        public int getAverageScoreForCompetitor () {
            //执行:筛选,排序,分页,计算平均分

            //这里介绍reduce()实现统计
        /*
        实现数据收集,指集合中任意两个元素做何种数学运算,这里要算出参赛者平均分
        要先计算总分,再除参赛者人数
        reduce() 第一个参数是收集器的初始值,第二个行为参数描述了收集方式:集合中的元素两两相加
         */
            Stream<Transcript> transcriptStream = this.allTranscripts.stream();
            int total = transcriptStream
                    .filter((Transcript t) -> t.getScore() >= 80)
                    .sorted((Transcript t1, Transcript t2) -> t1.getScore() - t2.getScore())
                    .skip(0).limit(3)
                    .map((Transcript t) -> t.getScore())
                    .reduce(0, (Integer a, Integer b) -> a + b);
            int count = this.getCompetitor().size();
            int average = total / count;
            return average;
        }

}

代码简述: 看注释看注释! 

使用并行流发挥多核优势

Stream API提供parallel()方法将串行流转换为并行流

串行是指集合中元素要排队逐个操作

并行是指集合中的元素可以同时操作,显然并行操作比串行操作花费的时间更少,但并行能力受到CPU核心数量的限制

除了计算机底层实现的区别,并行流和串行流在使用上没有任何分别

语法:Stream并行流 = 串行流.parallel();

之前的成绩单对象转为流的方式:

Stream. transcriptStream = this.allTranscripts.stream().parallel();

其他部分的代码都无需变化,Java会自动利用cpu的多核心优势,使程序得到提高。


 以上是课本内容的整理,以下是网上学习内容整理


什么是Stream

Stream是Java8中处理数组,集合的抽象概念,它可以指定你希望的集合进行的操作,可以执行非常复杂的查找,过滤映射数据等操作,使用Stream对集合数据进行操作,就类似于使用SQL执行的数据查询

Stream的特点

1.Stream自己不会存储元素

2.Stream不会改变源对象,相反他们会返回一个持有结果的新Stream

3.Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行

Stream遵循“做什么,而不是怎么去做”的原则,只需要描述还需要做什么,而不需要考虑程序怎样实现

//传统方式,查询集合中大于3的字符串的个数
        List<String> list = new ArrayList<>();
        list.add("a");
        list.add("baaaaa");
        list.add("cccccc");
        int count = 0;
        for(String s:list){
            if(s.length()>3){
                count++;
            }
        }
        System.out.println(count);

        //流式编程(链式编程)
        //Stream的方式,用stream去查询,filter过滤,s代表每一个对象,长度大于3的对象就数一次
        //s->s.length()>3是lambda表达式
        //stream和filter都是Stream接口类型
        long count1 = list.stream().filter(s -> s.length() > 3).count();
        System.out.println(count1);

使用Stream的步骤

1.创建一个Stream (创建)

2.在一个或多个步骤中,将初始Stream的中间操作,如上面例子中stream().filter (中间操作)

3.使用一个终止操作来产生一个结果,该操作会强制它之前的参翅操作立即执行,在这之后,该Stream就不会再被使用了(终止操作)

创建Stream的方式

\