Lambda 表达式

news2024/11/28 18:41:23

1. Lambda 表达式

1.1 通过接口传递代码

针对接口而非具体类型进行编程,可以降低程序的耦合性,提高灵活性,提高复用性。接口常被用于传递代码,比如,我们知道 File 有如下方法:

        public File[] listFiles(FilenameFilter filter)

listFiles需要的其实不是FilenameFilter对象,而是它包含的如下方法:

        boolean accept(File dir, String name);

或者说,listFiles希望接受一段方法代码作为参数,但没有办法直接传递这个方法代码本身,只能传递一个接口。

再如,类Collections中的很多方法都接受一个参数Comparator,比如:

        public static <T> void sort(List<T> list, Comparator<? super T> c)

它们需要的也不是Comparator对象,而是它包含的如下方法:

        int compare(T o1, T o2);

但是,没有办法直接传递方法,只能传递一个接口。

Callable和Runnable接口也用于传递任务代码。

通过接口传递行为代码,就要传递一个实现了该接口的实例对象,在之前,最简洁的方式是使用匿名内部类,比如:

        //列出当前目录下的所有扩展名为.txt的文件
        File f = new File(".");
        File[] files = f.listFiles(new FilenameFilter(){
            @Override
            public boolean accept(File dir, String name) {
                if(name.endsWith(".txt")){
                    return true;
                }
                return false;
            }
        });

将files按照文件名排序,代码为:

        Arrays.sort(files, new Comparator<File>() {
            @Override
            public int compare(File f1, File f2) {
                return f1.getName().compareTo(f2.getName());
            }
        });

1.2 Lambda 语法

Java 8 提供了一种新的紧凑的传递代码的语法:Lambda表达式。

        File f = new File(".");
        File[] files = f.listFiles((File dir, String name) -> {
            if(name.endsWith(".txt")) {
                return true;
            }
            return false;
        });

可以看出,相比匿名内部类,传递代码变得更为直观,不再有实现接口的模板代码,不再声明方法,也没有名字,而是直接给出了方法的实现代码。Lambda表达式由->分隔为两部分,前面是方法的参数,后面{}内是方法的代码上面的代码可以简化为:

        File[] files = f.listFiles((File dir, String name) -> {
            return name.endsWith(".txt");
        });

当主体代码只有一条语句的时候,括号和return语句也可以省略,上面的代码可以变为:

        File[] files = f.listFiles((File dir, String name) -> name.endsWith(".txt"));

注意:没有括号的时候,主体代码是一个表达式,这个表达式的值就是函数的返回值,结尾不能加分号,也不能加return语句。方法的参数类型声明也可以省略,上面的代码还可以继续简化为:

        File[] files = f.listFiles((dir, name) -> name.endsWith(".txt"));

之所以可以省略方法的参数类型,是因为Java可以自动推断出来,它知道listFiles接受的参数类型是FilenameFilter,这个接口只有一个方法accept,这个方法的两个参数类型分别是File和String。

排序的代码用Lambda表达式可以写为:

        Arrays.sort(files, (f1, f2) -> f1.getName().compareTo(f2.getName()));

参数只有一个的时候,参数部分的括号可以省略。比如,File还有如下方法:

        public File[] listFiles(FileFilter filter)

FileFilter的定义为:

        public interface FileFilter {
            boolean accept(File pathname);
        }

使用FileFilter重写上面的列举文件的例子,代码可以为:

        File[] files = f.listFiles(path -> path.getName().endsWith(".txt"));

与匿名内部类类似,Lambda表达式也可以访问定义在主体代码外部的变量,但对于局部变量,它也只能访问final类型的变量,与匿名内部类的区别是,它不要求变量声明为final,但变量事实上不能被重新赋值。

Java会将msg的值作为参数传递给Lambda表达式,为Lambda表达式建立一个副本,它的代码访问的是这个副本,而不是外部声明的msg变量。

Lambda表达式与匿名内部类很像,主要就是简化了语法,但它不是语法糖,内部实现不是内部类而是函数式接口。Java会为每个匿名内部类生成一个类,但Lambda表达式不会。

//普通方式1--------------------------------
class MThread implements Runnable {
    @Override
    public void run() {
        System.out.println("Runnable------1-------------");
    }
}

public class Test {
    public static void main(String[] args) {
        //方式1
        MThread mThread = new MThread();
        Thread thread = new Thread(mThread);
        thread.setName("线程1");
        thread.start();
    }

}

//匿名内部类 方式2--------------------------------
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Runnable------2-------------");
            }
        }, "线程2").start();


//lambda表达式 方式3--------------------------------
        new Thread(() -> System.out.println("Runnable------3-------------"),"线程3").start();

1.3 函数式接口

Java 8 引入了函数式接口的概念,函数式接口也是接口,但只能有一个抽象方法,前面提及的接口都只有一个抽象方法,都是函数式接口。之所以强调是“抽象”方法,是因为Java 8中还允许定义静态方法和默认方法。Lambda表达式可以赋值给函数式接口,比如:

        FileFilter filter = path -> path.getName().endsWith(".txt");

        FilenameFilter fileNameFilter = (dir, name) -> name.endsWith(".txt");

        Comparator<File> comparator = (f1, f2) ->
                            f1.getName().compareTo(f2.getName());

        Runnable task = () -> System.out.println("hello world");

如果看这些接口的定义,会发现它们都有一个注解@FunctionalInterface,比如:

 @FunctionalInterface用于清晰地告知使用者这是一个函数式接口,不过,这个注解不是必需的,不加,只要只有一个抽象方法,也是函数式接口。但如果加了,而又定义了超过一个抽象方法,Java编译器会报错,这类似于我们之前介绍的Override注解。

1.4 预定义的函数式接口

Java 8定义了大量的预定义函数式接口,用于常见类型的代码传递,这些函数定义在包java.util.function下,主要接口如下:

 对于基本类型boolean、int、long和double,为避免装箱/拆箱,Java 8提供了一些专门的函数,比如,int相关的部分函数如下:

这些函数有什么用呢?它们被大量用于Java 8的函数式数据处理Stream相关的类中,即使不使用Stream,也可以在自己的代码中直接使用这些预定义的函数。

Function 示例

为便于举例,我们先定义一个简单的学生类Student,它有name和score两个属性,如下所示。

        static class Student {
            String name;
            double score;
        }

我们省略了构造方法和getter/setter方法。有一个学生列表:

        List<Student> students = Arrays.asList(new Student[] {
                new Student("zhangsan", 89d), new Student("lisi", 89d),
                new Student("wangwu", 98d) });

列表处理的一个常见需求是转换。

比如,给定一个学生列表,需要返回名称列表,或者将名称转换为大写返回,可以借助Function写一个通用的方法,如下所示:

        public static <T, R> List<R> map(List<T> list, Function<T, R> mapper) {
            List<R> retList = new ArrayList<>(list.size());
            for(T e : list) {
                retList.add(mapper.apply(e));
            }
            return retList;
        }

根据学生列表返回名称列表的代码为:

        List<String> names = map(students, t -> t.getName());

将学生名称转换为大写的代码为:

        students = map(students, t -> new Student(
            t.getName().toUpperCase(), t.getScore()));

以上示例主要用于演示函数式接口的基本概念,实际中可以直接使用流API。

1.5 方法引用

Lambda 表达式经常用于调用对象的某个方法,比如:

        List<String> names = map(students, t -> t.getName());

这时,它可以进一步简化,如下所示:

        List<String> names = map(students, Student::getName);

Student::getName 这种写法是 Java 8 引入的一种新语法,称为方法引用。它是 Lambda 表达式的一种简写方法,由 ::分隔为两部分,前面是类名或变量名,后面是方法名。方法可以是实例方法,也可以是静态方法,但含义不同。

Lambda 方法引用举例

还是以Student为例,先增加一个静态方法:

        public static String getCollegeName(){
            return "Laoma School";
        }

对于静态方法,如下两条语句是等价的:

        1. Supplier<String> s = Student::getCollegeName;
        2. Supplier<String> s = () -> Student.getCollegeName();

它们的参数都是空,返回类型为String。

对于实例方法,它的第一个参数就是该类型的实例,比如,如下两条语句是等价的:

        1. Function<Student, String> f = Student::getName;
        2. Function<Student, String> f = (Student t) -> t.getName();

对于Student::setName,它是一个BiConsumer,即如下两条语句是等价的:

        1. BiConsumer<Student, String> c = Student::setName;
        2. BiConsumer<Student, String> c = (t, name) -> t.setName(name);

如果方法引用的第一部分是变量名,则相当于调用那个对象的方法。比如,假定t是一个Student类型的变量,则如下两条语句是等价的:

        1. Supplier<String> s = t::getName;
        2. Supplier<String> s = () -> t.getName();

下面两条语句也是等价的:

        1. Consumer<String> consumer = t::setName;
        2. Consumer<String> consumer = (name) -> t.setName(name);

对于构造方法,方法引用的语法是<类名>::new,如Student::new,即下面两条语句等价:

        1. BiFunction<String, Double, Student> s = (name, score) -> new Student(name, score);
        2. BiFunction<String, Double, Student> s = Student::new;

1.6 函数的复合

函数式接口和Lambda表达式还可用作方法的返回值,传递代码回调用者,将这两种用法结合起来,可以构造复合的函数,使程序简洁易读。

Comparator 中的复合方法

Comparator接口定义了如下静态方法:

        public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
                Function<? super T, ? extends U> keyExtractor) {
            Objects.requireNonNull(keyExtractor);
            return (Comparator<T> & Serializable)
                (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
        }

这个方法是什么意思呢?它用于构建一个Comparator,比如,在前面的例子中,对文件按照文件名排序的代码为:

        Arrays.sort(files, (f1, f2) -> f1.getName().compareTo(f2.getName()));

使用comparing方法,代码可以简化为:

        Arrays.sort(files, Comparator.comparing(File::getName));

这样,代码的可读性就大大增强了。

Comparator还有很多默认方法,我们看两个:

        default Comparator<T> reversed() {
            return Collections.reverseOrder(this);
        }
        default Comparator<T> thenComparing(Comparator<? super T> other) {
            Objects.requireNonNull(other);
            return (Comparator<T> & Serializable) (c1, c2) -> {
                int res = compare(c1, c2);
                return (res ! = 0) ? res : other.compare(c1, c2);
            };
        }

reversed返回一个新的Comparator,按原排序逆序排。thenComparing也返回一个新的Comparator,在原排序认为两个元素排序相同的时候,使用传递的Comparator other进行比较。

看一个使用的例子,将学生列表按照分数倒序排(高分在前),分数一样的按照名字进行排序:

        students.sort(Comparator.comparing(Student::getScore)
                                  .reversed()
                                  .thenComparing(Student::getName));

2. 函数式数据处理:基本用法

Java 8 引入了一套新的类库,位于包 java.util.stream 下,称为 Stream API。

接口Stream类似于一个迭代器,但提供了更为丰富的操作,Stream API的主要操作就定义在该接口中。Java 8给Collection接口增加了两个默认方法,它们可以返回一个Stream,如下所示:

        default Stream<E> stream() {
            return StreamSupport.stream(spliterator(), false);
        }

        default Stream<E> parallelStream() {
            return StreamSupport.stream(spliterator(), true);
        }

stream()返回的是一个顺序流,parallelStream()返回的是一个并行流。顺序流就是由一个线程执行操作。而并行流背后可能有多个线程并行执行,与之前介绍的并发技术不同,使用并行流不需要显式管理线程,使用方法与顺序流是一样的。

2.1 基本示例

1)基本过滤

返回学生列表中90分以上的,传统上的代码一般是这样:

        List<Student> above90List = new ArrayList<>();
        for(Student t : students) {
            if(t.getScore() > 90) {
                above90List.add(t);
            }
        }

使用Stream API,代码可以这样:

        List<Student> above90List = students.stream()
                .filter(t->t.getScore()>90)
                .collect(Collectors.toList());

先通过stream()得到一个Stream对象,然后调用Stream上的方法,filter()过滤得到90分以上的,它的返回值依然是一个Stream,为了转换为List,调用了collect方法并传递了一个Collectors.toList(),表示将结果收集到一个List中。

2)基本转换

根据学生列表返回名称列表,传统上的代码一般是这样:

        List<String> nameList = new ArrayList<>(students.size());
        for(Student t : students) {
            nameList.add(t.getName());
        }

使用Stream API,代码可以这样:

        List<String> nameList = students.stream()
                .map(Student::getName).collect(Collectors.toList());

这里使用了 Stream 的 map 函数,它的参数是一个 Function 函数式接口,这里传递了方法引用。

3)基本的过滤和转换组合

返回90分以上的学生名称列表,传统上的代码一般是这样:

        List<String> nameList = new ArrayList<>();

        for(Student t : students) {
            if(t.getScore() > 90) {
                nameList.add(t.getName());
            }
        }

使用Stream API,可以将基本函数filter()和map()结合起来,代码可以这样:

        List<String> above90Names = students.stream()
                .filter(t->t.getScore()>90).map(Student::getName)
                .collect(Collectors.toList());

这种组合利用基本函数、声明式实现集合数据处理功能的编程风格,就是函数式数据处理。

调用 filter() 和 map() 不会执行任何实际的操作,它们只是在构建操作的流水线,调用 collect 才会触发实际的遍历执行,在一次遍历中完成过滤、转换以及收集结果的任务。所以不用担心它的性能问题。

像 filter 和 map 这种不实际触发执行、用于构建流水线、返回 Stream 的操作称为中间操作(intermediate operation),而像 collect 这种触发实际执行、返回具体结果的操作称为终端操作(terminal operation)

2.2 中间操作

除了 filter 和 map, Stream API 的中间操作还有 distinct、sorted、skip、limit、peek、mapToLong、mapToInt、mapToDouble、flatMap 等。

1)distinct

distinct返回一个新的Stream,过滤重复的元素,只留下唯一的元素,是否重复是根据equals方法来比较的,distinct可以与其他函数(如filter、map)结合使用。比如,返回字符串列表中长度小于3的字符串、转换为小写、只保留唯一的,代码可以为:

        List<String> list = Arrays.asList(new String[]{"abc", "def", "hello", "Abc"});

        List<String> retList = list.stream()
                .filter(s->s.length()<=3).map(String::toLowerCase).distinct()
                .collect(Collectors.toList());

虽然都是中间操作,但distinct与filter和map是不同的。filter和map都是无状态的,对于流中的每一个元素,处理都是独立的,处理后即交给流水线中的下一个操作;distinct不同,它是有状态的,在处理过程中,它需要在内部记录之前出现过的元素,如果已经出现过,即重复元素,它就会过滤掉,不传递给流水线中的下一个操作。对于顺序流,内部实现时,distinct操作会使用HashSet记录出现过的元素,如果流是有顺序的,需要保留顺序,会使用LinkedHashSet。

2)sorted

有两个sorted方法:

        Stream<T> sorted()
        Stream<T> sorted(Comparator<? super T> comparator)

它们都对流中的元素排序,都返回一个排序后的Stream。第一个方法假定元素实现了Comparable接口,第二个方法接受一个自定义的Comparator。比如,过滤得到90分以上的学生,然后按分数从高到低排序,分数一样的按名称排序,代码为:

        List<Student> studentList = students.stream()
                .filter(t -> t.getScore() > 90)
                .sorted(Comparator.comparing(Student::getScore)
                        .reversed()
                        .thenComparing(Student::getName))
                .collect(Collectors.toList());

这里,使用了Comparator的comparing、reversed和thenComparing构建了Comparator。

与distinct一样,sorted也是一个有状态的中间操作,在处理过程中,需要在内部记录出现过的元素。其不同是,每碰到流中的一个元素,distinct都能立即做出处理,要么过滤,要么马上传递给下一个操作;sorted需要先排序,为了排序,它需要先在内部数组中保存碰到的每一个元素,到流结尾时再对数组排序,然后再将排序后的元素逐个传递给流水线中的下一个操作。

3)skip / limit

它们的定义为:

        Stream<T> skip(long n)
        Stream<T> limit(long maxSize)

skip跳过流中的n个元素,如果流中元素不足n个,返回一个空流,limit限制流的长度为maxSize。比如,将学生列表按照分数排序,返回第3名到第5名,代码为:

        List<Student> list = students.stream()
                .sorted(Comparator.comparing(Student::getScore)
                        .reversed())
                .skip(2)
                .limit(3)
                .collect(Collectors.toList());

skip和limit都是有状态的中间操作。对前n个元素,skip的操作就是过滤,对后面的元素,skip就是传递给流水线中的下一个操作。limit的一个特点是:它不需要处理流中的所有元素,只要处理的元素个数达到maxSize,后面的元素就不需要处理了,这种可以提前结束的操作称为短路操作

skip 和 limit 只能根据元素数目进行操作,Java 9 增加了两个新方法,相当于更为通用的 skip 和 limit:

        //通用的skip,在谓词返回为true的情况下一直进行skip操作,直到某次返回false
        default Stream<T> dropWhile(Predicate<? super T> predicate)

        //通用的limit,在谓词返回为true的情况下一直接受,直到某次返回false
        default Stream<T> takeWhile(Predicate<? super T> predicate)

4)peek

peek的定义为:

        Stream<T> peek(Consumer<? super T> action)

它返回的流与之前的流是一样的,没有变化,但它提供了一个Consumer,会将流中的每一个元素传给该Consumer。这个方法的主要目的是支持调试,可以使用该方法观察在流水线中流转的元素,比如:

        List<String> above90Names = students.stream()
                .filter(t -> t.getScore() > 90)
                .peek(System.out::println)
                .map(Student::getName)
                .collect(Collectors.toList());

        System.out.println("above90Names结果:" + above90Names);

打印结果:
Student(name=liHua, score=100.0)
Student(name=wangwu, score=98.0)
above90Names结果:[liHua, wangwu]

5)mapToLong / mapToInt / mapToDouble

map函数接受的参数是一个Function<T, R>,为避免装箱/拆箱,提高性能,Stream 还有如下返回基本类型特定流的方法:

        DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
        IntStream mapToInt(ToIntFunction<? super T> mapper)
        LongStream mapToLong(ToLongFunction<? super T> mapper)

DoubleStream / IntStream / LongStream 是基本类型特定的流,有一些专门的更为高效的方法。比如,求学生列表的分数总和,代码为:

        double sum = students.stream().mapToDouble(Student::getScore).sum();

6)flatMap

flatMap 的定义为:

        <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

它接受一个函数 mapper,对流中的每一个元素,mapper 会将该元素转换为一个流 Stream,然后把新生成流的每一个元素传递给下一个操作。比如:

        List<String> lines = Arrays.asList(new String[]{"hello abc", "Janet  Ruby"});

        List<String> words = lines.stream()
                .flatMap(line -> Arrays.stream(line.split("\\s+")))
                .collect(Collectors.toList());

        System.out.println(words); //[hello, abc, Janet, Ruby]

这里的 mappe r将一行字符串按空白符分隔为了一个单词流,Arrays.stream可以将一个数组转换为一个流,输出为:

[hello, abc, Janet, Ruby]

可以看出,实际上,flatMap完成了一个1到n的映射。

2.3 终端操作

中间操作不触发实际的执行,返回值是Stream,而终端操作触发执行,返回一个具体的值,除了collect, Stream API的终端操作还有max、min、count、allMatch、anyMatch、noneMatch、findFirst、findAny、forEach、toArray、reduce 等。

1)max / min

max / min 的定义为:

        Optional<T> max(Comparator<? super T> comparator)
        Optional<T> min(Comparator<? super T> comparator)

它们返回流中的最大值/最小值,它们的返回值类型是Optional<T>,而不是T。

java.util.Optional是Java 8引入的一个新类,它是一个泛型容器类,内部只有一个类型为T的单一变量value,可能为null,也可能不为null。Optional有什么用呢?它用于准确地传递程序的语义,它清楚地表明,其代表的值可能为null,程序员应该进行适当的处理。

Optional定义了一些方法,比如:

        //value不为null时返回true
        public boolean isPresent()

        //返回实际的值,如果为null,抛出异常NoSuchElementException
        public T get()

        //如果value不为null,返回value,否则返回other
        public T orElse(T other)

        //构建一个空的Optional, value为null
        public static<T> Optional<T> empty()

        //构建一个非空的Optional, 参数value不能为null
        public static <T> Optional<T> of(T value)

        //构建一个Optional,参数value可以为null,也可以不为null
        public static <T> Optional<T> ofNullable(T value)

在max/min的例子中,通过声明返回值为Optional,我们可以知道具体的返回值不一定存在,这发生在流中不含任何元素的情况下。

看个简单的例子,返回分数最高的学生,代码为:

        //  这里,假定students不为空。
        Student student = students.stream()
                .max(Comparator.comparing(Student::getScore)
                        .reversed())
                .get();

2)count

count很简单,就是返回流中元素的个数。比如,统计大于90分的学生个数,代码为:

        long above90Count = students.stream().filter(t -> t.getScore() > 90).count();

3)allMatch/anyMatch/noneMatch

这几个函数都接受一个谓词Predicate,返回一个boolean值,用于判定流中的元素是否满足一定的条件。它们的区别是:

  • allMatch:只有在流中所有元素都满足条件的情况下才返回true。
  • anyMatch:只要流中有一个元素满足条件就返回true。
  • noneMatch:只有流中所有元素都不满足条件才返回true。

如果流为空,那么这几个函数的返回值都是true。

比如,判断是不是所有学生都及格了(不小于60分),代码可以为:

        boolean allPass = students.stream().allMatch(t -> t.getScore() >= 60);

这几个操作都是短路操作,不一定需要处理所有元素就能得出结果,比如,对于all-Match,只要有一个元素不满足条件,就能返回false。

4)findFirst / findAny

它们的定义为:

        Optional<T> findFirst()
        Optional<T> findAny()

它们的返回类型都是Optional,如果流为空,返回Optional.empty()。findFirst返回第一个元素,而findAny返回任一元素,它们都是短路操作。随便找一个不及格的学生,代码可以为:

        Optional<Student> failStudent = students.stream()
                .filter(t -> t.getScore() < 60)
                .findAny();
        if (failStudent.isPresent()) {
            //处理不及格的学生
           
        }

5)forEach

有两个 forEach 方法:

        void forEach(Consumer<? super T> action)
        void forEachOrdered(Consumer<? super T> action)

它们都接受一个Consumer,对流中的每一个元素,传递元素给Consumer。区别在于:在并行流中,forEach不保证处理的顺序,而forEachOrdered会保证按照流中元素的出现顺序进行处理。

比如,逐行打印大于90分的学生,代码可以为:

        students.stream().filter(t -> t.getScore() > 90).forEach(System.out::println);

6)toArray

toArray 将流转换为数组,有两个方法:

        Object[] toArray()
        <A> A[] toArray(IntFunction<A[]> generator)

不带参数的toArray返回的数组类型为Object[],这通常不是期望的结果,如果希望得到正确类型的数组,需要传递一个类型为IntFunction的generator。IntFunction的定义为:

        public interface IntFunction<R> {
            R apply(int value);
        }

generator接受的参数是流的元素个数,它应该返回对应大小的正确类型的数组。

比如,获取90分以上的学生数组,代码可以为:

        Student[] above90Arr = students.stream()
                .filter(t -> t.getScore() > 90)
                .toArray(Student[]::new);

Student[]::new 就是一个类型为 IntFunction<Student[]>的generator。

7)reduce

reduce代表归约或者叫折叠,它是 max/min/count 的更为通用的函数,将流中的元素归约为一个值。有三个reduce函数:

        Optional<T> reduce(BinaryOperator<T> accumulator);

        T reduce(T identity, BinaryOperator<T> accumulator);

        <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);

第一个reduce函数基本等同于调用:

        boolean foundAny = false;
        T result = null;
        for(T element : this stream) {
            if(! foundAny) {
                foundAny = true;
                result = element;
            }
            else
                result = accumulator.apply(result, element);
        }
        return foundAny ? Optional.of(result) : Optional.empty();

比如,使用 reduce 函数求分数最高的学生,代码可以为:

        Student topStudent = students.stream()
                .reduce((accu, t) -> {
                    if (accu.getScore() >= t.getScore()) {
                        return accu;
                    } else {
                        return t;
                    }
                })
                .get();

第二个reduce函数多了一个identity参数,表示初始值,它基本等同于调用:

        T result = identity;
        for(T element : this stream)
            result = accumulator.apply(result, element)
        return result;

第一个和第二个reduce函数的返回类型只能是流中元素的类型,而第三个reduce函数更为通用,它的归约类型可以自定义,另外,它多了一个combiner参数。combiner用在并行流中,用于合并子线程的结果。对于顺序流,它基本等同于调用:

        U result = identity;
        for(T element : this stream)
            result = accumulator.apply(result, element)
        return result;

注意与第二个reduce函数相区分,它的结果类型不是T,而是U。

比如,使用reduce函数计算学生分数的和,代码可以为:

        double sumScore = students.stream().reduce(0d,
                (sum, t) -> sum += t.getScore(),
                (sum1, sum2) -> sum1 += sum2
        );

从以上可以看出,reduce 函数虽然更为通用,但比较费解,难以使用,一般情况下应该优先使用其他函数。collect函数比reduce函数更为通用、强大和易用。

2.4 构建流

前面我们主要使用的是Collection的stream方法,换做parallelStream方法,就会使用并行流,接口方法都是通用的。但并行流内部会使用多线程,线程个数一般与系统的CPU核数一样,以充分利用CPU的计算能力。

进一步来说,并行流内部会使用Java 7引入的fork/join框架,即处理由fork和join两个阶段组成,fork就是将要处理的数据拆分为小块,多线程按小块进行并行计算,join就是将小块的计算结果进行合并。使用并行流,不需要任何线程管理的代码,就能实现并行。

除了通过Collection接口的stream/parallelStream获取流,还有一些其他方式可以获取流。Arrays有一些stream方法,可以将数组或子数组转换为流,比如:

        public static IntStream stream(int[] array)
        
        public static DoubleStream stream(double[] array, int startInclusive, int endExclusive)
        
        public static <T> Stream<T> stream(T[] array)

输出当前目录下所有普通文件的名字,代码可以为:

        File[] files = new File(".").listFiles();
        Arrays.stream(files).filter(File::isFile).map(File::getName)
                .forEach(System.out::println);

Stream也有一些静态方法,可以构建流,比如:

        //返回一个空流
        public static<T> Stream<T> empty()

        //返回只包含一个元素t的流
        public static<T> Stream<T> of(T t)

        //返回包含多个元素values的流
        public static<T> Stream<T> of(T... values)

        //通过Supplier生成流,流的元素个数是无限的
        public static<T> Stream<T> generate(Supplier<T> s)

        //同样生成无限流,第一个元素为seed,第二个为f(seed),第三个为f(f(seed)),以此类推
        public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

输出10个随机数,代码可以为:

        Stream.generate(()->Math.random()).limit(10).forEach(System.out::println);

输出100个递增的奇数,代码可以为:

        Stream.iterate(1, t->t+2).limit(100).forEach(System.out::println);

3. 函数式数据处理:强大方便的收集器

对于 collect 方法,前面只是演示了其最基本的应用,它还有很多强大的功能,比如,可以分组统计汇总,实现类似数据库查询语言 SQL 中的 group by 功能。

在上节中,过滤得到90分以上的学生列表,代码是这样的:

        List<Student> above90List = students.stream().filter(t->t.getScore()>90)
                .collect(Collectors.toList());

collect(Collectors.toList()) 背后的伪代码如下所示:

        List<T> container = new ArrayList<>();
        for(T t : data)
            container.add(t);
        return container;

3.1 容器收集器

toList 类似的容器收集器还有 toSettoCollectiontoMap

1)toSet

toSet 的使用与 toList 类似,只是它可以排重。

toList 背后的容器是 ArrayList,toSet 背后的容器是 HashSet。

2)toCollection

toCollection 是一个通用的容器收集器,可以用于任何 Collection 接口的实现类,它接受一个工厂方法 Supplier 作为参数。

比如,如果希望排重但又希望保留出现的顺序,可以使用 LinkedHashSet, Collector 可以这么创建:

        Collectors.toCollection(LinkedHashSet::new)

3)toMap

toMap将元素流转换为一个Map,我们知道,Map有键和值两部分,toMap至少需要两个函数参数,一个将元素转换为键,另一个将元素转换为值。

举例,将学生流转换为学生名称和分数的Map,代码可以为:

        Map<String, Double> nameScoreMap = students.stream().collect(
                Collectors.toMap(Student::getName, Student::getScore));

这里,Student::getName 是 keyMapper, Student::getScore 是 valueMapper。

再比如,假定Student的主键是id,希望转换学生流为学生id和学生对象的Map,代码可以为:

        Map<String, Student> byIdMap = students.stream().collect(
                Collectors.toMap(Student::getId, t -> t));

t->t是valueMapper,表示值就是元素本身。这个函数用得比较多,接口Function定义了一个静态函数identity表示它。也就是说,上面的代码可以替换为:

        Map<String, Student> byIdMap = students.stream().collect(
                Collectors.toMap(Student::getId, Function.identity()));

上面的toMap假定元素的键不能重复,如果有重复的,会抛出异常。

比如 希望得到字符串与其长度的Map,但由于包含重复字符串"abc",程序会抛出异常。这种情况下,我们希望的是程序忽略后面重复出现的元素,这时,可以使用另一个toMap函数(相比前面的toMap,它接受一个额外的参数mergeFunction,它用于处理冲突):

        Map<String, Integer> strLenMap = Stream.of("abc", "hello", "abc").collect(
                Collectors.toMap(Function.identity(), t->t.length(), (oldValue, value)->value));

有时,我们可能希望合并新值与旧值,比如一个联系人列表,对于相同的联系人,我们希望合并电话号码,mergeFunction可以定义为:

        BinaryOperator<String> mergeFunction = (oldPhone, phone)->oldPhone+", "+phone;

3.2 字符串收集器

除了将元素流收集到容器中,另一个常见的操作是收集为一个字符串。比如,获取所有的学生名称,用逗号连接起来,传统上代码看上去像这样:

        StringBuilder sb = new StringBuilder();
        for(Student t : students){
            if(sb.length()>0){
                sb.append(", ");
            }
            sb.append(t.getName());
        }
        return sb.toString();

针对这种常见的需求,Collectors提供了joining收集器,比如:

        public static Collector<CharSequence, ? , String> joining()

        public static Collector<CharSequence, ? , String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix)

第一个就是简单地把元素连接起来,第二个支持一个分隔符,还可以给整个结果字符串加前缀和后缀,比如:

        String result = Stream.of("Janet", "Ruby", "Leo")
                .collect(Collectors.joining(", ", "[", "]"));
        System.out.println(result); //[Janet, Ruby, Leo]

        String result1 = students.stream().map(t -> t.getName()).collect(Collectors.joining());
        System.out.println(result1); //liHuawangwulisizhangsanXiaoMei

        String result2 = students.stream().map(Student::getName).collect(Collectors.joining(",", "[", "]"));
        System.out.println(result2); //[liHua,wangwu,lisi,zhangsan,XiaoMei]

3.3 分组

分组类似于数据库查询语言SQL中的group by语句,它将元素流中的每个元素分到一个组,可以针对分组再进行处理和收集。

为便于举例,我们先修改下学生类Student,增加一个字段grade表示年级:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private String name; //名字

    private double score; //分数

    private String grade; //年级

    public static String getCollegeName() {
        return "Laoma School";
    }
}

1)基本用法

将学生流按照年级进行分组,代码为:

        Map<String, List<Student>> groups = students.stream()
                .collect(Collectors.groupingBy(Student::getGrade));
        
        System.out.println(groups); //{1=[Student(name=zhangsan, score=89.0, grade=1), Student(name=lisi, score=89.0, grade=1)], 2=[Student(name=liHua, score=100.0, grade=2), Student(name=wangwu, score=98.0, grade=2)], 3=[Student(name=XiaoMei, score=59.0, grade=3)]}

代码等同于:

        Map<String, List<Student>> groups = new HashMap<>();
        for(Student t : students) {
            String key = t.getGrade();
            List<Student> container = groups.get(key);
            if(container == null) {
                container = new ArrayList<>();
                groups.put(key, container);
            }
            container.add(t);
        }

2)分组计数、找最大/最小元素

为了便于使用 Collectors 中的方法,我们将其中的方法静态导入:

import static java.util.stream.Collectors.*;

统计每个年级的学生个数,代码可以为:

        Map<String, Long> gradeCountMap = students.stream().collect(
                groupingBy(Student::getGrade, counting()));

        System.out.println(gradeCountMap); //{1=2, 2=2, 3=1}

统计一个单词流中每个单词的个数,按出现顺序排序,代码可以为:

        Map<String, Long> wordCountMap =
                Stream.of("hello", "world", "abc", "hello").collect(
                        groupingBy(Function.identity(), LinkedHashMap::new, counting()));
        
        System.out.println(wordCountMap); //{hello=2, world=1, abc=1}

获取每个年级分数最高的一个学生,代码可以为:

        Map<String, Optional<Student>> topStudentMap = students.stream().collect(
                groupingBy(Student::getGrade,
                        maxBy(Comparator.comparing(Student::getScore))));
        
        System.out.println(topStudentMap); //{1=Optional[Student(name=zhangsan, score=89.0, grade=1)], 2=Optional[Student(name=liHua, score=100.0, grade=2)], 3=Optional[Student(name=XiaoMei, score=59.0, grade=3)]}

需要说明的是,这个分组收集结果是Optional<Student>,而不是Student,这是因为maxBy处理的流可能是空流,但对我们的例子,这是不可能的。为了直接得到Student,可以使用Collectors的另一个收集器collectingAndThen,在得到Optional<Student>后调用Optional的get方法,如下所示:

        Map<String, Student> topStudentMap1 = students.stream().collect(
                groupingBy(Student::getGrade, collectingAndThen(
                        maxBy(Comparator.comparing(Student::getScore)), Optional::get)));

        System.out.println(topStudentMap1); //{1=Student(name=zhangsan, score=89.0, grade=1), 2=Student(name=liHua, score=100.0, grade=2), 3=Student(name=XiaoMei, score=59.0, grade=3)}

3)分组数值统计

除了基本的分组计数,还经常需要进行一些分组数值统计,比如求学生分数的和、平均分、最高分、最低分等、针对int、long和double类型,Collectors提供了专门的收集器,如:

        //求平均值,int和long也有类似方法
        public static <T> Collector<T, ? , Double> averagingDouble(ToDoubleFunction<? super T> mapper)

        //求和,long和double也有类似方法
        public static <T> Collector<T, ? , Integer> summingInt(ToIntFunction<? super T> mapper)

        //求多种汇总信息,int和double也有类似方法
        //LongSummaryStatistics包括个数、最大值、最小值、和、平均值等多种信息
        public static <T> Collector<T, ? , LongSummaryStatistics> summarizingLong(ToLongFunction<? super T> mapper)

比如,按年级统计学生分数信息,代码可以为:

        Map<String, DoubleSummaryStatistics> gradeScoreStat =
                students.stream().collect(groupingBy(Student::getGrade,
                        summarizingDouble(Student::getScore)));
        
        System.out.println(gradeScoreStat); //{1=DoubleSummaryStatistics{count=2, sum=178.000000, min=89.000000, average=89.000000, max=89.000000}, 2=DoubleSummaryStatistics{count=2, sum=198.000000, min=98.000000, average=99.000000, max=100.000000}, 3=DoubleSummaryStatistics{count=1, sum=59.000000, min=59.000000, average=59.000000, max=59.000000}}

4)分组内的 map

对于每个分组内的元素,我们感兴趣的可能不是元素本身,而是它的某部分信息。在Stream API中,Stream有map方法,可以将元素进行转换,Collectors也为分组元素提供了函数mapping。

如,对学生按年级分组,得到学生名称列表,代码可以为:

        Map<String, List<String>> gradeNameMap =
                students.stream().collect(groupingBy(Student::getGrade,
                        mapping(Student::getName, toList())));
        
        System.out.println(gradeNameMap); //{1=[zhangsan, lisi], 2=[liHua, wangwu], 3=[XiaoMei]}

5)分组结果处理(filter / sort / skip / limit)

对分组后的元素,我们可以计数,找最大/最小元素,计算一些数值特征,还可以转换(map)后再收集,那可不可以像Stream API一样,排序(sort)、过滤(filter)、限制返回元素(skip/limit)呢?Collector没有专门的收集器,但有一个通用的方法:

          public static<T, A, R, RR> Collector<T, A, RR> collectingAndThen( Collector<T, A, R> downstream, Function<R, RR> finisher)

将学生按年级分组,分组内的学生按照分数由高到低进行排序,利用这个方法,代码可以为:

代码附录:

第一部分,Student 类加 grade 属性之前:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private String name; //名字

    private double score; //分数
    
    public static String getCollegeName() {
        return "Laoma School";
    }
}
    public static void main(String[] args) {

        //创建学习数组
        List<Student> students = Arrays.asList(new Student[]{
                new Student("zhangsan", 89d),
                new Student("lisi", 89d),
                new Student("liHua", 100d),
                new Student("XiaoMei", 59d),
                new Student("wangwu", 98d)});

        //---------------中间操作----------------------------------
        //1. 将学生列表按照分数倒序排(高分在前),分数一样的按照名字进行排序
        students.sort(Comparator.comparing(Student::getScore)
                .reversed()
                .thenComparing(Student::getName));

        //System.out.println(students); //[Student(name=liHua, score=100.0), Student(name=wangwu, score=98.0), Student(name=lisi, score=89.0), Student(name=zhangsan, score=89.0), Student(name=XiaoMei, score=59.0)]

        //2.返回学生列表中90分以上的
        List<Student> above90List = students.stream()
                .filter(t -> t.getScore() > 90).collect(Collectors.toList());

        //System.out.println(above90List); //[Student(name=liHua, score=100.0), Student(name=wangwu, score=98.0)]

        //3.根据学生列表返回名称列表
        List<String> nameList = students.stream()
                .map(Student::getName).collect(Collectors.toList());

        //System.out.println(nameList); //[liHua, wangwu, lisi, zhangsan, XiaoMei]


        //4. 返回字符串列表中长度小于3的字符串、转换为小写、只保留唯一的  (distinct:过滤)
        List<String> list = Arrays.asList(new String[]{"abc", "def", "hello", "Abc"});
        List<String> retList = list.stream()
                .filter(s -> s.length() <= 3)
                .map(String::toLowerCase)
                .distinct()
                .collect(Collectors.toList());

        //System.out.println(retList); //[abc, def]


        //5. 过滤得到90分以上的学生,然后按分数从高到低排序,分数一样的按名称排序
        List<Student> studentList = students.stream()
                .filter(t -> t.getScore() > 90)
                .sorted(Comparator.comparing(Student::getScore)
                        .reversed()
                        .thenComparing(Student::getName))
                .collect(Collectors.toList());

        //System.out.println(studentList); //[Student(name=liHua, score=100.0), Student(name=wangwu, score=98.0)]


        //6. 将学生列表按照分数排序,返回第3名到第5名
        List<Student> sList = students.stream()
                .sorted(Comparator.comparing(Student::getScore)
                        .reversed())
                .skip(2)
                .limit(3)
                .collect(Collectors.toList());

        //System.out.println(sList); //[Student(name=lisi, score=89.0), Student(name=zhangsan, score=89.0), Student(name=XiaoMei, score=59.0)]


        //7. 求学生列表的分数总和
        double studentSum = students.stream().mapToDouble(Student::getScore).sum();
        //System.out.println("学生列表的分数总和:"+studentSum); //学生列表的分数总和:435.0


        //---------------终端操作----------------------------------
        //1. 返回分数最高的学生  这里,假定students不为空。
        Student student = students.stream()
                .max(Comparator.comparing(Student::getScore)
                        .reversed())
                .get();
        //System.out.println(student); //Student(name=XiaoMei, score=59.0)


        //2. 统计大于90分的学生个数
        long above90Count = students.stream().filter(t -> t.getScore() > 90).count();
        //System.out.println(above90Count); //2

        //3. 判断是不是所有学生都及格了(不小于60分)
        boolean allPass = students.stream().allMatch(t -> t.getScore() >= 60);
        //System.out.println(allPass); //false


        //4. 随便找一个不及格的学生
        Optional<Student> failStudent = students.stream()
                .filter(t -> t.getScore() < 60)
                .findAny();
        if (failStudent.isPresent()) {
            //处理不及格的学生
            //System.out.println("这个人不及格:"+failStudent); //这个人不及格:Optional[Student(name=XiaoMei, score=59.0)]
        }

        //5. 逐行打印大于90分的学生
        //students.stream().filter(t -> t.getScore() > 90).forEach(System.out::println);

        //6. 获取90分以上的学生数组
        Student[] above90Arr = students.stream()
                .filter(t -> t.getScore() > 90)
                .toArray(Student[]::new);


        //---------------字符串收集器----------------------------------
        //9. 获取所有的学生名称,用逗号连接起来
        String result = Stream.of("Janet", "Ruby", "Leo")
                .collect(Collectors.joining(", ", "[", "]"));
        System.out.println(result); //[Janet, Ruby, Leo]

        String result1 = students.stream().map(t -> t.getName()).collect(Collectors.joining());
        System.out.println(result1); //liHuawangwulisizhangsanXiaoMei

        String result2 = students.stream().map(Student::getName).collect(Collectors.joining(",", "[", "]"));
        System.out.println(result2); //[liHua,wangwu,lisi,zhangsan,XiaoMei]

    }

第二部分,加 grade 之后:

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private String name; //名字

    private double score; //分数

    private String grade; //年级

    public static String getCollegeName() {
        return "Laoma School";
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/49414.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity3D中实现Player的第一人称视角

效果,上面为Scene场景&#xff0c;下面为Game场景 0创建地形&#xff0c;当然可以先简单的创建一个空白的Terrain。这里我已经对地形进行了初步的编辑和渲染。 1.在Hierarchy视图中右键创建一个胶囊体&#xff08;Capsule&#xff09;作为Player&#xff0c;添加好后重置胶囊体…

039-java集合框架的作用及使用(Collection,Set,Map,SimpleDateFormat)

上一讲:038-JTable控件应用案例讲解_CSDN专家-赖老师(软件之家)的博客-CSDN博客 Java集合体系框架 ava集合类主要由两个根接口Collection和Map派生出来的。 Collection派生出了三个子接口: 集合框架中所有的接口,类都在java.util包中 集合就是动态数组,与数组…

Ubuntu18 vscode配置Ceres的调试

Ceres只支持CMake编译&#xff0c;不支持外部链接方式。因此vscode无法配置Ceres调试&#xff01; 具体过程如下&#xff1a; 首先写了个CMakeLists.txt&#xff0c;(官方demo)&#xff0c;测试编译&#xff1a; project(ceres_test) cmake_minimum_required(VERSION 3.0) f…

uni-app进阶使用(vuex、组件、api)

在上一篇文章介绍了uni-app的基本用法&#xff0c;本章介绍在uni-app中vuex、组件、api的用法。 一、如何使用vuex 1.1 初始化 在项目根目录下创建store文件夹&#xff0c;在其内新建一个文件index.js&#xff0c;在index.js对vuex进行初始化。 import Vuex from vuex impor…

TML+CSS+JS大作业:腾讯课堂首页 1页 侧拉菜单

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 家公司官网网站 | 企业官网 | 酒店官网 | 等网站的设计与制 | HTML期末大学生网页设计作业&#xff0c;Web大学生网页 HTML&#xff1a;结构 CSS&…

算法竞赛入门【码蹄集进阶塔335题】(MT2281-2285)

算法竞赛入门【码蹄集进阶塔335题】(MT2281-2285&#xff09; 文章目录算法竞赛入门【码蹄集进阶塔335题】(MT2281-2285&#xff09;前言为什么突然想学算法了&#xff1f;为什么选择码蹄集作为刷题软件&#xff1f;目录1. MT2281 另一种模2. MT2282 小码哥的认可3. MT2283 整数…

安卓版微信8.0.31内测版出炉:安装包变小,功能变多!

人是社会性生物&#xff0c;建立依恋、经营亲密关系是人的本能&#xff0c;只不过到了网络时代之后&#xff0c;用户进行交流的方式几乎都变成了微信等社交软件。 不仅可以让用户很便捷的和朋友进行沟通&#xff0c;并且在上班办公的时候&#xff0c;也是可以轻松传输文件等&a…

C++ · 手把手教你写一个扫雷小游戏

Hello&#xff0c;大家好&#xff0c;我是余同学。这两个月真是太忙了&#xff0c;无暇给大家更新文章… 暑假不是写了个扫雷小游戏吗(Link)&#xff1f;考虑到很多同学对代码没有透彻的理解&#xff0c;那么&#xff0c;这篇文章&#xff0c;我们来详细分析一下代码. 我们分…

【图像处理OpenCV(C++版)】——初学OpenCV

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; 本专栏主要结合OpenCV和C来实现一些基本的图像处理算法并详细解释各参数含义&#xff0c;适用于平时学习、工作快…

百数低代码开发平台助力生产管理:制造管理系统

随着全球经济化与信息化&#xff0c;制造企业的生产管理系统的建立对于制造业企业的信息化以及生产的智能化具有重要的意义&#xff0c;同时也是促进现代工业进步和发展的基础条件之一。我国制造业属于传统行业&#xff0c;凭借生产规模大且劳动力资源丰富在全世界拥有着一定地…

[附源码]计算机毕业设计springboot旅游度假村管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

Android 反编译入门(基于 Mac)

1 反编译基础 1.1 什么是反编译 定义&#xff1a;反编译就是将可执行程序转换为某种形式的高级编程语言的过程。 1.2 APK 文件的构成 首先&#xff0c;我们通过一张图来看看 APK 的整体组成&#xff1a; 可以看到&#xff0c;APK 主要由六个部分组成&#xff1a; Dex 文件…

什么值得一个头条?从世界杯看“头条”正确打开方式

“足球皇帝”贝肯鲍尔曾说&#xff1a;“在绿茵场上滚动的不是足球&#xff0c;而是黄金。” 卡塔尔世界杯不仅是球迷的盛宴&#xff0c;也是品牌的盛宴。绿茵场广告屏上&#xff0c;众多品牌纷纷现身。还有部分中国企业通过签约球队和球星等形式露面世界杯&#xff0c;共同挖…

【附源码】计算机毕业设计JAVA紫陶文化传播与学习交流网站

【附源码】计算机毕业设计JAVA紫陶文化传播与学习交流网站 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a;…

熤星传媒文化:抖音怎么切换到旧版本?

抖音现在也在不断地更新版本&#xff0c;但是很多小伙伴可能想知道一些新版本的功能&#xff0c;就随着了、系统去升级了&#xff0c;但是用着用着发现自己更喜欢旧版本的&#xff0c;那么抖音店铺又怎么去做店铺带货呢&#xff1f;跟着熤星传媒小编来一起看看吧&#xff01; 帐…

OWASP top10 的介绍

​ OWASP top10 的介绍 2021年版TOP 10产生三个新类别&#xff0c;且进行了一些整合 ​​ A01&#xff1a;失效的访问控制 ​ 从第五位上升称为Web应用程序安全风险最严重的类别&#xff0c;常见的CWE包括&#xff1a;将敏感信息泄露给未经授权的参与者、通过发送的数据泄…

07 CSS04

目标&#xff1a; 1、结构伪类选择器 2、伪元素 3、标准流 4、浮动 5、清除浮动 6、&#xff08;拓展&#xff09;BFC介绍 一、结构伪类选择器 1、作用与优势 作用&#xff1a;根据元素在HTML中的结构关系查找元素 优势&#xff1a;减少对于HTML中类的依赖&#xff0c;有…

[附源码]计算机毕业设计springboot家庭整理服务管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【MySQL】 MySQL亿级数据、主从架构,Sharding分片

数据库Mysql 内容管理MySQL填充亿级数据Insert into select存储过程loop insertLoadfile 导入CVS文件MySQL基准测试&#xff1a; sysbench、mysqlslapsysbenchmysqlslapSQL优化分页查询优化慢SQL日志工具mysqldumpslowMySQL主从复制MySQL主从复制 knowledgeMySQL二进制日志log_…

APS生产计划排产降低企业的生产运营成本

企业运营成本是企业管理的关键&#xff0c;也是企业加强管理&#xff0c;提高企业效益的重要途径&#xff0c;在多数企业的发展中&#xff0c;如何更有效地控制企业运营成本将显得极为突出和十分重要。 APS生产计划排产可以从“设备、物料、人力”三方面降低企业的运营成本&…