Y a-t-il actuellement (Java 6) des choses que vous pouvez faire dans le bytecode Java que vous ne pouvez pas faire à partir du langage Java?
Je sais que les deux sont Turing complets, alors lisez "peut faire" comme "peut faire beaucoup plus vite / mieux, ou simplement d'une manière différente".
Je pense à des bytecodes supplémentaires comme invokedynamic
, qui ne peuvent pas être générés à l'aide de Java, sauf que celui-ci est spécifique pour une version future.
rol
dans l'assembleur, que vous ne pouvez pas écrire en C ++.(x<<n)|(x>>(32-n))
enrol
instruction.Réponses:
Autant que je sache, il n'y a pas de fonctionnalités majeures dans les bytecodes pris en charge par Java 6 qui ne sont pas également accessibles à partir du code source Java. La raison principale en est évidemment que le bytecode Java a été conçu avec le langage Java à l'esprit.
Cependant, certaines fonctionnalités ne sont pas produites par les compilateurs Java modernes:
Le
ACC_SUPER
drapeau :Il s'agit d'un indicateur qui peut être défini sur une classe et spécifie comment une casse d'angle spécifique du
invokespecial
bytecode est gérée pour cette classe. Il est défini par tous les compilateurs Java modernes (où "moderne" est> = Java 1.1, si je me souviens bien) et seuls les anciens compilateurs Java produisaient des fichiers de classe où cela n'était pas défini. Cet indicateur n'existe que pour des raisons de compatibilité descendante. Notez qu'à partir de Java 7u51, ACC_SUPER est complètement ignoré pour des raisons de sécurité.Les
jsr
/ret
bytecodes.Ces bytecodes ont été utilisés pour implémenter des sous-routines (principalement pour implémenter des
finally
blocs). Ils ne sont plus produits depuis Java 6 . La raison de leur dépréciation est qu'ils compliquent beaucoup la vérification statique sans grand gain (c'est-à-dire que le code qui utilise peut presque toujours être réimplémenté avec des sauts normaux avec très peu de surcharge).Avoir deux méthodes dans une classe qui ne diffèrent que par le type de retour.
La spécification du langage Java n'autorise pas deux méthodes dans la même classe lorsqu'elles ne diffèrent que par leur type de retour (c'est-à-dire même nom, même liste d'arguments, ...). Cependant, la spécification JVM n'a pas une telle restriction, donc un fichier de classe peut contenir deux de ces méthodes, il n'y a tout simplement aucun moyen de produire un tel fichier de classe en utilisant le compilateur Java normal. Il y a un bel exemple / explication dans cette réponse .
la source
a
et une autreA
dans le fichier JAR. Il m'a fallu environ une demi-heure de décompression sur une machine Windows avant de réaliser où se trouvaient les classes manquantes. :)'.'
,';'
,'['
ou'/'
. Les noms de méthode sont identiques, mais ils ne peuvent pas non plus contenir'<'
ni'>'
. (Avec les exceptions notables des constructeurs statiques<init>
et<clinit>
par exemple.) Je dois souligner que si vous suivez strictement la spécification, les noms de classe sont en fait beaucoup plus contraints, mais les contraintes ne sont pas appliquées."throws ex1, ex2, ..., exn"
dans le cadre des signatures de méthode; vous ne pouvez pas ajouter de clauses de levée d'exceptions aux méthodes remplacées. MAIS, la JVM s'en fiche. Ainsi, seules lesfinal
méthodes sont vraiment garanties par la machine virtuelle Java comme étant sans exception - à partRuntimeException
s etError
s, bien sûr. Voilà pour la gestion des exceptions vérifiée: DAprès avoir travaillé avec le code octet Java pendant un certain temps et effectué des recherches supplémentaires à ce sujet, voici un résumé de mes résultats:
Exécuter du code dans un constructeur avant d'appeler un super constructeur ou un constructeur auxiliaire
Dans le langage de programmation Java (JPL), la première instruction d'un constructeur doit être une invocation d'un super constructeur ou d'un autre constructeur de la même classe. Ce n'est pas le cas pour le code d'octet Java (JBC). Dans le code d'octet, il est absolument légitime d'exécuter n'importe quel code avant un constructeur, à condition que:
Définissez les champs d'instance avant d'appeler un super constructeur ou un constructeur auxiliaire
Comme mentionné précédemment, il est parfaitement légal de définir une valeur de champ d'une instance avant d'appeler un autre constructeur. Il existe même un hack hérité qui permet d'exploiter cette "fonctionnalité" dans les versions Java antérieures à 6:
De cette façon, un champ pourrait être défini avant que le super constructeur ne soit appelé, ce qui n'est cependant plus possible. Dans JBC, ce comportement peut toujours être implémenté.
Branche un appel de super constructeur
En Java, il n'est pas possible de définir un appel constructeur comme
Jusqu'à Java 7u23, le vérificateur de HotSpot VM a cependant manqué ce contrôle, c'est pourquoi il était possible. Cela a été utilisé par plusieurs outils de génération de code comme une sorte de hack, mais il n'est plus légal d'implémenter une classe comme celle-ci.Ce dernier n'était qu'un bogue dans cette version du compilateur. Dans les versions plus récentes du compilateur, c'est encore possible.
Définir une classe sans aucun constructeur
Le compilateur Java implémentera toujours au moins un constructeur pour n'importe quelle classe. Dans le code d'octet Java, cela n'est pas obligatoire. Cela permet la création de classes qui ne peuvent pas être construites même en utilisant la réflexion. Cependant, l'utilisation
sun.misc.Unsafe
permet toujours de créer de telles instances.Définir des méthodes avec une signature identique mais avec un type de retour différent
Dans le JPL, une méthode est identifiée comme unique par son nom et ses types de paramètres bruts. Dans JBC, le type de retour brut est également pris en compte.
Définissez des champs qui ne diffèrent pas par leur nom mais uniquement par leur type
Un fichier de classe peut contenir plusieurs champs du même nom tant qu'ils déclarent un type de champ différent. La machine virtuelle Java fait toujours référence à un champ comme un tuple de nom et de type.
Lancer des exceptions vérifiées non déclarées sans les attraper
Le runtime Java et le code d'octet Java ne connaissent pas le concept d'exceptions vérifiées. Seul le compilateur Java vérifie que les exceptions vérifiées sont toujours soit interceptées, soit déclarées si elles sont levées.
Utiliser l'appel de méthode dynamique en dehors des expressions lambda
L'appel de méthode dite dynamique peut être utilisé pour n'importe quoi, pas seulement pour les expressions lambda de Java. L'utilisation de cette fonctionnalité permet par exemple de désactiver la logique d'exécution lors de l'exécution. De nombreux langages de programmation dynamiques qui se résument à JBC ont amélioré leurs performances en utilisant cette instruction. Dans le code d'octet Java, vous pouvez également émuler des expressions lambda dans Java 7 où le compilateur n'a pas encore autorisé l'utilisation de l'invocation de méthode dynamique alors que la JVM comprenait déjà l'instruction.
Utilisez des identifiants qui ne sont normalement pas considérés comme légaux
Avez-vous déjà eu envie d'utiliser des espaces et un saut de ligne dans le nom de votre méthode? Créez votre propre JBC et bonne chance pour la révision du code. Les seuls caractères illégaux pour les identifiants sont
.
,;
,[
et/
. En outre, les méthodes qui ne sont pas nommées<init>
ou<clinit>
ne peuvent pas contenir<
et>
.Réaffecter les
final
paramètres ou lathis
référencefinal
les paramètres n'existent pas dans JBC et peuvent par conséquent être réaffectés. Tout paramètre, y compris lathis
référence, n'est stocké que dans un simple tableau au sein de la JVM, ce qui permet de réaffecter lathis
référence à l'index0
dans une seule trame de méthode.Réaffecter les
final
champsTant qu'un champ final est assigné dans un constructeur, il est légal de réaffecter cette valeur ou même de ne pas attribuer de valeur du tout. Par conséquent, les deux constructeurs suivants sont légaux:
Pour les
static final
champs, il est même permis de réaffecter les champs en dehors de l'initialiseur de classe.Traitez les constructeurs et l'initialiseur de classe comme s'il s'agissait de méthodes
C'est plus une fonctionnalité conceptuelle, mais les constructeurs ne sont pas traités différemment dans JBC que les méthodes normales. Seul le vérificateur de la JVM garantit que les constructeurs appellent un autre constructeur légal. En dehors de cela, c'est simplement une convention de dénomination Java que les constructeurs doivent être appelés
<init>
et que l'initialiseur de classe est appelé<clinit>
. Outre cette différence, la représentation des méthodes et des constructeurs est identique. Comme Holger l'a souligné dans un commentaire, vous pouvez même définir des constructeurs avec des types de retour autres quevoid
ou un initialiseur de classe avec des arguments, même s'il n'est pas possible d'appeler ces méthodes.Créez des enregistrements asymétriques * .
Lors de la création d'un enregistrement
javac générera un fichier de classe avec un seul champ nommé
bar
, une méthode d'accesseur nomméebar()
et un constructeur en prenant un seulObject
. En outre, un attribut d'enregistrement pourbar
est ajouté. En générant manuellement un enregistrement, il est possible de créer, une forme de constructeur différente, de sauter le champ et d'implémenter l'accesseur différemment. Dans le même temps, il est toujours possible de faire croire à l'API de réflexion que la classe représente un enregistrement réel.Appelez n'importe quelle super méthode (jusqu'à Java 1.1)
Cependant, cela n'est possible que pour les versions Java 1 et 1.1. Dans JBC, les méthodes sont toujours distribuées sur un type de cible explicite. Cela signifie que pour
il était possible de mettre en œuvre
Qux#baz
pour invoquerFoo#baz
en sautantBar#baz
. S'il est toujours possible de définir un appel explicite pour appeler une autre implémentation de super méthode que celle de la super classe directe, cela n'a plus d'effet dans les versions Java après 1.1. Dans Java 1.1, ce comportement était contrôlé en définissant l'ACC_SUPER
indicateur qui activerait le même comportement qui n'appelle que l'implémentation de la super classe directe.Définir un appel non virtuel d'une méthode déclarée dans la même classe
En Java, il n'est pas possible de définir une classe
Le code ci-dessus entraînera toujours un appel
RuntimeException
quandfoo
sur une instance deBar
. Il n'est pas possible de définir laFoo::foo
méthode pour invoquer sa proprebar
méthode qui est définie dansFoo
. Commebar
c'est une méthode d'instance non privée, l'appel est toujours virtuel. Avec le byte code, on peut cependant définir l'invocation pour utiliser l'INVOKESPECIAL
opcode qui relie directement l'bar
appel de méthodeFoo::foo
àFoo
la version de. Cet opcode est normalement utilisé pour implémenter des invocations de super méthode, mais vous pouvez réutiliser l'opcode pour implémenter le comportement décrit.Annotations de type à grain fin
En Java, les annotations sont appliquées en fonction de ce
@Target
que les annotations déclarent. En utilisant la manipulation de code d'octet, il est possible de définir des annotations indépendamment de ce contrôle. Aussi, il est par exemple possible d'annoter un type de paramètre sans annoter le paramètre même si l'@Target
annotation s'applique aux deux éléments.Définir n'importe quel attribut pour un type ou ses membres
Dans le langage Java, il est uniquement possible de définir des annotations pour les champs, méthodes ou classes. Dans JBC, vous pouvez essentiellement intégrer n'importe quelle information dans les classes Java. Pour utiliser ces informations, vous ne pouvez cependant plus vous fier au mécanisme de chargement de classe Java mais vous devez extraire les méta-informations vous-même.
Débordement et implicitement Assigner
byte
,short
,char
et lesboolean
valeursCes derniers types primitifs ne sont normalement pas connus dans JBC mais ne sont définis que pour les types de tableaux ou pour les descripteurs de champ et de méthode. Dans les instructions de code octet, tous les types nommés prennent l'espace de 32 bits ce qui permet de les représenter comme
int
. Officiellement, seuls lesint
,float
,long
etdouble
types existent dans le code octet qui tous besoin de conversion explicite par la règle du vérificateur de la machine virtuelle Java.Ne pas libérer un moniteur
Un
synchronized
bloc est en fait composé de deux instructions, une pour acquérir et une pour libérer un moniteur. Dans JBC, vous pouvez en acquérir un sans le relâcher.Remarque : dans les implémentations récentes de HotSpot, cela conduit à la place à un
IllegalMonitorStateException
à la fin d'une méthode ou à une version implicite si la méthode se termine par une exception elle-même.Ajouter plus d'une
return
instruction à un initialiseur de typeEn Java, même un initialiseur de type trivial tel que
est illégal. Dans le code octet, l'initialiseur de type est traité comme n'importe quelle autre méthode, c'est-à-dire que les instructions de retour peuvent être définies n'importe où.
Créer des boucles irréductibles
Le compilateur Java convertit les boucles en instructions goto dans le code d'octet Java. De telles instructions peuvent être utilisées pour créer des boucles irréductibles, ce que le compilateur Java ne fait jamais.
Définir un bloc catch récursif
Dans le code d'octet Java, vous pouvez définir un bloc:
Une instruction similaire est créée implicitement lors de l'utilisation d'un
synchronized
bloc en Java où toute exception lors de la libération d'un moniteur renvoie à l'instruction de libération de ce moniteur. Normalement, aucune exception ne devrait se produire sur une telle instruction, mais si elle le faisait (par exemple, la version obsolèteThreadDeath
), le moniteur serait toujours libéré.Appelez n'importe quelle méthode par défaut
Le compilateur Java requiert que plusieurs conditions soient remplies afin de permettre l'invocation d'une méthode par défaut:
B
étend l'interfaceA
mais ne remplace pas une méthode dansA
, la méthode peut toujours être appelée.Pour le code d'octet Java, seule la deuxième condition compte. Le premier n'est cependant pas pertinent.
Invoquer une super méthode sur une instance qui ne l'est pas
this
Le compilateur Java permet uniquement d'appeler une méthode super (ou par défaut d'interface) sur les instances de
this
. En byte code, il est cependant également possible d'appeler la super méthode sur une instance du même type similaire à la suivante:Accéder aux membres synthétiques
Dans le code octet Java, il est possible d'accéder directement aux membres synthétiques. Par exemple, considérez comment dans l'exemple suivant l'
Bar
accès à l' instance externe d'une autre instance:Cela est généralement vrai pour tout champ, classe ou méthode synthétique.
Définir les informations de type générique désynchronisées
Bien que l'environnement d'exécution Java ne traite pas les types génériques (après que le compilateur Java a appliqué l'effacement de type), ces informations sont toujours attachées à une classe compilée en tant que méta-informations et rendues accessibles via l'API de réflexion.
Le vérificateur ne vérifie pas la cohérence de ces
String
valeurs codées par métadonnées. Il est donc possible de définir des informations sur des types génériques qui ne correspondent pas à l'effacement. Par principe, les affirmations suivantes peuvent être vraies:En outre, la signature peut être définie comme non valide de sorte qu'une exception d'exécution est levée. Cette exception est levée lorsque les informations sont accédées pour la première fois car elles sont évaluées paresseusement. (Similaire aux valeurs d'annotation avec une erreur.)
Ajouter des méta-informations de paramètre uniquement pour certaines méthodes
Le compilateur Java permet d'incorporer le nom du paramètre et les informations de modificateur lors de la compilation d'une classe avec l'
parameter
indicateur activé. Dans le format de fichier de classe Java, ces informations sont cependant stockées par méthode ce qui permet de n'incorporer ces informations de méthode que pour certaines méthodes.Gâcher les choses et planter durement votre JVM
Par exemple, dans le code d'octet Java, vous pouvez définir pour appeler n'importe quelle méthode sur n'importe quel type. Habituellement, le vérificateur se plaindra si un type ne connaît pas une telle méthode. Cependant, si vous invoquez une méthode inconnue sur un tableau, j'ai trouvé un bogue dans une version de JVM où le vérificateur manquera cela et votre JVM se terminera une fois l'instruction invoquée. Ce n'est cependant pas une fonctionnalité, mais c'est techniquement quelque chose qui n'est pas possible avec Java compilé javac . Java a une sorte de double validation. La première validation est appliquée par le compilateur Java, la seconde par la JVM lorsqu'une classe est chargée. En ignorant le compilateur, vous pourriez trouver un point faible dans la validation du vérificateur. Il s'agit cependant d'une déclaration générale plutôt que d'une fonctionnalité.
Annoter le type de récepteur d'un constructeur lorsqu'il n'y a pas de classe externe
Depuis Java 8, les méthodes non statiques et les constructeurs de classes internes peuvent déclarer un type de récepteur et annoter ces types. Les constructeurs de classes de niveau supérieur ne peuvent pas annoter leur type de récepteur car ils n'en déclarent généralement pas un.
Comme
Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()
il renvoie cependant uneAnnotatedType
représentationFoo
, il est possible d'inclure des annotations de type pourFoo
le constructeur de s directement dans le fichier de classe où ces annotations sont lues ultérieurement par l'API de réflexion.Utiliser des instructions de code d'octet inutilisées / héritées
Puisque d'autres l'ont nommé, je l'inclurai également. Java utilisait autrefois des sous-programmes par les instructions
JSR
etRET
. JBC connaissait même son propre type d'adresse de retour à cette fin. Cependant, l'utilisation de sous-programmes compliquait trop l'analyse de code statique, raison pour laquelle ces instructions ne sont plus utilisées. Au lieu de cela, le compilateur Java dupliquera le code qu'il compile. Cependant, cela crée fondamentalement une logique identique, c'est pourquoi je ne considère pas vraiment qu'il y ait quelque chose de différent. De même, vous pouvez par exemple ajouter leNOOP
Instruction de code d'octet qui n'est pas non plus utilisée par le compilateur Java mais qui ne vous permettrait pas vraiment de réaliser quelque chose de nouveau non plus. Comme indiqué dans le contexte, ces "instructions de fonctionnalités" mentionnées sont désormais supprimées de l'ensemble des opcodes légaux, ce qui les rend encore moins d'une fonctionnalité.la source
<clinit>
méthode en définissant des méthodes avec le nom<clinit>
mais en acceptant des paramètres ou en ayant unvoid
type de non- retour. Mais ces méthodes ne sont pas très utiles, la JVM les ignorera et le byte code ne pourra pas les invoquer. La seule utilité serait de confondre les lecteurs.IllegalMonitorStateException
si vous avez omis l'monitorexit
instruction. Et en cas de sortie de méthode exceptionnelle qui n'a pas réussi à faire unmonitorexit
, il réinitialise le moniteur en silence.Voici quelques fonctionnalités qui peuvent être effectuées dans le bytecode Java mais pas dans le code source Java:
Lancer une exception vérifiée à partir d'une méthode sans déclarer que la méthode la lève. Les exceptions vérifiées et non vérifiées sont une chose qui n'est vérifiée que par le compilateur Java, pas par la JVM. Pour cette raison, par exemple, Scala peut lever des exceptions vérifiées à partir de méthodes sans les déclarer. Bien qu'avec les génériques Java, il existe une solution de contournement appelée lancer sournois .
Avoir deux méthodes dans une classe qui ne diffèrent que par le type de retour, comme déjà mentionné dans la réponse de Joachim : La spécification du langage Java n'autorise pas deux méthodes dans la même classe lorsqu'elles diffèrent uniquement par leur type de retour (c'est-à-dire même nom, même liste d'arguments, ...). Cependant, la spécification JVM n'a pas une telle restriction, donc un fichier de classe peut contenir deux de ces méthodes, il n'y a tout simplement aucun moyen de produire un tel fichier de classe en utilisant le compilateur Java normal. Il y a un bel exemple / explication dans cette réponse .
la source
Thread.stop(Throwable)
pour un lancer sournois. Je suppose que celui déjà lié est plus rapide.GOTO
peut être utilisé avec des étiquettes pour créer vos propres structures de contrôle (autres quefor
while
etc.)this
variable locale dans une méthodeComme point connexe, vous pouvez obtenir le nom du paramètre pour les méthodes si compilées avec debug ( Paranamer le fait en lisant le bytecode
la source
override
cette variable locale?this
variable a un index zéro, mais en plus d'être pré-initialisée avec lathis
référence lors de la saisie d'une méthode d'instance, il s'agit simplement d'une variable locale. Ainsi, vous pouvez y écrire une valeur différente, qui peut agir comme une finthis
de portée ou une modification de lathis
variable, selon la façon dont vous l'utilisez.this
peut être réaffecté? Je pense que c'était juste le mot override qui m'a amené à me demander ce que cela signifiait exactement.Peut-être que la section 7A de ce document est intéressante, bien qu'elle concerne les pièges du bytecode plutôt que les fonctionnalités du bytecode .
la source
En langage Java, la première instruction d'un constructeur doit être un appel au constructeur de la super classe. Bytecode n'a pas cette limitation, à la place la règle est que le constructeur de super classe ou un autre constructeur de la même classe doit être appelé pour l'objet avant d'accéder aux membres. Cela devrait permettre plus de liberté comme:
Je ne les ai pas testés, veuillez donc me corriger si je me trompe.
la source
Quelque chose que vous pouvez faire avec du code octet, plutôt que du code Java simple, est de générer du code qui peut être chargé et exécuté sans compilateur. De nombreux systèmes ont JRE plutôt que JDK et si vous voulez générer du code dynamiquement, il peut être préférable, sinon plus facile, de générer du code d'octet au lieu du code Java doit être compilé avant de pouvoir être utilisé.
la source
J'ai écrit un optimiseur de bytecode quand j'étais un I-Play, (il a été conçu pour réduire la taille du code pour les applications J2ME). Une fonctionnalité que j'ai ajoutée était la possibilité d'utiliser le bytecode en ligne (similaire au langage d'assemblage en ligne en C ++). J'ai réussi à réduire la taille d'une fonction qui faisait partie d'une méthode de bibliothèque en utilisant l'instruction DUP, car j'ai besoin de la valeur deux fois. J'avais également des instructions de zéro octet (si vous appelez une méthode qui prend un caractère et que vous voulez passer un int, dont vous savez qu'il n'a pas besoin d'être casté, j'ai ajouté int2char (var) pour remplacer char (var) et cela supprimerait l'instruction i2c pour réduire la taille du code. Je lui ai également fait faire float a = 2.3; float b = 3.4; float c = a + b; et cela serait converti en virgule fixe (plus rapide, et certains J2ME ne l'ont pas supporte la virgule flottante).
la source
En Java, si vous essayez de remplacer une méthode publique avec une méthode protégée (ou toute autre réduction d'accès), vous obtenez une erreur: "tentative d'attribution de privilèges d'accès plus faibles". Si vous le faites avec le bytecode JVM, le vérificateur convient parfaitement et vous pouvez appeler ces méthodes via la classe parente comme si elles étaient publiques.
la source