Java隨談(三)如何創建好一個對象?

本文推薦閱讀時間30分鐘

 

大家都知道,在編寫Java程式里,一般就是處理各種各樣的對象,那麼,你知道一共有多少種創建對象的方式嗎?

希望大家能稍微思考一下再往下翻。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

答案是4種

  • new 一個對象
  • 反射一個類實例化一個對象(反射類和反射構造方法)
  • clone 一個對象
  • 反序列化一個對象

前兩者調用了構造方法,後兩者沒有調用。

其中,日常使用中,最為常見的是使用new關鍵字創建對象;而在框架之中,最常用的是反射來控制對象生成。

 

下面詳細地梳理常見的new對象的邏輯

  1. 若編寫一個新的類,此時沒有任何額外的需求時,採用最簡單的構造方法即可。
  2. 若對象屬性很多,但其中必傳屬性不多,可以使用set設置屬性,或者重疊構造器模式。
  3. 若對象的屬性和方法的傳參類似,可以使用org.springframework.beans.BeanUtils.copyProperties()方法搭配set方法來設置參數。
  4. 若對象需要有一些特殊功能,比如單例,能夠快取等,可以使用靜態工廠方法。
  5. 若對象需要一次性構建(創建不可變對象),使用建造者模式。
  6. 若對象為底層資源,會被各種方法依賴,使用依賴注入。

1.1 Java原生構造方法,和類同名即可

public class Order {
    private String code;
    private List<String> offers;
    private Map<String, Object> features;
    public Order() {
    }
    public Order(String code, List<String> offers, Map<String, Object> features) {
        this.code = code;
        this.offers = offers;
        this.features = features;
    }
}

 

2.1 重疊構造器模式

public class Order {
    // not null!
    private String code;
    private List<String> offers;
    private Map<String, Object> features;
    //傳入code, offers設為空集合, features設為空表
    public Order(String code) {
        this(code, new ArrayList<>(), new HashMap<>());
    }
    //傳入code, offers, features設為空表
    public Order(String code, List<String> offers) {
        this(code, offers, new HashMap<>());
    }
    //傳入code,features,offers設為空集合
    public Order(String code, Map<String, Object> features) {
        this(code, new ArrayList<>(), features);
    }
    //傳入code, offers, features
    public Order(String code, List<String> offers, Map<String, Object> features) {
        this.code = code;
        this.offers = offers;
        this.features = features;
    }
}

 

2.2 set設置屬性

public class Order {
    private String code;
    private List<String> offers;
    private Map<String, Object> features;

    public String getCode() {
        return code;
    }
    public void setCode(String code) {
        this.code = code;
    }
    public List<String> getOffers() {
        return offers;
    }
    public void setOffers(List<String> offers) {
        this.offers = offers;
    }
    public Map<String, Object> getFeatures() {
        return features;
    }
    public void setFeatures(Map<String, Object> features) {
        this.features = features;
    }
}

 

3.1 從傳參中獲取屬性, 使用 org.springframework.beans.BeanUtils

//為省略程式碼量,使用lombok的@Data

//Order.java
@Data
public class Order {
    private String code;
    private List<String> offers;
    private Map<String, Object> features;
}

//FooModel.java
//和Order.java的屬性一致
@Data
public class FooModel {
    private String code;
    private List<String> offers;
    private Map<String, Object> features;
}

//FooTest.class

import org.springframework.beans.BeanUtils;

public class FooTest {
    public void handleFoo(FooModel model) {
        Order order = new Order();
        BeanUtils.copyProperties(model, order);
        //處理order對象
    }
}

 

4.1 靜態工廠方法單例對象

/**
 * 枚舉來創建單例對象有以下優勢
 * 1.構造方法已私有化
 * 2.是否多執行緒安全
 * 3.支援序列化機制,防止多次序列化創建對象
 */
public enum Singleton {
    INSTANCE;
    public void method() {
    //函數處理
    }
}

 

4.2 靜態工廠方法快取對象

/**
 * 下面以經典的Boolean.java來舉例
 * Boolean類創建了兩個常量屬性,TRUE和FALSE
 * 在調用valueOf時使用這個快取
 */
public final class Boolean implements java.io.Serializable,
                                      Comparable<Boolean>
{
    //快取 真
    public static final Boolean TRUE = new Boolean(true);
    //快取 假
    public static final Boolean FALSE = new Boolean(false);

    private final boolean value;

    public Boolean(boolean value) {
        this.value = value;
    }

    //返回快取值,不新創建對象
    public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
}

 

5.1 Java8之前建造者模式(不依賴第三方庫)

public class Order {
    private String code;
    private List<String> offers;
    private Map<String, Object> features;
    
    public static Builder builder() {
        return new Builder();
    }
    //私有化構造方法,只提供給Builder.build()使用
    private Order(String code, List<String> offers, Map<String, Object> features) {
        this.code = code;
        this.offers = offers;
        this.features = features;
    }

    public String toString() {
        return "Order(code=" + this.code + ", offers=" + this.offers + ", features=" + this.features + ")";
    }
    
    public static class Builder {
        private String code;
        private List<String> offers;
        private Map<String, Object> features;

        public Builder code(String code) {
            this.code = code;
            return this;
        }
        public Builder offers(List<String> offers) {
            this.offers = offers;
            return this;
        }
        public Builder offer(String offer) {
            if (null == this.offers) {
                this.offers = new ArrayList<>();
            }
            this.offers.add(offer);
            return this;
        }
        public Builder features(Map<String, Object> features) {
            this.features = features;
            return this;
        }
        public Builder feature(String key, Object value) {
            if (null == this.features) {
                this.features = new HashMap<>();
            }
            this.features.put(key, value);
            return this;
        }
        public Order build() {
            return new Order(this.code, this.offers, this.features);
        }
    }

        public static void main(String[] args) {
        Order order = Order.builder()
                .code("1234")
                .offer("滿100減5")
                .offer("滿200減15")
                .feature("color", "white")
                .feature("category", "shirt")
                .build();
        System.out.println(order);
    }
}

/** 輸出
Order(code=1234, offers=[滿100減5, 滿200減15], features={color=white, category=shirt})

Process finished with exit code 0
*/

 

5.2 Java8建造者模式(不依賴第三方庫)

函數式介面和泛型構造器

@FunctionalInterface
public interface KeyValueConsumer<T, K, V> {

    void accept(T t, K k, V v);

    default KeyValueConsumer<T, K, V> andThen(KeyValueConsumer<? super T, ? super K, ? super V> after) {
        Objects.requireNonNull(after);
        return (t, k, v) -> {
            accept(t, k, v);
            after.accept(t, k, v);
        };
    }
}


public class GenericBuilder<T> {
    private final Supplier<T> instantiator;
    private List<Consumer<T>> instantiatorModifiers = new ArrayList<>();
    private List<Consumer<T>> keyValueModifiers = new ArrayList<>();
    public GenericBuilder(Supplier<T> instantiator) {
        this.instantiator = instantiator;
    }
    public static <T> GenericBuilder<T> of(Supplier<T> instantiator) {
        return new GenericBuilder<T>(instantiator);
    }
    public <U> GenericBuilder<T> with(BiConsumer<T, U> consumer, U value) {
        Consumer<T> c = instance -> consumer.accept(instance, value);
        instantiatorModifiers.add(c);
        return this;
    }
    public <K, V> GenericBuilder<T> with(KeyValueConsumer<T, K, V> consumer, K key, V value) {
        Consumer<T> c = instance -> consumer.accept(instance, key, value);
        keyValueModifiers.add(c);
        return this;
    }
    public T build() {
        T value = instantiator.get();
        instantiatorModifiers.forEach(modifier -> modifier.accept(value));
        keyValueModifiers.forEach(keyValueModifier -> keyValueModifier.accept(value));
        instantiatorModifiers.clear();
        keyValueModifiers.clear();
        return value;
    }
}

實際操作類

package com.example.demo.bean;

import lombok.Builder;
import lombok.Singular;
import lombok.ToString;

import java.util.*;

public class Order {
    private String code;
    private List<String> offers;
    private Map<String, Object> features;

    //省略無參構造方法,set、get,toString方法

    public void addOffer(String offer) {
        offers = Optional.ofNullable(offers)
                .orElseGet(ArrayList::new);
        offers.add(offer);
    }

    public <T> void addFeature(String key, T value) {
        features = Optional.ofNullable(features)
                .orElseGet(HashMap::new);
        features.put(key, value);
    }

    public static void main(String[] args) {
        Order order = GenericBuilder.of(Order::new)
                .with(Order::setCode, "1234")
                .with(Order::addOffer, "滿200減15")
                .with(Order::addOffer, "滿200減15")
                .with(Order::addFeature, "color", "white")
                .with(Order::addFeature, "category", "shirt").build();
        System.out.println(order);
    }
}

/** 輸出
Order(code=1234, offers=[滿200減15, 滿200減15], features={color=white, category=shirt})

Process finished with exit code 0
*/

 

5.3 lombok第三方庫的建造者模式

@ToString
@Builder
public class Order {
    private String code;
    @Singular
    private List<String> offers;
    @Singular
    private Map<String, Object> features;

    public static void main(String[] args) {
        Order order = Order.builder()
                .code("1234")
                .offer("滿100減5")
                .offer("滿200減15")
                .feature("color", "white")
                .feature("category", "shirt")
                .build();
        System.out.println(order);
    }
}

/** 輸出
Order(code=1234, offers=[滿200減15, 滿200減15], features={color=white, category=shirt})

Process finished with exit code 0
*/

 

6.1 依賴注入

這裡移除了Spring的配置文件,只展示實際程式碼

@RestController
public class DemoController {
    //自動注入demo的服務
    @Autowired
    private DemoService demoService;

    @GetMapping
    public String getDemo() {
        return demoService.get();
    }

 

建造者模式來源於 如何實現Builder模式

單例模式來源於 Effective Java