Java dilinde bulunmayan bayt kodu özellikleri


146

Java bayt kodunda şu anda yapabileceğiniz (Java 6) Java dilinde yapamayacağınız şeyler var mı?

Her ikisinin de Turing'in tamamlandığını biliyorum, bu yüzden "önemli ölçüde daha hızlı / daha iyi veya sadece farklı bir şekilde yapabileceği gibi" "yapabilir" ifadesini okuyun.

invokedynamicJava kullanılarak üretilemeyen, belirli birinin gelecekteki bir sürüm için olması dışında ekstra bayt kodlarını düşünüyorum .


3
"Eşyaları" tanımlayın. Sonunda, Java dili ve Java baytkodu hem Turing eksiksiz ...
Michael Borgwardt

2
Asıl soru; Bayt kodunda programlamanın herhangi bir avantajı var mı, örneğin Java yerine Jasmin kullanarak?
Peter Lawrey

2
rolC ++ ile yazamayacağınız assembler'daki gibi .
Martijn Courteaux

1
(x<<n)|(x>>(32-n))Bir roltalimata göre derleyemeyen çok zayıf bir optimizasyon derleyicisidir .
Random832

Yanıtlar:


62

Bildiğim kadarıyla, Java 6 tarafından desteklenen bayt kodlarında Java kaynak kodundan da erişilemeyen önemli özellikler yok. Bunun ana nedeni, açık bir şekilde Java bayt kodunun Java dili düşünülerek tasarlanmış olmasıdır.

Modern Java derleyicileri tarafından üretilmeyen bazı özellikler vardır, ancak:

  • ACC_SUPERbayrak :

    Bu, bir sınıf üzerinde ayarlanabilen ve invokespecialbu sınıf için bayt kodunun belirli bir köşe durumunun nasıl işleneceğini belirten bir bayraktır . Tüm modern Java derleyicileri tarafından ayarlandı (burada "modern"> = Java 1.1, eğer doğru hatırlıyorsam) ve sadece eski Java derleyicileri bunun ayarlanmadığı sınıf dosyalarını üretiyordu. Bu bayrak yalnızca geriye dönük uyumluluk nedenleriyle mevcuttur. Java 7u51'den itibaren ACC_SUPER'ın güvenlik nedenleriyle tamamen yok sayıldığını unutmayın.

  • jsr/ retBaytkodlarına.

    Bu bayt kodları, alt rutinleri uygulamak için kullanıldı (çoğunlukla finallyblokları uygulamak için ). Artık Java 6'dan beri üretilmiyorlar . Kullanımdan kaldırılmalarının nedeni, statik doğrulamayı büyük bir kazanç olmaksızın çok karmaşık hale getirmeleridir (yani kullanan kod, hemen hemen her zaman çok az ek yük ile normal sıçramalarla yeniden uygulanabilir).

  • Bir sınıfta yalnızca dönüş türünde farklılık gösteren iki yönteme sahip olmak.

    Java dil belirtimi, yalnızca dönüş türlerinde farklılık gösterdiklerinde (yani aynı ad, aynı bağımsız değişken listesi, ...) aynı sınıfta iki yönteme izin vermez . Bununla birlikte, JVM belirtiminde böyle bir kısıtlama yoktur, bu nedenle bir sınıf dosyası bu tür iki yöntemi içerebilir, normal Java derleyicisini kullanarak böyle bir sınıf dosyası oluşturmanın hiçbir yolu yoktur. Bu cevapta güzel bir örnek / açıklama var .


5
Başka bir cevap daha ekleyebilirim, ama seninkini de kanonik cevap yapabiliriz. Bir yöntemin bayt kodundaki imzasının dönüş türünü içerdiğini belirtmek isteyebilirsiniz . Yani, tamamen aynı parametre türlerine sahip, ancak farklı dönüş türlerine sahip iki yönteminiz olabilir. Bu tartışmaya bakın: stackoverflow.com/questions/3110014/is-this-valid-java/…
Adam Paynter

8
Hemen hemen her karakterle sınıf, yöntem ve alan adlarına sahip olabilirsiniz. "Alanlar" ın adlarında boşluklar ve kısa çizgiler olduğu bir proje üzerinde çalıştım. : P
Peter Lawrey

3
@Peter: Dosya sistemi karakterlerinden bahsetmişken, bir sınıfı JAR dosyasının içine ave diğerini yeniden adlandıran bir obfuscator ile karşılaştım A. Eksik derslerin nerede olduğunu anlamadan önce bir Windows makinesinde fermuarını açmak yaklaşık yarım saatimi aldı . :)
Adam Paynter

3
@JoachimSauer: JVM spec sayfa 75 paraphrased: sınıf adları, Yöntemler, alanlar ve yerel değişkenler içerebilir herhangi hariç karakteri '.', ';', '[', veya '/'. Yöntem adları aynıdır, ancak '<'veya içeremezler '>'. (Dikkate değer istisnalar <init>ve <clinit>örneğin ve statik oluşturucularla.) Belirtmeyi kesinlikle takip ediyorsanız, sınıf adlarının aslında çok daha kısıtlı olduğunu, ancak kısıtlamaların uygulanmadığını belirtmeliyim.
leviathanbadger

3
@JoachimSauer: ayrıca, benim de belgelenmemiş bir eklemem: java dili "throws ex1, ex2, ..., exn", yöntem imzalarının bir parçası olarak içerir ; geçersiz kılınan yöntemlere istisna atma cümleleri ekleyemezsiniz. AMA, JVM daha az umursayamazdı. Bu nedenle final, JVM tarafından istisnasız olarak yalnızca yöntemlerin gerçekten garanti altına alınmıştır - RuntimeExceptions ve s'ler dışında Errortabii ki. Kontrol edilen istisna işleme için çok fazla: D
leviathanbadger

403

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.Unsafeyine 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 .<>

finalParametreleri veya thisreferansı yeniden atayın

finalparametreler JBC'de yoktur ve sonuç olarak yeniden atanabilir. thisReferans dahil olmak üzere herhangi bir parametre yalnızca JVM içindeki basit bir dizide depolanır ve bu, thisreferansın 0tek bir yöntem çerçevesi içinde indekste yeniden atanmasına izin verir .

finalAlanları 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() { } // bar == 0
  Foo(Void v) { // bar == 2
    bar = 1;
    bar = 2;
  }
}

İçin static finalalanlar, 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 voidgibi, 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 barbir erişimci yöntemi bar()ve tek bir Object. Ek olarak, için bir kayıt özniteliği bareklenir. 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#bazBar#bazACC_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 RuntimeExceptionzaman 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. barFoobarINVOKESPECIALbarFoo::fooFoo

İ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, @Targetaçı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, charve booleandeğ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, longve doubletü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 synchronizedblok 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 IllegalMonitorStateExceptionbir 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 .

returnBir 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, synchronizedbir 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:

  1. 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 ).
  2. 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 Bgenişletir Aancak 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(); // calls Object::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 Stringkodlu 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, parameterbayrağı 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() { } // Must not declare a receiver type
}

Yana Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()ancak bir çıkmıyor AnnotatedTypetemsil Foo, tip ek açıklamalarına yer mümkündür Foobu 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ı JSRve RETifadeleri 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,NOOPJava 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.


3
Yöntem isimleriyle ilgili olarak, yöntemleri isimle <clinit>tanımlayarak <clinit>ancak parametreleri kabul ederek veya voidgeri dönüşü olmayan bir türe sahip olarak birden fazla yönteme sahip olabilirsiniz . Ancak bu yöntemler pek kullanışlı değildir, JVM bunları görmezden gelir ve bayt kodu bunları başlatamaz. Tek kullanım, okuyucuların kafasını karıştırmak olacaktır.
Holger

2
Oracle'ın JVM'sinin, yöntem çıkışında yayınlanmamış bir monitör algıladığını IllegalMonitorStateExceptionve monitorexittalimatı atlarsanız bir attığını keşfettim . Ve başarısız olan istisnai bir yöntem çıkışı durumunda monitorexit, monitörü sessizce sıfırlar.
Holger

1
@Holger - bunu bilmiyordum, en azından önceki JVM'lerde bunun mümkün olduğunu biliyorum, JRockit'in bu tür bir uygulama için kendi işleyicisi bile var. Girişi güncelleyeceğim.
Rafael Winterhalter

1
JVM spesifikasyonu böyle bir davranışı zorunlu kılmaz. Bunu yeni keşfettim çünkü bu tür standart olmayan bayt kodunu kullanarak sarkan bir iç kilit oluşturmaya çalıştım.
Holger

3
Tamam, ilgili spesifikasyonu buldum : “ Yapılandırılmış kilitleme , bir yöntem çağrısı sırasında, belirli bir monitördeki her çıkışın o monitörde önceki bir girişle eşleştiği durumdur. Java Sanal Makinesine gönderilen tüm kodun yapılandırılmış kilitleme gerçekleştireceğine dair bir garanti olmadığından, Java Sanal Makinesi uygulamalarına izin verilir, ancak yapılandırılmış kilitlemeyi garanti eden aşağıdaki iki kuralın her ikisini de uygulamak zorunda değildir. … ”
Holger

14

Java bayt kodunda yapılabilen ancak Java kaynak kodunda yapılamayan bazı özellikler şunlardır:

  • Yöntemin onu attığını bildirmeden bir yöntemden denetlenen bir istisna atma. Kontrol edilen ve kontrol edilmeyen istisnalar, JVM tarafından değil, yalnızca Java derleyicisi tarafından kontrol edilen bir şeydir. Bu nedenle, örneğin Scala, yöntemlerden kontrol edilen istisnaları bildirmeden atabilir. Java jeneriklerinde sinsi atma adı verilen bir geçici çözüm var .

  • Joachim'in cevabında daha önce belirtildiği gibi , bir sınıfta yalnızca dönüş türünde farklı olan iki yönteme sahip olmak : Java dili belirtimi, yalnızca dönüş türlerinde farklılık gösterdiklerinde aynı sınıfta iki yönteme izin vermez (yani aynı ad, aynı bağımsız değişken listesi, ...). Bununla birlikte, JVM belirtiminde böyle bir kısıtlama yoktur, bu nedenle bir sınıf dosyası bu tür iki yöntemi içerebilir, normal Java derleyicisini kullanarak böyle bir sınıf dosyası oluşturmanın hiçbir yolu yoktur. Bu cevapta güzel bir örnek / açıklama var .


4
Orada unutmayın olan Java ilk iş yapmanın bir yolu. Bazen sinsi atış olarak adlandırılır .
Joachim Sauer

Şimdi bu sinsi! : D Paylaştığınız için teşekkürler.
Esko Luontola

Thread.stop(Throwable)Sinsi bir atış için de kullanabileceğinizi düşünüyorum . Zaten bağlanmış olanın daha hızlı olduğunu varsayıyorum.
Bart van Heukelom

2
Java bayt kodunda bir kurucu çağırmadan örnek oluşturamazsınız. Doğrulayıcı, başlatılmamış bir örneği kullanmaya çalışan herhangi bir kodu reddedecektir. Nesne serisini kaldırma uygulaması, yapıcı çağrısı yapmadan örnekler oluşturmak için yerel kod yardımcılarını kullanır.
Holger

Object sınıfını genişleten bir Foo sınıfı için, Object içinde bildirilen bir kurucu çağırarak Foo başlatamazsınız. Doğrulayıcı bunu reddeder. Java'nın ReflectionFactory'sini kullanarak böyle bir kurucu oluşturabilirsiniz, ancak bu bir bayt kodu özelliği değildir, ancak Jni tarafından gerçekleştirilmiştir. Cevabınız yanlış ve Holger doğru.
Rafael Winterhalter

8
  • GOTOkendi kontrol yapılarınızı oluşturmak için etiketlerle birlikte kullanılabilir ( for whilevb. dışında )
  • thisBir yöntem içindeki yerel değişkeni geçersiz kılabilirsiniz
  • Bunların her ikisini birleştirerek, kuyruk çağrısı optimize edilmiş bayt kodu oluşturabilirsiniz (Bunu JCompilo'da yapıyorum )

İlgili bir nokta olarak, hata ayıklama ile derlenmişse yöntemler için parametre adı alabilirsiniz ( Paranamer bunu bayt kodunu okuyarak yapar.


overrideBu yerel değişkeni nasıl yaparsınız ?
Michael

2
@Michael geçersiz kılma çok güçlü bir kelime. Bayt kodu seviyesinde, tüm yerel değişkenlere sayısal bir indeks ile erişilir ve mevcut bir değişkene yazma ile yeni bir değişkeni başlatmak (ayrık kapsam ile) arasında bir fark yoktur, her iki durumda da, sadece yerel bir değişkene yazılır. thisDeğişken indeksi sıfırdır, fakat olmasının yanı sıra ile önceden başlangıç thisbir örnek yöntemi girerken referans, sadece yerel değişkendir. Böylece, onu nasıl kullandığınıza bağlı olarak, thiskapsamı sonlandırmak veya thisdeğişkeni değiştirmek gibi davranabilen farklı bir değer yazabilirsiniz .
Holger

Anlıyorum! Yani gerçekten thisyeniden atanabilir mi? Sanırım tam olarak ne anlama geldiğini merak etmeme neden olan şey geçersiz kılma sözcüğüydü.
Michael

5

Bu belgedeki 7A bölümü , bayt kodu özelliklerinden ziyade bayt kodu tuzaklarıyla ilgili olmasına rağmen belki de ilgi çekicidir .


İlginç bir okuma, ancak bu şeylerden herhangi birini kullanmak (ab) isteyecek gibi görünmüyor .
Bart van Heukelom

4

Java dilinde bir yapıcıdaki ilk ifade, süper sınıf kurucusuna yapılan bir çağrı olmalıdır. Bayt kodu bu sınırlamaya sahip değildir, bunun yerine kural, süper sınıf yapıcısının veya aynı sınıftaki başka bir yapıcının üyelere erişmeden önce nesne için çağrılması gerektiğidir. Bu, aşağıdakiler gibi daha fazla özgürlüğe izin vermelidir:

  • Başka bir nesnenin bir örneğini oluşturun, onu yerel bir değişkende (veya yığında) saklayın ve başka bir kullanım için bu değişkendeki referansı korurken süper sınıf oluşturucusuna bir parametre olarak iletin.
  • Bir koşula göre farklı diğer kurucuları çağırın. Bu mümkün olmalıdır: Java'da farklı bir kurucu koşullu olarak nasıl çağrılır?

Bunları test etmedim, bu yüzden hatalıysam lütfen beni düzeltin.


Üst sınıf yapıcısını çağırmadan önce bir örneğin üyelerini bile ayarlayabilirsiniz. Ancak bundan önce alanları okumak veya yöntemleri çağırmak mümkün değildir.
Rafael Winterhalter

3

Düz Java kodu yerine bayt koduyla yapabileceğiniz bir şey, derleyici olmadan yüklenebilen ve çalışabilen kod üretmektir. Çoğu sistemde JDK yerine JRE bulunur ve eğer kodu dinamik olarak oluşturmak istiyorsanız, Java kodu yerine bayt kodu üretmek daha kolay olmasa da, kullanılmadan önce derlenmelidir.


6
Ama sonra sadece derleyiciyi atlıyorsunuz, derleyici kullanılarak üretilemeyen bir şey üretmiyorsunuz (eğer varsa).
Bart van Heukelom

2

I-Play iken bir bayt kodu iyileştirici yazdım (J2ME uygulamaları için kod boyutunu küçültmek için tasarlandı). Eklediğim özelliklerden biri, satır içi bayt kodunu kullanma yeteneğiydi (C ++ 'daki satır içi montaj diline benzer). Bir kütüphane yönteminin parçası olan bir işlevin boyutunu, değere iki kez ihtiyacım olduğu için DUP komutunu kullanarak küçültmeyi başardım. Ayrıca sıfır bayt komutlarım da vardı (bir karakter alan bir yöntemi çağırıyorsanız ve bir int iletmek istiyorsanız, dönüştürülmesi gerekmediğini bildiğiniz char (var) yerine int2char (var) ekledim ve kaldıracaktı Kodun boyutunu küçültmek için i2c komutu. Ben de float a = 2.3 yaptım; float b = 3.4; float c = a + b; ve bu sabit noktaya dönüştürülecek (daha hızlı ve ayrıca bazı J2ME kayan noktayı destekler).


2

Java'da, genel bir yöntemi korumalı bir yöntemle (veya erişimde başka herhangi bir azalma ile) geçersiz kılmaya çalışırsanız, bir hata alırsınız: "daha zayıf erişim ayrıcalıkları atamaya çalışma". Bunu JVM bayt kodu ile yaparsanız, doğrulayıcıda sorun yoktur ve bu yöntemleri ana sınıf aracılığıyla sanki herkese açıkmış gibi çağırabilirsiniz.

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.