1. 程式人生 > >java程式碼之美(16) ---Java8 Optional

java程式碼之美(16) ---Java8 Optional

Java8 Optional

一句話介紹Optional類:使用JDK8的Optional類來防止NullPointerException(空指標異常)問題

一、前言

在我們開放過程中,碰到的異常中NullPointerException必然是排行第一的。所以在平時編碼中,我們會時時的判斷null。

public void saveCity(City city) {
        if (city != null) {
            String cityName = city.getCityName();
            if (cityName != null) {
                String code = cityDao.findCodeByName(cityName);
                city.setCode(code);
                cityDao.save(city);
            }
        }
    }

雖然上面程式碼變得更加安全,但是過多巢狀 if 語句降低程式碼整體可讀性,提高複雜度。我們可以優化下程式碼

    public void saveCity(City city) {
        if (city == null) {
            return;
        }
        String cityName = city.getCityName();
        if (cityName == null) {
            return;
        }
        String code = cityDao.findCodeByName(cityName);
        city.setCode(code);
        cityDao.save(city);
    }

這樣還可以,但我們通過Optional變的更簡潔

    public void saveCity(City city) {
        //就一行 city不為空返回 城市名稱 否則直接返回空
        Optional<String> roleOpt = Optional.ofNullable(city).map(City::getCityName);
        //如果容器中 不為空
        if (roleOpt.isPresent()) {
            String code = cityDao.findCodeByName(roleOpt.get());
            city.setCode(code);
            cityDao.save(city);
        }
    }

這樣,我們僅需要對我們關心的做一次校驗,省卻了前面的一系列的檢驗操作。


二、Optional API

概念 Optiona本質是一個容器,容器中存在為null或者不包含非null值的容器物件。提供了一系列的方法供我們判斷該容器裡的物件是否存在。

1、JDK原始碼

/**
 * final修飾代表不能被子類繼承
 */
public final class Optional<T> {
    /**
     * 建立一個空容器
     */
    private static final java.util.Optional<?> EMPTY = new java.util.Optional<>();

    /**
     * 傳入的值
     */
    private final T value;

    /**
     * 建構函式私有化 說明不能被外部new
     */
    private Optional() {
        this.value = null;
    }

    /**
     * 私有化建構函式
     */
    private Optional(T value) {
        this.value = Objects.requireNonNull(value);
    }

    /**
     * 獲取空容器
     */
    public static <T> java.util.Optional<T> empty() {
        @SuppressWarnings("unchecked")
        java.util.Optional<T> t = (java.util.Optional<T>) EMPTY;
        return t;
    }


    /**
     * 傳入的物件不能為空 否則拋異常
     */
    public static <T> java.util.Optional<T> of(T value) {
        return new java.util.Optional<>(value);
    }

    /**
     * 傳入的物件可以為空
     */
    public static <T> java.util.Optional<T> ofNullable(T value) {
        return value == null ? empty() : of(value);
    }

    /**
     * 獲取容器物件的方法 注意 如果用這個方法則代表容器中一定有物件,否則拋異常
     */
    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }

    /**
     * 判斷容器物件是否為空
     */
    public boolean isPresent() {
        return value != null;
    }

    /**
     * 如果容器物件為空 則返回當前物件
     */
    public T orElse(T other) {
        return value != null ? value : other;
    }

    //==========有關下面這幾個JDK8自帶的函式式介面的作用,上一篇部落格有詳細說明,這裡就不多說了。

    /**
     * 傳入Consumer程式設計式介面引數
     */
    public void ifPresent(Consumer<? super T> consumer) {
        if (value != null)
            consumer.accept(value);
    }

    /**
     * 傳入Predicate程式設計式介面引數
     */
    public java.util.Optional<T> filter(Predicate<? super T> predicate) {
        Objects.requireNonNull(predicate);
        if (!isPresent())
            return this;
        else
            return predicate.test(value) ? this : empty();
    }

    /**
     * 傳入Function程式設計式介面引數
     */
    public <U> java.util.Optional<U> map(Function<? super T, ? extends U> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return java.util.Optional.ofNullable(mapper.apply(value));
        }
    }

    /**
     * 傳入Function程式設計式介面引數
     */
    public <U> java.util.Optional<U> flatMap(Function<? super T, java.util.Optional<U>> mapper) {
        Objects.requireNonNull(mapper);
        if (!isPresent())
            return empty();
        else {
            return Objects.requireNonNull(mapper.apply(value));
        }
    }

    /**
     * 傳入Supplier程式設計式介面引數
     */
    public T orElseGet(Supplier<? extends T> other) {
        return value != null ? value : other.get();
    }

    /**
     * 傳入Supplier程式設計式介面引數
     */
    public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
        if (value != null) {
            return value;
        } else {
            throw exceptionSupplier.get();
        }
    }
}

2、建立Optional物件

通過上面原始碼可以看出,Optional的建構函式都是私有化的,無法直接new物件。它這邊提供了3個靜態方法獲取物件。

1、建立一個一定是空的Optional容器

Optional<Car> optCar = Optional.empty();

2、建立一個一定是非空值Optional容器(傳入的物件不可以為null,否則丟擲NullPointerException)

Optional<Car> optUser = Optional.of(user);

3、建立一個可能是空也可能不為空的Optional容器(傳入的物件可以為null)

Optional<Car> optUser = Optional.ofNullable(user);

3、總結常用方法

1、isPresent()        //有值則返回true
2、get():             //值存在時返回值,否則丟擲一個NoSuchElement異常(所以調這個,一般先判斷上面方法返回是否為true)
3、orElse(T other)    //值存在時返回值,否則返回一個預設值
4、ifPresent(Consumer<T> block)             //會在值存在的時候執行給定的程式碼塊
5、orElseThrow(Supplier<? extends X> exceptionSupplier)  //與get()類似,不同的是可以自定義異常型別
6、orElseGet(Supplier<? extends T> other)   //orElse方法的延遲呼叫版,Supplier方法只有在Optional物件不含值時才執行呼叫
7、map/flatMap/filter                       //與Stream中用法類似


三、完整的示例

這裡寫一個針對以上API都涉及到的Demo,這個例子明白了,那麼Optional的使用也就都清楚了。

程式碼

public class OptionalDemo {
    public static void main(String[] args) {
        //1、建立Optional例項,傳入的物件不能為null
        Optional<String> nameOptional = Optional.of("張三");

        //2、建立Optional例項,傳入物件可以為null,也可以不weinull
        Optional emptyOptional = Optional.ofNullable(null);

        //3、isPresent方法用來檢查Optional例項是否有值。
        if (nameOptional.isPresent()) {
            //呼叫get()返回Optional值。
            System.out.println("1、" + nameOptional.get());
        }

        try {
            //4、在Optional例項上呼叫get()丟擲NoSuchElementException。
            System.out.println("2、" + emptyOptional.get());
        } catch (NoSuchElementException ex) {
            System.out.println("3、異常" + ex.getMessage());
        }

        //
        //5、如果Optional值不為空,lambda表示式會處理並在其上執行操作。(這裡x代表就是nameOptional中的物件)
        nameOptional.ifPresent((x) -> {
            System.out.println("4、字串長度為: " + x.length());
        });

        //6、如果有值orElse方法會返回Optional例項,沒值則返回當前值
        System.out.println("5、"+ emptyOptional.orElse("如果是空容器則返回李四"));
        System.out.println("6、"+nameOptional.orElse("如果是空容器則返回王五"));

        //7、orElseGet與orElse類似,區別在於傳入的引數不同,一個是直接傳入物件,這個是傳入Supplier函式式介面
        System.out.println("7、" + emptyOptional.orElseGet(() -> "李四"));
        System.out.println("8、" + nameOptional.orElseGet(() -> "王五"));

        try {
            //8、如果是空容器,則可以丟擲自定義異常。
            emptyOptional.orElseThrow(() -> new NullPointerException("空容器異常"));
        } catch (Throwable ex) {
            System.out.println("9、" + ex.getMessage());
        }

        Optional<String> ageOptional = Optional.of("10");
        //9、這裡入參是Function,所以可以轉換容器中的物件 好比將String物件轉為Integer物件
        Optional<Integer> age = ageOptional.map((value) -> Integer.parseInt(value));
        /**
         * 10、flatMap與map(Funtion)非常相似,不同在於 map返回可以將String物件轉為Integer物件,但flatMap轉換後一定還是String物件
         */
        Optional<String> upperName = nameOptional.flatMap((value) -> Optional.of(value.toUpperCase()));

        //11、filter方法檢查Optiona值是否滿足給定條件。如果滿足返回Optional例項值,否則返回空Optional。
        Optional<String> longName = nameOptional.filter((value) -> value.length() > 6);
        System.out.println("10、" + longName.orElse("longName容器的名字長度小於6位"));

        //12、另一個示例,Optional滿足給定條件。
        Optional<String> anotherName = Optional.of("烏啦啦市長公主");
        Optional<String> shortName = anotherName.filter((value) -> value.length() > 6);
        System.out.println("11、" + shortName.orElse("anotherName容器的名字長度小於6位"));

    }
}

執行結果


參考

1、JDK8新特性之:Optional

2、Optional類包含的方法介紹及其示例




你如果願意有所作為,就必須有始有終。(26)