一文读懂函数式接口、Lambda表达式、Stream

文章目录

  • 前言
  • 版本
  • 函数式接口
    • 定义
    • 特点
    • 使用
  • Lambda表达式
    • 主要场景
    • 用法
      • 无参写法
      • 有参写法
    • Lambda 表达式的基础:函数式接口 + 类型推断
    • 自定义函数接口使用 Lambda 表达式
    • 底层实现
      • `JDK7`
      • `JDK8`
    • `this` 的含义
      • `JDK7`
      • `JDK8`
  • Stream
    • 特点
    • 操作
      • `Stream` 流创建
      • 中间操作和终端操
        • 中间操作
          • `无状态 Stateless`
            • `filter`
            • `map`
            • `mapToInt() mapToLong() 等`
            • `flatMap() flatMapToInt() 等`
            • `peek`
          • `有状态 Stateful`
            • `distinct`
            • `limit`
        • 终端操作
          • `非短路操作`
            • `collect`
            • `reduce`
          • `短路操作`
            • `findFirst`
  • 总结
  • 个人简介

前言

  • Java 8 中引入很多有意思的新特性,本篇文章我们来聊聊其中三个比较重要的特性:函数式接口、Lambda表达式、Stream流,我们分别从示例用法、底层原理、最佳实践三个方面来了解这些特性。

版本

  • JDK 8

函数式接口

定义

  • 函数式接口是 Java 8 引入的一种接口,它只包含一个抽象方法。函数式接口的存在是为了支持 Lambda 表达式,使得我们可以使用更简洁、更灵活的方式编写匿名函数。
@FunctionalInterface
interface Calculator {
    int add(int a, int b);

    default int subtract(int a, int b) {
        return a - b;
    }

    static int multiply(int a, int b) {
        return a * b;
    }
}
  • @FunctionalInterface 注解是可选的,推荐使用。该注解会让编译器强制检查接口是否满足函数式接口定义。

特点

  • 只能有一个抽象方法,可以有参数和返回值。
  • 可以包含多个默认方法(使用 default 关键字)和静态方法(使用 static 关键字),不违反函数式接口的定义。
说明:
默认方法和静态方法在 Java 8 中引入,目的是在引入新功能的同时不改变已有实现。
从而实现接口的的逐步演进,不需要同时修改所有实现类。

使用

@FunctionalInterface
interface Calculator {
    int add(int a, int b);

    default int subtract(int a, int b) {
        return a - b;
    }

    static int multiply(int a, int b) {
        return a * b;
    }
}

public class TestMain {
    public static void main(String[] args) {
        Calculator addCalculator = (a, b) -> a + b;
        System.out.println(addCalculator.add(1, 2));
        System.out.println(addCalculator.subtract(1, 2));
    }
}

Lambda表达式

  • Lambda 表达式是一种用于传递匿名函数的简洁语法。它提供了一种更紧凑的方式来表示可以传递给方法的代码块。Lambda 表达式主要用于函数式接口,可以看作是对函数式接口的一个实现。
Calculator addCalculator = (a, b) -> a + b;

主要场景

  • 简化匿名内部类的写法,但无法简化所有匿名内部类,只能简化满足函数式接口的匿名内部类。

用法

无参写法

  • 实现创建一个简单的线程。

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}


        // JDK7 匿名内部类写法
        new Thread(new Runnable() {// 接口名
            @Override
            public void run() {// 方法名
                System.out.println("Thread run()");
            }
        }).start();
        
        // JDK8 Lambda表达式代码块写法
        new Thread(
                () -> System.out.print("Thread run()")
        ).start();

有参写法

  • 实现根据列表中字符串元素长度进行排序。

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);   
}


        // JDK7 匿名内部类写法
        List<String> list = Arrays.asList("my", "name", "is", "lorin");
        list.sort(new Comparator<String>() {
            @Override
            public int compare(String s1, String s2) {
                if (s1 == null)
                    return -1;
                if (s2 == null)
                    return 1;
                return s1.length() - s2.length();
            }
        });

        // JDK8 Lambda表达式写法
        List<String> list = Arrays.asList("my", "name", "is", "lorin");
        list.sort((s1, s2) -> {// 省略参数表的类型
            if (s1 == null)
                return -1;
            if (s2 == null)
                return 1;
            return s1.length() - s2.length();
        });

Lambda 表达式的基础:函数式接口 + 类型推断

  • Lambda 表达式除了上文中提到的函数式接口,还有一个比较重要的特性来支持 Lambda 表达式简洁的写法,即类型推断:指编译器根据上下文信息推断变量的类型,而不需要显式地指定类型。类型推断的引入是为了简化代码,并提高代码的可读性和可维护性。
    CustomerInterface<Integer> action = (Integer t) -> {
        System.out.println(this);
        return t + 1;
    };
    
    
    // 使用类型推断
    CustomerInterface<Integer> action1 = t -> {
        System.out.println(this);
        return t + 1;
    };

自定义函数接口使用 Lambda 表达式

  • 首先定义一个函数接口,函数作用是对传入的元素进行操作,最后返回操作后的元素。
// 自定义函数接口
@FunctionalInterface
public interface CustomerInterface<T> {
    T operate(T t);
}
  • 自定义的 MyStream 类来使用自定义的函数接口。
class MyStream<T> {
    private final List<T> list;

    MyStream(List<T> list) {
        this.list = list;
    }

    public void customerForEach(CustomerInterface<T> action) {
        Objects.requireNonNull(action);
        list.replaceAll(action::operate);
    }
}
  • 使用自定义的 MyStream 类实现对每一个元素的 +1 操作。
public class TestMain {
    public static void main(String[] args) {
        List<Integer> arr = Arrays.asList(1, 2, 3, 4);
        MyStream<Integer> myStream = new MyStream<>(arr);
        myStream.customerForEach(t -> t + 1);
        System.out.println(arr);
    }
}

// 输出结果
[2, 3, 4, 5]

底层实现

  • 上面我们回顾了 JDK7 和 JDK8 对匿名内部类的写法,我们发现 JDK8 中的实现更加简洁了,但实际上不仅仅语法上更加简洁,即不是纯粹的语法糖,底层实现也发生了一些变化,下面我们一起来看一下。

JDK7

  • 由于 JDK7 并不支持函数式接口、Lambda表达式,所以我们先对代码做一些简单的改造:
public interface CustomerInterface<T> {
    T operate(T t);
}

class MyStream<T> {
    private final List<T> list;

    MyStream(List<T> list) {
        this.list = list;
    }

    public void customerForEach(CustomerInterface<T> action) {
        Objects.requireNonNull(action);
        for (int i = 0; i < list.size(); i++) {
            list.set(i, action.operate(list.get(i)));
        }
    }
}

public class TestMain {
    public static void main(String[] args) {
        List<Integer> arr = Arrays.asList(1, 2, 3, 4);
        MyStream<Integer> myStream = new MyStream<>(arr);
        myStream.customerForEach(new CustomerInterface<Integer>() {
            @Override
            public Integer operate(Integer integer) {
                return integer + 1;
            }
        });
        System.out.println(arr);
    }
}
  • 使用 javap 分析字节码:
 javap -c -p  .\TestMain.class
Compiled from "TestMain.java"
public class test.TestMain {
  public test.TestMain();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_4
       1: anewarray     #2                  // class java/lang/Integer
       4: dup
       5: iconst_0
       6: iconst_1
       7: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      10: aastore
      11: dup
      12: iconst_1
      13: iconst_2
      14: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      17: aastore
      18: dup
      19: iconst_2
      20: iconst_3
      21: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      24: aastore
      25: dup
      26: iconst_3
      27: iconst_4
      28: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      31: aastore
      32: invokestatic  #4                  // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
      35: astore_1
      36: new           #5                  // class test/MyStream
      39: dup
      40: aload_1
      41: invokespecial #6                  // Method test/MyStream."<init>":(Ljava/util/List;)V
      44: astore_2
      45: aload_2
      46: new           #7                  // class test/TestMain$1  创建匿名内部类
      49: dup
      50: invokespecial #8                  // Method test/TestMain$1."<init>":()V
      53: invokevirtual #9                  // Method test/MyStream.customerForEach:(Ltest/CustomerInterface;)V
      56: getstatic     #10                 // Field java/lang/System.out:Ljava/io/PrintStream;
      59: aload_1
      60: invokevirtual #11                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      63: return
}
  • 从上面 46 行我们可以看出,JDK7 创建了真实的的匿名内部类。

JDK8

  • JDK8 我们以上述 自定义函数接口使用 Lambda 表达式 为例:
  • 使用 javap 分析字节码可以发现,Lambda 表达式 被封装为一个内部的私有方法并通过 InvokeDynamic 调用,而不是像 JDK7 那样创建一个真实的匿名内部类。
 javap -c -p  .\TestMain.class
Compiled from "TestMain.java"
public class test.TestMain {
  public test.TestMain();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_4
       1: anewarray     #2                  // class java/lang/Integer
       4: dup
       5: iconst_0
       6: iconst_1
       7: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      10: aastore
      11: dup
      12: iconst_1
      13: iconst_2
      14: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      17: aastore
      18: dup
      19: iconst_2
      20: iconst_3
      21: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      24: aastore
      25: dup
      40: aload_1
      41: invokespecial #6                  // Method test/MyStream."<init>":(Ljava/util/List;)V
      44: astore_2
      45: aload_2
      46: invokedynamic #7,  0              // InvokeDynamic #0:operate:()Ltest/CustomerInterface;  InvokeDynamic 调用
      51: invokevirtual #8                  // Method test/MyStream.customerForEach:(Ltest/CustomerInterface;)V
      54: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      57: aload_1
      58: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      61: return

  private static java.lang.Integer lambda$main$0(java.lang.Integer); // lambda 表达式被封装为内部方法
    Code:
       0: aload_0
       1: invokevirtual #11                 // Method java/lang/Integer.intValue:()I
       4: iconst_1
       5: iadd
       6: invokestatic  #3                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
       9: areturn
}

this 的含义

  • 从上面我们可以知道 JDK7 和 JDK8 对匿名内部类不仅写法上不一致,底层原理也不相同。因此,如果我们在两种写法种使用 this 关键字,两者是一样的?先说答案:不一样,JDK7this 指向创建的匿名内部内,而 JDK8 中Lambda表达式并不会创建真实存在的类,指向的是当前类。
  • 下面我们结合实际案例来看一下:

JDK7

        CustomerInterface<Integer> action = new CustomerInterface<Integer>() {
            @Override
            public Integer operate(Integer integer) {
                System.out.println(this);
                return integer + 1;
            }
        };

        CustomerInterface<Integer> action1 = new CustomerInterface<Integer>() {
            @Override
            public Integer operate(Integer integer) {
                System.out.println(this);
                return integer + 1;
            }
        };

        System.out.println(action.operate(2));
        System.out.println(action1.operate(2));
        
        // 输出
        test.TestMain$1@8939ec3
        3
        test.TestMain$2@456bf9ce
        3
  • 可以看到两个 this 输出地址不同,分别指向自身的匿名内部类对象。

JDK8

public class TestMain {

    CustomerInterface<Integer> action = t -> {
        System.out.println(this);
        return t + 1;
    };

    CustomerInterface<Integer> action1 = t -> {
        System.out.println(this);
        return t + 1;
    };

    public static void main(String[] args) {
        TestMain testMain = new TestMain();
        System.out.println(testMain.action.operate(2));
        System.out.println(testMain.action1.operate(2));
    }
}

// 输出
test.TestMain@1d81eb93
3
test.TestMain@1d81eb93
3
  • 可以看到,两个 this 都指向同一个 testMain 对象,因为我们从前文我们可以知道 JDK8Lambda 表达式 被封装为一个内部的私有方法并通过 InvokeDynamic 调用,而不是创建一个真实的匿名内部类。

Stream

  • Stream 是一种用于处理集合数据的高级抽象,它允许我们以声明式的方式对集合进行操作。
  • 函数式接口提供了Lambda表达式的类型,Lambda表达式提供了一种简洁的语法来定义匿名内部类,而 Stream 提供了一种声明式的方式来处理集合数据,并与Lambda表达式无缝结合,共同支持函数式编程在Java中的应用。

特点

  • Stream 不存储数据,按照特定的规则进行计算,最后返回计算结果。
  • Stream 不改变源数据源,而返回一个新的数据源。
  • Stream 是惰性计算,只有调用终端操作时,中间操作才会执行。

操作

Stream 流创建

  • Stream 流支持并行流和串行流两种方式,串行流每个元素按照顺序依次处理,并行流会将流中元素拆分为多个子任务进行处理,最后再合并结果,从而提高处理效率。
    List<String> list = Arrays.asList("11", "2222", "333333");
    // 串行流
    list.stream().map(String::toString).collect(Collectors.toList());
    // 并行流
    list.parallelStream().map(String::toString).collect(Collectors.toList());
    list.stream().parallel().map(String::toString).collect(Collectors.toList());

中间操作和终端操

中间操作
  • 只会记录操作不会立即执行,中间操作可以细分为:无状态 Stateless有状态 Stateful 两种。
无状态 Stateless
  • 指元素不受其它元素影响,可以继续往下执行,比如 filter() map() mapToInt() 等。
filter
  • 用于筛选符合条件的元素,下一步只会拿到符合条件的元素。
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
// 获取空字符串的数量
long count = strings.stream().filter(string -> string.isEmpty()).count();
map
  • 用于将一个流中的元素通过指定的映射函数转换为另一个流。返回类型必须是传入类型或传入类型的子类型。
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用 map 方法将列表中的每个元素乘以2
        List<Integer> doubledNumbers = numbers.stream()
                .map(n -> n * 2)
                .collect(Collectors.toList());
mapToInt() mapToLong() 等
  • mapToInt() 方法用于将流中的元素映射为 int 类型的流。IntStream 是针对 int 类型数据进行优化的特殊流,提供了更高效的操作和更方便的处理方式。当处理基本类型 int 数据时,推荐使用 IntStream,可以提高代码的性能和可读性。
  • mapToLong() 方法用于将流中的元素映射为 long 类型的流。
        // 整数列表
        Long[] numbers = {1, 2, 3, 4, 5};

        // 使用 mapToLong() 方法将每个整数乘以自身,并收集到一个 LongStream 流中
        LongStream squares = Arrays.stream(numbers).mapToLong(t -> t * t);
        squares.sum();
flatMap() flatMapToInt() 等
  • flatMap()用于将流中的每个元素映射为一个流,然后将所有映射得到的流合并成一个新的流。
  • flatMapToInt()flatMap() 的区别在于返回的流为 IntStream
        // 字符串列表
        List<String> words = Arrays.asList("Java is fun", "Stream API is powerful", "FlatMap is useful");

        // 使用 flatMap() 提取每个字符串中的单词,并放入一个新的流中
        Stream<String> wordStream = words.stream()
                .flatMap(str -> Arrays.stream(str.split("\\s+")));

        // 打印流中的每个单词
        wordStream.forEach(System.out::println);
        
// 输出
Java
is
fun
Stream
API
is
powerful
FlatMap
is
useful
peek
  • 用于在流的每个元素上执行指定的操作,同时保留流中的元素。peek() 方法不会改变流中的元素,而是提供一种查看每个元素的机会,通常用于调试、日志记录或记录流中的中间状态。
        // 整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用 peek() 打印每个元素,并将元素乘以2,然后收集到一个新的列表中
        List<Integer> doubledNumbers = numbers.stream()
                .peek(num -> System.out.println("Original: " + num))
                .map(num -> num * 2)
                .peek(doubledNum -> System.out.println("Doubled: " + doubledNum))
                .collect(Collectors.toList());

        // 打印新列表中的元素
        System.out.println("Doubled Numbers: " + doubledNumbers);
有状态 Stateful
  • 指元素受到其它元素影响,比如 distinct() 去重,需要处理完所有元素才能往下执行。
distinct
  • 用于去除流中重复的元素,返回一个去重后的新流。distinct() 方法根据元素的 equals() 方法来判断是否重复,因此流中的元素必须实现了 equals() 方法以确保正确的去重。
        // 字符串列表
        List<String> words = Arrays.asList("hello", "world", "hello", "java", "world");

        // 使用 distinct() 方法获取不重复的单词,并收集到一个新的列表中
        List<String> uniqueWords = words.stream()
                                        .distinct()
                                        .collect(Collectors.toList());

        // 打印不重复的单词列表
        System.out.println("Unique Words: " + uniqueWords);
limit
  • 用于限制流中元素的数量,返回一个包含了指定数量元素的新流。limit() 方法通常用于在处理大型数据集时,限制处理的数据量,以提高性能或减少资源消耗。需要注意的,返回的元素不一定是前三个。
        // 整数列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // 使用 limit() 方法获取前3个元素,并收集到一个新的列表中
        List<Integer> limitedNumbers = numbers.stream()
                                              .limit(3)
                                              .collect(Collectors.toList());

        // 打印前3个元素
        System.out.println("Limited Numbers: " + limitedNumbers);
终端操作
  • 调用终端操作计算会立即开始执行,终端操作可以细分为:非短路操作短路操作
非短路操作
  • 非短路操作:需要处理完所有元素才可以拿到结果,比如 forEach() forEachOrdered()
collect
  • 将流中的元素收集到一个集合或者其他数据结构中。下面是一些常见的用法:
        // 将流中的元素收集到一个列表中:
        List<String> list = stream.collect(Collectors.toList());

        // 将流中的元素收集到一个集合中:
        Set<String> set = stream.collect(Collectors.toSet());

        // 将流中的元素收集到一个指定类型的集合中:
        ArrayList<String> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));

        // 将流中的元素收集到一个字符串中,使用指定的分隔符连接:
        String result = stream.collect(Collectors.joining(", "));

        // 将流中的元素收集到一个 Map 中,根据指定的键值对:
        Map<Integer, String> map = stream.collect(Collectors.toMap(String::length, Function.identity()));

        // 对流中的元素进行分组:
        Map<Integer, List<String>> groupedMap = stream.collect(Collectors.groupingBy(String::length));

        // 对流中的元素进行分区:
        Map<Boolean, List<String>> partitionedMap = stream.collect(Collectors.partitioningBy(s -> s.length() > 3));

        // 对流中的元素进行统计:
        IntSummaryStatistics statistics = stream.collect(Collectors.summarizingInt(String::length));
reduce
  • 用于将流中的元素组合成一个值。
  • 灵活性:reduce() 方法提供了灵活的参数选项,可以根据需求选择不同的重载形式,包括指定初始值、选择累加器函数和组合器函数等,使得它可以适用于各种场景。
  • 统一操作:reduce() 方法提供了一种统一的方式来对流中的元素进行组合操作,不论是求和、求积、字符串拼接还是其他任何类型的组合操作,都可以使用 reduce() 方法来实现,这样可以减少代码重复,提高代码的可读性和可维护性。
  • 并行流支持:在并行流中,reduce() 方法可以更高效地利用多核处理器,通过并行化操作来提高性能。使用合适的组合器函数,可以在并行流中正确地合并部分结果,从而实现更高效的并行计算。而 sum() 函数是串行的。
        // 将流中的元素累加求和:
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b);
        System.out.println("Sum: " + sum.orElse(0));  // 输出 15

        // 使用初始值进行累加求和:
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        int sum = numbers.stream().reduce(0, (a, b) -> a + b);
        System.out.println("Sum: " + sum);  // 输出 15

        // 使用初始值和组合器函数在并行流中进行累加求和:
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        int sum = numbers.parallelStream().reduce(0, (a, b) -> a + b, Integer::sum);
        System.out.println("Sum: " + sum);  // 输出 15
短路操作
  • 短路操作:得到符合条件的元素就可以立即返回,而不用处理所有元素,比如 anyMatch() allMatch()
findFirst
  • 用于获取流中的第一个元素(如果存在的话),返回一个 Optional 对象。注意:返回值不一定为第一个元素。
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        Optional<Integer> firstNumber = numbers.stream().findFirst();
        if (firstNumber.isPresent()) {
            System.out.println("First number: " + firstNumber.get());  // 输出 First number: 1
        } else {
            System.out.println("No elements found in the stream.");
        }

总结

  • 函数式接口、Lambda表达式和Stream是Java 8引入的重要特性,它们使得Java代码更加简洁、灵活、易读。函数式接口定义了一种新的编程模式,Lambda表达式提供了一种更加简洁的语法来实现函数式接口,Stream则提供了一套丰富的操作方法来处理集合数据。通过这些特性的组合应用,可以极大地提高Java代码的开发效率和质量。
  • 本文篇幅有限,Stream 部分仅介绍了基本定义和常见的用法,没有对 Stream 底层原理(并行、串行等)做深入解析,这部分将在下一篇文章中介绍。

个人简介

👋 你好,我是 Lorin 洛林,一位 Java 后端技术开发者!座右铭:Technology has the power to make the world a better place.

🚀 我对技术的热情是我不断学习和分享的动力。我的博客是一个关于Java生态系统、后端开发和最新技术趋势的地方。

🧠 作为一个 Java 后端技术爱好者,我不仅热衷于探索语言的新特性和技术的深度,还热衷于分享我的见解和最佳实践。我相信知识的分享和社区合作可以帮助我们共同成长。

💡 在我的博客上,你将找到关于Java核心概念、JVM 底层技术、常用框架如Spring和Mybatis 、MySQL等数据库管理、RabbitMQ、Rocketmq等消息中间件、性能优化等内容的深入文章。我也将分享一些编程技巧和解决问题的方法,以帮助你更好地掌握Java编程。

🌐 我鼓励互动和建立社区,因此请留下你的问题、建议或主题请求,让我知道你感兴趣的内容。此外,我将分享最新的互联网和技术资讯,以确保你与技术世界的最新发展保持联系。我期待与你一起在技术之路上前进,一起探讨技术世界的无限可能性。

📖 保持关注我的博客,让我们共同追求技术卓越。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vue.js el-tooltip根据文字长度控制是否提示toolTip

一、需求&#xff1a;如何判断当前文本文字是否超出文本长度&#xff0c;是否需要出现提示toolTip。效果图如下&#xff1a; 二、实现&#xff1a; 1、表格字段鼠标放置el-popover出现 “引用主题” 的具体内容&#xff1b; <!-- 表格字段&#xff1a;引用主题 --> <…

006 矢量数据属性表的使用和关联

1 空间属性关系 1.1 数据导入和图层组合 读取数据 除了使用关键项的连接之外&#xff0c;还有一种根据属性组合中的空间位置关系进行组合的方法。 在本节中&#xff0c;我们将介绍一种方法&#xff0c;用于将空间关系中的多边形数据属性与任意点数据属性相结合。 我们使用的是…

文件上传漏洞--Upload-labs--Pass11--(GET)00绕过

一、环境准备&#xff1a; php版本&#xff1a;推荐 php5.2.17&#xff08;官方推荐版本&#xff09;。小于php5.3.4也可以&#xff0c;但是要在 php.ini 配置文件中手动将 magic_quotes_gpc 的状态改为 Off。 magic_quotes_gpc的作用是对 get请求、post请求、cookie...传入的…

DIcom调试Planar configuration

最近和CBCT组同事调dicom图像 这边得图像模块老不兼容对方得dicom文件。 vtk兼容&#xff0c;自己写得原生解析不兼容。 给对方调好了格式&#xff0c;下次生成文件还会有错。 简单记录下&#xff0c;日后备查。 今天对方又加了 个字段&#xff1a;Planar configuration 查…

【开源】JAVA+Vue.js实现高校学生管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生管理模块2.2 学院课程模块2.3 学生选课模块2.4 成绩管理模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 学生表3.2.2 学院课程表3.2.3 学生选课表3.2.4 学生成绩表 四、系统展示五、核心代码5.1 查询课程5.2 新…

FPS游戏漫谈弱网环境时延优化

游戏在弱网情况下会变得体验很差&#xff0c;玩家的直观感受就是我的操作怎么没有反应&#xff0c;整个游戏世界都是一卡一顿的。这个就是因为网络问题导致了游戏体验变差。 那什么是弱网环境&#xff1f;弱网环境就是指网络不好的环境&#xff0c;尤其是移动网络下&#xff0…

Java毕业设计-基于springboot的人才招聘管理系统-第68期

获取源码资料&#xff0c;请移步从戎源码网&#xff1a;从戎源码网_专业的计算机毕业设计网站 项目介绍 基于springboot的人才招聘管理系统&#xff1a;前端jquery、easyui&#xff0c;后端 maven、springmvc、spring、jpa、hibernate&#xff0c;集成职位浏览、我的简历、投…

web前端宝藏题库,适用于0-3年前端工程师...

前些天意外认识了一个前端大佬&#xff0c;某大厂在职&#xff0c;我怕打扰就没敢多聊&#xff0c;没想到大佬挺平易近人&#xff0c;很亲切的给我出谋划策&#xff0c;还给了我一套面试题&#xff0c;难度不是很高&#xff0c;但是胜在全面十分契合我现在的状况&#xff0c;所…

在VsCode中通过Cookie登录LeetCode

在vscode中配置好leetcode之后&#xff0c;一般最常用的就是通过cookie登录leetcode ; 首先点击sign in &#xff0c; 然后选择最下面的 &#xff0c; LeetCode Cookie ! 然后输入username(也就是你的lc用户名) 或者 你leetcode绑定的邮箱 ; 输入完成之后 ; 就是要你输入你的l…

UE5 C++ 创建可缩放的相机

一.要将相机设置在Pawn类里 1.在MyPawn头文件里&#xff0c;加上摇臂和相机组件 #include "GameFramework/SpringArmComponent.h" #include "Camera/CameraComponent.h" 2.在Pawm里声明SceneComponet&#xff0c;SpringArmComponent,CameraComponent组件…

6.【架构师成长之路】职场新人:维护一张能力图谱

文章目录 导言一、能力图谱就是技能体系1、时刻提醒你&#xff0c;你有一个目标2、你可以知道靠近目标的具体方式3、你会变得更加自信 二、周期性review自己的能力图谱1、review能力项进度2、review能力项完整度3、固定周期review 本文总结说明 导言 上两篇文章我们讲了&#…

C语言中关于#include的一些小知识

写代码的过程中&#xff0c;因为手误&#xff0c;重复包含了头文件 可以看到没有报错 如果是你自己编写的头文件&#xff0c;那么如果没加唯一包含标识的话&#xff0c;那么编译器会编译报错的。如果是系统自带的头文件&#xff0c;由于其每个头文件都加了特殊标识&#xff0c…

【动态规划】【矩阵快速幂】LeetCode2851. 字符串转换

作者推荐 【深度优先搜索】【树】【有向图】【推荐】685. 冗余连接 II 涉及知识点 【矩阵快速幂】封装类及测试用例及样例 LeetCode 2851. 字符串转换 给你两个长度都为 n 的字符串 s 和 t 。你可以对字符串 s 执行以下操作&#xff1a; 将 s 长度为 l &#xff08;0 <…

安达发|APS排产软件的机台产线任务甘特图功能详解

在现代制造业中&#xff0c;高级计划与排产是制造业运营的关键环节。为了提高生产效率、降低成本并确保产品质量&#xff0c;企业需要对生产过程进行精细化管理。APS&#xff08;高级计划与排产&#xff09;系统作为一种先进的生产计划和调度工具&#xff0c;可以帮助企业实现这…

在计算机上设置和使用 KVM

为了使用 gem5 的 KVMCPU 来快进你的模拟&#xff0c;你必须有一个 KVM 兼容的处理器并且在你的机器上安装了 KVM。本页将引导您完成在计算机上启用 KVM 并将其与 gem5 一起使用的过程。 注意&#xff1a;以下教程假设 X86 Linux 主机。本教程的各个部分可能不适用于其他体系结…

IBM V5000存储更换控制器及电源模块

LED故障状态 后面板故障状态 系统内电源模块报错信息(可安全卸下状态为"是"&#xff0c;此时可直接热拔插) 控制器报错信息&#xff08;当前已是脱机状态可直接拔插&#xff0c;该型号控制器不需要更换缓存可直接热拔插更换&#xff09; 更换故障备件应先核对新旧备件…

CTFHub技能树web之RCE(二)

第五题&#xff1a;远程包含 根据题目&#xff0c;使用远程包含进行 打开phpinfo&#xff0c;可以看到allow_url_fopen和allow_url_include都是On&#xff0c;因此可以使用php://input&#xff0c;由于代码会检查file中的内容&#xff0c;因此不能够使用php://filter包含文件&a…

C++正则表达式笔记

最近翻了翻正则表达式的一些资料&#xff0c;做个记录。 1、微软官方 <regex> 函数 | Microsoft Learn 2、正则表达式语法简介 正则表达式语法简介 - 简书 3、正则表达式基础语法大全 正则表达式基础语法大全_正则表达式语法大全-CSDN博客 4、练习 &#xff08;1…

ffmpeg TS复用代码详解——mpegtsenc.c

一、mpegtsenc.c 整体架构 二、主要函数 mpegts_write_pes(AVFormatContext *s, AVStream *st, const uint8_t *payload, int payload_size, int64_t pts, int64_t dts)这个函数就是TS打包的主函数了&#xff0c;这个函数主要功能就是把一帧数据拆分成188字节的TS包&#xff0…

openai DALL-E 3 从文本描述生成图像原理通俗解释

序言 在数字时代&#xff0c;图像生成技术正日益成为人工智能领域的热点。 本讨论将重点聚焦于两个备受瞩目的模型&#xff1a;DALL-E和其他主流AI绘图方法。 我们将探讨它们的优势、局限性以及未来的发展方向。通过比较分析&#xff0c;我们期望能够更全面地了解这些技术&a…