Java

Thursday, April 27, 2006

 

Cast

Compila:

byte b = 10;
float f = 6;
float f = 32.2f;
char c = 982; //compila, pois char é 16 bits.
b+=7;

Os operadores +=, *=, /*, forçam uma conversão implícita, por isso não há erro de compilação.

Não compila, erro: possible loss of precision. Correção: cast explícito:

byte b = 128;
byte f = 32.2;
char c = 70000;
b = b+7; //o resultado da operação é um inteiro.

byte c = 12;
byte d = 14;
byte e = c+d; //não compila. Embora os operandos sejam bytes, o resultado da soma é um inteiro.

Monday, April 24, 2006

 

Serialização

Para que um objeto de uma classe seja serializável, ela precisa implementar a interface Serializable. Quando um objeto é serializado, todas as suas variáveis de instância são serializadas também, inclusive variáveis que se referem a outros objetos. No entanto, se algumas das variáveis de instância de um objeto serializável não é ela mesma serializável, então ocorrerá um erro em tempo de execução ao tentar serializar o objeto. Uma instância da classe abaixo provocaria este erro ao ser serializado.

public class Animal implements Serializable{
public X peso;
public int idade;
}

public class X{int peso}

Este erro pode ser contornado marcando-se a variável de instância peso como transient:

public transient X peso;

Neste caso, no entanto, a variável de instância peso não será serializada quando um objeto do tipo Animal for serializado. Variáveis transient recebem o valor default quando um objeto é deserializado. A variável peso de um objeto Animal receberia o valor default null num processo de deserialização.

Vejamos agora a situação em que a classe serializável tem como superclasse uma outra não-serializável:

public class Animal{public int peso=10;}
public class Gato extends Animal implements Serializable{
public int idade;
}

Imagine agora que criamos um objeto g do tipo Gato e fazemos g.peso=12; Em seguida, serializamos o objeto. Ao ser deserializado, a variável g.peso valerá 10 e não 12. Quando um objeto serializável tem uma superclasse não-serializável, o construtor dessa classe é chamado e as suas variáveis de instância são inicializadas normalmente.

Saturday, April 22, 2006

 

sobrecarga

Algumas regras para a sobrecarga de métodos.

1. Métodos com parâmetros primitivos têm preferência sobre métodos com parâmetros do tipo Wrapper:

public void fazAlgo(int x){}

public void fazAlgo(Integer x){}

public void chamaFazAlgo(){
fazAlgo(5);
}


Se o método fazAlgo(int x) não existisse, o autoboxing entraria em operação e o compilador chamaria adequadamente fazAlgo(Integer x). Como ambos os métodos existem, o primeiro tem preferência.

2. Métodos com parâmetros primitivos têm preferência sobre métodos com a sintaxe de var-args:

public void fazAlgo(int x, int y){}

public void fazAlgo(int... x){}

public void chamaFazAlgo(){
fazAlgo(5,5);
}


3. Métodos com parâmetros do tipo Wrapper têm preferência sobre métodos com a sintaxe de var-args:

public void fazAlgo(Integer x, Integer y){}

public void fazAlgo(int... x){}

public void chamaFazAlgo(){
fazAlgo(5,5);
}

O compilador opta por colocar em operação o autoboxing, convertendo os argumentos 5, 5 para objetos da classe wrapper equivalente, no caso, Integer, chamando, então, o método fazAlgo(Integer x, Integer y).

4. O compilador também pode converter tipos primitivos para outros que não causem perda de informação antes de chamar um método:

public void fazAlgo(int x){}
public void chamaFazAlgo(){
byte b=5;
fazAlgo(b);
}


5. No entanto, o compilador não pode, ao mesmo tempo, converter primitivos para outros que não causem perda de informação e depois empregar autoboxing para chamar um método. Isso não é legal:

public void fazAlgo(Integer x){}
public void chamaFazAlgo(){
byte b=5;
fazAlgo(b);
}


6. Mas o inverso é possível, isto é, o compilador pode empregar autoboxing e depois converter um wrapper para outro objeto e chamar um método (mas lembre-se que objetos wrapper não possuem entre si relações de parentesco):

public void fazAlgo(Object x){}
public void chamaFazAlgo(){
byte b=5;
fazAlgo(b);
}

Neste caso, o compilador emprega autoboxing, transformando o byte b em um objeto Byte e, em seguida, o converte para uma instância de Object, chamando, enfim, o método fazAlgo(Object x).

Monday, April 17, 2006

 

Blocos de inicialização

Java permite blocos de inicialização que podem ficar em qualquer lugar no corpo de uma classe. Os blocos podem ser estáticos ou não-estáticos. Os blocos estáticos são inicializados apenas uma vez, quando a classe é carregada pela JVM. Os blocos não-estáticos são executados sempre que é criada uma nova instância de variável. Eles são executados imediatamente após a chamda de super(). Caso haja mais de um bloco estático ou mais de um bloco não-estático, eles serão executados na ordem em que se encontram na classe, de cima para baixo.


public class Num{
int x;
int y;
static int z;

{x=5;}

public Num(){
//super() implicito
System.out.println(x);
}

static{z=7;}

{y=9;}

}

Tuesday, April 11, 2006

 

Collections

Collections é uma classe com métodos estáticos para lidar com objetos que são instâncias de Collection, que, por sua vez, é uma interface implementada por classes como ArrayList, TreeSet etc. As coleções podem ser ordered ou sorted. No primeiro caso, a navegação pelos objetos da coleção segue uma determinada ordem precisa, que pode, por exemplo, ser a ordem de inserção. No segundo caso, os objetos da coleção estão ordenados segundo alguma regra, como a ordenação numérica e alfabética.

Há quatro tipos básicos de coleções. List, Set, Map e Queue. Um Set jamais permite objetos duplicados, isto é, que passem pelo teste x.equals(y), embora sejam instâncias distintas. Um Queue mantém a ordem de entrada dos objetos. Um Map serve para correlacionar chaves e valores e não aceita chaves duplicadas.

Monday, April 10, 2006

 

hashcode() e equals()

hashcode() e equals são métodos da classe Object. Como todos os objetos estendem impliciamente Object, eles possuem ambos os métodos. Se hashcode é sobrescrito, em alguma classe, então equals também deve ser sobrescrito, para se manter a consistência. Se duas instâncias de variáveis x e y se referem ao mesmo objeto, isto é, se a chamada de x.equals(y) retorna true, então a chamada de hashcode() para ambas as instâncias deve retornar o mesmo valor. O Modus Tollens dessa afirmação é que se a chamada de hashcode() para os dois objetos retorna valores diferentes, então esses dois objetos devem ser distintos, ou seja, a chamada de x.equals(y) deve retornar false. O método hashcode() pode ser completamente ineficiente e correto, por exemplo, se ele retorna o mesmo valor para qualquer objeto. O contrato de equals() especifica que ele deve ser reflexivo, simétrico, transitivo e consistente.

 

javac -source x.x -target x.x

A opção -source serve para indicar ao compilador a versão da linguagem com a qual o código a ser compilado é compatível. Assertions, por exemplo, só existe em java a partir da versão 1.4. Sendo assim, para compilar um código com assertions, podemos usar:

javac -source 1.4 arquivo.java

ou

javac -source 1.5 arquivo.java

mas não

javac -source 1.3 arquivo.java

Nesse último caso, a compilação falha. Se, por outro lado, assert é usado como um identificador e compilado com a opção -source 1.3, então a compilação é bem sucedida, mas com alertas. Se compilado com a opção -source 1.4 ou 1.5 e assert é um identificador, a compilação falha. Generics só passou a fazer parte da linguagem a partir da versão 1.5. Por default, o compilador pressupõe que o código é compatível com a versão 1.5.

A opção -target serve para indicar ao compilador com qual versão da JVM os bytecodes devem ser compatíveis. Se você deseja que o seu programa rode em JVMs antigas como a 1.2, então compile o seu código com o seguinte comando:

javac -target 1.2 arquivo.java

Saturday, April 08, 2006

 

Applet

Para obter os eventos de teclado em um applet, não basta adicionar ou ouvinte de teclado ao applet, é necessário também fazer com que o applet obtenha o foco:

this.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyPressed(java.awt.event.KeyEvent evt) {
formKeyPressed(evt); //processa o evento
}
});

this.requestFocus(); //faz com que o applet ganhe o foco.

Thursday, April 06, 2006

 

Modificadores

Outros modificadores que se aplicam às classes são final, abstract e strictfp. Uma classe é marcada como final quando se deseja que os seus métodos não possam ser sobrescritos. Isso significa que se uma classe A é declarada como final, então uma classe B não pode estender A. Uma classe abstrata por outro lado é um classe que jamais pode ser instanciada. Sua razão de ser, então, é que ela seja estendida por outra classe, que, então, irá implementar os métodos abstratos da primeira, caso eles existam. Se uma classe tem métodos abstratos, então a classe deve ser marcada como abstrata. Obviamente, uma classe não pode ser marcada como final e abstract ao mesmo tempo. O modificador strictfp serve para indicar em um método ou em uma classe, que as operações de ponto flutuante seguem o padrão especificado pela IEEE.

Java possui ainda mais dois modificadores para variáveis de instância e um para métodos. São eles, respectivamente: transient, volatile e native. O modificador transient serve para marcar uma variável de instância que deverá ser ignorada em um processo de serialização. volatile serve para assegurar que uma determinada variável de instância tendo por referência a memória principal da JVM e não a memória local da thread. Em algumas situações, 3 threads diferentes podem ter acessado a mesma variável e alterado o seu valor, sem, no entanto, que eles tenham propagado para a memória. Utilizando-se o marcador volatile, evita-se esse tipo de situação. Já o modificador native de métodos serve para indicar que o método é implementado de maneira dependente da plataforma, provavelmente chamando alguma biblioteca em C.

 

Modificadores de Acesso[2]

Apenas dois dos quatro modificadores de acesso se aplicam às classes: default e public. Ter acesso à classe B a partir de A significa (i) que você pode criar em A uma instância de B, (ii) fazer com que A estenda B, ou (iii) acessar as variáveis de instância de B, conforme seja permitido pelos seus modificadores de acesso. Se a classe A é declarada com o acesso default, então uma classe B que esteja em outro pacote não pode estender A, nem ter uma instância de A. Somente classes do mesmo pacote de A podem herdar ou instanciar A. Se a classe A é declarada com o modificador public, entao classes de outros pacotes podem estender A desde que o import apropriado seja declarado. Classes não podem ser declaradas com os modificadores private e protecteda, a não ser que sejam classes internas, neste caso, podem receber o modificador private. Vejamos uma exemplificação dos conceitos acima:


package com;
class A{}

package com2;
class B extends A{}


A classe B não compila, pois ela não tem acesso à classe A. Para compilar B, não é suficiente adicionar o import apropriado, pois a classe A{} continua tendo acesso default, ela é invisível para classes em outros pacotes. Para compilar B, ou colocamos A e B no mesmo pacote, ou declaramos A como pública e adicionamos o import apropiado na classe B.

 

Modificadores de Acesso

Java fornece 4 modificadores de acesso: public, protected, private e default. Se uma variável de instância z é delcarada como public na classe X, então ela pode ser acessada por qualquer outro objeto que tenha a referência para uma instância de X, estando ambas as classes no mesmo pacote ou não. As classes que estendem X também herdam a variável de instância z. default é o modo de acesso padrão de uma instância de variável, não há uma palavra reservada para ele. Se uma variável de instância tem esse tipo de acesso, então somente classes e subclasses do mesmo pacote terão acesso a esta variável. Se uma subclasse de outro pacote estender X, ela nao terá uma variável de instância z. Nisto o protected se diferencia do default. Se z é declarada como protected, então uma classe K de outro pacote pode estender X e ela terá uma variável de instância z. Mas agora vem um dado importante. Esta variável z em K terá a modalidade de acesso private. Por fim, se uma variável de instância z é declarada como privada, então nenhuma outra classe tem acesso a ela, nem mesmo as subclasses do mesmo pacote. Vejamos o seguinte exemplo:


package com;
public class X{
protected int z;
}

package com2;

import com.*;

public class K extends X{
public static void main(String args[]){
X x = new X();
K k = new K();
W w = new W();
System.out.println(x.z);
System.out.println(k.z);
System.out.println(w.k.z);
}
}

package com2;

import com.*;

public class W {
K k;
public W(){
k = new K();
}

public void test(){
System.out.println(k.z);
}
}

O compilador vai acusar z has protected access in com.X em dois momentos. Ao tentar compilar o comando System.out.println(k.z) do método test() de W, e ao tentar compilar o comando System.out.println(x.z) do método main() de K. Embora K estenda X, em K, só podemos acessar z através de herança, ou seja, através de uma instância de K, já que K e X não estão no mesmo pacote. Por outro lado, em W, não podemos acessar a variável de instância z de um objeto K, pois agora essa variável tem acesso privado, muito embora ela tenha sido declarada como protected em X. Em K, no entando, podemoz acessar z por meio de w.k.z, pois dentro de K, z é visível por meio de uma instância de K e w.k é uma instância de K. Por isso, o comando System.out.println(w.k.z) no main() de K não gera erro de compilação.

 

Parâmetro

Em java, a passagem de parâmetros é por cópia do valor. A referência de um objeto não é passada como parâmetro, mas sim uma cópia desta referência. No caso dos tipos primitivos, uma cópia do seu valor é passada. Deste modo, se eu passo a cópia de uma referência para um método e, neste método, faço com que esta referência aponte para outro objeto, a referência original continuará apontando para o objeto anterior. Exemplo:

public class Referencia{
public int x;
public static void main(String args []){
Referencia r = new Referencia();
r.x=8;
r.setNewRef(r);
System.out.println(r.x);
}

public void setNewRef(Referencia r){
r = new Referencia();
r.x=10;
}
}

O resultado impresso será 8. O fato de ter mudado a referência de r em setNewRef() não altera em nada a referência de r em main(). Pois o r de setNewRef() eh uma cópia de r em Main().

Wednesday, April 05, 2006

 

java -D

A opção -D do comando java serve para criar e configurar uma variável de sistema que poderá ser obtida, pela aplicação, por meio do método estático System.getProperties(). Assim, o comando

java -Dprop1=xxx aplicacao

irá criar uma propriedade de nome prop1 e valor igual a "xxx". Não pode haver espaço entre a opção -D e o nome da propriedade.

 

javac

A opção -d do compilador serve para indicar o destino dos arquivos .class gerados. Assim,

javac -d classes com/MyClass.java

irá colocar o arquivo MyClass.class gerado pelo compilador na pasta classes.

Vamos agora supor que MyClass.java pertence ao pacote com.acme. Sua declaração seria a seguinte:

package com.acme;

public class MyClass{...}

Neste caso, o comando

javac -d classes com/acme/MyClass.java

vai colocar o arquivo MyClass.class em classes/com/acme, quer as pastas com e com/acme existam ou não. Caso não existam, ele irá criá-las. O diretório classes, no entanto, precisa existir, caso contrário o compilador irá gerar um erro.

Tuesday, April 04, 2006

 

Thread[3]

Os métodos wait(), notify() e notifyAll() só podem ser chamados no interior de um bloco ou método sincronizado, pois uma thread não pode notificar ou se colocar no modo de espera a não ser que ela possua a chave do objeto. Esses métodos geralmente são usados para a comunicação entre threads que se relacionam em um sistema de produtor e consumidor. O thread produtor usa notify() ou notifyAll() para comunicar à thread consumidora que o produto já está disponível para consumo, enquanto a thread consumidora usa o método wait() para esperar que um produto fique disponível. Abaixo segue um exemplo de um relacionamento entre Threads nesse sistema de produtor e consumidor.

public class Produtor implements Runnable{

int num;
boolean peguei;

public Produtor(){
peguei=false;
}

public void run(){
while (true){
synchronized(this){
if (peguei){
peguei=false;
setNum();
notifyAll();
}
}
}
}

public synchronized int getNum(){
return num;
}

public synchronized void setNum(){
num = (int)(100*Math.random());
}

public boolean getPeguei(){
return peguei;
}

public void setPeguei(boolean p){
peguei=p;
}
}


public class Consumidor implements Runnable{
Produtor produtor;
String name;

public Consumidor(Produtor p, String s){
setName(s);
produtor=p;
}

public void run(){
while(true)
synchronized (produtor){
if(!produtor.getPeguei()){
System.out.println(produtor.getNum()+": "+getName());
produtor.setPeguei(true);
}
else
try{
produtor.wait();
Thread.sleep(10);
}
catch(InterruptedException e){
e.printStackTrace();
}
}
}

public void setName(String name){
this.name=name;
}

public String getName(){
return name;
}

public static void main(String args[]){
Runnable produtor = new Produtor();
Runnable consumidor1 = new Consumidor((Produtor)produtor, "Lucy");

Thread t1 = new Thread(produtor);
Thread t2 = new Thread(consumidor1);

t1.start();
t2.start();

}
}

Saturday, April 01, 2006

 

Thread[2]

O método não-estático t.join(Thread x), onde t é uma instância de Thread, faz com que a execução de t seja dependente do término da execução de x. Caso t esteja ativa, ela deixa este estado e fica inativa até que x morra, quanto, então, t voltará ao estado de executável. O método estático t.yield() faz com que t deixe de sera ativa e volte para o estado de executável caso haja outra thread com prioridade igual a ela no pool de threads. Não há garantia de que o escalonador não vá escolher t novamente para ser ativa.

Para garantir que o método de um objeto seja acessado por uma única thread de cada vez, colocamos a palavra synchronized em sua assinatura:

private synchronized void mudaEstado(){...}

Também é possível sincronizar apenas um trecho de código, ao invés do método inteiro, como no exemplo a seguir:

public class ThreadABC extends Thread{

private StringBuffer buffer;

public ThreadABC(StringBuffer sb){
synchronized(this){
buffer=sb;
}
}

public void run(){
for (int i=0; i<100; i++){
System.out.print(buffer.toString());
}
buffer.setCharAt(0,(char)(buffer.charAt(0)+1));

}

public static void main(String args []){
StringBuffer buffer = new StringBuffer("A");
Thread t1 = new ThreadABC(buffer);
Thread t2 = new ThreadABC(buffer);
Thread t3 = new ThreadABC(buffer);
t1.start();
t2.start();
t3.start();
}
}

Archives

March 2006   April 2006   May 2006   July 2006   August 2006  

This page is powered by Blogger. Isn't yours?