• Revista PROGRAMAR: Já está disponível a edição #53 da revista programar. Faz já o download aqui!

Baderous

Onde e como codificar métodos de escrita/leitura de streams?

19 mensagens neste tópico

Tenho dúvidas relativamente à codificação de métodos de leitura/escrita em ficheiros usando streams.

Tenho em ideia que os métodos de escrita, quer usando streams de objectos, quer de caracteres, devem ser codificados nas classes que pretendemos que implementem a possibilidade de serem escritas em ficheiro. Mas será que estou certo? É que já vi nos apontamentos da cadeira de Java que estou a fazer que existia um método de escrita de caracteres em ficheiro definido na classe e depois existia outro igual definido na classe de teste. Podem ver aqui. Estou a falar da classe Stock com o método gravaTxt e da classe TesteStock com o método gravaFichTxt.

Também não sei onde devem ser escritos os métodos de leitura. É na classe de teste (quer leitura usando streams de objectos, quer de caracteres)?

Depois, também relacionado com o problema anterior, tenho dúvidas relativas ao modo como estes métodos devem ser codificados. Porque às vezes, vejo os métodos escritos com try-catch, mas noutras vejo escrito com throws excepção no cabeçalho, sendo o try-catch feito quando a classe principal invoca esse método. Sei que se se colocar no cabeçalho do método as excepções que são lançadas, pode-se depois fazer o try-catch na invocação do método, mas, segundo aquilo que li, esse método deve ter algures no seu código, uma instrução throw excepção (a qual será tratada então no try-catch). O que acontece (como podem ver no pdf que postei) é que não é escrito qualquer throw nos métodos e isso está-me a fazer alguma confusão.

Alguém que me explique, porque isto parece-me ser apenas uma questão de escrever as coisas correctamente.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites
Depois, também relacionado com o problema anterior, tenho dúvidas relativas ao modo como estes métodos devem ser codificados. Porque às vezes, vejo os métodos escritos com try-catch, mas noutras vejo escrito com throws excepção no cabeçalho, sendo o try-catch feito quando a classe principal invoca esse método. Sei que se se colocar no cabeçalho do método as excepções que são lançadas, pode-se depois fazer o try-catch na invocação do método, mas, segundo aquilo que li, esse método deve ter algures no seu código, uma instrução throw excepção (a qual será tratada então no try-catch). O que acontece (como podem ver no pdf que postei) é que não é escrito qualquer throw nos métodos e isso está-me a fazer alguma confusão.

Relativamente a esta parte, acho que já percebi, pelo menos a questão de uns não terem throw no meio do código e outros terem. Penso que só têm throw aqueles métodos onde as excepções que são explícitas (programadas), os outros (implícitas, ou seja, lançadas automaticamente pela JVM) não têm (por ex: IOException).

Mesmo assim gostava de saber a vossa opinião.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

A forma como irás colocar os métodos de escrita é, como em qualquer outra questão relacionada com POO, dependente do sistema que estás a modelar e como pretendes que esse sistema funcione.

Em termos de funcionalidade, é igual colocares os métodos em qualquer objecto dos que mencionaste. Apesar de ter passado apenas os olhos por cima do exemplo, acho que, mesmo existindo os dois métodos, só é usado um, penso que tenha sido criado assim apenas porque é para exemplo.

Quando fazes a análise do sistema a implementar, essas questões são resolvidas facilmente no decorrer da análise. Se tivesse de arriscar uma "regra", dir-te-ia que o objecto que quer ser gravado deve implementar os métodos de gravação. Mas se pensarmos, por exemplo, num programa que guarda endereços de pessoas, em que temos a classe Pessoa e classe LivroDeEnderecos, o que fará mais sentido, ser a classe pessoa ou a classe LivroDeEnderecos a ter os métodos de gravação? Neste caso acho que deverá ser a classe LivroDeEndereços, dado que nós gravamos livros, abrimos livros, etc. Mas será que essa classe deverá saber guardar pessoas? Ou a classe Pessoa deve saber guardar os seus atributos? Se pensarmos em caso de encapsulamento, terá de ser a classe Pessoa a guardar os seus atributos dado que podem existir condições especiais que só essa classe sabe resolver. Assim terias a classe LivroDeEndereco que sabe guardar os seus atributos, neste caso, percorrer todas as pessoas que contém e dizer-lhes para se guardarem, dado que apenas elas sabem guardar o seu estado interno.

Resumindo, depende do que tens de fazer :(

Quanto a apanhar as excepções ou propagar, novamente depende do que estás a fazer, pessoalmente acho melhor que as excepções de IO sejam apanhadas logo no sítio onde são criadas, isto porque as excepções servem para controlo de erros. No caso das streams, deves fecha-las no método onde as abriste.

Repara que se no método grava abres uma stream e em vez de apanhares uma possível excepção e a propagares, colocando na assinatura do método a excepção que o método lança, não vais conseguir fechar a stream, uma vez que esse método vai terminar e o controlo de execução irá para o método que o invocou, a não ser que este último método tenha acesso à stream que foi aberta, perdeste a referencia e  não vais conseguir fechar uma stream onde ocorreu um erro. Resultado, vais ficar com recursos pendentes na aplicação, vais consumir mais recursos que devias.

1

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Ok, acho que já percebi. Isto agora vai lá com um bocado de treino. Thanks!

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Estou outra vez com dúvidas nesta cena.

Quero que uma classe que eu criei com 5 variáveis de instância seja gravada numa ObjectStream.

Agora não sei é como codificar o método.

Hipótese 1: Indico que o método lança 1 excepção, a qual trato apenas na main, quando o invoco.

public void gravaObjStream(String fich) throws IOException {
        ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(fich));
        oout.writeObject(this);
        oout.flush(); oout.close();
    }

/* Na main faço o tratamento */
...
try {
gravaObjStream(ficheiro);
} catch(IOException e) {System.out.println(e.getMessage()); }

Hipótese 2: Faço o tratamento da excepção no método que a lança.

public void gravaStreamObj(String fich) throws IOException {
ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(fich));
try {
	oout.writeObject(this);
} catch(IOException) { System.out.println(e.getMessage()); }
finally {
	oout.flush();
	oout.close();
}
}

Acho que, segundo o post do Knitter, a 2ª opção é a melhor pois o tratamento é local e permite o fecho da stream em caso de existir uma excepção, certo?

Btw, o código da 2ª hipótese está correcto, isto é, deve ter no cabeçalho o throws IOException, sendo o tratamento feito localmente? E quanto ao finally, está bem colocado ali com as 2 instruções de flush e close?

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Btw, o código da 2ª hipótese está correcto, isto é, deve ter no cabeçalho o throws IOException, sendo o tratamento feito localmente? E quanto ao finally, está bem colocado ali com as 2 instruções de flush e close?

Se apanhas a excepção então na vale a pena colocares a indicação que a vais lançar, aliás, esse código não deve compilar :P

Depois, o flush deve ser feito na situação normal, pelo que terá de passar para o bloco try. Mas repara que podes estar a fazer um flush desnecessário, o close já faz um flush.

Quanto ao finally, não estás a ter em consideração que quando ocorre uma excepção, a variável oout pode não estar iniciada, pelo que ao fazeres oout.close() podes ter uma NullPointerException, que por não estares a apanhar, vai ser propagada até ao utilizador :D

Uma nota, não se apanham runtime exceptions por isso, apesar de ter dito que não estavas a apanhar a NPE, não quero dizer que a terás de apanhar, apenas que terás de prevenir a situação.

Exemplo:

public void gravaStreamObj(String fich) {
ObjectOutputStream oout = null;
try {
                oout = new ObjectOutputStream(new FileOutputStream(fich));
	oout.writeObject(this);
} catch(IOException e) {
             e.printStackTrace();//Correctamente deverias efectuar o logging da excepção.
                                        //System.out não é garantido que seja apresentado.
                                        //Neste exemplo também não estou a fazer logging
}finally {
            if(oout != null) {//oout pode estar a null
	oout.close();
            }
}
}

Caso pretendas usar o mecanismo de excepção para controlo fora deste método, coisa que deves fazer sempre que seja algo que o utilizador possa fazer, podes propagar a excepção.

public void gravaStreamObj(String fich) throws MinhaExcepcao {
ObjectOutputStream oout = null;
try {
                oout = new ObjectOutputStream(new FileOutputStream(fich));
	oout.writeObject(this);
} catch(IOException e) {
            MinhaExcepcao me = new MinhaExcepcao ("Falhou: " + e);
            me.initCause(e);
           throw me;
} finally {
            if(oout != null) {//oout pode estar a null
	oout.close();
            }
}
}

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Percebi a 1ª solução, mas quanto à 2ª não percebi:

Caso pretendas usar o mecanismo de excepção para controlo fora deste método, coisa que deves fazer sempre que seja algo que o utilizador possa fazer, podes propagar a excepção.

Não percebi muito bem esta frase. No exemplo que dás depois, lanças uma excepção MinhaExcepcao. É essa que será tratada então pelo utilizador (que é o que me parece com o throw me)? Se sim então no método que invoca gravaStreamObj, deverei ter algo:

try {
gravaStreamObj(file);
} catch(MinhaExcepcao e) { e.printStackTrace(); }

?

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

+/-, o método printStackTrace(), termina a execução da aplicação mostrando o registo da stack onde aconteceu a excepção. Por isso deves apanhar a excepção mas deverás tratá-la convenientemente :P

Não tens necessariamente de criar uma excepção nova, podes simplesmente propagar a excepção que recebeste dentro do catch.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Imaginemos que tinha uma classe Tipo que implementava um determinado tipo de qualquer coisa.

Depois tinha uma classe Lista que implementava uma Lista de Tipos.

Depois tinha uma classe teste, onde criava uma instância dessa Lista com vários Tipos lá dentro e guardava num ficheiro de objectos.

Se depois eu quiser ler desse ficheiro de objectos essa mesma lista e reconstituí-la, tenho de, obrigatoriamente implementar, quer na classe Lista, quer na Tipo, métodos de leitura de ObjectStream, para que os objectos possam ser reconstituídos correctamente, certo?

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Se estás a falar de ObjectStream, estás a falar de Serialização certo?

Então depende, se estiveres a serializar a classe Lista, então todos os seus objectos têm de ser serializáveis. Se estás a serializar os objectos dentro da lista então a lista não precisa ser serializável.

Se estiveres a usar as listas do Java, não vejo porque não estejas, então as listas já são serializáveis.

Não sei se respondi à pergunta.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Quanto à serialização, todas as minhas classes implementam a interface Serializable.

Pegando num exemplo prático:

Seja EmpresaList uma classe que implementa um ArrayList de Empregados, tendo EmpresaList e Empregados implementado a interface Serializable.

Se numa classe de teste onde queiramos ler uma EmpresaList de um ficheiro de objectos fizermos o seguinte:

EmpresaList lstEmp = new EmpresaList();
try {
ObjectInputStream ooin = new ObjectInputStream(new FileInputStream("emp1.obj"));
lstEmp = (EmpresaList) ooin.readObject(); ooin.close();
}
catch (IOException e) {...}
catch (ClassNotFoundException ec) {...}

Conseguimos reconstruir correctamente a instância de EmpresaList?

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Esta linha, EmpresaList lstEmp = new EmpresaList();, é desnecessária, pelo menos a iniciação dado que depois vais substituir a instância.

E sim, consegues reconstruir correctamente a lista, tens de ter atenção que se gravares um objecto, depois alterares alguma coisa na classe e tentares ler o objecto, os dados guardados são inválidos pelo que perdes os dados.

Mas sim, se guardaste apenas uma lista então ao leres vais recuperar essa lista e todos os elementos que não sejam transient.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Não li este tópico todo pois já está demasiado grande e peço desculpa se repetir o que já foi dito. Mas, se reparares, na última página do PDF "STREAMS_JAVA_ESSENCIAL" que está no site da cadeira fala como deves codificar os métodos de escrita/leitura.

Pessoalmente, estou a fazer da seguinte forma. Cada classe tem um método estático para ler o objecto e um método "normal" que escreve o objecto e o código é igual ao seguinte com as diferenças necessárias para cada classe:

public static FichaTripulante carregaObjecto(String ficheiro) throws IOException, ClassNotFoundException {
    ObjectInputStream objInputStream = new ObjectInputStream(new FileInputStream(ficheiro));
    FichaTripulante fichaTripulante = (FichaTripulante)objInputStream.readObject();

    objInputStream.close();

    return fichaTripulante;
}

public void gravaObjecto(String ficheiro) throws IOException {
ObjectOutputStream objOutputStream = new ObjectOutputStream(new FileOutputStream(ficheiro));
    objOutputStream.writeObject(this);
    objOutputStream.close();
}

Depois é só usa-los... E como ambos lançam excepções, somos obrigados a ter try/catch onde chamar-mos estes métodos.

Só uma dúvida relativamente ao flush, ele é mesmo chamado quando se faz um close? É que em todos os documentos/exemplos do prof, ele chama sempre o flush antes do close... E acho estranho ele fazer isso se realmente não fosse necessário.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

E porque é que tens um método de leitura em cada classe? Nos restantes exemplos de exercícios, as classes tinham apenas os métodos de escrita, sendo o método de leitura codificado na classe de teste. Será essencial ter um método de leitura em cada classe?

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

No meu caso, retorno sempre o tipo da classe que estou a ler em vez de ter um global que devolva um objecto... Se fosse um object, como é que eu depois saberia o tipo dele? Eu sei que há maneiras de o fazer, mas assim é mais directo...

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Só não concoerdo com uma coisa nesse código, ao ocorrer uma excepção não é possível fechar a stream, deixas recursos pendurados na aplicação.

Quanto ao close, o comportamento normal é ele fazer um flush, pode acontecer que não esteja a ser feito, mas um close deverá libertar todos os recursos associados, o que implicará esvaziar qualquer buffer que seja usado. Se repararem que os vossos dados não estão a ser guardados pode ser por isso, experimentem invocar um flush.

Estive a ver a documentação e no caso da ObjectOutputStream não é indicado se faz ou não um flush.

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Eu estava realmente a falar apenas do ObjectOutputStream. Em todas as minhas classes tinha um flush() antes do close() mas removi essa linha por alguma razão (só não me lembro qual), mas agora que tive a ver todos os exemplos de código do prof, ele tem um flush e por isso estou na dúvida...

0

Partilhar esta mensagem


Link para a mensagem
Partilhar noutros sites

Crie uma conta ou ligue-se para comentar

Só membros podem comentar

Criar nova conta

Registe para ter uma conta na nossa comunidade. É fácil!


Registar nova conta

Entra

Já tem conta? Inicie sessão aqui.


Entrar Agora