Java 8 trouxe mudanças nas interfaces, incluindo métodos estáticos e métodos padrão. Antes do Java 8, só podíamos ter declarações de método nas interfaces. Mas a partir do Java 8, podemos ter métodos padrão e estáticos nas interfaces.
Interface Java 8
Projetar interfaces sempre foi um trabalho difícil porque se quisermos adicionar métodos adicionais nas interfaces, será necessário fazer alterações em todas as classes que as implementam. À medida que a interface envelhece, o número de classes que a implementam pode crescer a ponto de não ser possível estender as interfaces. Por isso, ao projetar uma aplicação, a maioria dos frameworks fornece uma classe de implementação base e então a estendemos e sobrescrevemos os métodos que são aplicáveis para nossa aplicação. Vamos analisar os métodos de interface padrão e os métodos de interface estática e a razão de sua introdução nas mudanças de interface do Java 8.
Método Padrão da Interface Java
Para criar um método padrão em uma interface Java, precisamos usar a palavra-chave “default” com a assinatura do método. Por exemplo,
package com.journaldev.java8.defaultmethod;
public interface Interface1 {
void method1(String str);
default void log(String str){
System.out.println("I1 logging::"+str);
}
}
Observe que log(String str) é o método padrão na Interface1
. Agora, quando uma classe implementar Interface1, não é obrigatório fornecer uma implementação para os métodos padrão da interface. Essa característica nos ajudará a estender interfaces com métodos adicionais, tudo o que precisamos é fornecer uma implementação padrão. Digamos que temos outra interface com os seguintes métodos:
package com.journaldev.java8.defaultmethod;
public interface Interface2 {
void method2();
default void log(String str){
System.out.println("I2 logging::"+str);
}
}
Sabemos que o Java não nos permite estender múltiplas classes porque resultará no “Problema do Diamante”, onde o compilador não pode decidir qual método da superclasse usar. Com os métodos padrão, o problema do diamante também surgiria para interfaces. Porque se uma classe estiver implementando tanto Interface1
quanto Interface2
e não implementar o método padrão comum, o compilador não poderá decidir qual escolher. Estender múltiplas interfaces é uma parte integral do Java, você encontrará isso nas classes Java principais, bem como na maioria das aplicações empresariais e frameworks. Então, para garantir que esse problema não ocorra em interfaces, é obrigatório fornecer uma implementação para os métodos padrão comuns das interfaces. Portanto, se uma classe estiver implementando ambas as interfaces acima, ela terá que fornecer uma implementação para o método log()
caso contrário, o compilador lançará um erro de tempo de compilação. Uma classe simples que está implementando tanto Interface1
quanto Interface2
será:
package com.journaldev.java8.defaultmethod;
public class MyClass implements Interface1, Interface2 {
@Override
public void method2() {
}
@Override
public void method1(String str) {
}
@Override
public void log(String str){
System.out.println("MyClass logging::"+str);
Interface1.print("abc");
}
}
Pontos importantes sobre os métodos padrão de interface Java:
- Métodos padrão de interface Java nos ajudarão a estender interfaces sem o receio de quebrar as classes de implementação.
- Métodos padrão de interface Java têm reduzido as diferenças entre interfaces e classes abstratas.
- Os métodos padrão de interface do Java 8 nos ajudarão a evitar classes utilitárias, como todos os métodos da classe Collections podem ser fornecidos nas próprias interfaces.
- Os métodos padrão de interface Java nos ajudarão a remover classes de implementação base, podemos fornecer uma implementação padrão e as classes de implementação podem escolher qual delas substituir.
- Uma das principais razões para a introdução de métodos padrão em interfaces é aprimorar a API de coleções no Java 8 para suportar expressões lambda.
- Se alguma classe na hierarquia tiver um método com a mesma assinatura, então os métodos padrão se tornam irrelevantes. Um método padrão não pode substituir um método de
java.lang.Object
. A razão é muito simples, é porque Object é a classe base para todas as classes Java. Portanto, mesmo se tivermos métodos de classe Object definidos como métodos padrão nas interfaces, será inútil porque o método de classe Object sempre será utilizado. É por isso que, para evitar confusão, não podemos ter métodos padrão que substituam os métodos de classe Object. - Métodos padrão de interface Java também são referidos como Métodos Defensores ou Métodos de Extensão Virtual.
Método Estático de Interface Java
O método estático da interface Java é semelhante ao método padrão, exceto que não podemos substituí-los nas classes de implementação. Essa característica nos ajuda a evitar resultados indesejados em caso de implementação inadequada nas classes de implementação. Vamos analisar isso com um exemplo simples.
package com.journaldev.java8.staticmethod;
public interface MyData {
default void print(String str) {
if (!isNull(str))
System.out.println("MyData Print::" + str);
}
static boolean isNull(String str) {
System.out.println("Interface Null Check");
return str == null ? true : "".equals(str) ? true : false;
}
}
Agora vamos ver uma classe de implementação que possui o método isNull() com uma implementação inadequada.
package com.journaldev.java8.staticmethod;
public class MyDataImpl implements MyData {
public boolean isNull(String str) {
System.out.println("Impl Null Check");
return str == null ? true : false;
}
public static void main(String args[]){
MyDataImpl obj = new MyDataImpl();
obj.print("");
obj.isNull("abc");
}
}
Observe que isNull(String str)
é um método de classe simples, não está substituindo o método da interface. Por exemplo, se adicionarmos a anotação @Override ao método isNull(), resultará em erro de compilação. Agora, ao executarmos a aplicação, obtemos a seguinte saída.
Interface Null Check
Impl Null Check
Se tornarmos o método da interface de estático para padrão, obteremos a seguinte saída.
Impl Null Check
MyData Print::
Impl Null Check
O método estático da interface Java é visível apenas para métodos da interface, se removermos o método isNull() da classe MyDataImpl
, não poderemos usá-lo para o objeto MyDataImpl
. No entanto, como outros métodos estáticos, podemos usar métodos estáticos da interface usando o nome da classe. Por exemplo, uma declaração válida será:
boolean result = MyData.isNull("abc");
Pontos importantes sobre o método estático da interface Java:
- O método estático da interface Java faz parte da interface, não podemos usá-lo para objetos de classe de implementação.
- Os métodos estáticos da interface Java são bons para fornecer métodos utilitários, por exemplo, verificação de nulo, ordenação de coleções etc.
- O método estático de uma interface Java nos ajuda a fornecer segurança, não permitindo que as classes de implementação os substituam.
- Não podemos definir um método estático de interface para métodos da classe Object, receberemos um erro do compilador dizendo “Este método estático não pode ocultar o método de instância de Object”. Isso ocorre porque não é permitido em Java, uma vez que Object é a classe base para todas as classes e não podemos ter um método estático de nível de classe e outro método de instância com a mesma assinatura.
- Podemos usar métodos estáticos de interface Java para remover classes utilitárias como Collections e mover todos os seus métodos estáticos para a interface correspondente, o que seria fácil de encontrar e usar.
Interfaces Funcionais Java
Antes de concluir a postagem, gostaria de fornecer uma breve introdução às Interfaces Funcionais. Uma interface com exatamente um método abstrato é conhecida como Interface Funcional. Uma nova anotação @FunctionalInterface foi introduzida para marcar uma interface como Interface Funcional. A anotação @FunctionalInterface é uma facilidade para evitar a adição acidental de métodos abstratos nas interfaces funcionais. É opcional, mas é uma boa prática usá-la. As interfaces funcionais são uma característica muito aguardada do Java 8, pois nos permite usar expressões lambda para instanciá-las. Um novo pacote java.util.function
com um conjunto de interfaces funcionais foi adicionado para fornecer tipos-alvo para expressões lambda e referências de método. Vamos explorar as interfaces funcionais e as expressões lambda nas postagens futuras.