型クラスとより高い種類の型の理解を明確にするためにこの質問をすることで、Javaの回避策を探しているわけではありません。
Haskellでは、次のように書くことができます
class Negatable t where
negate :: t -> t
normalize :: (Negatable t) => t -> t
normalize x = negate (negate x)
次に、Bool
にNegatable
のインスタンスがあると仮定し、
v :: Bool
v = normalize True
そして、すべてが正常に動作します。
Javaでは、適切なNegatable
インターフェイスを宣言することはできないようです。私たちは書くことができます:
interface Negatable {
Negatable negate();
}
Negatable normalize(Negatable a) {
a.negate().negate();
}
しかし、その後、Haskellとは異なり、以下はキャストなしではコンパイルされません(MyBoolean
がNegatable
を実装すると仮定します):
MyBoolean val = normalize(new MyBoolean()); // does not compile; val is a Negatable, not a MyBoolean
Javaインターフェイスで実装タイプを参照する方法はありますか、またはこれはJavaタイプシステムの基本的な制限ですか?制限、より高い種類のサポートに関連していますか?私はそうは思わない:これは別の種類の制限のように見える。その場合、名前はありますか?
ありがとう、質問が不明な場合は私に知らせてください!
実はそうです。直接ではありませんが、あなたはそれを行うことができます。ジェネリックパラメーターを含めるだけで、ジェネリック型から派生します。
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);
}
私は質問を
can Javaで非常によく似た処理を行いますが、Haskellの型安全性の保証がなければ、以下に示すソリューションは実行時にエラーをスローする可能性があります。
方法は次のとおりです。
タイプクラスを表すインターフェースを定義する
interface Negatable<T> {
T negate(T t);
}
さまざまなタイプのタイプクラスのインスタンスを登録できるメカニズムを実装します。ここでは、静的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);
}
上記のメカニズムを使用して、渡されたオブジェクトのランタイムクラスに基づいて適切なインスタンスを取得するnormalize
メソッドを定義します。
public static <T> T normalize(T t) {
Negatable<T> inst = Negatable.<T>getInstance(t.getClass());
return inst.negate(inst.negate(t));
}
さまざまなクラスの実際のインスタンスを登録します。
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;
}
});
これを使って!
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));
}
}
ジェネリックとセルフタイピングを探しています。自己入力は、インスタンスのクラスに相当する一般的なプレースホルダーの概念です。
ただし、自己入力は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
メソッドを再度オーバーライドする必要があります。