Interface

interface的设计初衷是面向抽象,提高扩展性。这也留有一点遗憾,Interface修改的时候,实现它的类也必须跟着改。

为了解决接口的修改与现有的实现不兼容的问题。新interface的方法可以用defaultstatic修饰,这样就可以有方法体,实现类也不必重写此方法。

一个interface中可以有多个方法被他们修饰,这两个修饰符的区别主要也是普通方法和静态方法的区别。

1.default修饰的方法,是普通实例方法,可以用this调用,可以被子类继承、重写。
2.static修饰的方法,使用上和一般静态方法一样。但它不能被子类继承,只能用interface调用。

在Java8,接口和抽象类有什么区别?

既然interface也可以有自己的方法实现,似乎和abstrace class每多大区别了

其实还是有区别的
①interface和class的区别:

  • 接口多实现,类单继承
  • 接口的方法是public abstract修饰,变量是public static final修饰。abstrace class可以用其他修饰符

②interface的方法更像是一个扩展插件。而abstract class的方法是要继承的。

开始我们也提到,interface新增defaultstatic修饰的方法,为了解决接口的修改与现有的实现不兼容的问题,并不是为了要替代abstrace class。在使用上,该用abstract class的地方还是要用abstract class,不要因为interface的新特性而将之替换。

记住接口永远和类不一样。

functional interface函数式接口

**定义:**也称SAM接口,即Single AbstractMethod interfaces,有且只有一个抽象方法,但可以有多个非抽象方法的接口。

在java8中专门有个包存放函数式接口java.util.function,该包下的所有接口都有@FunctionalInterface注解,提供函数式编程。

在其他包中也有函数式接口,其中一些没有@FunctionalInterface注解,但是只要符合函数式接口的定义就是函数式接口,与是否有@FunctionalInterface注解无关,注解只是在编译时起到强制规范定义的作用。其在Lamdba表达式中有广泛应用。

Lamdba表达式

Lamdba是继泛型(Generics)和注解(Annotation)以来最大的变化。

使用Lamdba表达式可以使代码变的更加简洁紧凑。让java也能支持简单的函数式编程。

Lamdba表达式是一个匿名函数,java8允许把函数作为参数传递进方法中。

语法格式

1
2
(parameters) -> expression 或
(parameters) -> {statements;}

Lamdba实战

替代匿名内部类

1.Runnable接口

1
2
3
4
5
6
7
8
9
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("The runable now is using!");
}
}).start();

//使用lamdba
new Thread(() -> System.out.println("It's a lambda function!")).start();

2.Comparator接口

1
2
3
4
5
6
7
8
9
10
11
12
13
List<Integer> strings = Arrays.asList(1, 2, 3);

Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;}
});

//Lamdba
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);
//分解开
Comparator<Integer> comperator = (Integer o1, Integer o2) -> o1 - o2;
Collections.sort(strings, comperator);

3.Lintener接口

1
2
3
4
5
6
7
8
9
10
JButton button = new JButton();
button.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
e.getItem();
}
});

//Lamdba
button.addItemListener(e -> e.getItem());

集合迭代

1
2
3
4
5
6
7
8
9
10
11
12
13
List<String> strings = Arrays.asList("1", "2", "3");
//传统foreach
for (String s : strings) {
System.out.println(s);
}

//Lambda foreach
strings.forEach((s) -> System.out.println(s));
//or
strings.forEach(System.out::println);
//map
Map<Integer, String> map = new HashMap<>();
map.forEach((k,v)->System.out.println(v));

方法的引用

Java8允许使用::关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是functional-interface。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class LambdaClassSuper {
LambdaInterface sf(){
return null;
}
}

public class LambdaClass extends LambdaClassSuper {
public static LambdaInterface staticF() {
return null;
}
public LambdaInterface f() {
return null;
}
void show(){
//1.调用静态函数,返回类型必须是functional-interface
LamdbaInterface t = LamdbaClass::staticF;

//2.实例方法调用
LamdbaClass lamdbaClass = new LamdbaClass();
LamdbaInterface lamdbaInterface = lamdbaClass::f;

//3.超类上的方法调用
LamdbaInterface superf = super::sf;

//4.构造方法调用
LamdbaInterface tt = LamdbaClassSuper::new;
}
}

Stream

java新增了java.util.stream包,他和之前的流大同小异。之前接触最多的是资源流,比如java.io.FileInputStream,通过流把文件从一个地方输入到另一个地方,他只是内容搬运工,对文件内容不做任何CRUD。

Stream依然不存储数据,不同的是它可以检索和逻辑处理集合数据、包括筛选、排序、统计、技术等。可以想象成是Sql语句。

它的元数据可以是CollectionArray等。由于它的方法参数都是函数式接口类型,所以一般和Lamdba配合使用。

流类型

1.stream串行流
2.parallelStream并行流,可多线程执行

常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
* 返回一个串行流
*/
default Stream<E> stream()

/**
* 返回一个并行流
*/
default Stream<E> parallelStream()

/**
* 返回T的流
*/
public static<T> Stream<T> of(T t)

/**
* 返回其元素是指定值的顺序流。
*/
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}

/**
* 过滤,返回由与给定predicate匹配的该流的元素组成的流
*/
Stream<T> filter(Predicate<? super T> predicate);

/**
* 此流的所有元素是否与提供的predicate匹配。
*/
boolean allMatch(Predicate<? super T> predicate)

/**
* 此流任意元素是否有与提供的predicate匹配。
*/
boolean anyMatch(Predicate<? super T> predicate);

/**
* 返回一个 Stream的构建器。
*/
public static<T> Builder<T> builder();

/**
* 使用 Collector对此流的元素进行归纳
*/
<R, A> R collect(Collector<? super T, A, R> collector);

/**
* 返回此流中的元素数。
*/
long count();

/**
* 返回由该流的不同元素(根据 Object.equals(Object) )组成的流。
*/
Stream<T> distinct();

/**
* 遍历
*/
void forEach(Consumer<? super T> action);

/**
* 用于获取指定数量的流,截短长度不能超过 maxSize 。
*/
Stream<T> limit(long maxSize);

/**
* 用于映射每个元素到对应的结果
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

/**
* 根据提供的 Comparator进行排序。
*/
Stream<T> sorted(Comparator<? super T> comparator);

/**
* 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。
*/
Stream<T> skip(long n);

/**
* 返回一个包含此流的元素的数组。
*/
Object[] toArray();

/**
* 使用提供的 generator函数返回一个包含此流的元素的数组,以分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组。
*/
<A> A[] toArray(IntFunction<A[]> generator);

/**
* 合并流
*/
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)

实战

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
@Test
public void test() {
List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
//返回符合条件的stream
Stream<String> stringStream = strings.stream().filter(s -> "abc".equals(s));
//计算流符合条件的流的数量
long count = stringStream.count();

//forEach遍历->打印元素
strings.stream().forEach(System.out::println);

//limit 获取到1个元素的stream
Stream<String> limit = strings.stream().limit(1);
//toArray 比如我们想看这个limitStream里面是什么,比如转换成String[],比如循环
String[] array = limit.toArray(String[]::new);

//map 对每个元素进行操作返回新流
Stream<String> map = strings.stream().map(s -> s + "22");

//sorted 排序并打印
strings.stream().sorted().forEach(System.out::println);

//Collectors collect 把abc放入容器中
List<String> collect = strings.stream().filter(string -> "abc".equals(string)).collect(Collectors.toList());
//把list转为string,各元素用,号隔开
String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(","));

//对数组的统计,比如用
List<Integer> number = Arrays.asList(1, 2, 5, 4);

IntSummaryStatistics statistics = number.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("列表中最大的数 : "+statistics.getMax());
System.out.println("列表中最小的数 : "+statistics.getMin());
System.out.println("平均数 : "+statistics.getAverage());
System.out.println("所有数之和 : "+statistics.getSum());

//concat 合并流
List<String> strings2 = Arrays.asList("xyz", "jqx");
Stream.concat(strings2.stream(),strings.stream()).count();

//注意 一个Stream只能操作一次,不能断开,否则会报错。
Stream stream = strings.stream();
//第一次使用
stream.limit(2);
//第二次使用
stream.forEach(System.out::println);
//报错 java.lang.IllegalStateException: stream has already been operated upon or closed

//但是可以这样, 连续使用
stream.limit(2).forEach(System.out::println);
}

延迟执行

在执行返回Stream的方法时,并不立刻执行,而是等返回一个非Stream得方法后才执行。因为拿到Stream并不能直接用,而是需要处理成一个常规类型。这里的Stream可以想象成是二进制流,拿到也看不懂。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Test
public void laziness(){
List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
Stream<Integer> stream = strings.stream().filter(new Predicate() {
@Override
public boolean test(Object o) {
System.out.println("Predicate.test 执行");
return true;
}
});

System.out.println("count 执行");
stream.count();
}
/*-------执行结果--------*/
count 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行

按执行顺序应该是先打印4次Predicate.test 执行,再打印count执行。实际结果恰恰相反。说明filter中的方法并没有立刻执行,而是等调用count()方法后才执行。

上面都是串行Stream的实例。并行parallelStream在使用方法上和串行一样。主要区别是parallelStream可多线程执行,是基于FrokJoin框架实现的。

1
2
3
4
5
6
7
8
9
10
11
@Test
public void parallelStreamTest(){
List<Integer> numbers = Arrays.asList(1, 2, 5, 4);
numbers.parallelStream() .forEach(num->System.out.println(Thread.currentThread().getName()+">>"+num));
}

//执行结果
main>>5
ForkJoinPool.commonPool-worker-2>>4
ForkJoinPool.commonPool-worker-11>>1
ForkJoinPool.commonPool-worker-9>>2

小结

从源码和实例中我们可以总结出一些stream的特点

1.通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。
2.方法参数都是函数式接口类型
3.一个Stream只能操作一次,操作完就关闭了,继续使用这个stream会报错。
4.Stream不保存数据,不改变数据源

Optional

建议使用Optional解决NPE问题,他就是为NPE而生的,其中可以包含控制或非空值。

假设有一个Zoo类,里面有个属性Dog,需要获取Dogage

1
2
3
4
5
6
7
class Zoo {
private Dog dog;
}

class Dog {
private int age;
}

传统解决NPE的办法如下:

1
2
3
4
5
6
7
8
Zoo zoo = getZoo();
if(zoo != null){
Dog dog = zoo.getDog();
if(dog != null){
int age = dog.getAge();
System.out.println(age);
}
}

层层判断对象非空,有人说这种方式很丑陋不优雅,我并不这么认为。反而觉得很整洁,易读,易懂。你们觉得呢?

Optional是这样实现的:

1
2
3
Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).ifPresent(age ->
System.out.println(age)
);

如何创建一个Optional

上面的Optional.ofNullable是其中一种创建Optional的方式。我们看一下源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* Common instance for {@code empty()}. 全局EMPTY对象
*/
private static final Optional<?> EMPTY = new Optional<>();

/**
* Optional维护的值
*/
private final T value;

/**
* 如果value是null就返回EMPTY,否则就返回of(T)
*/
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
/**
* 返回 EMPTY 对象
*/
public static<T> Optional<T> empty() {
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
/**
* 返回Optional对象
*/
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
/**
* 私有构造方法,给value赋值
*/
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
/**
* 所以如果of(T value) 的value是null,会抛出NullPointerException异常,这样貌似就没处理NPE问题
*/
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}

ofNullable方法和of方法唯一区别就是当value为null时,ofNullable返回的是EMPTY,of会抛出NullPointerException异常。如果需要把NullPointerException暴漏出来就用of,否则使用ofNullable

map()相关方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 如果value为null,返回EMPTY,否则返回Optional封装的参数值
*/
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
/**
* 如果value为null,返回EMPTY,否则返回Optional封装的参数值,如果参数值返回null会抛 NullPointerException
*/
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}

map()和flatMap()有什么区别?

①参数不一样,map的参数上面看到过,flatMap的参数是这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class ZooFlat {
private DogFlat dog = new DogFlat();

public DogFlat getDog() {
return dog;
}
}

class DogFlat {
private int age = 1;
public Optional<Integer> getAge() {
return Optional.ofNullable(age);
}
}

ZooFlat zooFlat = new ZooFlat();
Optional.ofNullable(zooFlat).map(o -> o.getDog()).flatMap(d -> d.getAge()).ifPresent(age ->
System.out.println(age)
);

②flatMap参数返回值如果是null会抛NullPointerException,而map()返回EMPTY

判断value是否为null

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* value是否为null
*/
public boolean isPresent() {
return value != null;
}
/**
* 如果value不为null执行consumer.accept
*/
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}

小结

Optional的高频方法

1
ptional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).filter(v->v==1).orElse(3);

Date-Time API

这是对java.util.Date强有力的补充,解决了Date类的大部分痛点:

1.非线程安全
2.时区处理麻烦
3.各种格式化、和时间计算繁琐
4.设计有缺陷,Date类同时包含日期和时间;还有一个java.sql.Date,容易混淆。

java.time主要类

java.util.Date既包含日期又包含时间,而java.time把它们进行了分离

1
2
3
LocalDateTime.class	//日期+时间 format:yyyy-MM-dd HH:mm:ss.SSS
LocalDate.class //日期 format:yyyy-MM-dd
LocalTime.class //时间 format:HH:mm:ss

格式化

1.8之前:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void oldFormat(){
Date now = new Date();
//format yyyy-MM-dd HH:mm:ss
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String date = sdf.format(now);
System.out.println(String.format("date format : %s", date));

//format HH:mm:ss
SimpleDateFormat sdft = new SimpleDateFormat("HH:mm:ss");
String time = sdft.format(now);
System.out.println(String.format("time format : %s", time));

//format yyyy-MM-dd HH:mm:ss
SimpleDateFormat sdfdt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String datetime = sdfdt.format(now);
System.out.println(String.format("dateTime format : %s", datetime));
}

java8之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void newFormat(){
//format yyyy-MM-dd
LocalDate date = LocalDate.now();
System.out.println(String.format("date format : %s", date));

//format HH:mm:ss
LocalTime time = LocalTime.now().withNano(0);
System.out.println(String.format("time format : %s", time));

//format yyyy-MM-dd HH:mm:ss
LocalDateTime dateTime = LocalDateTime.now();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String dateTimeStr = dateTime.format(dateTimeFormatter);
System.out.println(String.format("dateTime format : %s", dateTimeStr));
}

字符串转日期格式

Java8之前:

1
2
3
4
5
//已弃用
Date date = new Date("2021-01-26");
//替换为
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date1 = sdf.parse("2021-01-26");

Java8之后:

1
2
3
4
5
6
7
8
LocalDate date = LocalDate.of(2021, 1, 26);
LocalDate.parse("2021-01-26");

LocalDateTime dateTime = LocalDateTime.of(2021, 1, 26, 12, 12, 22);
LocalDateTime.parse("2021-01-26 12:12:22");

LocalTime time = LocalTime.of(12, 12, 22);
LocalTime.parse("12:12:22");

Java8之前转需要借助SimpleDateFormat类,而Java8之后只需要LocalDateLocalTimeLocalDateTimeofparse方法。

日期计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void pushWeek(){
//一周后的日期
LocalDate localDate = LocalDate.now();
//方法1
LocalDate after = localDate.plus(1, ChronoUnit.WEEKS);
//方法2
LocalDate after2 = localDate.plusWeeks(1);
System.out.println("一周后日期:" + after);

//算两个日期间隔多少天,计算间隔多少年,多少月
LocalDate date1 = LocalDate.parse("2021-02-26");
LocalDate date2 = LocalDate.parse("2021-12-23");
Period period = Period.between(date1, date2);
System.out.println("date1 到 date2 相隔:"
+ period.getYears() + "年"
+ period.getMonths() + "月"
+ period.getDays() + "天");
//打印结果是 “date1 到 date2 相隔:0年9月27天”
//这里period.getDays()得到的天是抛去年月以外的天数,并不是总天数
//如果要获取纯粹的总天数应该用下面的方法
long day = date2.toEpochDay() - date1.toEpochDay();
System.out.println(date2 + "和" + date2 + "相差" + day + "天");
//打印结果:2021-12-23和2021-12-23相差300天
}

获取指定日期

1
2
3
4
5
6
7
8
9
10
11
12
13
public void getDayNew() {
LocalDate today = LocalDate.now();
//获取当前月第一天:
LocalDate firstDayOfThisMonth = today.with(TemporalAdjusters.firstDayOfMonth());
// 取本月最后一天
LocalDate lastDayOfThisMonth = today.with(TemporalAdjusters.lastDayOfMonth());
//取下一天:
LocalDate nextDay = lastDayOfThisMonth.plusDays(1);
//当年最后一天
LocalDate lastday = today.with(TemporalAdjusters.lastDayOfYear());
//2021年最后一个周日,如果用Calendar是不得烦死。
LocalDate lastMondayOf2021 = LocalDate.parse("2021-12-31").with(TemporalAdjusters.lastInMonth(DayOfWeek.SUNDAY));
}

JDBC和java8

现在jdbc时间类型和java8时间类型对应关系是:
1.Date --> LocalDate
2.Time --> LocalTime
3.TimeStamp --> LocalDateTime

总结

我们梳理总结的java8新特性有

  • interface & functional Interface
  • Lamdba
  • Stream
  • Optional
  • Date time-api