继续着上次的java完全解读一
1.强大的Stream API
1.1什么是Stream
1.2 Stream操作的三大步骤
1.2.1 创建Stream
1.2.2 Stream的中间操作
筛选和切片
映射
排序
1.2.3 Stream 的终止操作
查找与匹配
归约
收集
2 新时间日期API
2.1 使用LocalDate、LocalTime、LocalDateTime
2.2 Duration 和Period
2.3 解析与格式化
2.4 时区的处理
3 接口中的默认方法与静态方法
3.1 接口中的默认方法
3.1.1 默认方法的使用
3.1.2 接口默认方法的”类优先”原则
3.1.3 接口默认方法的"接口冲突"
3.2 接口中的静态方法
1.强大的Stream API
Stream 是Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用SQL 执行的数据库查询。也可以使用Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
1.1什么是Stream
流(Stream)是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”
注意:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
1.2 Stream操作的三大步骤
- 创建Stream
一个数据源(如:集合、数组),获取一个流 - 中间操作
一个中间操作链,对数据源的数据进行处理 - 终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果
1.2.1 创建Stream
创建Stream严格来说有三种方式
- Java8 中的Collection 接口被扩展,提供了两个获取流的方法:
//Collection的方法
default Stream<E> stream() : 返回一个顺序流
default Stream<E> parallelStream() : 返回一个并行流
- Java8 中的Arrays的静态方法stream() 可以获取数组流:
- 可以使用静态方法Stream.of(), 通过显示值创建一个流。它可以接收任意数量的参数
//Arrays的方法
static <T> Stream<T> stream(T[] array);// 返回一个流
//Stream的方法
public static<T> Stream<T> of(T… values);// 返回一个流
- 可以使用静态方法Stream.iterate() 和Stream.generate(), 创建无限流。
//迭代
public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
//生成
public static<T> Stream<T> generate(Supplier<T> s) :
/*
*UnaryOperator是传入类型T返回类型T
* Supplier是无参,返回类型T
*/
实际运用
public void test1(){
/**
* list中的数据本来是用List包裹,创建stream后,将list中的数据换成了stream包裹了,
* 同时对流中的数据操作变得更加方便
*/
//1. Collection 提供了两个方法 stream() 与 parallelStream()
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream(); //获取一个顺序流
Stream<String> parallelStream = list.parallelStream(); //获取一个并行流
//2. 通过 Arrays 中的 stream() 获取一个数组流
Integer[] nums = new Integer[10];
Stream<Integer> stream1 = Arrays.stream(nums);
//通过 Stream 类中静态方法 of()底层也是调用
Stream<Integer> stream2 = Stream.of(1,2,3,4,5,6);
//3.创建无限流,这两个底层调用的都是一样的
//迭代
Stream<Integer> stream3 = Stream.iterate(0, (x) -> x + 2).limit(10);
stream3.forEach(System.out::println);
//生成
Stream<Double> stream4 = Stream.generate(Math::random).limit(2);
stream4.forEach(System.out::println);
}
1.2.2 Stream的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为“惰性求值”。
筛选和切片
运用
//筛选年龄<=35岁的员工
@Test
public void test2(){
//所有的中间操作不会做任何的处理
Stream<Employee> stream = emps.stream()
.filter((e) -> {
System.out.println("测试中间操作");
return e.getAge() <= 35;
});
//只有当做终止操作时,所有的中间操作会一次性的全部执行,称为“惰性求值”
stream.forEach(System.out::println);
}
//查询薪水>=5000的前三个员工
@Test
public void test4(){
emps.stream()
.filter((e) -> {
return e.getSal() >= 5000;
}).limit(3)
.forEach(System.out::println);
}
//查询薪水>=5000的员工,去掉前两个
@Test
public void test5(){
emps.stream()
.filter((e) -> e.getSal()>= 5000)
.skip(2)//跳过2元素,返回一个扔掉了前2个元素的流
.forEach(System.out::println);
}
//去重
@Test
public void test6(){
emps.stream()
.distinct()
.forEach(System.out::println);
}
映射
实例运用
- map
@Test
public void test1() {
List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
//将传入的String值转为大写并返回String
Stream<String> stream = strList.stream()
.map(String::toUpperCase);
stream.forEach(System.out::println);
//传入的String,通过方法将String转化成包裹字符的stream了
Stream<Stream<Character>> stream2 = strList.stream()
.map(Stream2::filterCharacter);
stream2.forEach((sm) -> {
sm.forEach(System.out::println);
});
}
public static Stream<Character> filterCharacter(String str) {
List<Character> list = new ArrayList<>();
for (Character ch : str.toCharArray()) {
list.add(ch);
}
return list.stream();
}
- flatMap
//flatmap
@Test
public void Test3() {
List<String> strList = Arrays.asList("aaa", "bbb", "ccc", "ddd", "eee");
Stream<Character> stream3 = strList.stream()
.flatMap(Stream2::filterCharacter);
stream3.forEach(System.out::println);
}
排序
实际运用
@Test
public void test3() {
emps.stream()
.map(Employee::getName)
.sorted()
.forEach(System.out::println);
emps.stream()//按年龄排序,如果年龄相同:按名字排序
.sorted((x, y) -> {
if (x.getAge() == y.getAge()) {
return x.getName().compareTo(y.getName());
} else {
return Integer.compare(x.getAge(), y.getAge());
}
}).forEach(System.out::println);
}
1.2.3 Stream 的终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如:List、Integer,甚至是void 。
查找与匹配
归约
备注:map 和reduce 的连接通常称为map-reduce 模式,因Google 用它来进行网络搜索而出名。
使用
@Test
public void test1(){
//将数组中的值加起来
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
Integer sum = list.stream()
.reduce(0, (x, y) -> x + y);
System.out.println(sum);
//使用map将员工的工资提取出来,使用reduce将工资累加
Optional<Double> op = emps.stream()
.map(Employee::getSal)
.reduce(Double::sum);
System.out.println(op.get());
}
//需求:搜索名字中 “六” 出现的次数
@Test
public void test2(){
Optional<Integer> sum = emps.stream()
.map(Employee::getName)
.flatMap(Stream2::filterCharacter)
.map((ch) -> {
if(ch.equals('六'))
return 1;
else
return 0;
}).reduce(Integer::sum);
System.out.println(sum.get());
}
收集
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到List、Set、Map)。但是Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
2 新时间日期API
2.1 使用LocalDate、LocalTime、LocalDateTime
LocalDate、LocalTime、LocalDateTime 类的实例是不可变的对象,分别表示使用ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
提供的方法:
运用
@Test
public void test() {
LocalDate localDate = LocalDate.now();//现在的日期
LocalTime localTime = LocalTime.now();//现在的时间
LocalDateTime localDateTime = LocalDateTime.now();//现在的时间和日期
System.out.println(localDate+"==>"+localTime+"==>"+localDateTime);
}
@Test
public void test2() {
LocalDate localDate = LocalDate.of(1996, 02, 03);//指定现在的日期
LocalTime localTime = LocalTime.of(16, 04);//指定现在的时间
LocalDateTime localDateTime = LocalDateTime.of(1996, 02, 03, 16, 04);//指定现在的日期时间
System.out.println(localDate+"==>"+localTime+"==>"+localDateTime);
}
@Test
public void test3() {
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = localDate.plusDays(5);//在当前时间上加5天
LocalDate localDate2 = localDate.plusMonths(2);//加2个月
LocalDate localDate3 = localDate.plusWeeks(1);//加一周
LocalDate localDate4 = localDate.plusYears(1);//加一年
LocalDate localDate5 = localDate.minusMonths(5);//减5天
System.out.println(localDate4);
}
@Test
public void test5() {
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = localDate.withDayOfMonth(12);//将日期中的,这个月的天数修改为12
LocalDate localDate2 = localDate.withYear(2015);//将日期中的年修改为2015
LocalDate localDate3 = localDate.withMonth(8);//将日期中的月修改为8月
System.out.println(localDate3);
}
@Test
public void test6() {
LocalDate localDate = LocalDate.now();
int year = localDate.getYear();//获取当前的年份
System.out.println(year);
}
@Test
public void test7() {
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.now();
Period period = localDate.until(localDate1);//获取两个日期间的日期间隔
boolean before = localDate.isBefore(localDate1);//比较两个localData
boolean leapYear = localDate.isLeapYear();//是否是闰年
System.out.println(before);
}
2.2 Duration 和Period
- Duration:用于计算两个“时间”间隔
- Period:用于计算两个“日期”间隔
使用
@Test
public void test4() {
//时间戳
Instant start = Instant.now();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
Instant end = Instant.now();
//Duration:用于计算两个“时间”间隔
Duration duration = Duration.between(start, end);
// Period:用于计算两个“日期”间隔
LocalDate localDate = LocalDate.now();
LocalDate localDate1 = LocalDate.of(2017, 12, 1);
Period period = Period.between(localDate, localDate1);
//给日期加一个间隔
LocalDate localDate3 = localDate.plus(period);
System.out.println(localDate3);
}
2.3 解析与格式化
使用java.time.format.DateTimeFormatter对时间和日期进行格式化
@Test
public void test8() {
LocalDateTime localDateTime = LocalDateTime.now();
//第一种对时间日期的格式化方式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yy年MM月dd日 HH:mm:ss");
String format = formatter.format(localDateTime);
System.out.println(format);
//第二种对时间日期的格式化方式
String format1 = localDateTime.format(DateTimeFormatter.ofPattern("yy年MM月dd日"));
System.out.println(format1);
}
2.4 时区的处理
Java8 中加入了对时区的支持,带时区的时间为分别为:ZonedDate、ZonedTime、ZonedDateTime其中每个时区都对应着ID,地区ID都为“{区域}/{城市}”的格式例如:Asia/Shanghai 等
@Test
public void test9() {
//时区
LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
System.out.println(ldt);
}
3 接口中的默认方法与静态方法
3.1 接口中的默认方法
Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用default关键字修饰.
3.1.1 默认方法的使用
如
public interface MyInterface {
default String getName(){
return "呵呵呵";
}
}
一个实现这个接口的类
public class SubClass implements MyInterface{
}
在主函数中调用
public static void main(String[] args) {
SubClass sc = new SubClass();
System.out.println(sc.getName());
}
打印结果呵呵呵
3.1.2 接口默认方法的”类优先”原则
这时假如这个子类有一个父类,父类中也有一个getName()方法
public class MyClass {
public String getName(){
return "嘿嘿嘿";
}
}
子类此时是
public class SubClass extends MyClass implements MyInterface{
}
这时在主类中掉用
public static void main(String[] args) {
SubClass sc = new SubClass();
System.out.println(sc.getName());
}
结果为嘿嘿嘿
类优先原则:若一个接口中定义了一个默认方法,而另外一个父类中又定义了一个同名的方法时.
选择父类中的方法。如果父类提供了具体的实现,那么接口中具有相同名称和参数的默认方法会被忽略。
3.1.3 接口默认方法的"接口冲突"
"接口冲突":假如子类实现的多个接口中有相同的方法和参数(不管方法是否是默认方法),那么必须覆盖该方法来解决冲突
如,子类的另一个实现的接口
public interface MyFun {
default String getName(){
return "哈哈哈";
}
}
这时子类必须覆盖方法,通过接口名来指定到底调用哪一个
public class SubClass implements MyFun,MyInterface{
@Override
public String getName() {
return MyInterface.super.getName();//指定调用的接口
}
}
在主类中调用
public static void main(String[] args) {
SubClass sc = new SubClass();
System.out.println(sc.getName());
}
执行结果:呵呵呵
3.2 接口中的静态方法
Java8 中,接口中允许添加静态方法。
使用
public interface MyInterface {
static void show(){
System.out.println("接口中的静态方法");
}
}
直接使用类名调用
public static void main(String[] args) {
MyInterface.show();
}
输出结果:接口中的静态方法