Java'da farklı bir sınıftan özel bir alanın değeri nasıl okunur?


482

3. bir partide kötü tasarlanmış bir sınıfım var JARve özel alanlarından birine erişmem gerekiyor. Örneğin, neden özel bir alan seçmem gerekiyor?

class IWasDesignedPoorly {
    private Hashtable stuffIWant;
}

IWasDesignedPoorly obj = ...;

Değerini elde etmek için yansımayı nasıl kullanabilirim stuffIWant?

Yanıtlar:


668

Özel alanlara erişmek için, bunları sınıfın bildirilen alanlarından almanız ve ardından erişilebilir hale getirmeniz gerekir :

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

EDIT : aperkins tarafından yorumlandığı gibi , hem alana erişim, hem de erişilebilir olarak ayarlanması ve değerin alınması Exceptions atabilir , ancak dikkat etmeniz gereken tek kontrol edilen istisnalar yukarıda yorumlanmıştır.

Belirtilen NoSuchFieldExceptionbir alana karşılık gelmeyen bir adla bir alan istediyseniz, bu alan atılır.

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

IllegalAccessExceptionAlanının erişilebilir değildi eğer özel ve eksik üzerinden erişilebilir hale getirilmediği takdirde, örneğin (atılmış olacağını f.setAccessible(true)hattı.

RuntimeExceptionAtılabilmektedir lar ya vardır SecurityException(JVM en takdirde ler SecurityManagerbir alanın erişilebilirliğini değiştirmesine izin vermez) veya IllegalArgumentExceptiondeğil alanın sınıfının türünde bir nesne üzerinde erişim alanını denemek ve eğer s:

f.get("BOB"); //will throw IllegalArgumentException, as String is of the wrong type

4
İstisna yorumlarını açıklayabilir misiniz? kod çalışacak, ancak istisnalar atacak mı? ya da kod istisnalar atabilir?
Nir Levy

1
@Nir - Hayır - ihtimalle kodu (SecurityManager sağlayacak varsayılan olarak alanlar erişilebilirlik değiştirilmesi) sadece iyi çalışır - ama buna mecbur kolu (ya onları yakalamak veya olmalarını beyan istisnalar kontrol rethrown ). Cevabımı biraz değiştirdim. Oynamak ve ne olduğunu görmek için küçük bir test
senaryosu

3
Üzgünüm, bu cevap benim için kafa karıştırıcı. Belki de tipik istisna yönetimine bir örnek gösterebiliriz. Sınıflar yanlış bağlandığında istisnalar oluşacak gibi görünüyor. Kod örneği, istisnalar karşılık gelen llnes atılmış gibi görünüyor yapar.
LaFayette

2
getDeclaredField()üst sınıflarda tanımlanmışsa alanları bulamaz - ayrıca getDeclaredField()bir eşleşme bulana kadar (daha sonra arayabileceğiniz setAccessible(true)) veya siz ulaşana kadar her birini çağıran ana sınıf hiyerarşisi üzerinden yineleme yapmanız gerekir Object.
Luke Hutchison

1
@legend, bu tür erişimi yasaklamak için bir güvenlik yöneticisi yükleyebilirsiniz. Java 9'dan beri, bu tür bir erişimin modül sınırları boyunca çalışması beklenmemektedir (bir modül açıkça açık olmadıkça), ancak yeni kuralların uygulanmasına ilişkin bir geçiş aşaması vardır. Bunun yanı sıra, Yansıma gibi ek çaba gerektiren privatealan dışı alanlarda da her zaman bir fark yarattı . Yanlışlıkla erişimi engeller.
Holger

159

FieldUtilsApache commons-lang3'ten deneyin :

FieldUtils.readField(object, fieldName, true);

76
Commons-lang3'ün birkaç yöntemini bir araya getirerek dünyanın sorunlarının çoğunu çözebileceğinize inanıyorum.
Cameron

@yegor java neden buna izin veriyor? Java neden özel üyelere erişim izni veriyor?
Asif Mushtaq

1
@ yegor256 Hala C # ve C ++ özel üyelerine de erişebiliyorum .. !! Sonra? tüm dil yaratıcıları zayıf mı?
Asif Mushtaq

3
@UnKnown java bunu yapmaz. İki farklı şey vardır - java dili ve java sanal makinesi olarak java. İkincisi bayt kodunda çalışır. Ve bunu manipüle edecek kütüphaneler var. Bu nedenle java dili, kapsam dışındaki özel alanları kullanmanıza veya nihai referansları değiştirmenize izin vermez. Ancak aracı kullanarak bunu yapmak mümkündür. Hangi sadece standart kütüphanenin içinde olur.
evgenii

3
@Bilinmeyen nedenlerden biri Java'nın yalnızca DI, ORM veya XML / JSON serileştirici gibi bir altyapı çerçevesi ile alana erişimi sağlamak için "arkadaş" erişimine sahip olmamasıdır. Bu tür çerçeveler, bir nesnenin iç durumunu doğru şekilde serileştirmek veya başlatmak için nesne alanlarına erişim gerektirir, ancak yine de iş mantığınız için uygun derleme zamanı kapsülleme uygulamasına ihtiyacınız olabilir.
Ivan Gammel

26

Yansıma sorununuzu çözmenin tek yolu değildir (bir sınıfın / bileşenin özel işlevine / davranışına erişmektir)

Alternatif bir çözüm, sınıfı .jar'dan ayıklamak, Jode veya Jad kullanarak düzeltin , alanı değiştirin (veya bir erişimci ekleyin) ve orijinal .jar ile yeniden derleyin. Sonra yeni .class .jarsınıf yolunun önüne koyun ya da içine yeniden yerleştirin .jar. (jar yardımcı programı mevcut bir .jar dosyasını ayıklamanıza ve yeniden takmanıza olanak tanır)

Aşağıda belirtildiği gibi, bu, bir alana erişmek / değiştirmek yerine özel duruma erişme / değiştirme konusundaki daha geniş sorunu çözmektedir.

Bu .jarelbette imzalanmamayı gerektirir .


36
Bu yol sadece basit bir alan için oldukça acı verici olacaktır.
Valentin Rocher

1
Katılmıyorum. Sadece alanınıza erişmenize izin vermekle kalmaz, aynı zamanda alana erişim yeterli değilse, sınıfı değiştirmenize de izin verir.
Brian Agnew

4
O zaman bunu tekrar yapmak zorundasın. Kavanoz bir güncelleme alırsa ve artık var olmayan bir alan için yansıma kullanırsanız ne olur? Aynı konu. Sadece yönetmek zorundasın.
Brian Agnew

4
A) pratik bir alternatif olarak vurgulandığında, b) bir alanın görünürlüğünün değiştirilmesinin yeterli olmadığı senaryolara hitap ettiği göz önüne alındığında şaşkınlık duyuyorum
Brian Agnew

2
@BrianAgnew belki sadece anlamsaldır, ancak soruya sadık kalırsak (özel bir alanı okumak için yansımayı kullanarak), yansımayı kullanmamak hemen kendi kendine bir çelişkidir. Ancak, alana erişim sağladığınızı kabul ediyorum ... ancak yine de alan artık özel değil, bu yüzden sorunun "özel alanını okumaya" devam etmiyoruz. Başka bir açıdan, .jar değişikliği bazı durumlarda (imzalı kavanoz) çalışmayabilir, kavanoz her güncellendiğinde yapılması gerekir, sınıf yolunun dikkatli bir şekilde manipüle edilmesini gerektirir (bu, uygulama kabı) vb.
Remi Morin

18

Henüz bahsedilmeyen bir diğer seçenek: Groovy kullanın . Groovy, dil tasarımının bir yan etkisi olarak özel bulut sunucusu değişkenlerine erişmenizi sağlar. Alan için bir alıcıya sahip olsanız da olmasanız da,

def obj = new IWasDesignedPoorly()
def hashTable = obj.getStuffIWant()

4
OP özellikle Java'yı sordu
Jochen

3
Günümüzde pek çok Java projesi mükemmeldir. Projenin Spring'in harika DSL'sini kullanması yeterlidir ve örneğin sınıf yolunda harika olacaklar. Bu durumda bu cevap faydalıdır ve OP'ye doğrudan cevap vermemekle birlikte, birçok ziyaretçi için yararlı olacaktır.
Amir Abiri

10

Kullanımı Java Reflection tüm erişebilirsiniz private/publicuyarınca başka bir .ama alanları ve bir sınıfın yöntemlerini Oracle belgelerine bölümündeki sakıncaları da tavsiye:

"Yansıma, kodun özel alanlara ve yöntemlere erişim gibi yansıtıcı olmayan kodda yasa dışı olabilecek işlemleri gerçekleştirmesine izin verdiğinden, yansıma kullanımı, kodun işlevsiz hale gelmesine ve taşınabilirliği yok edebilecek beklenmedik yan etkilere neden olabilir. soyutlamaları bozar ve bu nedenle platformun yükseltilmesiyle davranışı değiştirebilir "

yansıma ile ilgili temel kavramları göstermek için aşağıdaki kod eklerini takip ediyoruz

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()));
        }
    }

}

Umarım yardımcı olur.


5

Oxbow_lakes'den bahsedildiği gibi, erişim kısıtlamalarını aşmak için yansımayı kullanabilirsiniz (SecurityManager'ınızın size izin vereceğini varsayarak).

Bununla birlikte, eğer bu sınıf o kadar kötü tasarlanmışsa, bu tür bir hackery'e başvurmanızı sağlarsa, belki de bir alternatif aramalısınız. Elbette bu küçük kesmek birkaç saatten tasarruf etmenizi sağlayabilir, ancak yolda ne kadar tutacaktır?


3
Aslında bundan daha şanslıyım, sadece bazı verileri ayıklamak için bu kodu kullanıyorum, bundan sonra geri dönüşüm kutusuna atmak olabilir.
Frank Krueger

2
Bu durumda, hackleme. :-)
Laurence Gonsalves

3
Bu soruya bir cevap sağlamaz. Bir yazardan eleştiri veya açıklama istemek için gönderilerinin altına bir yorum bırakın.
Mureinik

@Mureinik - "Yansımayı kullanabilirsiniz" kelimeleriyle soruyu cevaplıyor. Bir örneği ya da daha büyük bir açıklaması ya da nasıl yok, ama bu bir cevap. Eğer beğenmediysen aşağı oy ver.
ArtOfWarfare


3

Aşağıdakileri yapmanız gerekir:

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);
}

2

Spring kullanıyorsanız, ReflectionTestUtils , burada en az çabayla yardımcı olan bazı kullanışlı araçlar sağlar. "Birim ve entegrasyon test senaryolarında kullanım için" olarak tanımlanır . ReflectionUtils adında benzer bir sınıf da vardır, ancak bu "Yalnızca dahili kullanım için tasarlanmıştır" olarak tanımlanmıştır - bunun ne anlama geldiğini yorumlamak için bu cevaba bakınız .

Gönderilen örneği ele almak için:

Hashtable iWantThis = (Hashtable)ReflectionTestUtils.getField(obj, "stuffIWant");

1
Bir Utils sınıfını Spring'e göre kullanmaya karar verirseniz , elbette, test etmediyseniz ( docs.spring.io/spring-framework/docs/current/javadoc-api/org/… ) kullanmalısınız. ünite testleri için kullanıyor.
Sync

Her ne kadar bu sınıf "Yalnızca dahili kullanım için tasarlanmıştır" olarak tanımlanmış olsa da. Bunun cevabına biraz bilgi ekledim. (Deneyimde olduğu gibi kullanarak örneği sakladım, ReflectionTestUtilssadece bir test durumunda bu tür bir şey yapmam gerekiyordu.)
Steve Chambers

1

Sadece yansıma hakkında ek bir not: Bazı özel durumlarda, farklı paketlerde aynı ada sahip birkaç sınıf bulunduğunda, üst cevapta kullanılan yansımanın nesneden doğru sınıfı seçemeyebileceğini gözlemledim. Bu nedenle, nesnenin package.class dosyasının ne olduğunu biliyorsanız, özel alan değerlerine aşağıdaki gibi erişmek daha iyidir:

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);

(Bu benim için çalışmayan örnek sınıf)


1

Doğrudan, tür güvenli Java yansıması için Manifold'un @JailBreak'ini kullanabilirsiniz :

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

public class Foo {
    private String stuffIWant;
}

@JailBreakhiyerarşisindeki footüm üyelere doğrudan erişim için derleyicideki yerel değişkenin kilidini açar Foo.

Benzer şekilde , bir kerelik kullanım için jailbreak () uzantı yöntemini kullanabilirsiniz:

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

Bu jailbreak()yöntem sayesinde üyelerin Foohiyerarşisindeki herhangi bir üyeye erişebilirsiniz .

Her iki durumda da derleyici sizin için alan erişimini kamusal bir alan gibi güvenli bir şekilde yazarken, Manifold da sizin için başlık altında verimli yansıma kodu üretir.

Manifold hakkında daha fazla bilgi edinin .


1

XrayInterface aracı ile oldukça kolaydır . Sadece eksik alıcıları / belirleyicileri tanımlayın, örn.

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

ve kötü tasarlanmış projenize xray:

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

Dahili olarak bu yansımaya dayanır.


0

Veri setini ayarlamak / almak istediğiniz vaka için sorunu çözmeye çalışın, kendi sınıflarınızdan biridir.

Bunun için bir public setter(Field f, Object value)ve oluşturun public Object getter(Field f). Hatta kendi üye tezlerinizin kendi işlevleri üzerinde bazı güvenlik kontrolleri de yapabilirsiniz. Örneğin pasör için:

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);
    }
}

Tabii ki, şimdi öğrenmek zorunda java.lang.reflect.Fieldiçin sStringönce alan değerinin ayarlanması için.

Bu tekniği genel bir ResultSet-to-and-from-mapperper içinde kullanıyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.