Recursos de bytecode não disponíveis na linguagem Java

146

Atualmente, existem (Java 6) coisas que você pode fazer no bytecode Java que não pode ser feito na linguagem Java?

Eu sei que ambos são Turing completos, então leia "pode ​​fazer" como "pode ​​fazer significativamente mais rápido / melhor ou apenas de uma maneira diferente".

Estou pensando em bytecodes extras como invokedynamic, que não podem ser gerados usando Java, exceto que um específico é para uma versão futura.

Bart van Heukelom
fonte
3
Defina "coisas". No final, a linguagem Java e Java bytecode são ambos Turing completo ...
Michael Borgwardt
2
É a verdadeira questão; existe alguma vantagem em programar em código de bytes, por exemplo, usando Jasmin, em vez de Java?
Peter Lawrey
2
Como rolno assembler, que você não pode escrever em C ++.
Martijn Courteaux
1
É um compilador de otimização muito ruim que não pode compilar (x<<n)|(x>>(32-n))com uma rolinstrução.
Random832

Respostas:

62

Tanto quanto sei, não há recursos importantes nos bytecodes suportados pelo Java 6 que também não sejam acessíveis a partir do código-fonte Java. A principal razão para isso é obviamente que o bytecode Java foi projetado com a linguagem Java em mente.

Existem alguns recursos que não são produzidos pelos compiladores Java modernos, no entanto:

  • A ACC_SUPERbandeira :

    Este é um sinalizador que pode ser definido em uma classe e especifica como um caso de canto específico do invokespecialbytecode é tratado para essa classe. Ele é definido por todos os compiladores Java modernos (onde "moderno" é> = Java 1.1, se bem me lembro) e apenas os compiladores Java antigos produziram arquivos de classe onde isso não estava definido. Este sinalizador existe apenas por motivos de compatibilidade com versões anteriores. Observe que a partir do Java 7u51, o ACC_SUPER é completamente ignorado devido a motivos de segurança.

  • Os jsr/ retbytecodes.

    Esses bytecodes foram usados ​​para implementar sub-rotinas (principalmente para implementar finallyblocos). Eles não são mais produzidos desde o Java 6 . A razão para a sua descontinuação é que eles complicam muito a verificação estática sem grandes ganhos (ou seja, o código que usa quase sempre pode ser reimplementado com saltos normais com muito pouca sobrecarga).

  • Ter dois métodos em uma classe que diferem apenas no tipo de retorno.

    A especificação da linguagem Java não permite dois métodos na mesma classe quando diferem apenas no tipo de retorno (ou seja, mesmo nome, mesma lista de argumentos, ...). A especificação da JVM, no entanto, não possui essa restrição; portanto, um arquivo de classe pode conter dois métodos, não há como produzir um arquivo de classe usando o compilador Java normal. Há um bom exemplo / explicação nesta resposta .

Joachim Sauer
fonte
5
Eu poderia acrescentar outra resposta, mas podemos também fazer da sua a resposta canônica. Você pode mencionar que a assinatura de um método no bytecode inclui o tipo de retorno . Ou seja, você pode ter dois métodos com exatamente os mesmos tipos de parâmetros, mas diferentes tipos de retorno. Consulte esta discussão: stackoverflow.com/questions/3110014/is-this-valid-java/…
Adam Paynter:
8
Você pode ter nomes de classe, método e campo com praticamente qualquer caractere. Eu trabalhei em um projeto em que os "campos" tinham espaços e hífens em seus nomes. : P
Peter Lawrey
3
@ Peter: Falando em caracteres do sistema de arquivos, encontrei um ofuscador que havia renomeado uma classe para aoutra Adentro do arquivo JAR. Levei cerca de meia hora de descompactar em uma máquina Windows antes de perceber onde estavam as aulas ausentes. :)
Adam Paynter
3
@JoachimSauer: parafraseado JVM spec, página 75: nomes de classes, métodos, campos e variáveis locais podem conter qualquer caractere, exceto '.', ';', '[', ou '/'. Os nomes dos métodos são os mesmos, mas eles também não podem conter '<'or '>'. (Com as notáveis exceções de <init>e <clinit>para instância e construtores estáticos.) Gostaria de salientar que, se você está seguindo a especificação estritamente, os nomes de classe são realmente muito mais restrito, mas as restrições não são aplicadas.
Leviathanbadger #
3
@ JoachimSauer: também, uma adição não documentada da minha autoria: a linguagem java inclui o "throws ex1, ex2, ..., exn"como parte das assinaturas do método; você não pode adicionar cláusulas de exceção a métodos substituídos. MAS, a JVM não se importava. Portanto, apenas os finalmétodos são verdadeiramente garantidos pela JVM como livres de exceção - além de RuntimeExceptions e Errors, é claro. Tanto para o tratamento de exceção verificada: D
leviathanbadger
401

Depois de trabalhar com o código de bytes Java por um bom tempo e fazer algumas pesquisas adicionais sobre este assunto, aqui está um resumo das minhas descobertas:

Executar código em um construtor antes de chamar um super construtor ou construtor auxiliar

Na linguagem de programação Java (JPL), a primeira instrução de um construtor deve ser uma invocação de um super construtor ou outro construtor da mesma classe. Isso não se aplica ao código de bytes Java (JBC). No código de bytes, é absolutamente legítimo executar qualquer código antes de um construtor, desde que:

  • Outro construtor compatível é chamado em algum momento após esse bloco de código.
  • Esta chamada não está dentro de uma declaração condicional.
  • Antes dessa chamada do construtor, nenhum campo da instância construída é lido e nenhum de seus métodos é chamado. Isso implica o próximo item.

Defina os campos da instância antes de chamar um super construtor ou construtor auxiliar

Como mencionado anteriormente, é perfeitamente legal definir um valor de campo de uma instância antes de chamar outro construtor. Existe até um hack herdado que permite explorar esse "recurso" nas versões Java anteriores ao 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();
  }
}

Dessa forma, um campo pode ser definido antes da invocação do super construtor, o que não é mais possível. No JBC, esse comportamento ainda pode ser implementado.

Ramificar uma chamada de super construtor

Em Java, não é possível definir uma chamada de construtor como

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

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

Até o Java 7u23, o verificador da HotSpot VM, no entanto, não atendia a essa verificação, razão pela qual era possível. Isso foi usado por várias ferramentas de geração de código como uma espécie de hack, mas não é mais legal implementar uma classe como esta.

Este último foi apenas um bug nesta versão do compilador. Nas versões mais recentes do compilador, isso é novamente possível.

Definir uma classe sem nenhum construtor

O compilador Java sempre implementará pelo menos um construtor para qualquer classe. No código de bytes Java, isso não é necessário. Isso permite a criação de classes que não podem ser construídas, mesmo quando se usa reflexão. No entanto, o uso sun.misc.Unsafeainda permite a criação de tais instâncias.

Definir métodos com assinatura idêntica, mas com tipo de retorno diferente

Na JPL, um método é identificado como exclusivo por seu nome e seus tipos de parâmetros brutos. No JBC, o tipo de retorno bruto é considerado adicionalmente.

Definir campos que não diferem por nome, mas apenas por tipo

Um arquivo de classe pode conter vários campos com o mesmo nome, desde que declarem um tipo de campo diferente. A JVM sempre se refere a um campo como uma tupla de nome e tipo.

Lance exceções verificadas não declaradas sem capturá-las

O tempo de execução Java e o código de byte Java não estão cientes do conceito de exceções verificadas. É apenas o compilador Java que verifica se as exceções verificadas são sempre capturadas ou declaradas se forem lançadas.

Usar invocação de método dinâmico fora das expressões lambda

A chamada chamada de método dinâmico pode ser usada para qualquer coisa, não apenas para expressões lambda do Java. O uso desse recurso permite, por exemplo, alternar a lógica de execução em tempo de execução. Muitas linguagens de programação dinâmica que se resumem ao JBC melhoraram seu desempenho usando esta instrução. No código de byte Java, você também pode emular expressões lambda no Java 7 em que o compilador ainda não permitiu o uso de invocação de método dinâmico enquanto a JVM já entendia a instrução.

Use identificadores que normalmente não são considerados legais

Já imaginou usar espaços e uma quebra de linha no nome do seu método? Crie seu próprio JBC e boa sorte para revisão de código. Os únicos caracteres ilegais para identificadores são ., ;, [e /. Além disso, métodos que não são nomeados <init>ou <clinit>não podem conter <e >.

Reatribuir finalparâmetros ou a thisreferência

finalparâmetros não existem no JBC e, consequentemente, podem ser reatribuídos. Qualquer parâmetro, incluindo a thisreferência, é armazenado apenas em uma matriz simples na JVM, o que permite reatribuir a thisreferência no índice0 dentro de um único quadro de método.

Reatribuir final campos

Desde que um campo final seja atribuído dentro de um construtor, é legal reatribuir esse valor ou até mesmo não atribuir um valor. Portanto, os dois construtores a seguir são legais:

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

Para static finalcampos, é permitido até reatribuir os campos fora do inicializador de classe.

Trate os construtores e o inicializador de classe como se fossem métodos

Esse é um recurso mais conceitual, mas os construtores não são tratados de maneira diferente no JBC que os métodos normais. É apenas o verificador da JVM que garante que os construtores chamam outro construtor legal. Fora isso, é apenas uma convenção de nomenclatura Java que os construtores devem ser chamados <init>e que o inicializador de classe é chamado <clinit>. Além dessa diferença, a representação de métodos e construtores é idêntica. Como Holger apontou em um comentário, você pode até definir construtores com tipos de retorno diferentes de voidou um inicializador de classe com argumentos, mesmo que não seja possível chamar esses métodos.

Crie registros assimétricos * .

Ao criar um registro

record Foo(Object bar) { }

O javac irá gerar um arquivo de classe com um único campo nomeado bar, um método acessador nomeado bar()e um construtor usando um único Object. Além disso, um atributo de registro para baré adicionado. Ao gerar manualmente um registro, é possível criar, uma forma diferente de construtor, pular o campo e implementar o acessador de maneira diferente. Ao mesmo tempo, ainda é possível fazer a API de reflexão acreditar que a classe representa um registro real.

Chame qualquer super método (até Java 1.1)

No entanto, isso só é possível para as versões 1 e 1.1 do Java. No JBC, os métodos são sempre despachados em um tipo de destino explícito. Isso significa que para

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

foi possível implementar Qux#bazpara invocar Foo#bazenquanto pulava Bar#baz. Embora ainda seja possível definir uma chamada explícita para chamar outra implementação de super método que não seja a superclasse direta, isso não tem mais efeito nas versões Java após a 1.1. No Java 1.1, esse comportamento era controlado pela configuração do ACC_SUPERsinalizador que permitiria o mesmo comportamento que chama apenas a implementação direta da superclasse.

Definir uma chamada não virtual de um método declarado na mesma classe

Em Java, não é possível definir uma classe

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

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

O código acima sempre resultará em um RuntimeExceptionquando fooé chamado em uma instância de Bar. Não é possível definir o Foo::foométodo para chamar seu próprio bar método, definido em Foo. Como baré um método de instância não particular, a chamada é sempre virtual. No entanto, com o código de bytes, é possível definir a invocação para usar o INVOKESPECIALcódigo de operação que vincula diretamente a barchamada do método Foo::fooà Fooversão. Esse opcode é normalmente usado para implementar invocações de super métodos, mas você pode reutilizá-lo para implementar o comportamento descrito.

Anotações do tipo granulação fina

Em Java, as anotações são aplicadas de acordo com o @Targetque as anotações declaram. Usando a manipulação de código de bytes, é possível definir anotações independentemente desse controle. Além disso, é possível, por exemplo, anotar um tipo de parâmetro sem anotar o parâmetro, mesmo que a @Targetanotação se aplique aos dois elementos.

Defina qualquer atributo para um tipo ou seus membros

Na linguagem Java, só é possível definir anotações para campos, métodos ou classes. No JBC, você pode basicamente incorporar qualquer informação nas classes Java. Para usar essas informações, você não pode mais confiar no mecanismo de carregamento de classe Java, mas precisa extrair as meta informações por conta própria.

Overflow e implicitamente atribuir byte, short, chare booleanvalores

Os últimos tipos primitivos normalmente não são conhecidos no JBC, mas são definidos apenas para tipos de matriz ou para descritores de campo e método. Nas instruções do código de bytes, todos os tipos nomeados ocupam o espaço de 32 bits, o que permite representá-los como int. Oficialmente, apenas os int, float, longe doubleexistem tipos no código byte que toda a necessidade de conversão explícita pelo Estado de verificador do JVM.

Não libera um monitor

Na synchronizedverdade, um bloco é composto de duas instruções, uma para adquirir e outra para liberar um monitor. No JBC, você pode adquirir um sem liberá-lo.

Nota : Em implementações recentes do HotSpot, isso leva a um IllegalMonitorStateExceptionno final de um método ou a uma liberação implícita se o método for finalizado por uma exceção em si.

Adicione mais de uma returninstrução a um inicializador de tipo

Em Java, mesmo um inicializador de tipo trivial como

class Foo {
  static {
    return;
  }
}

é ilegal. No código de bytes, o inicializador de tipo é tratado como qualquer outro método, ou seja, as instruções de retorno podem ser definidas em qualquer lugar.

Criar loops irredutíveis

O compilador Java converte loops em instruções goto no código de bytes Java. Tais instruções podem ser usadas para criar loops irredutíveis, o que o compilador Java nunca cria.

Definir um bloco de captura recursivo

No código de bytes Java, você pode definir um bloco:

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

Uma instrução semelhante é criada implicitamente ao usar um synchronizedbloco em Java, em que qualquer exceção ao liberar um monitor retorna à instrução para liberá-lo. Normalmente, nenhuma exceção deve ocorrer em tal instrução, mas se ocorrer (por exemplo, a obsoleta ThreadDeath), o monitor ainda será liberado.

Chame qualquer método padrão

O compilador Java requer que várias condições sejam atendidas para permitir a chamada de um método padrão:

  1. O método deve ser o mais específico (não deve ser substituído por uma sub interface que é implementada por qualquer tipo, incluindo super tipos).
  2. O tipo de interface do método padrão deve ser implementado diretamente pela classe que está chamando o método padrão. No entanto, se a interface Bestender a interface, Amas não substituir um método A, o método ainda poderá ser chamado.

Para código de bytes Java, apenas a segunda condição conta. O primeiro é, no entanto, irrelevante.

Invoque um super método em uma instância que não seja this

O compilador Java apenas permite chamar um método super (ou padrão da interface) em instâncias de this. No código de bytes, no entanto, também é possível invocar o super método em uma instância do mesmo tipo semelhante à seguinte:

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

Acessar membros sintéticos

No código de bytes Java, é possível acessar membros sintéticos diretamente. Por exemplo, considere como no exemplo a seguir a instância externa de outra Barinstância é acessada:

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

Isso geralmente é verdade para qualquer campo, classe ou método sintético.

Definir informações de tipo genérico fora de sincronização

Embora o tempo de execução Java não processe tipos genéricos (depois que o compilador Java aplica o apagamento do tipo), essas informações ainda são anexadas a uma classe compilada como meta-informação e tornadas acessíveis por meio da API de reflexão.

O verificador não verifica a consistência desses Stringvalores codificados por metadados . Portanto, é possível definir informações sobre tipos genéricos que não correspondem à eliminação. Como concepção, as seguintes afirmações podem ser verdadeiras:

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

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

Além disso, a assinatura pode ser definida como inválida, de modo que uma exceção de tempo de execução seja lançada. Essa exceção é lançada quando as informações são acessadas pela primeira vez e avaliadas preguiçosamente. (Semelhante aos valores da anotação com erro.)

Anexar informações meta meta apenas para certos métodos

O compilador Java permite incorporar o nome do parâmetro e as informações do modificador ao compilar uma classe com o parametersinalizador ativado. No formato de arquivo da classe Java, essas informações são armazenadas por método, o que torna possível incorporar apenas essas informações a determinados métodos.

Estrague tudo e faça um crash pesado na sua JVM

Como exemplo, no código de byte Java, você pode definir para chamar qualquer método em qualquer tipo. Normalmente, o verificador reclama se um tipo não conhece esse método. No entanto, se você invocar um método desconhecido em uma matriz, encontrei um bug em alguma versão da JVM, na qual o verificador perderá isso e sua JVM terminará assim que a instrução for chamada. Isso dificilmente é um recurso, mas é tecnicamente algo que não é possível com o Java compilado por javac . Java tem algum tipo de validação dupla. A primeira validação é aplicada pelo compilador Java, a segunda pela JVM quando uma classe é carregada. Ignorando o compilador, você pode encontrar um ponto fraco na validação do verificador. Esta é mais uma afirmação geral do que um recurso.

Anotar o tipo de receptor de um construtor quando não houver classe externa

Desde o Java 8, métodos não estáticos e construtores de classes internas podem declarar um tipo de receptor e anotar esses tipos. Os construtores de classes de nível superior não podem anotar seu tipo de receptor, pois a maioria não declara um.

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

Como Foo.class.getDeclaredConstructor().getAnnotatedReceiverType(), no entanto, retorna uma AnnotatedTyperepresentação Foo, é possível incluir anotações de tipo para Fooo construtor diretamente no arquivo de classe em que essas anotações são lidas posteriormente pela API de reflexão.

Use instruções de código de bytes não utilizados / herdados

Como outros o nomearam, também o incluirei. O Java anteriormente fazia uso de sub-rotinas pelas instruções JSRe RET. A JBC até conhecia seu próprio tipo de endereço de retorno para esse fim. No entanto, o uso de sub-rotinas complicou demais a análise de código estático, razão pela qual essas instruções não são mais usadas. Em vez disso, o compilador Java duplicará o código que compila. No entanto, isso basicamente cria uma lógica idêntica e é por isso que eu realmente não considero alcançar algo diferente. Da mesma forma, você pode, por exemplo, adicionar oNOOPinstrução de código de bytes que também não é usada pelo compilador Java, mas isso também não permitiria que você conseguisse algo novo. Como apontado no contexto, essas "instruções de recurso" mencionadas agora são removidas do conjunto de códigos legais que os tornam ainda menos um recurso.

Rafael Winterhalter
fonte
3
Em relação aos nomes dos métodos, você pode ter mais de um <clinit>método definindo métodos com o nome, <clinit>mas aceitando parâmetros ou tendo um voidtipo de não retorno. Mas esses métodos não são muito úteis, a JVM os ignorará e o código de bytes não poderá invocá-los. O único uso seria confundir os leitores.
Holger
2
Acabei de descobrir que a JVM da Oracle detecta um monitor não lançado na saída do método e lança um IllegalMonitorStateExceptionse você omitiu a monitorexitinstrução. E, no caso de uma saída excepcional do método que falhou na execução de a monitorexit, ela redefine o monitor silenciosamente.
Holger
1
@ Holger - não sabia disso, eu sei que isso era possível em JVMs anteriores, pelo menos, o JRockit ainda tem seu próprio manipulador para esse tipo de implementação. Vou atualizar a entrada.
Rafael Winterhalter
1
Bem, a especificação da JVM não exige esse comportamento. Acabei de descobri-lo porque tentei criar um bloqueio intrínseco oscilante usando esse código de bytes não padrão.
Holger
3
Ok, encontrei a especificação relevante : “ O bloqueio estruturado é a situação em que, durante uma chamada de método, todas as saídas em um determinado monitor correspondem a uma entrada anterior nesse monitor. Como não há garantia de que todo o código enviado à Java Virtual Machine execute o bloqueio estruturado, as implementações da Java Virtual Machine são permitidas, mas não necessárias, para impor as duas regras a seguir, garantindo o bloqueio estruturado. ... ”
Holger
14

Aqui estão alguns recursos que podem ser executados no bytecode Java, mas não no código-fonte Java:

  • Lançar uma exceção verificada de um método sem declarar que o método a lança. As exceções verificadas e não verificadas são verificadas apenas pelo compilador Java, não pela JVM. Por isso, por exemplo, o Scala pode lançar exceções verificadas dos métodos sem declará-las. Embora, com os genéricos Java, exista uma solução alternativa chamada sneaky throw .

  • Tendo dois métodos em uma classe que diferem apenas no tipo de retorno, como já mencionado na resposta de Joachim : A especificação da linguagem Java não permite dois métodos na mesma classe quando eles diferem apenas no tipo de retorno (ou seja, mesmo nome, mesma lista de argumentos, ...) A especificação da JVM, no entanto, não possui essa restrição; portanto, um arquivo de classe pode conter dois métodos, simplesmente não há como produzir um arquivo de classe usando o compilador Java normal. Há um bom exemplo / explicação nesta resposta .

Esko Luontola
fonte
4
Note que não é uma maneira de fazer a primeira coisa em Java. Às vezes é chamado de arremesso furtivo .
Joachim Sauer
Agora isso é sorrateiro! : D Obrigado por compartilhar.
Esko Luontola
Eu acho que você também pode usar Thread.stop(Throwable)para um lançamento sorrateiro. Presumo que o já vinculado seja mais rápido.
Bart van Heukelom 26/07
2
Você não pode criar uma instância sem chamar um construtor no bytecode Java. O verificador rejeitará qualquer código que tente usar uma instância não inicializada. A implementação de desserialização de objetos usa auxiliares de código nativos para criar instâncias sem chamar o construtor.
Holger
Para uma classe Foo estendendo Object, você não pode instanciar Foo chamando um construtor declarado em Object. O verificador recusaria. Você pode criar esse construtor usando o ReflectionFactory do Java, mas esse dificilmente é um recurso de código de bytes, mas é realizado pelo Jni. Sua resposta está errada e Holger está correto.
Rafael Winterhalter 15/03
8
  • GOTOpode ser usado com etiquetas para criar suas próprias estruturas de controle (além de for whileetc)
  • Você pode substituir a thisvariável local dentro de um método
  • Combinando os dois, é possível criar bytecode otimizado para criar chamada de cauda (eu faço isso no JCompilo )

Como um ponto relacionado, você pode obter o nome do parâmetro para métodos se compilado com depuração (o Paranamer faz isso lendo o bytecode

Daniel Worthington-Bodart
fonte
Como você overrideesta variável local?
Michael
2
@ Michael substituir é uma palavra muito forte. No nível do bytecode, todas as variáveis ​​locais são acessadas por um índice numérico e não há diferença entre gravar em uma variável existente ou inicializar uma nova variável (com escopo disjuntivo); em ambos os casos, é apenas uma gravação em uma variável local. A thisvariável possui índice zero, mas além de ser pré-inicializada com a thisreferência ao inserir um método de instância, é apenas uma variável local. Assim, você pode escrever um valor diferente, que pode agir como finalização thisdo escopo ou alteração da thisvariável, dependendo de como você o usa.
Holger
Entendo! Realmente é isso que thispode ser reatribuído? Eu acho que foi apenas a substituição da palavra que me fez pensar no que isso significava exatamente.
Michael
5

Talvez a seção 7A deste documento seja interessante, embora se trate de armadilhas de códigos de bytes, e não de recursos de códigos de código .

eljenso
fonte
Leitura interessante, mas não parece que alguém queira (ab) usar qualquer uma dessas coisas.
Bart van Heukelom 26/07/11
4

Na linguagem Java, a primeira instrução em um construtor deve ser uma chamada para o construtor de superclasse. O bytecode não possui essa limitação; em vez disso, a regra é que o construtor da superclasse ou outro construtor da mesma classe deve ser chamado para o objeto antes de acessar os membros. Isso deve permitir mais liberdade, como:

  • Crie uma instância de outro objeto, armazene-a em uma variável local (ou pilha) e passe-a como um parâmetro para o construtor de super classes, mantendo a referência nessa variável para outro uso.
  • Chame diferentes outros construtores com base em uma condição. Isso deve ser possível: Como chamar um construtor diferente condicionalmente em Java?

Eu não os testei, por favor, corrija-me se estiver errado.

msell
fonte
Você pode até definir membros de uma instância antes de chamar seu construtor de superclasse. No entanto, a leitura de campos ou métodos de chamada não é possível antes disso.
Rafael Winterhalter
3

Algo que você pode fazer com o código de bytes, em vez do código Java simples, é gerar código que pode ser carregado e executado sem um compilador. Muitos sistemas têm JRE em vez de JDK e se você deseja gerar código dinamicamente, pode ser melhor, se não mais fácil, gerar código de bytes em vez de código Java, que deve ser compilado antes de poder ser usado.

Peter Lawrey
fonte
6
Mas então você está pulando o compilador, não produzindo algo que não possa ser produzido usando o compilador (se disponível).
Bart van Heukelom 26/07/11
2

Escrevi um otimizador de bytecode quando eu era um I-Play (ele foi projetado para reduzir o tamanho do código para aplicativos J2ME). Um recurso que eu adicionei foi a capacidade de usar bytecode embutido (semelhante à linguagem assembly embutida em C ++). Consegui reduzir o tamanho de uma função que fazia parte de um método de biblioteca usando a instrução DUP, pois preciso do valor duas vezes. Eu também tinha instruções de zero byte (se você está chamando um método que usa um char e deseja passar um int, que você sabe que não precisa ser convertido, adicionei int2char (var) para substituir char (var) e ele removeria a instrução i2c para reduzir o tamanho do código.Eu também fiz float a = 2.3; float b = 3.4; float c = a + b; e isso seria convertido em ponto fixo (mais rápido, e também alguns J2ME não suporte ponto flutuante).

nharding
fonte
2

Em Java, se você tentar substituir um método público por um método protegido (ou qualquer outra redução no acesso), receberá um erro: "ao tentar atribuir privilégios de acesso mais fracos". Se você fizer isso com o bytecode da JVM, o verificador estará bem com ele e poderá chamar esses métodos através da classe pai como se fossem públicos.

Joseph Sible-Restabelecer Monica
fonte