リフレクションを使用して、非表示クラス、別名パッケージプライベートクラスのインスタンスを取得しようとしています。修飾子を切り替えて公開し、を使用してアクセスする方法があるかどうか疑問に思いましたClass.forName
。私が今それを試みるとき、それは私がそれをすることができないと言うのを止めます。残念ながら、クラスのsetAccesible
メソッドはありません。Class
6 に答える
ネストされたクラス-他のクラス内で定義されたクラス(静的クラスと非静的クラスを含む)
内部クラス-非静的ネストされたクラス(内部クラスのインスタンスには外部クラスのインスタンスが存在する必要があります)
##ネストされていない(トップレベル)クラス
あなたの質問に基づいて、アクセスしたいコンストラクターは公開されていないことがわかります。したがって、クラスは次のようになります(A
クラスは私たちのものとは異なるパッケージに含まれています)
package package1;
public class A {
A(){
System.out.println("this is non-public constructor");
}
}
このクラスのインスタンスを作成するには、呼び出したいコンストラクターにアクセスして、アクセス可能にする必要があります。完了するConstructor#newInstance(arguments)
と、インスタンスの作成に使用できます。
Class<?> c = Class.forName("package1.A");
//full package name --------^^^^^^^^^^
//or simpler without Class.forName:
//Class<package1.A> c = package1.A.class;
//In our case we need to use
Constructor<?> constructor = c.getDeclaredConstructor();
//note: getConstructor() can return only public constructors
//so we needed to search for any Declared constructor
//now we need to make this constructor accessible
constructor.setAccessible(true);//ABRACADABRA!
Object o = constructor.newInstance();
##ネストされたクラスと内部クラス
ネストされた(静的および非静的)クラスにアクセスする場合Class.forName
は、構文を使用する必要があります。
Class<?> clazz = Class.forName("package1.Outer$Nested");
Outer$Nested
Nested
クラスはクラス内で宣言されていると言いますOuter
。ネストされたクラスはメソッドと非常によく似ており、外部クラスのすべてのメンバー(プライベートクラスを含む)にアクセスできます。
ただし、存在する内部クラスのインスタンスには、その外部クラスのインスタンスが必要であることを覚えておく必要があります。通常、次の方法で作成します。
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
ご覧のとおり、Innerクラスの各インスタンスには、その外部クラスに関する情報があります(その外部インスタンスへの参照はthis$0
フィールドに格納されます。詳細:Javaのデバッグ中にIntelliJIDEAで変数の名前が「this$0」の場合はどういう意味ですか?)
したがって、クラスのインスタンスを作成するInner
ときConstructor#newInstance()
に、最初の引数としてクラスのインスタンスへの参照を渡す必要がありますOuter
(動作をシミュレートするouter.new Inner()
ため)。
これが例です。
package1に
package package1;
public class Outer {
class Inner{
Inner(){
System.out.println("non-public constructor of inner class");
}
}
}
package2で
package package2;
import package1.Outer;
import java.lang.reflect.Constructor;
public class Test {
public static void main(String[] args) throws Exception {
Outer outerObject = new Outer();
Class<?> innerClazz = Class.forName("package1.Outer$Inner");
// constructor of inner class as first argument need instance of
// Outer class, so we need to select such constructor
Constructor<?> constructor = innerClazz.getDeclaredConstructor(Outer.class);
//we need to make constructor accessible
constructor.setAccessible(true);
//and pass instance of Outer class as first argument
Object o = constructor.newInstance(outerObject);
System.out.println("we created object of class: "+o.getClass().getName());
}
}
##静的にネストされたクラス
静的にネストされたクラスのインスタンスは、Outerクラスのインスタンスを必要としません(静的であるため)。Outer.class
したがって、彼らの場合、最初の引数としてコンストラクターを探す必要はありません。そして、最初の引数として外部クラスのインスタンスを渡す必要はありません。言い換えると、コードはネストされていない(トップレベルの)クラスの場合と同じになります(たとえば、のようにネストされたクラスを参照するときに、クラス名$
の代わりにuseを追加する必要があるという事実を除いて)。.
Class.forName()
Class.forName("some.package.Outer$Nested1$NestedNested")
Class.forName
動作するはずです。クラスがセキュリティプロパティのパッケージ階層リスト内にある場合は"package.access"
、適切な権限(通常はすべての権限、またはセキュリティマネージャがない)で操作を実行する必要があります。
使用しようとしている場合は、使用Class.newInstance
しないでください。Class.newInstance
例外の処理が不十分です。代わりに、を取得してConstructor
それを呼び出しますnewInstance
。例外トレースがないと、問題が発生していることを確認するのは困難です。
相変わらず、リフレクションのすべてではありませんがほとんどの使用法は悪い考えです。
最近、リフレクションを介してプライベートフィールド、メソッド、および内部クラスにアクセスするのに役立つライブラリをリリースしました:BoundBox
のようなクラスの場合
public class Outer {
private static class Inner {
private int foo() {return 2;}
}
}
次のような構文を提供します。
Outer outer = new Outer();
Object inner = BoundBoxOfOuter.boundBox_new_Inner();
new BoundBoxOfOuter.BoundBoxOfInner(inner).foo();
BoundBoxクラスを作成するためにあなたがしなければならない唯一のことは書くこと@BoundBox(boundClass=Outer.class)
であり、BoundBoxOfOuter
クラスは即座に生成されます。
Manifoldの @Jailbreakを使用して、タイプセーフなJavaリフレクションを直接実行できます。
Foo foo = new @Jailbreak Foo();
public class Foo {
Foo() {...}
private void yodog() {...}
}
これにより、コンパイラーはコンストラクターをパブリックのようにタイプセーフに解決できます@Jailbreak
が、Manifoldは内部で効率的なリフレクションコードを生成します。
さらに@Jailbreak
、非表示クラスにアクセスして構築するために使用できます。
com.abc. @Jailbreak Bar bar = new com.abc. @Jailbreak Bar();
package com.abc;
// package-private class
class Bar {
Bar() {...}
}
非表示のクラスアクセスの場合、Javaの注釈文法では、パッケージとは別にクラスに注釈を付ける必要があります。
@Jailbreak
より一般的には、あらゆるタイプの反射に使用できます。
@Jailbreak Foo foo = new Foo();
foo.yodog();
@Jailbreak
Foo
コンパイラのfooローカル変数のロックを解除して、の階層内のすべてのメンバーに直接アクセスできるようにします。
同様に、jailbreak()拡張メソッドを1回限りの使用に使用できます。
foo.jailbreak().yodog();
このメソッドを介して、の階層内のjailbreak()
任意のメンバーにアクセスできます。Foo
マニホールドの詳細をご覧ください。
最新バージョンで値がnullの場合、古いバージョンのオブジェクトからフィールドの値をコピーする必要がありました。これらの2つのオプションがありました。
コアJava:
for (Field f : object.getClass().getSuperclass().getDeclaredFields()) {
f.setAccessible(true);
System.out.println(f.getName());
if (f.get(object) == null){
f.set(object, f.get(oldObject));
}
}
Springを使用する[org.springframework.beans.BeanWrapper]:
BeanWrapper bw = new BeanWrapperImpl(object);
PropertyDescriptor[] data = bw.getPropertyDescriptors();
for (PropertyDescriptor propertyDescriptor : data) {
System.out.println(propertyDescriptor.getName());
Object propertyValue = bw.getPropertyValue(propertyDescriptor.getName());
if(propertyValue == null )
bw.setPropertyValue( new PropertyValue(propertyDescriptor.getName(),"newValue"));
}
ここでAndroidで同様のことをしなければならなかったのは、私が思いついたものです。
/**
* To fix issues with wrong edge-to-edge bottom sheet top padding and status bar icon color…
* …we need to call [BottomSheetDialog.EdgeToEdgeCallback.setPaddingForPosition] which is a private function from a private class.
* See: https://github.com/material-components/material-components-android/issues/2165
*/
fun adjustBottomSheet(aDialog : BottomSheetDialog) {
// Get our private class
val classEdgeToEdgeCallback = Class.forName("com.google.android.material.bottomsheet.BottomSheetDialog\$EdgeToEdgeCallback")
// Get our private method
val methodSetPaddingForPosition: Method = classEdgeToEdgeCallback.getDeclaredMethod("setPaddingForPosition", View::class.java)
methodSetPaddingForPosition.isAccessible = true
// Get private field containing our EdgeToEdgeCallback instance
val fieldEdgeToEdgeCallback = BottomSheetDialog::class.java.getDeclaredField("edgeToEdgeCallback")
fieldEdgeToEdgeCallback.isAccessible = true
// Get our bottom sheet view field
val fieldBottomField = BottomSheetDialog::class.java.getDeclaredField("bottomSheet")
fieldBottomField.isAccessible = true
// Eventually call setPaddingForPosition from EdgeToEdgeCallback instance passing bottom sheet view as parameter
methodSetPaddingForPosition.invoke(fieldEdgeToEdgeCallback.get(aDialog),fieldBottomField.get(aDialog))
}
また、proguardがアクセスしようとしているメソッドを破棄しないようにすることもできます。
# Needed for now to fix our bottom sheet issue from
com.google.android.material:material:1.4.0-alpha02
-keep class com.google.android.material.bottomsheet.BottomSheetDialog$EdgeToEdgeCallback {
private void setPaddingForPosition(android.view.View);
}