Neden yansıma kullanmalıyım?


29

Java’da yeniyim; Yaptığım çalışmalar sayesinde, yansıtmanın sınıfları ve yöntemleri çağırmak ve hangi yöntemlerin uygulanıp uygulanmadığını bilmek için kullanıldığını okudum.

Yansımayı ne zaman kullanmalıyım ve yansıma ile başlatılan nesneleri kullanma ve geleneksel yöntemlerle çağrı yöntemleri arasındaki fark nedir?



10
Lütfen yayınlamadan önce araştırma payınızı yapın. StackExchange (@Jalayn'ın belirttiği gibi) ve web'in yansıması hakkında genel olarak çok fazla materyal var. Örneğin, Yansıma konusunda Java Eğitimini okumanızı ve daha somut sorularınız varsa geri gelmenizi öneririm .
Péter Török

1
Bir milyon dupe olmalı.
DeadMG

3
Birkaç profesyonel programcıdan fazlası “mümkün olduğunca nadiren, belki de asla” yanıtını verecektir.
Ross Patterson

Yanıtlar:


38
  • Yansıma sadece adlarına göre arama yöntemlerinden çok daha yavaştır, çünkü sadece önceden derlenmiş adresler ve sabitler kullanmak yerine bytecode'daki meta verileri denetlemek zorundadır.

  • Yansıma ayrıca daha güçlüdür: Bir protectedveya finalüyenin tanımını geri alabilir , korumayı kaldırabilir ve değişken olarak bildirilmiş gibi manipüle edebilirsiniz! Açıkçası bu, dilin normalde programlarınız için yaptığı garantilerin çoğunu altüst eder ve çok, çok tehlikeli olabilir.

Bu da ne zaman kullanılacağını açıklıyor. Normalde yapma. Bir yöntemi çağırmak istiyorsanız, sadece aramanız yeterlidir. Bir üyeyi mutasyona uğratmak istiyorsanız, derlemenin arkasına geçmek yerine sadece değişebilir olduğunu beyan edin.

Yansımanın yararlı gerçek dünyalarından biri, kullanıcı tanımlı sınıflarla birlikte çalışması gereken bir çerçeve yazarken, çerçeve yazarının üyelerin (hatta sınıfların) ne olacağını bilmediği durumlardadır. Yansıma, önceden bilmeden herhangi bir sınıfla başa çıkmalarını sağlar. Mesela yansıma olmadan karmaşık bir boyuta dayalı bir kütüphane yazmanın mümkün olacağını sanmıyorum.

Başka bir örnek olarak, JUnit önemsiz bir yansıma biti kullanmak için kullanılır: sınıfınızdaki tüm yöntemleri sıralar, çağrılanların testXXXtest yöntemleri olduğunu varsayar ve sadece bunları uygular. Ancak bu şimdi açıklamalarla yerine daha iyi yapılabilir ve aslında JUnit 4 bunun yerine büyük ölçüde notlara taşınır.


6
"Daha güçlü" özen ister. Turing'in bütünlüğünü elde etmek için yansıma gerekmez, bu nedenle hiçbir hesaplama yansıma gerektirmez. Elbette Turing tamamlandı, G / Ç özellikleri ve tabii ki yansıma gibi diğer tür güçler hakkında hiçbir şey söylemez.
Steve314

1
Yansıma mutlaka "çok yavaş" değildir. Doğrudan arama sarmalayıcı bayt kodu oluşturmak için yansımayı bir kez kullanabilirsiniz.
SK-mantığı

2
İhtiyacınız olduğunda bunu bileceksiniz. Sık sık neden (dil üretimi dışında) birinin ihtiyaç duyduğunu merak ettim. Sonra birdenbire ... Yaptığım sistemlerden birine yerleştirmek için diğer aygıtlardan paneller aldığımda, verileri gözetmek / dürtmek için ebeveyn / çocuk zincirlerinde yukarı ve aşağı yürümek zorunda kaldım.
Brian Knoblauch

@ SK-mantık: aslında bytecode oluşturmak için hiç bir şekilde yansıtmaya ihtiyacınız yoktur (aslında yansıma, bytecode manipülasyonu için hiçbir API içermez!).
Joachim Sauer

1
@JoachimSauer, elbette, ancak bu oluşturulan bytecode'u yüklemek için bir yansıma API'sine ihtiyacınız olacak .
SK-mantık

15

Bir zamanlar senin gibiydim, yansıma hakkında pek bir şey bilmiyordum - hala bilmiyor - ama bir kere kullandım.

İki iç sınıfa sahip bir sınıfım vardı ve her sınıfın birçok yöntemi vardı.

İç sınıftaki tüm yöntemleri çağırmam gerekiyordu ve onları manuel olarak çağırmak çok fazla iş olurdu.

Yansıma kullanarak, tüm bu yöntemleri, metotların sayısı yerine sadece 2-3 kod satırında çağırabilirim.


4
Neden aşağı oy?
Mahmud Hossam,

1
Giriş için Upvoting
Dmitry Minkovsky

1
@MahmoudHossam Belki de en iyi uygulama değil, ancak cevabınız birinin uygulayabileceği önemli bir taktiği gösteriyor.
ankush981

13

Yansıma kullanımlarını üç gruba ayırırım:

  1. Keyfi sınıfları başlatmak. Örneğin, bir bağımlılık enjeksiyon çerçevesinde, muhtemelen ThingDoer arayüzünün NetworkThingDoer sınıfı tarafından uygulandığını beyan etmiş olursunuz. Çerçeve daha sonra NetworkThingDoer'in kurucusunu bulur ve onu başlatır.
  2. Başka bir formata marşaling ve marşal. Örneğin, bir nesneyi JSON'a göre fasulye kuralını izleyen alıcılarla ve ayarlarla eşleştirmek ve tekrar geri getirmek. Kod aslında alanların veya yöntemlerin adlarını bilmez, sadece sınıfı inceler.
  3. Bir yeniden yönlendirme katmanına bir sınıf (belki de List aslında yüklenmemiş, ancak veritabanından nasıl getirileceğini bilen bir şeye işaretçi olarak) ya da tamamen sınıfa taklit etmek (jMock, bir arayüz uygulayan sentetik bir sınıf oluşturacaktır) test amaçlı).

Bu StackExchange'te bulduğum en iyi yansıma açıklaması. Çoğu cevap Java İzinin söylediklerini tekrarlar (“tüm bu özelliklere erişebilirsiniz” dır, ancak neden böyle yapmazsınız), yansıma ile yapmadan çok daha kolay olan şeyler yapmanın örneklerini ya da Bahar'ın nasıl belirsiz bir cevap verdiğini söyleyin. kullanır. Bu cevap aslında CANNOT'un JVM tarafından yansıma olmadan kolayca çalışabileceği üç geçerli örnek veriyor. Teşekkür ederim!
ndm13

3

Yansıma, bir programın bulunamayabilecek kodla çalışmasını ve güvenilir bir şekilde yapılmasını sağlar.

"Normal kod" URLConnection c = null, sınıf varlığı yükleyicisinin URLConnection sınıfını bu sınıfı yüklemenin bir parçası olarak yüklemesine, bir ClassNotFound istisnası atmasına ve çıkmasına neden olduğu gibi parçacıklara sahiptir.

Yansıma, sınıflara isimlerine göre sınıfları yüklemenizi ve bunlara bağlı gerçek sınıfları başlatmadan önce bunları çeşitli özellikler (testinizin dışındaki birden fazla sürüm için yararlıdır) için test etmenizi sağlar. Tipik bir örnek, Java programlarının, diğer platformlarda bulunmayan OS X altında yerel görünmesini sağlamak için kullanılan OS X'e özgü koddur.


2

Temel olarak, yansıma, programınızın kodunu veri olarak kullanmak anlamına gelir.

Bu nedenle, programınızın kodu faydalı bir veri kaynağı olduğunda yansıma kullanmak iyi bir fikir olabilir. (Ancak takaslar vardır, bu yüzden her zaman iyi bir fikir olmayabilir.)

Örneğin, basit bir sınıf düşünün:

public class Foo {
  public int value;
  public string anotherValue;
}

ve ondan XML oluşturmak istiyorsunuz. XML'i oluşturmak için kod yazabilirsiniz:

public XmlNode generateXml(Foo foo) {
  XmlElement root = new XmlElement("Foo");
  XmlElement valueElement = new XmlElement("value");
  valueElement.add(new XmlText(Integer.toString(foo.value)));
  root.add(valueElement);
  XmlElement anotherValueElement = new XmlElement("anotherValue");
  anotherValueElement.add(new XmlText(foo.anotherValue));
  root.add(anotherValueElement);
  return root;
}

Fakat bu çok fazla kazan kodudur ve sınıfı her değiştirdiğinizde kodu güncellemeniz gerekir. Gerçekten, bu kodun ne yaptığını tanımlayabilirsiniz.

  • sınıfın ismiyle bir XML elemanı yaratın
  • sınıfın her özelliği için
    • özelliğin adıyla bir XML öğesi oluşturun
    • özelliğin değerini XML öğesinin içine koy
    • XML öğesini köke ekle

Bu bir algoritmadır ve algoritmanın girdisi sınıftır: onun adına ve özelliklerinin adlarına, türlerine ve değerlerine ihtiyacımız var. Yansımanın geldiği yer burasıdır: size bu bilgilere erişim sağlar. Java, Classsınıfın yöntemlerini kullanarak türleri denetlemenizi sağlar .

Bazı daha fazla kullanım durumları:

  • Bir web sunucusundaki URL'leri bir sınıfın yöntem adlarına ve yöntem argümanlarına göre URL parametrelerine göre tanımlar.
  • bir sınıfın yapısını GraphQL tip tanımına dönüştürmek
  • ünite test durumu olarak adı "test" ile başlayan bir sınıfın her yöntemini çağırın

Bununla birlikte, tam yansıtma sadece varolan koda bakmak (kendi başına "iç gözlem" olarak da bilinir) değil, aynı zamanda kodu değiştirmek veya üretmek anlamına gelir. Bunun için Java’da iki önemli kullanım durumu vardır: proxy'ler ve alaylar.

Arayüzünüz olduğunu varsayalım:

public interface Froobnicator {
  void froobnicateFruits(List<Fruit> fruits);
  void froobnicateFuel(Fuel fuel);
  // lots of other things to froobnicate
}

ve ilginç bir şey yapan bir uygulamanız var:

public class PowerFroobnicator implements Froobnicator {
  // awesome implementations
}

Ve aslında sizin de ikinci bir uygulamanız var:

public class EnergySaverFroobnicator implements Froobnicator {
  // efficient implementations
}

Şimdi ayrıca bazı günlük çıktıları istiyorsunuz; ne zaman bir yöntem çağrılırsa sadece bir günlük mesajı istersiniz. Açıkça her yönteme günlük çıktısı ekleyebilirsiniz, ancak bu can sıkıcı olurdu ve bunu iki kez yapmanız gerekirdi; Her uygulama için bir kez. (Daha fazla uygulama eklediğinizde daha da fazla.)

Bunun yerine, bir proxy yazabilirsiniz:

public class LoggingFroobnicator implements Froobnicator {
  private Logger logger;
  private Froobnicator inner;

  // constructor that sets those two

  public void froobnicateFruits(List<Fruit> fruits) {
    logger.logDebug("froobnicateFruits called");
    inner.froobnicateFruits(fruits);
  }

  public void froobnicateFuel(Fuel fuel) {
    logger.logDebug("froobnicateFuel( called");
    inner.froobnicateFuel(fuel);
  }
  // lots of other things to froobnicate
}

Yine de, bir algoritma ile tanımlanabilen tekrarlayan bir desen vardır:

  • logger proxy, bir arayüz uygulayan bir sınıftır
  • Arabirimin başka bir uygulamasını alan bir yapıcı ve bir kaydedici var
  • Arayüzdeki her yöntem için
    • uygulama "$ methodname adlı" mesajını kaydeder
    • ve sonra, tüm argümanlardan geçen iç arayüzde aynı yöntemi çağırır.

ve bu algoritmanın girişi arayüz tanımıdır.

Yansıma, bu algoritmayı kullanarak yeni bir sınıf tanımlamanıza izin verir. Java bunu java.lang.reflect.Proxysınıfın yöntemlerini kullanarak yapmanızı sağlar ve size daha da fazla güç veren kütüphaneler vardır.

Peki yansımanın olumsuz tarafları nelerdir?

  • Kodunuzun anlaşılması zorlaşıyor. Kodunuzun somut etkilerinden bir kez daha soyutlanmışsınız.
  • Kodunuz hata ayıklamak zorlaşıyor. Özellikle kod üreten kitaplıklarda, yürütülen kod yazdığınız kod olmayabilir, ancak oluşturduğunuz kod olabilir ve hata ayıklayıcı bu kodu size gösteremeyebilir (veya kesme noktaları koymanıza izin verebilir).
  • Kodunuz yavaşlar. Dinamik olarak tip bilgisini okumak ve alanlara kodlama erişimi yerine çalışma zamanı tutamaçlarına göre erişmek daha yavaştır. Dinamik kod oluşturma, hata ayıklamanın daha da zor olması pahasına bu etkiyi azaltabilir.
  • Kodunuz daha kırılgan hale gelebilir. Dinamik yansıma erişimi derleyici tarafından yazılmaz, ancak çalışma zamanında hatalar atar.

1

Yansıma otomatik olarak programınızın bölümlerini senkronize halde tutabilir; burada daha önce yeni arayüzleri kullanmak için programınızı manuel olarak güncellemeniz gerekirdi.


5
Bu durumda ödediğiniz fiyat, derleyici tarafından denetleme ve IDE'deki refactor-emniyetini kaybetmenizdir. Yapmaya istekli olmadığım bir takas.
Barend
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.