O princípio PECS
Producer Extends, Consumer Super.
O princípio PECS é uma aula grátis de Java Academy no CoddyKit. Esta é a aula 3 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Java Academy, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Java Academy inclui 4 aulas no total.
O significado de PECS
PECS significa Produtor com limite superior, consumidor com limite inferior. É um recurso mnemônico do livro Java eficaz, de Joshua Bloch.
- Se um parâmetro produz valores de T (você lê a partir dele), use
? extends T. - Se um parâmetro consome valores de T (você escreve nele), use
? super T.
public class Main {
public static void main(String[] args) {
System.out.println("Producer Extends, Consumer Super");
}
}O método copy clássico
O exemplo clássico de PECS é um método copy. A origem produz, portanto usa ? extends T. O destino consome, portanto usa ? super T.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T t : src) dest.add(t);
}
public static void main(String[] args) {
List<Integer> src = List.of(1, 2, 3);
List<Object> dest = new ArrayList<>();
copy(src, dest);
System.out.println(dest);
}
}Por que os dois curingas aumentam a flexibilidade
Graças a PECS, copy funciona com muitas combinações de tipos: uma origem de inteiros em um destino de números ou objetos, e uma origem de String em um destino de CharSequence.
Sem curingas, os tipos teriam de ser exatamente iguais, o que prejudicaria a reutilização.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T t : src) dest.add(t);
}
public static void main(String[] args) {
List<Integer> ints = List.of(10, 20);
List<Number> nums = new ArrayList<>();
copy(ints, nums);
System.out.println(nums);
}
}Um exemplo de produtor
Um método que apenas lê de sua coleção deve usar um limite superior. Aqui somamos os valores de um produtor.
import java.util.List;
public class Main {
static double sum(List<? extends Number> producer) {
double total = 0;
for (Number n : producer) total += n.doubleValue();
return total;
}
public static void main(String[] args) {
System.out.println(sum(List.of(1, 2, 3)));
System.out.println(sum(List.of(1.1, 2.2)));
}
}Um exemplo de consumidor
Um método que apenas escreve em sua coleção deve usar um limite inferior. Aqui inserimos valores em um consumidor.
import java.util.ArrayList;
import java.util.List;
public class Main {
static void pushDigits(List<? super Integer> consumer) {
for (int i = 0; i < 5; i++) consumer.add(i);
}
public static void main(String[] args) {
List<Number> nums = new ArrayList<>();
pushDigits(nums);
System.out.println(nums);
}
}As duas funções: use um tipo simples
Se um parâmetro atua como produtor e consumidor, ele não deve usar um curinga.
Um método que lê e escreve na mesma lista precisa do tipo exato T.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void rotate(List<T> list) {
if (list.isEmpty()) return;
T last = list.remove(list.size() - 1);
list.add(0, last);
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>(List.of(1, 2, 3));
rotate(list);
System.out.println(list);
}
}PECS em Comparator
O JDK aplica PECS de forma abrangente. Collections.max recebe um Comparator<? super T> porque o comparador consome valores de T para compará-los.
import java.util.Comparator;
import java.util.List;
public class Main {
public static void main(String[] args) {
Comparator<Object> byHash = Comparator.comparingInt(Object::hashCode);
List<String> words = List.of("a", "bb", "ccc");
String max = java.util.Collections.max(words, byHash);
System.out.println(max);
}
}PECS em Stream.collect
Muitas interfaces funcionais seguem PECS. Um Consumer<? super T> pode aceitar um manipulador escrito para um tipo mais abrangente.
import java.util.List;
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
Consumer<Object> printer = o -> System.out.println("got: " + o);
List<String> items = List.of("x", "y");
items.forEach(printer); // forEach takes Consumer<? super T>
}
}Produtores e consumidores de funções
Function<? super T, ? extends R> é a forma completa de PECS: a entrada é consumida (super), e a saída é produzida (extends).
Isso permite que uma transformação aceite entradas mais abrangentes e retorne saídas mais específicas.
import java.util.function.Function;
public class Main {
static <T, R> R apply(Function<? super T, ? extends R> fn, T value) {
return fn.apply(value);
}
public static void main(String[] args) {
Function<Object, Integer> len = o -> o.toString().length();
System.out.println(apply(len, "hello"));
}
}Tipos de retorno: evite curingas
Não use curingas em tipos de retorno. Eles obrigam os chamadores a lidar com curingas também, expondo essa complexidade.
Retorne um tipo concreto e mantenha os curingas nos parâmetros, onde eles aumentam a flexibilidade.
import java.util.ArrayList;
import java.util.List;
public class Main {
// Good: concrete return type
static List<Number> doubled(List<? extends Number> src) {
List<Number> out = new ArrayList<>();
for (Number n : src) out.add(n.doubleValue() * 2);
return out;
}
public static void main(String[] args) {
System.out.println(doubled(List.of(1, 2, 3)));
}
}Uma combinação completa de PECS
Reunindo tudo: uma operação merge que lê de dois produtores e grava em um consumidor.
import java.util.ArrayList;
import java.util.List;
public class Main {
static <T> void merge(List<? extends T> a, List<? extends T> b, List<? super T> out) {
for (T t : a) out.add(t);
for (T t : b) out.add(t);
}
public static void main(String[] args) {
List<Integer> a = List.of(1, 2);
List<Integer> b = List.of(3, 4);
List<Number> out = new ArrayList<>();
merge(a, b, out);
System.out.println(out);
}
}Verificação rápida
Aplique a regra PECS.
Recapitulação
Você aprendeu o princípio PECS:
- Produtor com limite superior: parâmetros somente para leitura usam
? extends T. - Consumidor com limite inferior: parâmetros somente para escrita usam
? super T. - Parâmetros com as duas funções usam um parâmetro de tipo simples.
- Evite curingas em tipos de retorno.
A seguir, a aplicação dessas ideias ao design de APIs reais.
public class Main {
public static void main(String[] args) {
System.out.println("PECS recap: Producer Extends, Consumer Super");
}
}Perguntas Frequentes
A aula “O princípio PECS” é grátis?
Sim — o texto completo de “O princípio PECS” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Java Academy, atualize para CoddyKit PRO. O curso de Java Academy inclui 4 aulas no total.
O que vou aprender em “O princípio PECS”?
Producer Extends, Consumer Super. Você pratica Java Academy com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Java Academy?
Nenhuma experiência prévia é necessária. Java Academy no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 3 de 4.
Quanto tempo leva a aula “O princípio PECS”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Java Academy?
Sim. Cada aula de Java Academy inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.