新特性Lambda表达式和Function函数式接口编程

目录

1、lambda在JSR(Java Specification Requests)

2、lambda在JLS(Java LanguageSpecifications)

3、lambda表达式在jvm规范

4、Stream流

4.1 两种stream流

4.2 简洁遍历

4.3 过滤

4.4 映射

4.5 查找元素

4.6 reduce方法

4.7 stream实战

5、Collectors收集之术

5.1 Grouping

5.2 partitioningBy收集器

5.3 reduce

5.4 Summarizing

6、小结


前言:jdk1.8推出了lambda表达式和函数式接口编程,本文主要从java中JSR需求规范到JLS语言规范再到JVM规范,介绍lambda和function。

1、lambda在JSR(Java Specification Requests)

首先,要想了解lambda,从需求规范开始,我们从JSR需求中可以看到,JSR335即为lambda的需求规范,我们可以进行download,打开如下图所示:

如上,Overview中,概述定义了如下几点功能:

  • Lambda expressions and method references——lambda表达式和方法引用
  • Enhanced type inference and target typing——增强类型推断和目标类型转换
  • Default and static methods in interfaces——在接口里默认和静态的方法

这三个新的features,规定了我们的lambda;jdk中util下提供了新的API,还有,用的比较多的util下的stream,关于function如下图所示:

Contents内容包括如下三点:

  • 规范更改,主要是对Java语言规范的更新,并支持对Java虚拟机规范(此处)和Java对象序列化规范(此处)的更改
  • Java SE API更新摘要
  • 对新语言和库功能的非正式(非规范性)概述

2、lambda在JLS(Java LanguageSpecifications)

在JLS规范中,我们可以看一下lambda表达式的规范定义:

定义了,lambda表达式在赋值上下文、调用上下文、强制类型转换上下文,否则编译器将报错。

表达式的求值将产生一个function接口实例。表达式的求值不一定导致表达式主体的执行,相反,这可能在以后调用功能接口的适当方法时发生。

接下来是,Lambda Parameters和 Lambda Body,显式与隐式参数类型;如果形式参数具有推断的类型,则这些类型是从lambda表达式所针对的功能接口类型派生的。

更多的细节,就不在此赘述了。有兴趣的同学可以自行官网查看。

3、lambda表达式在jvm规范

比如我们针对其中一个class查看其字节码文件,javap -v Filtering.class。

可以很明显的看出,函数式接口在字节码底层是InvokeDynamic,通过下面的jdk源码中可以看出,底层是newInvokeDynamicItem,当class build时 放入到常量池中。

invokedynamic 这个操作码的执行方法会关联到一个动态调用点对象(Call Site object),这个call site 对象会指向一个具体的bootstrap 方法(方法的二进制字节流信息在BootstrapMethods属性表中)的执行,invokedynamic指令的调用会有一个独特的调用链,不像其他指令会直接调用方法,在实际的运行过程也相对更加复杂。

   /*** Adds an invokedynamic reference to the constant pool of the class being* build. Does nothing if the constant pool already contains a similar item.* <i>This method is intended for {@link Attribute} sub classes, and is* normally not needed by class generators or adapters.</i>** @param name*            name of the invoked method.* @param desc*            descriptor of the invoke method.* @param bsm*            the bootstrap method.* @param bsmArgs*            the bootstrap method constant arguments.** @return a new or an already existing invokedynamic type reference item.*/Item newInvokeDynamicItem(final String name, final String desc,final Handle bsm, final Object... bsmArgs) {// cache for performanceByteVector bootstrapMethods = this.bootstrapMethods;if (bootstrapMethods == null) {bootstrapMethods = this.bootstrapMethods = new ByteVector();}int position = bootstrapMethods.length; // record current position

通过如下的方法执行,// (site.)invokedynamic(a*):R => mh = site.getTarget(); mh.invokeBasic(a*)。在栈针底层// NA, //INVOKEDYNAMIC, // visitInvokeDynamicInsn。

在底层jvm中generateOopMap.cpp,可以看如下图。

包括在Frame.java中的压栈。

case Opcodes.INVOKEDYNAMIC:pop(item.strVal2);push(cw, item.strVal2);break;

更多的细节就不再赘述了,有兴趣的同学可以自行了解深入。

4、Stream流

流的定义:从支持数据处理操作的源生成的元素序列

Stream API 基本都是返回Stream本身,这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 这样做可以对操作进行优化, 比如延迟执行(laziness)和短路( short-circuiting)

4.1 两种stream流

•stream() − 为集合创建串行流。
•parallelStream() − 为集合创建并行流。

4.2 简洁遍历

stream提供了一个内部遍历的方式forEach(),通过forEach可以大大简化集合遍历的代码

4.3 过滤

stream提供了一个用于数据过滤的方法filter(),与stream的其他API配合使用可以简单的实现数据过滤

public class Filtering {public static void main(String...args){//过滤出素菜List<Dish> vegetarianMenu =menu.stream()//filter参数为谓词  也就是boolean类型.filter(Dish::isVegetarian)   //method reference.collect(toList());vegetarianMenu.forEach(System.out::println);// 过滤指定的元素 去重List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);numbers.stream().filter(i -> i % 2 == 0).distinct()//去重.forEach(System.out::println);//limit 截断流List<Dish> dishesLimit3 =menu.stream().filter(d -> d.getCalories() > 300).limit(3).collect(toList());dishesLimit3.forEach(System.out::println);//skip:跳过n个元素List<Dish> dishesSkip2 =menu.stream().filter(d -> d.getCalories() > 300).skip(2).collect(toList());dishesSkip2.forEach(System.out::println);}public static final List<Dish> menu =Arrays.asList( new Dish("pork", false, 800, Type.MEAT),new Dish("beef", false, 700, Type.MEAT),new Dish("chicken", false, 400, Type.MEAT),new Dish("french fries", true, 530, Type.OTHER),new Dish("rice", true, 350, Type.OTHER),new Dish("season fruit", true, 120, Type.OTHER),new Dish("pizza", true, 550, Type.OTHER),new Dish("prawns", false, 400, Type.FISH),new Dish("salmon", false, 450, Type.FISH));
}

4.4 映射

stream提供了一个用于数据映射的方法map(),与stream的其他API配合使用可以简单的实现数据映射

public class Mapping {public static void main(String...args){// mapList<String> dishNames = menu.stream().map(Dish::getName)
//                                     .map(dish -> dish.getName()).collect(toList());System.out.println(dishNames);// mapList<String> words = Arrays.asList("Hello", "World");List<Integer> wordLengths = words.stream().map(String::length).collect(toList());System.out.println(wordLengths);// flatMapwords.stream()//切割成单个词.flatMap((String line) -> Arrays.stream(line.split(""))).distinct()//去重.forEach(System.out::println);// flatMapList<Integer> numbers1 = Arrays.asList(1,2,3,4,5);List<Integer> numbers2 = Arrays.asList(6,7,8);List<int[]> pairs =numbers1.stream()//两个list中纵向两两组合成为一个二维数组.flatMap((Integer i) -> numbers2.stream().map((Integer j) -> new int[]{i, j}))//过滤二维数组 两列之和为3的倍数.filter(pair -> (pair[0] + pair[1]) % 3 == 0).collect(toList());pairs.forEach(pair -> System.out.println("(" + pair[0] + ", " + pair[1] + ")"));}
}

4.5 查找元素

public class Finding {public static void main(String...args){if(isVegetarianFriendlyMenu()){System.out.println("Vegetarian friendly");}System.out.println(isHealthyMenu());System.out.println(isHealthyMenu2());Optional<Dish> dish = findVegetarianDish();dish.ifPresent(d -> System.out.println(d.getName()));}//相当于||private static boolean isVegetarianFriendlyMenu(){return menu.stream().anyMatch(Dish::isVegetarian);}//相当于 &&private static boolean isHealthyMenu(){return menu.stream().allMatch(d -> d.getCalories() < 1000);}//相当于 !=private static boolean isHealthyMenu2(){return menu.stream().noneMatch(d -> d.getCalories() >= 1000);}//匹配任何一个 适用于并发流执行private static Optional<Dish> findVegetarianDish(){return menu.stream().filter(Dish::isVegetarian).findAny();}}

4.6 reduce方法

reduce方法将流归约成一个值。用函数式编程语言的术语来说,这称为折叠(fold)
public class Reducing {public static void main(String...args){List<Integer> numbers = Arrays.asList(3,4,5,1,2);//语法 reduce(起点, 算法规则);int sum = numbers.stream().reduce(0, (a, b) -> a + b);System.out.println(sum);int sum2 = numbers.stream().reduce(0, Integer::sum);System.out.println(sum2);int max = numbers.stream().reduce(0, (a, b) -> Integer.max(a, b));System.out.println(max);Optional<Integer> min = numbers.stream().reduce(Integer::min);min.ifPresent(System.out::println);int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);System.out.println("Number of calories:" + calories);}
}

4.7 stream实战

public class PuttingIntoPractice {public static void main(String...args){Trader raoul = new Trader("Raoul", "Cambridge");Trader mario = new Trader("Mario","Milan");Trader alan = new Trader("Alan","Cambridge");Trader brian = new Trader("Brian","Cambridge");List<Transaction> transactions = Arrays.asList(new Transaction(brian, 2011, 300),new Transaction(raoul, 2012, 1000),new Transaction(raoul, 2011, 400),new Transaction(mario, 2012, 710),new Transaction(mario, 2012, 700),new Transaction(alan, 2012, 950));  // Query 1: Find all transactions from year 2011 and sort them by value (small to high).List<Transaction> tr2011 = transactions.stream().filter(transaction -> transaction.getYear() == 2011).sorted(comparing(Transaction::getValue)).collect(toList());System.out.println(tr2011);// Query 2: What are all the unique cities where the traders work?List<String> cities =transactions.stream().map(transaction -> transaction.getTrader().getCity()).distinct().collect(toList());System.out.println(cities);// Query 3: Find all traders from Cambridge and sort them by name.List<Trader> traders =transactions.stream().map(Transaction::getTrader).filter(trader -> trader.getCity().equals("Cambridge")).distinct().sorted(comparing(Trader::getName)).collect(toList());System.out.println(traders);// Query 4: Return a string of all traders’ names sorted alphabetically.String traderStr =transactions.stream().map(transaction -> transaction.getTrader().getName()).distinct().sorted().reduce("", (n1, n2) -> n1 + n2);System.out.println(traderStr);// Query 5: Are there any trader based in Milan?boolean milanBased =transactions.stream().anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));System.out.println(milanBased);// Query 6: Update all transactions so that the traders from Milan are set to Cambridge.transactions.stream().map(Transaction::getTrader).filter(trader -> trader.getCity().equals("Milan")).forEach(trader -> trader.setCity("Cambridge"));System.out.println(transactions);// Query 7: What's the highest value in all the transactions?int highestValue = transactions.stream().map(Transaction::getValue).reduce(0, Integer::max);System.out.println(highestValue);}
}
public  class Trader {private String name;private String city;public Trader(String n, String c){this.name = n;this.city = c;}public String getName(){return this.name;}public String getCity(){return this.city;}public void setCity(String newCity){this.city = newCity;}@Overridepublic String toString(){return "Trader:"+this.name + " in " + this.city;}
}
public class Transaction {private Trader trader;private int year;private int value;public Transaction(Trader trader, int year, int value){this.trader = trader;this.year = year;this.value = value;}public Trader getTrader(){ return this.trader;}public int getYear(){return this.year;}public int getValue(){return this.value;}@Overridepublic String toString(){return "{" + this.trader + ", " +"year: "+this.year+", " +"value:" + this.value +"}";}
}

5、Collectors收集之术

Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串

5.1 Grouping

多级分组、各种复杂分组

public class Grouping {enum CaloricLevel { DIET, NORMAL, FAT };public static void main(String... args) {System.out.println("Dishes grouped by type: " + groupDishesByType());System.out.println("Dish names grouped by type: " + groupDishNamesByType());System.out.println("Dish tags grouped by type: " + groupDishTagsByType());System.out.println("Caloric dishes grouped by type: " + groupCaloricDishesByType());System.out.println("Dishes grouped by caloric level: " + groupDishesByCaloricLevel());System.out.println("Dishes grouped by type and caloric level: " + groupDishedByTypeAndCaloricLevel());System.out.println("Count dishes in groups: " + countDishesInGroups());System.out.println("Most caloric dishes by type: " + mostCaloricDishesByType());System.out.println("Most caloric dishes by type: " + mostCaloricDishesByTypeWithoutOprionals());System.out.println("Sum calories by type: " + sumCaloriesByType());System.out.println("Caloric levels by type: " + caloricLevelsByType());}//给groupingBy方法传递了一个Function(以方法引用的形式),它提取了流中每一道Dish的Dish.Type。//我们把这个Function叫作分类函数,因为它用来把流中的元素分成不同的组private static Map<Dish.Type, List<Dish>> groupDishesByType() {return menu.stream().collect(groupingBy(Dish::getType));}//多级分组,可以使用一个由双参数版本的Collectors.groupingBy工厂方法创建的收集器,//除了普通的分类函数之外,还可以接受collector类型的第二个参数。private static Map<Dish.Type, List<String>> groupDishNamesByType() {return menu.stream().collect(groupingBy(Dish::getType, mapping(Dish::getName, toList())));}private static Map<Dish.Type, Set<String>> groupDishTagsByType() {return menu.stream().collect(groupingBy(Dish::getType,flatMapping(dish -> dishTags.get( dish.getName() ).stream(), toSet())));}private static Map<Dish.Type, List<Dish>> groupCaloricDishesByType() {
//        return menu.stream().filter(dish -> dish.getCalories() > 500).collect(groupingBy(Dish::getType));return menu.stream().collect(groupingBy(Dish::getType, filtering(dish -> dish.getCalories() > 500, toList())));}private static Map<CaloricLevel, List<Dish>> groupDishesByCaloricLevel() {return menu.stream().collect(groupingBy(dish -> {if (dish.getCalories() <= 400) return CaloricLevel.DIET;else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;else return CaloricLevel.FAT;} ));}private static Map<Dish.Type, Map<CaloricLevel, List<Dish>>> groupDishedByTypeAndCaloricLevel() {return menu.stream().collect(groupingBy(Dish::getType,groupingBy((Dish dish) -> {if (dish.getCalories() <= 400) return CaloricLevel.DIET;else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;else return CaloricLevel.FAT;} )));}private static Map<Dish.Type, Long> countDishesInGroups() {return menu.stream().collect(groupingBy(Dish::getType, counting()));}private static Map<Dish.Type, Optional<Dish>> mostCaloricDishesByType() {return menu.stream().collect(groupingBy(Dish::getType,reducing((Dish d1, Dish d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)));}private static Map<Dish.Type, Dish> mostCaloricDishesByTypeWithoutOprionals() {return menu.stream().collect(groupingBy(Dish::getType,collectingAndThen(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2),Optional::get)));}private static Map<Dish.Type, Integer> sumCaloriesByType() {return menu.stream().collect(groupingBy(Dish::getType,summingInt(Dish::getCalories)));}//自定义内部实现private static Map<Dish.Type, Set<CaloricLevel>> caloricLevelsByType() {return menu.stream().collect(groupingBy(Dish::getType, mapping(dish -> {if (dish.getCalories() <= 400) return CaloricLevel.DIET;else if (dish.getCalories() <= 700) return CaloricLevel.NORMAL;else return CaloricLevel.FAT;},toSet())));}
}
public class Dish {private final String name;private final boolean vegetarian;private final int calories;private final Type type;public Dish(String name, boolean vegetarian, int calories, Type type) {this.name = name;this.vegetarian = vegetarian;this.calories = calories;this.type = type;}public String getName() {return name;}public boolean isVegetarian() {return vegetarian;}public int getCalories() {return calories;}public Type getType() {return type;}public enum Type { MEAT, FISH, OTHER }@Overridepublic String toString() {return name;}public static final List<Dish> menu =Arrays.asList( new Dish("pork", false, 800, Type.MEAT),new Dish("beef", false, 700, Type.MEAT),new Dish("chicken", false, 400, Type.MEAT),new Dish("french fries", true, 530, Type.OTHER),new Dish("rice", true, 350, Type.OTHER),new Dish("season fruit", true, 120, Type.OTHER),new Dish("pizza", true, 550, Type.OTHER),new Dish("prawns", false, 400, Type.FISH),new Dish("salmon", false, 450, Type.FISH));public static final Map<String, List<String>> dishTags = new HashMap<>();static {dishTags.put("pork", asList("greasy", "salty"));dishTags.put("beef", asList("salty", "roasted"));dishTags.put("chicken", asList("fried", "crisp"));dishTags.put("french fries", asList("greasy", "fried"));dishTags.put("rice", asList("light", "natural"));dishTags.put("season fruit", asList("fresh", "natural"));dishTags.put("pizza", asList("tasty", "salty"));dishTags.put("prawns", asList("tasty", "roasted"));dishTags.put("salmon", asList("delicious", "fresh"));}
}

5.2 partitioningBy收集器

partitioningBy收集器:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函数。分区的优势分区:在于保留了分区函数返回true或false的两套流元素列表,可以二次分区
public class Partitioning {public static void main(String... args) {System.out.println("Dishes partitioned by vegetarian: " + partitionByVegeterian());System.out.println("Vegetarian Dishes by type: " + vegetarianDishesByType());System.out.println("Most caloric dishes by vegetarian: " + mostCaloricPartitionedByVegetarian());}private static Map<Boolean, List<Dish>> partitionByVegeterian() {return menu.stream().collect(partitioningBy(Dish::isVegetarian));}private static Map<Boolean, Map<Dish.Type, List<Dish>>> vegetarianDishesByType() {return menu.stream().collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));}//素菜和非素菜中卡路里最大的map集合//演示二次分区private static Object mostCaloricPartitionedByVegetarian() {return menu.stream().collect(partitioningBy(Dish::isVegetarian,collectingAndThen(maxBy(comparingInt(Dish::getCalories)),Optional::get)));}
}

5.3 reduce

public class Reducing {public static void main(String... args) {System.out.println("Total calories in menu: " + calculateTotalCalories());System.out.println("Total calories in menu: " + calculateTotalCaloriesWithMethodReference());System.out.println("Total calories in menu: " + calculateTotalCaloriesWithoutCollectors());System.out.println("Total calories in menu: " + calculateTotalCaloriesUsingSum());}private static int calculateTotalCalories() {return menu.stream().collect(reducing(0, Dish::getCalories, (Integer i, Integer j) -> i + j));}private static int calculateTotalCaloriesWithMethodReference() {return menu.stream().collect(reducing(0, Dish::getCalories, Integer::sum));}private static int calculateTotalCaloriesWithoutCollectors() {return menu.stream().map(Dish::getCalories).reduce(Integer::sum).get();}private static int calculateTotalCaloriesUsingSum() {return menu.stream().mapToInt(Dish::getCalories).sum();}
}

5.4 Summarizing

public class Summarizing {public static void main(String... args) {System.out.println("Nr. of dishes: " + howManyDishes());System.out.println("The most caloric dish is: " + findMostCaloricDish());System.out.println("The most caloric dish is: " + findMostCaloricDishUsingComparator());System.out.println("Total calories in menu: " + calculateTotalCalories());System.out.println("Average calories in menu: " + calculateAverageCalories());System.out.println("Menu statistics: " + calculateMenuStatistics());System.out.println("Short menu: " + getShortMenu());System.out.println("Short menu comma separated: " + getShortMenuCommaSeparated());}private static long howManyDishes() {return menu.stream().collect(counting());}private static Dish findMostCaloricDish() {return menu.stream().collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2)).get();}private static Dish findMostCaloricDishUsingComparator() {Comparator<Dish> dishCaloriesComparator = Comparator.comparingInt(Dish::getCalories);BinaryOperator<Dish> moreCaloricOf = BinaryOperator.maxBy(dishCaloriesComparator);return menu.stream().collect(reducing(moreCaloricOf)).get();}private static int calculateTotalCalories() {return menu.stream().collect(summingInt(Dish::getCalories));}private static Double calculateAverageCalories() {return menu.stream().collect(averagingInt(Dish::getCalories));}private static IntSummaryStatistics calculateMenuStatistics() {return menu.stream().collect(summarizingInt(Dish::getCalories));}private static String getShortMenu() {return menu.stream().map(Dish::getName).collect(joining());}private static String getShortMenuCommaSeparated() {return menu.stream().map(Dish::getName).collect(joining(", "));}
}

6、小结

通过上文,基本初步了解了lambda表达式是怎么规范定义、解析、执行。不仅Lambda用起来很方便,性能表现在多数情况也比匿名内部类好。我们在写lambda表达式时候,尽量写简介的代码,减少内部变量捕获(因为这样会创建额外的变量对象)。希望本文可以给大家一些参考。

2021-04-12——新特性Lambda表达式和Function函数式接口编程相关推荐

  1. java8新特性Lambda和Stream以及函数式接口等新特性介绍

    主要内容 1.Lambda 表达式 2.函数式接口 3.方法引用与构造器引用 4.Stream API 5.接口中的默认方法与静态方法 6.新时间日期API 7.其他新特性 Java 8新特性简介 速 ...

  2. java lambda表达式详解_Java8新特性Lambda表达式详解

    课程目标: 通过本课程的学习,详细掌握Java8新特性之Lambda表达式: 适用人群:有Java基础的开发人员: 课程概述:从Java 8出现以来lambda是最重要的特性之一,它可以让我们用简洁流 ...

  3. Java 8 新特性 lambda表达式

    / Created by Manager on 2021/4/1. Java 8 新特性 lambda表达式 StreamAPI 新日期 新注解 */ 视频连接 1https://www.bilibi ...

  4. Java8新特性----Lambda表达式详细探讨

    Java8新特性 Lambda表达式 入门演示 案例1 如何解决 cannot be cast to java.lang.Comparable问题? 案例2 优化方式一 : 策略设计模式 优化方式二: ...

  5. JDK1.8新特性Lambda表达式入门

    摘要:此篇文章主要介绍 Java8 Lambda 表达式产生的背景和用法,以及 Lambda 表达式与匿名类的不同等.本文系 OneAPM 工程师编译整理. Java 是一流的面向对象语言,除了部分简 ...

  6. Java 8 新特性Lambda 表达式

    Java 8 新特性Lambda 表达式 一.常用循环 二.匿名内部类 三.排序集合 四.循环打印对象 五.根据条件修改 六.排序 七.求和 八.统计方法 九.材料 一.常用循环 public cla ...

  7. C++11新特性——λ(lambda)表达式详解

    C++11新特性--λ(lambda)表达式 C++11中引入了λ表达式,它可以用来定义一个内联(inline)的函数,作为一个本地的对象或者一个参数.有了λ表达式,我们可以很方便的使用stl标准库. ...

  8. java新特性lambda表达式快速入门

    文章目录 序 常规写法 写法一:新建类并实现该接口 写法二:使用匿名函数 lambda写法 写法一:lambda常规写法 写法二:lambda简写 中场疑问 lambda的方法引用 写法一 写法二 练 ...

  9. Java8 新特性 -- Lambda表达式:函数式接口、方法的默认实现和静态方法、方法引用、注解、类型推测、Optional类、Stream类、调用JavaScript、Base64

    文章目录 1. Lambda表达式 1.1 Lambda表达式语法 1.2 Lambda表达式示例 1.3 说明:函数式接口 2. 方法的默认实现和静态方法 3. 方法引用 3.1 方法引用示例 4. ...

  10. JDK1.8新特性之Lambda表达式+Stream流+函数式接口

    一.Lambda表达式 Lambda表达式,是JDK1.8引入的一种语法,这种语法可以对匿名内部类的写法,进行简写. 1.快速入门 package org.westos.demo2;import ja ...

最新文章

  1. BZOJ 2707: [SDOI2012]走迷宫 [高斯消元 scc缩点]
  2. 如果遇到Hadoop集群正常,MapReduce作业运行出现错误,如何来查看作业运行日志(图文详解)...
  3. Scala vs. Groovy vs. Clojure [已结束]
  4. SpringBoot快速开发利器:Spring Boot CLI
  5. Winfrom中设置ZedGraph显示多个标题(一个标题换行显示)效果
  6. 网段和子网的区别_电焊石笼网与普通石笼网区别
  7. Apple Configurator 2下载 Apple Configurator 2 for mac官方最新版 支持M1
  8. 程序员常见保健方法【转贴】
  9. Spring Boot(5) web开发(3)拦截器、文件上传、异常处理
  10. python读取配置文件获取所有键值对_python读取配置文件 变量 ConfigParser模块
  11. SQL Server中删除重复数据
  12. C++标准程序库读书笔记-第四章通用工具
  13. windows7中文企业版安装英文语言包
  14. PS-tenday-强大的画笔工具(手绘)
  15. 分布式机器学习联邦学习论文阅读笔记(持续更新)
  16. Oracle 函数编写
  17. html实心向右三角形,css 如何在html页面上输出一个左三角形或右三角形呢?
  18. Maven基础概念和安装配置教程
  19. php采集今日头条出现问题,使用php蓝天采集抓取今日头条ajax的文章内容
  20. 【观察】神州数码:向云转型再提速,技术创新再发力

热门文章

  1. python处理excel——创建excel工作簿和工作表并录入信息(openpyxl库)
  2. 六面体单元matlab后处理,《有限元基础教程》_【MATLAB算例】基于节点六面体单元的空间块体分析(HexahedralDNode).doc...
  3. 【SpringMVC】DispatcherServlet重要组件之一MultipartResolver
  4. 利用神经网络预测股票价格走势
  5. matlab计算图像的曲率半径
  6. 热衷于摸鱼的大一新生
  7. 日本多城现共享单车 日网友:感受到中国式刺激
  8. 从专业角度分析国内创客教育发展
  9. 求模 和 求余 的区别
  10. Serval and Rooted Tree(CF1153D)-DP