Back-End

21 jan, 2015

Métodos padrão: os heróis não divulgados do Java 8

Publicidade

No ano passado, escrevi um artigo dizendo que os desenvolvedores aprendem novas linguagens porque elas são legais. Eu ainda mantenho essa afirmação, porque um fato a ser destacado no o Java 8 é que ele é muito legal. Enquanto as estrelas do show são a adição de lambdas e a promoção de funções para as variáveis de primeira classe, o meu atual favorito são os métodos padrão. Isso porque eles são uma maneira muito elegante de adicionar novas funcionalidades às interfaces existentes sem quebrar o código antigo.

A implementação é simples: pegue uma interface, adicione um método concreto e anexe a palavra-chave default como um modificador. O resultado é que, de repente, todas as implementações existentes de sua interface podem usar esse código. Neste primeiro exemplo simples, eu adicionei o método padrão que retorna o número de versão de uma interface.

public interface Version {

  /**
   * Normal method - any old interface method:
   *
   * @return Return the implementing class's version
   */
  public String version();

  /**
   * Default method example.
   *
   * @return Return the version of this interface
   */
  default String interfaceVersion() {
    return "1.0";
  }

}

Você pode, então, chamar esse método em qualquer classe de implementação.

public class VersionImpl implements Version {

  @Override
  public String version() {
    return "My Version Impl";
  }
}

Você pode perguntar: por que isso é legal? Se você pegar a interface java.lang.Iterable e adicionar o método padrão, você obtém a morte do loop for.

  default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
      action.accept(t);
    }
  }

O método forEach recebe uma instância de uma classe que implementa a interface Consumer<T> como um argumento. O Consumer<T> pode ser encontrado no novo pacote java.util.function e é o que o Java 8 chama de interface funcional, que é uma interface contendo somente um método. Nesse caso, é o método accept (T t) que tem um argumento e um retorno void.

O pacote java.util.function é provavelmente um dos pacotes mais importantes no Java 8. Ele contém um monte de métodos únicos, ou funcionais, interfaces que descrevem os tipos de funções comuns. Por exemplo, Consumer<T> contém uma função que recebe um argumento e tem um retorno void, enquanto Predicate<T> é uma interface com uma função que recebe um argumento e retorna um boolean, que geralmente é usado para escrever lambdas de filtragem.

A implementação dessa interface deve conter o que quer que você tenha escrito anteriormente entre os seus colchetes para loops.

Então, você pode pensar, o que isso me dá? Se isso não fosse Java 8, a resposta seria “não muito”. Para usar o método forEach (…) pré-Java 8, você precisa escrever algo assim:

    List<String> list = Arrays.asList(new String[] { "A", "FirsT", "DefaulT", "LisT" });

    System.out.println("Java 6 version - anonymous class");
    Consumer<String> consumer = new Consumer<String>() {

      @Override
      public void accept(String t) {
        System.out.println(t);
      }
    };

    list.forEach(consumer);

Mas, se você combinar isso com expressões lambda ou referências de métodos, você tem a capacidade de escrever alguns códigos que parecem realmente legais. Ao utilizar um método de referência, o exemplo anterior vira:

list.forEach(System.out::println);

Você pode fazer a mesma coisa com a expressão lambda:

list.forEach((t) -> System.out.println(t));

Tudo isso parece estar em sintonia com uma das grandes ideias por trás do Java 8: deixar o JDK fazer o trabalho para você. Parafraseando o político e namorador em série John F Kennedy: “não pergunte o que você pode fazer com o seu JDK. Pergunte o que o seu JDK pode fazer por você”.

Problemas de design dos métodos padrão

Essa é a nova maneira legal de escrever o ubíquo loop for, mas há problemas com a adição de métodos padrão para interfaces? Em caso afirmativo, o que eles são e como é que os caras no projeto do Java 8 os corrigem?

A primeira coisa se considerar é a herança. O que acontece quando você tem uma interface que estende outra interface e ambas têm um método padrão com a mesma assinatura? Por exemplo, o que acontece se você tiver SuperInterface estendida por MiddleInterface, e MiddleInterface estendida por SubInterface?

public interface SuperInterface {

  default void printName() {
    System.out.println("SUPERINTERFACE");
  }
}
public interface MiddleInterface extends SuperInterface {

  @Override
  default void printName() {
    System.out.println("MIDDLEINTERFACE");
  }
}
public interface SubInterface extends MiddleInterface {

  @Override
  default void printName() {
    System.out.println("SUBINTERFACE");
  }
}
public class Implementation implements SubInterface {

  public void anyOldMethod() {
    // Do something here
  }

  public static void main(String[] args) {

    SubInterface sub = new Implementation();
    sub.printName();

    MiddleInterface middle = new Implementation();
    middle.printName();

    SuperInterface sup = new Implementation();
    sup.printName();
  }
}

Não importa o caminho que você escolhe, printName() sempre imprime “SUBINTERFACE”.

A mesma questão surge quando você tem uma classe e uma interface contendo a mesma assinatura do método: qual método é executado? A resposta é a regra ‘vitórias de classe’. Os métodos padrão de interface serão sempre ignorados em favor dos métodos de classe.

public interface AnyInterface {

  default String someMethod() {
    return "This is the interface";
  }
}
public class AnyClass implements AnyInterface {

  @Override
  public String someMethod() {
    return "This is the class - WINNING";
  }

}

Executar o código acima vai sempre imprimir: “This is the class – WINNING”

Finalmente, o que acontece se uma classe implementa duas interfaces e ambas contêm métodos com a mesma assinatura? Esse é o problema milenar do C++; como você resolve a ambiguidade? Qual método é executado?

public interface SuperInterface {

  default void printName() {
    System.out.println("SUPERINTERFACE");
  }
}
public interface AnotherSuperInterface {

  default void printName() {
    System.out.println("ANOTHERSUPERINTERFACE");
  }
}

No caso do Java 8, a resposta é nada. Se você tentar implementar as duas interfaces, obterá o seguinte erro:

Duplicate default methods named printName with the parameters () and () are inherited from the types AnotherSuperInterface and SuperInterface.

No caso em que é absolutamente necessário implementar ambas as interfaces, a solução é invocar a regra ‘a classe vence’ e substituir o método ambíguo em sua implementação.

public class Diamond implements SuperInterface, AnotherSuperInterface {

  /** Added to resolve ambiguity */
  @Override
  public void printName() {
    System.out.println("CLASS WINS");
  }

  public static void main(String[] args) {

    Diamond instance = new Diamond();
    instance.printName();
  }

}

Quando usar os métodos padrão

De um ponto de vista purista, a adição de métodos padrão significa que as interfaces Java não são mais interfaces. As interfaces foram concebidas como uma especificação ou um contrato para um comportamento proposto/desejado: um contrato que a classe de implementação deve cumprir. A adição dos métodos padrão significa que não há virtualmente nenhuma diferença entre as interfaces e classes base. Isso significa que eles estão abertas a abusos, já que alguns desenvolvedores inexperientes podem pensar que é legal arrancar classes de base de sua base de código e substituí-las com interfaces baseadas no método padrão – apenas porque eles podem, enquanto outros podem simplesmente confundir as classes abstratas com interfaces implementando os métodos padrão. Atualmente, eu sugiro usar métodos padrão exclusivamente para seu caso de uso pretendido: evolução de interfaces herdadas sem quebrar o código existente. Embora eu possa mudar de ideia.

***

Texto traduzido com autorização do autor pela Redação iMasters. Original disponível em http://www.captaindebug.com/2014/07/default-methods-java-8s-unsung-heros.html#.VKvoxNLF9pt