使用Java Optional類的最佳實踐 - oracle

發表於2021-03-06

請遵循以下十二種最佳實踐,以保護您的應用程式免受醜陋的空指標異常的侵擾,並使您的程式碼更具可讀性和簡潔性。

每個認真的Java開發人員或架構師都曾經聽說過或經歷過NullPointerException異常的滋擾。Java工程師一直致力於解決該null問題很長時間,在Java 8中,新增了一個稱為的新型別Optional<T>,Optional原本是一種返回型別,當與流(或return的方法Optional)組合以構建流暢的API時使用。此外,它旨在幫助開發人員null正確處理引用。

下面談談如何避免使用Optional反模式? 或者說不正確用法:

 

1:切勿將null分配給可選變數。

有時,當開發人員正在處理資料庫以尋找Employee時,他們設計了return的方法Optional<Employee>。我發現null如果資料庫沒有返回任何結果,開發人員仍會返回,例如:

public Optional<Employee> getEmployee(int id) {
 // perform a search for employee 
  Optional<Employee> employee = null; // in case no employee
   return employee; 
}

上面的程式碼不正確,應該完全避免。要更正它,您應該將第3行替換為以下行,該行Optional以空初始化Optional:

Optional<Employee> employee = Optional.empty();

 

2:不要直接呼叫get()

Optional<Employee> employee = HRService.getEmployee();
Employee myEmployee = employee.get();

您是否猜到“Employee”Optional裡面是空著,所以get()直接打電話會丟擲一個“ java.util.NoSuchElementException?”。您應該始終首先使用該isPresent()方法檢查值的存在,如下所示:

if (employee.isPresent()) {
    Employee myEmployee = employee.get();
    ... // do something with "myEmployee"
} else {
    ... // do something that doesn't call employee.get()
}

請注意,上面的程式碼是樣板程式碼,現實中這樣做是不可取的。接下來,您將看到很多優雅的呼叫isPresent()/get()對替代方法。

 

3. 避免使用isPresent()和get()對來設定和返回值。

public static final String DEFAULT_STATUS = "Unknown";
...
public String getEmployeeStatus(long id) {
    Optional<String> empStatus = ... ;
    if (empStatus.isPresent()) {
        return empStatus.get();
    } else {
        return DEFAULT_STATUS;
    }
}

只需使用orElse()替換isPresent()和get()配對,如以下:

public String getEmployeeStatus(long id) {
    Optional<String> empStatus = ... ;
    return empStatus.orElse(DEFAULT_STATUS); 
}

這裡要考慮的一個非常重要的注意事項是可能的效能損失:orElse()無論可選值的存在與否,總是會評估返回的值。因此,這裡的規則是orElse()在您已經預先構造了值並且不使用昂貴的計算值時使用。

 

4. 請勿使用orElse()返回計算值。

避免使用orElse()返回計算值,因為這會降低效能。考慮以下程式碼片段:

Optional<Employee> getFromCache(int id) {
    System.out.println("search in cache with Id: " + id);
    // get value from cache
}

Optional<Employee> getFromDB(int id) {
    System.out.println("search in Database with Id: " + id);    
    // get value from database
}

public Employee findEmployee(int id) {        
    return getFromCache(id)
            .orElse(getFromDB(id)
                    .orElseThrow(() -> new NotFoundException("Employee not found with id" + id)));}

首先,程式碼嘗試從快取中獲取具有給定ID的Employee,如果該Employee不在快取中,則嘗試從資料庫中獲取Employee。然後,如果員工不在快取或資料庫中,則程式碼將引發一個NotFoundException。如果執行此程式碼,而僱員在快取中,則會列印以下內容:

Search in cache with Id: 1
Search in Database with Id: 1

即使將Employee從快取中返回,資料庫查詢仍然被呼叫。很昂貴,對不對?取而代之的是,我將使用orElseGet(Supplier<? extends T> supplier),就像,orElse()但有一個區別:如果theOptional為空,則orElse()直接返回預設值,而orElseGet()允許您傳遞Supplier僅當theOptional為空時才呼叫的函式。這對效能非常有用。

public Employee findEmployee(int id) {        
    return getFromCache(id)
        .orElseGet(() -> getFromDB(id)
            .orElseThrow(() -> {
                return new NotFoundException("Employee not found with id" + id);
            }));
}

這次,您將獲得所需的內容並提高了效能:該程式碼將僅列印以下內容:

Search in cache with Id: 1

不要考慮使用isPresent()和get()配對,因為它們並不雅緻。

orElseThrow()方法自Java 10起就存在。

 

5.如果僅當存在Optional值時才想執行操作,請不要使用isPresent()-get()。

Optional<String> confName = Optional.of("CodeOne");
if(confName.isPresent())
    System.out.println(confName.get().length());

只需將上面的第2行和第3行替換為一行,如下所示:

confName.ifPresent( s -> System.out.println(s.length()));

ifPresent()方法不返回任何內容,並且自Java 8起就存在。

6.
如果不存在值,則不要使用isPresent()-get()執行基於空的操作。

開發人員有時會編寫程式碼,如果存在Optional值,該程式碼會執行某些操作,但如果不存在,則執行基於空的操作,如下所示:

Optional<Employee> employee = ... ;
 if(employee.isPresent()) {
    log.debug("Found Employee: {}" , employee.get().getName());
 } else {
    log.error("Employee not found");
 }

請注意,ifPresentOrElse()就像是ifPresent(),唯一的區別是,它涵蓋了else分支為好。因此,您可以將第2行到第6行替換為:

employee.ifPresentOrElse(
emp -> log.debug("Found Employee: {}",emp.getName()), 
() -> log.error("Employee not found"));

ifPresentOrElse()方法自Java 9起就存在。

 

7.不存在任何值時,返回另一個Optional。

在某些情況下,如果Optional中有值存在,則返回一個Optional描述值的描述;否則,返回Optional由供應功能產生的產品。避免執行以下操作:

Optional<String> defaultJobStatus = Optional.of("Not started yet.");
public Optional<String> fetchJobStatus(int jobId) {
    Optional<String> foundStatus = ... ; // fetch declared job status by id
    if (foundStatus.isPresent())
        return foundStatus;
    else
        return defaultJobStatus; 
}

不要過度使用orElse()ororElseGet()方法來完成此操作,因為這兩個方法都返回一個未包裝的值。因此,也請避免執行以下操作:

public Optional<String> fetchJobStatus(int jobId) {
    Optional<String> foundStatus = ... ; // fetch declared job status by id
    return foundStatus.orElseGet(() -> Optional.<String>of("Not started yet."));
}

完美而優雅的解決方案是使用該 (Supplier<? extends Optional<? extends T>> supplier)方法,如下所示:

 public Optional<String> fetchJobStatus(int jobId) {
    Optional<String> foundStatus = ... ; // fetch declared job status by id
    return foundStatus.or(() -> defaultJobStatus);
 }

或者,即使沒有defaultJobStatus在開始時定義可選的內容,也可以使用以下程式碼替換第3行中的程式碼:

return foundStatus.or(() -> Optional.of("Not started yet."));

如果提供函式為或產生結果,則or()丟擲NullPointerExceptionnull異常。從Java 9開始就存在此方法。

 

8.無論是否為空,都獲取Optional的狀態。

從Java 11開始,您可以Optional使用isEmpty()方法直接檢查an是否為空。

public boolean isMovieListEmpty(int id){
    Optional<MovieList> movieList = ... ;
    return !movieList.isPresent();
 }

您可以將第3行替換為以下行,以使程式碼更具可讀性:

return movieList.isEmpty();

 

9.不要過度使用Optional。

有時,開發人員傾向於過度使用他們喜歡的東西,而Optional類就是其中之一。

1 public String fetchJobStatus(int jobId) {
2    String status = ... ; // fetch declared job status by id
3    return Optional.ofNullable(status).orElse("Not started yet.");
4 }

通過使用以下更清晰的程式碼行替換第3行,可以很簡單:

return status == null ? "Not started yet." : status;

 

相關文章