博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java8——快速入门手册(学习笔记)
阅读量:6445 次
发布时间:2019-06-23

本文共 13397 字,大约阅读时间需要 44 分钟。

目录

Java8特性学习笔记

  Java8中新增了许多的新特性,在这里本人研究学习了几个较为常用的特性,在这里与大家进行分享。(这里推荐用于理解基础知识)本文分为以下几个章节:

  • Lambda 表达式
  • 方法引用
  • 默认方法
  • 函数接口
  • Function
  • Stream
  • Optional API
  • Date Time API

Lambda表达式

Lambda 表达式,也可称为闭包。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。使用 Lambda 表达式可以使代码变的更加简洁紧凑。 Lambda表达式可以替代以前广泛使用的内部匿名类,各种回调,比如事件响应器、传入Thread类的Runnable等。

Lambda语法

lambda 表达式的语法格式如下:

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

Lambda表达式的特征

  • 类型声明(可选):可以不需要声明参数类型,编译器会识别参数值。
  • 参数圆括号(可选):在单个参数时可以不使用括号,多个参数时必须使用。
  • 大括号和return关键字(可选):如果只有一个表达式,则可以省略大括号和return关键字,编译器会自动的返回值;相对的,在使用大括号的情况下,则必须指明返回值。

Lambda表达式例子

这里以常用的list排序功能为例:

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
private 
static 
List<People> peopleList =
new 
ArrayList<People>();
{
    
peopleList.add(
new 
People(
"a"
,
17
));
    
peopleList.add(
new 
People(
"b"
,
16
));
    
peopleList.add(
new 
People(
"c"
,
19
));
    
peopleList.add(
new 
People(
"d"
,
15
));
}
 
@Test
public 
void 
testLambda(){
    
System.out.println(
"排序前:"
+peopleList);
 
    
//第一种,传统匿名Compartor接口排序
    
Collections.sort(peopleList,
new 
Comparator<People>() {
        
@Override
        
public 
int 
compare(People o1, People o2) {
            
return 
o1.getAge().compareTo(o2.getAge());
        
}
    
});
    
System.out.println(
"匿名接口方法——排序后:"
+peopleList);
 
    
//第二种,使用Lambda表达式来代替匿名接口方法
    
//1.声明式,不使用大括号,只可以写单条语句
    
Collections.sort(peopleList,(People a,People b)->a.getAge().compareTo(b.getAge()));
    
System.out.println(
"Lambda表达式1、排序:"
+peopleList);;
    
//2.不声明式,使用大括号,可以写多条语句
    
Collections.sort(peopleList,(a,b)->{
        
System.out.print(
"——————————————"
);
        
return 
a.getAge().compareTo(b.getAge());
    
});
    
System.out.println();
    
System.out.println(
"Lambda表达式2、排序:"
+peopleList);
 
    
//第三种,使用Lambda表达式调用类的静态方法
    
Collections.sort(peopleList,(a,b)->People.sortByName(a,b));
    
System.out.println(
"Lambda表达式调用静态方法:"
+peopleList);
 
    
//第四种,使用Lambda表达式调用类的实例方法
    
Collections.sort(peopleList,(a,b)->
new 
People().sortByAge(a,b));
    
System.out.println(
"Lambda表达式调用实例方法:"
+peopleList);
}

对应的运行结果:

注意:在Lambda表达式中只能对final的对象进行操作,声明的对象也为final)

有的朋友应该已经观察到了,Lambda 表达式与C中的函数指针,JavaScript的匿名function均有些相似。其实,Lambda表达式本质上是一个匿名的方法,只不过它的目标类型必须是“函数接口(functional interface)”,这是Java8引入的新概念,在接下来会进行更加详细的介绍。

方法引用

在一些Lambda中可能只是单纯的调用方法,比如前例中的三、四,在这种情况下,就可以使用方法引用的方式来提高可读性。

方法引用的种类

  • 类静态方法引用
    Class::staticMethodName
  • 某个对象的方法引用
    instance::instanceMethodName
  • 特定类的任意对象的方法引用:
    Class::method
  • 构造方法引用:
    Class::new

方法引用的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Test
public 
void 
testMethodReference() {
//第一种,引用类的静态方法
Collections.sort(peopleList, People::sortByName);
System.out.println(
"引用类的静态方法:" 
+ peopleList);
 
//第二种,引用类的实例方法
Collections.sort(peopleList,
new 
People()::sortByAge);
System.out.println(
"引用类的实例方法:" 
+ peopleList);
 
//第三种,特定类的方法调用()
Integer[] a =
new 
Integer[]{
3
,
1
,
2
,
4
,
6
,
5
};
Arrays.sort(a, Integer::compare);
System.out.println(
"特定类的方法引用:" 
+ Arrays.toString(a));
 
//第四种,引用类的构造器
Car car = Car.create(Car::
new
);
System.out.println(
"引用类的构造器:" 
+ car);
}
1
2
3
public 
static 
Car create(Supplier<Car> supplier){
    
return 
supplier.get();
}

默认方法

在Java8之前的时代,为已存在接口增加一个通用的实现是十分困难的,接口一旦发布之后就等于定型,如果这时在接口内增加一个方法,那么就会破坏所有实现接口的对象。

默认方法(之前被称为 虚拟扩展方法 或 守护方法)的目标即是解决这个问题,使得接口在发布之后仍能被逐步演化。

默认方法(defalut)

1
2
3
4
5
public 
interface 
vehicle {
   
default 
void 
print(){
      
System.out.println(
"我是一辆车!"
);
   
}
}

静态方法(static)

1
2
3
4
5
public 
interface 
vehicle {
   
static 
void 
blowHorn() {
      
System.out.println(
"按喇叭!!!"
);
   
}
}

注:静态方法与默认方法均可以有多个,默认方法可以被覆盖。

函数接口

“函数接口(functional interface)”,就是除去默认方法以及继承的抽象方法,只有显式声明一个抽象方法的接口。,也可以省略,Java会自动识别。接下来介绍一些常见的函数接口:

java.util.function.Predicate

该接口包含方法boolean test(T t),该接口一般用于条件的检测,内部包含三个默认方法:and、or、negate、,即与或非,用于各式的条件判断。例:

1
2
3
4
5
Predicate<Integer> predicate = x -> x >
3
;
 
predicate.test(
10
);
//true
predicate.negate().test(
10
);
//false
predicate.or(x -> x <
1
).and(x -> x > -
1
).negate().test(-
1
);
//true

注意:在这里与或非的判断顺序是从左到右的,调用的顺序会影响结果。

java.util.Comparator

Comparator是Java中的经典接口,在排序中较为常用。Java8在此之上添加了一些新的默认方法,来丰富该接口的功能。例:

1
2
3
4
5
6
7
8
Integer[] a =
new 
Integer[]{
3
,
1
,
2
,
4
,
6
,
5
};
Comparator<Integer> comparator = Integer::compare;
 
Arrays.sort(a, comparator);
System.out.println(
"升序:" 
+ Arrays.toString(a));
 
Arrays.sort(a,comparator.reversed());
System.out.println(
"降序:"
+Arrays.toString(a));

结果

升序:[1, 2, 3, 4, 5, 6]降序:[6, 5, 4, 3, 2, 1]

java.util.function.Supplier

该类只包含方法:

T get();
Supplier接口是在1.8中新出现的函数接口,用于支持函数式编程。它用于返回一个任意泛型的实例对象,与工厂的功能类似。

java.util.function.Consumer

该接口表示一个接受单个输入参数并且没有返回值的操作。不像其他函数式接口,Consumer接口期望执行修改内容的操作。例如 ,我们需要一个批量修改People的方法,利用Predicate和Consumer就可以这么写

在People内增加updateMany方法:

1
2
3
4
5
6
7
8
public 
static 
List updateMany(List<People> peopleList, Predicate<People> predicate, Consumer<People> consumer) {
    
for 
(
int 
i =
0
; i < peopleList.size(); i++) {
        
if 
(predicate.test(peopleList.get(i))) {
            
consumer.accept(peopleList.get(i));
        
}
    
}
    
return 
peopleList;
}

调用:

1
2
3
4
5
//批量修改,将age<18的对象的age改为18
People.updateMany(peopleList,
        
p -> p.getAge() <
18
,
        
p -> p.setAge(
18
));
System.out.println(
"修改后的结果:" 
+ peopleList);

通过这种方式,可以将内部的判断逻辑与修改代码放至外部调用,而将for、if等语句封装至内部,提高代码的可读性。

其他的还有一些函数接口,如Runnable,InvocationHandler等,在这里就不阐述了。有兴趣的大家可以自行查询资料。Stream、Function、Optional也是函数接口,将在下面进行详细介绍。

Function

说明

Java8提供的java.util.function包的核心函数接口有4个。

  • 函数型T ->R,完成参数类型T向结果类型R的转换和数据处理。核心函数接口Function
  • 判断型T ->boolean,核心函数接口Predicate
  • 消费型T ->void,核心函数接口Consumer
  • 供给型void->T,核心函数接口Supplier

Function接口是为Java8提供了函数式编程的基础,apply方法与Consumer的accept方法功能类似,但是提供了返回及类型转换的可能,功能更加强大;再通过andThen与compose方法可以使Function组成Function功能链,进行多级数据处理及转换。

主要方法

  • R apply(T t) – 将Function对象应用到输入的参数上,然后返回计算结果。

  • default Function<t,v> andThen(Function<? super R,? extends V> after) 返回一个先执行当前函数对象apply方法再执行after函数对象apply方法的函数对象。

  • default Function<t,v> compose(Function<? super V,? extends T> before)返回一个先执行before函数对象apply方法再执行当前函数对象apply方法的函数对象。

  • static Function<t,t> identity() 返回一个执行了apply()方法之后只会返回输入参数的函数对象。

方法详解

apply:
1
R apply(T t);

接收类型:T

返回类型:R
类型转换:T→R

Function接口的核心方法,可以执行任意的操作,且具有返回值。接收一个T类型的对象,在经过处理后,返回一个R类型的对象。主要功能为类型转换及数据处理。

compose:
1
2
3
4
default 
<V> Function<V, R> compose(Function<?
super 
V, ?
extends 
T> before) {
    
Objects.requireNonNull(before);
    
return 
(V v) -> apply(before.apply(v));
}

接收类型:Function<? super V, ? extends T>

返回类型:Function<v, r="">
类型转换:(V+)→(T-)→T→R
apply执行顺序:before→this

此处“V+”指代“? super V”,表示包含V在内的V的任意父类;"T-"指代“? extends T”,表示包含T在内的T的任意子类。compose方法返回一个Function<v,r>,这个Function先执行before的apply方法,将V+类型的数据转换为T-类型,再将T-作为参数传递给this的apply方法,将T类型转换为R类型。

通过compose方法,可以在某个Function执行之前插入一个Function执行。由于返回类型依旧为Function,可以重复调用compose方法形成方法链。

andThen:
1
2
3
4
default 
<V> Function<T, V> andThen(Function<?
super 
R, ?
extends 
V> after) {
    
Objects.requireNonNull(after);
    
return 
(T t) -> after.apply(apply(t));
}

接收类型:Function<? super R, ? extends V>

返回类型:Function<t, v="">
类型转换:T→R→(R+)→(V-)
apply执行顺序:this→after

此处“R+”指代“? super R”,表示包含R在内的R的任意父类;"V-"指代“? extends V”,表示包含V在内的V的任意子类。andThen方法返回一个Function<t,v>,这个Function先执行this的apply方法,将T类型的数据转换为R类型,再将R作为参数传递给after的apply方法,将R+类型转换为V-类型。

通过andThen方法,可以在某个Function执行之后插入一个Function执行。由于返回类型依旧为Function,可以重复调用andThen方法形成方法链。

identity:
1
2
3
static 
<T> Function<T, T> identity() {
    
return 
t -> t;
}

接收类型:无

返回类型:Function<t, t="">
类型转换:T→T

该方法的说明是:返回一个函数,它总是返回输入参数。调用该方法可以得到一个返回输入参数的Funtion,这个Function就可以单纯的用来做数据处理,而不用类型转换。

Stream

Java8中提供了Stream API,即流式处理。可以通过将List、Set、Array等对象转换成流进行操作。Stream内的流操作分为两种:中间操作和最终操作,中间操作会返回一个全新的Stream对象,意味着你的操作不会影响最初的流;最终操作会将流进行转换或者操作,返回非Stream的对象。Stream可以替代传统的循环操作,从线程上区别,Stream分为串行(Stream)和并行(parallelStream),关于Stream的性能分析可以查看这篇文章。下面来看下Strea内的一些方法:

中间操作

  • distinct

    1
    Stream<T> distinct();

    去除Stream中重复的对象,并返回一个流。(使用对象的equals方法)

  • skip

    1
    Stream<T> skip(
    long 
    n);

    跳过Stream中的前n个对象,将其他对象返回一个Stream。如果n超过了Stream中对象的个数,则会返回一个空的Stream。

  • limit

    1
    Stream<T> limit(
    long 
    maxSize);

    截取Stream的前maxSize个对象,并形成一个新Stream。

  • filter

    1
    Stream<T> filter(Predicate<?
    super 
    T> predicate);

    根据给定的predicate来过滤对象,返回满足条件的对象构成的Stream。

  • map

    1
    <R> Stream<R> map(Function<?
    super 
    T, ?
    extends 
    R> mapper);

    通过给定的mapper,将T类型的流转换为R类型的Stream。

  • flatMap

    1
    <R> Stream<R> flatMap(Function<?
    super 
    T, ?
    extends 
    Stream<?
    extends 
    R>> mapper);

    flatMap也是将Stream进行转换,flatMap与map的区别在于 flatMap是将一个Stream中的每个值都转成一个个Stream,然后再将这些流扁平化成为一个Stream。

    例(转自:):
    假设我们有一个字符串数组String[] strs = {"java8", "is", "easy", "to", "use"};,我们希望输出构成这一数组的所有非重复字符,那么我们可能首先会想到如下实现:

    1
    2
    3
    4
    List<String[]> distinctStrs = Arrays.stream(strs)
                        
    .map(str -> str.split(
    ""
    )) 
    // 映射成为Stream<String[]>
                        
    .distinct()
                        
    .collect(Collectors.toList());

    在执行map操作以后,我们得到是一个包含多个字符串(构成一个字符串的字符数组)的流,此时执行distinct操作是基于在这些字符串数组之间的对比,所以达不到我们希望的目的,此时的输出为:

    [j, a, v, a, 8][i, s][e, a, s, y][t, o][u, s, e]

    distinct只有对于一个包含多个字符的流进行操作才能达到我们的目的,即对Stream进行操作。此时flatMap就可以达到我们的目的:

    1
    2
    3
    4
    5
    List<String> distinctStrs = Arrays.stream(strs)
                                    
    .map(str -> str.split(
    ""
    )) 
    // 映射成为Stream<String[]>
                                    
    .flatMap(Arrays::stream) 
    // 扁平化为Stream<String>
                                    
    .distinct()
                                    
    .collect(Collectors.toList());

    flatMap将由map映射得到的Stream<string[]>,转换成由各个字符串数组映射成的流Stream,再将这些小的流扁平化成为一个由所有字符串构成的大流Steam,从而能够达到我们的目的。

  • sorted

    1
    2
    Stream<T> sorted();
    Stream<T> sorted(Comparator<?
    super 
    T> comparator);

    sorted方法可以对Stream进行排序。排序的对象必须实现Comparable,如果没实现会抛出ClassCastException;不提供comparator时,则会调用compareTo方法。

  • peek

    1
    Stream<T> peek(Consumer<?
    super 
    T> action);

    对流中的每个对象执行提供的action操作。

    在Stack中,peek用于查看一个对象。在流中也是一样,用于在流循环时,根据给定的action进行查看对象。虽然可以进行元素修改操作,但不建议。

  • 综合例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Integer[] a =
    new 
    Integer[]{
    3
    ,
    1
    ,
    2
    ,
    5
    ,
    11
    ,
    4
    ,
    6
    ,
    5
    ,
    3
    ,
    1
    };
        
    List<Integer> aList = Arrays.stream(a)
        
    .distinct()
        
    .skip(
    1
    )
        
    .filter((e) -> e <
    6
    )
        
    .peek(e -> System.out.println(
    "循环1次"
    ))
        
    .limit(
    4
    )
        
    .sorted()
        
    .collect(Collectors.toList());
    System.out.println(aList);

    输出:

    循环1次循环1次循环1次循环1次[1, 2, 4, 5]

    最终操作

  • 聚合
    • max & min

      1
      2
      Optional<T> min(Comparator<?
      super 
      T> comparator);
      Optional<T> max(Comparator<?
      super 
      T> comparator);

      根据给定的comparator返回Stream中的max或min。

    • count

      1
      long 
      count();

      返回Stream中对象的个数。

  • 匹配
    • anyMatch & allMatch & noneMatch

      1
      2
      3
      boolean 
      anyMatch(Predicate<?
      super 
      T> predicate);
      boolean 
      allMatch(Predicate<?
      super 
      T> predicate);
      boolean 
      noneMatch(Predicate<?
      super 
      T> predicate);

      根据给定的predicate判断Stream是否匹配条件。

    • collect

      1
      <R, A> R collect(Collector<?
      super 
      T, A, R> collector);

      根据给定的collector对Stream中的元素进行操作,返回复杂数据结构的对象。用于将Stream中的对象转换成我们想要的结构,如list、map、set等。

      前例中就使用collect(Collectors.toList())将Stream中的对象转换成List。

    • reduce

      1
      2
      Optional<T> reduce(BinaryOperator<T> accumulator);
      T reduce(T identity, BinaryOperator<T> accumulator);

      如果我们不知希望单纯的返回List这样的类型,而是希望将整个Stream经过一些操作后,规约成一个对象返回,就可以用到规约操作。reduce方法有两个参数,其中accumulator代表着规约的操作,即用何种的方式进行参数化处理;identity则是accumulator的标识值(具体用处暂不明)。

      例:求和

      1
      2
      3
      4
      5
      6
      Integer[] a =
      new 
      Integer[]{
      3
      ,
      1
      ,
      2
      ,
      5
      ,
      11
      ,
      4
      ,
      6
      ,
      5
      ,
      3
      ,
      1
      };
      int 
      sum = Arrays.stream(a)
          
      .distinct()
          
      .filter((e) -> e <
      6
      )
          
      .reduce(
      0
      , (x, y) -> x + y);
      //或.reduce(0, Integer::sum);
      System.out.println(sum);
      //15
    • toArray

      1
      Object[] toArray();

      将Stream中的对象返回成一个Object数组。

    • forEach

      1
      void 
      forEach(Consumer<?
      super 
      T> action);

      顾名思义,对Stream中每个元素进行action操作,与peek类似,但forEach是一个最终操作,一般在结束时查看对象使用。

    • findFirst & findAny

      1
      2
      Optional<T> findFirst();
      Optional<T> findAny();

      findFirst可以返回Stream中第一个对象,并将它封装在Optional中。

      findAny则不是返回第一个对象,而是任意一个对象。在顺序Stream中findFirst和findAny的结果是一致的,但在并行Stream中,findFirst存在着限制,故在并行Stream中需要使用findAny(findAny源码注释中写的是some element?)。同样将对象封装在Optional中。

Optional API

在java8之前的编程中,我们总是需要进行if(obj=null)来防止NullPointException,而在java8后,提供了Optional类,它一方面用于防止NullPotinException的判断,另一方面则为流式编程与函数式变成提供了更好的支持;Optional是一个包含对象的容器,它可以包含null值。在Optional类中封装了许多的方法,来让我们更好的处理我们的代码。接下来看看Optional中几个常用的方法:

  • empty & of & ofNullable

    1
    2
    3
    public 
    static 
    <T> Optional<T> empty(){...}
    public 
    static 
    <T> Optional<T> of(T value) {
    return 
    new 
    Optional<T>(value);}
    public 
    static 
    <T> Optional<T> ofNullable(T value){
    return 
    value ==
    null 
    ? empty() : of(value);}

    首先,Optioanl的构造方法是私有的,只能通过以上三个静态方法来获取Optional的实例。empty方法会返回Optional中的常量EMPTY对象,一般在compare时使用,注意这里的EMPTY是单例的而且为常量;一般我们需要构造一个Optional,使用of或ofNullable方法,of方法会将我们的传值构造一个新的Optional返回,而ofNullable则在接收null时返回EMPTY实例。

  • isPresent

    1
    2
    public 
    boolean 
    isPresent() {
    return 
    value !=
    null
    ;}
    public 
    void 
    ifPresent(Consumer<?
    super 
    T> consumer) {
    if 
    (value !=
    null
    )consumer.accept(value);}

    isPresent方法用于判断Optional包含的value是否为null,第一种方法返回一个boolean;第二种方法则根据判断,为null则什么都不执行,不为null则执行一个consumer操作。

  • map & flatMap

    1
    2
    public
    <U> Optional<U> map(Function<?
    super 
    T, ?
    extends 
    U> mapper){...}
    public
    <U> Optional<U> flatMap(Function<?
    super 
    T, Optional<U> mapper){...}

    map与flatMap与Stream中用法与功能大致相同,都是转换及合并转换,不再赘述。

  • get

    1
    public 
    T get() {...}

    get方法用于获取value。需要注意的是,如果value为null,则会抛出NoSuchElementException。

  • filter

    1
    public 
    Optional<T> filter(Predicate<?
    super 
    T> predicate) {...}

    filter方法也是获取value,它可以传入一个predicate,用于判断value是否满足条件。如果value为null,则会返回this;如果predicate.test为true,则返回this,否则会返回EMPTY。

  • orElse & orElseGet & orElseGet

    1
    2
    3
    public 
    T orElse(T other) {
    return 
    value !=
    null 
    ? value : other;}
    public 
    T orElseGet(Supplier<?
    extends 
    T> other) {
    return 
    value !=
    null 
    ? value : other.get();}
    public 
    <X
    extends 
    Throwable> T orElseThrow(Supplier<?
    extends 
    X> exceptionSupplier)
    throws 
    X{...}

    这三个方法都用于获取value,同时可以在value==null的情况下做出不同的操作。orElse可以传入一个other,当value==null时则返回null;orElseGet则是使用Supplier,为null时调用get方法;orElseThrow则是接收一个Supplier包含某种异常的exceptionSupplier,为null时则会调用get方法抛出一个异常。

Date Time API

Java8使用新的日期时间API覆盖旧的日期时间API的,处理了以下缺点。

  • 线程安全 - java.util.Date不是线程安全的,因此开发者必须在使用日期处理并发性问题。新的日期时间API是不可变的,并且没有setter方法。
  • 设计问题 - 默认的开始日期为1900年,月的开始月份为0而不是1,没有统一。不直接使用方法操作日期。新的API提供了这样操作实用方法。
  • 时区处理困难 - 开发人员必须编写大量的代码来处理时区的问题。新的API设计开发为这些特定领域提供了帮助。

JAVA8引入了java.time包,一个新的日期时间API。限于篇幅与精力问题,这里不对java.time进行过多的介绍,这里推荐几篇个人觉得不错的博文以供研究:

转载于:https://www.cnblogs.com/itcastzmm/p/9298546.html

你可能感兴趣的文章
用DELPHI 开发压缩、解压、自解压、加密
查看>>
Linux命令行得到系统IP
查看>>
SQL Server索引的维护 - 索引碎片、填充因子 <第三篇>
查看>>
python类型转换、数值操作(收藏)
查看>>
mysql delimiter
查看>>
关于C#静态构造函数的几点说明
查看>>
理解C# 4 dynamic(4) – 让人惊艳的Clay
查看>>
Spring Cloud Config 统一配置中心
查看>>
Java获取文本文件字符编码的两种方法
查看>>
js数据类型只string,object
查看>>
android httpClient(https/http)的优化构建方式二
查看>>
架设用Webservice实现文件上传功能CentOS服务器(一)--Tomcat
查看>>
一步一步部署Laravel项目
查看>>
.net 2.0 4.0 表单中危险字符
查看>>
dubbo负载均衡策略
查看>>
玩转大数据系列之Apache Pig如何通过自定义UDF查询数据库(五)
查看>>
axis实例包
查看>>
归并排序 MergeSort
查看>>
Javascript的this用法
查看>>
Fiddler下Firefox提示“您的连接并不安全”的解决办法
查看>>