Java8介绍

Dec 19, 2016


新特性

Lambda

Lambda表达式的语法是(argument) -> (body)

Runnable r1 = () -> System.out.println("My Runnable");

如何来理解上面的语句呢:

  • Runnable是一个函数式接口,注解为@FunctionalInterface

      @FunctionalInterface
      public interface Runnable {
        public abstract void run();
      }
    

    Java8中增加了@FunctionalInterface注解,可以将一个接口注解为函数接口。@FunctionalInterface注解的接口只能有一个抽象方法,如果多余一个抽象方法编译时会报错。

  • Runnable接口中的run方法没有参数,所以lambda表达式中也没有参数

  • 在body中只有一个语句,所以可以省略掉{}

Lambda表达式意味着可以创建匿名类,但是其并没有带来任何运行时的好处,那我们为什么要用它呢?

  • 比起匿名类来说,其减少代码行数

  • 融入Stream API的序列化和支持并发操作

    举个例子来说明序列化的操作,如判断某个数是否为素数,Java8之前的传统写法可能是:

      private static boolean isPrime(int number) {
          if(number < 2) return false;
          for(int i=2; i<number; i++){
              if(number % i == 0) return false;
          }
          return true;
      }
    

    使用Lambda表达式和Stream后

      private static boolean isPrime(int number) {		
          return number > 1
                  && IntStream.range(2, number).noneMatch(
                          index -> number % index == 0);
      }
    

    noneMatch中的lambda表达式可以提取出来变成一个接口局部变量

      private static boolean isPrime(int number) {
          IntPredicate isDivisible = index -> number % index == 0;
    
          return number > 1
                  && IntStream.range(2, number).noneMatch(isDivisible);
      }
    

    Java8的很多集合API被重写,引入了Stream API,其使用了很多函数式接口。Java8已经在java.util.function中定义了很多函数式接口,如Consumer, Supplier, Function和Predicate。

  • 函数可以当做参数来传递

  • 延迟计算

      public static int findSquareOfMaxOdd(List<Integer> numbers) {
          return numbers.stream()
                  .filter(NumberTest::isOdd) 		//Predicate is functional interface and
                  .filter(NumberTest::isGreaterThan3)	// we are using lambdas to initialize it
                  .filter(NumberTest::isLessThan11)	// rather than anonymous inner classes
                  .max(Comparator.naturalOrder())
                  .map(i -> i * i)
                  .get();
      }
    
      public static boolean isOdd(int i) {
          return i % 2 != 0;
      }
    
      public static boolean isGreaterThan3(int i){
          return i > 3;
      }
    
      public static boolean isLessThan11(int i){
          return i < 11;
      }
    

    :: 是Java8引入的语法糖,NumberTest::isGreaterThan3i -> NumberTest.isGreaterThan3(i)的简写。

Stream API

Java8中新增了java.util.stream,并在Collection接口中增加了对Stream的支持:

default Stream<E> stream() {
  return StreamSupport.stream(spliterator(), false);
}

default Stream<E> parallelStream() {
  return StreamSupport.stream(spliterator(), true);
}

为什么要引入Stream呢,我们来看一个例子,计算大于10的所有整数的和:

private static int sumIterator(List<Integer> list) {
	Iterator<Integer> it = list.iterator();
	int sum = 0;
	while (it.hasNext()) {
		int num = it.next();
		if (num > 10) {
			sum += num;
		}
	}
	return sum;
}

上面代码存在的问题是:

  • 我们仅仅想知道整数的和,但是我们必须知道list是怎么迭代的

  • 程序是连续的,我们很难再程序中做到并发执行

  • 简单的任务也需要些很多代码

Stream的引入就是为了解决上面代码存在的问题。

private static int sumStream(List<Integer> list) {
	return list.stream().filter(i -> i > 10).mapToInt(i -> i).sum();
}

Java Stream不存储数据,其在collection和array等数据结构上操作并提供管道数据,以便我们在数据上进行一些特定的操作,如filter, map等。为了提供集合对int, long的支持且避免自动拆装箱,一些特定的Steam被设计出来, IntStream, LongStream, DoubleStream。

如何创建Stream呢

  • Stream<Integer> stream = Stream.of(1,2,3,4);

  • Stream<Integer> stream = Stream.of(new Integer[]{1,2,3,4}); , 但是注意不能是new int[],因为其不支持自动装箱

  • myList.stream()myList.parallelStream()

  • Stream.generate(() -> {return "abc";});Stream.iterate("abc", (i) -> i);

  • LongStream is = Arrays.stream(new long[]{1,2,3,4});IntStream is2 = "abc".chars();

如何从Stream中得到Collection或Array呢

  • List<Integer> intList = intStream.collect(Collectors.toList());

  • Integer[] intArray = intStream.toArray(Integer[]::new);

Java Stream API的一些限制:

  • 需要使用没有状态的lambda表达式,有状态的lambda表达式会导致结果不可预测

  • 一旦创建的stream被消费,后续就不能再被用

  • Stream API中的大量方法需要学习

Optional

Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。

Optional<String> name = Optional.of("Sanaulla");
System.out.println(name.get());
name.ifPresent((value) -> {
  System.out.println("The length of the value is: " + value.length());
});

forEach

Java8在Iterable接口中引入forEach方法,myList.forEach(t -> System.out.println(t));

forEach方法接受java.util.function.Consumer作为其参数。

接口中的default和static方法

Java8中接口中可以有实现代码,可以使用default或static关键字来修饰有具体实现的方法。

Default方法加入到java中,这是引人关注的事情。Default方法可以认为是Lambda表达式和JDK类库之间的桥梁。引入Default方法的主要目的是为了升级标准JDK接口,另外也是为了我们最终能在Java8中顺畅使用Lambda表达式。

Java Time API

  • LocalDate today = LocalDate.now();

  • LocalDate firstDay_2014 = LocalDate.of(2014, Month.JANUARY, 1);

  • LocalDate hundredDay2014 = LocalDate.ofYearDay(2014, 100);

  • LocalTime specificTime = LocalTime.of(12,20,25,40);

  • LocalDateTime today = LocalDateTime.now();

  • Instant timestamp = Instant.now();

  • today.with(TemporalAdjusters.firstDayOfMonth())

  • today.format(DateTimeFormatter.ofPattern("d::MMM::uuuu"))

jdeps命令

jdeps是一个相当棒的命令行工具,它可以展示包层级和类层级的Java类依赖关系,它以.class文件、目录或者Jar文件为输入,然后会把依赖关系输出到控制台。 如jdeps org.springframework.core-3.0.5.RELEASE.jar

加强特性

Collection加强

  • Iterator中增加了forEachRemaining(Consumer action)方法

  • Collection中removeIf(Predicate filter), Collection spliterator()

  • Map中replaceAll(), compute(), merge()

Concurrency加强

  • ConcurrentHashMap compute(), forEach(), forEachEntry(), forEachKey(), forEachValue(), merge(), reduce() 和 search()方法

  • CompletableFuture

  • Executors newWorkStealingPool()方法

Java IO加强

  • Files.list(Path dir)读取一个目录,返回一个所有目录中条目的stream

  • Files.lines(Path path)读取整个文件的所有行返回stream

  • Files.find()

  • BufferedReader.lines()

参考资料