Seja bem-vindo a série de postagens sobre a certificação Java. Como funciona, o que fazer para comprar, marcar o dia da prova e o principal, o que estudar.
Para ver o índice da série e as datas das publicações, acesse este link
Parte 5 – Métodos e encapsulamento
Neste post, vamos nos aprofundar um pouco mais na orientação a objetos e ver como funcionam os métodos, seus modificadores, sobrecarga e um pouco de lambdas.
Objetivos do exame
- Criando métodos que retornam valores
- Sobrecarga de métodos
- Métodos estáticos
- Modificadores de acesso
- Encapsulamento
- Passagem de valor e passagem de parâmetro
- Escrevendo expressões lambda simples
Criando métodos
Um método pode conter até sete elementos, sendo que alguns obrigatórios e outros opcionais. No exemplo abaixo o método conhecido de todo programador Java:
public static void main(String[] args) {
System.out.println("hello!!");
}
O método acima contém seis elementos, onde o sétimo seria uma lista de exceções.
public static void main(String[] args) throws Exception {
System.out.println("hello!!");
}
Agora nosso método main pode propagar uma exceção, porém sem necessidade neste caso. Exceptions serão explicadas posteriormente. Voltando para o foco dos elementos do método, temos o seguinte:
Tipo do Elemento
|
Nome do Elemento
|
Obrigatório
|
Modificador
|
public
|
Não
|
Modificador opcional
|
static
|
Não
|
Tipo de retorno
|
void
|
Sim
|
Nome do método
|
main
|
Sim
|
Lista de parâmetros
|
(String[] args)
|
Sim. Aceita ( )
|
Lista de exceções
|
throws Exception
|
Não
|
Corpo
|
{ System.out.println("hello!!");}
|
Sim. Aceita { }
|
Esta é a definição do método main do Java, porém quando escrevemos nossos próprios métodos, teremos algo como:
public void printName(){
System.out.println(this.name);
}
Modificadores de acesso
Agora, precisamos entender como funcionam os modificadores de acesso. Começando com os tipos de modificadores:
public: O método pode ser chamado por qualquer outra classe.
private: O método pode ser acessado somente dentro da própria classe.
private: O método pode ser acessado somente dentro da própria classe.
protected: O método só pode ser chamado de classes no mesmo pacote ou subclasses.
“Default”: O método só pode ser chamado de classes no mesmo pacote. Não é necessário o colocar o modificador no método.
No exame é possível que a ordem dos elementos esteja incorreta ou sejam utilizados palavras reservadas não compatíveis com a assinatura do método, Ex:
default void printName(){ // não compila
System.out.println(this.name);
}
default é uma palavra reservada para o uso em switch case ou em interfaces.
void public printName(){ // não compila
System.out.println(this.name);
}
void printName(){}
Modificadores opcionais
Além dos modificadores de acesso, podemos utilizar os modificadores opcionais. Eles também farão com que o seu método mude a forma de acesso e podem ser combinados. Abaixo a lista de modificadores que aparecem na prova.
static: Métodos de classe. Podem ser invocados sem necessidade de criar uma instância.
abstract: Utilizado quando o método não provém uma implementação.
final: Utilizado com herança para não permitir a sobrescrita do método.
abstract: Utilizado quando o método não provém uma implementação.
final: Utilizado com herança para não permitir a sobrescrita do método.
Tipos de retorno
O retorno de um método pode ser qualquer tipo de objeto ou primitivo. Caso o método não possua um retorno, devemos utilizar void. Caso for definido algum tipo de retorno diferente de void, então no corpo do método deverá ter obrigatoriamente um return.
Obs: métodos void também podem ter o return.
void printName(){
return; // OK
}
String getName(){
return; // não compila
}
String getName(){
return ""; // OK
}
Nome do método
Os nomes dos métodos seguem as mesmas regras que praticamos com nomes de variáveis. Um identificador só pode conter letras, números, $ ou _.
Lista de parâmetros
Embora a lista de parâmetros seja obrigatória, ela não precisa conter nenhum parâmetro. Basta abrir e fechar os parênteses. Para uma lista de parâmetros, basta separá-los por vírgula.
Lista de exceções
Por enquanto, basta saber que um método pode lançar nenhuma, uma ou mais exceções. Ex:
void printName(){}void printName() throws Exception{}
void printName() throws RuntimeException, Exception{}
Os três métodos acima são válidos. Em outros posts estudaremos mais a fundo as exceções.
Corpo do método
O corpo do método é simplesmente um bloco de código Java onde as coisas acontecem. O corpo está entre as chaves que são obrigatórias na declaração do método.
Varargs
Aqui você deve lembrar que o varargs deve ser o único e último parâmetro do método.
void printNames(String... names) {
System.out.println(names);
}
void printNames(String... names, String address) { // Não compila
void printNames(String... names, String... address) { // Não compila
void printNames(String address, String... names) { // OK
Para chamar um método que contém varargs, temos duas opções. Passando como um array ou uma lista de parâmetros separados por vírgula.
printNames("Joao, Maria, Isabel");
Ou:
printNames(new String[]{"Joao, Maria, Isabel"});
void printNames(String... names) {
Também é válido passar um array sem elementos para o método printNames()
Trabalhando com os modificadores de acesso
Do modo mais restritivo (private) ao modo mais acessível (public) temos o default e o protected que podem nos confundir um pouco. Em ordem de restrição temos: private, default, protected e public. Os modificadores de acesso são aplicados para métodos e atributos da classe.
Acesso default
Um método default (sem modificador) significa que o método será acessível apenas dentro do próprio pacote. Também conhecido como (Package Private Access). Dada a classe Animal abaixo:
package com.giacom.oca1.animal;
public class Animal {
private int age;
private String name;
public Animal(int age, String name) {
this.age = age;
this.name = name;
}
int getAge() {
return age;
}
}public class Animal {
private int age;
private String name;
public Animal(int age, String name) {
this.age = age;
this.name = name;
}
int getAge() {
return age;
}
Podemos invocar o método getAge() de outra classe no mesmo pacote:
package com.giacom.oca1.animal;
public class Fish {
public class Fish {
Animal animal = new Animal(12, "cat");
System.out.println(animal.getAge());
}
}
Caso mude a classe Fish de pacote, vamos precisar importar a classe Animal e o método getAge() não estará mais visível.
package com.giacom.oca1.animal.fish;
import com.giacom.oca1.animal.Animal;
public class Fish {
Animal animal = new Animal(12, "cat");
System.out.println(animal.getAge()); // Erro de compilação
}
}
Acesso protected
Os atributos e métodos protected permitem ser acessados também pelas suas subclasses, além das classes do mesmo pacote. Alterando a nossa classe Animal deixando o método getAge() como protected.
protected int getAge() {
return age;
}
return age;
}
Agora, além de dar acesso a classes do mesmo pacote, também estará acessível pelas classes que estendem de Animal independente do pacote que estejam.
public class Fish extends Animal {
public static void main(String[] args) {
Animal animal = new Animal(2, "fish");
System.out.println(animal.getAge());
}
}
public static void main(String[] args) {
Animal animal = new Animal(2, "fish");
System.out.println(animal.getAge());
}
}
Acesso público
Como o nome já indica, os membros declarados como public serão acessíveis por qualquer classe de qualquer pacote. Lembrando que se estiver em um pacote diferente, a classe deve ser importada.
public class Animal {
int age;
public String name;
this.age = age;
}
public int getAge() {
return age;
}
}
Tornando nossa classe Animal com acessos públicos. Agora qualquer classe em qualquer pacote pode utilizar os membros públicos da classe Animal.
animal.name = "fish";
System.out.println(animal.getAge());
Atributos e métodos estáticos
Membros estáticos não necessitam que um objeto seja instanciado. São considerados membros de classe e não de instância. Sendo assim, todas as instâncias e classes compartilham do mesmo membro independente de quantas instâncias existirem.
class Counter{
public static int count;
public static int count;
System.out.println(count);
}
}
A classe acima possui o atributo count com o modificador de acesso public static.
public static void main(String[] args) {
Counter counter1 = new Counter();
counter1.count++;
counter1.showCount();
Counter counter2 = new Counter();
counter2.count++;
counter2.showCount();
counter1.showCount();
}
Counter counter1 = new Counter();
counter1.count++;
counter1.showCount();
Counter counter2 = new Counter();
counter2.count++;
counter2.showCount();
counter1.showCount();
}
Você saberia responder o que será impresso ao ser executado o código acima? Veja que temos duas instâncias de Conter e cada uma adiciona um valor a count. Na última linha temos a chamada do método showCount() de counter1 que incrementou a variável apenas uma vez. Naturalmente o valor de count seria 1 se não fosse o modificador static. Com isso, todas as vezes que o count for modificado, todas as instâncias passam a ter o mesmo valor. Portanto a saída do código acima é: 1, 2, 2.
Membros estáticos podem ser invocados através da variável de instância ou pelo próprio nome da classe. Alterando nosso código, poderíamos ter: Counter.count++;
Preste atenção em um detalhe do Java. Como você pode invocar um membro estático através de uma variável de instância, o Java entende o membro estático e realiza a chamada como se fosse utilizar o próprio nome da classe. Ficou confuso? Vamos alterar nosso código:
Counter counter1 = null;
counter1.count++;
Counter counter2 = new Counter();
counter2.count++;
counter2.showCount();
Veja que counter1 é null e mesmo assim podemos incrementar a variável count. Por mais estranho que pareça, o código acima compila e a saída é 2.
Estático versus Instância
Um membro estático somente pode acessar outros membros estáticos. Caso tente acessar uma variável ou método de instância, seu código não irá compilar. Ex:
String name;
public static void main(String[] args) {
System.out.println(name);// Erro de compilação
}
public static void main(String[] args) {
System.out.println(name);// Erro de compilação
}
Mensagem do compilador: Error:(8, 28) java: non-static variable name cannot be referenced from a static context.
Obs: No exame é comum uma pergunta do tipo: O que será preciso alterar para que o código compile? Neste caso, podemos incluir o modificador static na variável name.
Modificador final
Muito comum na utilização de constantes, indica que uma vez inicializada, a variável não pode ser alterada. Qualquer tentativa de alteração irá resultar em um erro de compilação.
private static final String NAME = "Name";
public static void main(String[] args) {
NAME = "New Name"; // Erro de compilação
}
public static void main(String[] args) {
NAME = "New Name"; // Erro de compilação
}
Inicializando estáticos
Podemos inicializar variáveis estáticas de duas formas. No momento da declaração ou em chamados blocos estáticos.
private static String name;
static {
name = "Name";
}
public static void main(String[] args) {
System.out.println(name);
}
static {
name = "Name";
}
public static void main(String[] args) {
System.out.println(name);
}
Blocos de inicialização podem ser colocados em qualquer parte da classe (inclusive dentro dos métodos), e isso pode nos confundir no momento da prova. O que o código abaixo imprime?
private static String name = "First";
static {
name = "Name";
}
public static void main(String[] args) {
System.out.println(name);
}
static {
name = "New Name";
}
static {
name = "Name";
}
public static void main(String[] args) {
System.out.println(name);
}
static {
name = "New Name";
}
Neste caso, o último bloco static será considerado e a saída será “New Name”. E agora, qual será a saída?
private static String name = "First";
{
name = "Name";
}
public static void main(String[] args) {
System.out.println(name);
}
{
name = "New Name";
}
{
name = "Name";
}
public static void main(String[] args) {
System.out.println(name);
}
{
name = "New Name";
}
A saída agora é "First" porque removemos os blocos de inicialização estáticos. Os blocos de inicialização agora são de instância.
Quando incluirmos o modificador final em uma variável estática, teremos que prestar mais atenção. Devemos verificar algumas regras básicas:
- Variáveis static final devem ser inicializadas.
- Não podemos ter mais de uma inicialização para a variável
private static final String name; //Erro de compilação
2) Inicialização em um bloco:
private static final String name;
static {
name = "ssss";
}
static {
name = "ssss";
}
3) Tentativa de inicializar mais de uma vez:
private static final String name;
static {
name = "Name";
name = "Last"; // Erro de compilação
}
static {
name = "Name";
name = "Last"; // Erro de compilação
}
Imports estáticos
Antes da existência dos imports estáticos, para utilizar um membro estático era necessário colocar o nome da classe ou variável de instância. Agora é possível reduzirmos um bocado de código redundante utilizando os imports estáticos.
double num = Math.random();
double exp = Math.getExponent(9.0);
double abs = Math.abs(100.05);
double exp = Math.getExponent(9.0);
double abs = Math.abs(100.05);
double num = random();
double exp = getExponent(9.0);
double abs = abs(100.05);
double exp = getExponent(9.0);
double abs = abs(100.05);
Nosso bloco de imports, teremos:
import static java.lang.Math.abs;
import static java.lang.Math.getExponent;
import static java.lang.Math.random;
import static java.lang.Math.getExponent;
import static java.lang.Math.random;
import static java.lang.Math.*;
Lembre-se de que o que deve ser importado é o membro estático e não a classe.
import static java.lang.Math; // Não compila
static import java.lang.Math.random; // Não compila
import static java.util.Arrays.*;
List<String> names = Arrays.asList("Lisa", "Amanda"); // Erro de compilação
Faltando o import de java.util.Arrays; (sem static)
Passagem por valor ou referência?
O Java suporta apenas passagem por valor, ou seja, ao receber um parâmetro no método, é feita uma cópia da variável do ponto que chamou o método.
public static void main(String[] args) {
String name = "Name";
changeName(name);
System.out.println(name); // print "Name"
}
static void changeName(String name) {
name = "Last";
}
String name = "Name";
changeName(name);
System.out.println(name); // print "Name"
}
static void changeName(String name) {
name = "Last";
}
No código acima, por mais que a variável name tenha sido alterada no método changeName, ela permaneceu com o mesmo valor. O que foi alterada foi a sua cópia. Por coincidência o nome do parâmetro tem o mesmo nome. Mas isto não interfere em nada.
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
append(sb);
sb.append(" Name");
System.out.println(sb); // print "Last Name"
}
static void append(StringBuilder sb) {
sb.append("Last");
}
StringBuilder sb = new StringBuilder();
append(sb);
sb.append(" Name");
System.out.println(sb); // print "Last Name"
}
static void append(StringBuilder sb) {
sb.append("Last");
}
Porque este código imprime “Last Name”? Porque uma cópia da referência de StringBuilder é passada para o método. A variável original e a cópia apontam para o mesmo objeto, então a alteração é refletida em todas as variáveis que apontam para aquele objeto.
Sobrecarga de método
Podemos ter vários métodos com o mesmo nome na mesma classe, porém devemos ter uma lista de parâmetros diferentes. Isso se chama sobrecarga de método. Um exemplo clássico de sobrecarga de método é a classe StringBuilder:
Temos vários métodos append com tipos de parâmetros diferentes. Para o exame é preciso reconhecer se uma sobrecarga de método é válida ou não.
void showName(String name){
System.out.println(name);
}
String showName(String name){
return name;
}
System.out.println(name);
}
String showName(String name){
return name;
}
No exemplo acima, temos um problema. Por mais que o tipo de retorno seja diferente, temos o mesmo nome de método com os mesmos tipos de parâmetros. Neste caso a classe não irá compilar. Outra questão do exame é referente a qual método será chamado dependendo do valor passado na chamada.
public static void main(String[] args) {
showNumber(1);
}
static void showNumber(int num) {showNumber(1);
}
System.out.println("int");
}
static void showNumber(short num) {
System.out.println("short");
}
static void showNumber(byte num) {
System.out.println("byte");
}
Qual será a saída da execução do bloco de código acima? "int" pois estamos passando um literal “1” que é convertido automaticamente para int. Para que seja chamado o método correto conforme o tipo, é preciso deixar explícito o tipo. Passando uma variável do mesmo tipo ou fazendo a conversão no momento da chamada:
public static void main(String[] args) {
showNumber((byte)1); //print byte
showNumber((short)1); //print short
}
Ou:
public static void main(String[] args) {
byte b = 1;
showNumber(b); //print byte
}
showNumber((byte)1); //print byte
showNumber((short)1); //print short
}
Ou:
public static void main(String[] args) {
byte b = 1;
showNumber(b); //print byte
}
Sobrecarga com Varargs
Um parâmetro Varargs é convertido automaticamente para um Array, então cuidado quando se deparar com métodos como o abaixo:
static void showNumber(int... num) {
System.out.println("int");
}
System.out.println("int");
}
System.out.println("int");
}
O código acima não compila.
O que ocorre quando temos um método com parâmetro primitivo e a versão Object daquele tipo primitivo?
public static void main(String[] args) {
showNumber(1);
}
static void showNumber(int num) {
System.out.println("int");
}
static void showNumber(Integer num) {
System.out.println("Integer");
}
O código acima compila sem problemas e a saída é "int" pois o Java encontra o mesmo tipo e não precisará fazer a conversão para o tipo Objeto. Caso o método com o parâmetro primitivo não estivesse presente, o método com o parâmetro Integer seria chamado.
Construtores
Construtores são métodos especiais que tem o mesmo nome da classe e não possuem tipo de retorno. Abaixo um exemplo de construtor sem parâmetros:
public class Person {
public Person() {
System.out.println("Constructor...");
}
}
public Person() {
System.out.println("Constructor...");
}
}
O Java é case sensitive, então preste atenção se ver um método sem o tipo de retorno e com o mesmo nome da classe, porém com letras minúsculas ou qualquer coisa diferente do nome da classe. Se trata de um erro de compilação:
public person() {
System.out.println("invalid constructor");
}
System.out.println("invalid constructor");
}
O código acima não é um método válido pois não tem tipo de retorno e também não é um construtor pois não é igual ao nome da classe.
Caso você não saiba, o Java gera um construtor padrão quando o mesmo não for definido. Porém se você definir construtores com parâmetros, o construtor padrão não será gerado.
public class Person {
String name;
String age;
public Person(String name, String age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Person p = new Person(); //Erro de compilação
}
}
String name;
String age;
public Person(String name, String age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Person p = new Person(); //Erro de compilação
}
}
O código acima não compila, pois não temos um construtor sem parâmetros, apenas o construtor recebendo o name e o age.
Utilizamos construtores com parâmetros para inicializar nossos atributos da instância. Geralmente utilizamos a palavra chave this para nos referir ao atributo. Porém no exame é possível ver algo do tipo:
public Person(String name, String age) {
name = name;this.age = age;
}
Veja que a variável de instância “name” nunca será atribuída, pois o que está sendo manipulado é a variável local do método. Para resolver isso, pode-se alterar o nome do parâmetro ou utilizar o this.
public Person(String personName, int age) {
name = personName;
this.age = age;
}
name = personName;
this.age = age;
}
Agora a variável name será inicializada corretamente. Assim como os métodos, os construtores podem ser sobrecarregados e as regras para a sobrecarga são as mesmas.
Construtores podem chamar outros construtores? Sim, porém existem algumas regras. Não se pode utilizar a chamada a outro construtor como se fosse um método:
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(int age) {
Person("name", age); // Erro de compilação
}
this.name = name;
this.age = age;
}
public Person(int age) {
Person("name", age); // Erro de compilação
}
Um construtor somente pode ser utilizado na criação do objeto, ou seja, quando tivermos a palavra chave new:
new Person("name", age);
Por mais que o código acima compile dentro do construtor, ele não servirá para nada, pois não está sendo atribuído a nenhuma variável. O correto é utilizar a palavra reservada this:
public Person(int age) {
this("name", age);
}
this("name", age);
}
Obs: A chamada a outro construtor deve ser a primeira instrução do construtor, caso contrário, teremos um erro de compilação.
public Person(int age) {
System.out.println(age);
this("Name", age); // Não compila
}
System.out.println(age);
this("Name", age); // Não compila
}
Construtores privados
Utilizamos construtores privados quando queremos que a nossa classe não seja instanciada externamente. Isso para ter controle sobre as instâncias ou porque a nossa classe tem somente métodos estáticos:
public class Person {
private Person() {
}
}
class Schedule{
public static void main(String[] args) {
Person p = new Person(); // Erro de compilação
}
}
private Person() {
}
}
class Schedule{
public static void main(String[] args) {
Person p = new Person(); // Erro de compilação
}
}
Não é possível instanciar a classe a partir de outra. Apenas a própria classe poderá criar uma instância dela mesma.
Atributos final
Outra forma de inicializamos os atributos final, é através do construtor. Porém se o Java perceber que a variável pode não ser inicializada, teremos um erro de compilação. Como assim?
final String name; // Erro de compilação
int age;
int age;
this.name = name;
this.age = age;
}
public Person(int age) {
}
Observe que método Person(int age) não tem nada no corpo. Então se a classe for instanciada através deste construtor, a variável name ficará sem ser inicializada e isso não é permitido. Se deletarmos o método, a classe compila normalmente.
Ordem de inicialização
Devemos conhecer bem como será a ordem de inicialização de um objeto e ter isso bem claro no momento do exame. A ordem das chamadas é a seguinte:
- Blocos estáticos * da superclasse
- Blocos estáticos * da classe
- Blocos não estáticos * da superclasse
- Construtor da superclasse
- Blocos não estáticos * da classe
- Construtor da classe
Encapsulamento
Os métodos operam no estado interno de um objeto e servem como o mecanismo principal para a comunicação objeto-objeto. Esconder o estado interno e exigir que toda interação seja realizada através dos métodos de um objeto é conhecido como encapsulamento de dados - um princípio fundamental da programação orientada a objetos.
Com este conceito em mente, definimos nossos atributos com o acesso mais restrito possível (private) e então utilizamos os métodos para atribuir os valores e fazer as verificações necessária a fim de manter nosso objeto em um estado consistente.
private String name;
if (isValidName(name)) {
this.name = name;
}
}
Somente será possível alterar a variável name de fora da classe se você disponibilizar um método acessível e a sua regra de negócio permitir.
Lambdas
Java 8 adicionou o conceito de programação funcional através dos lambdas. Podemos pensar em uma expressão lambda como um método anônimo. Lambdas permitem que você escreva códigos poderosos em Java. Apenas as expressões lambda mais simples estão no exame e serão descritas nesta sessão. Para saber mais sobre lambdas:
Exemplos
Busca de pessoas idosas.
Neste exemplo, vamos criar a classe Person e uma interface para verificar se a pessoa é idosa:
public class Person {
private String name;
private int age;
private String name;
private int age;
this.name = name;
this.age = age;
}
}
public interface CheckPerson {
boolean isOld(Person p);
}
Interface CheckPerson com o método isOld.boolean isOld(Person p);
}
public class PersonSearch {
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Jose", 33));
people.add(new Person("Ane", 90));
people.add(new Person("Faria", 65));
printOld(people, new CheckPersonImpl());
}
static void printOld(List<Person> personList, CheckPerson cp) {
for (Person p : personList) {
if (cp.isOld(p)) {
System.out.println(p);
}
}
}
}
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Jose", 33));
people.add(new Person("Ane", 90));
people.add(new Person("Faria", 65));
printOld(people, new CheckPersonImpl());
}
static void printOld(List<Person> personList, CheckPerson cp) {
for (Person p : personList) {
if (cp.isOld(p)) {
System.out.println(p);
}
}
}
}
Nossa classe PersonSearch que imprime somente pessoas idosas conforme a regra implementada na classe CheckPersonImpl.
public class CheckPersonImpl implements CheckPerson {
@Override
public boolean isOld(Person p) {
return p.getAge() >= 80;
}
}
Implementação da interface CheckPerson@Override
public boolean isOld(Person p) {
return p.getAge() >= 80;
}
}
Com o uso de lambdas, podemos substituir a implementação da interface por uma expressão lambda.
Antes: printOld(people, new CheckPersonImpl());
Depois: printOld(people, cp -> cp.getAge() >= 80);
Com o uso da expressão, eliminamos a necessidade de implementar a interface. Pense no seguinte: O segundo parâmetro que recebe uma interface é simplesmente a implementação de um método em uma classe anônima, tal como:
printOld(people, new CheckPerson() {
@Override
public boolean isOld(Person p) {
return p.getAge() >= 80;
}
});
@Override
public boolean isOld(Person p) {
return p.getAge() >= 80;
}
});
Obs: Quando uma interface tem apenas um método, podemos substituir por uma expressão lambda.
Examinando a expressão
Nosso exemplo utilizando a expressão lambda:
cp -> cp.getAge() >= 80
A parte da direita é a declaração da variável (cp) e a parte da esquerda após o sinal -> é a implementação do método que retorna um boolean. Várias partes do lambda são opcionais. Poderíamos ter o mesmo código desta forma:
printOld(people, (Person cp) -> {
return cp.getAge() >= 80;
});
Partes da expressão
1 - Especifique um único parâmetro com o nome cp2 - O operador de seta -> para separar o parâmetro e o corpo
3 - Um corpo que chama um único método e retorna o resultado desse método
Caso a expressão tenha mais de um parâmetro, é preciso colocá los entre parênteses. Alteramos nossa interface:
boolean isOld(Person p, String address);
printOld(people, (person, address) -> person.getAge() >= 80);
Veja que por mais que não utilizamos a variável address, ela deve ser informada na expressão. Caso o método não possua parâmetros, devemos também utilizar os parênteses:
boolean isOld();
printOld(people, () -> true);
printOld(people, p -> {
Person p; // Erro de compilação
return p.getAge() >= 80;
});
Obs: Se você abrir um bloco de código utilizando as chaves e o método tiver um retorno, é obrigatório colocar um return no corpo da expressão:
printOld(people, p -> {
p.getAge() >= 80; // Erro de compilação
});
Expressão com corpo e sem return.p.getAge() >= 80; // Erro de compilação
});
Predicates
Lambdas trabalham com interfaces que possuem apenas um método e isso é conhecido como interfaces funcionais. Imagine agora que precisamos criar outras interfaces para utilizar com outros tipos de objetos, ex:
public interface CheckAnimal {
boolean isOld(Person p);
}
public interface Check<T> {
boolean isOld(T t);
}
static void printOld(List<Person> personList, Check<Person> c) {
for (Person p : personList) {
if (c.isOld(p)) {
System.out.println(p);
}
}
}
A chamada para o método continua a mesma:for (Person p : personList) {
if (c.isOld(p)) {
System.out.println(p);
}
}
}
printOld(people, p -> p.getAge() >= 80);
Porém ainda precisamos criar uma interface genérica para fazer esta operação. Pensando nisso, a API Java disponibiliza a interface funcional Predicate. Podemos trocar a nossa interface Check pela interface Predicate do Java que está no pacote “java.util.function”. Agora a assinatura do nosso método fica:
static void printOld(List<Person> personList, Predicate<Person> c)
O método da interface Predicate que realiza uma verificação é o test, então precisamos alterar também a nossa lógica para chamar o método test.
static void printOld(List<Person> personList, Predicate<Person> c) {
for (Person p : personList) {
if (c.test(p)) {
System.out.println(p);
}
}
}
for (Person p : personList) {
if (c.test(p)) {
System.out.println(p);
}
}
}
Método printOld alterado para utilizar interface funcional do Java.
A própria API do Java foi alterada para fazer uso dos predicates. A classe ArrayList é um exemplo disso e utiliza no método removeIf:
public boolean removeIf(Predicate<? super E> filter)
1: List<Person> people = new ArrayList<>();
2: people.add(new Person("Jose", 33));
3: people.add(new Person("Ane", 90));
4: people.add(new Person("Faria", 65));
5: System.out.println(people);
6: people.removeIf(person -> person.getAge() < 80);
7: System.out.println(people);
2: people.add(new Person("Jose", 33));
3: people.add(new Person("Ane", 90));
4: people.add(new Person("Faria", 65));
5: System.out.println(people);
6: people.removeIf(person -> person.getAge() < 80);
7: System.out.println(people);
Na linha 1, criamos um novo Array de Person. Nas linhas 2, 3 e 4 adicionamos pessoas a lista. na linha 4 imprimimos a lista de pessoas:
[Person{name='Jose', age=33}, Person{name='Ane', age=90}, Person{name='Faria', age=65}]
Na linha 6, utilizamos uma expressão lambda para remover todas as pessoas com idade menor que 80. A saída na linha 7 será:
[Person{name='Ane', age=90}]
Muito fácil não é? Para o exame, você só precisa saber como implementar expressões lambda que usam a interface do Predicate. Lembre-se de que a interface Predicate só tem um método e a assinatura dele é:
boolean test(T t);
Resumo
Para o exame, você precisará ser capaz de:- Identificar declarações de método corretas e incorretas
- Identificar quando um método ou atributo está acessível.
- Reconhecer usos válidos e inválidos de importações estáticas.
- Identificar quando chamar métodos estáticos em vez de instâncias.
- Avaliar código envolvendo construtores.
- Reconhecer quando uma classe está adequadamente encapsulada.
- Escrever expressões lambda simples.
Este post foi um tanto extenso e com muitos detalhes novos. Revise o conteúdo e treine bastante código em sua IDE preferida. Caso fique com dúvidas, faça um comentário que vamos ajudá-lo a entender os tópicos abordados. Um abraço e bons estudos!
Parabéns pelo conteúdo, não tem outro com dicas tão importantes.
ResponderExcluir