Java bayt kodu ile bir süre çalıştıktan ve bu konuda bazı ek araştırmalar yaptıktan sonra, işte bulgularımın bir özeti:
Bir süper yapıcıyı veya yardımcı oluşturucuyu çağırmadan önce bir yapıcıda kodu yürütün
Java programlama dilinde (JPL), bir kurucunun ilk ifadesi, bir süper yapıcının veya aynı sınıfın başka bir yapıcısının bir çağrısı olmalıdır. Bu Java bayt kodu (JBC) için geçerli değildir. Bayt kodu içinde, herhangi bir kodu bir yapıcıdan önce yürütmek, aşağıdaki durumlarda kesinlikle meşrudur:
- Bu kod bloğundan bir süre sonra başka bir uyumlu kurucu çağrılır.
- Bu çağrı bir koşullu ifade dahilinde değildir.
- Bu yapıcı çağrısından önce, inşa edilen örneğin hiçbir alanı okunmaz ve yöntemlerinden hiçbiri çağrılmaz. Bu, bir sonraki maddeyi ima eder.
Bir süper yapıcı veya yardımcı oluşturucu çağırmadan önce örnek alanlarını ayarlayın
Daha önce de belirtildiği gibi, başka bir kurucu çağırmadan önce bir örneğin alan değerini ayarlamak tamamen yasaldır. 6'dan önceki Java sürümlerinde bu "özelliği" istismar etmesini sağlayan eski bir hack bile vardır:
class Foo {
public String s;
public Foo() {
System.out.println(s);
}
}
class Bar extends Foo {
public Bar() {
this(s = "Hello World!");
}
private Bar(String helper) {
super();
}
}
Bu şekilde, süper yapıcı çağrılmadan önce bir alan ayarlanabilir, ancak bu artık mümkün değildir. JBC'de bu davranış hala uygulanabilir.
Süper yapıcı çağrısını dallandırma
Java'da, bir yapıcı çağrısının tanımlanması mümkün değildir.
class Foo {
Foo() { }
Foo(Void v) { }
}
class Bar() {
if(System.currentTimeMillis() % 2 == 0) {
super();
} else {
super(null);
}
}
Java 7u23'e kadar, HotSpot VM'nin doğrulayıcısı bu kontrolü kaçırdı, bu yüzden mümkündü. Bu, birkaç kod oluşturma aracı tarafından bir tür hack olarak kullanıldı, ancak artık böyle bir sınıfı uygulamak yasal değil.
İkincisi, bu derleyici sürümünde yalnızca bir hataydı. Daha yeni derleyici sürümlerinde bu yine mümkündür.
Herhangi bir kurucu olmadan bir sınıf tanımlayın
Java derleyicisi her zaman herhangi bir sınıf için en az bir kurucu uygulayacaktır. Java bayt kodunda bu gerekli değildir. Bu, yansıma kullanıldığında bile inşa edilemeyen sınıfların oluşturulmasına izin verir. Bununla birlikte, kullanmak sun.misc.Unsafe
yine de bu tür örneklerin oluşturulmasına izin verir.
Aynı imzaya sahip ancak farklı dönüş türüne sahip yöntemleri tanımlayın
JPL'de bir yöntem, adı ve ham parametre türleri ile benzersiz olarak tanımlanır. JBC'de ek olarak ham iade türü dikkate alınır.
Ada göre değil, yalnızca türe göre farklılık gösteren alanları tanımlayın
Bir sınıf dosyası, farklı bir alan türü bildirdikleri sürece aynı adda birkaç alan içerebilir. JVM her zaman bir alana ad ve tür demeti olarak başvurur.
Bildirilmemiş işaretli istisnaları yakalamadan atın
Java çalışma zamanı ve Java bayt kodu, kontrol edilen istisnalar kavramından haberdar değildir. Kontrol edilen istisnaların her zaman yakalandığını veya atıldıklarında bildirildiğini doğrulayan yalnızca Java derleyicisidir.
Lambda ifadelerinin dışında dinamik yöntem çağrısı kullanın
Sözde dinamik yöntem çağrısı , yalnızca Java'nın lambda ifadeleri için değil, her şey için kullanılabilir. Bu özelliğin kullanılması, örneğin çalışma zamanında yürütme mantığının kapatılmasına izin verir. JBC'ye indirgenen birçok dinamik programlama dili, bu talimatı kullanarak performanslarını artırdı . Java bayt kodunda, JVM talimatı zaten anlarken derleyicinin dinamik yöntem çağrısının kullanımına henüz izin vermediği Java 7'deki lambda ifadelerini de taklit edebilirsiniz.
Normalde yasal olarak kabul edilmeyen tanımlayıcıları kullanın
Yönteminizin adına boşluk ve satır sonu kullanmayı hiç düşündünüz mü? Kendi JBC'nizi oluşturun ve kod incelemesi için iyi şanslar. Tanımlayıcıları için sadece yasadışı karakterler .
, ;
, [
ve /
. Ayrıca, adlandırılmayan <init>
veya ve <clinit>
içeremeyen yöntemler .<
>
final
Parametreleri veya this
referansı yeniden atayın
final
parametreler JBC'de yoktur ve sonuç olarak yeniden atanabilir. this
Referans dahil olmak üzere herhangi bir parametre yalnızca JVM içindeki basit bir dizide depolanır ve bu, this
referansın 0
tek bir yöntem çerçevesi içinde indekste yeniden atanmasına izin verir .
final
Alanları yeniden ata
Bir kurucu içinde son bir alan atandığı sürece, bu değeri yeniden atamak veya hatta bir değer atamamak yasaldır. Bu nedenle, aşağıdaki iki kurucu yasaldır:
class Foo {
final int bar;
Foo() { }
Foo(Void v) {
bar = 1;
bar = 2;
}
}
İçin static final
alanlar, hatta sınıf başlatıcısı dışında alanlarını yeniden atamak için izin verilir.
Yapıcılara ve sınıf başlatıcısına yöntemmiş gibi davranın
Bu daha çok kavramsal bir özelliktir, ancak kurucular JBC içinde normal yöntemlerden farklı olarak ele alınmaz. İnşaatçıların başka bir yasal kurucu çağırmasını sağlayan yalnızca JVM'nin doğrulayıcısıdır. Bunun dışında, kurucuların çağrılması <init>
ve sınıf başlatıcısının çağrılması yalnızca bir Java adlandırma kuralıdır <clinit>
. Bu farkın yanı sıra, yöntemlerin ve kurucuların temsili aynıdır. Holger'ın bir yorumda belirttiği void
gibi, bu metotları çağırmak mümkün olmasa bile, yapıcıları argümanlardan farklı dönüş türleriyle veya bir sınıf başlatıcısıyla tanımlayabilirsiniz .
Asimetrik kayıtlar oluşturun * .
Bir kayıt oluştururken
record Foo(Object bar) { }
javac, adında tek bir alan, adında bar
bir erişimci yöntemi bar()
ve tek bir Object
. Ek olarak, için bir kayıt özniteliği bar
eklenir. Manuel olarak bir kayıt oluşturarak, farklı bir kurucu şekli oluşturmak, alanı atlamak ve erişimciyi farklı şekilde uygulamak mümkündür. Aynı zamanda, yansıma API'sini sınıfın gerçek bir kaydı temsil ettiğine inandırmak hala mümkündür.
Herhangi bir süper yöntemi çağırın (Java 1.1'e kadar)
Ancak, bu yalnızca Java sürüm 1 ve 1.1 için mümkündür. JBC'de, yöntemler her zaman açık bir hedef türünde gönderilir. Bu şu demektir
class Foo {
void baz() { System.out.println("Foo"); }
}
class Bar extends Foo {
@Override
void baz() { System.out.println("Bar"); }
}
class Qux extends Bar {
@Override
void baz() { System.out.println("Qux"); }
}
üzerinden atlarken Qux#baz
çağırmak mümkündü . Doğrudan süper sınıftan başka bir süper yöntem uygulamasını çağırmak için açık bir çağrı tanımlamak hala mümkün olsa da, bu, 1.1'den sonraki Java sürümlerinde artık herhangi bir etkiye sahip değildir. Java 1.1'de bu davranış, yalnızca doğrudan süper sınıfın uygulamasını çağıran aynı davranışı etkinleştirecek bayrak ayarlanarak kontrol edildi .Foo#baz
Bar#baz
ACC_SUPER
Aynı sınıfta bildirilen bir yöntemin sanal olmayan çağrısını tanımlayın
Java'da bir sınıf tanımlamak mümkün değildir
class Foo {
void foo() {
bar();
}
void bar() { }
}
class Bar extends Foo {
@Override void bar() {
throw new RuntimeException();
}
}
Yukarıdaki kod daima a sonuçlanacaktır RuntimeException
zaman foo
örneğinde çağrılır Bar
. İçinde tanımlanan kendi yöntemini Foo::foo
çağırmak için yöntemi tanımlamak mümkün değildir . As olmayan bir özel örnek yöntemi, çağrı hep sanaldır. Bayt kodu ile, bir yandan kullanımı çağırma tanımlayabilir doğrudan bağlantılı bir işlem kodu yöntem çağrısı için bireyin versiyonu. Bu işlem kodu normalde süper yöntem çağrılarını uygulamak için kullanılır, ancak açıklanan davranışı uygulamak için işlem kodunu yeniden kullanabilirsiniz. bar
Foo
bar
INVOKESPECIAL
bar
Foo::foo
Foo
İnce taneli tip açıklamalar
Java'da açıklamalar @Target
, açıklamaların bildirdiklerine göre uygulanır . Bayt kodu manipülasyonunu kullanarak, açıklamaları bu kontrolden bağımsız olarak tanımlamak mümkündür. Ayrıca, örneğin, @Target
açıklama her iki öğe için geçerli olsa bile, parametreye açıklama eklemeden bir parametre türüne açıklama eklemek mümkündür .
Bir tür veya üyeleri için herhangi bir öznitelik tanımlayın
Java dili içinde, yalnızca alanlar, yöntemler veya sınıflar için ek açıklamalar tanımlamak mümkündür. JBC'de, temel olarak herhangi bir bilgiyi Java sınıflarına yerleştirebilirsiniz. Bu bilgilerden yararlanmak için artık Java sınıfı yükleme mekanizmasına güvenemezsiniz, ancak meta bilgileri kendiniz çıkarmanız gerekir.
Taşma ve örtük atama byte
, short
, char
ve boolean
değerler
İkinci ilkel türler normalde JBC'de bilinmemektedir, ancak yalnızca dizi türleri veya alan ve yöntem tanımlayıcıları için tanımlanmıştır. Bayt kodu komutları içinde, adlandırılmış türlerin tümü 32 bitlik alanı kaplar ve bu da onları int
. Resmi olarak, sadece int
, float
, long
ve double
türleri bayt kodu içinde mevcut olduğu tüm ihtiyaç JVM doğrulayıcı üstünlüğüyle açık dönüştürme.
Bir monitörü serbest bırakmamak
Bir synchronized
blok aslında iki ifadeden oluşur; biri elde etmek ve diğeri bir monitörü serbest bırakmak için. JBC'de, yayınlamadan bir tane edinebilirsiniz.
Not : HotSpot'un son uygulamalarında, bunun yerine IllegalMonitorStateException
bir yöntemin sonunda bir ya da yöntemin bir istisna tarafından sonlandırılması durumunda örtük bir sürüme yol açar .
return
Bir tür başlatıcıya birden fazla ifade ekleyin
Java'da, önemsiz bir tür başlatıcı bile, örneğin
class Foo {
static {
return;
}
}
yasa dışıdır. Bayt kodunda, tür başlatıcı diğer herhangi bir yöntem gibi ele alınır, yani dönüş ifadeleri herhangi bir yerde tanımlanabilir.
İndirgenemez döngüler oluşturun
Java derleyicisi döngüleri Java bayt kodunda goto ifadelerine dönüştürür. Bu tür ifadeler, Java derleyicisinin asla yapmadığı indirgenemez döngüler oluşturmak için kullanılabilir.
Özyinelemeli bir yakalama bloğu tanımlama
Java bayt kodunda bir blok tanımlayabilirsiniz:
try {
throw new Exception();
} catch (Exception e) {
<goto on exception>
throw Exception();
}
Benzer bir ifade, synchronized
bir monitör serbest bırakılırken herhangi bir istisnanın bu monitörü serbest bırakma talimatına döndüğü Java'da bir blok kullanıldığında dolaylı olarak oluşturulur . Normalde, böyle bir talimatta hiçbir istisna olmamalıdır, ancak böyle olsaydı (örneğin, kullanımdan kaldırıldı ThreadDeath
), monitör yine de serbest bırakılacaktır.
Herhangi bir varsayılan yöntemi çağırın
Java derleyicisi, varsayılan bir yöntemin çağrılmasına izin vermek için birkaç koşulun yerine getirilmesini gerektirir:
- Yöntem en spesifik olanı olmalıdır ( süper tipler dahil herhangi bir tip tarafından uygulanan bir alt arayüz tarafından geçersiz kılınmamalıdır ).
- Varsayılan yöntemin arabirim türü, doğrudan varsayılan yöntemi çağıran sınıf tarafından uygulanmalıdır. Bununla birlikte, arabirim arabirimi
B
genişletir A
ancak içindeki bir yöntemi geçersiz kılmazsa A
, yöntem yine de çağrılabilir.
Java bayt kodu için yalnızca ikinci koşul geçerlidir. İlki, ancak konu dışıdır.
Olmayan bir örnekte bir süper yöntemi çağırın this
Java derleyicisi, yalnızca this
. Ancak bayt kodunda, aşağıdakine benzer aynı tipteki bir örnekte süper yöntemi çağırmak da mümkündür:
class Foo {
void m(Foo f) {
f.super.toString();
}
public String toString() {
return "foo";
}
}
Sentetik üyelere erişin
Java bayt kodunda, sentetik üyelere doğrudan erişmek mümkündür. Örneğin, aşağıdaki örnekte, başka bir Bar
örneğin dış örneğine nasıl erişildiğini düşünün :
class Foo {
class Bar {
void bar(Bar bar) {
Foo foo = bar.Foo.this;
}
}
}
Bu genellikle herhangi bir sentetik alan, sınıf veya yöntem için geçerlidir.
Senkronize olmayan genel tür bilgilerini tanımlama
Java çalışma zamanı, jenerik türleri işlemezken (Java derleyicisi tür silme işlemini uyguladıktan sonra), bu bilgi yine de meta bilgi olarak derlenmiş bir sınıfa atanır ve yansıma API'si aracılığıyla erişilebilir hale getirilir.
Doğrulayıcı, bu meta veri String
kodlu değerlerin tutarlılığını kontrol etmez . Bu nedenle, silme ile eşleşmeyen genel tipler hakkında bilgi tanımlamak mümkündür. Bir sonuç olarak, aşağıdaki iddialar doğru olabilir:
Method method = ...
assertTrue(method.getParameterTypes() != method.getGenericParameterTypes());
Field field = ...
assertTrue(field.getFieldType() == String.class);
assertTrue(field.getGenericFieldType() == Integer.class);
Ayrıca imza, bir çalışma zamanı istisnası atılacak şekilde geçersiz olarak tanımlanabilir. Bu istisna, bilgiye ilk kez erişildiğinde tembel olarak değerlendirildiği için atılır. (Hatalı açıklama değerlerine benzer.)
Yalnızca belirli yöntemler için parametre meta bilgilerini ekleyin
Java derleyicisi, parameter
bayrağı etkinleştirilmiş bir sınıfı derlerken parametre adı ve değiştirici bilgilerinin gömülmesine izin verir . Ancak Java sınıfı dosya biçiminde, bu bilgiler yöntem başına depolanır ve bu, yalnızca belirli yöntemler için bu tür yöntem bilgilerinin gömülmesini mümkün kılar.
İşleri karıştırın ve JVM'nizi zorlayın
Örnek olarak, Java bayt kodunda, herhangi bir türdeki herhangi bir yöntemi çağırmak için tanımlayabilirsiniz. Doğrulayıcı, bir tipin böyle bir yöntemi bilmemesi durumunda genellikle şikayet eder. Ancak, bir dizide bilinmeyen bir yöntemi çağırırsanız, bazı JVM sürümlerinde doğrulayıcının bunu kaçıracağı ve komut çalıştırıldığında JVM'nizin biteceği bir hata buldum. Yine de bu bir özellik değil, ancak teknik olarak javac derlenmiş Java ile mümkün olmayan bir şey . Java'nın bir çeşit çift doğrulaması vardır. İlk doğrulama Java derleyicisi tarafından, ikincisi ise bir sınıf yüklendiğinde JVM tarafından uygulanır. Derleyiciyi atlayarak, doğrulayıcının doğrulamasında zayıf bir nokta bulabilirsiniz. Yine de bu, bir özellikten çok genel bir ifadedir.
Dış sınıf olmadığında bir kurucunun alıcı türüne açıklama ekleyin
Java 8'den beri, statik olmayan yöntemler ve iç sınıfların kurucuları bir alıcı türü bildirebilir ve bu türlere açıklama ekleyebilir. En üst düzey sınıfların oluşturucuları, çoğu bildirimde bulunmadıkları için alıcı türlerine açıklama ekleyemezler.
class Foo {
class Bar {
Bar(@TypeAnnotation Foo Foo.this) { }
}
Foo() { }
}
Yana Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()
ancak bir çıkmıyor AnnotatedType
temsil Foo
, tip ek açıklamalarına yer mümkündür Foo
bu açıklamalar daha sonra yansıma API tarafından okunan sınıf dosyasında doğrudan bireyin yapıcı.
Kullanılmayan / eski bayt kodu talimatlarını kullanın
Başkaları adını verdiği için ben de dahil edeceğim. Java daha önce alt yordamları JSR
ve RET
ifadeleri ile kullanıyordu. JBC, bu amaç için kendi dönüş adresi türünü bile biliyordu. Bununla birlikte, alt yordamların kullanımı statik kod analizini aşırı karmaşık hale getirdi ve bu nedenle bu talimatlar artık kullanılmamaktadır. Bunun yerine, Java derleyicisi derlediği kodu çoğaltacaktır. Bununla birlikte, bu temelde aynı mantığı yaratır, bu yüzden gerçekten farklı bir şey başarmayı düşünmüyorum. Benzer şekilde, örneğin,NOOP
Java derleyicisi tarafından da kullanılmayan bayt kodu talimatı, ancak bu da yeni bir şey elde etmenize gerçekten izin vermez. Bağlamda belirtildiği gibi, bu bahsedilen "özellik talimatları" artık yasal işlem kodları kümesinden kaldırılmıştır ve bu da onları bir özellikten daha az hale getirir.