Interface
interface的设计初衷是面向抽象,提高扩展性。这也留有一点遗憾,Interface修改的时候,实现它的类也必须跟着改。
为了解决接口的修改与现有的实现不兼容的问题。新interface的方法可以用default或static修饰,这样就可以有方法体,实现类也不必重写此方法。
一个interface中可以有多个方法被他们修饰,这两个修饰符的区别主要也是普通方法和静态方法的区别。
1.default修饰的方法,是普通实例方法,可以用this调用,可以被子类继承、重写。
2.static修饰的方法,使用上和一般静态方法一样。但它不能被子类继承,只能用interface调用。
在Java8,接口和抽象类有什么区别?
既然interface也可以有自己的方法实现,似乎和abstrace class每多大区别了
其实还是有区别的
①interface和class的区别:
- 接口多实现,类单继承
- 接口的方法是public abstract修饰,变量是public static final修饰。abstrace class可以用其他修饰符
②interface的方法更像是一个扩展插件。而abstract class的方法是要继承的。
开始我们也提到,interface新增default和static修饰的方法,为了解决接口的修改与现有的实现不兼容的问题,并不是为了要替代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();
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;} });
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(); } });
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");
for (String s : strings) { System.out.println(s); }
strings.forEach((s) -> System.out.println(s));
strings.forEach(System.out::println);
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(){ LamdbaInterface t = LamdbaClass::staticF; LamdbaClass lamdbaClass = new LamdbaClass(); LamdbaInterface lamdbaInterface = lamdbaClass::f; LamdbaInterface superf = super::sf; LamdbaInterface tt = LamdbaClassSuper::new; } }
|
Stream
java新增了java.util.stream包,他和之前的流大同小异。之前接触最多的是资源流,比如java.io.FileInputStream,通过流把文件从一个地方输入到另一个地方,他只是内容搬运工,对文件内容不做任何CRUD。
Stream依然不存储数据,不同的是它可以检索和逻辑处理集合数据、包括筛选、排序、统计、技术等。可以想象成是Sql语句。
它的元数据可以是Collection、Array等。由于它的方法参数都是函数式接口类型,所以一般和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()
public static<T> Stream<T> of(T t)
public static<T> Stream<T> of(T... values) { return Arrays.stream(values); }
Stream<T> filter(Predicate<? super T> predicate);
boolean allMatch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate);
public static<T> Builder<T> builder();
<R, A> R collect(Collector<? super T, A, R> collector);
long count();
Stream<T> distinct();
void forEach(Consumer<? super T> action);
Stream<T> limit(long maxSize);
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
Stream<T> sorted(Comparator<? super T> comparator);
Stream<T> skip(long n);
Object[] toArray();
<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<String> stringStream = strings.stream().filter(s -> "abc".equals(s)); long count = stringStream.count(); strings.stream().forEach(System.out::println); Stream<String> limit = strings.stream().limit(1); String[] array = limit.toArray(String[]::new); Stream<String> map = strings.stream().map(s -> s + "22"); strings.stream().sorted().forEach(System.out::println); List<String> collect = strings.stream().filter(string -> "abc".equals(string)).collect(Collectors.toList()); 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()); List<String> strings2 = Arrays.asList("xyz", "jqx"); Stream.concat(strings2.stream(),strings.stream()).count(); Stream stream = strings.stream(); stream.limit(2); stream.forEach(System.out::println); 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,需要获取Dog的age。
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
|
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
public static <T> Optional<T> ofNullable(T value) { return value == null ? empty() : of(value); }
public static<T> Optional<T> empty() { Optional<T> t = (Optional<T>) EMPTY; return t; }
public static <T> Optional<T> of(T value) { return new Optional<>(value); }
private Optional(T value) { this.value = Objects.requireNonNull(value); }
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
|
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)); } }
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
|
public boolean isPresent() { return value != null; }
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 LocalDate.class LocalTime.class
|
格式化
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(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); String date = sdf.format(now); System.out.println(String.format("date format : %s", date));
SimpleDateFormat sdft = new SimpleDateFormat("HH:mm:ss"); String time = sdft.format(now); System.out.println(String.format("time format : %s", time));
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(){ LocalDate date = LocalDate.now(); System.out.println(String.format("date format : %s", date));
LocalTime time = LocalTime.now().withNano(0); System.out.println(String.format("time format : %s", time));
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之后只需要LocalDate、LocalTime、LocalDateTime的of或parse方法。
日期计算
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(); LocalDate after = localDate.plus(1, ChronoUnit.WEEKS); 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() + "天"); long day = date2.toEpochDay() - date1.toEpochDay(); System.out.println(date2 + "和" + date2 + "相差" + day + "天"); }
|
获取指定日期
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()); 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