Java8中的新特性Optional

Optional 類是一個能夠爲null的容器對象。若是值存在則isPresent()方法會返回true,調用get()方法會返回該對象。
Optional 是個容器:它能夠保存類型T的值,或者僅僅保存null。Optional提供不少有用的方法,這樣咱們就不用顯式進行空值檢測。
Optional 類的引入很好的解決空指針異常。html

package com.demo.optional;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestOptional {

    @Test
    public void testOptional() {
        TestOptional test = new TestOptional();
        Integer value1 = null;
        Integer value2 = new Integer(10);

        // Optional.ofNullable - 容許傳遞爲 null 參數
        Optional<Integer> a = Optional.ofNullable(value1);

        // Optional.of - 若是傳遞的參數是 null,拋出異常 NullPointerException
        Optional<Integer> b = Optional.of(value2);
        log.info("{}", test.sum(a, b));
    }

    public Integer sum(Optional<Integer> a, Optional<Integer> b) {
        // Optional.isPresent - 判斷值是否存在
        log.info("第一個參數值存在: {}", a.isPresent());
        log.info("第二個參數值存在: {}", b.isPresent());

        // Optional.orElse - 若是值存在返回它,不然返回默認值
        Integer value1 = a.orElse(new Integer(0));

        //Optional.get - 獲取值,值須要存在
        Integer value2 = b.get();

        return value1 + value2;
    }
}

 在Java8以前,任何訪問對象方法或屬性的調用均可能致使 NullPointerException。若是須要確保不觸發異常,就得在訪問每個值以前對其進行明確地檢查。java

 一、明確對象不爲null的時候使用of()。若是對象便可能是null也多是非null,你就應該使用ofNullable()方法。spring

package com.demo.optional;

import com.demo.optional.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.NoSuchElementException;
import java.util.Optional;

import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {

    @Test(expected = NoSuchElementException.class)
    public void whenCreateEmptyOptionalThenNull() {
        Optional<User> emptyOpt = Optional.empty();
        emptyOpt.get();
    }

    //明確對象不爲null的時候使用of()
    @Test(expected = NullPointerException.class)
    public void whenCreateOfEmptyOptionalThenNullPointerException() {
        User user = null;
        Optional<User> opt = Optional.of(user);
    }

    //若是對象便可能是null也多是非null,你就應該使用ofNullable()方法
    @Test
    public void whenCreateOfEmptyOptionalThenNoNullPointerException() {
        User user = null;
        Optional<User> opt = Optional.ofNullable(user);
    }
}

 二、訪問Optional對象的值dom

package com.demo.optional;

import com.demo.optional.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.NoSuchElementException;
import java.util.Optional;

import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {

    //從Optional實例中取回實際值對象的方法之一是使用get()方法
    @Test
    public void whenCreateOfNullableOptionalThenOk() {
        String name = "John";
        Optional<String> opt = Optional.ofNullable(name);
        assertEquals("John", opt.get());
    }

    //get()方法會在值爲null的時候拋出異常。要避免異常,你能夠選擇首先驗證是否有值
    @Test
    public void whenCheckIsPresentThenOk() {
        User user = new User("john@gmail.com", "1234");
        Optional<User> opt = Optional.ofNullable(user);
        assertTrue(opt.isPresent());

        assertEquals(user.getEmail(), opt.get().getEmail());
    }

    //檢查是否有值的另外一個選擇是ifPresent()方法。該方法除了執行檢查,還接受一個Consumer(消費者)參數,若是對象不是空的,就對執行傳入的Lambda表達式
    @Test
    public void whenCheckIfPresentThenOk() {
        User user = new User("john@gmail.com", "1234");
        Optional<User> opt = Optional.ofNullable(user);

        opt.ifPresent(u -> assertEquals(user.getEmail(), u.getEmail()));
    }
}

 三、返回默認值:Optional類提供了API用以返回對象值,或者在對象爲空的時候返回默認值。函數

package com.demo.optional;

import com.demo.optional.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;
import static org.junit.Assert.assertEquals;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {

    //orElse()若是有值則返回該值,不然返回傳遞給它的參數值
    @Test
    public void whenEmptyValueThenReturnDefault() {
        User user = null;
        User user2 = new User("anna@gmail.com", "1234");
        User result = Optional.ofNullable(user).orElse(user2);

        assertEquals(user2.getEmail(), result.getEmail());
    }

    //若是對象的初始值不是 null,那麼默認值會被忽略
    @Test
    public void whenValueNotNullThenIgnoreDefault() {
        User user = new User("john@gmail.com", "1234");
        User user2 = new User("anna@gmail.com", "1234");
        User result = Optional.ofNullable(user).orElse(user2);

        assertEquals("john@gmail.com", result.getEmail());
    }

    //orElseGet()方法會在有值的時候返回值,若是沒有值,它會執行做爲參數傳入的Supplier(供應者)函數式接口,並將返回其執行結果
    @Test
    public void testOrElseGet() {
        User user = new User("john@gmail.com", "1234");
        User user2 = new User("anna@gmail.com", "1234");
        User result = Optional.ofNullable(user).orElseGet(() -> user2);

        assertEquals("john@gmail.com", result.getEmail());
    }
}

 四、orElse()和orElseGet()的不一樣之處性能

package com.demo.optional;

import com.demo.optional.domain.User;
import jdk.nashorn.internal.runtime.regexp.joni.constants.OPCode;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.NoSuchElementException;
import java.util.Optional;

import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {
    /*
     * 對象爲空時,兩種方法都調用了createNewUser()方法,這個方法會記錄一個消息並返回User對象。
     * 當對象爲空而返回默認對象時,行爲並沒有差別。
     */
    @Test
    public void givenEmptyValueWhenCompareThenOk() {
        User user = null;
        log.info("Using orElse");
        User result = Optional.ofNullable(user).orElse(createNewUser());
        log.info("User orElseGet");
        User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
    }

    /*
     * 兩個Optional對象都包含非空值,兩個方法都會返回對應的非空值。orElse()方法仍然建立了User對象。與之相反,orElseGet()方法不建立 User 對象。
     * 在執行較密集的調用時,好比調用Web服務或數據查詢,這個差別會對性能產生重大影響。
     */
    @Test
    public void givenPresentValueWhenCompareThenOk() {
        User user = new User("john@gmail.com", "1234");
        log.info("Using orElse");
        User result = Optional.ofNullable(user).orElse(createNewUser());
        log.info("Using orElseGet");
        User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
    }

    private User createNewUser() {
        log.info("Creating New User");
        return new User("extra@gmail.com", "1234");
    }
}

 五、返回異常測試

除了orElse()和orElseGet()方法,Optional還定義了orElseThrow(),它會在對象爲空的時候拋出異常,而不是返回備選的值。this

package com.demo.optional;

import com.demo.optional.domain.User;
import jdk.nashorn.internal.runtime.regexp.joni.constants.OPCode;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.NoSuchElementException;
import java.util.Optional;

import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {
    /*
     * orElseThrow()方法有更豐富的語義,能夠決定拋出什麼樣的異常,而不老是拋出 NullPointerException。
     * 若是 user 值爲 null,會拋出 IllegalArgumentException。
     */
    @Test(expected = IllegalArgumentException.class)
    public void whenThrowExceptionThenOk() {
        User user = null;
        User result = Optional.ofNullable(user)
                .orElseThrow(() -> new IllegalArgumentException());
    }
}

 六、轉換值.net

package com.demo.optional;

import com.demo.optional.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;

import static org.junit.Assert.assertEquals;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {
    //若是建立的Optional中的值存在,對該值執行提供的Function函數調用
    @Test
    public void whenMapThenOk() {
        User user = new User("anna@gmail.com", "1234");
        String email = Optional.ofNullable(user)
                .map(u -> u.getEmail()).orElse("default@gmail.com");
        assertEquals(email, user.getEmail());
    }

    //若是建立的Optional中的值存在,就對該值執行提供的Function函數調用,返回一個Optional類型的值,不然就返回一個空的Optional對象
    @Test
    public void whenFlatMapThenOk() {
        User user = new User("anna@gmail.com", "1234");
        user.setPosition("Developer");
        String position = Optional.ofNullable(user)
                .flatMap(u -> u.getPosition()).orElse("default");

        assertEquals(position, user.getPosition().get());
    }
}

 七、過濾值指針

filter()接受一個Predicate參數,返回測試結果爲true的值。若是測試結果爲false,會返回一個空的Optional。

package com.demo.optional;

import com.demo.optional.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;

import static junit.framework.TestCase.assertTrue;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {
    @Test
    public void whenFilterThenOk() {
        User user = new User("anna@gmail.com", "1234");
        Optional<User> result = Optional.ofNullable(user)
                .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));

        assertTrue(result.isPresent());
    }
}

 八、Optional類的鏈式方法

爲了更充分的使用Optional,你能夠連接組合其大部分方法,由於它們都返回相同相似的對象。

package com.demo.optional;

import com.demo.optional.domain.Address;
import com.demo.optional.domain.Country;
import com.demo.optional.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;

import static org.junit.Assert.assertEquals;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {
    @Test
    public void whenChainingThenOk() {
        User user = new User("anna@gmail.com", "1234");

        String result = Optional.ofNullable(user)
                .flatMap(u -> u.getAddress())
                .flatMap(a -> a.getCountry())
                .map(c -> c.getIsocode())
                .orElse("default");

        String res = Optional.ofNullable(user)
                .flatMap(User::getAddress)
                .flatMap(Address::getCountry)
                .map(Country::getIsocode)
                .orElse("default");

        assertEquals(result, "default");
        assertEquals(res, "default");
    }
}

 實體類User:

package com.demo.optional.domain;

import lombok.Data;

import java.util.Optional;

@Data
public class User {
    private Integer id;
    private String email;
    private String number;
    private String position;
    private Address address;

    public Optional<Address> getAddress() {
        return Optional.ofNullable(address);
    }

    public Optional<String> getPosition() {
        return Optional.ofNullable(position);
    }

    public User(String email, String number) {
        this.email = email;
        this.number = number;
    }
}

實體類Address:

package com.demo.optional.domain;

import lombok.Data;

import java.util.Optional;

@Data
public class Address {
    private Country country;

    public Optional<Country> getCountry() {
        return Optional.ofNullable(country);
    }
}

 實體類Country:

package com.demo.optional.domain;

import lombok.Data;

@Data
public class Country {
    private Integer id;
    private String isocode;
}

 九、Java9加強

Java9爲Optional類添加了三個方法:or()、ifPresentOrElse() 和 stream()。
or()方法與orElse()和orElseGet()相似,它們都在對象爲空的時候提供了替代狀況。
or()的返回值是由Supplier參數產生的另外一個Optional對象。若是對象包含值,則Lambda表達式不會執行。

package com.demo.optional;

import com.demo.optional.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;

import static org.junit.Assert.assertEquals;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {
    @Test
    public void whenEmptyOptionalThenGetValueFromOr() {
        User user=null;
        User result = Optional.ofNullable(user)
                .or( () -> Optional.of(new User("default","1234"))).get();

        assertEquals(result.getEmail(), "default");
    }
}

 ifPresentOrElse()方法須要兩個參數:一個Consumer和一個Runnable。若是對象包含值,會執行Consumer的動做,不然運行Runnable。

若是想在有值的時候執行某個動做,或者只是跟蹤是否認義了某個值,那麼這個方法很是有用。

package com.demo.optional;

import com.demo.optional.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {
    @Test
    public void testIfPresentOrElse() {
        User user = new User("anna@gmail.com", "1234");
        Optional.ofNullable(user).ifPresentOrElse(u -> log.info("User is:" + u.getEmail()),
                () -> log.info("User not found"));
    }
}

 stream()方法把實例轉換爲Stream對象。Stream的使用帶來了其filter()、map()和collect()接口,以獲取List。

package com.demo.optional;

import com.demo.optional.domain.Address;
import com.demo.optional.domain.Country;
import com.demo.optional.domain.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.stream.Collectors;

import static junit.framework.TestCase.assertTrue;
import static org.junit.Assert.assertEquals;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class OptionalTest {
    @Test
    public void testStream() {
        User user = new User("john@gmail.com", "1234");
        List<String> emails = Optional.ofNullable(user)
                .stream()
                .filter(u -> u.getEmail() != null && u.getEmail().contains("@"))
                .map(u -> u.getEmail())
                .collect(Collectors.toList());

        assertTrue(emails.size() == 1);
        assertEquals(emails.get(0), user.getEmail());
    }
}

Optional不是Serializable。所以,它不該該用做類的字段。
若是須要序列化的對象包含Optional值,Jackson庫支持把Optional看成普通對象。也就是說,Jackson會把空對象看做null,而有值的對象則把其值看做對應域的值。這個功能在jackson-modules-java8項目中。

參考:

https://www.cnblogs.com/zhangboyu/p/7580262.html

https://blog.csdn.net/zknxx/article/details/78586799

相關文章
相關標籤/搜索