LambdaStream
1. Lambda表达式
1.1 Lambda表达式概述
1、Lambda是一个匿名函数,本质是个“语法糖”。它是Java8的新增特性。
- 允许将函数作为方法的参数。
- 代码风格更加紧凑,更加简洁,使Java的语言表达能力得到了提升。
2、Lambda表达式的格式:(形参) -> {代码块}
- 形参:如果有多个参数,参数之间必须用逗号隔开;
- ->:Lambda操作符;
- 代码块:Lambda表达式中所需要执行的功能,即Lambda体;
3、Lambda表达式的使用条件:
- 依赖于函数式接口
- 该接口有且只有一个抽象方法
函数式接口需要用@FunctionalInterface注解修饰,保证该接口中有且只有一个抽象方法。
1.2 Lambda表达式六大语法
- 语法一:无参、无返回值。
public void test1() {Runnable runnable = () -> {int i = 0;System.out.println(i);};runnable.run();}
- 语法二:有一个参数,无返回值。
public void test2() {Consumer<String> consumer = (x) -> {System.out.println(x);};consumer.accept("0");}
- 语法三:只有一个参数,省略括号。
public void test3() {Consumer<String> consumer = x -> {System.out.println(x);};consumer.accept("0"); }
- 语法四:有两个以上参数,有返回值,并且Lambda体中有多条语句。
public void test4() {Comparator<Integer> comparator = (x, y) -> {x*=10;return Integer.compare(x, y);};System.out.println(comparator.compare(1,10)); }
- 语法五:Lambda体中只有一条语句,大括号、分号和return(有返回值时)都可以省略,但是要省得一起省。
public void test5() {Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);System.out.println(comparator.compare(1,10)); }
- 语法六:参数指定类型。要指定参数类型,所有参数必须都得指定。
public void test6() {Comparator<Integer> comparator = (Integer x, Integer y) -> Integer.compare(x, y);System.out.println(comparator.compare(1,10)); }
1.3 Lambda表达式访问外部变量
Lambda表达式访问外部变量的规则与匿名内部类一致,分以下两种:
1、仅访问,不修改
在仅访问、不修改其值的情况下,对于基本数据类型和引用类型的外部变量,Lambda表达式都可以访问得到。
2、既访问,且修改
错误代码示例:
public void test() {int i = 0;Runnable runnable = () -> {System.out.println(i++);}; }
报错:Variable used in lambda expression should be final or effectively final.
原因:Lambda表达式访问外部变量有一个非常重要的限制:变量不可变(不能改变引用地址,但可以改变其值)。
总结:可以把基本数据类型封装在数组或集合中,从而使Lambda表达式成功访问并修改外部变量。
正确代码示例:
public void test() {int[] ints = new int[1];ints[0] = 0;Runnable runnable = () -> {System.out.println(ints[0]++);}; }
1.4 Java四大内置函数式接口
Java四大内置函数式接口:
类型 类 抽象方法 特点 消费型接口 Consumer void accept(T t); 有入参,无返回值 供给型接口 Supplier T get(); 无入参,有返回值 函数型接口 Function<T, R> R apply(T t); 有入参,有返回值 断言型接口 Predicate boolean test(T t); 有入参,返回值为布尔型
- 消费型接口的应用
// 需求:传入一个参数做业务处理,不需要返回值java public void consume(BigDecimal money, Consumer<BigDecimal> consumer){consumer.accept(money); }
- 供给型接口的应用
// 需求:产生指定数量的整数,放到集合中,返回集合 public List<Integer> supply(int num, Supplier<Integer> supplier) {List<Integer> list = new ArrayList<>();for (int i = 0; i < num; i++) {list.add(supplier.get());}return list; }
- 函数型接口的应用
// 需求:传入一个字符串,返回一个不同的字符串 public String function(String str, Function<String, String> function) {return function.apply(str); }
- 断言型接口的应用
// 需求:传入一个数字,判断是否大于0 public boolean predicate(int number, Predicate<Integer> predicate) {return predicate.test(number); }
测试:
@Test public void test(){consume(new BigDecimal(1000), (m) -> System.out.println("小明消费了"+m+"元"));supply(5, () -> (int)(Math.random()*10)).forEach(System.out::println);System.out.println(function("逆天而行", (m) -> m + "冥族大元帅"));System.out.println(predicate(1, (m) -> m > 0 ? true : false)); }
1.5 方法引用和构造器引用
1、方法引用
Lambda表达式中的内容有方法已经实现了,我们可以使用”方法引用“。
方法应用可以理解为Lambda表达式的另外一种表达形式。
需要注意的是接口的抽象方法的形参表、返回值类型与调用的类的方法形参表、返回值类型保持一致。
- 类型一:对象::实例方法名
// 下面两种方法起到的效果是一样的 public void test1() {// 方法一:Lambda表达式Consumer<String> consumer1 = (m) -> System.out.println(m);consumer1.accept("橘子右");// 方法二:方法引用Consumer<String> consumer2 = System.out::println;consumer2.accept("橘子右"); }
- 类型二:类名::静态方法名
// 下面两种方法起到的效果是一样的 public void test2() {// 方法一:Lambda表达式Comparator<Integer> comparator1 = (x, y) -> Integer.compare(x, y);System.out.println(comparator1.compare(1,2));// 方法二:方法引用Comparator<Integer> comparator2 = Integer::compare;System.out.println(comparator2.compare(1,2)); }
- 类型三:类名::实例方法名
// 下面两种方法起到的效果是一样的 public void test3() {// 方法一:Lambda表达式BiPredicate<String, String> biPredicate1 = (x, y) -> x.equals(y);System.out.println(biPredicate1.test("0","0"));// 方法二:方法引用BiPredicate<String, String> biPredicate2 = String::equals;System.out.println(biPredicate2.test("0","0")); }
2、构造器引用
接口的选择要根据构造函数中参数列表的个数来决定。当参数过多时,难以找到对应参数个数的接口,因此构造器引用不常使用。
无参构造函数使用Supplier接口;一个参数的构造函数使用Function接口;两个参数的构造函数使用BiFunction接口。
@Data @NoArgsConstructor @AllArgsConstructor public class UserVO implements Serializable {private int userId;private String userName;public UserVO(int userId) {this.userId = userId;} }
public void test4() {// 无参构造函数Supplier<UserVO> supplier = UserVO::new;UserVO user1 = supplier.get();// 有一个参数的构造函数Function<Integer, UserVO> function = UserVO::new;UserVO user2 = function.apply(1011);// 有两个参数的构造函数BiFunction<Integer, String, UserVO> biFunction = UserVO::new;UserVO user3 = biFunction.apply(1011, "橘子右"); }
2. Stream流
2.1 Stream流概述
认识Stream:
Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合和数组进行的操作,可以执行复杂的查找、过滤和映射数据等操作。
使用Stream API对集合和数组进行操作,就类似于使用SQL执行的数据库查询。
简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
Stream的特点:
- Stream自己不会存储元素;
- Stream不会改变源对象,相反,它会返回一个持有结果的新Stream;
- Stream操作是延迟执行的,这意味着它会等到需要结果的时候才执行。
Stream的使用:
首先需要一个数据源,也就是集合或数组。
使用数据源创建流,有六种方法:
// 使用集合创建Stream流 List<UserVO> list = new ArrayList<>(); Stream<UserVO> stream1 = list.stream();// 使用数组创建Stream流 UserVO[] userVOS = new UserVO[10]; Stream<UserVO> stream2 = Arrays.stream(userVOS);// 使用Stream的of()方法创建Stream流 Stream<Integer> stream3 = Stream.of(1, 2, 3);// 使用Stream的iterate()方法利用某种规则创建Stream流 Stream<Integer> stream4 = Stream.iterate(1, m -> m + 8).limit(10);// 使用Stream的generate()方法创建Stream流 Stream<String> stream5 = Stream.generate(() -> "Hello Stream").limit(10);//使用Stream的builder()方法创建Stream流 Stream<Object> stream6 = Stream.builder().add("java").add("C++").build();
中间操作,如Stream流的filter()方法和map()方法等对数据进行流水线式操作。
filter()方法
// 过滤操作,返回Stream Stream<T> filter(Predicate<? super T> predicate);
// filter方法使用示例。打印结果:1248 1457 Stream.of("1248", "2346", "1457").filter(x -> x.startsWith("1")).forEach(System.out::println);
map()方法
// 映射操作,返回Stream <R> Stream<R> map(Function<? super T, ? extends R> mapper);
// map方法使用示例。打印结果:12 23 14 Stream.of("1248", "2346", "1457").map(x -> x.substring(0,2)).forEach(System.out::println);
reduce()方法
// 用途比较广,可以作为累加器,累乘器,map、filter操作它都可以实现 T reduce(T identity, BinaryOperator<T> accumulator); Optional<T> reduce(BinaryOperator<T> accumulator); <U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
// 返回值类型为T的reduce方法使用示例。计算过程:2-1=1,1-2=-1,-1-3=-4;打印结果:-4 System.out.println(Stream.of(1, 2, 3).reduce(2, (x, y) -> x - y)); // 返回值类型为Optional<T>的reduce方法使用示例。计算过程:1*1=1,1*2=2,2*3=6;打印结果:6 System.out.println(Stream.of(1, 2, 3).reduce((x, y) -> x * y).get()); // 返回值类型为<U> U的reduce方法在并行流操作的时候使用,串形流使用前两种方法。
collent()方法
// 参数supplier 是一个生成目标类型实例的方法,代表着目标容器是什么;accumulator是将操作的目标数据填充到supplier 生成的目标类型实例中去的方法,代表着如何将元素添加到容器中;而combiner是将多个supplier 生成的实例整合到一起的方法,代表着规约操作,将多个结果合并。 <R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner); // 主要是使用 Collectors(java.util.stream.Collectors)来进行各种 reduction 操作。 <R, A> R collect(Collector<? super T, A, R> collector);
// 第一个collect方法使用示例:将UserVO类型的List集合转换为以UserVO对象的userId字段为key值,以UserVO对象为value值的Map集合。 // 第一种写法 Map<Integer, UserVO> map = list.stream().collect(() -> new HashMap<>(), (x, y) -> x.put(y.getUserId(), y), (x, y) -> x.putAll(y)); // 第二种写法 Map<Integer, UserVO> map1 = list.stream().collect(HashMap::new, (x, y) -> x.put(y.getUserId(), y), HashMap::putAll);
// 第二个collect方法使用示例// 1.将数组组成字符串 String[] strings = {"ab", "cd", "ef"}; //打印结果:abcdef System.out.println(Arrays.stream(strings).collect(Collectors.joining())); //打印结果:ab|cd|ef System.out.println(Arrays.stream(strings).collect(Collectors.joining("|"))); //打印结果:{ab,cd,ef} System.out.println(Arrays.stream(strings).collect(Collectors.joining(",","{","}")));// 2.将数组转为集合List String[] strings = {"ab", "cd", "ef"}; List<String> list = Arrays.stream(strings).collect(Collectors.toList());// 3.将list中的数据分组并统计数量 Map<Integer, Long> map = list.stream().collect(Collectors.groupingBy(UserVO::getUserId, Collectors.counting()));
flatMap()方法
// 将最底层元素抽出来放到一起 <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
// flatMap方法使用示例。打印结果:1 2 3 4 5 6 Stream.of(Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6)).flatMap(x -> x.stream()).forEach(System.out::println);
max()和min()方法
// 求最大值 Optional<T> max(Comparator<? super T> comparator); // 求最小值 Optional<T> min(Comparator<? super T> comparator);
// max方法使用示例。打印结果:3 System.out.println(Stream.of(1, 2, 3).max(Integer::compareTo).get());
anyMatch()、allMatch()与noneMatch()方法
// 有一个匹配返回true boolean anyMatch(Predicate<? super T> predicate); // 全部匹配返回true boolean allMatch(Predicate<? super T> predicate); // 全部不匹配返回true boolean noneMatch(Predicate<? super T> predicate);
// allMatch方法使用示例。打印结果:false System.out.println(Stream.of("1248", "2346", "1457").allMatch(x -> x.startsWith("1")));
skip()方法
// 跳过前N个元素后,剩下的元素重新组成一个Stream Stream<T> skip(long n);
// skip方法使用示例。打印结果:c d Stream.of("a", "b", "c", "d").skip(2).forEach(System.out::println);
peek()方法
// 生成一个包含原Stream的所有元素的新Stream,新Stream每个元素被消费之前都会执行peek给定的消费函数 Stream<T> peek(Consumer<? super T> action);
// peek方法使用示例。打印结果:11 1 12 2 13 3 Stream.of(1, 2, 3).peek(x -> System.out.println(x += 10)).forEach(System.out::println);
concat()方法
// 流连接操作 public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
// concat方法使用示例。打印结果:1 2 a b Stream.concat(Stream.of(1, 2), Stream.of("a", "b")).forEach(System.out::println);
count()方法
// 统计Stream流中所有元素的总数 long count();
// count方法使用示例。打印结果:3 System.out.println(Stream.of("a", "b", "c").count());
distinct()方法
// 返回由该流的不同元素组成的流,等同于去重该流的重复元素 Stream<T> distinct();
// distinct方法使用示例。打印结果:a b c Stream.of("a", "b", "a", "c", "b").distinct().forEach(System.out::println);
sorted()方法
// 将Stream流中的元素排序 Stream<T> sorted();
// sorted方法使用示例。打印结果:a b c d Stream.of("d", "b", "c", "a").sorted().forEach(System.out::println);
终止操作,惰性求值,一次性执行全部内容。
2.2 并行流与串形流
并行流 : 就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
Java 8 中将并行进行了优化,我们可以很容易的对数据进行并行操作。
Stream API 可以声明性地通过 parallel() 与 sequential() 在并行流与顺序流之间进行切换。
// 使用单线程求1到100000000000的总和。打印输出:耗时32127毫秒 @Test public void test1 () {final long maxSize = 100000000000L;long sum = 0;Instant start = Instant.now();for (long i = 0; i < maxSize; i++) {sum += i;}Instant end = Instant.now();System.out.println("总和:"+sum);System.out.println("耗时:"+ Duration.between(start, end).toMillis()+"毫秒"); }// 使用并行流求1到100000000000的总和。打印输出:耗时:23460毫秒 @Test public void test2 () {final long maxSize = 100000000000L;Instant start = Instant.now();long sum = LongStream.rangeClosed(0, maxSize).parallel().reduce(0, Long::sum);Instant end = Instant.now();System.out.println("总和:"+sum);System.out.println("耗时:"+ Duration.between(start, end).toMillis()+"毫秒"); }
当maxSize的值较大时,使用并行流的方式求和的时间性能明显优于单线程方式;但是当maxSize的值较小时,由于并行流频繁切换线程导致时间性能会低于单线程方式。
2.3 Collectors工具类
Collectors 是 Java 8 加入的操作类,位于 java.util.stream 包下。它会根据不同的策略将元素收集归纳起来,比如最简单常用的是将元素装入Map、Set、List 等可变容器中。特别对于 Java 8 Stream API 来说非常有用,Stream API 提供了collect() 方法来对 Stream 流进行终结操作,并派生出基于各种策略的结果集。
下面介绍一些Collectors工具类常用的静态方法。
归纳类型
作用:将元素分别归纳进List、Map、Set等可变容器 。
静态方法:
// 将Stream流中的元素转换成Collection集合 public static <T, C extends Collection<T>> Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {} // 将Stream流中的元素转换成List集合 public static <T> Collector<T, ?, List<T>> toList() {} // 将Stream流中的元素转换成Set集合 public static <T> Collector<T, ?, Set<T>> toSet() {} // 以下三个方法是使用toMap()方法将Stream流中的元素转换成Map集合 public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper) {} public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) {} public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction,Supplier<M> mapSupplier) {} // 以下三个方法是使用toConcurrentMap()方法将Stream流中的元素转换成Map集合 public static <T, K, U> Collector<T, ?, ConcurrentMap<K,U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper) {} public static <T, K, U> Collector<T, ?, ConcurrentMap<K,U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper,BinaryOperator<U> mergeFunction) {} public static <T, K, U, M extends ConcurrentMap<K, U>> Collector<T, ?, M> toConcurrentMap(Function<? super T, ? extends K> keyMapper,Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) {}
示例:
- Collectors.toList()方法示例
// 将Stream流中的元素转换成List集合。打印结果:1 2 3 List<Integer> integerList = Stream.of(1, 2, 3).collect(Collectors.toList()); integerList.forEach(System.out::println);
Collectors.toMap()方法示例
Collectors.toMap 有三个重载方法.参数含义分别是:
- keyMapper:Key 的映射函数
- valueMapper:Value 的映射函数
- mergeFunction:当 Key 冲突时,调用的合并方法
- mapSupplier:Map 构造器,在需要返回特定的 Map 时使用
// List集合准备 List<User> userList = new ArrayList<>(); userList.add(new User(1002, "aaa")); userList.add(new User(1003, "bbb")); userList.add(new User(1001, "ccc"));
// toMap()方法第一个重载方法。打印结果:1002aaa 1003bbb 1001ccc Map<Integer, User> userMap = userList.stream().collect(Collectors.toMap(User::getId, x -> x)); userMap.forEach((x, y) -> System.out.println(x.toString() + y.getName()));
在List集合中加入一条数据:new User(1001, “ddd”),这时在将List转Map会报错,原因是key重复。
第二个重载方法的mergeFunction就是解决key值冲突问题的。
Lambda表达式的两个入参分别是key值冲突的第一个value值和第二个value值
// toMap()方法第二个重载方法。打印结果:1002aaa 1003bbb 1001cccddd Map<Integer, User> userMap = userList.stream().collect(Collectors.toMap(User::getId, x -> x, (x, y) -> {x.setName(x.getName()+y.getName());return x; })); userMap.forEach((x, y) -> System.out.println(x.toString() + y.getName()));
第四个参数(mapSupplier)用于自定义返回 Map 类型,比如我们希望返回的 Map 是根据 Key 排序的,可以使用如下写法:
// toMap()方法第三个重载方法。打印结果:1001ddd 1002aaa 1003bbb Map<Integer, User> userMap = userList.stream().collect(Collectors.toMap(User::getId, x -> x, (x, y) -> y, TreeMap::new)); userMap.forEach((x, y) -> System.out.println(x.toString() + y.getName()));
Collectors.toMap()和Collectors.toConcurrentMap()的区别与联系
联系:
都可以将数据元素转化为Map集合
都有三个重载方法,并且对应的重载方法的使用方法类似
区别:
toMap()方法执行过程是非并发的;而toConcurrentMap()方法执行过程是并发的
toMap()方法合并多个中间结果按遇到的顺序在结果Map中插入值;而toConcurrentMap()方法将所有元素扔到公共结果容器,以任何顺序收集元素
Joining
作用:将元素以某种规则连接起来。
静态方法:
// 将字符串连接起来 public static Collector<CharSequence, ?, String> joining() {} // 将字符串以定义的delimiter分隔符连接起来 public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {} // 将字符串以定义的delimiter分隔符、定义的prefix前缀符和定义的suffix后缀符连接起来 public static Collector<CharSequence, ?, String> joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) {}
示例:
// 输出样式:SpringTomcatMybatis list.stream().collect(Collectors.joining()); // 输出样式:Spring,Tomcat,Mybatis list.stream().collect(Collectors.joining(",")); // 输出样式:[Spring,Tomcat,Mybatis] list.stream().collect(Collectors.joining(",", "[", "]"));
collectingAndThen
作用:该方法先执行了一个归纳操作,然后再对归纳的结果进行 function 函数处理输出一个新的结果。
静态方法:
public static<T,A,R,RR> Collector<T,A,RR> collectingAndThen(Collector<T,A,R> downstream,Function<R,RR> finisher) {}
示例:
// 打印结果:[JAVA, HTML, PYTHON] System.out.println(Stream.of("java", "html", "python").collect(Collectors.collectingAndThen(Collectors.toList(),x -> x.toString().toUpperCase())).toString());
groupingBy
作用:按照条件对元素进行分组,和 SQL 中的 group by 用法有异曲同工之妙,通常也建议使用 Java 进行分组处理以减轻数据库压力。
静态方法:
// 将Stream流中元素根据classifier分类,并封装成Map<classifier类型, List<元素类型>>集合 public static <T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(Function<? super T, ? extends K> classifier) {} // 不仅将Stream流中元素封装成Map集合,还指定了Map集合中存储元素的集合 public static <T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream) {} // 考虑同步安全问题时,第三个重载方法在第二个重载方法的基础上使用了线程安全的同步容器 public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream) {}
示例:
// 打印结果:3: [101, 101] 4: [1001, 1002] Stream.of("1001", "1002", "101", "101").collect(Collectors.groupingBy(x -> x.length(), Collectors.toSet())).forEach((x, y) -> System.out.println(x+": "+ y.toString()));
// 打印结果:3: [101] 4: [1001, 1002] Stream.of("1001", "1002", "101", "101").collect(Collectors.groupingBy(x -> x.length(), Collectors.toSet())).forEach((x, y) -> System.out.println(x+": "+ y.toString()));
// 打印结果:3: [101] 4: [1001, 1002] // 创建一个同步的Map Supplier<Map<Integer, Set<String>>> mapSupplier = () -> Collections.synchronizedMap(new HashMap<>()); Stream.of("1001", "1002", "101", "101").collect(Collectors.groupingBy(x -> x.length(), mapSupplier, Collectors.toSet())).forEach((x, y) -> System.out.println(x+": "+ y.toString()));
LambdaStream相关推荐
- JDK8新特性详解LambdaStream
目录 1 Lambda 1.1 为什么使用lambda 1.2 lambda案例 1.3 lambda语法规则 1.3.1 接口里面只能有一个方法 1.4 lambda使用前提 2 Stream 2. ...
- java date和localdate_Java8 Date与LocalDate互转
Java8 Date与LocalDate互转 reference:https://blog.csdn.net/panchang199266/article/details/95724991 Java8 ...
- 敏捷项目的自动化单元测试的6大好处
The Agile testing method refers to a collaborative approach towards software development that was cr ...
- 看完这个你说还不会Lambda
Lambda表达式深入浅出 1 概念简介 1.1Lambda表达式是什么 Lambda表达式是JAVA8中提供的一种新的特性,是一个匿名函数方法,可以对某些匿名内部类的写法进行简化.我们可以把Lamb ...
最新文章
- MALTLAB 求出水仙花数
- CodeForces - 1316B String Modification(找规律)
- TensorFlow 教程 --进阶指南--3.7自定义数据读取
- 王者荣耀成功的营销之战
- NHibernate.3.0.Cookbook第一章第六节Handling versioning and concurrency的翻译
- JDK动态代理与CGLIB的区别
- 启动web项目卡在Initializing Spring root WebApplicationContext不动
- C语言题目:5-7 购物(二) (25 分)
- 第一章、银行会计的基本原理和基本核算方法
- golang:goredis库pipeline使用方式
- Puppeteer + relaxed docker 方案
- 路由器与交换机的基本工作原理
- java+vue+onlyoffice的简单集成
- Hive 取非 Group by 字段数据的方法
- 如何将普通相机改成VR相机
- 2022-2027年(新版)中国大米行业营销战略与供应情况预测报告
- 小米 2022校招 java后端一面凉经(55min)
- c++2048小游戏编写
- 基于FPGA的FFT
- ImageEdit 展示图片(XAML, C#)