Java并无没落,人们很快就会发现这一点 本教程将对java8新增的特性进行简明的较少,使得你们对于java8新特性有一个比较直观的印象。html
java在jdk1.8前仅容许在接口中使用抽象方法和静态常量定义。Java8将容许在接口中定义默认方法和静态方法。接口中的默认方法主要是用于解决jdk的向下兼容问题。如Collection接口中添加了新的stream方法,则jdk1.7以前的代码是没法在1.8平台上编译经过,故提供了接口中的默认方法。
###接口中的默认方法 Java 8 容许咱们使用default关键字,为接口声明添加非抽象的方法实现。这个特性又被称为扩展方法。该方法在函数接口或者是非函数接口均可以实现。java
public interface DefaultInterface { default void Print() { System.out.println("hello world"); } } public interface Formula { String TEXT = "text"; double calculate(int a); default double sqrt(int a) { return Math.sqrt(a); } }
####默认方法继承git
重写与普通方法的大体一致。多重继承时,使用加强的supergithub
####接口中的静态方法 该方法主要针对类库的开发人员,便于工具类(接口)的设计和开发。编程
public interface Formula { static void print() { System.out.println("hello, world"); } }
###函数接口(Functional Interfaces) 本节做为lambda表达式的基础部分,内容相对较为简单。Runnable接口、Callable接口、Comparator接口, 和Java中定义的其它大量接口——在Java 8中咱们称为函数式接口:它们是只须要实现一个方法去知足需求的接口。
每个lambda都可以经过一个特定的函数式接口,与一个给定的类型进行匹配。同时,任意只包含一个抽象方法的接口,咱们均可以用来作成lambda表达式。为了让你定义的接口知足要求,你应当在接口前加上@FunctionalInterface 标注。编译器会注意到这个标注,若是你的接口中定义了第二个抽象方法的话,编译器会抛出异常。api
@FunctionalInterface public interface Converter<F, T> { T convert(F from); } Converter<Integer, String> converter = from -> String.valueOf(from); System.out.println(converter.convert(33));
函数名| 参数| 返回类型| 示例| 对应Guava| 备注 ----|--- Predicate<T>| T |boolean |吃了没? |Predicate<T>| 断言,判断表达式是否为真 Consumer<T>| T |void ||| Function<T,R>| T |R |String 转int| Function<T,R> | Supplier<T> |无| T |工厂方法 || UnaryOperator<T>| T |T| 逻辑非(!)| | BinaOperator<T>| (T,T)| T| 求两个数的乘积 ||并发
###方法引用 方法引用语法格式有如下三种: objectName::instanceMethod ClassName::staticMethod ClassName::instanceMethod 对于函数式接口的代码块,还有更简单的实现方式,具体以下oracle
####静态方法引用app
Converter<Integer, String> methodReference = String::valueOf; System.out.println(methodReference.convert(21));
####非静态方法引用dom
class Something { String startsWith(String s) { return String.valueOf(s.charAt(0)); } } Something something = new Something(); Converter<String, String> converter = something::startsWith; String converted = converter.convert("Java"); System.out.println(converted); // "J"
####构造函数引用 对于构造函数(构造函数本质上也是一种静态方法),此处的用法比较精妙。且看示例:
public class Person { String firstName; String lastName; public Person() {} public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } } interface PersonFactory<P extends Person> { P create(String firstName, String lastName); } PersonFactory personFactory = Person::new; Person person = personFactory.create("黄", "大仙");
上面展现的代码中,咱们首先建立了一个Person类,和该类的一个工厂接口用于建立Person对象。最终经过构造函数引用来把全部东西拼到一块儿,而不是像之前同样,经过手动实现一个工厂来这么作。
咱们经过Person::new来建立一个Person类构造函数的引用。Java编译器会自动地选择合适的构造函数(person(string, string))来匹配PersonFactory.create函数的签名,并选择正确的构造函数形式。
PersonFatory为一个“函数接口”, 且Person::new 返回的为一个函数。具体请看上节内容。
##lambda表达式 ###认识lambda表达式
维基百科对lambda表达式的解释:没找到
简单的说lambda表达式:一段带有输入参数的可执行语句块。
(Type1 param1, Type2 param2, ..., TypeN paramN) -> { statment1; statment2; //............. return statmentM; }
//无参数的lambda表达式 Runnable noArgument = () -> System.out.print("hello, world"); ActionListener oneArgument = event -> System.out.print("button click"); Runnable multiStatement = () -> { System.out.print("hello "); System.out.print("world"); }; BinaryOperator<Long> add = (x, y) -> x + y; BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;
注意:1. 代码块和普通方法遵循的规则一致, 可使用返回或异常进行退出。
####体验lambda表达式
举个栗子:将list中的内容由小写变为大写
List<String> names = new ArrayList<>(); names.add("huang"); names.add("chuancong"); List<String> lowercaseNames = new ArrayList<>(); for (String name : names) { lowercaseNames.add(name.toLowerCase()); } List<String> names = new ArrayList<>(); names.add("huang"); names.add("chuancong"); List<String> lowercaseNames = FluentIterable.from(names).transform(new Function<String, String>() { @Override public String apply(String name) { return name.toLowerCase(); } }).toList(); List<String> names = new ArrayList<>(); names.add("huang"); names.add("chuancong"); List<String> lowercaseNames = names.stream().map((String name) -> {return name.toLowerCase();}).collect(Collectors.toList());
###Lambda表达式做用域 ####访问本地变量 对于JDK1.7匿名内部类而言,若是要使用外部的变量,则该变量须要显式的定义为final类型。
final String name = "黄传聪"; Button button = new Button(); button.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.out.println("hi: " + name); } }); Java 1.8以后,该final修饰符能够省略,可是、but java8虽然放松了限制,可是该变量仍是“既成事实上必须是final”。 String name = "黄传聪"; name += "黄传聪"; button.addActionListener(e -> System.out.println("hi: " + name));
该代码会编译失败, 总而言之,java1.8虽然放松了 限制,可是在编译时仍要保证该变量为final类型。所以,lambda表达式访问外部变量有一个很是重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)。
对于静态变量和属性的访问方式同匿名内部类一致。
class Lambda4 { static int outerStaticNum; int outerNum; void testScopes() { Converter<Integer, String> stringConverter1 = (from) -> { outerNum = 23; return String.valueOf(from); }; Converter<Integer, String> stringConverter2 = (from) -> { outerStaticNum = 72; return String.valueOf(from); }; } }
###访问接口的默认方法 接口的默认方法为java1.8新加的属性,具体可参看第一小节。接口中默认方法,能够被其实现类、或匿名内部类进行调用。可是,Lambda表达式不能使用。
#####类型推断 Lambda表达式中的类型推断,其实是java7中就引入的目标类型推断的扩充。Java7中提供了<>操做符,使得javac可根据该操做符推断出泛型参数的类型。如:
Map<String, String> map = new HashMap<String, String>(); Map<String, String> map = new HashMap<>(); private void useMap(Map<String, String> map); useMap(new HashMap<>);
lambda表达式可省略全部的参数类型,javac能够根据上下文推断参数类型,可是,也有推断不出的情景,且看下文方法重载。
方法的重载可能会致使lambda表达类型推断失败。 对于推导过程:
@FunctionalInterface public interface IntPredicate { public boolean test(int value); } @FunctionalInterface public interface Predicate<T> { boolean test(T t); } public boolean predicate(Predicate<Integer> predicate) { return predicate.test(1); } public boolean predicate(IntPredicate predicate) { return predicate.test(1); } application.predicate(a -> a > 1);
该段代码在Intellij 中提示:
表示,推断系统没法推断lambda表达式的具体类型,由于该表达式对于两个方法的参数都契合。
##Stream stream流 API是java8中最大的一次改动,咱们将从 什么是流,流的做用及流的实战等方面进行相关的讨论。
A sequence of elements supporting sequential and parallel aggregate operations.
针对java给出的定义咱们进行简单的解释: Stream是元素的集合,相似于Iterator;能够经过顺序和并行的方式对流进行汇聚操做。
你们能够把Stream当成一个高级版本的Iterator。原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操做;高级版本的Stream,用户只要给出须要对其包含的元素执行什么操做,好比“过滤掉长度大于10的字符串”、“获取每一个字符串的首字母”等,具体这些操做如何应用到每一个元素上,就给Stream就行了!
举个栗子:
List<Integer> integerList =new ArrayList<>(); integerList.add(1); integerList.add(2); integerList.add(null); integerList.stream().filter(num -> num != null).count();
该例子的左右:统计integerList中元素不为null的值得数量。
对于该例子的拆解,能够发现使用stream流能够分为如下三个步骤:
转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(能够有屡次转换);
对Stream进行聚合(Reduce)操做,获取想要的结果;
常见的建立stream的方法有两种
经过Stream接口的静态工厂方法;
经过Collection接口的默认方法把一个Collection对象转换成Stream
经过Stream接口的静态工厂方法建立
经过该接口中提供的静态方法 of()进行建立
其源码以下,该方法存在两个重载方法。
public static<T> Stream<T> of(T t) { return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false); } @SafeVarargs @SuppressWarnings("varargs") // Creating a stream from an array is safe public static<T> Stream<T> of(T... values) { return Arrays.stream(values); } 使用该方法建立Stream的示例以下: Stream<Integer> integerStream = Stream.of(1); Stream<String> stringStream = Stream.of("1", "2");
Returns an infinite sequential unordered stream where each element is generated by the provided Supplier 该方法返回一个无限长度的stream, 该方法适合生成 固定元素或随机元素的流。如: Stream.generate(new Supplier<String>() { @Override public String get() { return "1"; } }); Stream.generate(new Supplier<Double>() { @Override public Double get() { return Math.random(); } }); Stream.generate(() -> Math.random()); Stream.generate(Math::random);
三条语句的做用都是同样的,这个无限长度Stream是懒加载,通常这种无限长度的Stream都会配合Stream的limit()方法来用。
也是生成无限长度的Stream,和generator不一样的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素能够认为是:seed,f(seed),f(f(seed))无限循环
Returns an infinite sequential ordered Stream produced by iterative application of a function f to an initial element seed, producing a Stream consisting of seed, f(seed), f(f(seed)), etc. The first element (position 0) in the Stream will be the provided seed. For n > 0, the element at position n, will be the result of applying the function f to the element at position n - 1.
没看懂,不讲了
该种方法应该是使用比较频繁的方法,流大多数的使用场景在于对容器中得数据的处理。
default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); }
示例:
List<Integer> integerList =new ArrayList<>(); integerList.add(1); integerList.add(2); integerList.add(null); integerList.stream();
这一节是纯抄的的,抄的,抄的 转换Stream其实就是把一个Stream经过某些行为转换成一个新的Stream。Stream接口中定义了几个经常使用的转换方法,下面咱们挑选几个经常使用的转换方法来解释。
对于Stream中包含的元素进行去重操做(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;
distinct方法示意图(如下全部的示意图都要感谢RxJava项目的doc中的图片给予的灵感, 若是示意图表达的有错误和不许确的地方,请直接联系我。):
对于Stream中包含的元素使用给定的过滤函数进行过滤操做,新生成的Stream只包含符合条件的元素; filter方法示意图: #####map: 对于Stream中包含的元素使用给定的转换函数进行转换操做,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble, 主要用于解决自动装箱、拆箱的开销。这三个方法也比较好理解,好比mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的 Stream中的元素都是int类型。之因此会有这样三个变种方法,能够免除自动装箱/拆箱的额外消耗; map方法示意图:
和map相似,能够将其看做为列表的合并操做
flatMap方法示意图:
示例代码:
Stream<List<Integer>> inputStream = Stream.of( Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6) ); Stream<Integer> outputStream = inputStream. flatMap(childList -> childList.stream()); outputStream.forEach(System.out::println); //outputStream.forEach(t -> System.out.println(t));
运行结果为:
起做用:至关于将多个容器对应的流合并到同一个流中。
#####peek: 生成一个包含原Stream的全部元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每一个元素被消费的时候都会执行给定的消费函数;
peek方法示意图:
#####limit: 对一个Stream进行截断操做,获取其前N个元素,若是原Stream中包含的元素个数小于N,那就获取其全部的元素;
limit方法示意图:
返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,若是原Stream中包含的元素个数小于N,那么返回空Stream;
#####在一块儿,在一块儿!
List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10); System.out.println(“sum is:”+nums.stream().filter(num -> num != null). distinct().mapToInt(num -> num * 2). peek(System.out::println).skip(2).limit(4).sum());
这段代码演示了上面介绍的全部转换方法(除了flatMap),简单解释一下这段代码的含义:给定一个Integer类型的List,获取其对应的Stream对象,而后进行过滤掉null,再去重,再每一个元素乘以2,再每一个元素被消费的时候打印自身,在跳过前两个元素,最后去前四个元素进行加和运算(解释一大堆,很像废话,由于基本看了方法名就知道要作什么了。这个就是声明式编程的一大好处!)。你们能够参考上面对于每一个方法的解释,看看最终的输出是什么。
######性能问题
有些细心的同窗可能会有这样的疑问:在对于一个Stream进行屡次转换操做,每次都对Stream的每一个元素进行转换,并且是执行屡次,这样时间复杂度就是一个for循环里把全部操做都作掉的N(转换的次数)倍啊。其实不是这样的,转换操做都是lazy的,多个转换操做只会在汇聚操做(见下节)的时候融合起来,一次循环完成。咱们能够这样简单的理解,Stream里有个操做函数的集合,每次转换操做就是把转换函数放入这个集合中,在汇聚操做的时候循环Stream对应的集合,而后对每一个元素执行全部的函数。
##Reduce 该操做是对Stream流进行的最后一步操做。
A reduction operation (also called a fold) takes a sequence of input elements and combines them into a single summary result by repeated application of a combining operation, such as finding the sum or maximum of a set of numbers, or accumulating elements into a list. The streams classes have multiple forms of general reduction operations, called reduce() and collect(), as well as multiple specialized reduction forms such as sum(), max(), or count().
简单翻译一下:汇聚操做(也称为折叠)接受一个元素序列为输入,反复使用某个合并操做,把序列中的元素合并成一个汇总的结果。好比查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List对象。Stream接口有一些通用的汇聚操做,好比reduce()和collect();也有一些特定用途的汇聚操做,好比sum(),max()和count()。注意:sum方法不是全部的Stream对象都有的,只有IntStream、LongStream和DoubleStream是实例才有。
下面会分两部分来介绍汇聚操做:
可变汇聚对应的只有一个方法:collect,正如其名字显示的,它能够把Stream中的要有元素收集到一个结果容器中(好比Collection)。先看一下最通用的collect方法的定义(还有其余override方法):
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
先来看看这三个参数的含义:Supplier supplier是一个工厂函数,用来生成一个新的容器;BiConsumer accumulator也是一个函数,用来把Stream中的元素添加到结果容器中;BiConsumer combiner仍是一个函数,用来把中间状态的多个结果容器合并成为一个(并发的时候会用到)。看晕了?来段代码!
List<Integer> nums = Lists.newArrayList(1,1,null,2,3,4,null,5,6,7,8,9,10); List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null). collect(() -> new ArrayList<Integer>(), (list, item) -> list.add(item), (list1, list2) -> list1.addAll(list2));
上面这段代码就是对一个元素是Integer类型的List,先过滤掉所有的null,而后把剩下的元素收集到一个新的List中。进一步看一下collect方法的三个参数,都是lambda形式的函数(上面的代码可使用方法引用来简化,留给读者本身去思考)。 第一个函数生成一个新的ArrayList实例;
第二个函数接受两个参数,第一个是前面生成的ArrayList对象,二个是stream中包含的元素,函数体就是把stream中的元素加入ArrayList对象中。第二个函数被反复调用直到原stream的元素被消费完毕;
第三个函数也是接受两个参数,这两个都是ArrayList类型的,函数体就是把第二个ArrayList所有加入到第一个中;
可是上面的collect方法调用也有点太复杂了,不要紧!咱们来看一下collect方法另一个override的版本,其依赖Collector。
1 <R, A> R collect(Collector<? super T, A, R> collector);
这样清爽多了!少年,还有好消息,Java8还给咱们提供了Collector的工具类–Collectors,其中已经定义了一些静态工厂方法,好比:Collectors.toCollection()收集到Collection中, Collectors.toList()收集到List中和Collectors.toSet()收集到Set中。这样的静态方法还有不少,这里就不一一介绍了,你们能够直接去看JavaDoc。下面看看使用Collectors对于代码的简化:
List<Integer> numsWithoutNull = nums.stream().filter(num -> num != null). collect(Collectors.toList()); JavaDoc示例: // Accumulate names into a List List<String> list = people.stream().map(Person::getName).collect(Collectors.toList()); // Accumulate names into a TreeSet Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new)); // Convert elements to strings and concatenate them, separated by commas String joined = things.stream() .map(Object::toString) .collect(Collectors.joining(", ")); // Compute sum of salaries of employee int total = employees.stream() .collect(Collectors.summingInt(Employee::getSalary))); // Group employees by department Map<Department, List<Employee>> byDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment)); // Compute sum of salaries by department Map<Department, Integer> totalByDept = employees.stream() .collect(Collectors.groupingBy(Employee::getDepartment, Collectors.summingInt(Employee::getSalary))); // Partition students into passing and failing Map<Boolean, List<Student>> passingFailing = students.stream() .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
除去可变汇聚剩下的,通常都不是经过反复修改某个可变对象,而是经过把前一次的汇聚结果当成下一次的入参,反复如此。好比reduce,count,allMatch; – reduce方法:reduce方法很是的通用,后面介绍的count,sum等均可以使用其实现。reduce方法有三个override的方法,本文介绍两个最经常使用的,最后一个留给读者本身学习。先来看reduce方法的第一种形式,其方法定义以下:
Optional<T> reduce(BinaryOperator<T> accumulator);
接受一个BinaryOperator类型的参数,在使用的时候咱们能够用lambda表达式来。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10); System.out.println("ints sum is:" + ints.stream().reduce((sum, item) -> sum + item).get());
能够看到reduce方法接受一个函数,这个函数有两个参数,第一个参数是上次函数执行的返回值(也称为中间结果),第二个参数是stream中的元素,这个函数把这两个值相加,获得的和会被赋值给下次执行这个函数的第一个参数。要注意的是:第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素。这个方法返回值类型是Optional,这是Java8防止出现NPE的一种可行方法,后面的文章会详细介绍,这里就简单的认为是一个容器,其中可能会包含0个或者1个对象。 这个过程可视化的结果如图: reduce方法还有一个很经常使用的变种:
T reduce(T identity, BinaryOperator<T> accumulator);
这个定义上上面已经介绍过的基本一致,不一样的是:它容许用户提供一个循环计算的初始值,若是Stream为空,就直接返回该值。并且这个方法不会返回Optional,由于其不会出现null值。下面直接给出例子,就再也不作说明了。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10); System.out.println("ints sum is:" + ints.stream().reduce(0, (sum, item) -> sum + item));
– count方法:获取Stream中元素的个数。比较简单,这里就直接给出例子,不作解释了。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10); System.out.println("ints sum is:" + ints.stream().count());
– allMatch:是否是Stream中的全部元素都知足给定的匹配条件
– anyMatch:Stream中是否存在任何一个元素知足匹配条件
– findFirst: 返回Stream中的第一个元素,若是Stream为空,返回空Optional
– noneMatch:是否是Stream中的全部元素都不知足给定的匹配条件
– max和min:使用给定的比较器(Operator),返回Stream中的最大|最小值
下面给出allMatch和max的例子,剩下的方法读者当成练习。
List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10); System.out.println(ints.stream().allMatch(item -> item < 100)); ints.stream().max((o1, o2) -> o1.compareTo(o2)).ifPresent(System.out::println);
Stream到此告一段落,简单对Stream进行简单的总结:
上文中讲述的Stream都是顺序操做,固然Stream一样支持并行操做,具体可本身查阅 ;
Stream的使用,包括三个环节,同时有几个比较重要的状态须要了解一下。转换Stream,能够认为是Stream的中间状态,jdk描述以下:
Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as filter() does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed.
正如上文中所言,该状态的Stream为了效率起见,并不会当即执行,只有执行到终态时才触发。处于该状态的操做主要为上文中提到的Stream的转换操做。
###参考文献:
http://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html
http://winterbe.com/posts/2014/03/16/java-8-tutorial/
http://ifeve.com/java-8-tutorial-2/
http://www.importnew.com/10360.html
http://ifeve.com/lambda/
http://ifeve.com/stream/
http://ifeve.com/java8/#more-21384
http://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html