Bagaimana cara membaca nilai bidang pribadi dari kelas yang berbeda di Jawa?

482

Saya memiliki kelas yang dirancang dengan buruk di pihak ke-3 JARdan saya perlu mengakses salah satu bidang pribadinya . Sebagai contoh, mengapa saya harus memilih bidang pribadi?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

Bagaimana saya bisa menggunakan refleksi untuk mendapatkan nilai stuffIWant?

Frank Krueger
sumber

Jawaban:

668

Untuk mengakses bidang pribadi, Anda harus mendapatkannya dari bidang yang dideklarasikan kelas dan kemudian membuatnya dapat diakses:

Field f = obj.getClass().getDeclaredField("stuffIWant"); //NoSuchFieldException
f.setAccessible(true);
Hashtable iWantThis = (Hashtable) f.get(obj); //IllegalAccessException

EDIT : seperti yang telah berkomentar oleh aperkins , baik mengakses lapangan, pengaturan sebagai diakses dan mengambil nilai dapat membuang Exceptions, meskipun hanya diperiksa pengecualian Anda perlu berhati-hati dari yang berkomentar atas.

Itu NoSuchFieldExceptionakan dibuang jika Anda meminta bidang dengan nama yang tidak sesuai dengan bidang yang dinyatakan.

obj.getClass().getDeclaredField("misspelled"); //will throw NoSuchFieldException

The IllegalAccessExceptionakan dilemparkan jika lapangan tidak dapat diakses (misalnya, jika itu pribadi dan belum dilakukan diakses melalui hilang keluar f.setAccessible(true)jalur.

The RuntimeExceptions yang dapat dibuang yang baik SecurityExceptions (jika JVM SecurityManagertidak akan memungkinkan Anda untuk mengubah aksesibilitas lapangan), atau IllegalArgumentExceptions, jika Anda mencoba dan akses lapangan pada objek bukan dari jenis kelas ladang ini:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type
oxbow_lakes
sumber
4
dapatkah Anda menjelaskan komentar pengecualian? kode akan berjalan tetapi akan membuang pengecualian? atau kode dapat melempar pengecualian?
Nir Levy
1
@Nir - Tidak - dalam semua kemungkinan kode akan berjalan dengan baik (karena SecurityManager default akan memungkinkan aksesibilitas field diubah) - tetapi Anda harus menangani pengecualian yang diperiksa (menangkap atau menyatakan mereka akan dipindah ). Saya sedikit mengubah jawaban saya. Mungkin baik bagi Anda untuk menulis test case kecil untuk bermain-main dan melihat apa yang terjadi
oxbow_lakes
3
Maaf, jawaban ini membingungkan saya. Mungkin menunjukkan contoh penanganan pengecualian yang khas. Sepertinya pengecualian akan terjadi ketika kelas disatukan secara tidak benar. Sampel kode membuatnya tampak seperti pengecualian yang akan dilemparkan pada llnes yang sesuai.
LaFayette
2
getDeclaredField()tidak menemukan bidang jika didefinisikan dalam kelas induk - Anda juga harus beralih melalui hierarki kelas induk, memanggil getDeclaredField()masing-masing, hingga Anda menemukan kecocokan (setelah itu Anda dapat menelepon setAccessible(true)), atau Anda mencapai Object.
Luke Hutchison
1
@legend Anda dapat menginstal manajer keamanan untuk melarang akses tersebut. Sejak Java 9, akses tersebut tidak seharusnya bekerja lintas batas modul (kecuali modul terbuka secara eksplisit), meskipun ada fase transisi terkait penegakan aturan baru. Selain itu, membutuhkan upaya tambahan seperti Refleksi selalu membuat perbedaan untuk non- privatebidang. Ini menghalangi akses tidak disengaja.
Holger
159

Coba FieldUtilsdari apache commons-lang3:

FieldUtils.readField(object, fieldName, true);
yegor256
sumber
76
Saya yakin Anda bisa menyelesaikan sebagian besar masalah dunia dengan merangkai beberapa metode dari commons-lang3.
Cameron
@Yegor mengapa java mengizinkan ini? Maksud mengapa java memungkinkan untuk mengakses anggota pribadi?
Asif Mushtaq
1
@ yegor256 Saya masih dapat mengakses anggota pribadi C # dan C ++ juga .. !! Kemudian? semua pembuat bahasa lemah?
Asif Mushtaq
3
@UnKnown java tidak. Ada dua hal yang berbeda - bahasa java dan java sebagai mesin virtual java. Yang terakhir beroperasi pada bytecode. Dan ada perpustakaan untuk memanipulasi itu. Jadi bahasa java tidak memungkinkan Anda untuk menggunakan bidang pribadi di luar ruang lingkup atau tidak mengizinkan mutasi referensi akhir. Tetapi dimungkinkan untuk melakukannya menggunakan alat. Yang kebetulan berada di dalam perpustakaan standar.
evgenii
3
@UnKnown Salah satu alasannya adalah Java tidak memiliki akses "teman" untuk mengaktifkan akses ke lapangan hanya dengan kerangka infrastruktur seperti DI, ORM atau XML / JSON serializer. Kerangka kerja seperti itu memerlukan akses ke bidang objek untuk membuat cerita bersambung dengan benar atau menginisialisasi keadaan internal suatu objek, tetapi Anda mungkin masih memerlukan penegakan enkapsulasi waktu kompilasi yang tepat untuk logika bisnis Anda.
Ivan Gammel
26

Refleksi bukan satu-satunya cara untuk menyelesaikan masalah Anda (yaitu mengakses fungsionalitas / perilaku kelas / komponen)

Solusi alternatif adalah mengekstrak kelas dari .jar, mendekompilasinya menggunakan (katakanlah) Jode atau Jad , ganti bidang (atau tambahkan accessor), dan kompilasi ulang terhadap .jar yang asli. Kemudian letakkan .class yang baru di depan .jardalam classpath, atau masukkan kembali di .jar. (utilitas jar memungkinkan Anda untuk mengekstrak dan memasukkan kembali ke .jar yang ada)

Seperti dicatat di bawah ini, ini menyelesaikan masalah yang lebih luas dalam mengakses / mengubah negara swasta daripada hanya mengakses / mengubah bidang.

Ini mengharuskan yang .jartidak ditandatangani, tentu saja.

Brian Agnew
sumber
36
Cara ini akan sangat menyakitkan hanya untuk bidang sederhana.
Valentin Rocher
1
Saya tidak setuju. Ini memungkinkan Anda untuk tidak hanya mengakses bidang Anda, tetapi juga untuk mengubah kelas jika perlu jika mengakses bidang ternyata tidak cukup.
Brian Agnew
4
Maka Anda harus melakukannya lagi. Apa yang terjadi jika tabung mendapat pembaruan dan Anda menggunakan refleksi untuk bidang yang tidak ada lagi? Persisnya masalah yang sama. Anda hanya perlu mengelolanya.
Brian Agnew
4
Saya heran berapa banyak ini akan dibatalkan diberikan a) itu disorot sebagai alternatif praktis b) itu melayani skenario di mana mengubah visibilitas lapangan tidak cukup
Brian Agnew
2
@BrianAgnew mungkin hanya semantik tetapi jika kita tetap pada pertanyaan (menggunakan refleksi untuk membaca bidang pribadi) tidak menggunakan refleksi segera merupakan kontradiksi diri. Tapi saya setuju Anda memberikan akses ke bidang ... tapi bidang itu masih tidak pribadi lagi jadi sekali lagi kami tidak berpegang pada "baca bidang pribadi" dari pertanyaan. Dari sudut lain, modifikasi .jar mungkin tidak berfungsi dalam beberapa kasus (jar yang ditandatangani), perlu dilakukan setiap kali jar diperbarui, memerlukan manipulasi hati-hati classpath (yang mungkin tidak sepenuhnya Anda kontrol jika Anda dieksekusi di sebuah wadah aplikasi) dll.
Remi Morin
18

Satu opsi lain yang belum disebutkan: gunakan Groovy . Groovy memungkinkan Anda untuk mengakses variabel instance pribadi sebagai efek samping dari desain bahasa. Apakah Anda memiliki pengambil untuk bidang tersebut, Anda bisa menggunakannya

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()
lucas
sumber
4
OP secara khusus meminta Java
Jochen
3
Banyak proyek Jawa saat ini memasukkan groovy. Sudah cukup bahwa proyek menggunakan DSL asyik Spring dan mereka akan memiliki groovy pada classpath misalnya. Dalam hal ini jawaban ini berguna, dan walaupun tidak secara langsung menjawab OP, itu akan bermanfaat bagi banyak pengunjung.
Amir Abiri
10

Menggunakan Refleksi di Java Anda dapat mengakses semua private/publicbidang dan metode dari satu kelas ke kelas lain. Tetapi sesuai dengan dokumentasi Oracle di bagian kelemahan yang mereka rekomendasikan:

"Karena refleksi memungkinkan kode untuk melakukan operasi yang ilegal dalam kode non-reflektif, seperti mengakses bidang dan metode pribadi, penggunaan refleksi dapat menghasilkan efek samping yang tidak terduga, yang dapat membuat kode disfungsional dan dapat merusak portabilitas. Kode reflektif memecah abstraksi dan karenanya dapat mengubah perilaku dengan peningkatan platform "

di sini adalah mengikuti snap kode untuk menunjukkan konsep dasar Refleksi

Reflection1.java

public class Reflection1{

    private int i = 10;

    public void methoda()
    {

        System.out.println("method1");
    }
    public void methodb()
    {

        System.out.println("method2");
    }
    public void methodc()
    {

        System.out.println("method3");
    }

}

Reflection2.java

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;


public class Reflection2{

    public static void main(String ar[]) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
    {
        Method[] mthd = Reflection1.class.getMethods(); // for axis the methods 

        Field[] fld = Reflection1.class.getDeclaredFields();  // for axis the fields  

        // Loop for get all the methods in class
        for(Method mthd1:mthd)
        {

            System.out.println("method :"+mthd1.getName());
            System.out.println("parametes :"+mthd1.getReturnType());
        }

        // Loop for get all the Field in class
        for(Field fld1:fld)
        {
            fld1.setAccessible(true);
            System.out.println("field :"+fld1.getName());
            System.out.println("type :"+fld1.getType());
            System.out.println("value :"+fld1.getInt(new Reflaction1()));
        }
    }

}

Semoga ini bisa membantu.

Simmant
sumber
5

Seperti yang disebutkan oxbow_lakes, Anda dapat menggunakan refleksi untuk mengatasi batasan akses (dengan asumsi SecurityManager Anda akan mengizinkan Anda).

Yang mengatakan, jika kelas ini dirancang dengan sangat buruk sehingga membuat Anda menggunakan peretasan seperti itu, mungkin Anda harus mencari alternatif. Tentu hack kecil ini mungkin menyelamatkan Anda beberapa jam sekarang, tetapi berapa biayanya di jalan?

Laurence Gonsalves
sumber
3
Sebenarnya saya lebih beruntung dari itu, saya hanya menggunakan kode ini untuk mengekstrak beberapa data, setelah itu saya bisa melemparkannya kembali ke Recycle Bin.
Frank Krueger
2
Nah dalam hal itu, retas. :-)
Laurence Gonsalves
3
Ini tidak memberikan jawaban untuk pertanyaan itu. Untuk mengkritik atau meminta klarifikasi dari penulis, tinggalkan komentar di bawah posting mereka.
Mureinik
@Mureinik - Itu menjawab pertanyaan, dengan kata-kata "Anda dapat menggunakan refleksi". Tidak memiliki contoh atau penjelasan yang lebih besar atau bagaimana, tetapi ini adalah jawaban. Turunkan unduhan jika Anda tidak menyukainya.
ArtOfWarfare
4

Gunakan kerangka Soot Java Optimization untuk secara langsung memodifikasi bytecode. http://www.sable.mcgill.ca/soot/

Jelaga sepenuhnya ditulis dalam Java dan berfungsi dengan versi Java baru.

pcpratts
sumber
3

Anda perlu melakukan hal berikut:

private static Field getField(Class<?> cls, String fieldName) {
    for (Class<?> c = cls; c != null; c = c.getSuperclass()) {
        try {
            final Field field = c.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (final NoSuchFieldException e) {
            // Try parent
        } catch (Exception e) {
            throw new IllegalArgumentException(
                    "Cannot access field " + cls.getName() + "." + fieldName, e);
        }
    }
    throw new IllegalArgumentException(
            "Cannot find field " + cls.getName() + "." + fieldName);
}
Luke Hutchison
sumber
2

Jika menggunakan Spring, ReflectionTestUtils menyediakan beberapa alat praktis yang membantu di sini dengan upaya minimal. Ini digambarkan sebagai "untuk digunakan dalam skenario pengujian unit dan integrasi" . Ada juga kelas serupa bernama ReflectionUtils tetapi ini digambarkan sebagai "Hanya untuk penggunaan internal" - lihat jawaban ini untuk interpretasi tentang apa artinya ini.

Untuk mengatasi contoh yang diposting:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");
Steve Chambers
sumber
1
Jika Anda memutuskan untuk menggunakan kelas Utils sebelum musim semi, Anda benar-benar harus menggunakan yang non-tes ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) alih-alih kecuali, tentu saja, Anda Sebenarnya menggunakannya untuk tes unit.
Sinkronisasi
Mungkin, meskipun kelas itu digambarkan sebagai "Hanya dimaksudkan untuk penggunaan internal". Saya telah menambahkan beberapa info ke jawaban tentang ini. (Tetap menggunakan contoh ReflectionTestUtilsseperti dalam pengalaman saya, saya hanya perlu melakukan hal semacam ini dalam situasi pengujian.)
Steve Chambers
1

Hanya catatan tambahan tentang refleksi: Saya telah mengamati dalam beberapa kasus khusus, ketika beberapa kelas dengan nama yang sama ada dalam paket yang berbeda, refleksi yang digunakan dalam jawaban atas mungkin gagal untuk mengambil kelas yang benar dari objek. Jadi jika Anda tahu apa itu package.class objek, maka lebih baik untuk mengakses nilai bidang privatnya sebagai berikut:

org.deeplearning4j.nn.layers.BaseOutputLayer ll = (org.deeplearning4j.nn.layers.BaseOutputLayer) model.getLayer(0);
Field f = Class.forName("org.deeplearning4j.nn.layers.BaseOutputLayer").getDeclaredField("solver");
f.setAccessible(true);
Solver s = (Solver) f.get(ll);

(Ini adalah contoh kelas yang tidak berfungsi untuk saya)

xtof54
sumber
1

Anda dapat menggunakan @JailBreak Manifold untuk refleksi Java langsung dan aman:

@JailBreak Foo foo = new Foo();
foo.stuffIWant = "123;

public class Foo {
    private String stuffIWant;
}

@JailBreakmembuka foovariabel lokal dalam kompiler untuk akses langsung ke semua anggota dalam Foohierarki.

Demikian pula Anda dapat menggunakan metode ekstensi jailbreak () untuk sekali pakai:

foo.jailbreak().stuffIWant = "123";

Melalui jailbreak()metode ini Anda dapat mengakses anggota mana pun diFoo hierarki.

Dalam kedua kasus kompiler menyelesaikan akses bidang untuk Anda ketik-aman, seolah-olah bidang publik, sementara Manifold menghasilkan kode refleksi yang efisien untuk Anda di bawah tenda.

Temukan lebih lanjut tentang Manifold .

Scott
sumber
1

Sangat mudah dengan alat XrayInterface . Cukup tentukan getter / setter yang hilang, mis

interface BetterDesigned {
  Hashtable getStuffIWant(); //is mapped by convention to stuffIWant
}

dan xray proyek Anda yang dirancang buruk:

IWasDesignedPoorly obj = new IWasDesignedPoorly();
BetterDesigned better = ...;
System.out.println(better.getStuffIWant());

Secara internal ini bergantung pada refleksi.

Korona
sumber
0

Cobalah untuk mengatasi masalah untuk kasus ini, calass yang Anda ingin atur / dapatkan data adalah salah satu kelas Anda sendiri.

Cukup buat public setter(Field f, Object value)dan public Object getter(Field f)untuk itu. Anda bahkan dapat melakukan beberapa pemeriksaan keamanan pada fungsi anggota tesis Anda sendiri. Misalnya untuk setter:

class myClassName {
    private String aString;

    public set(Field field, Object value) {
        // (A) do some checkings here  for security

        // (B) set the value
        field.set(this, value);
    }
}

Tentu saja, sekarang Anda harus mencari tahu java.lang.reflect.FielduntuksString sebelum menetapkan nilai lapangan.

Saya memang menggunakan teknik ini dalam ResultSet-to-dan-dari-model-mapper generik.

karldegen
sumber