web-dev-qa-db-ja.com

JavaでNegatableインターフェイスを定義できますか?

型クラスとより高い種類の型の理解を明確にするためにこの質問をすることで、Javaの回避策を探しているわけではありません。


Haskellでは、次のように書くことができます

class Negatable t where
    negate :: t -> t

normalize :: (Negatable t) => t -> t
normalize x = negate (negate x)

次に、BoolNegatableのインスタンスがあると仮定し、

v :: Bool
v = normalize True

そして、すべてが正常に動作します。


Javaでは、適切なNegatableインターフェイスを宣言することはできないようです。私たちは書くことができます:

interface Negatable {
    Negatable negate();
}

Negatable normalize(Negatable a) {
    a.negate().negate();
}

しかし、その後、Haskellとは異なり、以下はキャストなしではコンパイルされません(MyBooleanNegatableを実装すると仮定します):

MyBoolean val = normalize(new MyBoolean()); // does not compile; val is a Negatable, not a MyBoolean

Javaインターフェイスで実装タイプを参照する方法はありますか、またはこれはJavaタイプシステムの基本的な制限ですか?制限、より高い種類のサポートに関連していますか?私はそうは思わない:これは別の種類の制限のように見える。その場合、名前はありますか?

ありがとう、質問が不明な場合は私に知らせてください!

52
zale

実はそうです。直接ではありませんが、あなたはそれを行うことができます。ジェネリックパラメーターを含めるだけで、ジェネリック型から派生します。

public interface Negatable<T> {
    T negate();
}

public static <T extends Negatable<T>> T normalize(T a) {
    return a.negate().negate();
}

このようにこのインターフェースを実装します

public static class MyBoolean implements Negatable<MyBoolean> {
    public boolean a;

    public MyBoolean(boolean a) {
        this.a = a;
    }

    @Override
    public MyBoolean negate() {
        return new MyBoolean(!this.a);
    }

}

実際、Java標準ライブラリはこの正確なトリックを使用してComparableを実装します。

public interface Comparable<T> {
    int compareTo(T o);
}
63
Silvio Mayolo

私は質問を

Javaで型クラスを使用してアドホックポリモーフィズムを実装するにはどうすればよいですか?

can Javaで非常によく似た処理を行いますが、Haskellの型安全性の保証がなければ、以下に示すソリューションは実行時にエラーをスローする可能性があります。

方法は次のとおりです。

  1. タイプクラスを表すインターフェースを定義する

    interface Negatable<T> {
      T negate(T t);
    }
    
  2. さまざまなタイプのタイプクラスのインスタンスを登録できるメカニズムを実装します。ここでは、静的HashMapが行います:

    static HashMap<Class<?>, Negatable<?>> instances = new HashMap<>();
    static <T> void registerInstance(Class<T> clazz, Negatable<T> inst) {
      instances.put(clazz, inst);
    }
    @SuppressWarnings("unchecked")
    static <T> Negatable<T> getInstance(Class<?> clazz) {
      return (Negatable<T>)instances.get(clazz);
    }
    
  3. 上記のメカニズムを使用して、渡されたオブジェクトのランタイムクラスに基づいて適切なインスタンスを取得するnormalizeメソッドを定義します。

      public static <T> T normalize(T t) {
        Negatable<T> inst = Negatable.<T>getInstance(t.getClass());
        return inst.negate(inst.negate(t));
      }
    
  4. さまざまなクラスの実際のインスタンスを登録します。

    Negatable.registerInstance(Boolean.class, new Negatable<Boolean>() {
      public Boolean negate(Boolean b) {
        return !b;
      }
    });
    
    Negatable.registerInstance(Integer.class, new Negatable<Integer>() {
      public Integer negate(Integer i) {
        return -i;
      }
    });
    
  5. これを使って!

    System.out.println(normalize(false)); // Boolean `false`
    System.out.println(normalize(42));    // Integer `42`
    

主な欠点は、既に述べたように、タイプクラスインスタンスのルックアップが、コンパイル時ではなく実行時に失敗する可能性があることです(Haskellのように)。静的なハッシュマップの使用も次善の方法です。共有グローバル変数のすべての問題が発生するため、より洗練された依存性注入メカニズムでこれを軽減できます。他のタイプクラスインスタンスからタイプクラスインスタンスを自動的に生成するには、さらに多くのインフラストラクチャが必要になります(ライブラリで実行できます)。しかし、原則として、Javaの型クラスを使用してアドホックポリモーフィズムを実装します。

完全なコード:

import Java.util.HashMap;

class TypeclassInJava {

  static interface Negatable<T> {
    T negate(T t);

    static HashMap<Class<?>, Negatable<?>> instances = new HashMap<>();
    static <T> void registerInstance(Class<T> clazz, Negatable<T> inst) {
      instances.put(clazz, inst);
    }
    @SuppressWarnings("unchecked")
    static <T> Negatable<T> getInstance(Class<?> clazz) {
      return (Negatable<T>)instances.get(clazz);
    }
  }

  public static <T> T normalize(T t) {
    Negatable<T> inst = Negatable.<T>getInstance(t.getClass());
    return inst.negate(inst.negate(t));
  }

  static {
    Negatable.registerInstance(Boolean.class, new Negatable<Boolean>() {
      public Boolean negate(Boolean b) {
        return !b;
      }
    });

    Negatable.registerInstance(Integer.class, new Negatable<Integer>() {
      public Integer negate(Integer i) {
        return -i;
      }
    });
  }

  public static void main(String[] args) {
    System.out.println(normalize(false));
    System.out.println(normalize(42));
  }
}
7
Andrey Tyukin

ジェネリックとセルフタイピングを探しています。自己入力は、インスタンスのクラスに相当する一般的なプレースホルダーの概念です。

ただし、自己入力はJavaには存在しません。

ただし、これはジェネリックで解決できます。

public interface Negatable<T> {
    public T negate();
}

それから

public class MyBoolean implements Negatable<MyBoolean>{

    @Override
    public MyBoolean negate() {
        //your impl
    }

}

実装者への影響:

  • インターフェイスを実装するときに、自分自身を指定する必要があります。 MyBoolean implements Negatable<MyBoolean>
  • MyBooleanを拡張するには、negateメソッドを再度オーバーライドする必要があります。
7
Taylor