7 - 函数式编程

文章目录

Interface

初衷是面向抽象,提高扩展性。Interface 修改的时候,实现它的类也必须跟着改。

为了解决接口的修改与现有的实现不兼容的问题。新 interface 的方法可以用defaultstatic修饰,这样就可以有方法体,实现类也不必重写此方法。

这 2 个修饰符的区别主要也是普通方法和静态方法的区别:

  • default修饰的方法,是普通实例方法,可以用this调用,可以被子类继承、重写
  • static修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用Interface调用
java 复制代码
public interface InterfaceNew {
    static void sm() {
        System.out.println("interface提供的方式实现");
    }

    default void def() {
        System.out.println("interface default方法");
    }
    //须要实现类重写
    void f();
}

public interface InterfaceNew1 {
    default void def() {
        System.out.println("InterfaceNew1 default方法");
    }
}

这里可以看到两个接口都有 def 方法,而且两个接口没有继承关系,那么如果一个类实现了这两个接口,就必须要重写 def 方法

java 复制代码
public class InterfaceNewImpl implements InterfaceNew , InterfaceNew1{
    public static void main(String[] args) {
        InterfaceNewImpl interfaceNew = new InterfaceNewImpl();
        interfaceNew.def();
    }
	// `def` 方法的重写
    @Override
    public void def() {
        InterfaceNew1.super.def();
    }
	//对应的方法实现
    @Override
    public void f() {
    }
}

这样接口就可有自己的方法实现了!!!那岂不是和抽象类很像了???

区别:

  • 接口多实现,类单继承
  • 接口的方法是 public abstract 修饰,变量是 public static final 修饰。 abstract class 可以用其他修饰符

牢记:接口是接口,类是类

函数式接口

SAM 接口:Single Abstract Method interfaces 有且只有一个抽象方法,但可以有多个非抽象方法的接口

在包 java.util.function 包里

Lambda表达式

Lambda 表达式可以使代码变的更加简洁紧凑,提高代码的可读性

语法格式

java 复制代码
(parameters) -> expression 或
(parameters) ->{ statements; }

替代匿名类

过去给方法传动态参数的唯一方法是使用内部类

  1. Runnnable接口
java 复制代码
//以前的格式
new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("The runable now is using!");
            }
}).start();
//用lambda的格式
new Thread(() -> System.out.println("It's a lambda function!")).start();
  1. Comparator接口
java 复制代码
List<Integer> strings = Arrays.asList(1, 2, 3);

Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
    	return o1 - o2;
    }
});
//Lambda
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);
  1. Listener接口
java 复制代码
JButton button = new JButton();
button.addItemListener(new ItemListener() {
	@Override
	public void itemStateChanged(ItemEvent e) {
   		e.getItem();
	}
});
//lambda
button.addItemListener(e -> e.getItem());
  1. 自定义接口
java 复制代码
@FunctionalInterface
public interface LambdaInterface {
	void f();
}
//使用
public class LambdaClass {
    public static void forEg() {
        lambdaInterfaceDemo(()-> System.out.println("自定义函数式接口"));
    }
    //函数式接口参数
    static void lambdaInterfaceDemo(LambdaInterface i){
        i.f();
    }
}

集合迭代

java 复制代码
void lamndaFor() {
        List<String> strings = Arrays.asList("1", "2", "3");
        //传统foreach
        for (String s : strings) {
            System.out.println(s);
        }
        //Lambda foreach
        strings.forEach((s) -> System.out.println(s));
        //or 这里把参数也一同省略了
        strings.forEach(System.out::println);
        //map
        Map<Integer, String> map = new HashMap<>();
        map.forEach((k,v)->System.out.println(v));
}

方法引用

:: 关键字来传递方法或者构造函数引用,要求表达式的返回类型是 functiuon-interface

java 复制代码
public class LambdaClassSuper {
    LambdaInterface sf(){
        return null;
    }
}

public class LambdaClass extends LambdaClassSuper {
    public static LambdaInterface staticF() {
        return null;
    }
    public LambdaInterface f() {
        return null;
    }
    void show() {
        //1.调用静态函数,返回类型必须是functional-interface
        LambdaInterface t = LambdaClass::staticF;

        //2.实例方法调用
        LambdaClass lambdaClass = new LambdaClass();
        LambdaInterface lambdaInterface = lambdaClass::f;

        //3.超类上的方法调用
        LambdaInterface superf = super::sf;

        //4. 构造方法调用
        LambdaInterface tt = LambdaClassSuper::new;
    }
}

作用域范围

和匿名内部类一样,不要在 Lambda 表达式主体内对方法内的局部变量进行修改,否则编译也不会通过

Lambda 表达式中要用到的,但又未在 Lambda 表达式中声明的变量,必须声明为 final 或者是 effectively final,否则就会出现编译错误

final 变量:

  • 一旦被初始化之后,其值就不能被改变,不能再次修改
  • 可以是类的成员变量、局部变量或者方法参数
  • final变量在声明时可以不立即初始化,但必须在成为非局部作用域之前被初始化

effectively final 变量:

  • 指在代码的实际执行过程中,变量的值在初始化之后不会被改变,尽管在语法上它并没有被声明为final
  • 通常用于匿名类和Lambda表达式中。在这些场景下,如果变量不在这个匿名类或Lambda表达式内部被声明,那么它必须不可变,即使没有使用final关键字
  • 实际上是一个语言规则,确保变量在匿名类或Lambda表达式中安全使用
  1. 把变量声明为 static
java 复制代码
public class ModifyVariable2StaticInsideLambda {
    static int limit = 10;
    public static void main(String[] args) {
        Runnable r = () -> {
            limit = 5;
            for (int i = 0; i < limit; i++) {
                System.out.println(i);
            }
        };
        new Thread(r).start();
    }
}
//输出
// 0 1 2 3 4   方案可行
  1. 变量声明为 AtomicInteger
    AtomicInteger 可以确保 int 值的修改是原子性 的,可以使用 set() 方法设置一个新的 int 值,get() 方法获取当前的int
java 复制代码
public class ModifyVariable2AtomicInsideLambda {
    public static void main(String[] args) {
        final AtomicInteger limit = new AtomicInteger(10);
        Runnable r = () -> {
            limit.set(5);
            for (int i = 0; i < limit.get(); i++) {
                System.out.println(i);
            }
        };
        new Thread(r).start();
    }
}
  1. 使用数组
    在声明数组的时候设置为 final,但更改int的值时却修改的是数组的一个元素
java 复制代码
public class ModifyVariable2ArrayInsideLambda {
    public static void main(String[] args) {
        final int [] limits = {10};
        Runnable r = () -> {
            limits[0] = 5;
            for (int i = 0; i < limits[0]; i++) {
                System.out.println(i);
            }
        };
        new Thread(r).start();
    }
}

this关键字

Lambda 表达式并不会引入新的作用域,这一点和匿名内部类是不同的。也就是说,Lambda 表达式主体内使用的 this 关键字和其所在的类实例相同。

Stream流

Stream不存储数据,可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。

就好像一个高级的迭代器,但只能遍历一次。

  1. 通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。
  2. 方法参数都是函数式接口类型
  3. 一个 Stream 只能操作一次,操作完就关闭了,继续使用这个 stream 会报错。
  4. Stream 不保存数据,不改变数据源

流类型

  1. stream 串行流
  2. parallelStream 并行流,可多线程执行

创建流

如果是数组 的话,可以使用Arrays.stream()或者 Stream.of() 创建流;

如果是集合 的话,可以直接使用 stream() 方法创建流,因为该方法已经添加到 Collection 接口中

java 复制代码
public class CreateStreamDemo {
    public static void main(String[] args) {
        String[] arr = new String[]{"加油1", "加油2", "加油3"};
        Stream<String> stream = Arrays.stream(arr);

        stream = Stream.of("加油1", "加油2", "加油3");

        List<String> list = new ArrayList<>();
        list.add("加油1");
        list.add("加油2");
        list.add("加油3");
        stream = list.stream();
    }
}

注: of() 方法内部其实调用了Arrays.stream()方法

java 复制代码
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}

集合还可以调用 parallelStream() 方法创建并发流,默认使用的是 ForkJoinPool.commonPool()线程池

java 复制代码
List<Long> aList = new ArrayList<>();
Stream<Long> parallelStream = aList.parallelStream();

中间操作流

过滤 filter

通过filter()方法可以从流中筛选出我们想要的元素
filter() 方法接收的是一个 Predicate(Java 8 新增的一个函数式接口,接受一个输入参数返回一个布尔值结果)类型的参数
forEach() 方法接收的是一个 Consumer(Java 8 新增的一个函数式接口,接受一个输入参数并且无返回的操作)类型的参数

java 复制代码
public class FilterStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("周杰伦");
        list.add("王力宏");
        list.add("陶喆");
        list.add("林俊杰");
        Stream<String> stream = list.stream().filter(element -> element.contains("王"));
        stream.forEach(System.out::println);
    }
}

映射 map

通过某种操作把一个流中的元素转化成新的流中的元素,map()接收的是一个 Function(Java 8 新增的一个函数式接口,接受一个输入参数 T,返回一个结果 R)类型的参数

java 复制代码
public class MapStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("周杰伦");
        list.add("王力宏");
        list.add("陶喆");
        list.add("林俊杰");
        Stream<Integer> stream = list.stream().map(String::length);
        stream.forEach(System.out::println);
    }
}

去重 distinct

它能够去除流中的重复元素

java 复制代码
public class StreamDistinctExample {
    public static void main(String[] args) {
        // 创建一个包含重复数字的列表
        List<Integer> numbersWithDuplicates = Arrays.asList(1, 2, 2, 3, 4, 4, 5, 5, 5);

        // 使用Stream API对列表进行处理,去除重复的数字
        List<Integer> distinctNumbers = numbersWithDuplicates.stream()  // 创建一个Stream
            .distinct()  // 去除重复元素
            .collect(Collectors.toList());  // 将结果收集到一个新的列表中
        // 输出结果
        distinctNumbers.forEach(System.out::println);
    }
}

排序 sorted

它可以对流中的元素进行排序

java 复制代码
public class StreamSortedExample {
    public static void main(String[] args) {
        // 创建一个包含数字的列表,这些数字将被排序
        List<Integer> numbers = Arrays.asList(3, 5, 1, 4, 2);

        // 使用Stream API对列表进行处理,对数字进行升序排序
        List<Integer> sortedNumbers = numbers.stream()  // 创建一个Stream
            .sorted()  // 对流中的元素进行排序
            .collect(Collectors.toList());  // 将排序后的流收集到一个新的列表中
        // 输出排序后的结果
        sortedNumbers.forEach(System.out::println);
    }
}

这里使用的是`sorted()``的默认方案,是进行自然排序,即为进行升序排序

可以传递一个Comparator给sorted()方法:来实现改变排序规则

java 复制代码
// 对字符串列表按照字符串长度降序排序
List<String> strings = Arrays.asList("banana", "apple", "cherry", "date");
List<String> sortedStrings = strings.stream()
    .sorted((s1, s2) -> s2.length() - s1.length())  // 按字符串长度降序排序
    .collect(Collectors.toList());
sortedStrings.forEach(System.out::println);

限制 limit

用来限制流中元素的数量,当想要从流中获取前 n 元素时,limit()操作非常有用

java 复制代码
public class StreamLimitExample {
    public static void main(String[] args) {
        // 创建一个包含多个数字的列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 使用Stream API对列表进行处理,获取前5个数字
        List<Integer> firstFiveNumbers = numbers.stream()  // 创建一个Stream
            .limit(5)  // 限制流中的元素数量为5
            .collect(Collectors.toList());  // 将结果收集到一个新的列表中
        // 输出获取到的数字
        firstFiveNumbers.forEach(System.out::println); // 1,2,3,4,5
    }
}

跳过 skip

用于跳过流中的前n个元素

java 复制代码
// 获取列表中的第6到10个数字
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<Integer> sixthToTenthNumbers = numbers.stream()
    .skip(5)  // 跳过前5个数字
    .limit(5)  // 限制流中的元素数量为5
    .collect(Collectors.toList());

sixthToTenthNumbers.forEach(System.out::println);  // 6,7,8,9,10

flatMap

可以简洁地将复杂的多级结构简化为单级结构

java 复制代码
public class StreamFlatMapExample {
    public static void main(String[] args) {
        // 创建一个包含多个列表的列表,即列表的列表
        List<List<String>> listOfLists = Arrays.asList(
            Arrays.asList("a1", "a2"),
            Arrays.asList("b1", "b2", "b3"),
            Arrays.asList("c1")
        );

        // 使用Stream API对列表进行处理,将列表的列表扁平化为一个单一的流
        List<String> flattenedList = listOfLists.stream()  // 创建一个Stream
            .flatMap(list -> list.stream())  // 将每个列表扁平化为流
            .collect(Collectors.toList());  // 将扁平化的流收集到一个新的列表中

        // 输出扁平化后的列表
        flattenedList.forEach(System.out::println);
        // a1 a2 b1 b2 b3 c1
    }
}

终结操作流

forEach

java 复制代码
public class MapStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("周杰伦");
        list.add("王力宏");
        list.add("陶喆");
        list.add("林俊杰");
        Stream<Integer> stream = list.stream().map(String::length);
        stream.forEach(System.out::println);
    }
}

count

count操作是一个终端操作,它返回一个long类型的值,表示流中元素的数量

java 复制代码
public class StreamCountExample {
    public static void main(String[] args) {
        // 创建一个包含一些数字的列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 使用Stream API对列表进行处理,计算大于5的数字的数量
        long count = numbers.stream()  // 创建一个Stream
                            .filter(num -> num > 5)  // 过滤出大于5的数字
                            .count();  // 计算过滤后的数量

        // 输出结果
        System.out.println("大于5的数字的数量是: " + count);
    }
}

min/max

用于找出流中元素的最小值和最大值。这些操作返回的是Optional类型的结果,因为流中可能不包含任何元素,在这种情况下,Optional将不会包含任何值

java 复制代码
public class StreamMinMaxExample {
    public static void main(String[] args) {
        // 创建一个包含数字的列表
        List<Integer> numbers = Arrays.asList(10, 4, 5, 8, 6, 11, 2);

        // 使用Stream API找出数字中的最小值
        Optional<Integer> minNumber = numbers.stream()
            .min(Integer::compareTo); // 使用Integer::compareTo作为比较器

        // 使用Stream API找出数字中的最大值
        Optional<Integer> maxNumber = numbers.stream()
            .max(Integer::compareTo); // 使用Integer::compareTo作为比较器

        // 输出最小值和最大值
        minNumber.ifPresent(System.out::println); // 输出: 2
        maxNumber.ifPresent(System.out::println); // 输出: 11
    }
}

这两个方法都需要一个Comparator作为参数来定义如何比较元素。在这个例子中,我们使用Integer::compareTo作为比较器,它是比较两个Integer对象的标准方式

collect

将流转换成其他形式。最常见的用途是将流收集到一个集合中,如列表、集合或映射。

java 复制代码
public class StreamCollectExample {
    public static void main(String[] args) {
        // 创建一个包含数字的列表
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 使用Stream API将流收集到一个列表中
        List<Integer> collectedNumbers = numbers.stream()
            .collect(Collectors.toList());

        // 输出收集到的列表
        collectedNumbers.forEach(System.out::println);
    }
}

除了toList()Collectors类还提供了许多其他有用的收集器,例如:

  • toSet(): 将流收集到一个Set中,自动去除重复元素。
  • toMap(): 将流收集到一个Map中,需要提供键和值的函数。
  • groupingBy(): 根据某个属性对流中的元素进行分组。
  • joining(): 将流中的元素连接成一个字符串。

匹配

Stream 类提供了三个方法可供进行元素匹配,它们分别是:

  • anyMatch(),只要有一个元素匹配传入的条件,就返回 true。

  • allMatch(),只有有一个元素不匹配传入的条件,就返回 false;如果全部匹配,则返回 true。

  • noneMatch(),只要有一个元素匹配传入的条件,就返回 false;如果全部不匹配,则返回 true。

java 复制代码
public class MatchStreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("周杰伦");
        list.add("王力宏");
        list.add("陶喆");
        list.add("林俊杰");

        boolean  anyMatchFlag = list.stream().anyMatch(element -> element.contains("王"));
        boolean  allMatchFlag = list.stream().allMatch(element -> element.length() > 1);
        boolean  noneMatchFlag = list.stream().noneMatch(element -> element.endsWith("沉"));
        System.out.println(anyMatchFlag);  // true
        System.out.println(allMatchFlag);  // true
        System.out.println(noneMatchFlag); // true
    }
}

组合 reduce

把 Stream 中的元素组合起来,最终得到一个汇总的结果。

有两种用法

  • Optional<T> reduce(BinaryOperator<T> accumulator)
    没有起始值,只有一个参数,就是运算规则,此时返回 Optional
  • T reduce(T identity, BinaryOperator<T> accumulator)
    有起始值,有运算规则,两个参数,此时返回的类型和起始值类型一致
java 复制代码
// 创建一个包含数字的列表
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// 使用Stream API和reduce操作计算数字的总和
Optional<Integer> sum = numbers.stream()
    .reduce((x, y) -> x + y);

// 输出总和
sum.ifPresent(System.out::println); // 输出: 15

reduce方法返回的是Optional<Integer>,因为如果流中没有元素,reduce操作将没有结果。我们使用ifPresent()方法来检查Optional是否包含值,并在控制台上打印出来

java 复制代码
Integer[] ints = {0, 1, 2, 3};
List<Integer> list = Arrays.asList(ints);

int reduce = list.stream().reduce(6, (a, b) -> a + b);
System.out.println(reduce);  //12
int reduce1 = list.stream().reduce(6, Integer::sum);
System.out.println(reduce1); // 12

相当于给了一个起始值:6

延迟执行

在执行返回 Stream 的方法时,并不立刻执行,而是等返回一个非Stream的方法后才执行。因为拿到 Stream 并不能直接用,而是需要处理成一个常规类型。

java 复制代码
@Test
public void laziness(){
  List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
  Stream<Integer> stream = strings.stream().filter(new Predicate() {
      @Override
      public boolean test(Object o) {
        System.out.println("Predicate.test 执行");
        return true;
        }
      });

   System.out.println("count 执行");
   stream.count();
}
/*-------执行结果--------*/
count 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行

filter 中的方法并没有立刻执行,而是等调用count()方法后才执行

并行流

并行 parallelStream 在使用方法上和串行一样。主要区别是parallelStream可多线程执行,是基于ForkJoin框架实现的。

这里可以简单的理解它是通过线程池来实现的 ,这样就会涉及到线程安全,线程消耗等问题

java 复制代码
@Test
public void parallelStreamTest(){
   List<Integer> numbers = Arrays.asList(1, 2, 5, 4);
   numbers.parallelStream() 
   .forEach(num->System.out.println(Thread.currentThread().getName()+">>"+num));
}
//执行结果
main>>5
ForkJoinPool.commonPool-worker-2>>4
ForkJoinPool.commonPool-worker-11>>1
ForkJoinPool.commonPool-worker-9>>2

Optional

该类提供了一种用于表示可选值而非空引用的类级别解决方案

就是一种针对 NPE(NullPointerException) 解决方案

创建对象

  1. 可以使用静态方法empty()创建一个空的 Optional 对象
java 复制代码
Optional<String> empty = Optional.empty();
System.out.println(empty); // 输出:Optional.empty
  1. 可以使用静态方法of()创建一个非空的 Optional 对象
java 复制代码
Optional<String> opt = Optional.of("你好");
System.out.println(opt); // 输出:Optional[你好]

传递给 of() 方法的参数必须是非空的,也就是说不能为 null,否则仍然会抛出 NullPointerException

  1. 可以使用静态方法ofNullable()创建一个即可空又可非空的Optional对象
java 复制代码
String name = null;
Optional<String> optOrNull = Optional.ofNullable(name);
System.out.println(optOrNull); // 输出:Optional.empty

ofNullable() 方法内部有一个三元表达式:

  • 如果为参数为 null,则返回私有常量 EMPTY;
  • 否则使用 new 关键字创建了一个新的 Optional 对象.

判断值是否存在

通过方法 isPresent() 判断一个Optional对象是否存在,如果存在,该方法返回 true,否则返回 false------取代了 obj != null 的判断

java 复制代码
Optional<String> opt = Optional.of("hello");
System.out.println(opt.isPresent()); // 输出:true

Optional<String> optOrNull = Optional.ofNullable(null);
System.out.println(optOrNull.isPresent()); // 输出:false

Java 11 后还可以通过方法 isEmpty() 判断与 isPresent() 相反的结果

非空表达式

ifPresent() 可以直接将 Lambda 表达式传递给该方法

java 复制代码
Optional<String> opt = Optional.of("hello");
opt.ifPresent(str -> System.out.println(str.length()));

Java 9 后还可以通过方法 ifPresentOrElse(action, emptyAction) 执行两种结果,非空时执行action,空时执行 emptyAction

java 复制代码
Optional<String> opt = Optional.of("hello");
opt.ifPresentOrElse(str -> System.out.println(str.length()), 
											  () -> System.out.println("为空"));

设置默认值

在创建(获取) Optional 对象的时候,需要一个默认值

orElse

orElse() 方法用于返回包裹在 Optional 对象中的值,如果该值不为 null,则返回;否则返回默认值

java 复制代码
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("hello");
System.out.println(name); // 输出:hello

orElseGet

参数类型不同。如果 Optional 对象中的值为 null,则执行参数中的函数

java 复制代码
String nullName = null;
String name = Optional.ofNullable(nullName).orElseGet(()->"hello");
System.out.println(name); // 输出:hello

获取值

建议 orElseGet() 方法获取Optional对象的值

使用get() 方法的话,因为假如 Optional 对象的值为 null,该方法会抛出NoSuchElementException异常

过滤值 filter

filter() 方法的参数类型为 Predicate(Java 8 新增的一个函数式接口),也就是说可以将一个 Lambda 表达式传递给该方法作为条件

如果表达式的结果为 false,则返回一个 EMPTY Optional 对象,否则返回过滤后的 Optional 对象

java 复制代码
Predicate<String> len6 = pwd -> pwd.length() > 6;
Predicate<String> len10 = pwd -> pwd.length() < 10;

password = "1234567";
Optional<String> opt = Optional.ofNullable(password);
boolean result = opt.filter(len6.and(len10)).isPresent();
System.out.println(result);

转换值 map

将原有 Optional 对象转换为一个新的 Optional 对象,原有的 Optional 对象不会更改

java 复制代码
// 创建一个非空的Optional
Optional<String> optionalString = Optional.of("Hello");
// 使用map方法将String转换为大写形式
Optional<String> optionalUpperCase = optionalString.map(String::toUpperCase);
// 输出转换后的结果
optionalUpperCase.ifPresent(System.out::println); // 输出: HELLO
// 创建一个空的Optional
Optional<String> emptyOptional = Optional.empty();
// 使用map方法尝试转换空Optional中的值
Optional<String> emptyOptionalResult = emptyOptional.map(String::toUpperCase);
// 输出空Optional的结果
emptyOptionalResult.ifPresent(System.out::println); // 不会执行,因为没有值
相关推荐
喔喔咿哈哈14 分钟前
【手撕 Spring】 -- Bean 的创建以及获取
java·后端·spring·面试·开源·github
码农小丘16 分钟前
了解springboot国际化用途以及使用
java·spring boot·spring
卡皮巴拉吖20 分钟前
【JavaEE初阶】多线程上部
java·jvm·java-ee
tian-ming21 分钟前
JavaWeb后端开发知识储备1
java·spring boot·nginx·spring·maven
spy47_22 分钟前
JavaEE 重要的API阅读
java·笔记·java-ee·api文档阅读
夏微凉.25 分钟前
【JavaEE进阶】Spring AOP 原理
java·spring boot·后端·spring·java-ee·maven
只因在人海中多看了你一眼27 分钟前
Java EE 技术基础知识体系梳理
java·java-ee
杨过姑父32 分钟前
org.springframework.context.support.ApplicationListenerDetector 详细介绍
java·前端·spring
理想不理想v42 分钟前
使用JS实现文件流转换excel?
java·前端·javascript·css·vue.js·spring·面试
hutaotaotao44 分钟前
c语言用户不同命令调用不同函数实现
c语言·开发语言