Respostas:
É 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.
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.
invokedynamic
que 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.
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 invokedynamic
seus programas.
invokedynamic
nunca 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.
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.
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.
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 invokedynamic
código de operação, juntamente com sua java.lang.invoke
bagagem, é 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!
Por algum tempo, a JVM suportou quatro tipos de chamada de método: invokestatic
chamar métodos estáticos, invokeinterface
chamar métodos de interface, invokespecial
chamar construtores super()
ou métodos privados e invokevirtual
chamar 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.
A primeira vez que a JVM vê uma invokedynamic
instruçã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:
Em seguida, o método de autoinicialização retorna uma instância de java.lang.invoke.CallSite
. Isso CallSite
mantém uma referência ao método real, ie MethodHandle
.
A partir de agora, toda vez que a JVM vir essa invokedynamic
instruçã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.
O Java 14 Records
está 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, bootstrap
que reside na java.lang.runtime.ObjectMethods
classe. Como você pode ver, esse método de inicialização espera os seguintes parâmetros:
MethodHandles.Lookup
representação do contexto de pesquisa (A Ljava/lang/invoke/MethodHandles$Lookup
parte).toString
, equals
, hashCode
, etc.) o bootstrap vai link. Por exemplo, quando o valor é toString
, o bootstrap retornará um ConstantCallSite
(a CallSite
que nunca muda) que aponta para a toString
implementação real desse registro em particular.TypeDescriptor
para o método ( Ljava/lang/invoke/TypeDescriptor
parte).Class<?>
, representando o tipo de classe Record. É
Class<Range>
neste caso.min;max
.MethodHandle
por componente. Dessa maneira, o método de autoinicialização pode criar uma MethodHandle
base nos componentes para essa implementação de método específica.A invokedynamic
instruçã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 ConstantCallSite
está mantendo uma referência à implementação do método solicitado, por exemplo toString
.
Ao contrário das APIs de reflexão, a java.lang.invoke
API é 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 invokedynamic
abordagem é 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!
Além do Java Records, a dinâmica de chamada foi usada para implementar recursos como:
LambdaMetafactory
StringConcatFactory
meth.invoke(args)
. Então, como seinvokedynamic
encaixameth.invoke
?