メソッドの戻り値の型をジェネリックにするにはどうすればよいですか?
-
19-08-2019 - |
質問
この例を検討してください(OOPブックで一般的):
Animal
クラスがあり、各Dog
は多くの友人を持つことができます。
また、Duck
、Mouse
、bark()
などの特定の動作を追加するquack()
、instanceof
などのサブクラス
<=>クラスは次のとおりです。
public class Animal {
private Map<String,Animal> friends = new HashMap<>();
public void addFriend(String name, Animal animal){
friends.put(name,animal);
}
public Animal callFriend(String name){
return friends.get(name);
}
}
そして、多くの型キャストを使用したコードスニペットを次に示します。
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();
戻り値の型にジェネリックを使用して型キャストを削除する方法はありますか?
jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();
ここでは、使用されないパラメーターとしてメソッドに返される戻り値の型を含む初期コードを示します。
public<T extends Animal> T callFriend(String name, T unusedTypeObj){
return (T)friends.get(name);
}
<=>を使用して追加のパラメーターなしで実行時に戻り値の型を把握する方法はありますか?または、少なくともダミーインスタンスの代わりに型のクラスを渡します。
ジェネリックはコンパイル時の型チェック用であると理解していますが、これに対する回避策はありますか?
解決
この方法でcallFriend
を定義できます:
public <T extends Animal> T callFriend(String name, Class<T> type) {
return type.cast(friends.get(name));
}
その後、次のように呼び出します:
jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();
このコードには、コンパイラ警告を生成しないという利点があります。もちろん、これは実際には一般的な時代からのキャストの更新されたバージョンであり、追加の安全性は追加されません。
他のヒント
いいえ。コンパイラは、jerry.callFriend("spike")
が返すタイプを知ることができません。また、実装では、型の安全性を追加せずに、メソッドのキャストを非表示にします。これを考慮してください:
jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast
この特定のケースでは、抽象talk()
メソッドを作成し、サブクラスで適切にオーバーライドすることで、はるかに役立ちます。
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();
次のように実装できます:
@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
return (T)friends.get(name);
}
(はい、これは法的なコードです。 Java Genericsを参照してください:戻り値の型としてのみ定義されている汎用型。)
戻り値の型は呼び出し元から推測されます。ただし、@SuppressWarnings
注釈に注意してください。これは、このコードはタイプセーフではないことを示しています。自分で確認するか、実行時にClassCastExceptions
を取得する必要があります。
残念ながら、(一時変数に戻り値を割り当てずに)使用する方法は、コンパイラを幸せにする唯一の方法は次のように呼び出すことです:
jerry.<Dog>callFriend("spike").bark();
これはキャストよりも少しいいかもしれませんが、David Schmittが言ったように、Animal
クラスに抽象talk()
メソッドを与える方が良いでしょう。
この質問は、 Effective JavaのItem 29 と非常によく似ています-<!> quot;タイプセーフな異種コンテナを検討してください。<!> quot;ラズの答えは、ブロッホの解に最も近いものです。ただし、putおよびgetは、安全のためにClassリテラルを使用する必要があります。署名は次のようになります。
public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
public <T extends Animal> T callFriend(String name, Class<T> type);
両方のメソッド内で、パラメーターが正常であることを確認する必要があります。有効なJavaおよびクラス javadocを参照してください。情報。
こちらがよりシンプルなバージョンです:
public <T> T callFriend(String name) {
return (T) friends.get(name); //Casting to T not needed in this case but its a good practice to do
}
完全に機能するコード:
public class Test {
public static class Animal {
private Map<String,Animal> friends = new HashMap<>();
public void addFriend(String name, Animal animal){
friends.put(name,animal);
}
public <T> T callFriend(String name){
return (T) friends.get(name);
}
}
public static class Dog extends Animal {
public void bark() {
System.out.println("i am dog");
}
}
public static class Duck extends Animal {
public void quack() {
System.out.println("i am duck");
}
}
public static void main(String [] args) {
Animal animals = new Animal();
animals.addFriend("dog", new Dog());
animals.addFriend("duck", new Duck());
Dog dog = animals.callFriend("dog");
dog.bark();
Duck duck = animals.callFriend("duck");
duck.quack();
}
}
クラスを渡すことは問題ないと言ったように、これを書くことができます:
public <T extends Animal> T callFriend(String name, Class<T> clazz) {
return (T) friends.get(name);
}
そして次のように使用します:
jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();
完全ではありませんが、これはJavaジェネリックを使用する限りではほぼ同じです。 Typesafe Heterogenous Containers(THC)を使用して実装する方法がありますスーパータイプトークンですが、それでも問題があります。
スーパータイプトークンと同じ考え方に基づいて、文字列の代わりに使用する型付きIDを作成できます。
public abstract class TypedID<T extends Animal> {
public final Type type;
public final String id;
protected TypedID(String id) {
this.id = id;
Type superclass = getClass().getGenericSuperclass();
if (superclass instanceof Class) {
throw new RuntimeException("Missing type parameter.");
}
this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
}
}
しかし、文字列ごとに新しいidオブジェクトを作成して保持する(または正しい型情報で再構築する)必要があるため、これは目的に反するかもしれません。
Mouse jerry = new Mouse();
TypedID<Dog> spike = new TypedID<Dog>("spike") {};
TypedID<Duck> quacker = new TypedID<Duck>("quacker") {};
jerry.addFriend(spike, new Dog());
jerry.addFriend(quacker, new Duck());
ただし、キャストを使用せずに、本来の方法でクラスを使用できるようになりました。
jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();
これはid内のtypeパラメーターを非表示にしているだけですが、必要に応じて後で識別子からタイプを取得できることを意味します。
IDの2つの同一のインスタンスを比較できるようにするには、TypedIDの比較およびハッシュメソッドも実装する必要があります。
<!> quot; instanceof?<!> quot;を使用して、追加のパラメーターなしで実行時に戻り値の型を把握する方法はありますか
代替ソリューションとして、このような訪問者パターンを利用できます。 Animalを抽象化し、Visitableを実装させます:
abstract public class Animal implements Visitable {
private Map<String,Animal> friends = new HashMap<String,Animal>();
public void addFriend(String name, Animal animal){
friends.put(name,animal);
}
public Animal callFriend(String name){
return friends.get(name);
}
}
Visitableは、Animalの実装が訪問者を受け入れることを意味します:
public interface Visitable {
void accept(Visitor v);
}
そして、ビジターの実装は、動物のすべてのサブクラスを訪問できます:
public interface Visitor {
void visit(Dog d);
void visit(Duck d);
void visit(Mouse m);
}
たとえば、Dog実装は次のようになります。
public class Dog extends Animal {
public void bark() {}
@Override
public void accept(Visitor v) { v.visit(this); }
}
ここでのコツは、Dogはそれがどのタイプであるかを知っているので、<!> quot; this <!> quot;を渡すことで、訪問者vの関連するオーバーロードされたvisitメソッドをトリガーできることです。パラメータとして。他のサブクラスは、accept()をまったく同じ方法で実装します。
サブクラス固有のメソッドを呼び出したいクラスは、次のようにVisitorインターフェースを実装する必要があります。
public class Example implements Visitor {
public void main() {
Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());
// Used to be: ((Dog) jerry.callFriend("spike")).bark();
jerry.callFriend("spike").accept(this);
// Used to be: ((Duck) jerry.callFriend("quacker")).quack();
jerry.callFriend("quacker").accept(this);
}
// This would fire on callFriend("spike").accept(this)
@Override
public void visit(Dog d) { d.bark(); }
// This would fire on callFriend("quacker").accept(this)
@Override
public void visit(Duck d) { d.quack(); }
@Override
public void visit(Mouse m) { m.squeak(); }
}
それはあなたが交渉したよりもはるかに多くのインターフェースとメソッドであることは知っていますが、正確にゼロのinstanceofチェックとゼロの型キャストで特定のサブタイプごとにハンドルを取得する標準的な方法です。そして、すべて標準言語にとらわれない方法で行われるため、Javaだけでなく、どのOO言語も同じように動作するはずです。
できません。 Stringキーのみが与えられた場合、MapはどのようにしてAnimalのサブクラスを取得するのかを知ることになっていますか?
これが可能な唯一の方法は、各Animalが1種類の友人のみを受け入れた場合(つまり、Animalクラスのパラメーターである場合)、またはcallFriend()メソッドがtypeパラメーターを取得した場合です。ただし、継承のポイントが欠落しているように見えます。スーパークラスメソッドのみを使用する場合、サブクラスを均一にしか処理できないということです。
概念実証、サポートクラス、および実行時にクラスがスーパータイプトークンを取得する方法を示すテストクラスを含む記事を執筆しました。 簡単に言うと、呼び出し元から渡された実際の汎用パラメーターに応じて、代替の実装に委任できます。例:
-
TimeSeries<Double>
はdouble[]
を使用するプライベート内部クラスに委任します
-
TimeSeries<OHLC>
はArrayList<OHLC>
を使用するプライベート内部クラスに委任します
参照: TypeTokenを使用して汎用パラメーターを取得する
ありがとう
リチャードゴメス-ブログ
ここにはたくさんの素晴らしい答えがありますが、これは私がAppiumテストのために取ったアプローチであり、単一の要素に作用するとユーザーの設定に基づいて異なるアプリケーション状態になります。 OPの例の規則に従っていませんが、誰かの助けになることを願っています。
public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//signInButton.click();
return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
}
- MobilePageは、型が拡張するスーパークラスです。つまり、その子(duh)を使用できます
- type.getConstructor(Param.classなど)を使用すると、 型のコンストラクター。このコンストラクタは、予想されるすべてのクラスで同じでなければなりません。
- newInstanceは、新しいオブジェクトコンストラクターに渡す宣言済み変数を受け取ります
エラーをスローしたくない場合は、次のようにキャッチできます:
public <T extends MobilePage> T tapSignInButton(Class<T> type) {
// signInButton.click();
T returnValue = null;
try {
returnValue = type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
} catch (Exception e) {
e.printStackTrace();
}
return returnValue;
}
サブクラスによってバークまたはクワックとして実装される抽象makeNoise()メソッドをAnimalに追加できませんか?
ここで探しているのは抽象化です。インターフェイスに対するコードが増えると、キャストを減らす必要があります。
以下の例はC#ですが、概念は同じままです。
using System;
using System.Collections.Generic;
using System.Reflection;
namespace GenericsTest
{
class MainClass
{
public static void Main (string[] args)
{
_HasFriends jerry = new Mouse();
jerry.AddFriend("spike", new Dog());
jerry.AddFriend("quacker", new Duck());
jerry.CallFriend<_Animal>("spike").Speak();
jerry.CallFriend<_Animal>("quacker").Speak();
}
}
interface _HasFriends
{
void AddFriend(string name, _Animal animal);
T CallFriend<T>(string name) where T : _Animal;
}
interface _Animal
{
void Speak();
}
abstract class AnimalBase : _Animal, _HasFriends
{
private Dictionary<string, _Animal> friends = new Dictionary<string, _Animal>();
public abstract void Speak();
public void AddFriend(string name, _Animal animal)
{
friends.Add(name, animal);
}
public T CallFriend<T>(string name) where T : _Animal
{
return (T) friends[name];
}
}
class Mouse : AnimalBase
{
public override void Speak() { Squeek(); }
private void Squeek()
{
Console.WriteLine ("Squeek! Squeek!");
}
}
class Dog : AnimalBase
{
public override void Speak() { Bark(); }
private void Bark()
{
Console.WriteLine ("Woof!");
}
}
class Duck : AnimalBase
{
public override void Speak() { Quack(); }
private void Quack()
{
Console.WriteLine ("Quack! Quack!");
}
}
}
これは、これとはまったく異なることを知っています。これを解決する別の方法は、リフレクションです。つまり、これはジェネリックの恩恵を受けませんが、何らかの方法で、型キャストを気にせずに実行したい動作(犬の樹皮を作る、アヒルの鳴き声を作るなど)をエミュレートできます:
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
abstract class AnimalExample {
private Map<String,Class<?>> friends = new HashMap<String,Class<?>>();
private Map<String,Object> theFriends = new HashMap<String,Object>();
public void addFriend(String name, Object friend){
friends.put(name,friend.getClass());
theFriends.put(name, friend);
}
public void makeMyFriendSpeak(String name){
try {
friends.get(name).getMethod("speak").invoke(theFriends.get(name));
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public abstract void speak ();
};
class Dog extends Animal {
public void speak () {
System.out.println("woof!");
}
}
class Duck extends Animal {
public void speak () {
System.out.println("quack!");
}
}
class Cat extends Animal {
public void speak () {
System.out.println("miauu!");
}
}
public class AnimalExample {
public static void main (String [] args) {
Cat felix = new Cat ();
felix.addFriend("Spike", new Dog());
felix.addFriend("Donald", new Duck());
felix.makeMyFriendSpeak("Spike");
felix.makeMyFriendSpeak("Donald");
}
}
はどうですか
public class Animal {
private Map<String,<T extends Animal>> friends = new HashMap<String,<T extends Animal>>();
public <T extends Animal> void addFriend(String name, T animal){
friends.put(name,animal);
}
public <T extends Animal> T callFriend(String name){
return friends.get(name);
}
}
lib kontraktorで次のことを行いました。
public class Actor<SELF extends Actor> {
public SELF self() { return (SELF)_self; }
}
サブクラス化:
public class MyHttpAppSession extends Actor<MyHttpAppSession> {
...
}
少なくとも、これは現在のクラス内で、強い型指定された参照を持つ場合に機能します。多重継承は機能しますが、それから本当にトリッキーになります:)
別のアプローチがあります。メソッドをオーバーライドするときに戻り値の型を絞り込むことができます。各サブクラスでcallFriendをオーバーライドして、そのサブクラスを返す必要があります。コストはcallFriendの複数の宣言になりますが、内部で呼び出されるメソッドに共通部分を分離できます。これは、上記の解決策よりもはるかに簡単なようであり、戻り値の型を決定するために追加の引数を必要としません。
public <X,Y> X nextRow(Y cursor) {
return (X) getRow(cursor);
}
private <T> Person getRow(T cursor) {
Cursor c = (Cursor) cursor;
Person s = null;
if (!c.moveToNext()) {
c.close();
} else {
String id = c.getString(c.getColumnIndex("id"));
String name = c.getString(c.getColumnIndex("name"));
s = new Person();
s.setId(id);
s.setName(name);
}
return s;
}
任意のタイプを返して、同様に直接受け取ることができます。型キャストする必要はありません。
Person p = nextRow(cursor); // cursor is real database cursor.
これは、実際のカーソルの代わりに他の種類のレコードをカスタマイズする場合に最適です。