A maior parte dos programas fazem a mesma coisa sempre que são executados com os mesmos dados, por isso dizemos que são deterministas. Normalmente é bom que um programa seja determinista, já que esperamos que o mesmo cálculo, feito com os mesmos valores, tenha o mesmo resultado. Mas, para algumas aplicações, gostaríamos de ter um programa imprevisível – por exemplo, os jogos.
Tornar um programa verdadeiramente imprevisível não é fácil mas há formas de o fazer parecer imprevisível. Uma delas é gerar valores aleatórios e usá-los para determinar o resultado do nosso programa.
A biblioteca do Java fornece uma classe que gera valores pseudo aleatórios que, apesar de não serem puramente aleatórios, servem o nosso objetivo.
Random
Consulte a documentação da classe Random
do pacote java.util
em https://docs.oracle.com/javase/9/docs/api/java/util/Random.html
Uma instância desta classe é um objeto que sabe gerar valores aleatórios.
A classe define vários métodos de instância que retornam valores aleatórios de vários tipos. Por exemplo,
int nextInt(int max)
retorna um inteiro aleatório no intervalo [0, max - 1];int nextInt()
retorna um inteiro aleatório no domínio definido para o tipo int
;boolean nextBoolean()
retorna um boolean
aleatório;Antes de podermos usar um objeto do tipo Random
para gerarmos valores aleatórios, temos que o criar.
Criamos novos objetos usando o operador
new
.
- Exemplo:
Random gerador = new Random();
O operador
new
instancia a classe indicada (i.e., cria um novo objeto desse tipo)
- reservando espaço para um novo objeto e
- retornando uma referência para esse local de memória.
Também invoca o construtor da classe (mais sobre isto adiante neste texto).
O operador
new
é um operador unário, ou seja, requer um só operando:
o operando é a invocação de um construtor, ou seja,
- o nome da classe a instanciar, seguido de parêntesis delimitando zero ou mais valores para parâmetros;
é o nome desse construtor que indica o nome da classe a ser instanciada.
O operador new
retorna uma referência para o objeto acabado de criar; esta referência pode ser atribuída a uma variável de tipo apropriado, como na instrução acima.
Como exemplo, as instruções seguintes geram dez inteiros aleatórios no intervalo [0,5] e imprimem-nos no ecrã:
Random gerador = new Random();
for (int i = 1; i <= 10; i++) {
System.out.println(gerador.nextInt(6));
}
Se executar várias vezes o mesmo programa, os valores gerados não serão os mesmos, com toda a probabilidade.
Poderíamos representar o conteúdo da memória durante a execução do código acima, quando o i
tem o valor 2, da seguinte forma:
Aqui representamos os objetos no lado direito, numa coluna de título "Objects" e as variáveis numa coluna de título "Frames".
Como a variável gerador
é de um tipo não primitivo, é usada uma seta para representar o seu conteúdo, que é uma referência a um objeto.
Esse objeto é representado aqui somente pelo texto "java.util.Random instance". Veremos mais para a frente exemplos mais ricos.
Para gerar números aleatórios no intervalo [1,5]:
xxxxxxxxxx
Random gerador = new Random();
for (int i = 1; i <= 10; i++) {
int n = gerador.nextInt(5) + 1;
System.out.println(n);
}
Para gerar números aleatórios no intervalo [-val,val] para um dado valor val:
xxxxxxxxxx
Random gerador = new Random();
for (int i = 1; i <= 10; i++) {
int n = gerador.nextInt(2 * val + 1) - val;
System.out.println(n);
}
De forma mais geral, o método seguinte gera um número aleatório num intervalo [min,max]:
xxxxxxxxxx
/**
* Um numero aleatorio num dado intervalo, gerado por um dado gerador
* @param min O limite inferior do intervalo
* @param max O limite superior do intervalo
* @param g O gerador de aleatorios que deve ser usado
* @return Um inteiro aleatorio no intervalo [min,max], gerado pelo
* gerador g
* @requires min <= max && g != null
*/
public static int aleatorioIntervalo(int min, int max, Random g) {
return g.nextInt(max - min + 1) + min;
}
A sequência de instruções para gerar números aleatórios no intervalo [1,5] apresentada acima tem o mesmo efeito que:
xxxxxxxxxx
Random gerador = new Random();
for (int i = 1; i <= 10; i++) {
System.out.println(aleatorioIntervalo(1,5,gerador));
}
Um objeto do tipo Random
tem um estado; sempre que lhe pedimos um novo valor aleatório, ele muda de estado.
A ideia de definir o método aleatorioIntervalo
com um parâmetro do tipo Random
é boa, pois permite que seja o invocador do método que define qual o objeto Random
que vai criar o valor aleatório, em vez de um novo objeto Random
ser criado dentro do método, sempre que ele é invocado.
O conteúdo de memória na altura em que o método aleatorioIntervalo
está a ser executado pela primeira vez (quando o i
tem o valor 1) , pode ser representado assim:
Quando um método é invocado, o valor indicado para o parâmetro, na invocação, é copiado para o parâmetro do método. Neste caso particular, o valor da variável gerador
é copiado para o parâmetro g
.
Como estas duas variáveis são de um tipo não primitivo, já sabemos que o conteúdo de gerador
é uma referência a um objeto. Então, o valor que é copiado para g
é essa mesma referência.
Na representação da memória, acima, uma referência é representada por uma seta. Por isso, quando duas variáveis contêm referências iguais, as respetivas setas apontam para o mesmo objeto.
Se usarmos dois objetos Random
criados da seguinte forma:
xxxxxxxxxx
Random g1 = new Random();
Random g2 = new Random();
para gerar valores aleatórios sucessivos,exatamente da mesma maneira com cada um deles, como em:
xxxxxxxxxx
for (int i = 1; i <= 10; i++) {
int n1 = g1.nextInt();
int n2 = g2.nextInt();
System.out.println(n1 + " " + n2);
}
não só g1
cria 10 valores pseudo aleatórios e g2
cria 10 valores pseudo aleatórios, como os valores que g1
cria são perfeitamente independentes dos valores criados por g2
. Além disso, a cada nova execução deste conjunto de instruções são gerados valores potencialmente diferentes das execuções anteriores.
Podemos criar objetos Random
usando outro construtor – o construtor Random(int seed)
– com o qual podemos definir o valor inicial que serve de base para a criação dos valores aleatórios – a semente. Se criarmos dois objetos Random
com a mesma semente e os fizermos gerar dez valores cada, como nas instruções que se seguem:
xRandom g1 = new Random(1); // usamos uma semente 1
Random g2 = new Random(1); // aqui tambem
for (int i = 1; i <= 10; i++) {
int n1 = g1.nextInt();
int n2 = g2.nextInt();
System.out.println(n1 + " " + n2);
}
então g1
e g2
geram 10 valores pseudo aleatórios cada um, mas os valores que g1
gera são iguais aos valores gerados por g2
pois a semente usada na construção dos dois objetos foi a mesma. Repare que a única diferença entre este pedaço de código e o anterior é o construtor usado na criação dos objetos Random
.
De igual forma, um gerador criado com uma semente gerará sempre a mesma sequência de valores. Por isso, o seguinte programa escreve sempre os mesmos 5 valores no ecrã de cada vez que é executado.
xxxxxxxxxx
public class GeracaoAleatoriosV2 {
public static void main(String[] args) {
Random gerador = new Random(1); // definimos a semente
for (int i = 1; i <= 5; i++) {
System.out.println(gerador.nextInt());
}
}
}
Anterior: 8.1. Pacotes
Seguinte: 8.3. Construindo uma String passo a passo