jdk1.8

Jdk1.8新特性

毫无疑问,Java 8是Java自Java 5(发布于2004年)之后的最重要的版本。这个版本包含语言、编译器、库、工具和JVM等方面的十多个新特性。在本文中我们将学习这些新特性,并用实际的例子说明在什么场景下适合使用。

这个教程包含Java开发者经常面对的几类问题:

  • 语言
  • 编译器
  • 工具
  • 运行时(JVM)

 Lambda表达式和函数式接口

简介:

Lambda表达式(也称为闭包)是Java 8中最大和最令人期待的语言改变。它允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理:函数式开发者非常熟悉这些概念。很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持Lambda表达式,但是Java开发者没有选择,只能使用匿名内部类代替Lambda表达式。

Lambda的设计耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。最简单的Lambda表达式可由逗号分隔的参数列表、->符号和语句块组成

例子:

1.示例

Arrays.asList("a","b","c").forEach(e -> System.out.println(e));

2.在上面这个代码中的参数e的类型是由编译器推理得出的,你也可以显式指定该参数的类型,例如:

Arrays.asList("a","b","c").forEach((String e) -> System.out.println(e));

3.如果Lambda表达式需要更复杂的语句块,则可以使用花括号将该语句块括起来,类似于Java中的函数体,例如:

Arrays.asList("a","b","c").forEach((String e) -> {
    System.out.println(e);
    System.out.println(e);
});

4Lambda表达式可以引用类成员和局部变量(会将这些变量隐式得转换成final),例如下列两个代码块的效果完全相同:

String name=",";
final String name2=",,";
Arrays.asList("a","b","c").forEach((String e) -> {
    System.out.println(e+name);
    System.out.println(e+name2);
});

5Lambda表达式有返回值,返回值的类型也由编译器推理得出。如果Lambda表达式中的语句块只有一行,则可以不用使用return语句,下列两个代码片段效果相同:

Arrays.asList("a","b","c").sort((e1,e2)->e1.compareTo(e2));

Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    int result = e1.compareTo( e2 );
    return result;
} );

函数接口

Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念。函数接口指的是只有一个函数的接口,这样的接口可以隐式转换为Lambda表达式。java.lang.Runnablejava.util.concurrent.Callable是函数式接口的最佳例子。在实践中,函数式接口非常脆弱:只要某个开发者在该接口中添加一个函数,则该接口就不再是函数式接口进而导致编译失败。为了克服这种代码层面的脆弱性,并显式说明某个接口是函数式接口,Java 8 提供了一个特殊的注解@FunctionalInterface(Java 库中的所有相关接口都已经带有这个注解了)

@FunctionalInterface
public interface Functional{
    void method();
  }

不过有一点需要注意,默认方法和静态方法不会破坏函数式接口的定义,因此如下的代码是合法的。

@FunctionalInterface
public interface Functional{
    void method();

    default void defaultMethod(){

    }
}

接口的默认方法和静态方法

Java 8使用两个新概念扩展了接口的含义:默认方法和静态方法。默认方法使得接口有点类似traits,不过要实现的目标不一样。默认方法使得开发者可以在 不破坏二进制兼容性的前提下,往现存接口中添加新的方法,即不强制那些实现了该接口的类也同时实现这个新加的方法。

默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:

private interface Defaulable{
    default String notRequired(){
        return "Default implementation";
    }
}
private static class DefaultableImpl implements Defaulable {
   
}
private static class OverridableImpl implements Defaulable {
    @Override
    public String notRequired() {
        return "Overridden implementation";
    }
}

方法实现与打印:

Defaulable defaulable = DefaulableFactory.create(DefaultableImpl::new);
System.out.println(defaulable.notRequired());

Defaulable defaulable1 = DefaulableFactory.create(OverridableImpl::new);
System.out.println(defaulable1);

方法的引用

方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象。方法引用和Lambda表达式配合使用,使得java类的构造方法看起来紧凑而简洁,没有很多复杂的模板代码。

public class Demo2 {
    public static Demo2 create(Supplier<Demo2> supplier){
        return supplier.get();
    }

    public static void collide(Demo2 demo2){
        System.out.println("Collided "+demo2.toString());
    }

    public void follow( Demo2 another ) {
        System.out.println( "Following the " + another.toString() );
    }

    public void repair() {
        System.out.println( "Repaired " + this.toString() );
    }
}

引用方法:

/**
 *
第一种方法引用的类型是构造器引用,语法是Class::new,或者更一般的形式:Class<T>::new。注意:这个构造器没有参数。
 
*/
Demo2 demo2= Demo2.create(Demo2::new);
List<Demo2> demo2s= Arrays.asList(demo2);
/**
 *
第二种方法引用的类型是静态方法引用,语法是Class::static_method。注意:这个方法接受一个Car类型的参数。
 
*/
demo2s.forEach(Demo2::collide);
/**
 *
第三种方法引用的类型是某个类的成员方法的引用,语法是Class::method,注意,这个方法没有定义入参:
 
*/
demo2s.forEach(Demo2::repair);
/**
 *
第四种方法引用的类型是某个实例对象的成员方法的引用,语法是instance::method。注意:这个方法接受一个Car类型的参数:
 
*/

demo2s.forEach(demo2::follow);

重复注解

自从Java 5中引入注解以来,这个特性开始变得非常流行,并在各个框架和项目中被广泛使用。不过,注解有一个很大的限制是:在同一个地方不能多次使用同一个注解。Java 8打破了这个限制,引入了重复注解的概念,允许在同一个地方多次使用同一个注解。

在Java 8中使用@Repeatable注解定义重复注解,实际上,这并不是语言层面的改进,而是编译器做的一个trick,底层的技术仍然相同。

拓宽注解的应用场景

public class Annotations {

    @Retention( RetentionPolicy.RUNTIME )

    @Target( { ElementType.TYPE_USE, ElementType.TYPE_PARAMETER } )

    public @interface NonEmpty {       

    }

 

    public static class Holder< @NonEmpty T > extends @NonEmpty Object {

        public void method() throws @NonEmpty Exception {           

        }

    }

 

    @SuppressWarnings( "unused" )

    public static void main(String[] args) {

        final Holder< String > holder = new @NonEmpty Holder< String >();       

        @NonEmpty Collection< @NonEmpty String > strings = new ArrayList<>();       

    }

}

Java编译器的新特性

获取参数名称:

为了在运行时获得Java程序中方法的参数名称,老一辈的Java程序员必须使用不同方法,例如Paranamer liberary。Java 8终于将这个特性规范化,在语言层面(使用反射API和Parameter.getName()方法)和字节码层面(使用新的javac编译器以及-parameters参数)提供支持。

Optional

Java应用中最常见的bug就是空值异常。在Java 8之前,Google Guava引入了Optionals类来解决NullPointerException,从而避免源码被各种null检查污染,以便开发者写出更加整洁的代码。Java 8也将Optional加入了官方库。

Optional仅仅是一个容易:存放T类型的值或者null。它提供了一些有用的接口来避免显式的null检查,可以参考Java 8官方文档了解更多细节。

接下来看一点使用Optional的例子:可能为空的值或者某个类型的值:

Optional< String > fullName = Optional.ofNullable( null );
System.out.println( "Full Name is set? " + fullName.isPresent() );
System.out.println( "Full Name: " + fullName.orElseGet( () -> "[none]" ) );
System.out.println( fullName.map( s -> "Hey " + s + "!" ).orElse( "Hey Stranger!" ) );

如果Optional实例持有一个非空值,则isPresent()方法返回true,否则返回false;orElseGet()方法,Optional实例持有null,则可以接受一个lambda表达式生成的默认值;map()方法可以将现有的Opetional实例的值转换成新的值;orElse()方法与orElseGet()方法类似,但是在持有null的时候返回传入的默认值。

Optional方法详解

Of

为非null的值创建一个Optional。

of方法通过工厂方法创建Optional类。需要注意的是,创建对象时传入的参数不能为null。如果传入参数为null,则抛出NullPointerException 

//调用工厂方法创建Optional实例
Optional<String> name = Optional.of("Sanaulla");
//传入参数为null,抛出NullPointerException.
Optional<String> someNull = Optional.of(null);

ofNullable

为指定的值创建一个Optional,如果指定的值为null,则返回一个空的Optional。

ofNullable与of方法相似,唯一的区别是可以接受参数为null的情况。示例如下:

//下面创建了一个不包含任何值的Optional实例
//例如,值为'null'
Optional empty = Optional.ofNullable(null);

isPresent

非常容易理解

如果值存在返回true,否则返回false。

//isPresent方法用来检查Optional实例中是否包含值
if (name.isPresent()) {
    //在Optional实例内调用get()返回已存在的值
    System.out.println(name.get());//输出Sanaulla
}

Get

如果Optional有值则将其返回,否则抛出NoSuchElementException。

上面的示例中,get方法用来得到Optional实例中的值。下面我们看一个抛出NoSuchElementException的例子:

//执行下面的代码会输出:No value present
try {
    //在空的Optional实例上调用get(),抛出NoSuchElementException
    System.out.println(empty.get());
} catch (NoSuchElementException ex) {
    System.out.println(ex.getMessage());
}

ifPresent

如果Optional实例有值则为其调用consumer,否则不做处理

要理解ifPresent方法,首先需要了解Consumer类。简答地说,Consumer类包含一个抽象方法。该抽象方法对传入的值进行处理,但没有返回值。Java8支持不用接口直接通过lambda表达式传入参数。

如果Optional实例有值,调用ifPresent()可以接受接口段或lambda表达式。类似下面的代码:

//ifPresent方法接受lambda表达式作为参数。
//lambda表达式对Optional的值调用consumer进行处理。
name.ifPresent((value) -> {
    System.out.println("The length of the value is: " + value.length());
});

orElse

如果有值则将其返回,否则返回指定的其它值。

如果Optional实例有值则将其返回,否则返回orElse方法传入的参数。示例如下:

//如果值不为null,orElse方法返回Optional实例的值。
//如果为null,返回传入的消息。
//输出:There is no value present!
Optional<String> name = Optional.of("Sanaulla");
//传入参数为null,抛出NullPointerException.
Optional<String> someNull = Optional.of(null);
System.out.println(empty.orElse("There is no value present!"));
//输出:Sanaulla
System.out.println(name.orElse("There is some value!"));

orElseGet

orElseGet与orElse方法类似,区别在于得到的默认值。orElse方法将传入的字符串作为默认值,orElseGet方法可以接受Supplier接口的实现用来生成默认值。示例如下:

Optional<String> name = Optional.of("Sanaulla");
//传入参数为null,抛出NullPointerException.
Optional<String> someNull = Optional.of(null);
//orElseGet与orElse方法类似,区别在于orElse传入的是默认值,
//orElseGet可以接受一个lambda表达式生成默认值。
//输出:Default Value
System.out.println(someNull.orElseGet(() -> "Default Value"));
//输出:Sanaulla
System.out.println(name.orElseGet(() -> "Default Value"));

orElseThrow

如果有值则将其返回,否则抛出supplier接口创建的异常。

在orElseGet方法中,我们传入一个Supplier接口。然而,在orElseThrow中我们可以传入一个lambda表达式或方法,如果值不存在来抛出异常。

try {
    //orElseThrow与orElse方法类似。与返回默认值不同,
    //orElseThrow会抛出lambda表达式或方法生成的异常
    Optional empty = Optional.ofNullable("asa");
    empty.orElseThrow(Exception::new);
} catch (Throwable ex) {
    //输出: No value present in the Optional instance
    System.out.println(ex.getMessage());
}

map

map方法文档说明如下:

如果有值,则对其执行调用mapping函数得到返回值。如果返回值不为null,则创建包含mapping返回值的Optional作为map方法返回值,否则返回空Optional

map方法用来对Optional实例的值执行一系列操作。通过一组实现了Function接口的lambda表达式传入操作。如果你不熟悉Function接口,可以参考我的这篇博客。map方法示例如下:

Optional<String> name = Optional.of("Sanaulla");
//map方法执行传入的lambda表达式参数对Optional实例的值进行修改。
//为lambda表达式的返回值创建新的Optional实例作为map方法的返回值。
Optional<String> upperName = name.map((value) -> value.toUpperCase());
System.out.println(upperName.orElse("No value found"));

flatMap

如果有值,为其执行mapping函数返回Optional类型返回值,否则返回空Optional。flatMap与map(Funtion)方法类似,区别在于flatMap中的mapper返回值必须是Optional。调用结束时,flatMap不会对结果用Optional封装。

flatMap方法与map方法类似,区别在于mapping函数的返回值不同。map方法的mapping函数返回值可以是任何类型T,而flatMap方法的mapping函数必须是Optional参照map函数,使用flatMap重写的示例如下:

//flatMap与map(Function)非常类似,区别在于传入方法的lambda表达式的返回类型。
//map方法中的lambda表达式返回值可以是任意类型,在map函数返回之前会包装为Optional。
//但flatMap方法中的lambda表达式返回值必须是Optionl实例。
upperName = name.flatMap((value) -> Optional.of(value.toUpperCase()));
System.out.println(upperName.orElse("No value found"));//输出SANAULLA

Filter

filter个方法通过传入限定条件对Optional实例的值进行过滤。文档描述如下:

如果有值并且满足断言条件返回包含该值的Optional,否则返回空Optional。

读到这里,可能你已经知道如何为filter方法传入一段代码。是的,这里可以传入一个lambda表达式。对于filter函数我们应该传入实现了Predicate接口的lambda表达式。

现在我来看看filter的各种用法,下面的示例介绍了满足限定条件和不满足两种情况:

//filter方法检查给定的Option值是否满足某些条件。
//如果满足则返回同一个Option实例,否则返回空Optional。
Optional<String> longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters"));//输出Sanaulla

//另一个例子是Optional值不满足filter指定的条件。
Optional<String> anotherName = Optional.of("Sana");
Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
//输出:name长度不足6字符
System.out.println(shortName.orElse("The name is less than 6 characters"));

Streams

新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。

public class Streams {

    private enum Status{
        OPEN,CLOSED
   
};

    private static final class Task {
        private final Status status;
        private final Integer points;

        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }

        public Integer getPoints() {
            return points;
        }

        public Status getStatus() {
            return status;
        }

        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }

    public static void main(String[] args) {
        final Collection< Task > tasks = Arrays.asList(
                new Task( Status.OPEN, 5 ),
                new Task( Status.OPEN, 13 ),
                new Task( Status.CLOSED, 8 )
        );

        long sum = tasks.stream().filter(task -> task.getStatus() == Status.OPEN).mapToInt(Task::getPoints).sum();

         System.out.println("Total points: "+sum);

         double reduce = tasks.stream().parallel().map(task -> task.getPoints()).reduce(0, Integer::sum);

         System.out.println("Total points (all tasks): "+reduce);
        
Map<Integer, List<Task>> collect = tasks.stream().collect(Collectors.groupingBy(Task::getPoints));

         System.out.println(collect);
        
List<String> collect1 = tasks.stream().mapToInt(Task::getPoints).asLongStream().mapToDouble( points -> points / reduce).boxed().mapToLong(weigth -> (long) (weigth * 100)).mapToObj(percentage -> percentage + "%").collect(Collectors.toList());

         System.out.println(collect1);

    }
}

首先,tasks集合被转换成steam表示;其次,在steam上的filter操作会过滤掉所有CLOSED的task;第三,mapToInt操作基于每个task实例的Task::getPoints方法将task流转换成Integer集合;最后,通过sum方法计算总和,得出最后的结果。

晚期操作(例如forEach或者sum),会遍历steam并得出结果或者附带结果;在执行晚期操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,晚期操作都是立刻对steam进行遍历。

可以通过steam处理io:

final Path path = new File( filename ).toPath();

try( Stream< String > lines = Files.lines( path, StandardCharsets.UTF_8 ) ) {

    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );

}

Stream流特性解释:

Stream接口还包含几个基本类型的子接口如IntStream, LongStream 和 DoubleStream。

关于流和其它集合具体的区别,可以参照下面的列表:

  1. 不存储数据 。流是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。
  2. 函数式编程 。流的操作不会修改数据源,例如filter不会将数据源中的数据删除。
  3. 延迟操作 。流的很多操作如filter,map等中间操作是延迟执行的,只有到终点操作才会将操作顺序执行。
  4. 可以解绑 。对于无限数量的流,有些操作是可以在有限的时间完成的,比如limit(n)或findFirst(),这些操作可是实现”短路”(Short-circuiting),访问到有限的元素后就可以返回。
  5. 纯消费 。流的元素只能访问一次,类似Iterator,操作没有回头路,如果你想从头重新访问流的元素,对不起,你得重新生成一个新的流。

流的操作是以管道的方式串起来的。流管道包含一个数据源,接着包含零到N个中间操作,最后以一个终点操作结束。

Stream特征:

并行 Parallelism

所有的流操作都可以串行执行或者并行执行。除非显示地创建并行流,否则Java库中创建的都是串行流。Collection.stream()为集合创建串行流而Collection.parallelStream()为集合创建并行流。IntStream.range(int, int)创建的是串行流。通过parallel()方法可以将串行流转换成并行流,sequential()方法将流转换成并行流。

除非方法的Javadoc中指明了方法在并行执行的时候结果是不确定(比如findAny、forEach),否则串行和并行执行的结果应该是一样的。

不干涉 Non-interference

流可以从非线程安全的集合中创建,当流的管道执行的时候,非concurrent数据源不应该被改变。下面的代码会抛出java.util.ConcurrentModificationException异常:

List<String> l = new ArrayList(Arrays.asList("one", "two"));Stream<String> sl = l.stream();sl.forEach(s -> l.add("three"));

在设置中间操作的时候,可以更改数据源,只有在执行终点操作的时候,才有可能出现并发问题(抛出异常,或者不期望的结果),比如下面的代码不会抛出异常:

List<String> l = new ArrayList(Arrays.asList("one", "two"));Stream<String> sl = l.stream();l.add("three");sl.forEach(System.out::println);

对于concurrent数据源,不会有这样的问题,比如下面的代码很正常:

List<String> l = new CopyOnWriteArrayList<>(Arrays.asList("one", "two"));Stream<String> sl = l.stream();sl.forEach(s -> l.add("three"));

虽然我们上面例子是在终点操作中对非并发数据源进行修改,但是非并发数据源也可能在其它线程中修改,同样会有并发问题。

无状态 Stateless behaviors

大部分流的操作的参数都是函数式接口,可以使用Lambda表达式实现。它们用来描述用户的行为,称之为行为参数(behavioral parameters)。

如果这些行为参数有状态,则流的操作的结果可能是不确定的,比如下面的代码:

List<String> l = new ArrayList(Arrays.asList("one", "two", ……));class State {    boolean s;}final State state = new State();Stream<String> sl = l.stream().map(e -> {    if (state.s)        return "OK";    else {        state.s = true;        return e;    } });sl.forEach(System.out::println);

上面的代码在并行执行时多次的执行结果可能是不同的。这是因为这个lambda表达式是有状态的。

副作用 Side-effects

有副作用的行为参数是被鼓励使用的。

副作用指的是行为参数在执行的时候有输入输入,比如网络输入输出等。

这是因为Java不保证这些副作用对其它线程可见,也不保证相同流管道上的同样的元素的不同的操作运行在同一个线程中。

很多有副作用的行为参数可以被转换成无副作用的实现。一般来说println()这样的副作用代码不会有害。

ArrayList<String> results = new ArrayList<>();stream.filter(s -> pattern.matcher(s).matches())      .forEach(s -> results.add(s));  // 副作用代码

上面的代码可以改成无副作用的。

List<String>results =    stream.filter(s -> pattern.matcher(s).matches())          .collect(Collectors.toList());  // No side-effects!

排序 Ordering

某些流的返回的元素是有确定顺序的,我们称之为encounter order。这个顺序是流提供它的元素的顺序,比如数组的encounter order是它的元素的排序顺序,List是它的迭代顺序(iteration order),对于HashSet,它本身就没有encounter order。

一个流是否是encounter order主要依赖数据源和它的中间操作,比如数据源List和Array上创建的流是有序的(ordered),但是在HashSet创建的流不是有序的。

sorted()方法可以将流转换成有序的,unordered可以将流转换成无序的。除此之外,一个操作可能会影响流的有序,比如map方法,它会用不同的值甚至类型替换流中的元素,所以输入元素的有序性已经变得没有意义了,但是对于filter方法来说,它只是丢弃掉一些值而已,输入元素的有序性还是保障的。

对于串行流,流有序与否不会影响其性能,只是会影响确定性(determinism),无序流在多次执行的时候结果可能是不一样的。

对于并行流,去掉有序这个约束可能会提供性能,比如distinct、groupingBy这些聚合操作。

结合性 Associativity

一个操作或者函数op满足结合性意味着它满足下面的条件:

(a op b) op c == a op (b op c)

对于并发流来说,如果操作满足结合性,我们就可以并行计算:

a op b op c op d == (a op b) op (c op d)

比如min、max以及字符串连接都是满足结合性的。

Stream创建方式:

可以通过多种方式创建流:

1、通过集合的stream()方法或者parallelStream(),比如Arrays.asList(1,2,3).stream()。

2、通过Arrays.stream(Object[])方法, 比如Arrays.stream(new int[]{1,2,3})。

3、使用流的静态方法,比如Stream.of(Object[]),IntStream.range(int, int)或者Stream.iterate(Object, UnaryOperator),如Stream.iterate(0, n -> n * 2),或者generate(Supplier<T> s)如Stream.generate(Math::random)。

4、BufferedReader.lines()从文件中获得行的流。

5、Files类的操作路径的方法,如list、find、walk等。

6、随机数流Random.ints()。

7、其它一些类提供了创建流的方法,如BitSet.stream(),Pattern.splitAsStream(java.lang.CharSequence), 和JarFile.stream()。

8、更底层的使用StreamSupport,它提供了将Spliterator转换成流的方法。

中间操作 intermediate operations

中间操作会返回一个新的流,并且操作是延迟执行的(lazy),它不会修改原始的数据源,而且是由在终点操作开始的时候才真正开始执行。这个Scala集合的转换操作不同,Scala集合转换操作会生成一个新的中间集合,显而易见Java的这种设计会减少中间对象的生成。

常见流的中间操作:

Distinct

distinct保证输出的流中包含唯一的元素,它是通过Object.equals(Object)来检查是否包含相同的元素。

List<String> collect = Stream.of("a", "b", "c", "d").distinct().collect(Collectors.toList());
System.out.println(collect);

Filter

filter返回的流中只包含满足断言(predicate)的数据。

下面的代码返回流中的偶数集合。

List<Integer> collect1 = IntStream.range(1, 10).filter(i -> i % 2 == 0).boxed().collect(Collectors.toList());
System.out.println(collect1);

Map

map方法将流中的元素映射成另外的值,新的值类型可以和原来的元素的类型不同。

下面的代码中将字符元素映射成它的哈希码(ASCII值)。

List<Integer> collect2 = Stream.of("a", "b", "c").map(c -> c.hashCode()).collect(Collectors.toList());
System.out.println(collect2);

Flatmap

flatmap方法混合了map+flattern的功能,它将映射后的流的元素全部放入到一个新的流中。它的方法定义如下:

String poetry = "Where, before me, are the ages that have gone?/n" + "And where, behind me, are the coming generations?/n" + "I think of heaven and earth, without limit, without end,/n" + "And I am all alone and my tears fall down.";
Stream<String> lines = Arrays.stream(poetry.split("/n"));
Stream<String> words = lines.flatMap(line -> Arrays.stream(line.split(" ")));
List<String> l = words.map(w -> {
    if (w.endsWith(",") || w.endsWith(".") || w.endsWith("?"))
        return w.substring(0, w.length() - 1).trim().toLowerCase();
    else return w.trim().toLowerCase();
}).distinct().sorted().collect(Collectors.toList());
System.out.println(l);

Limit

limit方法指定数量的元素的流。对于串行流,这个方法是有效的,这是因为它只需返回前n个元素即可,但是对于有序的并行流,它可能花费相对较长的时间,如果你不在意有序,可以将有序并行流转换为无序的,可以提高性能。

List<Integer> collect3 = IntStream.range(1, 100).limit(5).boxed().collect(Collectors.toList());
System.out.println(collect3);

Peek

peek方法方法会使用一个Consumer消费流中的元素,但是返回的流还是包含原来的流中的元素。

String[] strings = {"a", "b", "c", "d"};
Arrays.asList(strings).stream().peek(System.out::println);

Sorted

sorted()将流中的元素按照自然排序方式进行排序,如果元素没有实现Comparable,则终点操作执行时会抛出java.lang.ClassCastException异常。sorted(Comparator<? super T> comparator)可以指定排序的方式。

对于有序流,排序是稳定的。对于非有序流,不保证排序稳定。

String[] arr = new String[]{"b_123", "c+342", "b#632", "d_123"};
List<String> collect = Arrays.stream(arr).sorted((s1, s2) -> {
    if (s1.charAt(0) == s2.charAt(0)) return s1.substring(2).compareTo(s2.substring(2));
    else return s1.charAt(0) - s2.charAt(0);
}).collect(Collectors.toList());
System.out.println(collect);

Skip

skip返回丢弃了前n个元素的流,如果流中的元素小于或者等于n,则返回空的流。

终点操作 terminal operations

Match

这一组方法用来检查流中的元素是否满足断言。

allMatch只有在所有的元素都满足断言时才返回true,否则flase,流为空时总是返回true

anyMatch只有在任意一个元素满足断言时就返回true,否则flase,

noneMatch只有在所有的元素都不满足断言时才返回true,否则flase,

System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).anyMatch( i -> i > 0)); //true
System.out.println(Stream.of(1,2,3,4,5).noneMatch( i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().allMatch( i -> i > 0)); //true
System.out.println(Stream.<Integer>empty().anyMatch( i -> i > 0)); //false
System.out.println(Stream.<Integer>empty().noneMatch( i -> i > 0)); //true

Count

count方法返回流中的元素的数量。它实现为:

mapToLong(e -> 1L).sum();

Collect

使用一个collector执行mutable reduction操作。辅助类 Collectors 提供了很多的collector,可以满足我们日常的需求,你也可以创建新的collector实现特定的需求。它是一个值得关注的类,你需要熟悉这些特定的收集器,如聚合类averagingInt、最大最小值maxByminBy、计数counting、分组groupingBy、字符串连接joining、分区partitioningBy、汇总summarizingInt、化简reducing、转换toXXX等。】

Find

findAny()返回任意一个元素,如果流为空,返回空的Optional,对于并行流来说,它只需要返回任意一个元素即可,所以性能可能要好于findFirst(),但是有可能多次执行的时候返回的结果不一样。findFirst()返回第一个元素,如果流为空,返回空的Optional。

forEach、forEachOrdered

forEach遍历流的每一个元素,执行指定的action。它是一个终点操作,和peek方法不同。这个方法不担保按照流的encounter order顺序执行,如果对于有序流按照它的encounter order顺序执行,你可以使用forEachOrdered方法。

Stream.of(1,2,3,4,5).forEach(System.out::println);

最大最小值

max返回流中的最大值,min返回流中的最小值。

Reduce

第一个方法使用流中的第一个值作为初始值,后面两个方法则使用一个提供的初始值。

Optional<Integer> total = Stream.of(1, 2, 3, 4, 5).reduce((x, y) -> x + y);
Integer total2 = Stream.of(1, 2, 3, 4, 5).reduce(0, (x, y) -> x + y);

toArray()

将流中的元素放入到一个数组中。

Date/time时间api

Java 8引入了新的Date-Time API(JSR 310)来改进时间、日期的处理。时间和日期的管理一直是最令Java开发者痛苦的问题。java.util.Date和后来的java.util.Calendar一直没有解决这个问题(甚至令开发者更加迷茫)。

因为上面这些原因,诞生了第三方库Joda-Time,可以替代Java的时间管理API。Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。新的java.time包包含了所有关于日期、时间、时区、Instant(跟日期类似但是精确到纳秒)、duration(持续时间)和时钟操作的类。新设计的API认真考虑了这些类的不变性(从java.util.Calendar吸取的教训),如果某个实例需要修改,则返回一个新的对象。

我们接下来看看java.time包中的关键类和各自的使用例子。首先,Clock类使用时区来返回当前的纳秒时间和日期。Clock可以替代System.currentTimeMillis()TimeZone.getDefault()

// Get the system clock as UTC offset

final Clock clock = Clock.systemUTC();

System.out.println( clock.instant() );

System.out.println( clock.millis() );

这个例子的输出结果是:

2014-04-12T15:19:29.282Z

1397315969360

第二,关注下LocalDateLocalTime类。LocalDate仅仅包含ISO-8601日历系统中的日期部分;LocalTime则仅仅包含该日历系统中的时间部分。这两个类的对象都可以使用Clock对象构建得到。

// Get the local date and local time

final LocalDate date = LocalDate.now();

final LocalDate dateFromClock = LocalDate.now( clock );

 

System.out.println( date );

System.out.println( dateFromClock );

 

// Get the local date and local time

final LocalTime time = LocalTime.now();

final LocalTime timeFromClock = LocalTime.now( clock );

 

System.out.println( time );

System.out.println( timeFromClock );

上述例子的输出结果如下:

2014-04-12

2014-04-12

11:25:54.568

15:25:54.568

Base64支持

public class Base64s {

    public static void main(String[] args) {

        final String text = "Base64 finally in Java 8!";

 

        final String encoded = Base64

            .getEncoder()

            .encodeToString( text.getBytes( StandardCharsets.UTF_8 ) );

        System.out.println( encoded );

 

        final String decoded = new String(

            Base64.getDecoder().decode( encoded ),

            StandardCharsets.UTF_8 );

        System.out.println( decoded );

    }

}

并行数组

Java8版本新增了很多新的方法,用于支持并行数组处理。最重要的方法是parallelSort(),可以显著加快多核机器上的数组排序。下面的例子论证了parallexXxx系列的方法:

Arrays.parallelSetAll(longs,index-> ThreadLocalRandom.current().nextInt(1000000));
Arrays.stream(longs).limit(10).forEach(i-> System.out.print(i+" "));
System.out.println();

Arrays.parallelSort(longs);
Arrays.stream(longs).limit(10).forEach(i-> System.out.print(i+" "));
System.out.println();

并发性

基于新增的lambda表达式和steam特性,为Java 8中为java.util.concurrent.ConcurrentHashMap类添加了新的方法来支持聚焦操作;另外,也为java.util.concurrentForkJoinPool类添加了新的方法来支持通用线程池操作(更多内容可以参考我们的并发编程课程)。

Java 8还添加了新的java.util.concurrent.locks.StampedLock类,用于支持基于容量的锁——该锁有三个模型用于支持读写操作(可以把这个锁当做是java.util.concurrent.locks.ReadWriteLock的替代者)。

java.util.concurrent.atomic包中也新增了不少工具类,列举如下:

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder

匿名内部类

以前:

bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
    @Override
    public ChannelPipeline getPipeline() throws Exception {
        return Channels.pipeline(new HelloClientHandler());
    }
});

现在直接()->跟返回结果

bootstrap.setPipelineFactory(() -> Channels.pipeline(new HelloClientHandler()));

 

posted on 2017-02-16 18:19  阿发仔  阅读(4631)  评论(0编辑  收藏  举报

导航