文章目录
- java8新特性
- 一,Lambada表达式理解
- 二:接口的默认方法
- 三:Date API
- 五lambda的stream
- 1.colect(Collectors.toList())`:转换成List集合。/ `Collectors.toSet()`:转换成set集合。
- 2.collect(`Collectors.toCollection(TreeSet::new)):转换成特定的set集合
- 3.collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction)):转换成map。
- `4.Collectors.minBy(Integer::compare)`:
- `5.Collectors.averagingInt(x->x)`:
- `6.Collectors.summingInt(x -> x))`:求和。
- `7.Collectors.summarizingDouble(x -> x)`:
- 8. `Collectors.groupingBy(x -> x)`:
- 9.`Collectors.partitioningBy(x -> x > 2)`,
- 10.`Collectors.joining(",")`:拼接字符串。
- `11.`Collectors.collectingAndThen(Collectors.toList(), x -> x.size()):
- `12.Collectors.mapping(...)`:
java8新特性
一,Lambada表达式理解
0.序言:java8中为了支持函数式编程,新增了lambada表达式。
函数式编程的三大特性
immutable data 不可变数据
像Clojure一样,默认上变量是不可变的,如果你要改变变量,你需要把变量copy出去修改。这样一来,可以让你的程序少很多Bug。因为,程序中的状态不好维护,在并发的时候更不好维护。(你可以试想一下如果你的程序有个复杂的状态,当以后别人改你代码的时候,是很容易出bug的,在并行中这样的问题就更多了)
first class functions
这个技术可以让你的函数就像变量一样来使用。也就是说,你的函数可以像变量一样被创建,修改,并当成变量一样传递,返回或是在函数中嵌套函数。这个有点像Javascript的Prototype(参看Javascript的面向对象编程)
尾递归优化
我们知道递归的害处,那就是如果递归很深的话,stack受不了,并会导致性能大幅度下降。所以,我们使用尾递归优化技术——每次递归时都会重用stack,这样一来能够提升性能,当然,这需要语言或编译器的支持。Python就不支持。
函数式编程的几个技术
map & reduce
这个技术不用多说了,函数式编程最常见的技术就是对一个集合做Map和Reduce操作。这比起过程式的语言来说,在代码上要更容易阅读。(传统过程式的语言需要使用for/while循环,然后在各种变量中把数据倒过来倒过去的)这个很像C++中的STL中的foreach,find_if,count_if之流的函数的玩法。
pipeline
这个技术的意思是,把函数实例成一个一个的action,然后,把一组action放到一个数组或是列表中,然后把数据传给这个action list,数据就像一个pipeline一样顺序地被各个函数所操作,最终得到我们想要的结果。
recursing 递归
递归最大的好处就简化代码,他可以把一个复杂的问题用很简单的代码描述出来。注意:递归的精髓是描述问题,而这正是函数式编程的精髓。
currying
把一个函数的多个参数分解成多个函数, 然后把函数多层封装起来,每层函数都返回一个函数去接收下一个参数这样,可以简化函数的多个参数。在C++中,这个很像STL中的bind_1st或是bind2nd。
higher order function 高阶函数
所谓高阶函数就是函数当参数,把传入的函数做一个封装,然后返回这个封装函数。现象上就是函数传进传出,就像面向对象对象满天飞一样。
还有函数式的一些好处
parallelization 并行
所谓并行的意思就是在并行环境下,各个线程之间不需要同步或互斥
lazy evaluation 惰性求值
这个需要编译器的支持。表达式不在它被绑定到变量之后就立即求值,而是在该值被取用的时候求值,也就是说,语句如x:=expression; (把一个表达式的结果赋值给一个变量)明显的调用这个表达式被计算并把结果放置到 x 中,但是先不管实际在 x 中的是什么,直到通过后面的表达式中到 x 的引用而有了对它的值的需求的时候,而后面表达式自身的求值也可以被延迟,最终为了生成让外界看到的某个符号而计算这个快速增长的依赖树。
determinism 确定性
所谓确定性的意思就是像数学那样 f(x) = y ,这个函数无论在什么场景下,都会得到同样的结果,这个我们称之为函数的确定性。而不是像程序中的很多函数那样,同一个参数,却会在不同的场景下计算出不同的结果。所谓不同的场景的意思就是我们的函数会根据一些运行中的状态信息的不同而发生变化。
1.定义:
Lambdad表达式是一种匿名函数,形如
(arg1, arg2...) -> { body }
(type1 arg1, type2 arg2...) -> { body }
(int a, int b) -> { return a + b; }
() -> System.out.println("Hello World");
(String s) -> { System.out.println(s); }
() -> 42
() -> { return 3.1415 };
//一般常见的写法是在 thread中去写
new thread(()-> System.out.println("hello,I am lambada"))
//lombda里面也有返回值可以用return去进行返回,这个就和java8之前的里面函数一样,采用内部类的方式去处理这个问题
public class LambadaExample {
public static void main(String[] args) {
//不使用lamba表达式
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello,I am lambada");
}
}).start();
//使用lambada表达式
new Thread(() -> System.out.println("hello,I am lambada")).start();
//lambada的执行代码有多行
new Thread( () -> {
try {
TimeUnit.SECONDS.sleep(5);
System.out.println("I am also lambada");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//有参数的lambada表达式
Consumer<Integer> consumer = (i) -> System.out.println(i);
consumer.accept(666);
}
}
2:简述
Runnable runnable = () -> {
System.out.println("test");
};
runnable.run();
这是一段非常简单的Lambda表达式。如果理解起来比较困难的话,他可以完全的转化为如下代码。
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("test");
}
};
runnable.run();
Runnable是实现多线程的一个常用方法
但是我们现在用到的不是他的这个特性。
我们知道,接口是不能够被直接实例化的。所以我们如果想对一个接口进行实例化的时候,必须要给他一个实现类。
那么我们转化后的代码可以看出,我们在 “ = ” 的后面实现实现了一个匿名实现类,用来实现Runnable接口的类。
然后将这个实现类赋给了runnable对象。
让我们回到上面的代码。我们可以看出Lambda所做的事情,和转化后的代码所做的事是一样的。
所以个人理解:Lambda表达式可以用来为接口简便的生成一个实现类。
在Lambda表达式中我们可以看出方法体就是代表着Runnable接口中run方法的方法体。
那么这个时候包括我自己刚刚学习的时候也会有一个疑问,凭什么它就代表run的方法体呢?如果有其他的方法呢?
这个时候Lambda表达式的一个特性,或者说是要求完美的解决了这个问题:
Lambda表达式只能用来实例化单抽象方法的接口 —> “函数式接口”。
这样问题就解决了,我的接口里只有一个方法,那么当然,方法体就是写给他的啊。
那么接下来我简单的说一下Lambda表达式的语法。
3:语法
Lambda表达式一般形式如下:
()->{}
<函数式接口> <变量名> = (参数1,参数2...) -> {
//方法体
}
()->{}
():参数
->:固定箭头写法
{}:方法体
(参数1,参数2…)表示参数列表;->表示连接符;{}内部是方法体
1、=右边的类型会根据左边的函数式接口类型自动推断;
2、如果形参列表为空,只需保留();
3、如果形参只有1个,()可以省略,只需要参数的名称即可;
4、如果执行语句只有1句,且无返回值,{}可以省略,若有返回值,则若想省去{},则必须同时省略return,且执行语句也保证只有1句;
5、形参列表的数据类型会自动推断;
6、lambda不会生成一个单独的内部类文件;
7、lambda表达式若访问了局部变量,则局部变量必须是final的,若是局部变量没有加final关键字,系统会自动添加,此后在修改该局部变量,会报错;
Runnable runnable = () -> {
System.out.println("test");
};
runnable.run();
如同上方的写法,我们知道run方法是无参的,所以括号里只需要置空即可。
Consumer consumer = (x) -> {};
//只有一个参数,可以省略参数的括号
Consumer consumer1 = x -> {};
//两个参数
BiConsumer biConsumer = (x, y) -> {};
JDK1.8之后为了我们使用Lambda表达式方便,官方增加了许多这样的单方法接口。
上方代码的两个类都是单方法接口,只不过一个是接单参,一个是接双参
但是这里有一点:如果方法仅有一个参数的时候,括号可以被省略。
另外,如上图的x,y,参数类型是可以被省略的。
如果方法体只有一行语句,并且此语句为返回值时,方法体的大括号可以被省略,例子如下:
Function function = (x) -> {
return "abc";
};
//只有一行一句,并且其为返回值,方法体大括号可省略
Function function1 = (x) -> "abc";
4.结合引用方法
方法的引用大致分为如下几类:(这些类型的前一个代码均是未优化的,后一个代码均是优化后的)
0.语法
<函数式接口> <变量名> = <实例>::<实例方法名>
//调用
<变量名>.接口方法([实际参数...])
将调用方法时的传递的实际参数,全部传递给引用的方法,执行引用的方法;
示例代码:
如我们引用PrintStream类中的println方法。我们知道System类中有一个PrintStream的实例为out,引用该实例方法:System.out::println:
public class Main {
public static void main(String[] args) {
LambdaTest2 lt1 = s-> System.out.println(s);
lt1.print("有一个参数");
//改写为:
LambdaTest2 lt2 = System.out::println;
lt2.print("实例引用方式调用");
}
}
1、静态方法的引用
语法
<函数式接口> <变量名> = <类>::<类方法名称>
//调用
<变量名>.接口方法([实际参数...])
public class Example {
public static void main(String[] args) {
//常规写法
Consumer<String> consumer = x -> {
Example.say(x);
};
consumer.accept("abc");
}
private static void say(String o) {
System.out.println(o);
}
}
public class Example {
public static void main(String[] args) {
//在上方代码中,我们的Lambda表达式的内容仅仅是用来调用Example的say静态方法
//另外我们Lambda的参数正好是say所需要的参数
//那么我们就可以改成如下这样
Consumer<String> consumer = Example::say;
consumer.accept("abc");
}
private static void say(String o) {
System.out.println(o);
}
}
2、实例方法的引用
语法
//定义接口
interface <函数式接口>{
<返回值> <方法名>(<类><类名称>,[其他参数...]);
}
<函数式接口> <变量名> = <类>::<类实例方法名>
//调用
<变量名>.接口方法(类的实例,[实际参数...])
将调用方法时的传递的实际参数,从第二个参数开始(第一个参数指定的类的实例),全部传递给引用的方法,执行引用的方法;
示例代码:
public class LambdaClassTest {
public int add(int a, int b){
System.out.println("LambdaClassTest类的add方法");
return a+b;
}
}
public interface LambdaTest4 {
abstract int add(LambdaClassTest lt,int a,int b);
}
public class Main {
public static void main(String[] args) {
LambdaTest4 lt4 = LambdaClassTest::add;
LambdaClassTest lct = new LambdaClassTest();
System.out.println(lt4.add(lct, 5, 8));
}
}
public class Example {
public static void main(String[] args) {
Example e = new Example();
//未经过转变的代码
Consumer<String> consumer = x -> {
e.say(x);
};
consumer.accept("abc");
}
private void say(String o) {
System.out.println(o);
}
}
public class Example {
public static void main(String[] args) {
Example e = new Example();
//我们仅仅调用了e实例的say方法,并且lambda接受的参数与方法相同
//可以转化为如下代码
Consumer<String> consumer = e::say;
consumer.accept("abc");
}
private void say(String o) {
System.out.println(o);
}
}
3、对象方法的引用
public class Example {
public static void main(String[] args) {
BiConsumer<Example, String> consumer = (Example e, String x) -> {
e.say(x);
};
consumer.accept(new Example(), "abc");
}
private void say(String o) {
System.out.println(o);
}
}
public class Example {
public static void main(String[] args) {
//当我们对Lambda表达式传递多个参数的时候
//我们如果想通过Lambda表达式第二个及以后的参数作为
//Lambda表达式第一个参数所持有的方法的参数的话,可以作出如下变化。
BiConsumer<Example, String> consumer = Example::say;
consumer.accept(new Example(), "abc");
}
private void say(String o) {
System.out.println(o);
}
}
4、构造方法的引用
语法
<函数式接口> <变量名> = <类>::<new>
//调用
<变量名>.接口方法([实际参数...])
public class Example {
private String content;
public static void main(String[] args) {
//未经过转化的代码
Consumer<String> consumer = (x) -> {
new Example(x);
};
}
Example(String content){
this.content = content;
}
}
public class Example {
private String content;
public static void main(String[] args) {
//如果这个方法仅仅是用来调用一个构造函数的话,并且lambda表达式的参数与构造方法相同
//则可以转化为如下代码
Consumer<String> consumer = Example::new;
}
Example(String content){
this.content = content;
}
}
从中我们可以看出,使用方法引用的最关键的地方就是调用的方法参数与我们Lambda表达式的参数相同。
只有这样我们才可以将Lambda表达式与方法引用结合在一起,编写出更简洁的代码(当然也更难以理解)。
5.函数式接口
如果一个接口只有一个抽象方法,则该接口称之为函数式接口,因为 默认方法 不算抽象方法,所以你也可以给你的函数式接口添加默认方法。
函数式接口可以使用Lambda表达式,lambda表达式会被匹配到这个抽象方法上
我们可以将lambda表达式当作任意只包含一个抽象方法的接口类型,确保你的接口一定达到这个要求,你只需要给你的接口添加 @FunctionalInterface 注解,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的
示例代码:
@FunctionalInterface
interface Converter<F, T> {
T convert(F from);
}
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted); // 123
6.Lambda 作用域
在lambda表达式中访问外层作用域和老版本的匿名对象中的方式很相似。你可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量
7.访问局部变量
我们可以直接在lambda表达式中访问外层的局部变量,但是该局部变量必须是final的,即使没有加final关键字(也会字自动添加final),之后我们无论在哪(lambda表达式内部或外部)修改该变量,均报错。
8.访问对象字段与静态变量
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);
};
}
}
9、访问接口的默认方法
Predicate接口
Predicate 接口只有一个参数,返回boolean类型。该接口包含多种默认方法来将Predicate组合成其他复杂的逻辑(比如:与,或,非):
public static void main(String[] args) {
Predicate<String> predicate = (s) -> s.length() > 0;
System.out.println(predicate.test("foo")); // true
System.out.println(predicate.negate().test("foo")); // false
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
System.out.println(nonNull.test(null));
System.out.println(isNull.test(null));
System.out.println(isEmpty.test("sss"));
System.out.println(isNotEmpty.test(""));
}
运行结果:
true
false
false
true
false
false
Function 接口
Function 接口有一个参数并且返回一个结果,并附带了一些可以和其他函数组合的默认方法(compose, andThen):
Function<String, Integer> toInteger = Integer::valueOf;
System.out.println(toInteger.apply("123").getClass());
Function<String, Object> toInteger2 = toInteger.andThen(String::valueOf);
System.out.println(toInteger2.apply("123").getClass());
输出:
class java.lang.Integer
class java.lang.String
Supplier 接口
Supplier 接口返回一个任意范型的值,和Function接口不同的是该接口没有任何参数
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
Consumer 接口
Consumer 接口表示执行在单个参数上的操作。接口只有一个参数,且无返回值
Supplier<LambdaClassTest> personSupplier = LambdaClassTest::new;
Consumer<LambdaClassTest> greeter = (lt) -> System.out.println("Hello, " + lt.getTest());
greeter.accept(personSupplier.get());
Comparator 接口
Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法:
Comparator<Person> comparator = (p1, p2) -> p1.firstName.compareTo(p2.firstName);
Person p1 = new Person("John", "Doe");
Person p2 = new Person("Alice", "Wonderland");
comparator.compare(p1, p2); // > 0
comparator.reversed().compare(p1, p2); // < 0
Optional 接口
Optional 不是函数是接口,这是个用来防止NullPointerException异常的辅助类型,这是下一届中将要用到的重要概念,现在先简单的看看这个接口能干什么:
Optional 被定义为一个简单的容器,其值可能是null或者不是null。在Java 8之前一般某个函数应该返回非空对象但是偶尔却可能返回了null,而在Java 8中,不推荐你返回null而是返回Optional。
Optional<String> optional = Optional.of("bam");
optional.isPresent(); // true
optional.get(); // "bam"
optional.orElse("fallback"); // "bam"
optional.ifPresent((s) -> System.out.println(s.charAt(0))); // "b"
Stream 接口 重要!!!
创建stream通过of方法
Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
Stream<String> stringStream = Stream.of("taobao");
创建stream–通过generator方法
生成一个无限长度的Stream,其元素的生成是通过给定的Supplier(这个接口可以看成一个对象的工厂,每次调用返回一个给定类型的对象)
Stream.generate(new Supplier<Double>() {
@Override
public Double get() {
return Math.random();
}
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);
三条语句的作用都是一样的,只是使用了lambda表达式和方法引用的语法来简化代码。每条语句其实都是生成一个无限长度的Stream,其中值是随机的。这个无限长度Stream是懒加载,一般这种无限长度的Stream都会配合Stream的limit()方法来用。
创建stream–通过iterate方法
也是生成无限长度的Stream,和generator不同的是,其元素的生成是重复对给定的种子值(seed)调用用户指定函数来生成的。其中包含的元素可以认为是:seed,f(seed),f(f(seed))无限循环
Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::println);
这段代码就是先获取一个无限长度的正整数集合的Stream,然后取出前10个打印。千万记住使用limit方法,不然会无限打印下去。
通过Collection子类获取Stream
public interface Collection<E> extends Iterable<E> {
//其他方法省略
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
}
java.util.Stream 表示能应用在一组元素上一次执行的操作序列。Stream 操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样你就可以将多个操作依次串起来。Stream 的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, Map不支持。Stream的操作可以串行执行或者并行执行。
Java 8扩展了集合类,可以通过 Collection.stream() 或者 Collection.parallelStream() 来创建一个Stream。
Stream有串行和并行两种,串行Stream上的操作是在一个线程中依次完成,而并行Stream则是在多个线程上同时执行。
下面的例子展示了是如何通过并行Stream来提升性能:
首先我们创建一个没有重复元素的大表:
int max = 1000000;
List<String> values = new ArrayList<>(max);
for (int i = 0; i < max; i++) {
UUID uuid = UUID.randomUUID();
values.add(uuid.toString());
}
然后我们计算一下排序这个Stream要耗时多久,
串行排序:
long t0 = System.nanoTime();
long count = values.stream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("sequential sort took: %d ms", millis));
// 串行耗时: 899 ms
并行排序:
long t0 = System.nanoTime();
long count = values.parallelStream().sorted().count();
System.out.println(count);
long t1 = System.nanoTime();
long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
System.out.println(String.format("parallel sort took: %d ms", millis));
// 并行排序耗时: 472 ms
上面两个代码几乎是一样的,但是并行版的快了50%之多,唯一需要做的改动就是将stream()改为parallelStream();
stream的其他应用:
**1、count()、max()、min()**方法
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> collection = new ArrayList<Integer>();
collection.add(14);
collection.add(5);
collection.add(43);
collection.add(89);
collection.add(64);
collection.add(112);
collection.add(55);
collection.add(55);
collection.add(58);
//list长度
System.out.println(collection.parallelStream().count());
//求最大值,返回Option,通过Option.get()获取值
System.out.println(collection.parallelStream().max((a,b)->{return a-b;}).get());
//求最小值,返回Option,通过Option.get()获取值
System.out.println(collection.parallelStream().min((a,b)->{return a-b;}).get());
}
}
2、Filter 过滤方法
过滤通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> collection = new ArrayList<Integer>();
collection.add(14);
collection.add(5);
collection.add(43);
collection.add(89);
collection.add(64);
collection.add(112);
collection.add(55);
collection.add(55);
collection.add(58);
Long count =collection.stream().filter(num -> num!=null).
filter(num -> num.intValue()>50).count();
System.out.println(count);
}
}
3、distinct方法
去除重复
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<Integer> collection = new ArrayList<Integer>();
collection.add(14);
collection.add(5);
collection.add(43);
collection.add(89);
collection.add(64);
collection.add(112);
collection.add(55);
collection.add(55);
collection.add(58);
collection.stream().distinct().forEach(System.out::println);;
}
}
4、Sort 排序
排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
stringCollection
.stream()
.sorted()
.filter((s) -> s.startsWith("a"))
.forEach(System.out::println);
// "aaa1", "aaa2"
需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据stringCollection是不会被修改的:
System.out.println(stringCollection);
// ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1
5、Map 映射
对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> collection = new ArrayList<String>();
collection.add("14");
collection.add("5");
collection.add("43");
collection.add("89");
collection.add("64");
collection.add("112");
collection.add("55");
collection.add("55");
collection.add("58");
//将String转化为Integer类型
collection.stream().mapToInt(Integer::valueOf).forEach(System.out::println);
//或
collection.stream().mapToInt(a->Integer.parseInt(a)).forEach(System.out::println);
}
}
也可以这样用:
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());
7、limit:
对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;
8、skip:
返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;
9、Match 匹配
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。
boolean anyStartsWithA =
stringCollection
.stream()
.anyMatch((s) -> s.startsWith("a"));
System.out.println(anyStartsWithA); // true
boolean allStartsWithA =
stringCollection
.stream()
.allMatch((s) -> s.startsWith("a"));
System.out.println(allStartsWithA); // false
boolean noneStartsWithZ =
stringCollection
.stream()
.noneMatch((s) -> s.startsWith("z"));
System.out.println(noneStartsWithZ); // true
10、Count 计数
计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。
long startsWithB =
stringCollection
.stream()
.filter((s) -> s.startsWith("b"))
.count();
System.out.println(startsWithB); // 3
11、Reduce 规约
这是一个最终操作,允许通过指定的函数来讲stream中的多个元素规约为一个元素,规越后的结果是通过Optional接口表示的:
Optional<String> reduced =
stringCollection
.stream()
.sorted()
.reduce((s1, s2) -> s1 + "#" + s2);
reduced.ifPresent(System.out::println);
// "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"
Map
前面提到过,Map类型不支持stream,不过Map提供了一些新的有用的方法来处理一些日常任务。
Map<Integer, String> map = new HashMap<>();
for (int i = 0; i < 10; i++) {
map.putIfAbsent(i, "val" + i);
}
map.forEach((id, val) -> System.out.println(val));
以上代码很容易理解, putIfAbsent 不需要我们做额外的存在性检查,而forEach则接收一个Consumer接口来对map里的每一个键值对进行操作。
下面的例子展示了map上的其他有用的函数:
map.computeIfPresent(3, (num, val) -> val + num);
map.get(3); // val33
map.computeIfPresent(9, (num, val) -> null);
map.containsKey(9); // false
map.computeIfAbsent(23, num -> "val" + num);
map.containsKey(23); // true
map.computeIfAbsent(3, num -> "bam");
map.get(3); // val33
接下来展示如何在Map里删除一个键值全都匹配的项:
map.remove(3, "val3");
map.get(3); // val33
map.remove(3, "val33");
map.get(3); // null
另外一个有用的方法:
map.getOrDefault(42, "not found"); // not found
对Map的元素做合并也变得很容易了:
map.merge(9, "val9", (value, newValue) -> value.concat(newValue));
map.get(9); // val9
map.merge(9, "concat", (value, newValue) -> value.concat(newValue));
map.get(9); // val9concat
Merge做的事情是如果键名不存在则插入,否则则对原键对应的值做合并操作并重新插入到map中。
steam在实际项目中使用的代码片段:
//1、有list集合生成以productId为key值得map集合
Map<String, List<CartManager>> cartManagerGroup =
carts.stream().collect(
Collectors.groupingBy(CartManager::getProductId)
);
//2、取得购物车中数量之和
IntStream is = list.stream().mapToInt((CartManager c)->c.getQuantity());
is.sum();//数量之和
//3、所有订单中商品数量*订单金额求和
orderDetailsNew.parallelStream()
.mapToDouble(orderDetailMid -> orderDetailMid.getQuantity()*orderDetailMid.getFinalPrice()).sum()
//4、过滤出指定类型的订单,并生成新的集合
orderDetails.stream().
filter(orderDetail -> StringUtil.isEmpty(orderDetail.getPromotionsType())|| !orderDetail.getPromotionsType().equals(PromotionTypeEnum.ORDERGIFTPROMOTION.getType())).collect(Collectors.toList());
//5、过滤购物车未被选中商品并生成新的list
carts.stream().filter(cart -> cart.getSelectFlag()==1).collect(Collectors.toList());
//6、将list以商品促销委key转化为map
Map<String,List<PromotionsGiftProduct>> map =
promotionsGiftProducts.stream().collect( Collectors.groupingBy(PromotionsGiftProduct::getPromotionId));
//7、从list<Cart>中分离出只存储productId的列表list<String>
List<String> productIds = needUpdate.parallelStream()
.map(CartManager::getProductId)
.collect(Collectors.toList());
二:接口的默认方法
Java8在接口中新增了default方法和static方法,这两种方法可以有方法体
1、static方法
public interface DefalutTest {
static int a =5;
default void defaultMethod(){
System.out.println("DefalutTest defalut 方法");
}
int sub(int a,int b);
static void staticMethod() {
System.out.println("DefalutTest static 方法");
}
}
接口里的静态方法,即static修饰的有方法体的方法不会被继承或者实现,但是静态变量会被继承
例如:我们添加一个接口DefalutTest的实现类DefaultTestImpl
public class DefaultTestImpl implements DefalutTest{
@Override
public int sub(int a, int b) {
// TODO Auto-generated method stub
return a-b;
}
}
接口中的static方法不能被继承,也不能被实现类调用,只能被自身调用
2、default方法
准备一个子接口继承DefalutTest接口
public interface SubTest extends DefalutTest{
}
准备一个子接口的实现类
public class SubTestImp implements SubTest{
@Override
public int sub(int a, int b) {
// TODO Auto-generated method stub
return a-b;
}
}
现在我们创建一个子接口实现类对象,并调用对象中的default方法:
public class Main {
public static void main(String[] args) {
SubTestImp stl = new SubTestImp();
stl.defaultMethod();
}
}
执行结果:
DefalutTest defalut 方法
结论1:default方法可以被子接口继承亦可被其实现类所调用
现在我们在子接口中重写default方法,在进行调用:
public interface SubTest extends DefalutTest{
default void defaultMethod(){
System.out.println("SubTest defalut 方法");
}
}
执行结果:SubTest defalut 方法
结论2:default方法被继承时,可以被子接口覆写
现在,我们去除接口间的继承关系,并使得SubTestImp同时实现父接口和子接口,我们知道此时父接口和子接口中存在同名同参数的default方法,这会怎么样?
如下图所示,实现类报错,实现类要求必须指定他要实现那个接口中的default方法
结论3:如果一个类实现了多个接口,且这些接口中无继承关系,这些接口中若有相同的(同名,同参数)的default方法,则接口实现类会报错,接口实现类必须通过特殊语法指定该实现类要实现那个接口的default方法
特殊语法:<接口>.super.<方法名>([参数])
示例代码:
public class SubTestImp implements SubTest,DefalutTest{
@Override
public int sub(int a, int b) {
// TODO Auto-generated method stub
return a-b;
}
@Override
public void defaultMethod() {
// TODO Auto-generated method stub
DefalutTest.super.defaultMethod();
}
}
三:Date API
Java 8 在包java.time下包含了一组全新的时间日期API。
Clock 时钟
Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代 System.currentTimeMillis() 来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。
Clock clock = Clock.systemDefaultZone();
long millis = clock.millis();
Instant instant = clock.instant();
Date legacyDate = Date.from(instant); // legacy java.util.Date
Timezones 时区
System.out.println(ZoneId.getAvailableZoneIds());
// prints all available timezone ids
ZoneId zone1 = ZoneId.of("Europe/Berlin");
ZoneId zone2 = ZoneId.of("Brazil/East");
System.out.println(zone1.getRules());
System.out.println(zone2.getRules());
// ZoneRules[currentStandardOffset=+01:00]
// ZoneRules[currentStandardOffset=-03:00]
LocalTime 本地时间
LocalTime 定义了一个没有时区信息的时间,例如 晚上10点,或者 17:30:15。下面的例子使用前面代码创建的时区创建了两个本地时间。之后比较时间并以小时和分钟为单位计算两个时间的时间差:
LocalTime now1 = LocalTime.now(zone1);
LocalTime now2 = LocalTime.now(zone2);
System.out.println(now1.isBefore(now2)); // false
long hoursBetween = ChronoUnit.HOURS.between(now1, now2);
long minutesBetween = ChronoUnit.MINUTES.between(now1, now2);
System.out.println(hoursBetween); // -3
System.out.println(minutesBetween); // -239
LocalTime 提供了多种工厂方法来简化对象的创建,包括解析时间字符串。
LocalTime late = LocalTime.of(23, 59, 59);
System.out.println(late); // 23:59:59
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedTime(FormatStyle.SHORT)
.withLocale(Locale.GERMAN);
LocalTime leetTime = LocalTime.parse("13:37", germanFormatter);
System.out.println(leetTime); // 13:37
LocalDate 本地日期
LocalDate 表示了一个确切的日期,比如 2014-03-11。该对象值是不可变的,用起来和LocalTime基本一致。下面的例子展示了如何给Date对象加减天/月/年。另外要注意的是这些对象是不可变的,操作返回的总是一个新实例。
LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS);
LocalDate yesterday = tomorrow.minusDays(2);
LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4);
DayOfWeek dayOfWeek = independenceDay.getDayOfWeek();
System.out.println(dayOfWeek); // FRIDAY
从字符串解析一个LocalDate类型和解析LocalTime一样简单:
DateTimeFormatter germanFormatter =
DateTimeFormatter
.ofLocalizedDate(FormatStyle.MEDIUM)
.withLocale(Locale.GERMAN);
LocalDate xmas = LocalDate.parse("24.12.2014", germanFormatter);
System.out.println(xmas); // 2014-12-24
LocalDateTime 本地日期时间
LocalDateTime 同时表示了时间和日期,相当于前两节内容合并到一个对象上了。LocalDateTime和LocalTime还有LocalDate一样,都是不可变的。LocalDateTime提供了一些能访问具体字段的方法。
LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59);
DayOfWeek dayOfWeek = sylvester.getDayOfWeek();
System.out.println(dayOfWeek); // WEDNESDAY
Month month = sylvester.getMonth();
System.out.println(month); // DECEMBER
long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY);
System.out.println(minuteOfDay); // 1439
只要附加上时区信息,就可以将其转换为一个时间点Instant对象,Instant时间点对象可以很容易的转换为老式的java.util.Date。
Instant instant = sylvester
.atZone(ZoneId.systemDefault())
.toInstant();
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014
格式化LocalDateTime和格式化时间和日期一样的,除了使用预定义好的格式外,我们也可以自己定义格式:
DateTimeFormatter formatter =
DateTimeFormatter
.ofPattern("MMM dd, yyyy - HH:mm");
LocalDateTime parsed = LocalDateTime.parse("Nov 03, 2014 - 07:13", formatter);
String string = formatter.format(parsed);
System.out.println(string); // Nov 03, 2014 - 07:13
和java.text.NumberFormat不一样的是新版的DateTimeFormatter是不可变的,所以它是线程安全的。
五lambda的stream
1.colect(Collectors.toList()):转换成List集合。/
Collectors.toSet()`:转换成set集合。
System.out.println(Stream.of("a", "b", "c","a").collect(Collectors.toSet()));
//结果[a, b, c]
2.collect(`Collectors.toCollection(TreeSet::new)):转换成特定的set集合
TreeSet<String> treeSet = Stream.of("a", "c", "b", "a").collect(Collectors.toCollection(TreeSet::new));
System.out.println(treeSet);
//结果[a, b, c]
3.collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction)):转换成map。
Map<String, String> collect = Stream.of("a", "b", "c", "a").collect(Collectors.toMap(x -> x, x -> x + x,(oldVal, newVal) -> newVal)));
collect.forEach((k,v) -> System.out.println(k + ":" + v));
//结果:{a=aa, b=bb, c=cc}
语法解析
.collect(Collectors.toMap(UserBo::getUserId, v -> v, (v1, v2) -> v1));
第一个参数UserBo::getUserId 表示选择UserBo的getUserId作为map的key值;
第二个参数v -> v表示选择将原来的对象作为map的value值;
第三个参数(v1, v2) -> v1中,如果v1与v2的key值相同,选择v1作为那个key所对应的value值
例子
ProValue proValue1=new ProValue();
ProValue proValue2=new ProValue();
ProValue proValue3=new ProValue();
ProValue proValue4=new ProValue();
ProValue proValue5=new ProValue();
proValue1.setId("1");
proValue1.setName("凯");
proValue1.setAge("男");
proValue2.setId("1");
proValue2.setName("熊");
proValue2.setAge("女");
proValue3.setId("2");
proValue3.setName("婷");
proValue3.setAge("女");
proValue4.setId("2");
proValue4.setName("威");
proValue4.setAge("男");
proValue5.setId("2");
proValue5.setName("威2");
proValue5.setAge("男");
List<ProValue> proPlanTime =new ArrayList<>();
proPlanTime.add(proValue1);
proPlanTime.add(proValue2);
proPlanTime.add(proValue3);
proPlanTime.add(proValue4);
Map<String ,ProValue> collect1=proPlanTime.stream().collect(Collectors.toMap(ProValue::getId,a->a,(a1,a2)->a2));
//结果{1=ProValue(id=1, name=熊, age=女), 2=ProValue(id=2, name=威2, age=男)}
解析:首先进行对list对象进行流处理(stream),然后转化为map对象,转化规则为,采用Provalue的Id作为key,这个对象作为value ,如果遇到key重复,则采用第一个
此处有一个问题,就是在toMap里面的前两个参数是lambda表达式的写法,lambda实质是实现一个借口,但是在上述式子明显就是没有返回一个借口类型,这是在toMap的底层实现了Function()这个接口
补充
关于合并函数 BinaryOperator mergeFunction
对象``
当toMap中没有用合并函数时,出现key重复时,会抛出异常 : Exception in thread “main” java.lang.IllegalStateException: Duplicate key aa
当使用合并函数时,可通过Labmda表达式,对重复值进行处理
因为出现重复值会直接抛出异常,所以一定要对这个问题进行处理
4.Collectors.minBy(Integer::compare)
:
求最小值,相对应的当然也有maxBy方法。
5.Collectors.averagingInt(x->x)
:
求平均值,同时也有averagingDouble、averagingLong方法。
6.Collectors.summingInt(x -> x))
:求和。
7.Collectors.summarizingDouble(x -> x)
:
可以获取最大值、最小值、平均值、总和值、总数。
DoubleSummaryStatistics summaryStatistics = Stream.of(1, 3, 4).collect(Collectors.summarizingDouble(x -> x));
System.out.println(summaryStatistics .getAverage());
8. Collectors.groupingBy(x -> x)
:
有三种方法,查看源码可以知道前两个方法最终调用第三个方法, 第二个参数默认HashMap::new
第三个参数默认Collectors.toList()
Map<Integer, List<Integer>> map = Stream.of(1, 3, 3, 2).collect(Collectors.groupingBy(Function.identity()));
System.out.println(map);
//
Map<Integer, Integer> map1 = Stream.of(1, 3, 3, 2).collect(Collectors.groupingBy(Function.identity(), Collectors.summingInt(x -> x)));
System.out.println(map1);
HashMap<Integer, List<Integer>> hashMap = Stream.of(1, 3, 3, 2).collect(Collectors.groupingBy(Function.identity(), HashMap::new, Collectors.mapping(x -> x + 1, Collectors.toList())));
System.out.println(hashMap);
//结果
{1=[1], 2=[2], 3=[3, 3]}
{1=1, 2=2, 3=6}
{1=[2], 2=[3], 3=[4, 4]}
//注意这个key不是下标,而是这个list对应的1234
外充:identity()是Function类的静态方法,和x->x是一个意思,当仅仅需要自己返回自己时,使用identity()能更清楚的表达作者的意思.
写的复杂一点,绕一点,对理解很有好处.下边是运行结果:
9.Collectors.partitioningBy(x -> x > 2)
,
把数据分成两部分,key为ture/false
。第一个方法也是调用第二个方法,第二个参数默认为Collectors.toList()
Map<Boolean, List<Integer>> map = Stream.of(1, 3, 3, 2).collect(Collectors.partitioningBy(x -> x > 2));
Map<Boolean, Long> longMap = Stream.of(1, 3, 3, 2).collect(Collectors.partitioningBy(x -> x > 1, Collectors.counting()));
//结果
{false=[1, 2], true=[3, 3]}
{false=1, true=3}
//Collectors.counting()计数
10.Collectors.joining(",")
:拼接字符串。
System.out.println(Stream.of("1", "3", "3", "2").collect(Collectors.joining(",")));、
//结果1,3,3,2
11.
Collectors.collectingAndThen(Collectors.toList(), x -> x.size()):
先执行collect操作后再执行第二个参数的表达式。这里是先塞到集合,再得出集合长度。
Integer integer = Stream.of("1", "2", "3").collect(Collectors.collectingAndThen(Collectors.toList(), x -> x.size()));
//结果:3
//执行顺序Collectors.toList()-》x -> x.size()
12.Collectors.mapping(...)
:
跟Stream的map操作类似,只是参数有点区别
System.out.println(Stream.of(1, 3, 5).collect(Collectors.mapping(x -> x + 1, Collectors.toList())));