Complementando a resposta do Hugo:
Vc já entendeu que o método hasNext()
nos diz se o iterator ainda possui elementos a serem iterados e que o método next()
avança para o próximo elemento.
Foca na palavra que eu destaquei. O iterator apenas avança, nunca retrocede.
Tentar avançar para o próximo elemento depois que o iterator chegou ao fim, causa uma NoSuchElementException
.
Experimente este exemplo:
import java.util.HashSet;
import java.util.Iterator;
public class Main {
public static void main(String... args) {
HashSet<Integer> set = new HashSet<>();
set.add(123);
Iterator<Integer> iterator = set.iterator();
System.out.println(iterator.next()); // Ok: Retorna 123
System.out.println(iterator.next()); // Erro: NoSuchElementException
}
}
Sobre isto não tem muito o que se pensar, a interface Iterator
foi projetada para isso, apenas avançar.
Cada vez que vc invoca o método iterator()
uma nova instância de Iterator
é retornada, dá para atestar isso assim:
import java.util.HashSet;
import java.util.Iterator;
public class Main {
public static void main(String... args) {
HashSet<Integer> set = new HashSet<>();
Iterator<Integer> it1 = set.iterator();
Iterator<Integer> it2 = set.iterator();
System.out.println(it1.equals(it2)); // false
}
}
Será que se a gente construir nossa própria implementação de Iterator
vc pega melhor a ideia? Olha só:
import java.util.Iterator;
import java.util.NoSuchElementException;
class IntegerList implements Iterable<Integer> {
private Integer[] elements;
private int currentSize = 0;
IntegerList(int size) {
this.elements = new Integer[size];
}
void add(int element) {
this.elements[this.currentSize++] = element;
}
@Override
public Iterator<Integer> iterator() {
return new IntegerListIterator();
}
private class IntegerListIterator implements Iterator<Integer> {
private int currentIndex = 0;
@Override
public boolean hasNext() {
return this.currentIndex < IntegerList.this.elements.length;
}
@Override
public Integer next() {
try {
return IntegerList.this.elements[this.currentIndex++];
} catch (IndexOutOfBoundsException e) {
throw new NoSuchElementException();
}
}
}
}
public class Main {
public static void main(String... args) {
IntegerList list = new IntegerList(2);
list.add(123);
list.add(456);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// Olha que da hora:
// Classes que implementam Comparable podem ser usadas no Enhanced For!
for (Integer number : list) {
System.out.println(number);
}
}
}
Deu pra pegar a ideia? Óbvio que falta muita coisa, mas é só pra vc ter uma noção mesmo.
Se quiser um exemplo real, leia o código fonte de ArrayList
do OpenJDK a partir da linha abaixo:
Agora, se vc quer um Iterator
que vai e vem, vc precisa do ListIterator
:
https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/ListIterator.html
Olha um exemplo doido:
import java.util.ArrayList;
import java.util.ListIterator;
public class Main {
public static void main(String... args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
ListIterator<Integer> iterator = list.listIterator();
System.out.println("Pra frente:");
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
System.out.println("Pra trás:");
while (iterator.hasPrevious()) {
System.out.println(iterator.previous());
}
System.out.println("Pra frente de novo:");
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
Não sei se entendi bem a pergunta, mas este negócio de não dar para alterar o valor de uma variável final, mas dar para mudar o estado interno dos objetos apontados por aquela variável vale para qualquer contexto.
Por exemplo, o código abaixo vai dar erro:
class Person {
private String name;
Person(String name) {
this.name = name;
}
void changeName(String name) {
this.name = name;
}
String getName() {
return this.name;
}
}
public class Main {
public static void main(String... args) {
final Person joao = new Person("João");
joao = new Person("Marcos");
System.out.println(joao.getName());
}
}
Mas vc é livre para mudar o estado interno de Person
através do método changeName()
.
final Person joao = new Person("João");
joao.changeName("Marcos");
System.out.println(joao.getName());
Por isso a palavra-chave final
não garante imutabilidade de verdade. Vc teria que projetar sua classe para ser imutável como é a String
e a BigDecimal
, por exemplo.
É interessante mesmo, mas eu também não sei qual é o motivo disso também .
Bom, fora o caso das lambdas, que eu sei até o momento, tem o caso das inner classes.
Vc também não pode usar variáveis que não sejam final ou efetivamente final dentro inner classes. Isto vai dar erro:
public class Main {
public static void main(String... args) {
int x = 123;
x = 456;
class MyClass {
void doSomething() {
System.out.println(x);
}
}
new MyClass().doSomething();
}
}