O que é invocado dinâmico e como eu o uso?

159

Eu continuo ouvindo sobre todos os novos recursos interessantes que estão sendo adicionados à JVM e um desses recursos interessantes é invocado dinâmico. Gostaria de saber o que é e como torna a programação reflexiva em Java mais fácil ou melhor?

David K.
fonte

Respostas:

165

É uma nova instrução da JVM que permite a um compilador gerar código que chama métodos com uma especificação mais flexível do que era possível anteriormente - se você sabe o que é " digitação de pato ", invokedynamic basicamente permite a digitação de pato. Não há muito que você, como programador Java, possa fazer com isso; se você é um criador de ferramentas, pode usá-lo para criar linguagens baseadas em JVM mais flexíveis e eficientes. Aqui está um post muito bom que fornece muitos detalhes.

Ernest Friedman-Hill
fonte
3
Na programação Java diária, não é incomum ver a reflexão sendo usada para chamar métodos dinamicamente meth.invoke(args). Então, como se invokedynamicencaixa meth.invoke?
David K.
1
O post do blog que menciono fala MethodHandle, que é realmente o mesmo tipo de coisa, mas com muito mais flexibilidade. Mas o poder real disso tudo não vem das adições à linguagem Java, mas dos recursos da própria JVM no suporte a outras linguagens que são intrinsecamente mais dinâmicas.
Ernest Friedman-Hill
1
Parece que o Java 8 traduz algumas das lambdas usando, o invokedynamicque a torna com desempenho (comparado a envolvê-las em uma classe interna anônima, que era quase a única opção antes da introdução invokedynamic). Provavelmente muitas linguagens de programação funcionais sobre a JVM optarão por compilar isso em vez de classes não internas.
Nader Ghanbari
2
Apenas um pequeno aviso, que o blog de 2008 está irremediavelmente desatualizado e não reflete o estado real da versão (2011).
Holger
9

Há algum tempo, o C # adicionou um recurso interessante, sintaxe dinâmica dentro do C #

Object obj = ...; // no static type available 
dynamic duck = obj;
duck.quack(); // or any method. no compiler checking.

Pense nisso como açúcar de sintaxe para chamadas de método reflexivo. Pode ter aplicações muito interessantes. consulte http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter

Neal Gafter, responsável pelo tipo dinâmico de C #, acabou de desertar de SUN para MS. Portanto, não é irracional pensar que as mesmas coisas foram discutidas no SUN.

Lembro-me logo depois disso, um cara Java anunciou algo semelhante

InvokeDynamic duck = obj;
duck.quack(); 

Infelizmente, o recurso não existe em Java 7. Muito decepcionado. Para programadores Java, eles não têm como tirar proveito de invokedynamicseus programas.

irreputável
fonte
41
invokedynamicnunca foi planejado para ser usado por programadores Java. OMI não se encaixa na filosofia Java. Foi adicionado como um recurso da JVM para linguagens não Java.
Mark Peters
5
@ Mark Nunca pretendido por quem? Não é como se houvesse uma estrutura clara de poder nas celebridades da linguagem Java, ou houvesse uma "intenção" coletiva bem definida. Quanto à filosofia da linguagem - é bastante viável, consulte Neal Gafter (traidor!) Explicação: infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
irreputable
3
@mark peters: invokedynamic também é destinado a programadores java que não são acessíveis diretamente. É a base para o fechamento do Java 8.
M Platvoet
2
@ irreputable: nunca pretendido pelos colaboradores do JSR. É revelador que o nome do JSR é "Suporte a linguagens dinamicamente tipadas na plataforma Java". Java não é uma linguagem de tipo dinâmico.
10789 Mark-Peters
5
@ Platvoet: Não me atualizei com os fechamentos, mas certamente não seria um requisito absoluto para os fechamentos. Outra opção discutida foi simplesmente fazer o açúcar sintático dos fechamentos para classes internas anônimas, o que poderia ser feito sem uma alteração nas especificações da VM. Mas o que quero dizer é que o JSR nunca teve a intenção de trazer tipagem dinâmica para a linguagem Java, isso fica claro se você ler o JSR.
Mark Peters
4

Existem dois conceitos a serem entendidos antes de continuar com o invocado dinâmico.

1. Digitação Estática vs. Dinamina

Estático - verificação de tipo de pré-formas em tempo de compilação (por exemplo, Java)

Dinâmico - verificação de tipo de pré-formas em tempo de execução (por exemplo, JavaScript)

A verificação de tipo é um processo para verificar se um programa é do tipo seguro, ou seja, verificar as informações digitadas para variáveis ​​de classe e instância, parâmetros de método, valores de retorno e outras variáveis. Por exemplo, Java sabe sobre int, String, .. em tempo de compilação, enquanto o tipo de um objeto em JavaScript só pode ser determinado em tempo de execução

2. Digitação forte vs. fraca

Forte - especifica restrições sobre os tipos de valores fornecidos para suas operações (por exemplo, Java)

Fraco - converte (lança) argumentos de uma operação se esses argumentos tiverem tipos incompatíveis (por exemplo, Visual Basic)

Sabendo que o Java é do tipo Estático e Fraco, como você implementa linguagens do tipo Dinâmico e Fortemente na JVM?

O invokedynamic implementa um sistema de tempo de execução que pode escolher a implementação mais apropriada de um método ou função - após a compilação do programa.

Exemplo: Tendo (a + b) e sem saber nada sobre as variáveis ​​a, b em tempo de compilação, invokedynamic mapeia essa operação para o método mais apropriado em Java em tempo de execução. Por exemplo, se for a, b são Strings, chame o método (String a, String b). Se, a, b são ints, chame o método (int a, int b).

invokedynamic foi introduzido com o Java 7.

Sabina Orazem
fonte
4

Como parte do meu artigo da Java Records , articulei sobre a motivação por trás do Inoke Dynamic. Vamos começar com uma definição grosseira de Indy.

Apresentando Indy

Invoke Dynamic (também conhecido como Indy ) fazia parte do JSR 292 que pretendia aprimorar o suporte da JVM para idiomas de tipo dinâmico. Após seu primeiro lançamento em Java 7, o invokedynamiccódigo de operação, juntamente com sua java.lang.invokebagagem, é amplamente utilizado por linguagens dinâmicas baseadas em JVM, como o JRuby.

Embora o indy tenha sido projetado especificamente para aprimorar o suporte ao idioma dinâmico, oferece muito mais do que isso. Por uma questão de fato, é adequado para uso sempre que um designer de linguagem precisar de qualquer forma de dinâmica, de acrobacias de tipo dinâmico a estratégias dinâmicas!

Por exemplo, as Java 8 Lambda Expressions são realmente implementadas usando invokedynamic, mesmo que Java seja uma linguagem de tipo estaticamente!

Bytecode Definível pelo Usuário

Por algum tempo, a JVM suportou quatro tipos de chamada de método: invokestaticchamar métodos estáticos, invokeinterfacechamar métodos de interface, invokespecialchamar construtores super()ou métodos privados e invokevirtualchamar métodos de instância.

Apesar de suas diferenças, esses tipos de invocação compartilham uma característica comum: não podemos enriquecê-los com nossa própria lógica . Pelo contrário, invokedynamic nos permite inicializar o processo de chamada da maneira que desejarmos. Em seguida, a JVM cuida de chamar o método Bootstrapped diretamente.

Como o Indy funciona?

A primeira vez que a JVM vê uma invokedynamicinstrução, ela chama um método estático especial chamado Método Bootstrap . O método bootstrap é uma parte do código Java que escrevemos para preparar a lógica real a ser invocada:

insira a descrição da imagem aqui

Em seguida, o método de autoinicialização retorna uma instância de java.lang.invoke.CallSite. Isso CallSitemantém uma referência ao método real, ie MethodHandle.

A partir de agora, toda vez que a JVM vir essa invokedynamicinstrução novamente, ela ignora o Caminho Lento e chama diretamente o executável subjacente. A JVM continua ignorando o caminho lento, a menos que algo mude.

Exemplo: Registros Java 14

O Java 14 Recordsestá fornecendo uma boa sintaxe compacta para declarar classes que deveriam ser detentoras de dados estúpidas.

Considerando esse registro simples:

public record Range(int min, int max) {}

O bytecode para este exemplo seria algo como:

Compiled from "Range.java"
public java.lang.String toString();
    descriptor: ()Ljava/lang/String;
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokedynamic #18,  0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String;
         6: areturn

Em sua tabela de métodos de inicialização :

BootstrapMethods:
  0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap:
     (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;
     Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class;
     Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object;
    Method arguments:
      #8 Range
      #48 min;max
      #50 REF_getField Range.min:I
      #51 REF_getField Range.max:I

Portanto, o método de autoinicialização para registros é chamado, bootstrapque reside na java.lang.runtime.ObjectMethodsclasse. Como você pode ver, esse método de inicialização espera os seguintes parâmetros:

  • Uma instância de MethodHandles.Lookuprepresentação do contexto de pesquisa (A Ljava/lang/invoke/MethodHandles$Lookupparte).
  • O nome do método (ou seja toString, equals, hashCode, etc.) o bootstrap vai link. Por exemplo, quando o valor é toString, o bootstrap retornará um ConstantCallSite(a CallSiteque nunca muda) que aponta para a toStringimplementação real desse registro em particular.
  • O TypeDescriptorpara o método ( Ljava/lang/invoke/TypeDescriptor parte).
  • Um token de tipo, ou seja Class<?>, representando o tipo de classe Record. É Class<Range>neste caso.
  • Uma lista separada por ponto e vírgula de todos os nomes de componentes, ou seja min;max.
  • Um MethodHandlepor componente. Dessa maneira, o método de autoinicialização pode criar uma MethodHandlebase nos componentes para essa implementação de método específica.

A invokedynamicinstrução passa todos esses argumentos para o método de inicialização. O método Bootstrap, por sua vez, retorna uma instância de ConstantCallSite. Isso ConstantCallSiteestá mantendo uma referência à implementação do método solicitado, por exemplo toString.

Por que Indy?

Ao contrário das APIs de reflexão, a java.lang.invokeAPI é bastante eficiente, pois a JVM pode ver completamente todas as invocações. Portanto, a JVM pode aplicar todos os tipos de otimizações, desde que evitemos o caminho lento o máximo possível!

Além do argumento da eficiência, a invokedynamicabordagem é mais confiável e menos quebradiça devido à sua simplicidade .

Além disso, o bytecode gerado para Java Records é independente do número de propriedades. Portanto, menos bytecode e tempo de inicialização mais rápido.

Por fim, vamos supor que uma nova versão do Java inclua uma implementação de método de autoinicialização nova e mais eficiente. Com invokedynamic, nosso aplicativo pode tirar proveito dessa melhoria sem recompilação. Dessa forma, temos algum tipo de compatibilidade binária direta . Além disso, essa é a estratégia dinâmica de que estávamos falando!

Outros exemplos

Além do Java Records, a dinâmica de chamada foi usada para implementar recursos como:

Ali Dehghani
fonte