【JavaSE之JDK8新特性】三万字详文带你了解JDK8新特性

news2024/11/15 10:49:44

JDK8新特性

  • 一、Lambda
    • 1.1需求分析
    • 2.Lambda表达式的初级体验
    • 3.Lambda表达式的语法规则
      • 3.1.Lambda练习1
      • 3.2.Lambda表达式练习2
    • 4.FunctionalInterfa注解说明
    • 5.Lambda表达式的原理
    • 6.Lambda表达式的省略写法
    • 7.lambda表达式的使用前提
    • 8.lambda和匿名内部类的对比
  • 二、接口中新增的方法
    • 1、JDK8中接口的新增
    • 2、默认方法
      • 2.1、为什么增加默认方法
      • 2.2、接口默认方法的格式
      • 2.3接口中默认方法的使用
    • 3.静态方法
      • 3.1语法规则
      • 3.2 静态方法的使用
    • 4.俩者的区别
  • 三、函数式接口
    • 1.函数式接口的由来
    • 2.函数式接口介绍
      • 2.1 Supplier
      • 2.2 Consumer
      • 2.3 Function
      • 2.4 Predicate
  • 四、方法引用
    • 1.为什么要用方法引用
      • 1.1 lambda表达式冗余
      • 1.2 解决方案
    • 2 方法引用的语法格式
      • 2.1 对象名::方法名
      • 2.2 类名::静态方法名
      • 2.3 类名::普通方法
      • 2.4 类名::构造器
      • 2.5 数组::构造器
    • 总结
  • 五、Stream API
    • 1. 集合处理数据的弊端
    • 2. Stream流式思想概述
    • 3. Stream 流的获取方式
      • 3.1 根据Collection获取
      • 3.2 根据Stream的of方法
      • 3.3 数组创建流
    • 4. Stream常用方法介绍
      • 4.1 forEach❤️
      • 4.2 count❤️
      • 4.3 filter
      • 4.4 limit
      • 4.5 skip
      • 4.6 映射 map 和 flatmap
        • 4.6.1 map
        • 4.6.2 flatMap
      • 4.7 sorted
      • 4.8 distinct
      • 4.9 match❤️
        • 4.9.1 anyMatch
        • 4.9.2 allMatch
        • 4.9.3 noneMatch
      • 4.10 find
        • 4.10.1 findAny
      • 4.10.2 findFirst
      • 4.11 reduce 方法❤️
      • 4.12 map和reduce的组合
      • 4.13 mapToInt
        • 4.13.1 数值流
          • 4.13.1.1 IntStream
      • 4.14 concat
  • 六、总结
  • 七、学后练习

一、Lambda

1.1需求分析

创建一个线程,指定线程要执行的任务:

package jdk8.jdk8;

public class lambdaText01 {
    public static void main(String[] args) {
        //开启一个新线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("新线程中执行的代码:" + Thread.currentThread().getName());
            }
        }).start();

        System.out.println("主线程的代码:" + Thread.currentThread().getName());
    }
}

代码分析:
1、Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的核心;
2、为了指定run方法体,不得不需要Runnable的实现类;
3、为了省去定义一个Runnable的实现类,不得不使用匿名内部类;
4、必须覆盖写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不重写一遍,而且不能出错;
5、而实际上,我们只在乎方法体中的代码(也就是Runnable中的run方法)

2.Lambda表达式的初级体验

针对上面的代码我们进行一个lambda的实现,
Lambda表达式是一个匿名函数,可以理解为一段可以传递的代码

package jdk8.jdk8;

public class lambdaText01 {
    public static void main(String[] args) {
        //开启一个新线程
        //这里用lambda实现的
        new Thread(()->{
            System.out.println("Lambda新线程表达式..." + Thread.currentThread().getName());
        }).start();

        System.out.println("主线程的代码:" + Thread.currentThread().getName());
    }
}

Lambda表达式的优点:简化了匿名内部类的使用,语法更加简单。
匿名内部类语法冗余,体验了lambda表达式后,发现lambda表达式是简化匿名内部类的一种方式。

3.Lambda表达式的语法规则

Lambda省去了面向对象的一些条条框框(如:访问类型,函数类型…等),Lambda的标准格式由三部分组成:

(参数类型 参数名称)-> {
	代码体;
}

格式说明:

  • (参数类型 参数名称):参数列表
  • {代码体}:方法体
  • ->:箭头,分割参数列表和方法体

3.1.Lambda练习1

练习无参无返回值的Lambda
首先定义了一个接口:

package jdk8.jdk8.service;

@FunctionalInterface
public interface Userservice {
    void show();
}

随后创建一主方法运用:

package jdk8.jdk8;

import jdk8.jdk8.service.Userservice;

public class lambdaText02 {
    public static void main(String[] args) {
        //匿名内部类的实现
        goShow(new Userservice() {
            @Override
            public void show() {
                System.out.println("show 方法执行成功");
            }
        });
        System.out.println("-------------------------------");
        //lambda的实现
        goShow(()->{
            System.out.println("Lambda show 方法执行成功");
        });
    }

    public static void goShow(Userservice userservice){
        userservice.show();
    }
}

输出结果:

show 方法执行成功
-------------------------------
Lambda show 方法执行成功

3.2.Lambda表达式练习2

完成一个含有参数的Lambda表达式
首先创建一个Person类:

package jdk8.jdk8;

public class Person {
    private String name;

    private Integer age;

    private Integer height;

    public Person() {
    }
    public Person(String name,Integer age,Integer height){
        this.name = name;
        this.age = age;
        this.height = height;
    }

    public Integer getAge() {
        return age;
    }

    public Integer getHeight() {
        return height;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", height=" + height +
                '}';
    }
}

用Lambda对list集合进行排序

package jdk8.jdk8;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class lambdaText03 {
    public static void main(String[] args) {
        List<Person> list = new ArrayList<>();
        list.add(new Person("徐美琪",19,160));
        list.add(new Person("zs",20,170));
        list.add(new Person("lisi",30,165));
        list.add(new Person("mazi",51,180));


        //匿名内部类的实现
        /*Collections.sort(list,new Comparator<Person>(){
            public int compare(Person o1,Person o2){
                return o1.getAge() - o2.getAge();
            }
        });
        for(Person person:list){
            System.out.println(person);
        }*/
        System.out.println("----------------------------");


        //Lambda表达式实现
        Collections.sort(list,(o1,o2)->{
            return o1.getAge() - o2.getAge();
        });
        for(Person person:list){
            System.out.println(person.toString());
        }

    }

}

输出结果:

Person{name='徐美琪', age=19, height=160}
Person{name='zs', age=20, height=170}
Person{name='lisi', age=30, height=165}
Person{name='mazi', age=51, height=180}

4.FunctionalInterfa注解说明

@FunctionalInterface
这是一个标志注解,被该注解修饰的接口,只能声明一个抽象方法
(如果被它修饰的接口,存在的不是一个抽象方法,会报错)

5.Lambda表达式的原理

匿名内部类的本质是在编译时生成一个Class文件(.class)
Lambda表达式在程序运行时候会形成一个类:
	1、在类中新增一个方法,这个方法的方法体就是Lambda表达式中的代码
	2、还会形成一个匿名内部类,实现接口,重写抽象方法
	3、在接口中重写方法会调用新生成的方法

6.Lambda表达式的省略写法

在lambda表达式的标准写法基础上,可以省略写法的规则为:
1、小括号内从参数类型可以省略
2、如果有且只有一个参数,则小括号可以省略
3、如果大括号内有且只有一个语句,可以同时省略大括号,return关键字以及语句分号。

创建两个接口:

package jdk8.jdk8.service;

public interface StudentService {
    String show(String name,Integer Age);
}

package jdk8.jdk8.service;

public interface OrderService {
    void show(String name);
}

测试:

package jdk8.jdk8;

import jdk8.jdk8.service.OrderService;
import jdk8.jdk8.service.StudentService;

public class lambdaText04 {

    public static void main(String[] args) {
        //lambda表达式的完整写法(goStudent)
        goStudent((String name,Integer age)->{
            return name + age + " 666";
        });
        System.out.println("-------------------");
        //省略写法
        goStudent((name,age)->name+age+" 666");
        System.out.println("----------------");

        //lambda表达式完整写法(OrderService)
        goOrder((String name)->{
            System.out.println(name);
        });
        System.out.println("--------------------------");
        //省略写法
        goOrder(name-> System.out.println(name));

    }

    public static void goStudent(StudentService studentService){
        studentService.show("zhangsan",19);
    }

    public static void goOrder(OrderService orderService){
        orderService.show("lisi");
    }

}

输出:

-------------------
----------------
lisi
--------------------------
lisi

7.lambda表达式的使用前提

Lambda表达式的语法是非常简洁的,但是lambda表达式不说随便使用的,使用有几个条件要特别注意:
1、方法的参数或者局部变量必须为接口中才能使用lambda
2、接口中有且仅有一个抽象方法(@FunctionalInterface)
(这里的一个不包括从Object类里继承的方法,由于Object类是所有类的父类,也就是如果一个接口中有Object方法的话,那么这个接口的实现类也实现它就相当于对Object中的方法进行了重写,这里的指的一个抽象方法是这个意思。
比如:Comparator接口中除了有compare方法还有个,equals方法,但仍然可以对Comparator接口进行lambda表达式的使用)

在这里插入图片描述
可以我往上面测试案例中加入了equals方法和hashcode方法都没有报错,说明在此没有把它算做接口的“抽象方法”.

8.lambda和匿名内部类的对比

	lambda和匿名内部类的对比
		1、所需类型不一样
			a、匿名内部类的类型可以是类,抽象类或者接口
			b、lambda表达式需要的类型只能是接口
		2、抽象方法的数量不一样
			a、匿名内部类所需的接口中的抽象方法的数量是随意的
			b、lambda表达式所需的接口中只能有一个抽象方法
		3、实现原理不一样
			a、匿名内部类是在编译后形成一个class
			b、lambda表达式是在程序运行的时候动态生成class

二、接口中新增的方法

1、JDK8中接口的新增

在JDK8中接口的新增,之前:

Interface 接口名{
静态常量;
抽象方法;
}
之后对接口做了新增,接口中可以有了默认方法和静态方法
Interface 接口名{
静态常量;
抽象方法;
默认方法;
静态方法;
}

2、默认方法

2.1、为什么增加默认方法

在JDK8以前接口中只能由抽象方法和静态常量,会存在以下问题:
如果接口中新增抽象方法,那么实现类都必须要实现这个抽象方法,非常不利于接口的扩展的。

2.2、接口默认方法的格式

接口中默认方法的语法格式:

interface 接口名{
修饰符 default 返回值类型 方法名{
方法体;
} }

package jdk8.jdk8;

public class Demo01Interface {
    public static void main(String[] args) {
        A b = new B();
        A c = new C();

        System.out.println("-----------------");
        //调用接口中的默认方法
        b.text02();
    }
}

interface A{
    void test01();
    
//A中接口的默认方法
    public default String text02(){
        System.out.println("默认方法已被执行");
        return "hello";
    }
}

class B implements A{
    @Override
    public void test01(){
        System.out.println("实现A接口text01方法");
    }
    public String text02(){
        System.out.println("B重写了A中默认方法");
        return "Bhello";
    }
}

class C implements A{
    public void test01(){

    }
}

2.3接口中默认方法的使用

接口中的默认方法有两种使用方式
1、实现类对默认方法进行重写
2、实现类直接调用接口中的默认方法

3.静态方法

JDK8中为接口新增了静态方法,作用也是为了接口的拓展

3.1语法规则

interface 接口名{
修饰符 static 返回类型 方法名{
方法体;
}}

3.2 静态方法的使用

1、接口中的静态方法在实现类中不能被重写
2、调用的话只能用接口名来调用:接口名.静态方法名()

package jdk8.jdk8;

public class Demo01Interface {
    public static void main(String[] args) {
        A b = new B();
        A c = new C();

        System.out.println("-----------------");
        //调用接口中的默认方法
        b.text02();
        System.out.println("-------------");
        A.text03();

    }
}

interface A{
    void test01();

    /**
     * 默认方法
     * @return
     */
    public default String text02(){
        System.out.println("默认方法已被执行");
        return "hello";
    }

    /**
     * 接口中的静态方法
     * @return
     */
    public static String text03(){
        System.out.println("A中的静态方法执行了");
        return "xmq";
    }
}

class B implements A{
    @Override
    public void test01(){
        System.out.println("实现A接口text01方法");
    }
    public String text02(){
        System.out.println("B重写了A中默认方法");
        return "Bhello";
    }
}

class C implements A{
    public void test01(){

    }
}

4.俩者的区别

1、默认方法通过实例调用,静态方法通过接口名调用
2、默认方法可以被继承,实现类可以直接调用接口默认方法,也可以重写接口默认方法
3、静态方法不能被继承(都叫它类方法了,当然不能被继承了),实现类不能重写接口的静态方法,但继承关系仍然存在,可以使用接口名调用

三、函数式接口

1.函数式接口的由来

lambda表达式的使用前提是需要有函数式接口,而lambda表达式使用时候不关心接口名,抽象方法名,只关心抽象方法的参数列表返回类型。因此为了让我们使用lambda表达式更加的方便,在JDK中提供了大量的函数式接口。

package jdk8.jdk8;

public class Demo01Fun {
    public static void main(String[] args) {
        fun1((arr)->{
            int sum = 0;
            for(int x:arr)
                sum += x;
            return sum;
        });
    }
    static void fun1(Operator operator){
        int[] arr = {1,2,3,4};
        int sum = operator.getSum(arr);
        System.out.println("sum = " + sum);
    }
}

/**
 * 函数式接口
 */
@FunctionalInterface
interface Operator{

    int getSum(int[] arr);

}

2.函数式接口介绍

在JDK中帮我们提供的有函数式接口,主要是在java.util.function 包中。

2.1 Supplier

无参有返回值(生产数据)

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

对Supplier进行个测试

package jdk8.jdk8;

import java.util.function.Supplier;

/**
 * 函数式接口的使用
 */
public class supplierText {
    public static void main(String[] args) {
        printSum(()->{
            int[] arr = {1,5,9,4,8,6,4};
            int sum = 0;
            //对数组求和
            for(int num:arr)
                sum += num;
            return sum;
        });
    }

    /**
     * 
     * @param supplier
     */
    private static void printSum(Supplier<Integer> supplier){
        //无参有返回值
        Integer sum = supplier.get();
        System.out.println("sum = " + sum);
    }
}

2.2 Consumer

有参无返回值(消费数据)使用的时候需要指定一个泛型来定义参数类型

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

使用:将输入的数据统一转换成小写

package jdk8.jdk8;

import java.util.function.Consumer;

public class consumerText {
    public static void main(String[] args) {
        test(msg->{
            System.out.println(msg + "--->转换成小写:" + msg.toLowerCase());
        });
    }

    /**
     * 
     * @param consumer
     */
    private static void test(Consumer<String> consumer){
        consumer.accept("Hello World!!!");
    }
}

默认方法:andThen

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

package jdk8.jdk8;

import java.util.function.Consumer;

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

        /*test(msg->{
            System.out.println(msg + "--->转换成小写:" + msg.toLowerCase());
        });*/

        test02(msg1->{
            System.out.println(msg1 + "--->转换成小写:" + msg1.toLowerCase());
        },msg2->{
            System.out.println(msg2 + "--->转换成大写:" + msg2.toUpperCase());
        });

    }

    /**
     *
     * @param consumer
     */
    private static void test(Consumer<String> consumer){
        consumer.accept("Hello World!!!");
    }

    private static void test02(Consumer<String> consumer1,Consumer<String> consumer2){
        String str = "Hello World";
        //consumer1.accept(str);//转小写
        //consumer2.accept(str);//转大写
        consumer1.andThen(consumer2).accept(str);
    }
}

2.3 Function

有参有返回值,Function接口是根据一个类型的数据得到另一个数据类型的数据,前者称为前置条件,后者称为后置条件。有参数有返回值。

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
    }

使用例子:传递一个字符串返回一个Integer对象

package jdk8.jdk8;

import java.util.function.Function;

public class functionText {
    public static void main(String[] args) {
        test(msg->{
            return Integer.valueOf(msg);
        });
    }

    /**
     * 传递一个字符串,返回该字符串数字
     * @param function
     */
    private static void test(Function<String,Integer> function){
        Integer apply = function.apply("666");
        System.out.println("apply = " + apply);
    }
}

默认方法andThen:

default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

操作:

package jdk8.jdk8;

import java.util.function.Function;

public class functionText {
    public static void main(String[] args) {
        test(msg->{
            return Integer.valueOf(msg);
        },msg2->{
            return msg2 * 10;
        });
    }


    /**
     *可以理解先由function1操作,再由function2操作
     * @param function1
     * @param function2
     */
    private static void test(Function<String,Integer> function1,Function<Integer,Integer> function2){
//        Integer res1 = function1.apply("666");
//        Integer res2 = function2.apply(res1);
//        System.out.println("res2 = " + res2);
        Integer res = function1.andThen(function2).apply("666");
        System.out.println("res = " + res);
    }
}

默认的compose方法顺序和andThen相反,而静态方法identity则是,输入什么参数就返回什么参数。

2.4 Predicate

有参有返回结果(且返回类型为boolean型)

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    }

操作:对传入的字符串长度进行个判断

package jdk8.jdk8;

import java.util.function.Predicate;

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

        test(msg->{
            return msg.length()<6?false:true;
        },"Hello World");
    }

    /**
     *
     * @param predicate
     * @param msg
     */
    private static void test(Predicate<String> predicate, String msg){
        boolean flag = predicate.test(msg);
        System.out.println("flag = " + flag);
    }
}

四、方法引用

1.为什么要用方法引用

1.1 lambda表达式冗余

在使用lambda表达式的时候,也会出现代码冗余的情况。

package jdk8.jdk8.function;

import java.util.function.Consumer;

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

        toSum(arr->{
            int sum = 0;
            for(int num:arr)
                sum += num;
            System.out.println("sum = " + sum);
        });

    }

    /**
     * 对数组进行求和
     * @param arr
     */
    private static void totalSum(Integer[] arr){
        int sum = 0;
        for(int num:arr)
            sum += num;
        System.out.println("sum = " + sum);
    }

    /**
     * 对数据进行”消费“
     * @param consumer
     */
    private static void toSum(Consumer<Integer[]> consumer){
        Integer[] arr = {3,6,6,5,9,7,6,2,3,45};
        consumer.accept(arr);
    }
}

1.2 解决方案

因为在lambda表达式中要执行的代码和我们另一个方法中的代码是一样的,这时就没有必要重写一份逻辑了,这时我们就可以“引用”重复代码。

package jdk8.jdk8.function;

import java.util.function.Consumer;

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

        //:: 方法引用 也是JDK8新的语法
        toSum(methodRef01::totalSum);

    }

    /**
     * 对数组进行求和
     * @param arr
     */
    private static void totalSum(Integer[] arr){
        int sum = 0;
        for(int num:arr)
            sum += num;
        System.out.println("sum = " + sum);
    }

    /**
     * 对数据进行”消费“
     * @param consumer
     */
    private static void toSum(Consumer<Integer[]> consumer){
        Integer[] arr = {3,6,6,5,9,7,6,2,3,45};
        consumer.accept(arr);
    }
}

2 方法引用的语法格式

符号表示: ::
符号说明:双冒号为方法引用运算符,而它所在的表达式被称为方法引用
应用场景:如果lambda表达式所要实现的方案,已经有其他方法存在相同的方案,那么则可以使用方法引用。
常见的引用方式:

方法引用在JDK8中使用相当灵活,有以下几种方式:
1、InstanceName::methodName 对象::方法名
2、ClassName::staticMethodName 类名::静态方法
3、ClassName::methodName 类名::普通方法
4、ClassName::new 类名::new 调用的构造器
5、TypeName[]::new String[]::new 调用数组的构造器

2.1 对象名::方法名

package jdk8.jdk8.function;

import java.util.Date;
import java.util.function.Supplier;

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

        Date now = new Date();
        Supplier<Long> supplier = ()-> now.getTime();
        System.out.println("时间: " + supplier.get());

        //再试试通过方法引用的方式进行处理
        Supplier<Long> supplier1 = now::getTime;
        System.out.println("时间: " + supplier1.get());
    }
}

输出结果:

时间: 1666766083734
时间: 1666766083734

方法引用注意事项:
1、被引用的方法,参数要和接口中的抽象方法的参数一样
2、当接口抽象方法有返回值时被引用的方法也必须有返回值

2.2 类名::静态方法名

package jdk8.jdk8.function;

import java.util.function.Supplier;

public class methodRef03 {
    public static void main(String[] args) {
        //获取当前时间(毫秒为单位)
        Supplier<Long> supplier = ()->System.currentTimeMillis();
        System.out.println(supplier.get());  //*1

        //用方法引用测试(currentTimeMillis)是System类中的静态方法
        Supplier<Long> supplier1 = System::currentTimeMillis;
        System.out.println(supplier1.get());  //*2

    }
}

输出:

1666766539050
1666766539051
//从那个*1位置运行到*2位置用了一毫秒

2.3 类名::普通方法

Java面向对象中,类名只能调用静态方法,类名引用实例方法是要有前提的,实际上是拿第一个参数作为方法的调用者。

2.4 类名::构造器

package jdk8.jdk8.function;

import jdk8.jdk8.Person;

import java.util.function.Supplier;

public class methodRef04 {
    public static void main(String[] args) {
        Supplier<Person> supplier = ()->new Person();
        System.out.println(supplier.get());
        //通过引用方法实现
        Supplier<Person> supplier1 = Person::new;
        System.out.println(supplier1.get());
    }
}

2.5 数组::构造器

package jdk8.jdk8.function;

import java.util.function.Function;

public class methodRef05 {
    public static void main(String[] args) {
        Function<Integer,String[]> function = (len)->new String[len];
        String[] s = function.apply(3);
        System.out.println("数组的长度为:" + s.length);
        //使用方法引用的方式
        Function<Integer,String[]> function1 = String[]::new;
        String[] ss = function1.apply(5);
        System.out.println("数组的长度为:" + ss.length);
    }
}

总结

方法引用是对lambda表达式符合特定情况下的一种缩写方式,它使得我们的lambda表达式更加的精简,也可以理解为lambda表达式的缩写形式。不过需要注意的是方法引用只能引用已经存在了的方法,解决lambda表达式的冗余问题。

五、Stream API

1. 集合处理数据的弊端

当我们需要对集合中的元素进行操作的时候,除了必须的添加删除获取之外,最典型的操作就是集合的遍历(集合的外部迭代)。

package jdk8.jdk8.stream;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class StreamText01 {
    public static void main(String[] args) {
        //定义一个List集合
        List<String> list = Arrays.asList("xmq","zhangsan","mazi","zhanghanzi");
        //1.获取所有姓张的信息
        List<String> list1 = new ArrayList<>();
        for(String s:list){
            if(s.startsWith("zhang"))
            list1.add(s);
        }

        //输出
        for(String s: list1)
            System.out.println("s = " + s);
        System.out.println("----------------------");

        //获取长度大于8的
        List<String> list2 = new ArrayList<>();
        for(String s: list1){
            if(s.length()>8)
                list2.add(s);
        }

        //输出
        for(String s:list2)
            System.out.println("s = " + s);

    }
}

上面的代码根据我们不同的需求总是一次次的循环循环。这时我们希望有更加高效的处理方式,这时我们就可以通过JDK8中提供的Stream API来解决这个问题了(Stream的内部迭代)。
Stream有更加优雅的解决方案。

package jdk8.jdk8.stream;

import java.util.Arrays;
import java.util.List;

public class StreamText02 {
    public static void main(String[] args) {
        //定义一个List集合
        List<String> list = Arrays.asList("xmq","zhangsan","mazi","zhanghanzi");
        //1.获取所有姓张的信息
        //2.获取长度大于8的
        //3.输出获取的结果
        list.stream()
                .filter(s->s.startsWith("zhang"))
                .filter(s->s.length()>8)
                .forEach(s-> System.out.println(s));
        System.out.println("-----------------------------");
        //这里可以采用方法引用的方式输出
        list.stream()
                .filter(s->s.startsWith("zhang"))
                .filter(s->s.length()>8)
                .forEach(System.out::println);


    }
}

上面的Stream API的含义:获取流,过滤长度,逐一打印。代码相比于上面的案例显得更加的简介直观。
在这里插入图片描述

2. Stream流式思想概述

注意:Stream和IO流(InputStream/OutputStream)没有任何关系。
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构不保存数据而是对数据进行加工处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。
在这里插入图片描述
Stream API能让我们快速完成许多复杂的操作,如筛选,切片,映射,查找,去除重复,统计,匹配和归约。

3. Stream 流的获取方式

3.1 根据Collection获取

首先,java.util.Collection接口中加入了default默认方法 stream(),也就是说Collection接口下的所有实现都可以通过stream方法来获取Stream流。
在这里插入图片描述
如:

List<String> list = new ArrayList<>();
list.stream();
Set<String> set = new HashSet<>();
set.stream();
.......

但是map接口没有实现Colletion,但是我们可以通过map中的keySet,values,entrySet得到集合然后进行操作。

3.2 根据Stream的of方法

在实际开发中我们不可避免的会操作数组中的数据,由于数组对象不可能添加默认方法,所以Stream提供了静态方法of。
of的俩种形式:
一、可变参数的形式(可以传多个同类型T,也可以传T数组)
在这里插入图片描述

底层也是通过数组创建流的方式:

public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
    }

二、传入单个对象,对单个对象进行Stream流
在这里插入图片描述
测试:

package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class StreamText03 {
    public static void main(String[] args) {
        Stream<String> a1 = Stream.of("a1","a2","a3");
        String[] arr1 = {"aa","bb","cc"};
        Stream<String> sarr1 = Stream.of(arr1);
        Integer[] arr2 = {1,2,3};
        Stream<Integer> sarr2 = Stream.of(arr2);
        sarr2.forEach(System.out::println);
        //注意:基本数据类型的数组是不行的;如果是基本数据类型的数组,那么就是整个arr3被当成对象传进去,这和of参数中泛型T有关
        int[] arr3 = {1,2,3};
        Stream.of(arr3).forEach(System.out::println);

    }
}

输出:

1
2
3
[I@4dd8dc3

3.3 数组创建流

你可以使用静态方法Arrays.stream从数组创建一个流。它接受一个数组作为参数。例如,你可以将一个原始类型int的数组转换成一个IntStream。
如下所示:

int[] numbers = {2, 3, 5, 7, 11, 13}; int sum =
Arrays.stream(numbers).sum();

4. Stream常用方法介绍

Stream流模型的操作很丰富,这里介绍一些常用的API,这些方法可以被分为两种:
1、终结方法:返回值类型不再是Stream类型的方法,不再支持链式调用。这里介绍了 count 和 forEach 俩大终结方法。
2、非终结方法:返回值类型仍然是Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法都是非终结的方法)
3、终端方法我在后面打了爱心号
在这里插入图片描述

终端操作都是返回一个boolean(allMatch之类的)、void
(forEach)或Optional对象(findAny等)…

Stream 注意事项(重要)

  1. Stream只能操作一次(就是流一旦被操作了就不能回头了,上面说Stream好比流水线工序,就好比一个被整了的脸不能回到以前一样)
  2. Stream方法返回的最新的流
  3. Stream不调用终结方法,中间步骤是不执行的
package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class StreamText04 {
    public static void main(String[] args) {
        Stream<String> sarr1 = Stream.of("a1","xmq","a2","aa");
        sarr1.filter(s->{
            System.out.println("------------");
            return s.contains("a");
        });//没有终结方法,最后没有输出任何东西
    }
}

下面添加上终结方法就有输出了:

package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class StreamText04 {
    public static void main(String[] args) {
        Stream<String> sarr1 = Stream.of("a1","xmq","a2","aa");
        sarr1.filter(s->{
            System.out.println("------------");
            return s.contains("a");
        }).forEach(System.out::println);
        System.out.println("------------------");
    }
}

输出结果:

------------
a1
------------//这里我们也可以发现“xmq”被过滤掉了
------------
a2
------------
aa
------------------

4.1 forEach❤️

forEach用来遍历流中的数据的:

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

该方法接受一个Consumer接口,会将每一个流元素交给函数 accept 处理

package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class forEachText {
    public static void main(String[] args) {
        //这里正常写法
        Stream.of("xmq","love","leyang").forEach(s->System.out.print(s + " "));
        System.out.println();
        //这里用了方法引用
        Stream.of("xmq","leyang").forEach(System.out::println);
    }
}

输出:

xmq love leyang 
xmq
leyang

注意:别把这个当成循环用,所以什么continue,什么break别用,这里参数是Consumer ,用匿名类或者lambda实现,根据里面的 void accept方法,我们想结束该遍历可以return。

4.2 count❤️

Stream 流中的count方法用来统计其中元素的个数(返回一个long值,代表元素的个数)

long count();

package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class countText {
    public static void main(String[] args) {
        long count = Stream.of("shabi","is","you").count();
        System.out.println("count = " + count);//count = 3
    }
}

4.3 filter

filter 方法的作用是用来过滤数据的,返回符合条件的数据
在这里插入图片描述
可以通过filter方法将一个流转换成另一个子集流。

Stream filter(Predicate<? super T> predicate);

该接口接受一个Predicate 函数式接口参数

package jdk8.jdk8.stream;

import java.util.ArrayList;
import java.util.List;

public class filterText {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("xmq");
        list.add("a1");
        list.add("bb");
        list.add("aa");

        //这里过滤掉开头为字母a的字符串
        list.stream().filter(s->!s.startsWith("a")).forEach(System.out::println);//xmq bb
    }
}

4.4 limit

limit 方法可以对流进行截取处理,支取前 n 个数据
在这里插入图片描述

Stream limit(long maxSize);

package jdk8.jdk8.stream;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class limitText {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();
        list.add(new Student("xmq",19,87));
        list.add(new Student("zs",20,90));
        list.add(new Student("mz",21,79));
        list.add(new Student("ly",20,87));
        //这里对学生成绩进行排序,如果成绩相同按年龄,年龄小的在前,如果年龄还相同,那按名字字典排序
        Collections.sort(list,(stu1,stu2)->{
            if(stu1.score!=stu2.score)
                return stu2.score-stu1.score;
            else if(stu1.age!=stu2.age)
                return stu1.age - stu2.age;
            else
                return stu1.name.compareTo(stu2.name);
        });
        //输出前三名
        list.stream().limit(3).forEach(System.out::println);
    }
}

class Student{
    public String name;//姓名
    public int age;//年龄
    public int score;//成绩

    public Student(String name,int age,int score){
        this.name = name;
        this.age = age;
        this.score = score;
    }

    public Student() {
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

注意:如果传递的参数为负数,那么就会报错,如果传递的大于集合的本身长度,那么可以理解为不操作(在原集合中含有的元素上进行工作)

4.5 skip

这个方法可以和limit 相对立,跳过前面几个元素,选取后面的元素
在这里插入图片描述

Stream skip(long n);

操作:

package jdk8.jdk8.stream;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class skipText {
    public static void main(String[] args) {
            List<jdk8.jdk8.stream.Student> list = new ArrayList<>();
            list.add(new jdk8.jdk8.stream.Student("xmq",19,87));
            list.add(new jdk8.jdk8.stream.Student("zs",20,90));
            list.add(new jdk8.jdk8.stream.Student("mz",21,79));
            list.add(new jdk8.jdk8.stream.Student("ly",20,87));
            //这里对学生成绩进行排序,如果成绩相同按年龄,年龄小的在前,如果年龄还相同,那按名字字典排序
            Collections.sort(list,(stu1, stu2)->{
                if(stu1.score!=stu2.score)
                    return stu2.score-stu1.score;
                else if(stu1.age!=stu2.age)
                    return stu1.age - stu2.age;
                else
                    return stu1.name.compareTo(stu2.name);
            });
            //输出不是前三名的学生
            list.stream().skip(3).forEach(System.out::println);//输出mz那个学生
        }
    }

4.6 映射 map 和 flatmap

常用的数据处理工具。

4.6.1 map

如果我们需要将流中的元素映射到另一个流中,我们可以使用map方法(看参数是 Function 接口我们也是比较好理解的):

在这里插入图片描述

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

该方法需要一个Function 函数式接口参数,可以将当前流中的T类型数据转换成另一种R类型的数据。
操作:

package jdk8.jdk8.stream;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Stream;

public class mapText {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        String date = in.next();//输入的格式是 年-月-日
        String[] dates = date.split("-");

        List<Integer> list = new ArrayList<>();
        Stream.of(dates).map(Integer::valueOf).forEach(num->list.add(num));//这里用了方法引用的方式,Integer::valueOf
        //Stream.of(dates).map(s->Integer.valueOf(s)).forEach(num->list.add(num));
        //Stream.of(dates).map(s->Integer.valueOf(s)).forEach(System.out::println);

        list.stream().forEach(System.out::println);//这样我们就将年月日三个数字得到了,可以进行操作了
    }
}


4.6.2 flatMap

🎈🎈🎈这里先介绍一下 java.util.Arrays 类下的stream 方法。该方法可以接收一个数组,产生一个流。比如说一个单词,经过了一个split 后会产生一个一个字母,从一个产品(String)变成了多产品(String[]),这时候如果你想要其中的单个产品处理,就可以使用 Arrays.stream(由于是有参有返回值,所以是寻Function函数式接口,也就是映射里)这里只是介绍,用起来还是很巧妙的。

注意:这里传递给的参数应该是一个数组
public static Stream stream(T[] array) {
return stream(array, 0, array.length);
}

字符串分割去重案例:
在这里插入图片描述
那如果在map后加个distinct会有变化吗,答案是不会的。因为字符串数组各个字符串不相等。
如何实现字符串数组各个字母间的去重呢?
这里再了解一下:flatMap(将各个生成流扁平成一个流,就上面说的单词分割开后成了很多新流,flatMap可以使得这些分割的流扁平成一个流进行操作(Arrays.stream不就会产生新流吗,流作为元素流中流?这不行,所以flatMap帮我们把它们变成一个流))

Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
在这里插入图片描述

输出:
在这里插入图片描述
😊再来个例题:
给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],只返回总和能被3整除的数对呢?

我们先从一个列表元素开始,去选择另一个列表元素合并,先判断条件,再返回结果,最后去重得到答案。

package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class flatMapText {
    public static void main(String[] args) {
        Integer[] arr1 = {1,2,3};
        Integer[] arr2 = {3,4};
        Stream.of(arr1)
                .flatMap(i->Stream.of(arr2)
                        .filter(j->(i+j)%3==0)
                        .map(j->new Integer[]{i,j})
                )
                .distinct()
                .forEach(res->System.out.println(res[0] + " " + res[1]));//输出:2 4
                														       //3 3
    }
}

(这不比俩个for循环看着舒服多了吗)😍😍😍

4.7 sorted

如果需要将数据进行排序,可以使用sorted 方法:

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

第一个就默认为升序,我们来模拟一下第二个(其实和Arrays,Collections中的sort用法类似)
操作:

package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class sortedText {
    public static void main(String[] args) {
        Stream.of(50,67,94,67,85,76,95,94,93,66,84,70)
                .filter(num->num>=70)//筛选不低于70分的
                .sorted((num1,num2)->num2-num1)//排序为降序
                .forEach(System.out::println);//输出看看结果咯
/*	95
	94
	94
	93
	85
	84
	76
	70*/
    }
}

4.8 distinct

如果要去掉重复数据,可以使用distinct方法(这里与SQL中的distinct用法类似,都是排除重复数据):
在这里插入图片描述

Stream distinct();

在上面的sorted 操作基础上进行操作一下:

package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class distinctText {
    public static void main(String[] args) {
        Stream.of(50,67,94,67,85,76,95,94,93,66,84,70)
                .filter(num->num>=70)//筛选不低于70分的
                .sorted((num1,num2)->num2-num1)//排序为降序
                .distinct()//去重
                .forEach(System.out::println);//输出看看结果咯
    }
}/*输出:
95
94
93
85
84
76
70*/

注意:对面自定义对象去重我们需要对Object类中的hashcode和equals方法进行重写。

4.9 match❤️

对数据处理,怎么离得开匹配数据呢?

4.9.1 anyMatch

这个方法可以用来查看流中是否存在与之匹配的元素。(返回结果是个boolean型)这是一个终端条件

boolean anyMatch(Predicate<? super T> predicate);

package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class anyMatchText {
    public static void main(String[] args) {
        String[] name = {"zs","lisi","mary","jack"};
        //查查是否存在首字母是z的姓名
        if(Stream.of(name).anyMatch(s->s.startsWith("z")||s.startsWith("Z")))
            System.out.println("这些人中存在首字母为z/Z的人");
        else
            System.out.println("没找到");
    }
}

4.9.2 allMatch

这和上面差不多,就是检验是否全部元素满足条件。

4.9.3 noneMatch

这与上面相反,就不举例了。就是检验是否没有任何元素满足条件。

4.10 find

4.10.1 findAny

用法比较简单

Optional< T > findAny();

public static void main(String[] args) {
        String[] name = {"ly","zs","mz","mary","xmq","jack","zz","zx","zp"};
        Arrays.stream(name)
                .filter(s->s.startsWith("z"))
                .findAny()
                .ifPresent(System.out::println);//zs
    }

4.10.2 findFirst

与findAny用法类似

4.11 reduce 方法❤️

该方法对数据进行操作也被称为折叠,一段折一段

T reduce(T identity, BinaryOperator< T > accumulator);
这里BinaryOperator是一个函数式接口,继承了BiFunction的抽象方法
R apply(T t, U u);由于就给了一个参数类型T(泛型)那BinaryOperator接口真正继承的方法应该是 T apply(T t,T u);这样的形式。

这个apply 方法第一个参数是上次的结果,第二个参数 u 是新传进去的元素。而reduce 中第一个参数identity 是对 apply方法的第一个参数的一个初始化。

举例:

package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class reduceText {
    public static void main(String[] args) {
        Integer[] arr = new Integer[]{4,5,3,9};
        Integer sum = Stream.of(arr)
                .reduce(0,(num1,num2)->num1 + num2);
        System.out.println("sum = " + sum);
    }
}

在这里插入图片描述
还有个没有初始化第一个参数identity 的

Optional< T> reduce(BinaryOperator< T> accumulator);
在这里插入图片描述
还是比较直观的,开始是为空的,然后出现把result 赋值后,再apply,所以集合流化应该保证里面元素应该最好大于一个,不然null 就出来了。

可以看到该方法返回是一个Optional 对象,Optional是一个容器(final类),它用来代表着一个值存在还是不存在。(这里就不多介绍了)
Optional 类中有一个ifPresent 方法:

public void ifPresent(Consumer<? super T> action) {
        if (value != null) {
            action.accept(value);
        }
    }//这里value开始是默认为null 的,如果不为空就对它操作,Consumer前面提过了
package jdk8.jdk8.stream;

import java.util.stream.Stream;

public class reduceText02 {
    public static void main(String[] args) {
        Integer[] arr = {3,5,7,1,6,9,8,5};
        //现在求这个数组中的最小数
        Stream.of(arr)
                .reduce(Integer::min)
                .ifPresent(num-> System.out.println("num = " + num));//存在这个数才会输出,不存在啥也不会输出,不存在就不会走这步,刚刚源代码说明了
    }
}

求最大值和最小值方法一样没区别,这里就不演示了。

4.12 map和reduce的组合

4.13 mapToInt

该方法用于映射到数值流(IntStream),具体下面说明

4.13.1 数值流

前面用reduce 进行数值求和,它需要着拆箱和装箱的成本:

Integer sum = Stream.of(arr)
                .reduce(0,(num1,num2)->num1 + num2);

这里需要将Integer类型,拆箱成基本类型然后再求和,不仅影响效率,而且也不够方便。
Stream API提供了原始类型流特化(基本数据类型),专门支持处理数值流的方法。

4.13.1.1 IntStream

JDK8 引入了三个原始类型特化流接口:IntStream、DoubleStream、LongStream,分别将流中的元素特化为int,double,long,从而避免了暗含的装箱成本。
将流转换为特化版本的常用方法是mapToInt、mapToDouble和mapToLong。这些方法和前面说的map方法的工作方式一样,只是它们返回的是一个特化流,而不是Stream< T >。

以IntStream 流为例说明一下,其他俩用法一样(下面说说除了正常流都有以外的几个方法):
1、 终端方法,就将流元素构成一个数组,然后返回

int[] toArray();

操作:

 public static void main(String[] args) {
        String[] arr = {"33","56","98","45","65","78","32"};
        int[] arr1 = Stream.of(arr)
                .mapToInt(Integer::valueOf)
                .toArray();

        for(int x:arr1)
            System.out.println(x);
    }

2、四个常用的数值运算(这些都是终端方法):

int sum();
OptionalInt min();
OptionalInt max();
OptionalDouble average();

拿一个操作一下:

public static void main(String[] args) {
        String[] arr = {"33","56","98","45","65","78","32"};
        int sum = Stream.of(arr)
                .mapToInt(Integer::valueOf)
                .sum();
        System.out.println("sum = " + sum);
    }

3、将该原始流转换回对象流

Stream boxed();

由于IntStream里面的map,int->int,那我想转换成别的嘞,还得靠对象流来操作。
操作:

public static void main(String[] args) {
        String[] arr = {"33","56","98","45","65","78","32"};
        IntStream x = Stream.of(arr)
                .mapToInt(Integer::valueOf);
        //划分一下等级,当然也可以前面直接划分,这里只是为了演示一下这个boxed方法
        x.boxed()
                .map(num->{return num>=90?"A":"B";})
                .forEach(System.out::println);
    }

4、数值范围(静态方法,返回值是IntStream)

左闭右开
public static IntStream range(int startInclusive, int endExclusive)
左闭右闭
public static IntStream rangeClosed(int startInclusive, int endInclusive)

操作:

public class rangeOrrangeClosedText {
    public static void main(String[] args) {
        //101以内的素数,不包括101
        IntStream.range(1,101)
                .filter(num->isPrim(num))
                .forEach(System.out::println);
        //101以内的素数,包括101
        IntStream.rangeClosed(1,101)
                .filter(num->isPrim(num))
                .forEach(System.out::println);

    }
    //判断数是否为素数
    private static boolean isPrim(int num){
        if(num<2)
            return false;
        for(int i=2;i*i<num;++i){
            if(num%i==0)
                return false;
        }
        return true;
    }
}

4.14 concat

它是Stream中的静态方法;用来合并俩个不为空的流

public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
        Objects.requireNonNull(a);
        Objects.requireNonNull(b);

        @SuppressWarnings("unchecked")
        Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
                (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
        Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
        return stream.onClose(Streams.composedClose(a, b));
    }

操作:

public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("a","b","c");
        Stream<String> stream2 = Stream.of("d","e","f");

        Stream.concat(stream1,stream2)
                .forEach(System.out::println);
    }

六、总结

  1. 了解了什么是lambda 表达式,lambda 表达式和匿名内部类的区别;
  2. 了解了默认方法是个啥,JDK8使得接口里也可以有静态方法了;
  3. 介绍了四个函数式接口,Supplier,Consumer,Function,Predicate;
  4. 方法引用的概念以及其中的语法格式;
  5. 引入了Stream流(遍历数据集的高级迭代器);
  6. 介绍了Stream的常用方法,学习了如何更好的处理数据。
    在这里插入图片描述

七、学后练习

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

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

相关文章

#define宏的妙用!实现你以为的函数offsetof等

目录 一.#define 1.1#define的使用 1.2在define定义标识符的时候&#xff0c;要不要在最后加上 ;? 二.#define定义宏 2.1好代码的写法: 2.2#define 替换规则 2.3#和## 三.带有副作用的宏参数 四.宏和函数的对比 五.实现offetof 思路解析过程&#xff1a; 代码实现&am…

破解系统密码

一、利用5次shift漏洞破解win7密码 1.1 漏洞 1. 在未登录时&#xff0c;连续按5次shift键&#xff0c;弹出程序C:\Windows\System32\sethc.exe 2. 部分win7及win10系统在未进入系统时&#xff0c;可以通过系统修复漏洞篡改系统文件名&#xff01; 注意&#xff1a;如win7或win…

SpringBoot/Spring AOP默认动态代理方式

Spring 5.x中AOP默认依旧使用JDK动态代理SpringBoot 2.x开始&#xff0c;AOP为了解决使用JDK动态代理可能导致的类型转换异常&#xff0c;而使用CGLIB。在SpringBoot 2.x中&#xff0c;AOP如果需要替换使用JDK动态代理可以通过配置项spring.aop.proxy-target-classfalse来进行修…

【Linux】7.0 信号

文章目录信号的基本概念kill -l 查看信号列表信号的处理方式signal( ) 自定义处理信号信号的产生方式键盘产生进程异常&#xff08;core dump&#xff09;系统调用软件条件信号的发送&#xff08;OS&#xff09;信号常见相关名词解释进程接收处理信号原理信号集函数的使用打印p…

【Redis】3.详解分布式锁

文章目录1. 什么是分布式锁2. 分布式锁的特点3. 常见的分布式锁4. 实现分布式锁5.解决分布式锁中的原子性问题5.1 Lua脚本5.2 使用Java代码调用Lua脚本实现原子性1. 什么是分布式锁 分布式锁是指分布式系统或者不同系统之间共同访问共享资源的一种锁实现&#xff0c;其是互斥的…

【Django框架】——20 Django视图 02 路由命名和反向解析

文章目录一、 路由命名二、reverse反向解析三、通过URL模板页面进行传参四、namespace1.reverse反向解析2.url模板标签在 Django 项⽬中&#xff0c;⼀个常⻅需求是获取最终形式的 URL&#xff0c;⽐如⽤于嵌⼊⽣成的内容中&#xff08;视图和资源⽹址&#xff0c;给⽤户展示⽹…

《网络安全笔记》第七章:注册表基础

一、注册表基础 1、概述 注册表是windows操作系统、硬件设备以及客户应用程序得以正常运行和保存设置的核心“数据库”&#xff0c;也可以说是一个非常巨大的树桩分层结构的数据库系统注册表记录了用户安装在计算机上的软件和每个程序的相互关联信息&#xff0c;它包括了计算…

【UDS】ISO14229之0x2F服务

文章目录前言一、理论描述二、使用步骤1.请求2.响应总结->返回总目录<- 前言 简称&#xff1a; “InputOutputControlByIdentifier”&#xff0c;根据标识符控制输入输出 功能&#xff1a; 根据标识符控制输入输出服务用于替换输入信号的值、电控单元内部参数或控制电子…

Telnet连接

❤️人生没有白走的路&#xff0c;每一步都算数❤️ 你是否安装Telnet没毛病&#xff0c;但登录总报错&#xff1f; 巧了&#xff0c; 我也遇到了。 于是我打开浏览器尝试搜索&#xff0c;有许多说的并不详细。 所以呢就有了这篇文章&#xff01; 首先我们准备实验环境&#xf…

oracle中替换字符串的不同写法

replace函数 replace(原字段&#xff0c;“原字段旧内容“,“原字段新内容“) 例如将DEPTNO字段值中的0替换为1&#xff1a; TRANSLATE TRANSLATE(expr, from_string, to_string) from_string 与 to_string 以字符为单位&#xff0c;对应字符一一替换。 用法示例&#xf…

详解数据结构——二叉排序树

目录 二叉排序树 二叉排序树的查找 二叉排序树的插入 二叉排序树的删除 查找时间效率分析 二叉排序树 二叉排序树&#xff0c;又称二叉查找树&#xff08;BST&#xff0c;Binary Search Tree)一棵二叉树或者是空二叉树&#xff0c;或者是具有如下性质的二叉树: 左子树上所有结…

SpringBoot - SpringBoot整合i18n实现消息国际化

文章目录1. MessageSource源码2. 项目环境搭建1. 创建项目服务auth2. 工具类 I18nUtils3. 自定义异常 CommonException4. 统一异常处理 GlobalExceptionHandler3. 业务实现1. 实体类 UserEntity2. 请求实体 UserQo3. 控制层 UserController4. 业务逻辑层 UserService5. 将异常信…

144. 授人以渔 - 如何查找 SAP UI5 官网上没有提到的控件属性的使用明细

本教程第 113 步骤, SAP UI5 应用开发教程之一百一十三 - 授人以渔 - 如何自行查询任意 SAP UI5 控件属性的文档和技术实现细节我用一整篇文章的篇幅,解答了一位学习者这个疑问: 想请教一下 sap.m.Input 控件中,value里设置的内容,比如path,type,constraints,在哪里可以查…

C++ 多态

目录 一、多态的定义和实现 1.1 多态的构成条件&#xff1a; 1.2 虚函数的重写&#xff08;覆盖&#xff09;&#xff1a; 1.3 多态的两个特殊点&#xff1a; 1.4 析构函数的重写&#xff1a; 1.5 override和final 1.6 重载&#xff0c;重定义&#xff08;隐藏&#xff…

Linux【进程地址空间】

进程地址空间&#x1f4d6;1. 地址空间概念&#x1f4d6;2. 写时拷贝&#x1f4d6;3. 虚拟地址空间的优点&#x1f4d6;1. 地址空间概念 在学习C/C内存管理时&#xff0c;我们可能见过这样一幅图&#xff1a; 但是我们可能不是很理解它&#xff0c;首先有一个问题&#xff1a;…

OpenTCS客户端开发之Web客户端(一)

越来越多人私信我关于OpenTCS的问题。可以感觉到很多人对OpenTCS的研究的人多了很多&#xff0c;很好。这些问题很多是关于算法方面的&#xff0c;也有一部分是关于UI方面的&#xff0c;毕竟OpenTCS本质上是一个算法项目&#xff0c;但是如果希望把它进行商业化&#xff0c;那免…

【微服务】服务拆分和远程调用

2.1 服务拆分原则 这里总结了微服务拆分时的几个原则&#xff1a; 不同微服务&#xff0c;不要重复开发相同业务微服务数据独立&#xff0c;不要访问其它微服务的数据库微服务可以将自己的业务暴露为接口&#xff0c;供其它微服务调用 2.2 服务拆分示例 以微服务cloud-demo为…

第三节:运算符【java】

目录 &#x1f392;运算符 &#x1f4c3;1. 什么是运算符 &#x1f4d7;2. 算术运算符 2.1 基本四则运算符&#xff1a;加减乘除模( - * / %) 2.2 增量运算符 - * % 2.3 自增/自减运算符 -- &#x1f4d9;3. 关系运算符 &#x1f4d5;4.逻辑运算符(重点) 4.1 逻辑与…

隔离出来的“陋室铭”

被隔离了 日常锻炼身体就是去公司旁边的酒店游泳&#xff0c;结果酒店里除了小阳人&#xff0c;我就喜提次密称号&#xff0c;7天隔离走起&#xff1b;又因为不想耽误家里孩子上学&#xff0c;老人外出&#xff0c;就选择了单独隔离&#xff0c;结果就拉到了单独的隔离点&…

精通Git(三)——Git分支机制

文章目录前言分支机制简述创建分支切换分支基本的分支与合并操作基本的分支操作基本的合并操作基本的合并冲突处理分支管理与分支有关的工作流长期分支主题分支远程分支推送跟踪分支拉取删除远程分支变基基本的变基操作变基操作的潜在危害只在需要的时候执行变基操作变基操作与…