O que é método manipula em Java

1. Introdução


Neste tutorial, abordaremos uma importante API introduzida no Java 7 e estendida em novas versões, java.lang.invoke.MethodHandles .



Vamos aprender o que são manipuladores de métodos, como criá-los e usá-los.


2. O que são manipuladores de métodos?


Na documentação da API, o identificador do método tem esta definição:


O identificador de método é uma referência executável digitada a um método base, construtor, campo ou outra operação de baixo nível com transformações adicionais de argumentos ou valores de retorno.

Em outras palavras, os identificadores de método são um mecanismo de baixo nível para localizar, adaptar e chamar métodos. Os identificadores de método são imutáveis ​​e não têm estado de exibição.


Para criar e usar o MethodHandle você precisa executar 4 etapas:


  1. Criar um descritor de pesquisa - pesquisa
  2. Declarar tipo de método
  3. Identificador do método de pesquisa
  4. Identificador do método de chamada

2.1 Método manipula vs reflexão


Os identificadores de método foram introduzidos para funcionar junto com a API java.lang.reflect , como Eles são criados para diferentes propósitos e diferem em suas características.


Em termos de desempenho, a API MethodHandles pode ser muito mais rápida que a API de reflexão, porque as verificações de acesso são realizadas no momento da criação e não na execução . Se houver um gerenciador de segurança, essa diferença aumenta, porque a procura de classes e a obtenção de seus elementos estão sujeitas a verificações adicionais.


No entanto, o desempenho não é o único indicador da otimização de uma tarefa; deve-se ter em mente que a API MethodHandles é mais difícil de usar devido à falta de mecanismos, como a obtenção de métodos de classe, verificação de tokens de acesso etc.


Apesar disso, a API MethodHandles permite métodos de curry, alterando o tipo e a ordem dos parâmetros.


Agora, conhecendo a definição e o objetivo da API MethodHandles, podemos trabalhar com eles. Vamos começar procurando métodos.


3. Criando pesquisa


A primeira coisa a fazer quando queremos criar um identificador de método é obter uma pesquisa, o objeto de fábrica responsável por criar identificadores de método para métodos, construtores e campos visíveis para a classe de pesquisa.


Usando a API MethodHandles, você pode criar um objeto de pesquisa com diferentes modos de acesso.


Crie uma pesquisa que forneça acesso a métodos públicos:


 MethodHandles.Lookup publicLookup = MethodHandles.publicLookup(); 

No entanto, se precisarmos acessar os métodos privado e protegido, podemos usar o método lookup ():


 MethodHandles.Lookup lookup = MethodHandles.lookup(); 

4. Criando um MethodType


Para criar um MethodHandle, o objeto de pesquisa deve ser definido como um tipo, e isso pode ser feito usando a classe MethodType.


Em particular, um MethodType representa os argumentos e o tipo de retorno aceitos e retornados pelo identificador do método, ou transmitidos e esperados pelo código de chamada.


A estrutura MethodType é simples, é formada pelo tipo de retorno, juntamente com o número correspondente de tipos de parâmetros, que devem ser completamente correlacionados entre o identificador do método e o código de chamada.


Como MethodHandle, todas as instâncias de MethodType são imutáveis.


Vamos ver como definir um MethodType que define a classe java.util.List como o tipo de retorno e a matriz Object como o tipo de entrada de dados:


 MethodType mt = MethodType.methodType(List.class, Object[].class); 

Caso o método retorne um tipo de valor simples ou nulo, usamos uma classe que representa esses tipos (void.class, int.class …) .


Defina um MethodType que retorna um int e aceita um Object:


 MethodType mt = MethodType.methodType(int.class, Object.class); 

Você pode começar a criar o MethodHandle.


5. Método de Pesquisa


Depois de definir o tipo do método, para criar um MethodHandle, você precisa encontrá-lo usando o objeto de pesquisa ou publicLookup, que também retorna a classe de origem e o nome do método.


A pesquisa fornece um conjunto de métodos que permite encontrar a manipulação do método de maneira ideal, levando em consideração o escopo do método. Considere as abordagens básicas, começando pela mais simples.


5.1 Identificador de método para métodos


Usando o método findVirtual() , você pode criar um MethodHandle para um método de instância. Crie-o com base no método concat() da classe String :


 MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt); 

5.2 Identificador de método para métodos estáticos


Para acessar o método estático, você pode usar o método findStatic() :


 MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asListMH = publicLookup.findStatic(Arrays.class, "asList", mt); 

Nesse caso, criamos um identificador de método para um método que converte uma matriz do tipo Object em uma List .


5.3 Identificador de método para construtores


Você pode acessar o construtor usando o método findConstructor() .


Crie um identificador de método com um comportamento semelhante ao construtor da classe Integer com o parâmetro String:


 MethodType mt = MethodType.methodType(void.class, String.class); MethodHandle newIntegerMH = publicLookup.findConstructor(Integer.class, mt); 

5.4 Identificador de método para campos


Usando o identificador do método, você também pode acessar os campos.


Vamos começar definindo a classe Book:


 public class Book { String id; String title; // constructor } 

Como condição inicial, temos visibilidade direta entre o identificador do método e a propriedade declarada, para que possamos criar um identificador de método com comportamento semelhante ao de um método get:


 MethodHandle getTitleMH = lookup.findGetter(Book.class, "title", String.class); 

Para obter mais informações sobre gerenciamento de variáveis ​​/ campos, consulte o artigo Java 9 Variable Handles Demystified , onde falamos sobre a API java.lang.invoke.VarHandle introduzida no Java 9.


5.5 Identificador de método para métodos particulares


Você pode criar um identificador de método para um método do tipo private usando a API java.lang.reflect .
Vamos começar criando um método privado para a classe Book:


 private String formatBook() { return id + " > " + title; } 

Agora podemos criar um identificador de método com o comportamento do método formatBook() :


 Method formatBookMethod = Book.class.getDeclaredMethod("formatBook"); formatBookMethod.setAccessible(true); MethodHandle formatBookMH = lookup.unreflect(formatBookMethod); 

6. Identificador do método de chamada


Depois de criar nosso identificador de método, prossiga para a próxima etapa. A classe MethodHandle nos fornece três maneiras diferentes de chamar o identificador do método: invoke() , invokeWithArugments() e invokeExact() .


Vamos começar com o método invoke .


6.1 Identificador do método de chamada


Ao usar o método invoke() , o número de argumentos (arity) é fixo, mas é possível executar a conversão de tipos e empacotar / descompactar os argumentos e tipos do valor de retorno.


Agora vamos ver como invoke() pode ser usado com um argumento compactado:


 MethodType mt = MethodType.methodType(String.class, char.class, char.class); MethodHandle replaceMH = publicLookup.findVirtual(String.class, "replace", mt); String output = (String) replaceMH.invoke("jovo", Character.valueOf('o'), 'a'); assertEquals("java", output); 

Nesse caso, replaceMH requer argumentos char , mas o método invoke() descompacta o argumento Character antes de executar.


6.2 Ligar com argumentos


A chamada de identificador de método usando invokeWithArguments tem a menor restrição.


De fato, além de verificar tipos e argumentos de empacotamento / desempacotamento e valores de retorno, ele permite fazer chamadas com um número variável de parâmetros.


Na prática, podemos criar uma lista Inteira com uma matriz de valores int de comprimento desconhecido:


 MethodType mt = MethodType.methodType(List.class, Object[].class); MethodHandle asList = publicLookup.findStatic(Arrays.class, "asList", mt); List<Integer> list = (List<Integer>) asList.invokeWithArguments(1, 2); assertThat(Arrays.asList(1,2), is(list)); 

6.3 Chamada exata


Se precisarmos que o identificador do método seja executado de forma mais restritiva (pelo conjunto de argumentos e seu tipo), usaremos o método invokeExact() .


De fato, ele não fornece a capacidade de converter tipos de uma classe e requer um conjunto fixo de argumentos.


Vamos ver como podemos adicionar dois valores int usando o identificador de método:


 MethodType mt = MethodType.methodType(int.class, int.class, int.class); MethodHandle sumMH = lookup.findStatic(Integer.class, "sum", mt); int sum = (int) sumMH.invokeExact(1, 11); assertEquals(12, sum); 

Nesse caso, se passarmos um número que não é um int para o método invokeExact , quando chamamos, obtemos uma WrongMethodTypeException .


7. Trabalhar com matrizes


MethodHandles pode funcionar não apenas com campos e objetos, mas também com matrizes. Usando a API asSpreader() , você pode criar um identificador de método que suporte matrizes como argumentos posicionais.


Nesse caso, o identificador do método pega uma matriz, distribuindo seus elementos como argumentos posicionais e, opcionalmente, o comprimento da matriz.


Vamos ver como obter o identificador do método para verificar se os argumentos da matriz são da mesma string:


 MethodType mt = MethodType.methodType(boolean.class, Object.class); MethodHandle equals = publicLookup.findVirtual(String.class, "equals", mt); MethodHandle methodHandle = equals.asSpreader(Object[].class, 2); assertTrue((boolean) methodHandle.invoke(new Object[] { "java", "java" })); 

8. Esclarecimento do identificador do método


Depois que o identificador do método estiver definido, você poderá refiná-lo vinculando o argumento, sem chamar o método.


Por exemplo, no Java 9, esse truque é usado para otimizar a concatenação de cadeias.


Vamos ver como a concatenação pode ser feita anexando o sufixo ao concatMH :


 MethodType mt = MethodType.methodType(String.class, String.class); MethodHandle concatMH = publicLookup.findVirtual(String.class, "concat", mt); MethodHandle bindedConcatMH = concatMH.bindTo("Hello "); assertEquals("Hello World!", bindedConcatMH.invoke("World!")); 

9. atualizações do Java 9


O Java 9 introduziu várias alterações na API MethodHandles para facilitar o uso.


As atualizações dizem respeito a três aspectos principais:


  • Funções de pesquisa - permitem pesquisar em diferentes contextos e suportam métodos não abstratos em interfaces.
  • Operações com argumentos - melhorando a funcionalidade de dobrar, coletar e distribuir argumentos.
  • Combinações adicionais estão adicionando operações de loop ( loop , whileLoop , doWhileLoop , ...) e gerenciamento aprimorado de exceções com tryFinally .

Essas mudanças envolveram outras inovações úteis:


  • Otimização aprimorada do compilador JVM
  • Instância diminuída
  • Concretização do uso da API MethodHandles

Uma lista mais detalhada de alterações está disponível na API Javadoc MethodHandles .


10. Conclusão


Neste artigo, nos reunimos com a API MethodHandles e também aprendemos o que são manipuladores de métodos e como usá-los.


Também descrevemos como ele está associado à API de reflexão. Como chamar identificadores de método é uma operação de baixo nível, seu uso é justificado apenas se eles se ajustarem exatamente às suas tarefas.


Como sempre, todo o código fonte do artigo está disponível no Github .

Source: https://habr.com/ru/post/pt431922/


All Articles