17、java8中Lambda表达式与Stream API的使用
17.1 Lambda 表达式(Lambda Expressions) 1课时
17.2 函数式(Functional)接口 1课时
17.3 方法引用与构造器引用 1课时
17.4 Stream API 1课时

Java 11 2018年9月25日发布,那么还要有必要学习java 8 吗?

Java 8新特性简介

Java 8 (又称为 jdk 1.8) 是 Java 语以来最具革命性的版本。Java 8为Java语言、编译器、类库、开发工具与JVM带来了大量新特性。

  • 速度更快
  • 代码更少(增加了新的语法:Lambda 表达式)
  • 强大的 Stream API
  • 便于并行
  • 最大化减少空指针异常:Optional
  • Nashorn引擎,允许在JVM上运行JS应用

17-1 Lambda表达式

为什么使用 Lambda 表达式

  • Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。使用它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
  • 从匿名类到 Lambda 的转换举例1



  • 从匿名类到 Lambda 的转换举例2



由一个问题的迭代看Lambda表达式

问题:针对员工的集合数据,有如下的一些需求,我们考虑如何完成?

需求1:获取当前公司中员工年龄大于30的员工信息

需求2:获取公司中工资大于 5000 的员工信息

....

Lambda 表达式语法

Lambda 表达式:在Java 8 语言中引入的一种新的语法元素和操作符。这个操作符为 “->” , 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:

左侧:指定了 Lambda 表达式需要的参数列表

右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即 Lambda 表达式要执行的功能。

Lambda表达式使用

  1. public class LambdaTest {
  2. private List<Employee> data = EmployeeData.getEmployees();
  3. //举例三:由一个具体的问题,展开对Lambda表达式和 Stream api的使用的理解
  4. //解决层次六:使用Stream API
  5. @Test
  6. public void test11(){
  7. //查询员工中年龄大于30岁的员工的信息
  8. data.stream().filter(e -> e.getAge() > 30).forEach(System.out::println);
  9. System.out.println();
  10. //查询员工中工资大于5000的员工的信息
  11. data.stream().filter(e -> e.getSalary() > 5000).forEach(System.out::println);
  12. }
  13. //解决层次五:使用Lambda表达式的进一步优化
  14. @Test
  15. public void test10(){
  16. //查询员工中年龄大于30岁的员工的信息
  17. List<Employee> list = filterData(data, e -> e.getAge() > 30);
  18. list.forEach(System.out::println);
  19. System.out.println("*****************");
  20. //查询员工中工资大于5000的员工的信息
  21. List<Employee> list1 = filterData(data,e -> e.getSalary() > 5000);
  22. list1.forEach(System.out::println);
  23. }
  24. //解决层次四:使用Lambda表达式
  25. @Test
  26. public void test9(){
  27. //查询员工中年龄大于30岁的员工的信息
  28. List<Employee> list = filterData(data, e -> e.getAge() > 30);
  29. for (Employee emp : list) {
  30. System.out.println(emp);
  31. }
  32. System.out.println("*****************");
  33. //查询员工中工资大于5000的员工的信息
  34. List<Employee> list1 = filterData(data,e -> e.getSalary() > 5000);
  35. for (Employee emp : list1) {
  36. System.out.println(emp);
  37. }
  38. }
  39. //解决层次三:使用FilterData接口的匿名实现类
  40. @Test
  41. public void test8(){
  42. //查询员工中年龄大于30岁的员工的信息
  43. List<Employee> list = filterData(data, new FilterData<Employee>() {
  44. @Override
  45. public boolean filter(Employee employee) {
  46. return employee.getAge() > 30;
  47. }
  48. });
  49. for (Employee emp : list) {
  50. System.out.println(emp);
  51. }
  52. System.out.println("*****************");
  53. //查询员工中工资大于5000的员工的信息
  54. List<Employee> list1 = filterData(data, new FilterData<Employee>() {
  55. @Override
  56. public boolean filter(Employee employee) {
  57. return employee.getSalary() > 5000;
  58. }
  59. });
  60. for (Employee emp : list1) {
  61. System.out.println(emp);
  62. }
  63. }
  64. //解决层次二:策略的设计模式
  65. @Test
  66. public void test7(){
  67. //查询员工中年龄大于30岁的员工的信息
  68. List<Employee> list = filterData(data, new FilterByAge());
  69. for (Employee emp : list) {
  70. System.out.println(emp);
  71. }
  72. System.out.println("*****************");
  73. //查询员工中工资大于5000的员工的信息
  74. List<Employee> list1 = filterData(data, new FilterBySalary());
  75. for (Employee emp : list1) {
  76. System.out.println(emp);
  77. }
  78. }
  79. public List<Employee> filterData(List<Employee> list,FilterData<Employee> filter){
  80. ArrayList<Employee> data = new ArrayList<>();
  81. for (Employee emp : list) {
  82. if(filter.filter(emp)){
  83. data.add(emp);
  84. }
  85. }
  86. return data;
  87. }
  88. //解决层次一:
  89. @Test
  90. public void test6(){
  91. List<Employee> list = filterEmployeesBySalary(data);
  92. for (Employee emp : list) {
  93. System.out.println(emp);
  94. }
  95. }
  96. //问题二:查询员工中工资大于5000的员工的信息
  97. public List<Employee> filterEmployeesBySalary(List<Employee> list){
  98. ArrayList<Employee> data = new ArrayList<>();
  99. for (Employee emp : list) {
  100. if(emp.getSalary() > 5000){
  101. data.add(emp);
  102. }
  103. }
  104. return data;
  105. }
  106. @Test
  107. public void test5(){
  108. List<Employee> list = filterEmployeesByAge(data);
  109. for (Employee employee : list) {
  110. System.out.println(employee);
  111. }
  112. }
  113. //问题一:查询员工中年龄大于30岁的员工的信息
  114. public List<Employee> filterEmployeesByAge(List<Employee> list){
  115. ArrayList<Employee> data = new ArrayList<>();
  116. for (Employee emp : list) {
  117. if(emp.getAge() > 30){
  118. data.add(emp);
  119. }
  120. }
  121. return data;
  122. }
  123. //***************************************************************************
  124. //举例二:Thread中的Runnable的使用
  125. //使用Lambda之后
  126. @Test
  127. public void test4(){
  128. Runnable r = () -> {
  129. for (int i = 0; i <= 100; i++) {
  130. System.out.println(i);
  131. }
  132. };
  133. Thread t = new Thread(r);
  134. t.start();
  135. }
  136. //使用Lambda之前
  137. @Test
  138. public void test3(){
  139. //提供实现接口的匿名实现类的对象
  140. Runnable r = new Runnable() {
  141. @Override
  142. public void run() {
  143. for (int i = 0; i <= 100; i++) {
  144. System.out.println(i);
  145. }
  146. }
  147. };
  148. Thread t = new Thread(r);
  149. t.start();
  150. }
  151. //***************************************************************************
  152. //举例一:TreeSet中使用Comparator的使用
  153. //使用Lambda之后
  154. @Test
  155. public void test2(){
  156. Comparator<String> com = (o1,o2) -> o1.compareTo(o2);
  157. TreeSet<String> set = new TreeSet<String>(com);
  158. set.add("HH");
  159. set.add("MM");
  160. set.add("GG");
  161. set.add("JJ");
  162. set.add("DD");
  163. for (String s : set) {
  164. System.out.println(s);
  165. }
  166. }
  167. //使用Lambda之前
  168. @Test
  169. public void test1(){
  170. // System.out.println("hello");
  171. //提供匿名实现类的对象
  172. Comparator<String> com = new Comparator<String>() {
  173. @Override
  174. public int compare(String o1, String o2) {
  175. return o1.compareTo(o2);
  176. }
  177. };
  178. TreeSet<String> set = new TreeSet<String>(com);
  179. set.add("HH");
  180. set.add("MM");
  181. set.add("GG");
  182. set.add("JJ");
  183. set.add("DD");
  184. for (String s : set) {
  185. System.out.println(s);
  186. }
  187. }
  188. }

  1. public class Employee {
  2. private int id;
  3. private String name;
  4. private int age;
  5. private double salary;
  6. public int getId() {
  7. return id;
  8. }
  9. public void setId(int id) {
  10. this.id = id;
  11. }
  12. public String getName() {
  13. return name;
  14. }
  15. public void setName(String name) {
  16. this.name = name;
  17. }
  18. public int getAge() {
  19. return age;
  20. }
  21. public void setAge(int age) {
  22. this.age = age;
  23. }
  24. public double getSalary() {
  25. return salary;
  26. }
  27. public void setSalary(double salary) {
  28. this.salary = salary;
  29. }
  30. public Employee(int id, String name, int age, double salary) {
  31. this.id = id;
  32. this.name = name;
  33. this.age = age;
  34. this.salary = salary;
  35. }
  36. public Employee() {
  37. }
  38. @Override
  39. public String toString() {
  40. return "Employee{" +
  41. "id=" + id +
  42. ", name='" + name + '\'' +
  43. ", age=" + age +
  44. ", salary=" + salary +
  45. '}';
  46. }
  47. }

public interface FilterData {

  1. public boolean filter(T t);
  2. }

/**
*
* 提供FilterData接口的实现类,实现按照工资过滤数据
*/
public class FilterBySalary implements FilterData {
@Override
public boolean filter(Employee employee) {
return employee.getSalary() > 5000;
}
}


  1. /**
  2. * 提供FilterData接口的实现类,实现按照年龄过滤数据
  3. */
  4. public class FilterByAge implements FilterData<Employee> {
  5. @Override
  6. public boolean filter(Employee employee) {
  7. return employee.getAge() > 30;
  8. }
  9. }

  1. /**
  2. * 提供用于测试的数据
  3. *
  4. */
  5. public class EmployeeData {
  6. public static List<Employee> getEmployees(){
  7. List<Employee> list = new ArrayList<>();
  8. list.add(new Employee(1001, "马化腾", 34, 6000.38));
  9. list.add(new Employee(1002, "马云", 12, 9876.12));
  10. list.add(new Employee(1003, "刘强东", 33, 3000.82));
  11. list.add(new Employee(1004, "雷军", 26, 7657.37));
  12. list.add(new Employee(1005, "李彦宏", 65, 5555.32));
  13. list.add(new Employee(1006, "比尔盖茨", 42, 9500.43));
  14. list.add(new Employee(1007, "任正非", 25, 4333.32));
  15. list.add(new Employee(1008, "扎克伯格", 35, 2500.32));
  16. return list;
  17. }
  18. }

语法格式一:无参,无返回值

语法格式二:Lambda 需要一个参数,但是没有返回值

语法格式三:数据类型可以省略,因为可由编译器推断得出,称为“类型推断”

语法格式四:Lambda 若只需要一个参数时,数参数的小括号可以省略

语法格式五:Lambda 需要两个或以上的参数,多条执行语句,并且可以有返回值

语法格式六:当 Lambda 体只有一条语句时,return 与大括号若有,都可以省略

类型推断

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的“类型推断”。

java中Lambda表达式使用

  1. /**
  2. * 一、Lambda表达式的基本语法。
  3. * 1.格式: lambda形参列表 -> lambda体
  4. * 2.说明: -> : lambda操作符 或箭头操作符
  5. * ->左边 :lambda表达式的形参列表
  6. * ->右边:lambda表达式的执行语句,称为lambda体
  7. *
  8. * 3.如何使用:分为六种情况
  9. *
  10. * 总结:
  11. * 1)lambda形参列表:如果有形参的话,都可以省略变量的数据类型 ---类型推断
  12. * 如果形参列表只有一个形参,还可以省略一对()
  13. * 2)lambda体:正常情况下,需要使用一对{}包起来所有的执行语句。
  14. * 特别的,①lambda体如果只有一条执行语句,可以省略这一对{}.
  15. * ②如果此唯一的一条执行语句是return,则除了省略一对{}之外,return关键字也可以省略
  16. *
  17. *
  18. * 二、函数式接口
  19. * 1.特点:如果一个接口中只有唯一的一个抽象方法,则此接口称为函数式接口
  20. * 2.可以在接口的声明上使用@FunctionalInterface注解去校验一个接口是否是函数式接口
  21. * 3.lambda表达式的使用依赖于函数式接口
  22. * 4.lambda表达式即为函数式接口的实例。
  23. *
  24. * 只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。
  25. * 所以以前用匿名类表示的现在都可以用Lambda表达式来写。
  26. */
  27. public class LambdaTest {
  28. //情况六:lambda体如果只有一条执行语句,可以省略这一对{}.
  29. //特别的,如果此唯一的一条执行语句是return,则除了省略一对{}之外,return关键字也可以省略
  30. @Test
  31. public void test7(){
  32. Comparator<Integer> com1 = (o1,o2) -> o1.compareTo(o2);
  33. int value = com1.compare(12, 34);
  34. System.out.println(value);
  35. }
  36. //情况五:lambda表达式的形参列表有两个或两个以上的变量,lambda体有多条执行语句,甚至有返回值
  37. @Test
  38. public void test6(){
  39. Comparator<Integer> com1 = new Comparator<Integer>() {
  40. @Override
  41. public int compare(Integer o1, Integer o2) {
  42. System.out.println(o1);
  43. System.out.println(o2);
  44. // return Integer.compare(o1,o2);
  45. return o1.compareTo(o2);
  46. }
  47. };
  48. int value = com1.compare(12, 34);
  49. System.out.println(value);
  50. System.out.println("******************");
  51. Comparator<Integer> com2 = (o1,o2) -> {
  52. System.out.println(o1);
  53. System.out.println(o2);
  54. // return Integer.compare(o1,o2);
  55. return o1.compareTo(o2);
  56. };
  57. int value1 = com2.compare(32, 12);
  58. System.out.println(value1);
  59. }
  60. //情况四:针对于情况三进行迭代。
  61. //如果lambda表达式的形参列表只有一个变量,则可以省略一对()
  62. @Test
  63. public void test5(){
  64. Consumer<String> con1 = s -> {
  65. System.out.println(s);
  66. };
  67. con1.accept("你好我也好!");
  68. }
  69. //以前在java程序中出现的类型推断
  70. @Test
  71. public void test4(){
  72. //举例1:
  73. int[] arr = new int[]{1,2,3,4};
  74. //类型推断
  75. int[] arr1 = {1,2,3,4};
  76. int[] arr2 ;
  77. arr2 = new int[]{1,2,3,4};
  78. //举例2:
  79. List<String> list = new ArrayList<String>();
  80. //类型推断
  81. List<String> list1 = new ArrayList<>();
  82. //举例3
  83. method(new HashMap<>());
  84. }
  85. public void method(HashMap<String,Employee> map){
  86. }
  87. //情况三:针对于情况二进行迭代。lambda形参列表的变量的数据类型可以省略
  88. //说明:java编译器可以根据上下文推断出变量的数据类型,故可以省略:类型推断
  89. @Test
  90. public void test3(){
  91. Consumer<String> con1 = (s) -> {
  92. System.out.println(s);
  93. };
  94. con1.accept("你好我也好!");
  95. }
  96. //情况二:lambda形参列表有一个参数,无返回值
  97. @Test
  98. public void test2(){
  99. Consumer<String> con = new Consumer<String>() {
  100. @Override
  101. public void accept(String s) {
  102. System.out.println(s);
  103. }
  104. };
  105. con.accept("昨天过的挺好!");
  106. System.out.println("*************");
  107. Consumer<String> con1 = (String s) -> {
  108. System.out.println(s);
  109. };
  110. con1.accept("你好我也好!");
  111. }
  112. //情况一:无形参,无返回值
  113. @Test
  114. public void test1(){
  115. Runnable r = new Runnable() {
  116. @Override
  117. public void run() {
  118. System.out.println("昨天是七夕情人节!");
  119. }
  120. };
  121. Thread t1 = new Thread(r);
  122. t1.start();
  123. System.out.println("*************");
  124. Runnable r1 = () -> {
  125. System.out.println("你们有没有因为爱情而鼓掌呢?");
  126. };
  127. Thread t2 = new Thread(r1);
  128. t2.start();
  129. }
  130. }

17-2 函数式接口

什么是函数式(Functional)接口

  • 只包含一个抽象方法的接口,称为函数式接口。
  • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda 表达式抛出一个受检异常(即:非运行时异常),那么该异常需要在目标接口的抽象方法上进行声明)。
  • 我们可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口。同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。
  • 在java.util.function包下定义了java 8 的丰富的函数式接口

如何理解函数式接口

Java从诞生日起就是一直倡导“一切皆对象”,在java里面面向对象(OOP)编程是一切。但是随着python、scala等语言的兴起和新技术的挑战,java不得不做出调整以便支持更加广泛的技术要求,也即java不但可以支持OOP还可以支持OOF(面向函数编程)

在函数式编程语言当中,函数被当做一等公民对待。在将函数作为一等公民的编程语言中,Lambda表达式的类型是函数。但是在Java8中,有所不同。在Java8中,Lambda表达式是对象,而不是函数,它们必须依附于一类特别的对象类型——函数式接口。

简单的说,在Java8中,Lambda表达式就是一个函数式接口的实例。这就是Lambda表达式和函数式接口的关系。也就是说,只要一个对象是函数式接口的实例,那么该对象就可以用Lambda表达式来表示。

所以以前用匿名内部类表示的现在都可以用Lambda表达式来写。

函数式接口举例

自定义函数式接口

函数式接口中使用泛型:

作为参数传递 Lambda 表达式

作为参数传递 Lambda 表达式

作为参数传递 Lambda 表达式:为了将 Lambda 表达式作为参数传递,接收Lambda 表达式的参数类型必须是与该 Lambda 表达式兼容的函数式接口的类型。

Java 内置四大核心函数式接口

  1. /**
  2. *
  3. * java8中关于Lambda表达式提供的4个基本的函数式接口:
  4. *
  5. * 1.Consumer<T> :消费型接口
  6. * void accept(T t)
  7. *
  8. * 2.Supplier<T> : 供给型接口
  9. * T get()
  10. *
  11. * 3. Function<T,R> : 函数型接口
  12. * R apply(T t)
  13. *
  14. * 4. Predicate<T> : 断定型接口
  15. * boolean test(T t)
  16. *
  17. * 总结:从方法的角度来说:
  18. * 1.方法在调用时,如果发现方法的形参是一个函数型接口,那么我们调用此方法时,可以使用lambda表达式
  19. * 作为实参传递给此接口形参
  20. * 2.方法定义时,如果需要一个定义接口,且此接口只有一个抽象方法,(说明此接口就是函数式接口),那么
  21. * 考虑是有有现成的函数式接口可用。如果有,则不需要我们再去定义。比如:FilterData 替换为Predicate
  22. */
  23. public class LambdaTest1 {
  24. //针对于昨天讲的举例三,可以不用定义FilterData接口,而使用Predicate接口即可
  25. @Test
  26. public void test5(){
  27. List list = EmployeeData.getEmployees();
  28. List data1 = filterData(list, e -> e.getAge() > 30);
  29. System.out.println(data1);
  30. List data2 = filterData(list, e -> e.getSalary() > 5000);
  31. System.out.println(data2);
  32. }
  33. public List<Employee> filterData(List<Employee> list, Predicate<Employee> filter){
  34. ArrayList<Employee> data = new ArrayList<>();
  35. for (Employee emp : list) {
  36. if(filter.test(emp)){
  37. data.add(emp);
  38. }
  39. }
  40. return data;
  41. }
  42. //4. Predicate<T> : 断定型接口
  43. //boolean test(T t)
  44. @Test
  45. public void test4(){
  46. List<String> list = Arrays.asList("北京","南京","东京","西京","普京","上海","深圳");
  47. List<String> data = getStrings(list, s -> s.contains("京"));
  48. System.out.println(data);
  49. }
  50. public List<String> getStrings(List<String> list, Predicate<String> pre){
  51. List<String> data = new ArrayList<>();
  52. for (String s : list) {
  53. if(pre.test(s)){
  54. data.add(s);
  55. }
  56. }
  57. return data;
  58. }
  59. //3. Function<T,R> : 函数型接口
  60. //R apply(T t)
  61. @Test
  62. public void test3(){
  63. strHandler(" hel lo ",str -> str.trim());
  64. strHandler("世界那么大,我想去看看",str -> str.substring(2,5));
  65. }
  66. public void strHandler(String str, Function<String,String> func){
  67. String s = func.apply(str);
  68. System.out.println(s);
  69. }
  70. // 2.Supplier<T> : 供给型接口
  71. // T get()
  72. @Test
  73. public void test2(){
  74. List<Double> list = getRandomValue(10, () -> Math.random() * 100);
  75. for (Double d : list) {
  76. System.out.println(d);
  77. }
  78. }
  79. public List<Double> getRandomValue(int num, Supplier<Double> sup){
  80. List<Double> list = new ArrayList<>();
  81. for(int i = 0;i < num;i++){
  82. list.add(sup.get());
  83. }
  84. return list;
  85. }
  86. //1.Consumer<T> :消费型接口
  87. //void accept(T t)
  88. @Test
  89. public void test1(){
  90. happyNight(500, s -> {
  91. System.out.println("学习很辛苦,累了的话,可以去正规的足浴店放松一下。花费:" + s);
  92. });
  93. }
  94. public void happyNight(Integer money, Consumer<Integer> con){
  95. con.accept(money);
  96. }
  97. }

其他接口

17-3 方法引用与构造器引用

方法引用(Method References)

  • 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
  • 方法引用就是Lambda表达式,就是函数式接口的一个实例,通过方法的名字来指向一个方法,可以认为是Lambda表达式的一个语法糖。
  • 要求:实现抽象方法的参数列表和返回值类型,必须与方法引用的方法的参数列表和返回值类型保持一致!
  • 方法引用:使用操作符 “::” 将类(或对象) 与 方法名分隔开来。
  • 如下三种主要使用情况:
    • 对象::实例方法名
    • 类::静态方法名
    • 类::实例方法名

方法引用

例如:

等同于:

例如:

等同于:

例如:

等同于:

注意:当函数式接口方法的第一个参数是需要引用方法的调用者,并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName


/**
* 方法引用的使用
* 1.方法引用可以看做是Lambda表达式深层次的表达,或者可以理解为:方法引用就是Lambda表达式。
* 又因为Lambda表达式本身就是函数式接口的实例,进而方法引用也可以看做函数式接口的实例。
*
* 2.使用情境:
* 当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!
*
* 3.要求:函数式接口中抽象方法的形参列表和返回值类型 要与 方法引用对应的方法的形参列表和返回值类型一致!
*
* 4.格式:类(或 对象) :: 方法名
*
* 5.分为如下的三种情况:
* ① 对象 :: 实例方法
* ② 类 :: 静态方法
* ③ 类 :: 实例方法 (有难度)
*
* 6.在满足如上的第2条d的情况下,使用方法引用替换Lambda表达式。如果方法引用不熟悉,可以使用Lambda表达式。
*/
public class MethodRefTest {

  1. //情况三:类 :: 实例方法 (有难度)
  2. //注意:当函数式接口方法的第一个参数是需要引用方法的调用者,
  3. //并且第二个参数是需要引用方法的参数(或无参数)时:ClassName::methodName
  4. @Test
  5. public void test7(){
  6. Employee emp = new Employee(1001, "Jerry", 32, 23430);
  7. Function<Employee,String> func1 = (e) -> e.getName();
  8. String name = func1.apply(emp);
  9. System.out.println(name);
  10. System.out.println("***************");
  11. Function<Employee,String> func2 = Employee::getName;
  12. String name1 = func2.apply(emp);
  13. System.out.println(name1);
  14. }
  15. @Test
  16. public void test6(){
  17. BiPredicate<String,String> bi = (s1,s2) -> s1.equals(s2);
  18. boolean b = bi.test("abc", "abc");
  19. System.out.println(b);
  20. System.out.println("***************");
  21. BiPredicate<String,String> bi1 = String::equals;
  22. boolean b1 = bi1.test("abc", "abc");
  23. System.out.println(b1);
  24. }
  25. @Test
  26. public void test5(){
  27. Comparator<String> com = (s1,s2) -> s1.compareTo(s2);
  28. int value = com.compare("abad", "abdd");
  29. System.out.println(value);
  30. System.out.println("***************");
  31. Comparator<String> com1 = String::compareTo;
  32. int value1 = com1.compare("aaaaaa", "aa");
  33. System.out.println(value1);
  34. }
  35. //情况二:类 :: 静态方法
  36. @Test
  37. public void test4(){
  38. Function<Double,Long> func1 = d -> Math.round(d);
  39. Long value = func1.apply(12.3);
  40. System.out.println(value);
  41. System.out.println("***************");
  42. Function<Double,Long> func2 = Math::round;
  43. Long value1 = func2.apply(12.6);
  44. System.out.println(value1);
  45. Function<Long,Long> func3 = Math::abs;
  46. Long value2 = func3.apply(-1234L);
  47. System.out.println(value2);
  48. }
  49. @Test
  50. public void test3(){
  51. Comparator<Integer> com = (num1,num2) -> Integer.compare(num1,num2);
  52. int value = com.compare(12, 32);
  53. System.out.println(value);
  54. System.out.println("***************");
  55. Comparator<Integer> com1 = Integer::compare;
  56. int value1 = com1.compare(43, 12);
  57. System.out.println(value1);
  58. }
  59. //情况一:对象 :: 实例方法
  60. @Test
  61. public void test2(){
  62. Employee emp = new Employee(1001, "Tom", 23, 4534);
  63. Supplier<String> sup = () -> emp.getName();
  64. String name = sup.get();
  65. System.out.println(name);
  66. System.out.println("***************");
  67. Supplier<String> sup1 = emp::getName;
  68. String name1 = sup1.get();
  69. System.out.println(name1);
  70. }
  71. @Test
  72. public void test1(){
  73. Consumer<String> con1 = str -> System.out.println(str);
  74. con1.accept("beijing");
  75. System.out.println("***************");
  76. PrintStream ps = System.out;
  77. Consumer<String> con2 = ps :: println;
  78. con2.accept("shanghai");
  79. }
  80. }

构造器引用

格式: ClassName::new

与函数式接口相结合,自动与函数式接口中方法兼容。

可以把构造器引用赋值给定义的方法,要求

构造器参数列表要与接口中抽象方法的参数列表一致!且方法的返回值即为构造器对应类的对象。

例如:

等同于:

数组引用

格式: ClassName::new

例如:

等同于:


  1. /**
  2. * 一、构造器引用
  3. * 1.格式: 类名 :: new
  4. * 2.要求:函数式接口中抽象方法的形参列表与构造器形参列表一致(类型相同,个数相同),
  5. * 同时,抽象方法的返回值类型即为构造器所属的类的类型。
  6. *
  7. *
  8. * 二、数组引用
  9. */
  10. public class ConstructorRefTest {
  11. @Test
  12. public void test4(){
  13. Function<Integer,String[]> func1 = (length) -> new String[length];
  14. String[] arr = func1.apply(10);
  15. System.out.println(arr.length);
  16. System.out.println("**********");
  17. Function<Integer,String[]> func2 = String[]::new;
  18. String[] arr1 = func2.apply(20);
  19. System.out.println(arr1.length);
  20. }
  21. @Test
  22. public void test3(){
  23. BiFunction<Integer,String,Employee> func1 = (id,name) -> new Employee(id,name);
  24. Employee emp = func1.apply(10, "Jim");
  25. System.out.println(emp);
  26. System.out.println("**********");
  27. BiFunction<Integer,String,Employee> func2 = Employee::new;
  28. Employee emp1 = func2.apply(20, "Jim");
  29. System.out.println(emp1);
  30. }
  31. @Test
  32. public void test2(){
  33. Function<Integer,Employee> func1 = (id) -> new Employee(id);
  34. Employee emp1 = func1.apply(12);
  35. System.out.println(emp1);
  36. System.out.println("**********");
  37. Function<Integer,Employee> func2 = Employee::new;
  38. Employee emp2 = func2.apply(12);
  39. System.out.println(emp2);
  40. }
  41. @Test
  42. public void test1(){
  43. Supplier<Employee> sup = () -> new Employee();
  44. Employee emp = sup.get();
  45. System.out.println(emp);
  46. System.out.println("**********");
  47. Supplier<Employee> sup1 = Employee::new;
  48. Employee emp1 = sup1.get();
  49. System.out.println(emp1);
  50. }
  51. }

17-4 强大的Stream API

Stream API说明

Java8中有两大最为重要的改变。 第一个是 Lambda 表达式;另外一个则是 Stream API。

Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充,因为Stream API可以极大提供Java程序员的生产力,让程序员写出高效率、干净、简洁的代码。

Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

为什么要使用Stream API

实际开发中,项目中多数数据源都来自于Mysql,Oracle等。但现在数据源可以更多了,有MongDB,Radis等,而这些NoSQL的数据就需要java层面去处理。

什么是 Stream

Stream到底是什么呢?

是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。

“集合讲的是数据,Stream流讲的是计算!”

注意:

①Stream 自己不会存储元素。

②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

Stream 的操作三个步骤

创建 Stream方式一:通过集合

Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:

  • default Stream stream() : 返回一个顺序流
  • default Stream parallelStream() : 返回一个并行流

####创建 Stream方式二:通过数组
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:

  • static Stream stream(T[] array): 返回一个流

重载形式,能够处理对应基本类型的数组:

  • public static IntStream stream(int[] array)
  • public static LongStream stream(long[] array)
  • public static DoubleStream stream(double[] array)

创建 Stream方式三:通过Stream的of()

可以调用Stream类静态方法 of(), 通过显示值创建一个流。它可以接收任意数量的参数。

  • public static Stream of(T... values) : 返回一个流

####创建 Stream方式四:创建无限流
可以使用静态方法 Stream.iterate() 和 Stream.generate(), 创建无限流。

  • 迭代
    public static Stream iterate(final T seed, final UnaryOperator f)
  • 生成

    public static Stream generate(Supplier s)

####Stream 的中间操作
![](https://i.imgur.com/cDcoGyW.png)

Stream 的终止操作

  • 终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是 void 。
  • 流进行了终止操作后,不能再次使用。





并行流与串行流

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

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

java中Stream API使用

  1. /**
  2. * 1.Stream API:
  3. * 可以理解为java提供的一套api,使用这套api可以实现对集合、数组中的数据进行过滤、映射、归约、查找等操作
  4. *
  5. * 2.注意点:
  6. * ①Stream 自己不会存储元素。
  7. * ②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  8. * ③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
  9. *
  10. * 3.Stream的使用流程:
  11. * 步骤一:Stream的实例化
  12. * 步骤二:一系列的中间操作
  13. * 步骤三:终止操作
  14. *
  15. * 注意:①步骤二中的中间操作可以有多个
  16. * ②如果没有终止操作,那么一系列的中间操作是不会执行的。只有执行了步骤三的终止操作,步骤二才会执行:惰性求值
  17. * ③终止操作一旦执行,就不可以再执行中间操作或其他的终止操作。
  18. *
  19. *
  20. * 测试Stream的实例化
  21. *
  22. */
  23. public class StreamAPITest {
  24. //方式四:创建无限流
  25. @Test
  26. public void test4(){
  27. // 迭代
  28. // public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
  29. Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
  30. stream.limit(10).forEach(System.out::println);
  31. // 生成
  32. // public static<T> Stream<T> generate(Supplier<T> s)
  33. Stream<Double> stream1 = Stream.generate(Math::random);
  34. stream1.limit(10).forEach(System.out::println);
  35. }
  36. //方式三:Stream的静态方法of()
  37. @Test
  38. public void test3(){
  39. //public static<T> Stream<T> of(T... values) : 返回一个流
  40. Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
  41. }
  42. //方式二:通过数组
  43. @Test
  44. public void test2(){
  45. //调用Arrays的static <T> Stream<T> stream(T[] array): 返回一个流
  46. String[] arr = new String[]{"MM","GG","JJ","DD"};
  47. Stream<String> stream = Arrays.stream(arr);
  48. }
  49. //方式一:通过集合
  50. @Test
  51. public void test1(){
  52. // default Stream<E> stream() : 返回一个顺序流
  53. List<Employee> list = EmployeeData.getEmployees();
  54. Stream<Employee> stream = list.stream();
  55. // default Stream<E> parallelStream() : 返回一个并行流
  56. Stream<Employee> stream1 = list.parallelStream();
  57. }
  58. }

/**
* 测试中间操作
* 1.可以通过Stream的实例,执行多次中间操作
* 2.中间操作,只有在执行了终止操作以后才会执行。
*/
public class StreamAPITest1 {

  1. //3-排序
  2. @Test
  3. public void test4(){
  4. // sorted()——自然排序
  5. List<Integer> list = Arrays.asList(23,43,454,32,1,2,5,5,-8);
  6. list.stream().sorted().forEach(System.out::println);
  7. //此时针对Employees进行排序:失败。原因:Employee类没有实现Comparable接口
  8. // List<Employee> list1 = EmployeeData.getEmployees();
  9. // list1.stream().sorted().forEach(System.out::println);
  10. // sorted(Comparator com)——定制排序
  11. List<Employee> list1 = EmployeeData.getEmployees();
  12. list1.stream().sorted((e1,e2) -> {
  13. if(e1.getAge() != e2.getAge()){
  14. return e1.getAge() - e2.getAge();
  15. }else{
  16. return -Double.compare(e1.getSalary(),e2.getSalary());
  17. }
  18. }).forEach(System.out::println);
  19. }
  20. @Test
  21. public void test3(){
  22. ArrayList list1 = new ArrayList();
  23. list1.add(1);
  24. list1.add(2);
  25. list1.add(3);
  26. ArrayList list2 = new ArrayList();
  27. list2.add(4);
  28. list2.add(5);
  29. list2.add(6);
  30. // list1.add(list2); ---map()
  31. // System.out.println(list1);//[1, 2, 3, [4, 5, 6]]
  32. // list1.addAll(list2); -- flatMap()
  33. // System.out.println(list1);//[1, 2, 3, 4, 5, 6]
  34. }
  35. //2-映射
  36. @Test
  37. public void test2(){
  38. // map(Function f)——接收一个函数作为参数,将元素转换成其他形式或提取信息,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  39. List<String> list = Arrays.asList("aa","bb","cc","dd");
  40. list.stream().map(String::toUpperCase).forEach(System.out::println);
  41. // 练习:获取员工姓名长度大于3的员工的姓名。
  42. Stream<Employee> stream = EmployeeData.getEmployees().stream();
  43. Stream<String> stream1 = stream.map(Employee::getName);
  44. stream1.filter(name -> name.length() > 3).forEach(System.out::println);
  45. Stream<Stream<Character>> stream2 = list.stream().map(StreamAPITest1::fromStringToChar);
  46. stream2.forEach(
  47. x ->{
  48. x.forEach(System.out::println);
  49. }
  50. );
  51. System.out.println();
  52. // flatMap(Function f)——接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。
  53. Stream<Character> stream3 = list.stream().flatMap(StreamAPITest1::fromStringToChar);
  54. stream3.forEach(System.out::println);
  55. }
  56. //将str中的字符存在集合中,返回集合的Stream
  57. public static Stream<Character> fromStringToChar(String str){
  58. ArrayList<Character> list = new ArrayList<>();
  59. // for(Character c : str.toCharArray()){
  60. // list.add(c);
  61. // }
  62. for(int i = 0;i < str.length();i++){
  63. list.add(str.charAt(i));
  64. }
  65. return list.stream();
  66. }
  67. //1-筛选与切片
  68. @Test
  69. public void test1(){
  70. List<Employee> list = EmployeeData.getEmployees();
  71. //体会方法链的调用方式。比如:StringBuffer s = new StringBuffer(); s.append("A").append("B");
  72. // filter(Predicate p)——接收 Lambda , 从流中排除某些元素。
  73. Stream<Employee> stream = list.stream().filter(e -> e.getAge() > 30);
  74. stream.forEach(System.out::println);
  75. System.out.println();
  76. // limit(n)——截断流,使其元素不超过给定数量。
  77. list.stream().filter(e -> e.getAge() > 30).limit(3).forEach(System.out::println);
  78. System.out.println();
  79. // skip(n) —— 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
  80. list.stream().filter(e -> e.getAge() > 30).skip(3).forEach(System.out::println);
  81. System.out.println();
  82. // distinct()——筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素
  83. list.add(new Employee(1009,"刘强东",30,6000));
  84. list.add(new Employee(1009,"刘强东",30,6000));
  85. list.add(new Employee(1009,"刘强东",30,6000));
  86. list.add(new Employee(1009,"刘强东",30,6000));
  87. list.add(new Employee(1009,"刘强东",30,6000));
  88. list.stream().distinct().forEach(System.out::println);
  89. }
  90. }

/**
* 步骤三:终止操作
*/
public class StreamAPITest2 {

  1. //3-收集:将集合--->Stream --->集合
  2. @Test
  3. public void test5(){
  4. // collect(Collector c)——将流转换为其他形式。接收一个 Collector接口的实现,
  5. // 用于给Stream中元素做汇总的方法
  6. List<Employee> list = EmployeeData.getEmployees();
  7. List<Employee> list1 = list.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toList());
  8. //遍历
  9. list1.forEach(System.out::println);
  10. System.out.println();
  11. Set<Employee> set = list.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toSet());
  12. set.forEach(System.out::println);
  13. System.out.println();
  14. ArrayList<Employee> list2 = list.stream().filter(e -> e.getSalary() > 5000).collect(Collectors.toCollection(ArrayList::new));
  15. for (Employee employee : list2) {
  16. System.out.println(employee);
  17. }
  18. }
  19. //2-归约
  20. @Test
  21. public void test4(){
  22. // reduce(T identity, BinaryOperator)——可以将流中元素反复结合起来,得到一个值。返回 T
  23. List<Integer> list = Arrays.asList(1,2,3,4,5,6);
  24. // Integer sum = list.stream().reduce(0, (x1, x2) -> x1 + x2);
  25. Integer sum = list.stream().reduce(10, Integer::sum);
  26. System.out.println(sum);
  27. // reduce(BinaryOperator) ——可以将流中元素反复结合起来,得到一个值。返回 Optional<T>
  28. // 练习1:计算公司所有员工工资的总和
  29. List<Employee> emps = EmployeeData.getEmployees();
  30. Stream<Double> moneyStream = emps.stream().map(Employee::getSalary);
  31. Optional<Double> moneyOptional = moneyStream.reduce(Double::sum);
  32. System.out.println(moneyOptional.get());
  33. // 练习2:员工姓名中包含“马”字的个数
  34. Stream<String> nameStream = emps.stream().map(Employee::getName);
  35. Stream<Character> charStream = nameStream.flatMap(StreamAPITest1::fromStringToChar);
  36. //方式一:
  37. // long count = charStream.filter(c -> c.equals('马')).count();
  38. // System.out.println(count);
  39. //方式二:
  40. Optional<Integer> op = charStream.map(c -> {
  41. if (c.equals('马')) {
  42. return 1;
  43. } else {
  44. return 0;
  45. }
  46. }).reduce(Integer::sum);
  47. System.out.println(op.get());
  48. //练习3:员工姓名中包含“马”的员工个数
  49. long count = emps.stream().map(Employee::getName).filter(name -> name.contains("马")).count();
  50. System.out.println(count);
  51. // 练习4:员工姓名中包含“马”的员工的姓名
  52. emps.stream().map(Employee::getName).filter(name -> name.contains("马")).forEach(System.out::println);
  53. }
  54. //1-匹配与查找
  55. @Test
  56. public void test2(){
  57. List<Employee> list = EmployeeData.getEmployees();
  58. // max(Comparator c)——返回流中最大值
  59. // 练习:返回最高的工资:
  60. Stream<Employee> stream = list.stream();
  61. Stream<Double> stream1 = stream.map(Employee::getSalary);
  62. Optional<Double> max = stream1.max(Double::compare);
  63. System.out.println(max.get());
  64. // min(Comparator c)——返回流中最小值
  65. // 练习:返回最低工资的员工
  66. Stream<Employee> stream2 = list.stream();
  67. Optional<Employee> min = stream2.min((e1, e2) -> Double.compare(e1.getSalary(), e2.getSalary()));
  68. System.out.println(min.get());
  69. // forEach(Consumer c)——内部迭代
  70. list.stream().forEach(System.out::println);
  71. }
  72. //外部迭代
  73. @Test
  74. public void test3(){
  75. List<Employee> list = EmployeeData.getEmployees();
  76. Iterator<Employee> iterator = list.iterator();
  77. while (iterator.hasNext()){
  78. System.out.println(iterator.next());
  79. }
  80. }
  81. @Test
  82. public void test1(){
  83. List<Employee> list = EmployeeData.getEmployees();
  84. // allMatch(Predicate p)——检查是否匹配所有元素
  85. //是否所有的员工的年龄都大于18
  86. boolean b = list.stream().allMatch(e -> e.getAge() > 18);
  87. System.out.println(b);
  88. // anyMatch(Predicate p)——检查是否至少匹配一个元素
  89. //是否存在员工的工资大于 10000
  90. boolean b1 = list.stream().anyMatch(e -> e.getSalary() > 9900);
  91. System.out.println(b1);
  92. // noneMatch(Predicate p)——检查是否没有匹配的元素
  93. //是否存在员工姓“雷”
  94. boolean b2 = list.stream().noneMatch(e -> e.getName().contains("雷"));
  95. System.out.println(b2);
  96. // findFirst——返回第一个元素
  97. Optional<Employee> emp = list.stream().sorted((e1,e2) -> {
  98. if(e1.getAge() != e2.getAge()){
  99. return e1.getAge() - e2.getAge();
  100. }else{
  101. return -Double.compare(e1.getSalary(),e2.getSalary());
  102. }
  103. }).findFirst();
  104. System.out.println(emp.get());
  105. // findAny——返回当前流中的任意元素
  106. Optional<Employee> emp1 = list.parallelStream().findAny();
  107. System.out.println(emp1.get());
  108. // count——返回流中元素的总个数
  109. long count = list.stream().filter(e -> e.getSalary() > 5000).count();
  110. System.out.println(count);
  111. }
  112. }

第十七章 java8特性的更多相关文章

  1. 20190903 On Java8 第十七章 文件

    第十七章 文件 在Java7中对 文件的操作 引入了巨大的改进.这些新元素被放在 java.nio.file 包下面,过去人们通常把nio中的n理解为new即新的io,现在更应该当成是non-bloc ...

  2. Pro ASP.NET MVC –第四章 语言特性精华

    C#语言有很多特性,并不是所有的程序员都了解本书我们将会使用的C#语言特性.因此,在本章,我们将了解一下作为一个好的MVC程序员需要了解C#语言的特性. 每个特性我们都只是简要介绍.如果你想深入了解L ...

  3. Linux内核设计第十七章笔记

    第十七章 设备与模块 关于设备驱动和设备管理,四种内核成分 设备类型:在所有unix系统中为了统一普通设备的操作所采用的分类 模块:Linux内核中用于按需加载和卸载目标代码的机制 内核对象:内核数据 ...

  4. 进击的Python【第十七章】:jQuery的基本应用

    进击的Python[第十七章]:jQuery的基本应用

  5. <构建之法>第十三章到十七章有感以及这个项目读后感

    <构建之法>第十三章到十七章有感 第13章:软件测试方法有哪些? 主要讲了软件测试方法:要说有什么问题就是哪种效率最高? 第14章:质量保障 软件的质量指标是什么?怎么样能够提升软件的质量 ...

  6. 程序员编程艺术第三十六~三十七章、搜索智能提示suggestion,附近点搜索

    第三十六~三十七章.搜索智能提示suggestion,附近地点搜索 作者:July.致谢:caopengcs.胡果果.时间:二零一三年九月七日. 题记 写博的近三年,整理了太多太多的笔试面试题,如微软 ...

  7. 《Linux命令行与shell脚本编程大全》 第二十七章 学习笔记

    第二十七章:shell脚本编程进阶 监测系统统计数据 系统快照报告 1.运行时间 uptime命令会提供以下基本信息: 当前时间 系统运行的天数,小时数,分钟数 当前登录到系统的用户数 1分钟,5分钟 ...

  8. [汇编学习笔记][第十七章使用BIOS进行键盘输入和磁盘读写

    第十七章 使用BIOS进行键盘输入和磁盘读写 17.1 int 9 中断例程对键盘输入的处理 17.2 int 16 读取键盘缓存区 mov ah,0 int 16h 结果:(ah)=扫描码,(al) ...

  9. 第十七章——配置SQLServer(3)——配置“对即时负载的优化”

    原文:第十七章--配置SQLServer(3)--配置"对即时负载的优化" 前言: 在第一次执行查询或者存储过程时,会创建执行计划并存储在SQLServer的过程缓存内存中.在很多 ...

随机推荐

  1. _quick_response

    在线答题,抢答 `question` 题库 `correctAnswer` 正确答案(A,B,C,D) `answerA` 选项显示 `answerB`选项显示 `answerC` 选项显示 `ans ...

  2. C#三层架构

    C#三层架构 三层架构分为:表现层(UI(User Interface)).业务逻辑层(BLL(Business Logic Layer)).数据访问层(DAL(Data Access Layer)) ...

  3. scala基础学习(一)

    scala学习 scala与java的不同之处: 1.scala中任何一个操作符都是一个方法. s = 1+2;    可以写作:s=(1).+(2) 2.异常捕获采用模式匹配的方式. try { v ...

  4. 查看CPU温度

    因为不喜欢鲁大师,所以检索看有没有别的软件. 1)先是找到了aida64, 结果好像是要付费的,就没装. 2)因为自己也就想看cpu温度,于是寻思自己编程解决. 找到了下面的文章. 不但介绍了怎么编程 ...

  5. kafka.common.FailedToSendMessageException: Failed to send messages after 3 tries. 最无语的配置

    注意: 本文不谈废话,低级问题请自行检查. 我使用Java版本的Kafka Producer生产数据,但是抛出了这个异常.百思不得其解,明明防火墙配置,ZooKeeper,Kafka配置都是没问题的啊 ...

  6. 『Python CoolBook』C扩展库_其三_简单数组操作

    点击进入项目 这里的数组要点在于: 数组结构,array.array或者numpy.array 本篇的数组仅限一维,不过基础的C数组也是一维 一.分块讲解 源函数 /* Average values ...

  7. vue轮播,vue-awesome-swiper动态数据渲染,loop无效,轮循无效

    解决办法:在渲染数组数据前.判断是否为空 v-if="slideList.length>1" <template> <div class="ban ...

  8. SAP 多语言文本翻译

    SAP自己的东西都是有语言包的,针对很多语言有是有对应文本的翻译,巴特,比较不是专业的翻译,多以很多时候还是有这样那样的文本描述需要调整. 语言包怎么打就不说了,也不知道,知道也没打过... 标准界面 ...

  9. css3实现自适应的3行,左右行固定宽度,中间自适应,要求先渲染中间部分

    https://blog.csdn.net/thqy39/article/details/73512478 https://www.cnblogs.com/ranzige/p/4097453.html ...

  10. linux 安装配置zookeeper

    1.什么是zookeeper ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用 ...