Solucionado (ver solução)
Solucionado
(ver solução)
17
respostas

carga do array empregados por referencia

Não concordo com a solução dada como correta pelo Alura: criar f1...f2...fn... simplesmente porque estaremos colocando dentro do array[10] ponteiros para os objetos f1, f2, etc. Se eles forem destuidos ou modificados, array[10] também o será. Não faz sentido proceder desta forma. Tentei de todos os modos (menos o correto, que não consegui), alimentar o array[10] com os dados, mas "o Java" não permitiu.

17 respostas

Concordo plenamente!!

Plenamente correto

Jorge, acontece que o garbage collector do java só destruirá realmente o objeto depois que a última referência para ele for destruída, ou seja, se você incluir o objeto f1 no array, mesmo que sua variável saia de escopo, o objeto permanece em memória dentro do array (claro, se o array tiver um escopo maior, a nível de classe por exemplo).

Thiago, você concorda com o que? Com a resposta da Alura, ou com a minha afirmação?

Sandro, quando passamos valor por referência, se você altera a origem, quem referencia refletirá a alteração. Vimos isso em dois exercícios/tópicos antes deste exercício e na vídeo aula também. Se eu tenho um array, no máximo eu preciso ter uma classe alimentada, que enviada como parâmetro, alimenta uma posição do meu array. A partir daí, eu posso destruir a classe alimentada, e fazer uso dos dados guardados no array sem problema. Como foi feito no exercício pedido, fizemos uma grande POG (Programação orientada a gambiarra). É péssimo isso, considerando que muitos levarão este conhecimento/conceito (errado) adiante em sua profissão.

Jorge, correto seu conceito de referência. Ao alterarmos o estado de um objeto, suas referências, sejam elas variáveis independentes ou arrays, refletirão essa alteração. Mas veja que se você destruir a variável original, o objeto ainda permanece em memória, porque o array ainda possui um subscrito referindo-se a ele. Foi sobre isso que falou na abertura desse tópico, e isso que eu tinha em mente quando dei a minha resposta. Você não está errado, mas veja que estamos falando de um curso introdutório, para pessoas que PODEM ter pouco ou nenhum conhecimento de programação, e que precisam de exemplos simples, mesmo que sob o ponto de vista de arquitetura de soluções ou engenharia de software sejam considerados violações das boas práticas de desenvolvimento. Mas é necessário que a pessoa consiga fazer algo, mesmo que da pior forma possível, para depois aprender a fazer do jeito certo. Eu tenho cerca de 20anos de desenvolvimento de software em outras linguagens (COBOL, Delphi, VB, VB.NET e C#), e claramente não faria um programa usando esses exemplos, mas imagine uma pessoa que está tentando aprender agora; já é difícil para ela fazer algo desse jeito, e muitas vezes pode ser até uma realização ver um código escrito por ela funcionando. Querer que essa pessoa já inicie com uma visão mais voltada para as boas práticas poderia desestimular o aprendizado. Mas claro, acho que deveriam deixar claro que esses exemplos são para apresentar conceitos de variáveis, vetores, etc. e que mais a frente o futuro programador apresenderá a fazer as coisas do jeito "certo".

solução!

Como o Sandro falou, o garbage collector é quem se encarrega de remover os objetos da memória, e ele só faz isso quando o objeto não é mais acessível(não existe mais uma referência apontando pra ele).

"A partir daí, eu posso destruir a classe alimentada, e fazer uso dos dados guardados no array sem problema."

Você não pode fazer isso!!!

você não tem como destruir um objeto; o máximo que você pode fazer é torna-lo inacessível para que o garbage collector se encarregue de remove-lo da memória. A solução dada a questão ficaria dessa forma:

Empresa empresa = new Empresa();
empresa.empregados = new Funcionario[10];

for (int i = 0; i < 10; i++) {
    Funcionario f = new Funcionario();
    f.salario = 1000 + i * 100; 
    empresa.adiciona(f);
}

cada objeto criado fica em uma posição do array;

note que fora do for nenhum dos objetos esta acessível, porém o garbage collector não pode remove-los da memória pois eles foram adicionados no array mantendo uma referência indireta tornando-os acessíveis. É um código simples porém não há nenhuma Gambiarra.

Pessoal, estamos falando de:

this.funcionarios[i] = func;

Existiria sentido se fosse passado o valor, e não a referência...

this.funcionarios[i].nome = func.nome;

Porque eu vou criar f1, f2, f3?... isso é redundante. Sobre a questão de iniciar aprendendo errado, para depois programar correto, com a experiência... me desculpem, mas não vejo como uma boa prática.

Deixem o garbage fora da história... Isso é coisa pra SO.

Jorge, afinal do que você está falando? Sua postagem inicial dava a entender que você estava com dúvidas sobre o que aconteceria com as variáveis depois que saíssem de escopo, e que não entendia o funcionamento do array com elas. Outra coisa: você está confundindo conceitos, garbage collector não tem nada a ver com SO! Isso é um recurso do JVM (no caso do .NET, do CLR) e não do SO. O SO deve apenas alocar recursos aos programas, a liberação desses recursos é de responsabilidade do próprio programa, o SO só libera recursos totalmente quando o processo encerra de forma normal ou anormal, antes disso não pois poderíamos cair em um deadlock. Não sei qual o seu nível de conhecimento ou experiência com programação, mas eu aprendi sozinho, depois fiz faculdade, e via a dificuldade de alguns colegas que não sabiam programar, em compreender coisas simples como constantes. Isso mesmo, constantes! Imagine a dificuldade de entenderem arrays, collections, listas encadeadas, árvores AVL, etc. Por isso eu disse que não há problemas em se começar fazendo as coisas desse jeito mostrado no curso, pois aos poucos serão apresentados outros modos de se fazer as coisas. Também não se trata de aprender ERRADO, mas apenas uma forma ingênua de programação, que funciona. Errado é exigir de um iniciante o raciocínio de um desenvolvedor experiente. Um detalhe que você está esquecendo, quando começar a tratar de injeção de dependências, sem um framework para fazer isso, você terá que instanciar um objeto e depois atribuí-lo à um atributo de outra classe, seja via construtor, seja via setter. Um dos padrões de projeto GoF, chamado Observer usa internamente uma collection ou um array que recebe a referência a objetos observadores, e uma das várias formas de fazer isso é exatamente:

this.Observers[i] = observer;

O importante aqui é ver como as coisas funcionam, depois podemos aprimorá-las. Você está tendo uma visão errada do curso. O curso em questão é um curso introdutório, e parte-se do princípio que a pessoa não tem qualquer conhecimento de programação. Da forma como é feito, funciona, mesmo que não seja bonito.

Pois é Sando eu estou achando que o Jorge está confundindo as coisas:

classe alimentada??? destruir a classe??? garbage collector de SO???

e não há nenhum problema em passar uma referência de objeto, ainda mais se esse objeto for criado no escopo local de um for.

E não há nenhuma redundância em: f1, f2, f3... desde que cada um referencie um espaço de memoria diferente.

João, ok, Garbage é do Java. Criar vários f's para alimentar um array[?] não é redundante? O razoável é que vc coloque os dados em um f e alimente o seu array[0], depois coloque novos dados em f, e alimente array[1], e assim por diante. E não ter f1..f10 para manter referencias em array[0..9] para f1..f10. Imagine 100, 1000 itens de entrada, o tamanho que o seu código main() vai ficar.

Jorge, mais uma vez: esse é um curso introdutório. Ninguém vai fazer um sistema complexo desta forma, nem instanciar 1000 variáveis em um método main para fazer isso. Quando estudar padrões de projeto e matérias relacionadas, você terá métodos ou objetos construtores de outros objetos. Outra coisa que você está confundindo no seu exemplo acima: se você cria uma variável f, atribui os valores de seus membros, insere no Array, e depois altera os dados de f e insere em outra posição do array, você fica com dois subscritos no array apontando para o mesmo objeto que agora possui os dados alterados, ou seja, se no primeiro momento atribuo o nome Jorge a f, insiro no Array, depois altero o nome de f para Sandro e insiro no array, tanto a primeira posição quanto a segunda posição do array apontarão para o mesmo objeto em memória, e ambos retornarão o nome Jorge. O nome Sandro é perdido. Por isso o uso de mais de uma variável nesse exemplo INTRODUTÓRIO, para que cada subscrito do array aponte para uma posição diferente em memória. Para fazer o que você quer, deveria após a inclusão de f no array, instanciar um novo objeto na variável f, e aí sim atribuir o nome diferente. Faça o teste com as classes abaixo:

public class pessoa {

    String nome;

}
public class TesteArray {

    public static void main(String args[]){

        pessoa f1 = new pessoa();
        pessoa f2 = new pessoa();
        pessoa[] vetor=new pessoa[10];

                //Duas variáveis como na nossa discussão, sem mistérios...
        f1.nome="Sandro";
        f2.nome="Jorge";

        vetor[0]=f1;
        vetor[1]=f2;

                //Aqui depois de incluído o objeto f1, altero o seu nome e atribuo a outra posição no array        
                f1.nome="João";
                vetor[2]=f1;

                //Já aqui, destruo o objeto original, crio outro e atribuo a f2 e incluo no array
                f2=new pessoa();
                f2.nome="José";
                vetor[3]=f2;

        for (int i = 0; i<=3; i++) {
            System.out.println(vetor[i].nome);
        }


    }

}

Perceba que quando o loop rodar, o nome Sandro foi perdido, repetindo duas vezes João. Isso porque o objeto original foi alterado, e o vetor não mantém uma cópia do objeto (claro que se f for um objeto de valor, como String, isso não se aplica, mas os objetos que normalmente manipulamos não são objetos de valor mas de referência).

João, para fins didáticos, me mostra uma classe que guarda valores passados de f, e não referência: tipo... this.funcionarios[i].nome = func.nome;

onde o array está uma classe e func é definido na main().

Ha cara eu acho que entendi onde você está confundindo. Quando eu faço: empresa.empregados = new Funcionario[10];, não quer dizer que tenho um array com 10 Funcionarios dentro, quer dizer apenas que tenho 10 espaços disponíveis na memória para adicionar 1 Funcionario em cada posição. Eu não posso simplesmente fazer:

empresa.funcionarios[0].nome = "Jorge";

porque vai dar nullPointerException. Eu tenho que para cada posição fazer:

empresa.funcionarios[0] = new Funcionario();
empresa.funcionarios[1] = new Funcionario();

para a partir daí adicionar valores. Se for segui o seu raciocínio, alem de criar os 10 objetos do array eu teria de criar um 11° para ficar atualizando os valores de cada um, além disso você estaria quebrando o conceito de Orientação a Objeto ao criar um funcionário "Mutante" a "Mística" no caso :). Imagine o seguinte empresa com 3 funcionários, usando a sua abordagem:

Empresa empresa = new Empresa();
empresa.empregados = new Funcionario[3];

Funcionario f = new Funcionario();

f.nome = "Jorge";

empresa.funcionarios[0] = new Funcionario();
empresa.funcionarios[0].nome = f.nome;

f.nome = "Sandro";
empresa.funcionarios[1] = new Funcionario();
empresa.funcionarios[1].nome = f.nome;

f.nome = "João";
empresa.funcionarios[2] = new Funcionario();
empresa.funcionarios[2].nome = f.nome;

nenhuma pessoa pode ser 3, a menos que você seja Deus :). Não tem problema algum nesse código, porém não tem a devida coerência, pois ninguém tem um nome pela manhã, outro a tarde e outro a noite, (algumas até tem :D). Imagina que eu quero dar uma altura pra cada um, eu teria que ir numa linha específica pra dizer a altura de Jorge, em outra pra dizer a altura de Sandro, perceba como isso vai ficando inviável. O mais correto seria fazer:

Funcionario jorge = new Funcionario();
Funcionario sandro = new Funcionario();
Funcionario joao = new Funcionario();

porque cada um tem sua própria identidade, veja que se eu quiser dizer a altura de joão, basta fazer joao.altura = 1.76. Mais uma vez, não ha problema em passar a referência pra seu array, desde que você como programador, garanta a integridade dela.

E em relação ao tamanho do array que você se referiu seja 10, 100, 1000, 10000... não existe mágica você vai ter que criar todos ele e adicionar dentro do array, por isso a sugestão do for:

Empresa empresa = new Empresa();
empresa.empregados = new Funcionario[1000000];
for (int i = 0; i < 1000000; i++) {
    Funcionario f = new Funcionario(); 
    empresa.adiciona(f);
}

Exatamente o que o João falou. Um array com 10 posições indica apenas ao compilador que ele deve alocar espaço em memória para receber 10 referências a objetos daquele tipo definido, mas essa memória está "zerada", ou seja, não tem nenhum objeto residindo lá. Ao atribuir um objeto à uma posição do array, você está armazenando na verdade um ponteiro para outra posição em memória que contém o objeto em si, posição essa que não é a sua variável f1 ou f2 em si, pois ela própria é um ponteiro para outra região de memória. Ao fazer uma atribuição a uma posição do array, estamos colocando nele o valor do ponteiro, o endereço de memória onde o objeto reside realmente, não colocando o objeto em si dentro do array. Daí toda a confusão. Parece que você, Jorge, está confundindo o dimensionamento do array com a criação de objetos automaticamente, e não é isso que acontece. Existem alguns tipos de objetos nativos da linguagem que são passados por valor, é o caso de String. Se você alterar aquele exemplo que eu dei, usando um array de strings, e no lugar de objetos do tipo pessoa usar variáveis string normais, você verá que não ocorre perda, pois um string sempre é passado por valor. Mais a frente você verá que uma string é imutável em memória, ao alterar o valor da string, mesmo que concatenando uma outra string à ela, o compilador na verdade irá criar uma nova string em memória para receber o resultado da alteração, provavelmente em outra posição, destruir a string original, e trocar o valor do ponteiro referenciado pela memória. Mas você não perceberá isso, pois ao consultar a sua variável ela terá exatamente o conteúdo que você indicou, dando a impressão que a string mudou, quando na verdade você tem outro objeto em memória.

João e Sandro, entendi o posicionamento de vocês, e consegui fazer a solução como eu imaginava, a partir do exemplo do João e com a concordância/explicação do Sandro.

Entendi/aprendi o seguinte conceito:

Uma array de tipos primitivos guarda valores, uma array de objetos guarda referências.

//Funcionario f = new Funcionario(); 
--> definir f antes do FOR é errado, 
--> pois usará sempre a mesma referência, 
--> onde foi criado f...

for (int i = 0; i < 5; i++) {
  Funcionario f = new Funcionario();
  f.salario = 1000 + i * 100;
  empresa.adiciona(f);
}

--> Temos de instanciar Funcionario dentro do laço. 
--> Se a instanciação de Funcionario ficasse acima do laço, 
--> estaríamos adicionado cinco vezes a mesma instância de 
--> Funcionario nesta Empresa e apenas mudando seu salário 
--> a cada iteração, que nesse caso não é o efeito desejado.

Abaixo transcrevo o código comprovando isso. Com o FOR é possível ter somente uma variável f, sem precisar definir f1, f2. Confirmei que a referência é mantida mesmo após o final do FOR.

class MinhaEmpresa {
    public static void main (String[] args) {
        Empresa empresa = new Empresa();
        empresa.empregados = new Funcionario[10];
        empresa.razao = "MONSTROS S/A";
        empresa.cnpj = "12.345.678/0001-90";
        empresa.indice = 0;

        for (int i = 0; i < 3; i++) {
            Funcionario f = new Funcionario();
            f.nome = "NOME DO FUNCIONARIO ["+i+"]";
            f.endereco = "ENDERECO DO FUNCIONARIO  ["+i+"]";
            empresa.adiciona(f);
        }
        empresa.mostraEmpresa();
        empresa.mostraEmpregados();
    }
}

class Funcionario {
    String nome;
    String endereco;
}    

class Empresa {
    String razao;
    String cnpj;
    int indice;
    Funcionario[] empregados;

    void adiciona(Funcionario f){
        this.empregados[indice] = f;
        this.indice++;
    }

    void mostraEmpregados(){
        for (int i=0; i < this.empregados.length; i++) {
            if (this.empregados[i] == null) continue;
            System.out.println(this.empregados[i].nome);
            System.out.println(this.empregados[i].endereco);
            System.out.println("- - - - - - - - - - - - - - - - - - - - Posicao["+i+"]");
            System.out.println("");
        }
    }

    void mostraEmpresa(){
        System.out.println("");
        System.out.println("Empresa: "+razao);
        System.out.println("CNPJ   : "+cnpj);
        System.out.println("- - - - - - - - - - - - - - - - - - - -");
        System.out.println("");
    }
}

Vou marcar o João como quem apresentou a solução, devido ao exemplo do FOR, que me levou a conseguir o que eu desejava no código. Obrigado Sandro, pelas dicas que colaboraram para o entendimento da matéria.

Por nada, estamos aí para ajudar.