[英]Class constructor with generic class
在我的應用程序中,我有泛型類,我必須根據這些泛型類構造新對象。 是否可以獲得處理子類的通用構造函數?
我會更容易理解代碼:
例如,我有一個有兩個孩子的父類
open class Fruit (
val name: String
)
class Apple (
name: String,
val count: Int
) : Fruit(name) {
constructor(name: String) : this(name, 0)
}
class Strawberry (
name: String,
val weight: Float
) : Fruit(name) {
constructor(name: String) : this(name, 0f)
}
如您所見,我的 3 個類具有相同的構造函數(只有一個名稱)。 現在我有一個 class,它包含一個通用的 class 水果。 在這個 class 中,我想要創建新水果的方法,而不關心它是水果、蘋果還是草莓:
class Farmer<GenericFruit: Fruit>(val name: String, val age: Int) {
var cart = arrayListOf<GenericFruit>()
fun cultivate() {
for (_ in 1..10) {
val fruit = cultivateFruit()
cart.add(fruit)
}
}
fun cultivateFruit() : GenericFruit {
// Here call constructor of GenericFruit which just the parameters "name"
val fruit = GenericFruit(name = "name of the fruit")
return fruit
}
}
我試過在這樣的方法cultivateFruit
中使用reified
:
inline fun <reified T: GenericFruit> cultivateFruit() : GenericFruit? {
val constructor = T::class.constructors.find {
it.parameters.size == 1 && it.parameters.first().name == "name"
}
return constructor?.call("name of the fruit")
}
但問題是,現在我需要在方法中使用cultivate
,而 Kotlin 不會讓我這樣做:
fun cultivate() {
for (_ in 1..10) {
val fruit = cultivateFruit<GenericFruit>() // Here there is an error, GenericFruit can't be called
cart.add(fruit)
}
}
問題是我沒有引用通用構造函數。 我什至沒有在cultivateFruit
中訪問 class T
。 我知道在 swift 上可以實現這一點,我想知道 Kotlin 是否也可以?
最誠實的做法是在static Map
Class<? extends Fruit>
Class<? extends Fruit>
。 該名稱也是 class 級別常量,因此不應是構造函數參數。 構造函數應該是每個子 class 的默認構造函數。
public class Fruit ...
public String getName() {
return fruitClasses.get(getClass());
}
private static<String, Class<? extends Fruit>> fruitClasses = new HashMap<>();
protected static register(String name, Class<? extends Fruit> fruitClass) {
Objects.requireNull(fruitClasses.put(name, fruitClass));
}
public static Fruit createFruit(String name) {
Class<? extends Fruit> type = fruitClasses.get(name);
if (type == null) {
throw new IllegalArgumentException(
"No fruit class registered for " + name);
}
return type.getConstructor().newInstance();
}
}
class Apple extends Fruit {
static { Fruit.register("Apple", Apple.class); }
在這里,每個 Fruit child class 都負責自己的注冊。
傳統上也可以使用FruitFactory
來完成,這對每個人來說似乎都更簡單一些。 缺點是 FruitFactory 要么需要導入每個子 class,要么與上面的Fruit
沒有區別。
使用 java 中的新sealed
類,您可以定義子類的封閉域,並將它們全部列出。 因此,您可以在 Fruit 本身中注冊所有類。
最后但並非最不重要的一點可能有人想知道 inheritance,多個孩子,是否是一個好主意。 還可以想到動態查找的功能、特性:
public interface JuicePressable {
float press();
}
public interface HasSeeds {
int seeds();
void removeSeeds();
}
Optional<JuicePressable> jp = fruit.as(JuicePressable.class);
jp.ifPresent(fr -> ... fr.press() ...);
fruit.as(HasSeeds.class).ifPresent(fr -> fr.removeSeeds());
這里的基本原理是,如果您有分類處理,並且需要過濾特定操作,並且操作類別的數量是開放的:然后創建功能查找。
Kotlin 回答。
由於無法具體化 class 類型,因此您的 Farmer 需要有一個屬性持有對 class 類型的引用才能使用反射來創建水果。 您可以添加一個配套的 object reified function 偽構造函數,以避免必須顯式傳遞 class 類型。
class Farmer<T: Fruit>(private val fruitClass: KClass<T>, val name: String, val age: Int) {
companion object {
inline fun <reified T: Fruit> Farmer(name: String, age: Int) = Farmer(T::class, name, age)
}
private val fruitConstructor = fruitClass.constructors.find {
it.parameters.size == 1 && it.parameters.first().name == "name"
} ?: error("Fruit class must have a constructor with a single argument of \"name\"")
var cart = arrayListOf<T>()
fun cultivate() {
repeat(10) {
val fruit = cultivateFruit()
cart.add(fruit)
}
}
private fun cultivateFruit(): T {
return fruitConstructor.call("name of the fruit")
}
}
就個人而言,我不想依靠反射來解決這個問題。 您可以為水果的構造函數添加構造函數參數。 在調用站點上不太方便,但是您將進行編譯時檢查,因此代碼會更加健壯。
class Farmer<T: Fruit>(private val fruitCreator: (String)->T, val name: String, val age: Int) {
var cart = arrayListOf<T>()
fun cultivate() {
repeat(10) {
val fruit = cultivateFruit()
cart.add(fruit)
}
}
private fun cultivateFruit(): T {
return fruitCreator("name of the fruit")
}
}
構造 Farmer 時,可以將構造函數作為參數傳遞:
val strawberryFarmer = Farmer(::Strawberry, "Foo", 40)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.