1. 程式人生 > >Java8新特性(lambda、Stream、Optional)

Java8新特性(lambda、Stream、Optional)

1、λ表示式

lambda表示式:λ表示式是Java8新增的一個特性,《Core Java》中的對它的解析是——“一個可傳遞的程式碼塊,可以在以後執行一次或多次”。
  • 從日常開發的角度來看,它可以簡化我們的很多程式碼(當然不止這一個原因),特別是很多匿名內部類的寫法都可以被λ表示式替換成一個語句。
  • λ表示式從本質上來看是語法糖,但它並不是簡單的匿名內部類的語法糖,λ表示式的內部實現機制也都不是採用匿名內部類,說到底還是效能原因。
  • 對於大多數情況來說,Lambda表示式要比匿名內部類效能更優。
// 使用匿名類的方式新建一個執行緒
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello world !");
    }
}).start();
 
 
// 使用λ表示式的方式新建一個執行緒
new Thread(() -> System.out.println("Hello world !")).start();

lambda表示式的語法:

(parameters) → expression
                  或者
(parameters) → { statements; }  // 這種方式可以寫多條語句
lambda表示式的使用場景:
  • 匿名內部類(上文已展示)
  • 集合排序
  • 結合stream使用
public class Human {
    private String name;
    private int age;
 
    public Human() {
        super();
    }
    // standard getters and setters……
}


// 使用傳統的方式對集合進行排序:對集合進行排序要為Comparator建立一個匿名內部類用來排序:
new Comparator<Human>() {
	@Override
	public int compare(Human h1, Human h2) {
		return h1.getName().compareTo(h2.getName());
	}
}
@Test
public void givenPreLambda_whenSortingEntitiesByName_thenCorrectlySorted() {
	List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
	Collections.sort(humans, new Comparator<Human>() {
		@Override
		public int compare(Human h1, Human h2) {
			return h1.getName().compareTo(h2.getName());
		}
	});
	Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}
// 使用λ表示式方式
@Test
public void whenSortingEntitiesByName_thenCorrectlySorted() {
	List<Human> humans = Lists.newArrayList(new Human("Sarah", 10), new Human("Jack", 12));
	humans.sort((Human h1, Human h2) -> h1.getName().compareTo(h2.getName()));
	Assert.assertThat(humans.get(0), equalTo(new Human("Jack", 12)));
}

2、Stream

Stream(流)是Java8的新增的一個庫,它最常見的用法是對集合中的資料進行操作,它就像一個高階版的Iterator,使用者只需要定義好對元素的操作,並將操作應用到元素上就好,而具體的實現演算法則交給類庫的設計者,這樣就很方便的將操作和演算法分開。所有流計算都有一種共同的結構:它們具有一個流來源、0 或多箇中間操作,以及一個終止操作。流的元素可以是物件引用 (Stream<String>),也可以是原始整數 (IntStream)、長整型 (LongStream) 或雙精度 (DoubleStream)。
// 使用傳統的for迴圈的方式:
// 將每個userId查詢到的記錄放在List<UserBaseInfo>中
for (UserBaseInfo userBaseInfo : userBaseInfoList) {
    userId = userBaseInfo.getUserId();
    if (userIDandBaseInfoMap.containsKey(userId)) {
        userBaseInfos = userIDandBaseInfoMap.get(userId);
    } else {
        userBaseInfos = new ArrayList<>();
        userIDandBaseInfoMap.put(userId, userBaseInfos);
    }
    userBaseInfos.add(userBaseInfo);
}    
// 使用stream進行操作
Map<Long, List<UserBaseInfo>> userIDandBaseInfoMap = userBaseInfoList.stream()       
                               .collect(Collectors.groupingBy(UserBaseInfo::getUserId));  // 說明:Collection介面的預設方法建立流     
建立stream的方式:
  • Stream介面的靜態工廠方法of (Java8裡介面可以帶靜態方法)
  • Collection介面預設方法或子類獲取流(Java8對Collection的增強)(見上文例子)
使用Stream介面建立流:
// stream介面的of方法有兩個過載的方法
Stream<String> stringStream = Stream.of("youzan");
 
Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
 
 
// 使用變長引數建立流返回的是一個Arrays的流(原始碼)
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}
 
 
// generator方法的方式
Stream.generate(new Supplier<Double>() {
    @Override
    public Double get() {
        return Math.random();
    }
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);

Java8對Collection介面的增強
public interface Collection<E> extends Iterable<E> {
    default Stream<E> stream() { // 介面中新增了Stream方法,讓集合類可以很方便的建立流
        return StreamSupport.stream(spliterator(), false);
    }
}
使用Stream介面建立流:
// stream介面的of方法有兩個過載的方法
Stream<String> stringStream = Stream.of("youzan");

Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);


// 使用變長引數建立流返回的是一個Arrays的流(原始碼)
@SafeVarargs
@SuppressWarnings("varargs") // Creating a stream from an array is safe
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}


// generator方法的方式
Stream.generate(new Supplier<Double>() {
	@Override
	public Double get() {
		return Math.random();
	}
});
Stream.generate(() -> Math.random());
Stream.generate(Math::random);
流的來源:
方法描述
Collection.stream()使用一個集合的元素建立一個流
Stream.of(T...)使用傳遞給工廠方法的引數建立一個流
Stream.of(T[])使用一個數組的元素建立一個流
Stream.empty()建立一個空流。
Stream.iterate(T first, BinaryOperator<T> f)建立一個包含序列 first, f(first), f(f(first)), ...的無限流
Stream.generate(Supplier<T> f)使用一個生成器函式建立一個無限流
IntStream.range(lower, upper)建立一個由下限到上限(不含)之間的元素組成的IntStream
IntStream.rangeClosed(lower, upper)建立一個由下限到上限(含)之間的元素組成的 IntStream
BufferedReader.lines()建立一個有來自 BufferedReader 的行組成的流
BitSet.stream()建立一個由 BitSet 中的設定位的索引組成的 IntStream
Stream.chars()建立一個與 String 中的字元對應的 IntStream

流的中間操作:
操作內容
filter(Predicate<T>)與預期匹配的流的元素
map(Function<T, U>)將提供的函式應用於流的元素的結果
flatMap(Function<T, Stream<U>>將提供的流處理函式應用於流元素後獲得的流元素
distinct()已刪除了重複的流元素
sorted()按自然順序排序的流元素
Sorted(Comparator<T>)按提供的比較符排序的流元素
limit(long)截斷至所提供長度的流元素
skip(long)丟棄了前 N 個元素的流元素
takeWhile(Predicate<T>)(僅限 Java 9)在第一個提供的預期不是 true 的元素處階段的流元素
dropWhile(Predicate<T>)(僅限 Java 9)丟棄了所提供的預期為 true 的初始元素分段的流元素


流的終止操作:
操作內容
forEach(Consumer<T> action)將提供的操作應用於流的每個元素
toArray()使用流的元素建立一個數組
reduce(...)將流的元素聚合為一個彙總值
collect(...)將流的元素聚合到一個彙總結果容器中
min(Comparator<T>)通過比較符返回流的最小元素
max(Comparator<T>)通過比較符返回流的最大元素
count()返回流的大小
{any,all,none}Match(Predicate<T>)返回流的任何/所有元素是否與提供的預期相匹配
findFirst()返回流的第一個元素(如果有)
findAny()返回流的任何元素(如果有)

3、Optional

Optional:Optional是Java8新引進的一個允許為null的容器物件,它的到來可以使程式設計師減少與NullPointException(NPE)打交道的次數。Optional提供了一種優雅的Java風格的方法來解決null安全問題。
// Java8之前想獲取一個實體的某一個屬性是這樣的
public String getName(Employee em) {
    if (em == null) {
        return "Unknow employee name";
    }


	return em.getName();
}


// 使用Optional優雅的獲取
public String getName(Employee em) {
    Optional.ofNullable(em).map(employee -> employee.getName()).orElse("Unknow employee name");
}

建立Optional物件:

  • of方法
  • ofNullable方法(推薦)
// 使用of方法,但是它仍然有可能報NPE
Optional<String> optional = Optional.of("youzan");


// 使用of方法,不會報NPE
Optional<String> optional = Optional.ofNullable("youzan");


// 通過原始碼來看為什麼of方法會報NPE
public static <T> Optional<T> of(T value) {
    return new Optional<>(value); // 新建一個物件
}
// 呼叫顯式建構函式去新建物件
private Optional(T value) {
    this.value = Objects.requireNonNull(value);
}
// Objects工具類的requireNonNull方法遇到null會報NPE
public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}


// 接下來還是通過原始碼看看ofNullable方法是怎麼樣做到允許null的
public static <T> Optional<T> ofNullable(T value) {
    return value == null ? empty() : of(value); // 如果value為null則呼叫empty方法
}
// 隱藏在empty方法中的祕密
public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY; // 返回一個EMPTY物件
    return t;
}
// EMPTY的廬山真面目
private static final Optional<?> EMPTY = new Optional<>();
// Optional提供的無參建構函式將返回一個值空的Optional物件
private Optional() {
    this.value = null; // value是用來儲存物件的值的
}

orElse方法:物件的值為null則返回引數傳進來的值
// 使用姿勢
String string = Optional.ofNullable("youzan").orElse("Unknow");


// orElse方法揭祕
public T orElse(T other) {
    return value != null ? value : other; // 如果當前的Optional物件為null則返回other
}

orElseGet方法:orElseGet與orElse方法類似,區別在於得到的預設值。orElse方法將傳入的字串作為預設值,orElseGet方法可以接受Supplier介面的實現用來生成預設值。
// orElseGet方法原始碼
public T orElseGet(Supplier<? extends T> other) {
    return value != null ? value : other.get();
}

orElseThrow方法:如果物件值為null則丟擲一個異常
// 使用姿勢
Optional.ofNullable(object).orElseThrow(NoSuchElementException::new);


// orElseThrow方法原始碼
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get(); // 嗯,這裡能幫你丟擲一個異常就是了
    }
}
map方法:map方法用來對Optional例項的值執行一系列操作。通過一組實現了Function介面的lambda表示式傳入操作。
// 使用姿勢
Optional<String> upperName = name.map((value) -> value.toUpperCase());


// map方法原始碼
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper); // 如果形參mapper為null,則報NPE
    if (!isPresent())
        return empty(); // 返回一個不包含值的Optional物件
    else {
        return Optional.ofNullable(mapper.apply(value));
    }
}
// isPresent方法是檢視當前optional物件的值是否有值
public boolean isPresent() {
    return value != null;
}

flatMap方法:flatMap方法與map方法類似,區別在於mapping函式的返回值不同。map方法的mapping函式返回值可以是任何型別T,而flatMap方法的mapping函式必須是Optional。
filter方法:filter個方法通過傳入限定條件對Optional例項的值進行過濾。
// filter方法檢查給定的Option值是否滿足某些條件。
// 如果滿足則返回同一個Option例項,否則返回空Optional。
Optional<String> name = Optional.of("Sana");
Optional<String> longName = name.filter((value) -> value.length() > 6);
System.out.println(longName.orElse("The name is less than 6 characters")); // 輸出Sanaulla