web-dev-qa-db-ja.com

Java Reflectionを使用してスーパークラスメソッドを呼び出す方法

2つのクラスがあります。

public class A {
    public Object method() {...}
}

public class B extends A {
    @Override
    public Object method() {...}
}

Bのインスタンスがあります。bからA.method()を呼び出すにはどうすればよいですか?基本的に、Bからsuper.method()を呼び出すのと同じ効果.

B b = new B();
Class<?> superclass = b.getClass().getSuperclass();
Method method = superclass.getMethod("method", ArrayUtils.EMPTY_CLASS_ARRAY);
Object value = method.invoke(obj, ArrayUtils.EMPTY_OBJECT_ARRAY);

ただし、上記のコードは引き続きB.method()を呼び出します

42
Ted

JDK7を使用している場合、MethodHandleを使用してこれを実現できます。

public class Test extends Base {
  public static void main(String[] args) throws Throwable {
    MethodHandle h1 = MethodHandles.lookup().findSpecial(Base.class, "toString",
        MethodType.methodType(String.class),
        Test.class);
    MethodHandle h2 = MethodHandles.lookup().findSpecial(Object.class, "toString",
        MethodType.methodType(String.class),
        Test.class);
    System.out.println(h1.invoke(new Test()));   // outputs Base
    System.out.println(h2.invoke(new Test()));   // outputs Base
  }

  @Override
  public String toString() {
    return "Test";
  }

}

class Base {
  @Override
  public String toString() {
    return "Base";
  }
}
27
java4script

不可能です。 Javaでのメソッドディスパッチは、リフレクションを使用する場合でも、常にオブジェクトのランタイムタイプを考慮します。 Method.invokeのjavadoc ;特にこのセクションを参照してください。

基になるメソッドがインスタンスメソッドである場合は、Java Language Specification、Second Edition、section 15.12.4.4;特にランタイムタイプに基づいてオーバーライドする)に記載されている動的メソッドルックアップを使用して呼び出されます。ターゲットオブジェクトの.

10
Greg

@ Java4scriptの答えに基づいて、このトリックをoutsideサブクラス(つまり、通常super.toString()を呼び出す場所)から実行しようとすると、IllegalAccessExceptionが返されることに気付きました。で始まる)。 inメソッドを使用すると、一部のケース(BaseおよびSubと同じパッケージから呼び出す場合など)でのみこれをバイパスできます。一般的なケースで私が見つけた唯一の回避策は、極端な(そして明らかに非移植性の)ハックです:

package p;
public class Base {
    @Override public String toString() {
        return "Base";
    }
}

package p;
public class Sub extends Base {
    @Override public String toString() {
        return "Sub";
    }
}

import p.Base;
import p.Sub;
import Java.lang.invoke.MethodHandle;
import Java.lang.invoke.MethodHandles;
import Java.lang.invoke.MethodType;
import Java.lang.reflect.Field;
public class Demo {
    public static void main(String[] args) throws Throwable {
        System.out.println(new Sub());
        Field IMPL_LOOKUP = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP");
        IMPL_LOOKUP.setAccessible(true);
        MethodHandles.Lookup lkp = (MethodHandles.Lookup) IMPL_LOOKUP.get(null);
        MethodHandle h1 = lkp.findSpecial(Base.class, "toString", MethodType.methodType(String.class), Sub.class);
        System.out.println(h1.invoke(new Sub()));
    }
}

印刷

Sub
Base
7
Jesse Glick

それはできません。多型が機能していないことを意味します。

Aのインスタンスが必要です。 superclass.newInstance()で作成し、BeanUtils.copyProperties(..)のようなものですべてのフィールドを転送できます(commons-beanutilsから)。しかし、それは「ハック」です-代わりに、あなたがそれを必要としないようにあなたのデザインを修正するべきです。

4
Bozho

Javaではメソッドディスパッチが機能するため、スーパークラスのインスタンスが必要になることはありません。

次のようなものを試すことができます:

import Java.lang.reflect.*;
class A {
    public void method() {
        System.out.println("In a");
    }
}
class B extends A {
    @Override
    public void method() {
        System.out.println("In b");
    }
}
class M {
    public static void main( String ... args ) throws Exception {
        A b = new B();
        b.method();

        b.getClass()
         .getSuperclass()
         .getMethod("method", new Class[]{} )
         .invoke(  b.getClass().getSuperclass().newInstance() ,new Object[]{}  );

    }
}

しかし、ほとんどの場合、bのデータが失われるため、意味がありません。

4
OscarRyz

リフレクションが機能しないため、含まれているライブラリに対してトリックを実行したい場合の方法はわかりませんが、自分のコードではこの簡単な回避策を実行します:

public class A {
    public Object method() {...}
}

public class B extends A {
    @Override
    public Object method() {...}

    public Object methodSuper() {
        return super.method();
    }
}

単純な場合にはこれで問題ありませんが、一部の自動呼び出しではそれほどではありません。たとえば、チェーンがある場合

A1 super A2 super A3 ... super An 

継承クラスのすべて、メソッドmをオーバーライドします。それから、AnのインスタンスでA1からmを呼び出すには、あまりにも悪いコーディングが必要になります:-)

2
reggie_7

invokespecial を呼び出す前に、異なるthisポインターを使用してバイトコードシーケンスを作成できます。

オブジェクトのsuper.toString()メソッドの呼び出しは次のようになります。

ALOAD X ;X = slot of object reference of the object to access
INVOKESPECIAL Java/lang/Object.toString ()Ljava/lang/String;
POP

これにより、必要なオブジェクトを含む匿名クラスを作成できます。

0
Martin Kersten