(→Leitura adicional) |
(→Leitura adicional) |
||
Line 491: | Line 491: | ||
Ver o manual de Java, para os conceitos acima. | Ver o manual de Java, para os conceitos acima. | ||
− | * https://docs.oracle.com/en/java/javase/ | + | * https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Iterable.html |
− | * https://docs.oracle.com/en/java/javase/ | + | * https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Iterator.html |
− | * https://docs.oracle.com/en/java/javase/ | + | * https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Comparator.html |
[[Category:Ensino]] | [[Category:Ensino]] | ||
[[Category:PO]] | [[Category:PO]] | ||
[[category:PO Exemplos]] | [[category:PO Exemplos]] |
A tarefa nestes exemplos é definir um gato iterável. Esta tarefa é realizada nos exemplos 1 a 5 de formas diferentes, embora produzindo sempre o mesmo resultado (excepto onde indicado). O exemplo 6 ilustra aspectos de polimorfismo relacionado com as várias partes de um gato construído desta forma.
Iterar implica a capacidade de obter múltiplas entidades a partir de um gato. Embora seja perfeitamente possível definir iteradores independentes da linguagem Java, é mais conveniente usar os conceitos Java para iteração, pois permitem utilizar algoritmos pré-definidos para iteradores (tirando partido do polimosfismo) e utilizar a sintaxe especial for-each, aplicável a entidades iteráveis e arrays (i.e., entidade primitivas com dimensão fixa).
Assim, serão implementadas duas interfaces pré-definidas em Java. A primeira das interfaces é java.lang.Iterable<T> e é implementada pelas classes que representam, em cada caso, o conceito de gato. Em geral, esta interface é implementada por qualquer entidade que representa um qualquer tipo de colecção de objectos.
A segunda interface é java.util.Iterator<T> e é implementada pelos iteradores definidos para as entidades iteráveis. Note-se que alguns dos exemplos de implementação serão internos, embora esta interface seja exterior a todas as classes.
Este exemplo apresenta o conceito de Gato como iterável, mas não disponibiliza nesse conceito nenhuma definição para o tipo do iterador. Esta situação implica que o iterador tem de ser definido depois da classe do gato estar terminada. Pode implicar ainda que a classe do gato tenha de disponibilizar uma interface pública para permitir a definição e funcionamento do iterador (sem a qual o iterador pode não ter acesso às estruturas internas do gato).
Os métodos size() e part(int i) são utilizados pelo iterador, mas expõem aspectos da implementação internada do gato.
Note-se que o método iterador(), imposto pela interface Iterable<T>, retorna a um iterador para a instância da classe do gato à qual é pedido o iterador. Como as intâncias das classes do iterador e do gato não têm nenhuma relação especial, é necessário informar o iterador sobre o gato a iterar.
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Cat implements Iterable<CatPart> {
private List<CatPart> _parts = new ArrayList<CatPart>();
public Cat(int nparts) {
for (int i = 0; i < nparts; i++)
_parts.add(new CatPart(i));
}
public int size() { return _parts.size(); }
public CatPart part(int i) { return _parts.get(i); }
public Iterator<CatPart> iterator() {
return new CatIterator(this);
}
}
Esta classe implementa a interface Iterator<T>, em que T é uma parte do gato. A interface obriga à implementação dos métodos hasNext, next e remove. Estas implementações são feitas com base no índice gerido pelo iterador e na intância de gato passada no construtor.
import java.util.Iterator;
public class CatIterator implements Iterator<CatPart> {
Cat _cat;
int _index = 0;
public CatIterator(Cat cat) { _cat = cat; }
public boolean hasNext() { return _index < _cat.size(); }
public CatPart next() { return _cat.part(_index++); }
public void remove() { throw new UnsupportedOperationException(); }
}
Esta é uma classe muito simples que serve apenas para ilustrar a construção de um gato a partir de partes de gato. Neste exemplo, não é feita nenhuma distinção entre as várias partes (em exemplos mais elaborados, o polimorfismo de inclusão vai permitir continuar a ignorar as diferenças na maior parte, se não em todos, dos casos). O único método definido (além do construtor) é o método toString, para permitir obter alguma informação útil (distinguir as partes) quando se imprime um gato.
public class CatPart {
private int _i;
public CatPart(int i) { _i = i; }
public String toString() { return "PART #" + _i; }
}
Esta aplicação ilustra a utilização dos conceitos acima. Note-se que o modo de iteração utilizado (for-each) é válido para entidades que implementem o conceito java.util.Iterable<T> e para arrays primitivos.
public class App {
public static void main(String[] args) {
Cat cat = new Cat(5);
for (CatPart part: cat)
System.out.println(part);
}
}
O processo de execução é o habitual. Primeiro, é necessário compilar as classes:
javac App.java CatIterator.java Cat.java CatPart.java
Em segundo lugar, executa-se a partir da classe que contém o método main:
java App
O resultado é:
PART #0
PART #1
PART #2
PART #3
PART #4
Este caso é como o primeiro, i.e., temos um gato que não define nenhum iterador específico. No entanto, ao contrário do primeiro caso, onde se definiu uma classe para iterar o gato, neste caso observamos que o iterador no caso anterior apenas iterava a lista de partes do gato. Como tanto o iterador do gato (no exemplo anterior), como o iterador da lista, implementam Iterator<CatPart>, é possível poupar a definição de uma classe e reutilizar o iterador da lista.
O gato fica assim definido:
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Cat implements Iterable<CatPart> {
private List<CatPart> _parts = new ArrayList<CatPart>();
public Cat(int nparts) {
for (int i = 0; i < nparts; i++)
_parts.add(new CatPart(i));
}
public Iterator<CatPart> iterator() {
return _parts.iterator();
}
}
A classe CatIterator deixa de ser necessária e as restantes permanecem inalteradas.
Note-se como a reutilização de um conceito equivalente (para esta situação) permitiu ainda simplificar o código da classe gato.
O processo de execução é o habitual. Primeiro, é necessário compilar as classes:
javac App.java Cat.java CatPart.java
A execução é exactamente como no caso anteriores.
No caso 1, o iterador de gatos era uma instância de uma classe definida especialmente para esse efeito. No caso 2, o processo de iteração era levado a cabo através de um iterador já definido (o iterador de uma lista, tal como implementado pela classe java.util.ArrayList<T>. O segundo exemplo mostrava apenas como é possível reutilizar o iterador de uma estrutura interna a um objecto para fornecer essa funcionalidade ao objecto que a contém.
Note-se que, tal como no caso 2, apenas é necessária a definição das classes Cat (para o conceito gato), CatPart (partes abstractas do gato) e App (a aplicação). A classe do iterador está contida na da classe gato. Estas duas classes são como nos casos anteriores.
Neste exemplo, volta-se a considerar uma classe específica para a iteração, mas melhora-se a implementação da classe de iteração, por forma a não ser necessária a interface adicional de suporte (métodos size e part de gato). Neste aspecto, a classe gato é semelhante à do caso 2. A nova classe de iteração recorre ao mecanismo das classes internas Java.
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Cat implements Iterable<CatPart> {
private List<CatPart> _parts = new ArrayList<CatPart>();
private class CatIterator implements Iterator<CatPart> {
int _index = 0;
public boolean hasNext() { return _index < _parts.size(); }
public CatPart next() { return _parts.get(_index++); }
public void remove() { throw new UnsupportedOperationException(); }
}
public Cat(int nparts) {
for (int i = 0; i < nparts; i++)
_parts.add(new CatPart(i));
}
public Iterator<CatPart> iterator() {
return new CatIterator();
}
}
Note-se que a definição de CatIterator é agora privada da classe Cat e que apenas pode ser vista como uma implementação de java.util.Iterator<CatPart>. A interface adicional do caso 1 não é necessária neste caso, pois, como a classe do iterador é interna, dispõe de acesso ao estado do gato em iteração. Além deste aspecto, a definição da classe gato é semelhante à de casos anteriores.
O processo de execução é o habitual. Primeiro, é necessário compilar as classes:
javac App.java Cat.java CatPart.java
Note-se que, neste caso, além dos ficheiros .class resultantes da compilação das classes indicadas, é produzido também o ficheiro Cat$CatIterator.class (correspondente à classe interna, cujo nome ao nível do código é Cat.CatIterator).
A execução é exactamente como nos casos anteriores.
O código no caso 4 difere do do caso 3 apenas num aspecto: a classe do iterador é privada da classe do gato e é utilizada apenas num local do código: o método que permite obter o iterador (iterator).
Não há outras alterações de código relativamente ao caso 3.
Consegue-se uma melhor gestão do código se a classe do iterador se for deslocada para junto da sua única utilização. Deste modo, neste caso a classe do iterador deixa de ser acessível ao nível da class gato e passa apenas a ser acessível no método iterator.
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Cat implements Iterable<CatPart> {
private List<CatPart> _parts = new ArrayList<CatPart>();
public Cat(int nparts) {
for (int i = 0; i < nparts; i++)
_parts.add(new CatPart(i));
}
public Iterator<CatPart> iterator() {
class CatIterator implements Iterator<CatPart> {
int _index = 0;
public boolean hasNext() { return _index < _parts.size(); }
public CatPart next() { return _parts.get(_index++); }
public void remove() { throw new UnsupportedOperationException(); }
}
return new CatIterator();
}
}
O processo de execução é o mesmo do caso 3.
Note-se que, neste caso, o ficheiro .class da classe interna já não se chama Cat$CatIterator.class, pois a classe é agora local a um método. O nome do ficheiro da classe interna é, neste caso, Cat$1CatIterator.class.
A execução é exactamente como nos casos anteriores.
Nos casos 3 e 4 utilizou-se uma classe interna para implementar a interface java.util.Iterator<T>. Dessa forma, consegui-se esconder a implementação desta interface para a classe gato. No entanto, tanto num caso, como no outro, a classe interna foi apenas utilizada uma vez, pelo que é ainda possível melhorar o aspecto da sua definição.
Assim, usam-se classes internas, cada um associada a uma utilização específica: o primeiro caso é como anteriormente; o segundo caso permite iterar a partir de uma dada posição.
Além da classe gato e da aplicação exemplo, todas as classes são como anteriormente.
Neste caso, poupa-se a definição de um nome especial para a classe do iterador e, considerando que a classe interna não é reutilizada, define-se uma implementação anónima, utilizada apenas para a criação da única instância associada a esta classe. A sintaxe para este código é a indicada abaixo: o corpo da classe aparece logo a seguir à indicação de criação (este processo pode ser aplicado a qualquer classe ou interface que se queixa especializar ou implementar através de uma classe anónima).
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Cat implements Iterable<CatPart> {
private List<CatPart> _parts = new ArrayList<CatPart>();
public Cat(int nparts) {
for (int i = 0; i < nparts; i++)
_parts.add(new CatPart(i));
}
public Iterator<CatPart> iterator() {
return new Iterator<CatPart>() {
int _index = 0;
public boolean hasNext() { return _index < _parts.size(); }
public CatPart next() { return _parts.get(_index++); }
public void remove() { throw new UnsupportedOperationException(); }
};
}
public Iterator<CatPart> iterator(final int start) {
return new Iterator<CatPart>() {
int _index = start;
/* ANONYMOUS() */ { System.out.println("Iterating from: " + _index); }
public boolean hasNext() { return _index < _parts.size(); }
public CatPart next() { return _parts.get(_index++); }
public void remove() { throw new UnsupportedOperationException(); }
};
}
}
Note-se que, no segundo método iterator, a variável start deve ser final, para poder ser utilizada na inicialização da classe interna. Esta variável poderia também ser inicializada no bloco anónimo (o comentário antes deste bloco serve apenas para indicar que, em alguns aspectos, o bloco pode ser visto como um construtor -- estes blocos, que pode ser colocados em qualquer classe, anónima ou não, são executados quando se criam objectos da classe).
Esta aplicação ilustra a utilização dos conceitos acima. Note-se que o modo de iteração utilizado (for-each) é válido para entidades que implementem o conceito java.util.Iterable<T> e para arrays primitivos. No segundo caso de iteração nesta aplicação, usa-se processo de iteração sem recurso a for-each, já que esse processo itera sempre todos os elementos de uma estrutura.
import java.util.Iterator;
public class App {
public static void main(String[] args) {
Cat cat = new Cat(5);
for (CatPart part: cat)
System.out.println(part);
Iterator<CatPart> it = cat.iterator(2);
while (it.hasNext())
System.out.println(it.next());
}
}
O processo de execução é o mesmo dos casos 3 e 4.
Note-se que, neste caso, os ficheiros .class das classes internas já não têm um componente com nome, pois as duas classes internas são anónimas. Os ficheiros .class que as contêm são Cat$1.class e Cat$2.class.
A execução é exactamente como nos casos anteriores.
O resultado de execução é agora:
PART #0
PART #1
PART #2
PART #3
PART #4
Iterating from: 2
PART #2
PART #3
PART #4
Neste caso, muito semelhante ao caso 5 (em termos de definição de classes internas), especializou-se a classe CatPart, por forma a tirar partido do polimorfismo e criar um gato com partes distintas e identificáveis.
Esta classe é como definida no caso 5, mas tem um construtor que inicializa aleatoriamente as partes do gato.
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;
public class Cat implements Iterable<CatPart> {
private List<CatPart> _parts = new ArrayList<CatPart>();
public Cat(int nparts) {
int lim = nparts + 1;
for (int i = 0; i < lim; i++) {
switch ((int)(lim*Math.random())) {
case 0: _parts.add(new Head()); break;
case 1: _parts.add(new Paw()); break;
case 2: _parts.add(new Ear()); break;
case 3: _parts.add(new Belly()); break;
case 4: _parts.add(new Tail()); break;
case 5: _parts.add(new Paw()); break;
case 6: _parts.add(new Paw()); break;
case 7: _parts.add(new Paw()); break;
case 8: _parts.add(new Ear()); break;
case 9: _parts.add(new Nose()); break;
default: _parts.add(new CatPart());
}
}
}
public Iterator<CatPart> iterator() {
return new Iterator<CatPart>() {
int _index = 0;
public boolean hasNext() { return _index < _parts.size(); }
public CatPart next() { return _parts.get(_index++); }
public void remove() { throw new UnsupportedOperationException(); }
};
}
public Iterator<CatPart> iterator(final int start) {
return new Iterator<CatPart>() {
int _index = start;
/* ANONYMOUS() */ { System.out.println("Iterating from: " + _index); }
public boolean hasNext() { return _index < _parts.size(); }
public CatPart next() { return _parts.get(_index++); }
public void remove() { throw new UnsupportedOperationException(); }
};
}
}
Note-se que é mantida a proporção (e.g., a probabilidade de produzir uma pate é 4 vezes superior à de produzir uma cauda).
A classe CatPart é uma classe mantém a simplicidade anterior. É ainda mais simples que no casos anteriores, de facto, já que não mantém informação sobre o número da parte. O único método definido é toString, para ser utilizado pelas subclasses.
public class CatPart {
public String toString() { return "PART "; }
}
A classe Belly representa uma barriga de gato:
public class Belly extends CatPart {
public String toString() { return super.toString() + ": Belly"; }
}
A classe Ear representa uma orelha de gato:
public class Ear extends CatPart {
public String toString() { return super.toString() + ": Ear"; }
}
A classe Head representa uma cabeça de gato:
public class Head extends CatPart {
public String toString() { return super.toString() + ": Head"; }
}
A classe Nose representa um nariz de gato:
public class Nose extends CatPart {
public String toString() { return super.toString() + ": Nose"; }
}
A classe Paw representa uma pata de gato:
public class Paw extends CatPart {
public String toString() { return super.toString() + ": Paw"; }
}
A classe Tail representa uma cauda de gato:
public class Tail extends CatPart {
public String toString() { return super.toString() + ": Tail"; }
}
A aplicação exemplo ilustra a utilização dos conceitos acima. Recorde-se que, tal como nos casos anteriores, o modo de iteração utilizado (for-each) é válido para entidades que implementem o conceito java.util.Iterable<T> e para arrays primitivos. Os elementos são agora instâncias das subclasses de CatPart que redefinem o método toString. Cada gato é construído a partir de 9 partes criadas aleatoriamente a partir de uma distribuição uniforme.
public class App {
public static void main(String[] args) {
Cat cat = new Cat(9);
for (CatPart part: cat)
System.out.println(part);
}
}
O processo de execução é o habitual. Primeiro, é necessário compilar as classes:
javac App.java Belly.java Cat.java CatPart.java Ear.java Head.java Nose.java Paw.java Tail.java
O processo de execução é como anteriormente:
java App
No entanto, caso execução produz agora um resultado distinto (por causa do uso de números aleatórios na construção do gato).
Exemplo de resultado de uma execução:
PART : Paw
PART : Ear
PART : Tail
PART : Paw
PART : Tail
PART : Ear
PART : Belly
PART : Tail
PART : Paw
PART : Paw
Uma outra execução produz resultados diferentes:
PART : Ear
PART : Paw
PART : Nose
PART : Paw
PART : Belly
PART : Ear
PART : Paw
PART : Paw
PART : Tail
PART : Paw
Ver o manual de Java, para os conceitos acima.