jdk1.8新特性知识点:

  • Lambda表达式
  • 函数式接口
  • 方法引用和构造器调用
  • Stream API
  • 接口中的默认方法和静态方法
  • 新时间日期API

在jdk1.8中对hashMap等map集合的数据结构优化。hashMap数据结构的优化,原来的hashMap采用的数据结构是哈希表(数组+链表),hashMap默认大小是16,一个0-15索引的数组,如何往里面存储元素,首先调用元素的hashcode方法,计算出哈希码值,经过哈希算法算成数组的索引值,如果对应的索引处没有元素,直接存放,如果有对象在,那么比较它们的equals方法比较内容;如果内容一样,后一个value会将前一个value的值覆盖,如果不一样,在1.7的时候,后加的放在前面,形成一个链表,形成了碰撞,在某些情况下如果链表。无限下去,那么效率极低,碰撞是避免不了的;加载因子:0.75,数组扩容,达到总容量的75%,就进行扩容,但是无法避免碰撞的情况发生;在1.8之后,在数组+链表+红黑树来实现hashmap,当碰撞的元素个数大于8时 & 总容量大于64,会有红黑树的引入;除了添加之后,效率都比链表高,1.8之后链表新进元素加到末尾。ConcurrentHashMap在jdk1.7锁分段机制,并发级别默认16,concurrentLevel,jdk1.8采用CAS算法(无锁算法,不再使用锁分段),数组+链表中也引入了红黑树的使用。

JVM内存结构

新特性简介:

  • 速度更快
  • 代码更少(Lambda)
  • 强大的StreamAPI
  • 便于并行
  • 最大化减少空指针异常Optional

Lambda表达式

lambda表达式本质上是一段匿名内部类,也可以是一段可以传递的代码。
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
Lambda 表达式可以使代码变的更加简洁紧凑。
lambda 表达式的语法格式如下:

(parameters) -> expression
或
(parameters) ->{ statements; }

以下是lambda表达式的重要特征:

  • **可选类型声明:**不需要声明参数类型,编译器可以统一识别参数值。
  • **可选的参数圆括号:**一个参数无需定义圆括号,但多个参数需要定义圆括号。
  • **可选的大括号:**如果主体包含了一个语句,就不需要使用大括号。
  • **可选的返回关键字:**如果主体只有一个表达式返回值则编译器会自动返回值,大括号指定明表达式返回一个数值。

Lambda 表达式的简单例子:

// 1. 不需要参数,返回值为 5
() -> 5
// 2. 接收一个参数(数字类型),返回其2倍的值
x -> 2 * x
// 3. 接受2个参数(数字),并返回他们的差值
(x, y) -> x – y
// 4. 接收2个int型整数,返回他们的和
(int x, int y) -> x + y
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
(String s) -> System.out.print(s)

先来体验一下lambda最直观的优点:简洁代码

package com.hanker.java8;
import java.util.Comparator;
import java.util.TreeSet;
public class LambdaDemo1 {public static void main(String[] args) {//匿名内部类Comparator<Integer> cpt = new Comparator<Integer>() {@Overridepublic int compare(Integer o1, Integer o2) {return Integer.compare(o1,o2);}};TreeSet<Integer> set = new TreeSet<>(cpt);System.out.println("=========================");//使用lambda表达式Comparator<Integer> cpt2 = (x,y) -> Integer.compare(x,y);TreeSet<Integer> set2 = new TreeSet<>(cpt2);}
}

只需要一行代码,极大减少代码量!!
这样一个场景,在商城浏览商品信息时,经常会有条件的进行筛选浏览,例如要选颜色为红色的、价格小于8000千的….
综合案例:
①需求:获取当前公司中年龄大于35岁的员工信息
②需求: 获取公司中员工工资大于5000的员工信息

package com.hanker.java8;
public class Employee {private String name;private Integer age;private Double salary;public Employee(String name, Integer age, Double salary) {super();this.name = name;this.age = age;this.salary = salary;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public Double getSalary() {return salary;}public void setSalary(Double salary) {this.salary = salary;}@Overridepublic String toString() {return "Employee [name=" + name + ", age=" + age + ", salary=" + salary + "]";}
}
//==========普通实现方法============
package com.hanker.java8;
import java.util.ArrayList;
import java.util.List;
public class LambdaDemo2 {private static List<Employee> list = new ArrayList<>();static {list.add( new Employee("张三",38,5500.00) );list.add( new Employee("李四",30,4500.00) );list.add( new Employee("王五",48,6500.00) );list.add( new Employee("赵六",28,8500.00) );}public static void main(String[] args) {List<Employee> emps = filterEmp1(list);for (Employee e : emps) {System.out.println(e);}System.out.println("===============");emps = filterEmp2(list);for (Employee e : emps) {System.out.println(e);}}//需求: 获取公司中员工工资大于5000的员工信息private static List<Employee> filterEmp2(List<Employee> list){List<Employee> emps = new ArrayList<>();for(Employee emp : list) {if(emp.getSalary() > 5000) {emps.add(emp);}}return emps;}//需求:获取当前公司中年龄大于35岁的员工信息private static List<Employee> filterEmp1(List<Employee> list){List<Employee> emps = new ArrayList<>();for(Employee emp : list) {if(emp.getAge() > 35) {emps.add(emp);}}return emps;}
}

上面两个方法的核心代码就一行:比较大小的代码;但是我们需要写很多代码。
四种代码优化方式:

package com.hanker.java8;
//编写一个策略接口
public interface MyPredicate<T> {boolean test(T t);
}
//======第一个实现类:根据年龄过滤=======
package com.hanker.java8;
public class FilterEmployeeByAge implements MyPredicate<Employee> {@Overridepublic boolean test(Employee t) {return t.getAge()>=35;}
}
//======第二个实现类:根据工资过滤=======
package com.hanker.java8;
public class FilterEmployeeBySalary implements MyPredicate<Employee> {@Overridepublic boolean test(Employee t) {return t.getSalary()>=5000;}
}
//==========测试类===============
package com.hanker.java8;
import java.util.ArrayList;
import java.util.List;
import org.junit.Test;
//需求:获取当前公司中年龄大于35岁的员工信息
//需求: 获取公司中员工工资大于5000的员工信息
public class LambdaDemo3 {private static List<Employee> list = new ArrayList<>();static {list.add( new Employee("张三",38,5500.00) );list.add( new Employee("李四",30,4500.00) );list.add( new Employee("王五",48,6500.00) );list.add( new Employee("赵六",28,8500.00) );}public static void main(String[] args) {List<Employee> emps = filterEmp(list,new FilterEmployeeByAge());for (Employee e : emps) {System.out.println(e);}System.out.println("================");List<Employee> emps2 = filterEmp(list,new FilterEmployeeBySalary());for (Employee e : emps2) {System.out.println(e);}}//优化方式一:使用策略模式private static List<Employee> filterEmp(List<Employee> list,MyPredicate<Employee> predicate){List<Employee> emps = new ArrayList<>();for(Employee emp : list) {if(predicate.test(emp)) {emps.add(emp);}}return emps;}//优化方式二: 使用匿名内部类优化代码@Testpublic void test1() {List<Employee> emps =  filterEmp(list, new MyPredicate<Employee>() {@Overridepublic boolean test(Employee t) {return t.getSalary()>5000;}});for (Employee employee : emps) {System.out.println(employee);}}//优化方式三:使用Lambda表达式@Testpublic void test2() {List<Employee> emps = filterEmp(list, e -> e.getSalary()>5000);emps.forEach(System.out::println);}//优化方式四:使用Stream API@Testpublic void test3() {list.stream().filter(e -> e.getSalary()>6000).limit(2).forEach(System.out::println);System.out.println("------------------------");list.stream().map(Employee::getName).forEach(System.out::println);}
}

Lmabda表达式的语法总结: ( ) -> ( );

前置 语法
无参数无返回值 () -> System.out.println(“Hello World”)
有一个参数无返回值 (x) -> System.out.println(x)
有且只有一个参数无返回值 x -> System.out.println(x)
有多个参数,有返回值,有多条lambda体语句 (x,y) -> {System.out.println(“xxx”);return xxxx;};
有多个参数,有返回值,只有一条lambda体语句 (x,y) -> xxxx

口诀:左右遇一省括号,左侧推断类型省
注:当一个接口中存在多个抽象方法时,如果使用lambda表达式,并不能智能匹配对应的抽象方法,因此引入了函数式接口的概念。

package com.hanker.java8;
import java.util.Comparator;
import java.util.function.Consumer;
import org.junit.Test;
//Lambda表达式的基础语法:
public class LambdaDemo4 {//语法格式1:无参数无返回值@Testpublic void test1() {int num = 0;//jdk1.7前必须是finalRunnable r = new Runnable() {public void run() {System.out.println("Welcome to Beijing " + num);}};r.run();System.out.println("--------------------");Runnable r1 = () -> System.out.println("Hello Lambda!!");r1.run();}//语法格式2:有一个参数无返回值, 注意参数的小括号可以省略@Testpublic void test2() {Consumer<String> con = (x) -> System.out.println("欢迎访问:"+x);con.accept("淘宝");}//语法格式3:有两个以上的参数,有返回值,并且Lambda体中有多条语句@Testpublic void test3() {Comparator<Integer> com = (x,y) -> {System.out.println(":::函数式接口:::");return Integer.compare(x, y);};}//语法格式4:若Lambda体中只有一条语句,return和大括号都可以省略不写@Testpublic void test4() {Comparator<Integer> com = (x,y) -> {System.out.println(":::函数式接口:::");return Integer.compare(x, y);};Comparator<Integer> com2 = (x,y) -> Integer.compare(x, y);}//语法格式5:Lambda表达式的参数列表的数据类型可以省略,因为JVM编译器通过上下文推断出数据类型,即“类型推断”@Testpublic void test5() {Comparator<Integer> com2 = (Integer x,Integer y) -> Integer.compare(x, y);Comparator<Integer> com3 = (x,y) -> Integer.compare(x, y);}
}

函数式接口

函数式接口的提出是为了给Lambda表达式的使用提供更好的支持。什么是函数式接口?
简单来说就是只定义了一个抽象方法的接口(Object类的public方法除外),就是函数式接口,并且还提供了注解:@FunctionalInterface. 函数式接口练习:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PZiO1iUV-1605148026323)(images/2.png)]

代码:

package com.hanker.java8;
@FunctionalInterface
public interface MyFunction {public String getValue(String str);
}
//================
package com.hanker.java8;
@FunctionalInterface
public interface MyFunction2<T,R> {public R getValue(T t1,T t2);
}
//=============
package com.hanker.java8;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.Test;public class FunctionInterfaceDemo {private static List<Employee> list = new ArrayList<>();static {list.add( new Employee("张三",38,5500.00) );list.add( new Employee("李四",30,4500.00) );list.add( new Employee("王五",48,6500.00) );list.add( new Employee("赵六",28,8500.00) );}@Testpublic void test2() {Collections.sort(list, (e1,e2) ->{if(e1.getAge() == e2.getAge()) {return e1.getName().compareTo(e2.getName());}else {return - Integer.compare(e1.getAge(), e2.getAge());}});for(Employee e:list) {System.out.println(e);}}@Testpublic void test3() {String s = strHandler("   欢迎光临    ", str -> str.trim());System.out.println(s);s = strHandler("abcdef", str -> str.toUpperCase());System.out.println(s);s = strHandler("13803711122",str->str.substring(3, 7));System.out.println(s);}//需求:用于处理字符串public String strHandler(String str, MyFunction mf) {return mf.getValue(str);}@Testpublic void test4() {op(100L,200L,(x,y) -> x + y);op(100L,200L,(x,y) -> x * y);}//需求: 对于两个Long型数据进行处理public void op(Long l1,Long l2,MyFunction2<Long, Long> mf) {System.out.println(mf.getValue(l1, l2));}@Testpublic void test1() {Integer num  = operation(100, (x) -> x * x);System.out.println("num==="+num);}public Integer operation(Integer num, MyFun mf) {return mf.getValue(num);}
}

常见的四大函数式接口

  • Consumer:消费型接口,有参无返回值

    //Consumer<T> 消费型接口:
    @Test
    public void test1() {happy(10000, m -> System.out.println("淘宝购物消费:"+m+"元"));
    }
    public void happy(double money,Consumer<Double> con) {con.accept(money);
    }
    
  • Supplier:供给型接口,无参有返回值

    //Supplier<T> 供给型接口:
    @Test
    public void test2() {List<Integer> numList = getNumList(10, () -> (int)(Math.random()*100) );for (Integer num : numList) {System.out.println(num);}
    }
    //需求:产生指定个数的整数,并放入集合
    public List<Integer> getNumList(int num,Supplier<Integer> sup){List<Integer> list = new ArrayList<Integer>();for(int i=0; i<num ; i++) {Integer n = sup.get();list.add(n);}return list;
    }
    
  • Function<T,R>: 函数式接口,有参有返回值

    //Function<T,R> 函数型接口
    @Test
    public void test3() {String s = strHandler("  \t\t大家好\t\t    ", str ->str.trim());System.out.println(s);
    }
    //需求: 用于处理字符串
    public String strHandler(String str,Function<String, String> fun) {return fun.apply(str);
    }
    
  • Predicate: 断言型接口,有参有返回值,返回值是boolean类型

    //Predicate<T> 断言型接口:
    @Test
    public void test4() {List<String> list = Arrays.asList("张无忌","周芷若","赵敏","殷离","小昭");List<String> newlist = filterStr(list, str -> str.length()>=3);for (String string : newlist) {System.out.println(string);}
    }//需求:将满足条件的字符串放入集合中
    public List<String> filterStr(List<String> list,Predicate<String> pre){List<String> retlist = new ArrayList<String>();for(String str : list) {if(pre.test(str)) {retlist.add(str);}}return retlist;
    }
    

    在四大核心函数式接口基础上,还提供了诸如BiFunction、BinaryOperation、toIntFunction等扩展的函数式接口,都是在这四种函数式接口上扩展而来的,不做赘述。

    总结:函数式接口的提出是为了让我们更加方便的使用lambda表达式,不需要自己再手动创建一个函数式接口,直接拿来用就好了.

方法引用

若lambda体中的内容有方法已经实现了,那么可以使用“方法引用”
也可以理解为方法引用是lambda表达式的另外一种表现形式并且其语法比lambda表达式更加简单.
注意:
1.lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
2.若lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method

(a) 方法引用
三种表现形式:
①对象::实例方法名
②类::静态方法名
③类:实例方法名 (lambda参数列表中第一个参数是实例方法的调用 者,第二个参数是实例方法的参数时可用)

package com.hanker.java8;import java.io.PrintStream;
import java.util.Comparator;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Supplier;import org.junit.Test;public class TestMethodRef {//对象::实例方法名@Testpublic void test1() {PrintStream ps1 = System.out;Consumer<String> con = x -> ps1.println(x);PrintStream ps = System.out;Consumer<String> con1 = ps::println;Consumer<String> con2 = System.out::println;con2.accept("张无忌");}@Testpublic void test2() {Employee emp = new Employee("赵敏", 18, 8899D);Supplier<String> sup = () -> emp.getName();System.out.println(sup.get());Supplier<Integer> sup2 = emp::getAge;System.out.println(sup2.get());}//类::静态方法@Testpublic  void test3() {Comparator<Integer> com = (x,y) -> Integer.compare(x, y);Comparator<Integer> com1 = Integer::compare;int x = com1.compare(100, 200);System.out.println(x);}//类::实例方法@Testpublic void test4() {BiPredicate<String, String> bp = (x,y) -> x.equals(y);//若Lambda参数列表中的第一个参数是实例方法的调用者,//而第二个参数是实例方法的参数时,可以使用ClassName::methodBiPredicate<String, String> bp2 = String::equals;}
}

(b)构造器引用
格式:ClassName::new
注意: 需要调用的构造器的参数列表要与函数式接口中抽象方法的参数列表保持一致。

//构造方法引用:ClassName::new
@Test public void test5() {Supplier<Employee> sup = () -> new Employee();Supplier<Employee> sup2 = Employee::new;Employee emp = sup2.get();System.out.println(emp);//调用带参数的构造器Function<String, Employee> fun2 = Employee::new;Employee emp2 = fun2.apply("小昭");System.out.println(emp2);BiFunction<String, Integer, Employee> bf = Employee::new;Employee emp3 = bf.apply("周芷若", 24);System.out.println(emp3);}

©数组引用
格式:Type[]::new

//数组引用
@Test public void test6() {Function<Integer, String[]> fun = x -> new String[x];String [] strs = fun.apply(10);System.out.println(strs.length);Function<Integer, String[]> fun2 = String[]::new;String[] strs2 = fun2.apply(20);System.out.println(strs2.length);
}

Stream API

流Stream到底是什么呢?是数据渠道,用于操作数据源(集合,数组等),所生成的元素序列。“集合讲的是数据,流讲的是计算”。
注意:
①Stream自己不会存储元素
②Stream不会改变源对象,相反,他们会返回一个持有结果的新Stream
③Stream操作是延迟执行的,这意味着他们会等到需要结果的时候才执行

Stream操作的三个步骤

  • 创建stream: 一个数据源(如:集合,数组),获取一个流
  • 中间操作(过滤、map):一个中间操作链,对数据源的数据进行处理
  • 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果

Stream的创建

package com.hanker.java8;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.junit.Test;
public class TestStreamAPI {//创建Stream@Test public void test1() {//1.可以通过Collection系列集合提供的stream()或parallelStream()List<String> list = new ArrayList<>();Stream<String> stream1 = list.stream();//2.通过Arrays中的静态方法stream()获取数组流Employee [] emps = new Employee[10];Stream<Employee> stream2 = Arrays.stream(emps);//3.通过Stream类中的静态方法of()Stream<String> stream3 = Stream.of("aaa","bbb","ccc");//4.创建无限流-迭代Stream<Integer> stream4 = Stream.iterate(0, x -> x + 2);stream4.limit(10).forEach(System.out::println);//4.创建无限流-生成Stream.generate( ()->Math.random() ).limit(5).forEach(System.out::println);}
}

Stream的中间操作

package com.hanker.java8;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;
import org.junit.Test;
public class TestStreamAPI {private static List<Employee> list = new ArrayList<>();static {list.add( new Employee("张三",38,5500.00) );list.add( new Employee("李四",30,4500.00) );list.add( new Employee("王五",48,6500.00) );list.add( new Employee("赵六",28,8500.00) );list.add( new Employee("赵六",28,8500.00) );list.add( new Employee("赵六",28,8500.00) );list.add( new Employee("赵六",28,8500.00) );}//中间操作:迭代操作由Stream API完成,也称为内部迭代,@Test public void test2() {//filter中间操作:接收Lambda,从流中过滤某些元素,不会只想任何操作Stream<Employee> stream = list.stream().filter(e -> e.getAge() > 35);//终止操作:一次性执行全部内容,即:“惰性求值”stream.forEach(System.out::println);}//中间操作:limit@Test public void test3() {list.stream().filter(e -> {System.out.println("短路。。");return e.getSalary()>5000;}).limit(2).forEach(System.out::println);}//中间操作:skip(n)跳过元素,返回一个扔掉前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补@Test public void test4() {list.stream().filter(e -> e.getSalary()>5000).skip(2).forEach(System.out::println);}//中间操作:distinct去重,通过流所生成元素的hashCode()和equals()去除重复@Test public void test5() {list.stream().filter(e -> e.getSalary()>5000).distinct().forEach(System.out::println);}//中间操作:map映射,接收Lambda,将元素转换成其他形式或提取信息。接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素@Test public void test6() {List<String> strList = Arrays.asList("aaa","bbb","ccc","ddd");strList.stream().map(str -> str.toUpperCase()).forEach(System.out::println);System.out.println("==============");list.stream().map(Employee::getName).forEach(System.out::println);}//中间操作:flatMap接收一个函数作为参数,讲流中的每个值都转换成一个另一个流,然后把所有流连接成一个流@Test public void test7() {List<String> strList = Arrays.asList("aaa","bbb","ccc","ddd");Stream<Character> sm = strList.stream().flatMap(TestStreamAPI::filterCharacter);sm.forEach(System.out::println);}public static Stream<Character> filterCharacter(String str){List<Character> list = new ArrayList<Character>();for (Character character : str.toCharArray()) {list.add(character);}return list.stream();}//中间操作:排序, sorted自然排序(Comparable),sorted(Comparator com)定制排序@Test public void test8() {List<String> strList = Arrays.asList("ccc","bbb","aaa","ddd");strList.stream().sorted().forEach(System.out::println);System.out.println("---------------");list.stream().sorted((e1,e2) ->{if(e1.getAge().equals(e2.getAge())) {return e1.getName().compareTo(e2.getName());}else {return -e1.getAge().compareTo(e2.getAge());}}).forEach(System.out::println);}
}

Stream的终止操作

实体类

package com.dubbo.demo;
public class Employee {private int id;private String name;private int age;private double salary;private Status status;public Employee() {}public Employee(String name) {this.name = name;}public Employee(String name, int age) {this.name = name;this.age = age;}public Employee(int id, String name, int age, double salary) {this.id = id;this.name = name;this.age = age;this.salary = salary;}public Employee(int id, String name, int age, double salary, Status status) {this.id = id;this.name = name;this.age = age;this.salary = salary;this.status = status;}public Status getStatus() {return status;}public void setStatus(Status status) {this.status = status;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public double getSalary() {return salary;}public void setSalary(double salary) {this.salary = salary;}public String show() {return "测试方法引用!";}@Overridepublic int hashCode() {final int prime = 31;int result = 1;result = prime * result + age;result = prime * result + id;result = prime * result + ((name == null) ? 0 : name.hashCode());long temp;temp = Double.doubleToLongBits(salary);result = prime * result + (int) (temp ^ (temp >>> 32));return result;}@Overridepublic boolean equals(Object obj) {if (this == obj)return true;if (obj == null)return false;if (getClass() != obj.getClass())return false;Employee other = (Employee) obj;if (age != other.age)return false;if (id != other.id)return false;if (name == null) {if (other.name != null)return false;} else if (!name.equals(other.name))return false;if (Double.doubleToLongBits(salary) != Double.doubleToLongBits(other.salary))return false;return true;}@Overridepublic String toString() {return "Employee [id=" + id + ", name=" + name + ", age=" + age + ", salary=" + salary + ", status=" + status+ "]";}public enum Status {FREE, BUSY, VOCATION;}
}

案例

package com.dubbo.demo;
import com.dubbo.demo.Employee.Status;
import org.junit.Test;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public class AppTest {List<Employee> emps = Arrays.asList(new Employee(102, "李四", 79, 6666.66, Status.BUSY),new Employee(101, "张三", 18, 9999.99, Status.FREE),new Employee(103, "王五", 28, 3333.33, Status.VOCATION),new Employee(104, "赵六", 8, 7777.77, Status.BUSY),new Employee(104, "赵六", 8, 7777.77, Status.FREE),new Employee(104, "赵六", 8, 7777.77, Status.FREE),new Employee(105, "田七", 38, 5555.55, Status.BUSY));/*** allMatch--检查是否匹配所有元素* anyMatch--检查是否至少匹配一个元素* noneMatch--检查是否没有匹配所有元素* findFirst--返回第一个元素* findAny--返回当前流的任一对象*/@Testpublic void test1(){boolean b1 = emps.stream().allMatch(e -> e.getStatus().equals(Status.BUSY));System.out.println(b1);boolean b2 = emps.stream().anyMatch(e -> e.getStatus().equals(Status.BUSY));System.out.println(b2);boolean b3 = emps.stream().noneMatch(e -> e.getStatus().equals(Status.BUSY));System.out.println(b3);//Optional容器,封装一个有可能为空的对象Optional<Employee> optional = emps.stream().sorted((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())).findFirst();System.out.println(optional.get());Optional<Employee> op2 = emps.stream().filter(e -> e.getStatus().equals(Status.FREE)).findAny();System.out.println(op2.get());}/*** count---总数* max-----最大值* min-----最小值*/@Testpublic void test2(){long count = emps.stream().count();System.out.println(count);Optional<Employee> max = emps.stream().max(Comparator.comparingDouble(Employee::getSalary));System.out.println(max);Optional<Double> min = emps.stream().map(Employee::getSalary).min(Double::compare);System.out.println(min.get());}
}

Stream的归约操作

    /*** 归约: reduce(T identity,BinaryOpertor) reduce(BinaryOperator)* 可以将流中元素反复结合起来,得到一个值*/@Testpublic void test3(){List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);//先把 0 作为x,从流中取出数据赋给 y 累加求和Integer sum = list.stream().reduce(0, (x, y) -> x + y);System.out.println(sum);//需求: 计算所有员工的工资总和Optional<Double> optional = emps.stream().map(Employee::getSalary).reduce(Double::sum);System.out.println(optional.get());}

Stream的收集操作

/*** 收集:collect--将流转换为其他形式,接收一个Collector接口的实现,用于给Stream中元素做汇总的方法* Collector接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors实用类提供了很多静态方法* 可以方便地创建常见收集器实例*/@Testpublic void test4(){List<String> list = emps.stream().map(Employee::getName).collect(Collectors.toList());list.forEach(System.out::println);System.out.println("====================");Set<String> set = emps.stream().map(Employee::getName).collect(Collectors.toSet());set.forEach(System.out::println);System.out.println("====================");HashSet<String> hashSet = emps.stream().map(Employee::getName).collect(Collectors.toCollection(HashSet::new));hashSet.forEach(System.out::println);}@Testpublic void test5(){//总数Long count = emps.stream().collect(Collectors.counting());System.out.println(count);System.out.println("====================");//平均值Double avg = emps.stream().collect(Collectors.averagingDouble(Employee::getSalary));System.out.println(avg);//总和Double sum = emps.stream().collect(Collectors.summingDouble(Employee::getSalary));System.out.println(sum);//最大值Optional<Employee> max = emps.stream().collect(Collectors.maxBy((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary())));System.out.println(max.get());//最小值Optional<Double> min = emps.stream().map(Employee::getSalary).collect(Collectors.minBy(Double::compare));System.out.println(min.get());//分组Map<Status, List<Employee>> collect = emps.stream().collect(Collectors.groupingBy(Employee::getStatus));System.out.println(collect);//多级分组Map<Status, Map<String, List<Employee>>> mapMap = emps.stream().collect(Collectors.groupingBy(Employee::getStatus, Collectors.groupingBy(e -> {if (e.getAge() <= 35) {return "青年";} else if (e.getAge() <= 50) {return "中年";} else {return "老年";}})));System.out.println(mapMap);}@Testpublic void test6(){//分区Map<Boolean, List<Employee>> map = emps.stream().collect(Collectors.partitioningBy(e -> e.getSalary() > 8000));System.out.println(map);//统计DoubleSummaryStatistics dss = emps.stream().collect(Collectors.summarizingDouble(Employee::getSalary));System.out.println(dss.getMax());System.out.println(dss.getSum());System.out.println(dss.getAverage());System.out.println(dss.getCount());//连接String names = emps.stream().map(Employee::getName).collect(Collectors.joining(","));System.out.println(names);}

Stream API 练习

见后续博客文章

并行流与顺序流

并行流就是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。

Java8中将并行进行了优化,我们可以很容易的对数据进行并行操作。StreamAPI可以声明性地通过parallel与sequential在并并流与顺序流之间进行切换。

了解Fork/Join框架

Fork/Join框架:就是在必要的情况下,将一个大任务,进行拆分(fork)成若干个小任务(拆到不可再拆时),再将一个个的小任务运算的结果进行join汇总。

Fork/Join框架与传统线程池的区别

采用“工作窃取”模式(work=stealing):当执行新的任务是它可以将其拆分成更小的任务执行,并将小任务加到线程队列中,然后再从一个随机线程的队列中偷一个并把它放在自己的队列中。

相对于一般的线程池实现,Fork/Join框架的优势体现在对其中包含的任务的处理方式上,在一般的线程池中,如果一个线程正在执行的任务由于某些原因无法继续执行,那么该线程会处于等待状态,而在fork/join框架实现中,如果某个子问题由于等待另外一个子问题的完成而无法继续执行,那么处理该子问题的线程会主动寻找其他尚未运行的子问题来执行,这种方式减少了线程的等待时间,提高了性能。

案例:

package com.dubbo.demo;
import java.util.concurrent.RecursiveTask;
public class ForkJoinCalculate extends RecursiveTask<Long> {private long start;private long end;private static final long THRESHOLD = 10000;public ForkJoinCalculate(long start,long end){this.start = start;this.end = end;}@Overrideprotected Long compute() {long length = end - start;if(length <= THRESHOLD){long sum  = 0;for (long i = start; i <= end; i ++){sum += i;}return sum;}else{long middle = (start + end) / 2;ForkJoinCalculate left = new ForkJoinCalculate(start,middle);left.fork();//拆分子任务,同时亚入线程队列ForkJoinCalculate right = new ForkJoinCalculate(middle + 1, end);right.fork();return left.join() + right.join();}}
}

测试类:

package com.dubbo.demo;
import org.junit.Test;
import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ForkJoinPool;
import java.util.stream.LongStream;public class ForkJoinTest {/*** fork/join框架*/@Testpublic void test1(){Instant start = Instant.now();ForkJoinPool pool = new ForkJoinPool();ForkJoinCalculate task = new ForkJoinCalculate(0,10000000);Long sum = pool.invoke(task);System.out.println(sum);Instant end = Instant.now();System.out.println("耗费时间:"+ Duration.between(start,end).toMillis());}/*** 普通for循环*/@Testpublic void test2(){Instant start = Instant.now();long sum = 0;for (long i=0; i < 10000000; i++){sum += i;}System.out.println(sum);Instant end = Instant.now();System.out.println("耗费时间:"+ Duration.between(start,end).toMillis());}/*** java8并行流*/@Testpublic void test3(){Instant start = Instant.now();LongStream.rangeClosed(0,10000000).parallel().reduce(0,Long::sum);Instant end = Instant.now();System.out.println("耗费时间:"+ Duration.between(start,end).toMillis());}
}

Optional类

Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在,原来用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。

常用方法:

  • Optional.of(T t):创建一个Optional实例
  • Optional.empty():创建一个空的Optional实例
  • Optional.ofNullable(T t):若t不为null,创建Optional实例,否则创建空实例
  • isPresent():判断是否包含值
  • orElse(T t): 如果调用对包含值,返回该值,否则返回t
  • orElseGet(Supplier s): 如果调用对象包含值,返回该值,否则返回s获取的值
  • map(Funcaiton f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
  • flatMap(Function mapper):与map类似,要求返回值必须是Optional

案例:

/***      Optional.of(T t); // 创建一个Optional实例*      Optional.empty(); // 创建一个空的Optional实例*      Optional.ofNullable(T t); // 若T不为null,创建一个Optional实例,否则创建一个空实例*      isPresent();    // 判断是够包含值*      orElse(T t);   //如果调用对象包含值,返回该值,否则返回T*      orElseGet(Supplier s);  // 如果调用对象包含值,返回该值,否则返回s中获取的值*      map(Function f): // 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty();*      flatMap(Function mapper);// 与map类似。返回值是Optional**      总结:Optional.of(null)  会直接报NPE*/
@Test
public void test1(){Optional<Employee> op = Optional.of(new Employee("zhansan", 11, 12.32, Employee.Status.BUSY));System.out.println(op.get());// NPEOptional<Employee> op2 = Optional.of(null);System.out.println(op2);
}
@Test
public void test2(){Optional<Object> op = Optional.empty();System.out.println(op);// No value presentSystem.out.println(op.get());
}
@Test
public void test3(){Optional<Employee> op = Optional.ofNullable(new Employee("lisi", 33, 131.42, Employee.Status.FREE));System.out.println(op.get());Optional<Object> op2 = Optional.ofNullable(null);System.out.println(op2);// System.out.println(op2.get());
}
@Test
public void test5(){Optional<Employee> op1 = Optional.ofNullable(new Employee("张三", 11, 11.33, Employee.Status.VOCATION));System.out.println(op1.orElse(new Employee()));System.out.println(op1.orElse(null));
}@Test
public void test6(){Optional<Employee> op1 = Optional.of(new Employee("田七", 11, 12.31, Employee.Status.BUSY));op1 = Optional.empty();Employee employee = op1.orElseGet(() -> new Employee());System.out.println(employee);
}@Test
public void test7(){Optional<Employee> op1 = Optional.of(new Employee("田七", 11, 12.31, Employee.Status.BUSY));System.out.println(op1.map( (e) -> e.getSalary()).get());
}

接口中可以定义默认实现方法和静态方法

在接口中可以使用default和static关键字来修饰接口中定义的普通方法

public interface Interface {default  String getName(){return "zhangsan";}static String getName2(){return "zhangsan";}
}

在JDK1.8中很多接口会新增方法,为了保证1.8向下兼容,1.7版本中的接口实现类不用每个都重新实现新添加的接口方法,引入了default默认实现,static的用法是直接用接口名去调方法即可。当一个类继承父类又实现接口时,若后两者方法名相同:

  • 则优先继承父类中的同名方法,即“类优先”,那么接口中具有相同名称和参数的默认方法会被忽略
  • 接口冲突:如果实现两个同名方法的接口,则要求实现类必须手动声明默认实现哪个接口中的方法。

类优先案例

package com.interview.ujiuye5;interface MyFun {default String getName(){return "哈哈哈";}
}
class MyClass {public String getName(){return "嘿嘿嘿";}}
class SubClass extends MyClass implements MyFun{}
public class TestInterface {public static void main(String[] args) {SubClass subClass = new SubClass();System.out.println(subClass.getName());//嘿嘿嘿}
}

接口冲突案例

package com.interview.ujiuye5;interface MyFun {default String getName(){return "哈哈哈";}
}
interface MyInterface {default String getName(){return "呵呵呵";}public static void show(){System.out.println("接口中的静态方法");}
}
class SubClass2 implements MyFun,MyInterface{@Overridepublic String getName() {return MyInterface.super.getName();//指定调用哪个接口的方法}
}
public class TestInterface {public static void main(String[] args) {SubClass2 subClass = new SubClass2();System.out.println(subClass.getName());MyInterface.show();//静态方法直接调用}
}

新的日期API LocalDate | LocalTime | LocalDateTime

新的日期API都是不可变的,更使用于多线程的使用环境中

老版本存在线程安全问题

package com.interview.ujiuye5;import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.*;/*** Copyright (C), 2018-2020*/
public class TestSimleDateFormat {public static void main(String[] args) throws Exception {SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");Callable<Date> task = new Callable<Date>() {public Date call() throws Exception {return sdf.parse("20201121");}};ExecutorService pool = Executors.newFixedThreadPool(10);List<Future<Date>> results = new ArrayList<>();for (int i = 0; i < 10; i++) {results.add(pool.submit(task));}for (Future<Date> future : results) {System.out.println(future.get());}pool.shutdown();}
}

解决线程安全问题:

添加一个工具类

package com.interview.ujiuye5;import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;public class DateFormatThreadLocal {private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>(){protected DateFormat initialValue(){return new SimpleDateFormat("yyyyMMdd");}};public static final Date convert(String source) throws ParseException{return df.get().parse(source);}}

使用工具类:

public class TestSimleDateFormat {public static void main(String[] args) throws Exception {  //解决多线程安全问题Callable<Date> task = new Callable<Date>() {@Overridepublic Date call() throws Exception {return DateFormatThreadLocal.convert("20201121");}};ExecutorService pool = Executors.newFixedThreadPool(10);List<Future<Date>> results = new ArrayList<>();for (int i = 0; i < 10; i++) {results.add(pool.submit(task));}for (Future<Date> future : results) {System.out.println(future.get());}pool.shutdown();}
}

使用jdk1.8的时间API

package com.interview.ujiuye5;import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;/*** Copyright (C), 2018-2020*/
public class TestDateFormat {public static void main(String[] args) throws Exception {DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyyMMdd");Callable<LocalDate> task = new Callable<LocalDate>() {@Overridepublic LocalDate call() throws Exception {LocalDate ld = LocalDate.parse("20201121", dtf);return ld;}};ExecutorService pool = Executors.newFixedThreadPool(10);List<Future<LocalDate>> results = new ArrayList<>();for (int i = 0; i < 10; i++) {results.add(pool.submit(task));}for (Future<LocalDate> future : results) {System.out.println(future.get());}pool.shutdown();}
}

正常执行没有线程安全问题。

使用LocalDate、LocalTime、LocalDateTime

  • LocalDate、LocalTime、LocalDateTime类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。

案例:

@Test
public void test(){// 从默认时区的系统时钟获取当前的日期时间。不用考虑时区差LocalDateTime date = LocalDateTime.now();//2020-07-15T14:22:39.759System.out.println(date);System.out.println(date.getYear());System.out.println(date.getMonthValue());System.out.println(date.getDayOfMonth());System.out.println(date.getHour());System.out.println(date.getMinute());System.out.println(date.getSecond());System.out.println(date.getNano());// 手动创建一个LocalDateTime实例LocalDateTime date2 = LocalDateTime.of(2020, 12, 17, 9, 31, 31, 31);System.out.println(date2);// 进行加操作,得到新的日期实例LocalDateTime date3 = date2.plusDays(12);System.out.println(date3);// 进行减操作,得到新的日期实例LocalDateTime date4 = date3.minusYears(2);System.out.println(date4);
}

时间戳类型:

@Test
public void test2(){// 时间戳  1970年1月1日00:00:00 到某一个时间点的毫秒值// 默认获取UTC时区Instant ins = Instant.now();System.out.println(ins);System.out.println(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli());System.out.println(System.currentTimeMillis());System.out.println(Instant.now().toEpochMilli());System.out.println(Instant.now().atOffset(ZoneOffset.ofHours(8)).toInstant().toEpochMilli());
}

时间间隔、日期间隔:

@Test
public void test3(){// Duration:计算两个时间之间的间隔// Period:计算两个日期之间的间隔Instant ins1 = Instant.now();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}Instant ins2 = Instant.now();Duration dura = Duration.between(ins1, ins2);System.out.println(dura);System.out.println(dura.toMillis());System.out.println("======================");LocalTime localTime = LocalTime.now();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}LocalTime localTime2 = LocalTime.now();Duration du2 = Duration.between(localTime, localTime2);System.out.println(du2);System.out.println(du2.toMillis());
}

日期间隔:

@Test
public void test4(){LocalDate localDate =LocalDate.now();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}LocalDate localDate2 = LocalDate.of(2016,12,12);Period pe = Period.between(localDate, localDate2);System.out.println(pe);
}

日期的操作

  • TemperalAdjuster 时间校验器,有时我们可能需要获取例如:将日期调整到“下个周日”等操作

  • TemperalAdjusts:该类通过静态方法提供了大量的常用TemperalAdjuster的实现。

  • 例如获取下个周日:

    LocalDate nextSunday = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.SUNDAY));
    

案例:

@Test
public void test5(){// temperalAdjust 时间校验器// 例如获取下周日  下一个工作日LocalDateTime ldt1 = LocalDateTime.now();System.out.println(ldt1);// 获取一年中的第一天LocalDateTime ldt2 = ldt1.withDayOfYear(1);System.out.println(ldt2);// 获取一个月中的第一天LocalDateTime ldt3 = ldt1.withDayOfMonth(1);System.out.println(ldt3);LocalDateTime ldt4 = ldt1.with(TemporalAdjusters.next(DayOfWeek.FRIDAY));System.out.println(ldt4);// 获取下一个工作日LocalDateTime ldt5 = ldt1.with((t) -> {LocalDateTime ldt6 = (LocalDateTime)t;DayOfWeek dayOfWeek = ldt6.getDayOfWeek();if (DayOfWeek.FRIDAY.equals(dayOfWeek)){return ldt6.plusDays(3);}else if (DayOfWeek.SATURDAY.equals(dayOfWeek)){return ldt6.plusDays(2);}else {return ldt6.plusDays(1);}});System.out.println(ldt5);
}

日期格式化:

@Test
public void test6(){// DateTimeFormatter: 格式化时间/日期// 自定义格式LocalDateTime ldt = LocalDateTime.now();DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日");String strDate1 = ldt.format(formatter);String strDate = formatter.format(ldt);System.out.println(strDate);System.out.println(strDate1);// 使用api提供的格式DateTimeFormatter dtf = DateTimeFormatter.ISO_DATE;LocalDateTime ldt2 = LocalDateTime.now();String strDate3 = dtf.format(ldt2);System.out.println(strDate3);// 解析字符串to时间DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");LocalDateTime time = LocalDateTime.now();String localTime = df.format(time);LocalDateTime ldt4 = LocalDateTime.parse("2021-09-28 17:07:05",df);System.out.println("LocalDateTime转成String类型的时间:"+localTime);System.out.println("String类型的时间转成LocalDateTime:"+ldt4);
}

时区的处理:

Java8中加入了对时区的支持,带时区的时间分别为:ZonedDate,ZonedTime,ZonedDateTime

@Test
public void test7(){LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));System.out.println(now);LocalDateTime now2 = LocalDateTime.now();ZonedDateTime zdt = now2.atZone(ZoneId.of("Asia/Shanghai"));System.out.println(zdt);Set<String> set = ZoneId.getAvailableZoneIds();set.stream().forEach(System.out::println);
}

重复注解与类型注解

Java8对注解处理提供了两点改进,可重复的注解及可用于类型的注解。

@Repeatable(MyAnnotations.class)
@Target({ElementType.TYPE_PARAMETER.....}) //用在类型参数
@Retention...
public @interface MyAnnotation{String value;
}
//容器注解
@Target
@Retention...
public @interface MyAnnotations{MyAnnotation [] value();
}public class TestAnnotation{@Testpublic void test1()throws Exception{Class<TestAnnotation> clazz = TestAnnotation.class;Method m1 = clazz.getMethod("show");MyAnnotation [] mas = m1.getAnnotationsByType(MyAnnotation.class);for(MyAnnotation m : mas){sout(m.value())}}@MyAnnotation("hello")@MyAnnotation("world")public void show(@MyAnnotation("123")  String str){   //注解用在类型参数}
}

JDK1.8 新特性相关推荐

  1. JDK1.8 新特性(全)

    JDK1.8 新特性 本文主要介绍了JDK1.8版本中的一些新特性,乃作者视频观后笔记,仅供参考. jdk1.8新特性知识点: Lambda表达式 函数式接口 方法引用和构造器调用 Stream AP ...

  2. jdk1.8新特性的应用-Stream 的终止操作

    jdk1.8新特性的应用-Stream 的终止操作 public class TestStreamApi4 {List<Employee> emps = Arrays.asList(new ...

  3. jdk1.8新特性_Lambda表达式的引入

    jdk1.8新特性_Lambda表达式的引入 引入 需求: 获取工资大于20000的员工信息 public class Employee {private String name;private in ...

  4. jdk1.5新特性5之枚举之模拟枚举类型

    一 简单应用 package cn.xy.Enum; public enum TrafficLamp {  RED,GREEN,YELLOW; } TrafficLamp red = TrafficL ...

  5. java 1.7 可变参数,JDK1.7新特性(2):异常和可变长参数处理

    异常 jdk1.7对try--catch--finally的异常处理模式进行了增强,下面我们依次来看增强的方面. 1. 为了防止异常覆盖,给Throwable类增加了addSuppressed方法,可 ...

  6. JAVA day20、21 双列集合Map<K,V>:HashMap,LinkedHashMap,TreeMap,Hashtable, ConcurrentHashMap;JDK1.9新特性

    一.Map<K,V> Java提供了专⻔的集合类⽤来存放这种这种⼀⼀对应的关系,叫做映射对象,即 java.util.Map 接⼝. 类型参数: K - 此映射所维护的键的类型 V - 映 ...

  7. JDK1.6“新“特性Instrumentation之JavaAgent

    JDK1.6"新"特性Instrumentation之JavaAgent 文章目录 JDK1.6"新"特性Instrumentation之JavaAgent 简 ...

  8. 黑马程序员————高新技术————JDK1.5新特性

    ----------------------ASP.Net+Android+IOS开发----------------------期待与您交流! JDK1.5新特性 一:静态导入 l  Import语 ...

  9. JDK-1.5_新特性

    1.泛型(Generics) 泛型是JDK1.5中一个最"酷"的特征.通过引入泛型,我们将获得编译时类型的安全和运行时更小地抛出ClassCastExceptions的可能.在JD ...

  10. java基础知识总结:基础知识、面向对象、集合框架、多线程、jdk1.5新特性、IO流、网络编程

    目录 一.基础知识: 二.面向对象 三.集合框架 四.多线程: 五.jdk1.5的新特性 六.IO流 七.网络编程: 一.基础知识: 1.JVM.JRE和JDK的区别: JVM(Java Virtua ...

最新文章

  1. 考察新人的两道c语言题目
  2. 美团即时物流的分布式系统架构设计
  3. “自拍神器”贴心实用功能大曝光
  4. 打破校史!这位参与发表学校首篇Science的博士小姐姐,近日一作再发Nature
  5. Paired Joint Coordinates
  6. MVC之AJAX异步提交表单
  7. Spring 核心容器类BeanDefinitionReader
  8. 他曾经负债2.5亿,如今身价超过500亿
  9. Java——集合带All的功能演示
  10. STL源码剖析 关联式容器
  11. python基础知识学习笔记(2)
  12. 如何用结构型信号量实现互斥和同步
  13. rockemq 发送延迟消息_58分布式消息队列WMB设计与实践
  14. bzoj 4293: [PA2015]Siano(线段树)
  15. 【转】Android游戏框架AndEngine使用入门
  16. 搜索树判断 (25 分)(先序建立二叉树)
  17. C#Redis 事务操作
  18. python之HTML文件转PDF文件,python之把HTML文件转换成PDF格式文档
  19. CentOS 5.3 安装后的基本软件配置
  20. 几何公差(GDT)的特征项目及符号

热门文章

  1. linux 屏幕录像软件,Linux系统下推荐使用的5个屏幕录像软件
  2. wps目录怎么加一条_wps目录怎么自动生成目录?目录自动生出方法介绍
  3. Laravel 8.63.0 之 RabbitMQ 生产消费案例
  4. 亿晟科技人脸识别门禁系统方案整体解决办法
  5. python编程包有什么用_一文提升你的编程能力,全面理解Python包的定义,拿走不谢...
  6. 关于国产数据库,不得不谈一下“数据库四小龙”
  7. 谁能辨我是雄雌?轩墨宝宝个人资料,轩墨宝宝CP照!
  8. Qt实现 员工培训管理系统
  9. 软件人员kpi制定模板_软件公司员工月度KPI考核表
  10. win8计算机时间同步时出错,win10电脑windows time同步出错的解决办法