Funzionalità Bytecode non disponibili nel linguaggio Java


146

Attualmente ci sono (Java 6) cose che puoi fare in bytecode Java che non puoi fare all'interno del linguaggio Java?

So che entrambi sono Turing completi, quindi leggi "può fare" come "può fare significativamente più velocemente / meglio, o semplicemente in modo diverso".

Sto pensando a bytecode extra come invokedynamic, che non possono essere generati usando Java, tranne che quello specifico è per una versione futura.


3
Definisci "cose". Alla fine, il linguaggio Java e il bytecode Java sono entrambi Turing completi ...
Michael Borgwardt,

2
È la vera domanda; c'è qualche vantaggio nella programmazione in codice byte, ad es. usando Jasmin, invece di Java?
Peter Lawrey,

2
Come rolin assembler, che non puoi scrivere in C ++.
Martijn Courteaux,

1
È un compilatore di ottimizzazione molto scarso che non può essere compilato (x<<n)|(x>>(32-n))in rolun'istruzione.
Casuale 832,

Risposte:


62

Per quanto ne so non ci sono caratteristiche principali nei bytecode supportati da Java 6 che non sono accessibili anche dal codice sorgente Java. La ragione principale di ciò è ovviamente che il bytecode Java è stato progettato pensando al linguaggio Java.

Ci sono alcune funzionalità che non sono prodotte dai moderni compilatori Java, tuttavia:

  • La ACC_SUPERbandiera :

    Questo è un flag che può essere impostato su una classe e specifica come invokespecialviene gestito un caso d'angolo specifico del bytecode per questa classe. È impostato da tutti i compilatori Java moderni (dove "modern" è> = Java 1.1, se ricordo bene) e solo i compilatori Java antichi hanno prodotto file di classe in cui questo era non impostato. Questo flag esiste solo per motivi di retrocompatibilità. Si noti che a partire da Java 7u51, ACC_SUPER viene ignorato completamente per motivi di sicurezza.

  • I jsr/ retbytecodes.

    Questi bytecode sono stati usati per implementare le routine secondarie (principalmente per implementare i finallyblocchi). Non sono più prodotti da Java 6 . Il motivo della loro deprecazione è che complicano molto la verifica statica senza grandi guadagni (cioè il codice che utilizza può quasi sempre essere implementato con salti normali con un sovraccarico minimo).

  • Avere due metodi in una classe che differiscono solo per il tipo restituito.

    La specifica del linguaggio Java non consente due metodi nella stessa classe quando differiscono solo per il loro tipo restituito (cioè stesso nome, stesso elenco di argomenti, ...). La specifica JVM, tuttavia, non ha tali restrizioni, quindi un file di classe può contenere due di questi metodi, non c'è modo di produrre un tale file di classe usando il normale compilatore Java. C'è un buon esempio / spiegazione in questa risposta .


5
Potrei aggiungere un'altra risposta, ma potremmo anche rendere la tua la risposta canonica. Si consiglia di menzionare che la firma di un metodo in bytecode include il tipo restituito . Cioè, puoi avere due metodi con esattamente gli stessi tipi di parametri, ma tipi di restituzione diversi. Vedi questa discussione: stackoverflow.com/questions/3110014/is-this-valid-java/…
Adam Paynter,

8
Puoi avere nomi di classe, metodo e campo praticamente con qualsiasi carattere. Ho lavorato a un progetto in cui i "campi" avevano spazi e trattini nei loro nomi. : P
Peter Lawrey,

3
@Peter: A proposito di caratteri del file system, mi sono imbattuto in un offuscatore che aveva rinominato una classe in ae un'altra Aall'interno del file JAR. Mi ci è voluta circa mezz'ora di decompressione su un computer Windows prima di capire dove fossero le classi mancanti. :)
Adam Paynter,

3
@JoachimSauer: parafrasato JVM spec, pagina 75: i nomi delle classi, metodi, campi, e le variabili locali può contenere qualsiasi carattere tranne '.', ';', '[', o '/'. I nomi dei metodi sono gli stessi, ma non possono contenere '<''>'. (Con le notevoli eccezioni di <init>e, <clinit>ad esempio, e costruttori statici). Devo sottolineare che se si seguono rigorosamente le specifiche, i nomi delle classi sono in realtà molto più vincolati, ma i vincoli non vengono applicati.
leviathanbadger

3
@JoachimSauer: anche una mia aggiunta non documentata: il linguaggio java include "throws ex1, ex2, ..., exn"come parte delle firme del metodo; non è possibile aggiungere clausole di lancio di eccezioni ai metodi sostituiti. MA, alla JVM non potrebbe importare di meno. Quindi solo i finalmetodi sono veramente garantiti dalla JVM per essere privi di eccezioni - a parte RuntimeExceptions e Errors, ovviamente. Questo per quanto riguarda la gestione delle eccezioni verificate: D
leviathanbadger

401

Dopo aver lavorato con il codice byte Java per un po 'di tempo e aver fatto ulteriori ricerche su questo argomento, ecco un riepilogo delle mie scoperte:

Eseguire il codice in un costruttore prima di chiamare un costruttore super o costruttore ausiliario

Nel linguaggio di programmazione Java (JPL), la prima istruzione di un costruttore deve essere l'invocazione di un super costruttore o di un altro costruttore della stessa classe. Questo non è vero per il codice byte Java (JBC). All'interno del codice byte, è assolutamente legittimo eseguire qualsiasi codice prima di un costruttore, purché:

  • Un altro costruttore compatibile viene chiamato dopo questo blocco di codice.
  • Questa chiamata non è inclusa in un'istruzione condizionale.
  • Prima di questa chiamata del costruttore, nessun campo dell'istanza costruita viene letto e nessuno dei suoi metodi viene invocato. Ciò implica l'elemento successivo.

Impostare i campi dell'istanza prima di chiamare un costruttore super o costruttore ausiliario

Come accennato in precedenza, è perfettamente legale impostare un valore di campo di un'istanza prima di chiamare un altro costruttore. Esiste persino un hack legacy che lo rende in grado di sfruttare questa "funzionalità" nelle versioni Java precedenti alla 6:

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

In questo modo, è possibile impostare un campo prima che venga invocato il super costruttore, che tuttavia non è più possibile. In JBC, questo comportamento può ancora essere implementato.

Diramazione di una chiamata del super costruttore

In Java, non è possibile definire una chiamata del costruttore come

class Foo {
  Foo() { }
  Foo(Void v) { }
}

class Bar() {
  if(System.currentTimeMillis() % 2 == 0) {
    super();
  } else {
    super(null);
  }
}

Fino a Java 7u23, il verificatore della VM HotSpot non ha comunque perso questo controllo, motivo per cui è stato possibile. Questo è stato utilizzato da diversi strumenti di generazione del codice come una sorta di hack, ma non è più legale implementare una classe come questa.

Quest'ultimo era semplicemente un bug in questa versione del compilatore. Nelle versioni più recenti del compilatore, questo è di nuovo possibile.

Definire una classe senza alcun costruttore

Il compilatore Java implementerà sempre almeno un costruttore per qualsiasi classe. Nel codice byte Java, questo non è richiesto. Ciò consente la creazione di classi che non possono essere costruite anche quando si usa la riflessione. Tuttavia, l'utilizzo sun.misc.Unsafeconsente ancora di creare tali istanze.

Definire metodi con firma identica ma con tipo di ritorno diverso

Nella JPL, un metodo è identificato come unico dal nome e dai tipi di parametri non elaborati. In JBC, viene considerato anche il tipo di ritorno non elaborato.

Definire i campi che non differiscono per nome ma solo per tipo

Un file di classe può contenere diversi campi con lo stesso nome purché dichiarino un tipo di campo diverso. La JVM si riferisce sempre a un campo come una tupla di nome e tipo.

Lancia eccezioni controllate non dichiarate senza catturarle

Il runtime Java e il codice byte Java non sono a conoscenza del concetto di eccezioni verificate. È solo il compilatore Java che verifica che le eccezioni verificate vengano sempre rilevate o dichiarate se vengono generate.

Usa l'invocazione del metodo dinamico al di fuori delle espressioni lambda

La cosiddetta chiamata del metodo dinamico può essere utilizzata per qualsiasi cosa, non solo per le espressioni lambda di Java. L'uso di questa funzione consente ad esempio di cambiare la logica di esecuzione in fase di esecuzione. Molti linguaggi di programmazione dinamici che si riducono a JBC hanno migliorato le loro prestazioni usando queste istruzioni. Nel codice byte Java, è anche possibile emulare espressioni lambda in Java 7 in cui il compilatore non consentiva ancora l'uso della chiamata al metodo dinamico mentre JVM aveva già compreso l'istruzione.

Utilizzare identificatori che normalmente non sono considerati legali

Hai mai immaginato di usare spazi e un'interruzione di riga nel nome del tuo metodo? Crea il tuo JBC e buona fortuna per la revisione del codice. Gli unici caratteri non validi per gli identificatori sono ., ;, [e /. Inoltre, i metodi che non sono nominati <init>o <clinit>non possono contenere <e >.

Riassegna i finalparametri o il thisriferimento

finali parametri non esistono in JBC e di conseguenza possono essere riassegnati. Qualsiasi parametro, incluso il thisriferimento, è memorizzato in un semplice array all'interno della JVM, ciò che consente di riassegnare il thisriferimento all'indice 0all'interno di un singolo frame di metodo.

Riassegna i finalcampi

Finché un campo finale viene assegnato all'interno di un costruttore, è legale riassegnare questo valore o addirittura non assegnarlo affatto. Pertanto, i seguenti due costruttori sono legali:

class Foo {
  final int bar;
  Foo() { } // bar == 0
  Foo(Void v) { // bar == 2
    bar = 1;
    bar = 2;
  }
}

Per i static finalcampi, è anche consentito riassegnare i campi all'esterno dell'inizializzatore di classi.

Tratta i costruttori e l'inizializzatore di classe come se fossero metodi

Questa è più una caratteristica concettuale, ma i costruttori non sono trattati in modo diverso all'interno di JBC rispetto ai metodi normali. È solo il verificatore della JVM che assicura che i costruttori chiamino un altro costruttore legale. Oltre a ciò, è semplicemente una convenzione di denominazione Java che i costruttori devono essere chiamati <init>e che viene chiamato l'inizializzatore di classe <clinit>. Oltre a questa differenza, la rappresentazione di metodi e costruttori è identica. Come ha sottolineato Holger in un commento, puoi persino definire costruttori con tipi di ritorno diversi da voido un inizializzatore di classe con argomenti, anche se non è possibile chiamare questi metodi.

Crea record asimmetrici * .

Quando si crea un record

record Foo(Object bar) { }

javac genererà un file di classe con un singolo campo denominato bar, un metodo di accesso denominato bar()e un costruttore che ne prende uno singolo Object. Inoltre, barviene aggiunto un attributo record per . Generando manualmente un record, è possibile creare una forma del costruttore diversa, saltare il campo e implementare l'accessor in modo diverso. Allo stesso tempo, è ancora possibile far credere all'API di riflessione che la classe rappresenti un record effettivo.

Chiama qualsiasi metodo super (fino a Java 1.1)

Tuttavia, questo è possibile solo per le versioni Java 1 e 1.1. In JBC, i metodi vengono sempre inviati su un tipo di destinazione esplicito. Questo significa che per

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

è stato possibile implementare Qux#bazper invocare Foo#bazsaltando sopra Bar#baz. Mentre è ancora possibile definire un richiamo esplicito per chiamare un'altra implementazione di super metodo rispetto a quella della superclasse diretta, ciò non ha più alcun effetto nelle versioni Java dopo 1.1. In Java 1.1, questo comportamento era controllato impostando il ACC_SUPERflag che abilitava lo stesso comportamento che chiama solo l'implementazione diretta della superclasse.

Definire una chiamata non virtuale di un metodo dichiarato nella stessa classe

In Java, non è possibile definire una classe

class Foo {
  void foo() {
    bar();
  }
  void bar() { }
}

class Bar extends Foo {
  @Override void bar() {
    throw new RuntimeException();
  }
}

Il codice sopra riportato si tradurrà sempre in un RuntimeExceptionquando fooviene invocato su un'istanza di Bar. Non è possibile definire il Foo::foometodo per invocare il proprio bar metodo in cui è definito Foo. Come barè un metodo di istanza non privata, la chiamata è sempre virtuale. Con il codice byte, si può tuttavia definire l'invocazione per utilizzare il INVOKESPECIALcodice operativo che collega direttamente la barchiamata del metodo Foo::fooalla Fooversione di. Questo codice operativo viene normalmente utilizzato per implementare invocazioni di super metodi ma è possibile riutilizzare il codice operativo per implementare il comportamento descritto.

Annotazioni di tipo a grana fine

In Java, le annotazioni vengono applicate in base al loro @Targetdichiarato dalle annotazioni. Utilizzando la manipolazione del codice byte, è possibile definire le annotazioni indipendentemente da questo controllo. Inoltre, è ad esempio possibile annotare un tipo di parametro senza annotare il parametro anche se l' @Targetannotazione si applica a entrambi gli elementi.

Definire qualsiasi attributo per un tipo o i suoi membri

All'interno del linguaggio Java, è possibile definire solo annotazioni per campi, metodi o classi. In JBC, puoi fondamentalmente incorporare qualsiasi informazione nelle classi Java. Per utilizzare queste informazioni, tuttavia, non è più possibile fare affidamento sul meccanismo di caricamento della classe Java, ma è necessario estrarre le meta informazioni da soli.

Overflow e implicitamente assegnare byte, short, chare booleanvalori

Questi ultimi tipi primitivi non sono normalmente noti in JBC ma sono definiti solo per tipi di array o per descrittori di campi e metodi. All'interno delle istruzioni del codice byte, tutti i tipi nominati occupano lo spazio a 32 bit che consente di rappresentarli come int. Ufficialmente, solo i int, float, longed doubleesistono tipi del codice byte, che tutti hanno bisogno di conversione esplicita la regola del verificatore della JVM.

Non rilasciare un monitor

Un synchronizedblocco è in realtà composto da due istruzioni, una da acquisire e una da rilasciare un monitor. In JBC, puoi acquisirne uno senza rilasciarlo.

Nota : nelle recenti implementazioni di HotSpot, ciò porta invece IllegalMonitorStateExceptionalla fine di un metodo o a una versione implicita se il metodo viene terminato da un'eccezione stessa.

Aggiungi più di returnun'istruzione a un inizializzatore di tipo

In Java, anche un inizializzatore di tipo banale come

class Foo {
  static {
    return;
  }
}

è illegale. Nel codice byte, l'inizializzatore del tipo viene trattato come qualsiasi altro metodo, vale a dire le istruzioni di ritorno possono essere definite ovunque.

Crea loop irriducibili

Il compilatore Java converte i loop in istruzioni goto nel codice byte Java. Tali istruzioni possono essere utilizzate per creare loop irriducibili, cosa che il compilatore Java non esegue mai.

Definire un blocco catch ricorsivo

Nel codice byte Java, è possibile definire un blocco:

try {
  throw new Exception();
} catch (Exception e) {
  <goto on exception>
  throw Exception();
}

Un'istruzione simile viene creata implicitamente quando si utilizza un synchronizedblocco in Java in cui qualsiasi eccezione durante il rilascio di un monitor ritorna alle istruzioni per il rilascio di questo monitor. Normalmente, non dovrebbe verificarsi alcuna eccezione su tale istruzione, ma se ciò accadesse (ad es. Il deprecato ThreadDeath), il monitor verrebbe comunque rilasciato.

Chiama qualsiasi metodo predefinito

Il compilatore Java richiede che siano soddisfatte diverse condizioni per consentire l'invocazione di un metodo predefinito:

  1. Il metodo deve essere il più specifico (non deve essere ignorato da un'interfaccia secondaria implementata da alcun tipo, inclusi i super tipi).
  2. Il tipo di interfaccia del metodo predefinito deve essere implementato direttamente dalla classe che chiama il metodo predefinito. Tuttavia, se l'interfaccia Bestende l'interfaccia Ama non sovrascrive un metodo A, è comunque possibile invocare il metodo.

Per il codice byte Java, conta solo la seconda condizione. Il primo è tuttavia irrilevante.

Richiamare un metodo super su un'istanza che non lo è this

Il compilatore Java consente solo di invocare un metodo super (o interfaccia predefinito) su istanze di this. Nel codice byte, tuttavia, è anche possibile richiamare il metodo super su un'istanza dello stesso tipo simile al seguente:

class Foo {
  void m(Foo f) {
    f.super.toString(); // calls Object::toString
  }
  public String toString() {
    return "foo";
  }
}

Accedi ai membri sintetici

Nel codice byte Java, è possibile accedere direttamente ai membri sintetici. Ad esempio, considera come nel seguente esempio Barsi accede all'istanza esterna di un'altra istanza:

class Foo {
  class Bar { 
    void bar(Bar bar) {
      Foo foo = bar.Foo.this;
    }
  }
}

Questo è generalmente vero per qualsiasi campo, classe o metodo sintetico.

Definire informazioni di tipo generico non sincronizzate

Mentre il runtime Java non elabora tipi generici (dopo che il compilatore Java applica la cancellazione dei tipi), queste informazioni sono ancora associate a una classe compilata come meta informazioni e rese accessibili tramite l'API di reflection.

Il verificatore non controlla la coerenza di questi Stringvalori codificati con metadati . È quindi possibile definire informazioni su tipi generici che non corrispondono alla cancellazione. Come concezione, le seguenti affermazioni possono essere vere:

Method method = ...
assertTrue(method.getParameterTypes() != method.getGenericParameterTypes());

Field field = ...
assertTrue(field.getFieldType() == String.class);
assertTrue(field.getGenericFieldType() == Integer.class);

Inoltre, la firma può essere definita non valida in modo da generare un'eccezione di runtime. Questa eccezione viene generata quando si accede alle informazioni per la prima volta quando viene valutata pigramente. (Simile ai valori di annotazione con un errore.)

Aggiungi le informazioni meta dei parametri solo per determinati metodi

Il compilatore Java consente di incorporare il nome del parametro e le informazioni sul modificatore durante la compilazione di una classe con il parameterflag abilitato. Nel formato di file di classe Java, queste informazioni vengono comunque memorizzate per metodo, il che rende possibile incorporare tali informazioni sul metodo solo per determinati metodi.

Disordinare le cose e bloccare la tua JVM

Ad esempio, nel codice byte Java, è possibile definire di invocare qualsiasi metodo su qualsiasi tipo. Di solito, il verificatore si lamenterà se un tipo non è a conoscenza di tale metodo. Tuttavia, se invochi un metodo sconosciuto su un array, ho trovato un bug in alcune versioni di JVM in cui il verificatore mancherà questo e la tua JVM finirà una volta invocata l'istruzione. Questa non è una caratteristica però, ma è tecnicamente qualcosa che non è possibile con Java compilato javac . Java ha una sorta di doppia convalida. La prima convalida viene applicata dal compilatore Java, la seconda dalla JVM quando viene caricata una classe. Saltando il compilatore, potresti trovare un punto debole nella convalida del verificatore. Questa è piuttosto un'affermazione generale piuttosto che una caratteristica.

Annota il tipo di ricevitore di un costruttore quando non esiste una classe esterna

A partire da Java 8, metodi e costruttori non statici di classi interne possono dichiarare un tipo di ricevitore e annotare questi tipi. I costruttori di classi di livello superiore non possono annotare il loro tipo di ricevitore in quanto la maggior parte non ne dichiara uno.

class Foo {
  class Bar {
    Bar(@TypeAnnotation Foo Foo.this) { }
  }
  Foo() { } // Must not declare a receiver type
}

Poiché Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()tuttavia restituisce un AnnotatedTyperappresentante Foo, è possibile includere le annotazioni dei tipi per Fooil costruttore direttamente nel file di classe in cui tali annotazioni vengono successivamente lette dall'API di reflection.

Utilizzare le istruzioni del codice byte non utilizzate / legacy

Dal momento che altri lo hanno chiamato, lo includerò anche io. In passato Java utilizzava subroutine dalle dichiarazioni JSRe RET. JBC conosceva anche il proprio tipo di indirizzo di ritorno per questo scopo. Tuttavia, l'uso di subroutine ha complicato notevolmente l'analisi del codice statico, motivo per cui queste istruzioni non vengono più utilizzate. Al contrario, il compilatore Java duplicherà il codice che compila. Tuttavia, questo in pratica crea una logica identica, motivo per cui non lo considero davvero per ottenere qualcosa di diverso. Allo stesso modo, potresti ad esempio aggiungere ilNOOPistruzione di codice byte che non viene utilizzata neanche dal compilatore Java, ma ciò non consentirebbe in realtà di ottenere qualcosa di nuovo. Come sottolineato nel contesto, queste "istruzioni sulle funzionalità" menzionate vengono ora rimosse dall'insieme di codici operativi legali che le rendono ancora meno di una funzione.


3
Per quanto riguarda i nomi dei metodi, puoi avere più di un <clinit>metodo definendo i metodi con il nome <clinit>ma accettando i parametri o avendo un voidtipo di non ritorno. Ma questi metodi non sono molto utili, la JVM li ignorerà e il codice byte non può invocarli. L'unico uso sarebbe confondere i lettori.
Holger,

2
Ho appena scoperto che la JVM di Oracle rileva un monitor inedito all'uscita dal metodo e genera un errore IllegalMonitorStateExceptionse si omette l' monitorexitistruzione. E in caso di uscita di un metodo eccezionale che non è riuscita a fare un monitorexit, reimposta il monitor in silenzio.
Holger,

1
@Holger - non lo sapevo, so che questo era possibile almeno nelle precedenti JVM, JRockit ha persino il suo gestore per questo tipo di implementazione. Aggiornerò la voce.
Rafael Winterhalter,

1
Bene, la specifica JVM non impone tale comportamento. L'ho appena scoperto perché ho cercato di creare un blocco intrinseco penzolante utilizzando tale codice byte non standard.
Holger,

3
Ok, ho trovato la specifica pertinente : “ Il blocco strutturato è la situazione in cui, durante una chiamata di metodo, ogni uscita su un dato monitor corrisponde a una voce precedente su quel monitor. Poiché non vi è alcuna garanzia che tutto il codice inviato alla Java Virtual Machine eseguirà il blocco strutturato, le implementazioni della Java Virtual Machine sono consentite ma non sono necessarie per applicare entrambe le seguenti due regole che garantiscono il blocco strutturato. ... "
Holger,

14

Ecco alcune funzionalità che possono essere eseguite nel bytecode Java ma non nel codice sorgente Java:

  • Lanciare un'eccezione controllata da un metodo senza dichiarare che il metodo la lancia. Le eccezioni controllate e non selezionate sono qualcosa che viene controllato solo dal compilatore Java, non dalla JVM. Per questo motivo, ad esempio, Scala può generare eccezioni verificate dai metodi senza dichiararle. Sebbene con i generici Java ci sia una soluzione alternativa chiamata tiro subdolo .

  • Avere due metodi in una classe che differiscono solo per il tipo restituito, come già accennato nella risposta di Joachim : La specifica del linguaggio Java non consente due metodi nella stessa classe quando differiscono solo nel loro tipo restituito (cioè stesso nome, stesso elenco di argomenti, ...). La specifica JVM, tuttavia, non ha tali restrizioni, quindi un file di classe può contenere due di questi metodi, non c'è modo di produrre un tale file di classe usando il normale compilatore Java. C'è un buon esempio / spiegazione in questa risposta .


4
Si noti che non è un modo per fare la prima cosa in Java. A volte viene chiamato un tiro subdolo .
Joachim Sauer,

Questo è subdolo! : D Grazie per averlo condiviso.
Esko Luontola,

Penso che puoi anche usare Thread.stop(Throwable)per un tiro subdolo. Presumo che quello già collegato sia più veloce però.
Bart van Heukelom,

2
Non è possibile creare un'istanza senza chiamare un costruttore in bytecode Java. Il verificatore rifiuterà qualsiasi codice che tenti di utilizzare un'istanza non inizializzata. L'implementazione della deserializzazione degli oggetti utilizza helper di codice nativo per la creazione di istanze senza chiamata del costruttore.
Holger,

Per un oggetto con estensione Foo di classe, non è possibile creare un'istanza di Foo chiamando un costruttore dichiarato in Object. Il verificatore lo rifiuterebbe. È possibile creare un costruttore di questo tipo utilizzando ReflectionFactory di Java, ma difficilmente si tratta di una funzione di codice byte ma realizzata da Jni. La tua risposta è sbagliata e Holger è corretta.
Rafael Winterhalter,

8
  • GOTOpuò essere utilizzato con le etichette per creare le proprie strutture di controllo (diverse da for whileecc.)
  • È possibile sovrascrivere la thisvariabile locale all'interno di un metodo
  • Combinando entrambi questi è possibile creare creare bytecode ottimizzato per la chiamata in coda (lo faccio in JCompilo )

Come punto correlato puoi ottenere il nome del parametro per i metodi se compilato con il debug ( Paranamer lo fa leggendo il bytecode


Come si fa overridequesta variabile locale?
Michael,

2
@Michael override è una parola troppo forte. A livello di bytecode, si accede a tutte le variabili locali mediante un indice numerico e non vi è alcuna differenza tra la scrittura su una variabile esistente o l'inizializzazione di una nuova variabile (con un ambito disgiunto), in entrambi i casi, è solo una scrittura su una variabile locale. La thisvariabile ha indice zero, ma oltre a essere pre-inizializzata con il thisriferimento quando si immette un metodo di istanza, è solo una variabile locale. Quindi puoi scrivere un valore diverso su di esso, che può agire come terminare thisl'ambito o cambiare la thisvariabile, a seconda di come lo usi.
Holger,

Vedo! Quindi è davvero che thispuò essere riassegnato? Penso che sia stata solo la parola override a farmi chiedermi cosa significasse esattamente.
Michael,

5

Forse la sezione 7A di questo documento è interessante, sebbene si tratti di insidie di bytecode piuttosto che di caratteristiche di bytecode .


Lettura interessante, ma non sembra che si potrebbe desiderare di (ab) uso nessuna di queste cose.
Bart van Heukelom il

4

Nel linguaggio Java la prima istruzione in un costruttore deve essere una chiamata al costruttore di superclasse. Il bytecode non ha questa limitazione, invece la regola è che il costruttore della superclasse o un altro costruttore nella stessa classe deve essere chiamato per l'oggetto prima di accedere ai membri. Ciò dovrebbe consentire una maggiore libertà come:

  • Creare un'istanza di un altro oggetto, archiviarlo in una variabile locale (o stack) e passarlo come parametro al costruttore della superclasse mantenendo comunque il riferimento in quella variabile per altri usi.
  • Chiama diversi altri costruttori in base a una condizione. Ciò dovrebbe essere possibile: come chiamare condizionalmente un costruttore diverso in Java?

Non li ho testati, quindi per favore correggimi se sbaglio.


È anche possibile impostare membri di un'istanza prima di chiamare il suo costruttore superclasse. La lettura di campi o metodi di chiamata non è tuttavia possibile prima di quello.
Rafael Winterhalter,

3

Qualcosa che puoi fare con il codice byte, piuttosto che con il semplice codice Java, è generare codice che può essere caricato ed eseguito senza un compilatore. Molti sistemi hanno JRE anziché JDK e se si desidera generare codice in modo dinamico, potrebbe essere meglio, se non più semplice, generare il codice byte anziché il codice Java prima di poterlo utilizzare.


6
Ma poi stai semplicemente saltando il compilatore, non producendo qualcosa che non potrebbe essere prodotto usando il compilatore (se fosse disponibile).
Bart van Heukelom il

2

Ho scritto un ottimizzatore bytecode quando ero un I-Play, (è stato progettato per ridurre la dimensione del codice per le applicazioni J2ME). Una caratteristica che ho aggiunto è stata la possibilità di utilizzare il bytecode inline (simile al linguaggio assembly inline in C ++). Sono riuscito a ridurre le dimensioni di una funzione che faceva parte di un metodo di libreria utilizzando l'istruzione DUP, poiché ho bisogno del valore due volte. Ho anche avuto istruzioni zero byte (se stai chiamando un metodo che richiede un carattere e vuoi passare un int, che sai che non ha bisogno di essere lanciato ho aggiunto int2char (var) per sostituire char (var) e rimuoverà l'istruzione i2c per ridurre le dimensioni del codice. Ho anche fatto in modo che float a = 2.3; float b = 3.4; float c = a + b; e che sarebbe stato convertito in punto fisso (più veloce, e anche alcuni J2ME non lo facevano supporto in virgola mobile).


2

In Java, se si tenta di sostituire un metodo pubblico con un metodo protetto (o qualsiasi altra riduzione dell'accesso), viene visualizzato un errore: "tentativo di assegnare privilegi di accesso più deboli". Se lo fai con il bytecode JVM, il verificatore va bene con esso e puoi chiamare questi metodi tramite la classe genitore come se fossero pubblici.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.