Executando verificação de segurança...
1

Parametrização de Comportamento com Java - Parte II

Lidando com a verbosidade

Parte I deste artigo caso você não tenha lido ;D

Até aqui nosso código está bom, porém sempre é possível melhorar, deste ponto em diante é importantíssimo que conceitos como Classes Anônimas e Polimorfismo estejam bem claros, se esse não é o seu caso ou se você ainda tem dúvidas nesses conceitos não tem problema, pode dar uma olhadinha nesses vídeos:

Universidade XTI - JAVA - 049 - Polimorfismo, Sobrescrita de Métodos

Universidade XTI - JAVA - 050 - Polimorfismo, Classes abstract

Universidade XTI - JAVA - 051 - Polimorfismo, Classes final

Universidade XTI - JAVA - 052 - Polimorfismo, Interfaces

Universidade XTI - JAVA - 098 - Classes Aninhadas e Anônimas

90 - Orientação Objetos - Polimorfismo pt 01 - Introdução

91 - Orientação Objetos - Polimorfismo pt 02 - Funcionamento

92 - Orientação Objetos - Polimorfismo pt 03 - Parâmetros polimórficos

94 - Orientação Objetos - Polimorfismo pt 05 - Programação orientada a interface

191 - Classes Internas pt 03 - Classes Anônimas

Curso Java Completo - Aula 64: Polimorfismo pt 01

Curso Java Completo - Aula 65: Polimorfismo pt 02

Curso Java Completo - Aula 66: Polimorfismo pt 03

Curso de Java 64: Classes aninhadas: internas, locais e anônimas

191 - Classes Internas pt 03 - Classes Anônimas

O problema do código que escrevemos até aqui é que ele ainda é muito verboso, temos de a todo tempo instanciar algo pra usar, fora que temos diversas regras separadas em rotinas diferentes.

Veja que temos que implementar diversas classes, depois instanciar cada uma delas o que gera um overhead completamente desnecessário pois o Java possui um mecanismo chamado de classes anonimas que nos permite delcarar e instanciar uma clase ao mesmo tempo.

Isso nos permite melhorar nosso código tornando ele mais conciso, mas ainda não satisfatório.

Utilizando Classes Anônimas

Uma classe anonima é uma classe comum mas que não tem um nome, essas classes nos permitem declarar e instancia-las ao mesmo tempo, o que nos permite criar implementações ad hoc.

Classes anônimas abordam um pouco a verbosidade associada à declaração de múltiplas classes concretas para uma interface, e isso ainda é insatisfatório para o que estamos buscando pois ainda teremos de criar um objeto e implementar um método explicitamente pra definir um novo comportamento.

Assim sendo, para lidar com esse problema da verbosidade, a partir do Java 8 foram implementadas as expressões lambda.

A boa notícia é que o nosso código está pronto para utilziar expressões lambda, teremos apenas de alterar a chamada:

public class Main {
  public static void main(String[] args) {

    List<Car> collection = asList(
      new Car(Color.RED, "Ferrari 488", "Ferrari", 661),
      new Car(Color.YELLOW, "Lamborghini Huracan", "Lamborghini", 602),
      new Car(Color.GRAY, "Porsche 911 GT3", "Porsche", 500),
      new Car(Color.BLACK, "McLaren 720S", "McLaren", 710),
      new Car(Color.RED, "Chevrolet Corvette", "Chevrolet", 495),
      new Car(Color.GRAY, "Audi R8", "Audi", 532),
      new Car(Color.RED, "Ford GT", "Ford", 660),
      new Car(Color.GRAY, "Bugatti Chiron", "Bugatti", 1479),
      new Car(Color.YELLOW, "Ferrari LaFerrari", "Ferrari", 950),
      new Car(Color.RED, "Lamborghini Aventador", "Lamborghini", 730)
    );

    List<Car> redCars = CarService.filterCars(collection, (Car car) -> Color.RED.equals(car.color()));
    System.out.println(redCars);
    
    CarService.printCar(collection, (Car car) -> car.model() + " is a car of " + car.hp() + " HPs");

  }
}

Veja quie nesses exemplo não precisamos mais instanciar os predicados como anteriormente, basta alterar a forma como chamamos os métodos.

Se você não está familiarizado ainda com expressões lambda não se preocupe em entender a sintaxe agora, apenas quero que você saiba que isso existe e que você saiba pra que serve, em breve vou escrever um próximo artigo dedicado especialmente as expressões lambda.

Utilizando conceitos de abstração e generics

Nosso código vem evoluindo bastante, porém veja que as nossas interfaces de predicado ainda são dependentes de um tipo específico

public interface CarPredicate {
  boolean test(Car car);
}

Ou seja cada vez que eu tenho um tipo diferente eu tenho de criar uma nova interface, porém o Java possui um feature chamada Generics que nos permite inferir tipos em algumas situações, então vamos fazer o seguinte vamos alterar um pouquinho o nosso predicado e dizer que ele deve esperar um tipo qualquer:

public interface Predicate<T> {
  boolean test(T t);
}

Agora não importa mais o tipo que eu passe e eu posso utilizar esse mesmo predicado pra tipos diferentes veja:

public class HighHPCarsPredicate implements Predicate<Car> {
  @Override
  public boolean test(Car car) {
    return car.hp() >= 900;
  }
}
public class RedCarOver700HP implements Predicate<Car> {
  @Override
  public boolean test(Car car) {
    return Color.RED.equals(car.color()) && car.hp() >= 700;
  }
}

Se no lugar de Car eu tivesse Fruit, ou qualquer outro tipo bastaria criar o predicado passando o tipo, por exemplo:

public class RedCarOver700HP implements Predicate<Fruit> {
  @Override
  public boolean test(Fruit fruit) {
    return Color.RED.equals(fruit.color()) && fruit.weight() >= 45;
  }
}

Mas não é só isso, agora que generalizamos a interface vamos para o método que a utiliza:

Veja que no momento temos diversos filtros:

public class CarService {

  public static List<Car> filterCarsByColor(List<Car> collection, Color color) {
    List<Car> filteredCars = new ArrayList<>();
    for (Car car : collection) {
      if (color.equals(car.color())) {
        filteredCars.add(car);
      }
    }
    return filteredCars;
  }

  public static List<Car> filterCarsByHP(List<Car> collection, Integer hp) {
    List<Car> filteredCars = new ArrayList<>();
    for (Car car : collection) {
      if (car.hp().equals(hp)) {
        filteredCars.add(car);
      }
    }
    return filteredCars;
  }

  public static List<Car> filterCars(List<Car> collection, CarPredicate predicate) {
    List<Car> filteredCars = new ArrayList<>();
    for (Car car : collection) {
      if (predicate.test(car)) {
        filteredCars.add(car);
      }
    }
    return filteredCars;
  }

  public static void printCar(List<Car> collection, CarFormatter formatter) {
    for (Car car : collection) {
      String output = formatter.accept(car);
      System.out.println(output);
    }
  }
}

Vamos trocar todos por apenas um chamado filter:

public class CarService {

  public static <T> List<T> filter(List<T> collection, Predicate<T> predicate) {
    List<T> filtered = new ArrayList<>();
    for (T t : collection) {
      if (predicate.test(t)) {
        filtered.add(t);
      }
    }
    return filtered;
  }

  public static void printCar(List<Car> collection, CarFormatter formatter) {
    for (Car car : collection) {
      String output = formatter.accept(car);
      System.out.println(output);
    }
  }
}

Note que utlizamos bastante de generics fazendo com que o método filter aceite qualquer tipo.

Agora podemos utilizar nosso filtro:

public class Main {
  public static void main(String[] args) {

    List<Car> collection = asList(
      new Car(Color.RED, "Ferrari 488", "Ferrari", 661),
      new Car(Color.YELLOW, "Lamborghini Huracan", "Lamborghini", 602),
      new Car(Color.GRAY, "Porsche 911 GT3", "Porsche", 500),
      new Car(Color.BLACK, "McLaren 720S", "McLaren", 710),
      new Car(Color.RED, "Chevrolet Corvette", "Chevrolet", 495),
      new Car(Color.GRAY, "Audi R8", "Audi", 532),
      new Car(Color.RED, "Ford GT", "Ford", 660),
      new Car(Color.GRAY, "Bugatti Chiron", "Bugatti", 1479),
      new Car(Color.YELLOW, "Ferrari LaFerrari", "Ferrari", 950),
      new Car(Color.RED, "Lamborghini Aventador", "Lamborghini", 730)
    );

    List<Car> redCars = CarService.filter(collection, (Car car) -> Color.RED.equals(car.color()));
    System.out.println(redCars);

  
  }
}

Note que o filtro agora funciona com carros e com qualquer outra coisa, por exemplo vamos filtrar os números pares em uma sequência:

public class Main {
  public static void main(String[] args) {

    List<Integer> numbers = asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);

    List<Integer> evenNumbers = filter(numbers, (Integer i) -> i % 2 == 0);

    System.out.println(evenNumbers);

  }
}

Ordenações

Por fim para fecharmos o assunto de Behavior Parametrization ou Parametrização de Comportamento vamos falar de ordenação.

Vamos dizer que queremos ordenar nossos carros pela potência de cada um, para cumprir essa tarefa vamos utilizar a interface Comparator

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
}

Então para ordenar os carros pelo HP podemos escrever o seguinte código:


public class Main {
  public static void main(String[] args) {

    List<Car> collection = asList(
      new Car(Color.RED, "Ferrari 488", "Ferrari", 661),
      new Car(Color.YELLOW, "Lamborghini Huracan", "Lamborghini", 602),
      new Car(Color.GRAY, "Porsche 911 GT3", "Porsche", 500),
      new Car(Color.BLACK, "McLaren 720S", "McLaren", 710),
      new Car(Color.RED, "Chevrolet Corvette", "Chevrolet", 495),
      new Car(Color.GRAY, "Audi R8", "Audi", 532),
      new Car(Color.RED, "Ford GT", "Ford", 660),
      new Car(Color.GRAY, "Bugatti Chiron", "Bugatti", 1479),
      new Car(Color.YELLOW, "Ferrari LaFerrari", "Ferrari", 950),
      new Car(Color.RED, "Lamborghini Aventador", "Lamborghini", 730)
    );

    System.out.println("Unordered: " + collection);

    collection.sort(new Comparator<Car>() {
      @Override
      public int compare(Car o1, Car o2) {
        return o1.hp().compareTo(o2.hp());
      }
    });

    System.out.println("Sorted: " + collection);
    
  }
}

Veja que Comparator funciona mais ou menos como o Predicate que criamos anteriormente, sendo assim podemos utilizar expressões lambda também para reduzir a verbosidade:

public class Main {
  public static void main(String[] args) {

    List<Car> collection = asList(
      new Car(Color.RED, "Ferrari 488", "Ferrari", 661),
      new Car(Color.YELLOW, "Lamborghini Huracan", "Lamborghini", 602),
      new Car(Color.GRAY, "Porsche 911 GT3", "Porsche", 500),
      new Car(Color.BLACK, "McLaren 720S", "McLaren", 710),
      new Car(Color.RED, "Chevrolet Corvette", "Chevrolet", 495),
      new Car(Color.GRAY, "Audi R8", "Audi", 532),
      new Car(Color.RED, "Ford GT", "Ford", 660),
      new Car(Color.GRAY, "Bugatti Chiron", "Bugatti", 1479),
      new Car(Color.YELLOW, "Ferrari LaFerrari", "Ferrari", 950),
      new Car(Color.RED, "Lamborghini Aventador", "Lamborghini", 730)
    );

    System.out.println("Unordered: " + collection);

    collection.sort((Car o1, Car o2) -> o1.hp().compareTo(o2.hp()));

    System.out.println("Sorted: " + collection);

  }
}

E podemos reduzir ainda mais utilizando o operador ::, chamado pra referênciar métodos - method reference - o qual vamos ver em artigos futuros, nossa chamada ficaria mais ou menos assim:

public class Main {
  public static void main(String[] args) {

    List<Car> collection = asList(
      new Car(Color.RED, "Ferrari 488", "Ferrari", 661),
      new Car(Color.YELLOW, "Lamborghini Huracan", "Lamborghini", 602),
      new Car(Color.GRAY, "Porsche 911 GT3", "Porsche", 500),
      new Car(Color.BLACK, "McLaren 720S", "McLaren", 710),
      new Car(Color.RED, "Chevrolet Corvette", "Chevrolet", 495),
      new Car(Color.GRAY, "Audi R8", "Audi", 532),
      new Car(Color.RED, "Ford GT", "Ford", 660),
      new Car(Color.GRAY, "Bugatti Chiron", "Bugatti", 1479),
      new Car(Color.YELLOW, "Ferrari LaFerrari", "Ferrari", 950),
      new Car(Color.RED, "Lamborghini Aventador", "Lamborghini", 730)
    );

    System.out.println("Unordered: " + collection);

    collection.sort(Comparator.comparing(Car::hp));

    System.out.println("Sorted: " + collection);

  }
}

Bem mais fácil de ler e entender que estamos comparando o hp dos carros em questão para fazer a ordenação.

Bom pessoal nesse artigo vimos como parametrizar nosso métodos pelo comportamento das nossas aplicações, no próximo vamos falar de expressões lambda.

Carregando publicação patrocinada...