Java8 新特性

本来打算通过看书《Java 8 函数式编程》,然后加深对Java 8特性的学习;但读书还是慢的,可能也有些浮躁,看不下去书。
先速成一波,对一些我不知道的功能先进行一个了解,然后再通过看书进行补充。

概要:

  • Lambda 表达式 − Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。
  • Stream API −新添加的Stream API(java.util.stream) 把真正的函数式编程风格引入到Java中。
  • 方法引用 −方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
  • 函数式接口 - 是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
  • 默认方法 − 默认方法就是一个在接口里面有了一个实现的方法。
  • 新工具 − 新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps。
  • Date Time API − 加强对日期与时间的处理。
  • Optional 类 − Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常。
  • Nashorn, JavaScript 引擎 − Java 8提供了一个新的Nashorn
  • javascript引擎,它允许我们在JVM上运行特定的javascript应用。

1、Lambda 表达式:

排序例子:—Java 7重写比较器、Java 8 Lambda表达式

 // 使用 java 7 排序
   private void sortUsingJava7(List<String> names){   
      Collections.sort(names, new Comparator<String>() {
         @Override
         public int compare(String s1, String s2) {
            return s1.compareTo(s2);
         }
      });
   }
   
   // 使用 java 8 排序
   private void sortUsingJava8(List<String> names){
      Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
   }

Lambda表达式,又称为闭包。
允许把函数作为一个方法的参数传进方法中。
将匿名内部类中的最核心的内容(方法参数、方法体、返回值)简化出来–函数式编程思想。

基本语法:

简单的Lambda表达式可以用 逗号分隔的参数列表、-> 符号及语句块组成。

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

注意:—Collections.sort(names, (s1, s2) -> s1.compareTo(s2));

  1. 不需要声明参数类型,编译器可以统一识别参数值(小括号中的参数类型可以省略) – String name
  2. 一个参数不需要定义圆括号(),但是多个参数需要定义圆括号(如果小括号中只有一个参数,那么可以省略小括号) – ( name )
    如果大括号中只有一条语句,那么可以省略大括号,return,分号
  3. 如果主体只包含了一个语句,就不需要使用大括号 – {(s1, s2) -> s1.compareTo(s2)}
  4. 如果主体只有一个表达式返回值则编译器会自动返回值,使用大括号时(即主体有多个语句时)需要指明表达式返回一个数值
简单例子:
// 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)

实例–解释注意:

public class Java8Tester {
   public static void main(String args[]){
      Java8Tester tester = new Java8Tester();
        
      // 类型声明
      MathOperation addition = (int a, int b) -> a + b;
        
      // 不用类型声明
      MathOperation subtraction = (a, b) -> a - b;
        
      // 大括号中的返回语句
      MathOperation multiplication = (int a, int b) -> { return a * b; };
        
      // 没有大括号及返回语句
      MathOperation division = (int a, int b) -> a / b;
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
      System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
      System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
      System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        
      // 不用圆括号
      GreetingService greetService1 = message -> System.out.println("Hello " + message);
        
      // 用圆括号
      GreetingService greetService2 = (message) -> System.out.println("Hello " + message);
        
      greetService1.sayMessage("Runoob");
      greetService2.sayMessage("Google");
   }
   // 定义一个MathOperation接口,包含一个operation方法
   interface MathOperation {
      int operation(int a, int b);
   }
    
   interface GreetingService {
      void sayMessage(String message);
   }
    
   private int operate(int a, int b, MathOperation mathOperation){
      return mathOperation.operation(a, b);
   }
}
变量的作用域
  • lambda 表达式只能引用标记了 final 的外层局部变量,这就是说不能在 lambda内部修改定义在域外的局部变量,否则会编译错误。
  • 也可以直接在 lambda 表达式中访问外层的局部变量:
  • lambda 表达式的局部变量可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
  • 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

2、Java 8 流–stream

Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。


+--------------------+       +------+   +------+   +---+   +-------+
| stream of elements +-----> |filter+-> |sorted+-> |map+-> |collect|
+--------------------+       +------+   +------+   +---+   +-------+

// 以上的流程转换为 Java 代码为:

List<Integer> transactionsIds = 
widgets.stream()
             .filter(b -> b.getColor() == RED)
             .sorted((x,y) -> x.getWeight() - y.getWeight())
             .mapToInt(Widget::getWeight)
             .sum();
什么是Stream?

Stream是一个来自数据源并支持聚合操作的元素队列。

  • 元素:元素是特定类型的对象,形成一个队列。Stream并不会存储元素,只是进行按需计算。
  • 数据源:是流的来源,可以是集合、数组、IO、生成器gennerator等。
  • 聚合操作:类似SQL语句,filter、map、reduce、find、match、sorted等。

不同于Collection操作,Stream操作还有:

  • Pipelining:中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,可以对操作进行优化–延迟执行(laziness)和短路(short-circuiting)。
  • 内部迭代:之前对集合遍历是通过Iterator迭代器或者for-each方式,显式地在集合外部进行迭代–外部迭代。Stream可以通过访问者模式(Visitor)实现内部迭代。
基本用法:
  • 1、生成流

stream() - 为集合创建串行流
parallelStream() - 为集合创建并行流

// 将字符串数组去掉空的字符串
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());

// 从现有数组中获取流
private static Employee[] arrayOfEmps = {
    new Employee(1, "Jeff Bezos", 100000.0), 
    new Employee(2, "Bill Gates", 200000.0), 
    new Employee(3, "Mark Zuckerberg", 300000.0)
};

Stream.of(arrayOfEmps);

// 从现有列表中获取流
private static List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream();



// Java 8向Collection接口添加了新的stream()方法。

// 可以使用Stream.of()从单个对象创建一个流:
Stream.of(arrayOfEmps[0], arrayOfEmps[1], arrayOfEmps[2]);

// 简单地使用Stream.builder()
Stream.Builder<Employee> empStreamBuilder = Stream.builder();

empStreamBuilder.accept(arrayOfEmps[0]);
empStreamBuilder.accept(arrayOfEmps[1]);
empStreamBuilder.accept(arrayOfEmps[2]);

Stream<Employee> empStream = empStreamBuilder.build();

  • 2、forEach

新的方法forEach,用于迭代流中的每个数据

// 使用forEach输出10个随机数
Random random = new Random();
random.ints().limit(10).forEach(System.out::println);

这里System.out::println是一个方法引用表达式,是对实例方法的引用,是e -> System.out.println(e)的进一步简写

// 将Integer的流转换为Employee的流:
@Test
public void whenMapIdToEmployees_thenGetEmployeeStream() {
    Integer[] empIds = { 1, 2, 3 };

    List<Employee> employees = Stream.of(empIds)
      .map(employeeRepository::findById)
      .collect(Collectors.toList());

    assertEquals(employees.size(), empIds.length);
}
// 得到一个整数雇员ID的从数组流。每个Integer都传递给函数employeeRepository :: findById(),该函数 返回相应的Employee对象。这有效地形成了员工流。
  • toArray():-从流中获取数组
@Test
public void whenStreamToArray_thenGetArray() {
	// Employee [] :: new创建一个空的Employee数组 -然后用流中的元素填充它。
    Employee[] employees = empList.stream().toArray(Employee[]::new);

    assertThat(empList.toArray(), equalTo(employees));
}

  • collect(): 完成所有处理,从流中获取内容

使用toList收集器将所有Stream元素收集到一个List实例中。

@Test
public void whenCollectStreamToList_thenGetList() {
    List<Employee> employees = empList.stream().collect(Collectors.toList());

    assertEquals(empList, employees);
}
  • 3、map
    在这里插入图片描述

map方法用于映射每个元素到对应的结果。

// 获取对应的平方数
List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
List<Integer> squaresList = numbers.stream().map( i -> i*i).distinct().collect(Collectors.toList());
  • flatMap()-帮助展平数据结构简化进一步的操作
    在这里插入图片描述
    flatMap的可以处理更深层次的数据,入参为多个list,结果可以返回为一个list,而map是一对一的,入参是多个list,结果返回必须是多个list。通俗的说,如果入参都是对象,那么flatMap可以操作对象里面的对象,而map只能操作第一层。
		// Stream <List <String >>
        List<List<String>> namesNested = Arrays.asList(
                Arrays.asList("Jeff", "Bezos"),
                Arrays.asList("Bill", "Gates"),
                Arrays.asList("Mark", "Zuckerberg"));
        System.out.println(namesNested);
		结果:[[Jeff, Bezos], [Bill, Gates], [Mark, Zuckerberg]]

        List<String> namesFlatStream = namesNested.stream()
                .flatMap(Collection::stream)
                .collect(Collectors.toList());

        System.out.println(namesFlatStream);
        
		结果:[Jeff, Bezos, Bill, Gates, Mark, Zuckerberg]

  • peek()
    forEach()是一个终端操作。是指返回最终的结果。
    而peek是个中间操作。
    通过peek可以查看每个值,同时能继续操作流。

  • 4、filter
    在这里插入图片描述

filter方法用于通过设置的条件过滤出元素,会产生一个新流。

// 获取空字符串的数量
List<String>strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
long count = strings.stream().filter(string -> string.isEmpty()).count();

首先过滤掉空的ID,过滤器仅保留薪水超过阈值 的员工。

@Test
public void whenFilterEmployees_thenGetFilteredStream() {
    Integer[] empIds = { 1, 2, 3, 4 };

    List<Employee> employees = Stream.of(empIds)
      .map(employeeRepository::findById)
      .filter(e -> e != null)
      .filter(e -> e.getSalary() > 200000)
      .collect(Collectors.toList());

    assertEquals(Arrays.asList(arrayOfEmps[2]), employees);
}

// 将返回薪水大于100000的第一位员工。如果不存在这样的雇员,则返回null。
@Test
public void whenFindFirst_thenGetFirstEmployeeInStream() {
    Integer[] empIds = { 1, 2, 3, 4 };

    Employee employee = Stream.of(empIds)
      .map(employeeRepository::findById)
      .filter(e -> e != null)
      .filter(e -> e.getSalary() > 100000)
      .findFirst()
      .orElse(null);

    assertEquals(employee.getSalary(), new Double(200000));
}
  • 5、limit:limit方法用于获取指定数量的流。
  • 6、sorted:sorted方法用于对流进行排序。
  • 7、并行(parallel)程序

parallelStream是流并行处理程序的代替方法。

// 获取空字符串的数量
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd","", "jkl");
long count = strings.parallelStream().filter(string -> string.isEmpty()).count();
  • 8、Collectors

Collectors类实现了像将流转换成集合、聚合元素等操作。Collectors可用于返回列表或字符串。
Collectors.toList()、Collectors.joining()

  • 9、统计

产生统计结果的收集器,主要用于int、double、long等基本类型上。

List<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
 
IntSummaryStatistics stats = numbers.stream().mapToInt((x) -> x).summaryStatistics();
 
System.out.println("列表中最大的数 : " + stats.getMax());
System.out.println("列表中最小的数 : " + stats.getMin());
System.out.println("所有数之和 : " + stats.getSum());
System.out.println("平均数 : " + stats.getAverage());

3、方法引用

方法引用通过方法的名字来指向一个方法。
使用一对冒号:: 实现方法引用。

0、在Car类中预定义4个方法:
@FunctionalInterface
public interface Supplier<T> {
    T get();
}
 
class Car {
    //Supplier是jdk1.8的接口,这里和lamda一起使用了
    public static Car create(final Supplier<Car> supplier) {
        return supplier.get();
    }
 
    public static void collide(final Car car) {
        System.out.println("Collided " + car.toString());
    }
 
    public void follow(final Car another) {
        System.out.println("Following the " + another.toString());
    }
 
    public void repair() {
        System.out.println("Repaired " + this.toString());
    }
}
1、构造器引用:

语法是 Class::new, 或者: Class::new

final Car car = Car.create(Car::new );
final List< Car > cars = Arrays.asList(car);
2、静态方法引用

语法是 Class::static_method

cars.forEach( Car::collide );
3、特定类的任意对象的方法引用

语法是 Class::method

cars.forEach(Car::repair)
4、特定对象的方法引用

语法是 instance::method

final Car police = Car.create( Car::new );
cars.forEach( police::follow );
names.forEach(System.out::println);
  • System.out是一个PrintStream实例的引用;System.out::println 是对一个实例方法的引用
    该引用同时指定了对实例(System.out)的引用以及对方法(PrintStream::println)的引用
  • System.out::println 不是 System.out.println的等价物;前者是一个方法引用表达式,而后者不能单独作为一个表达式,而必须在后面跟上由圆括号包围的参数列表来构成方法调用表达式。
  • System.out::println 可以看作 lambda表达式 e -> System.out.println(e) 的缩写形式。

4、函数式接口

函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
函数式接口可以被隐式转换为 lambda 表达式。

// 定义了一个函数式接口
@FunctionalInterface
interface GreetingService {
    void sayMessage(String message);
}

// 使用Lambda表达式来表示接口的一个实现
GreetingService greetService1 = message -> System.out.println("Hello " + message);

JDK 1.8 之前已有的函数式接口:
java.lang.Runnable java.util.concurrent.Callable java.security.PrivilegedAction java.util.Comparator java.io.FileFilter java.nio.file.PathMatcher java.lang.reflect.InvocationHandler java.beans.PropertyChangeListener java.awt.event.ActionListener javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
java.util.function
function类包含的函数式接口

5、默认方法

Java 8之前接口中的方法都是抽象方法,没有实现–面向抽象编程;但是需要修改接口时,必须修改全部实现该接口的类。
Java 8中引入了接口的默认方法,接口可以有default实现的默认方法,可以不需要实现类去实现其方法。

接口默认方法存在“类优先”原则

若一个接口中定义了一个默认方法,而另外一个父类或接口中又定义了一个同名的方法时:

  • 选择父类中的方法。如果一个父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
  • 接口冲突。如果一个父接口提供一个默认方法,而另一个接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突。

多个默认方法:

// 一个接口有默认方法,考虑这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,

public interface vehicle {
   default void print(){
      System.out.println("我是一辆车!");
   }
}

public interface fourWheeler {
   default void print(){
      System.out.println("我是一辆四轮车!");
   }
}

// 第一个解决方案是创建自己的默认方法,来覆盖重写接口的默认方法:
public class car implements vehicle, fourWheeler {
   public void print(){
      System.out.println("我是一辆四轮汽车!");
   }
}

// 第二种解决方案可以使用 super 来调用指定接口的默认方法:
public class car implements vehicle, fourWheeler {
   public void print(){
      vehicle.super.print();
   }
}

静态方法:

public interface vehicle {

    default void print() {
        System.out.println("我是一辆车!");
    }

    // 静态方法
    static void blowHorn() {
        System.out.println("按喇叭!!!");
    }
}

其余的新特性用到再查看

菜鸟-Java8新特性

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值