Pergunta de entrevista - parece fácil mas não é

61 respostas
saoj

Você tem um objeto Student que precisa ser armazenado num Set e ordenado por idade. Para tal voce decide usar um TreeSet com um Comparator. Complete o código abaixo:

public class Student {

        public int id; // the unique ID of the student...
        public int age; // the student's age...

        public Student(int id, int age) {
            this.id = id;
            this.age = age;
        }

        @Override
        public boolean equals(Object o) {
            if (o instanceof Student) {
                Student s = (Student) o;
                return s.id == this.id;
            }
            return false;
        }

        @Override
        public int hashCode() {
            return id;
        }

        @Override
        public String toString() {
            return "[Student id=" + id + ";age=" + age + "]";
        }


        private static class StudentComparator implements Comparator<Student> {

            @Override
            public int compare(Student s1, Student s2) {

                // ====================
                // YOUR CODE GOES HERE
                // ====================
            }
        }

        public static void main(String[] args) {

            Student s1 = new Student(1, 20);
            Student s2 = new Student(2, 18);
            Student s3 = new Student(3, 22);
            Student s4 = new Student(4, 20);

            Set<Student> set = new TreeSet<Student>(new StudentComparator());
            set.add(s1);
            set.add(s2);
            set.add(s3);
            set.add(s4);

            for(Student s : set) {
                System.out.println(s);
            }
        }
    }

61 Respostas

rmendes08

Cara, acho normal esse tipo de questão aparecer em entrevista. É bem básica mas corta muita gente. Principalmente arrastador de componente.

ViniGodoy

Seria

return s1.age - s2.age;

?

saoj

Ok, mas qual a sua respota para essa questão básica?

saoj

ViniGodoy:
Seria

return s1.age - s2.age;

?

Bom ver que eu não seria o único capado por essa pergunta. :slight_smile:

Isso aí não funciona de jeito nenhum. Pode executar o código pra ver. :slight_smile:

ViniGodoy

Como não?
[Student id=2;age=18]
[Student id=1;age=20]
[Student id=3;age=22]

Resposta perfeita.

Claro, se você queria evitar a eliminação de um Student, então não deveria ter decidido usar um Set. Para evitar a eliminação, só incluindo o id no comparador, mas o enunciado não diz nada a respeito dessa necessidade.

Nesse caso, o código ficaria:

int diff = s1.age - s2.age; if (diff != 0) return diff; return s1.id - s2.id;

rmendes08

Eu teria dado a mesma resposta do Vini, que é a mais óbvia, mas agora que você disse que não funcionou fiquei curioso …

ViniGodoy

Só pq o enunciado é tendencioso e induz ao erro. Ele está baseado em um pressuposto que não está dito.

Zeed01

Boa noite galera…

Considerando que esta usando o Set, como o Vini já falou, a resposta dele me parece correta, para mim também não ficou claro no enunciado o caso de idades iguais…
De qualquer forma uma solução, digamos, mais didática seria:

if (s1.age == s2.age) {
            		if (s1.id != s2.id) {
            			return 1;
            		}
            		return 0;
            	} else if (s1.age > s2.age) {
            		return 1;
            	} else { 
            		return -1;
            	}

[]s

saoj

ViniGodoy:
Como não?
[Student id=2;age=18]
[Student id=1;age=20]
[Student id=3;age=22]

Resposta perfeita.

Claro que essa resposta é errada, né? Um Student diferente foi eliminado do Set sem qualquer cabimento. A tua resposta está certa, mas eu usario o equals dentro do compare, só para enfatizar que eles precisam ser coerentes. :wink:

Quando vc usa comparator ou comparable, o método equals é IGNORADO e o hash/set usa compare/compareTo. Eu não sabia disso até pouco tempo. :frowning:

saoj

Zeed01:
Boa noite galera…

Considerando que esta usando o Set, como o Vini já falou, a resposta dele me parece correta, para mim também não ficou claro no enunciado o caso de idades iguais…
De qualquer forma uma solução, digamos, mais didática seria:

if (s1.age == s2.age) {
            		if (s1.id != s2.id) {
            			return 1;
            		}
            		return 0;
            	} else if (s1.age > s2.age) {
            		return 1;
            	} else { 
            		return -1;
            	}

[]s

Isso tá errado. :slight_smile:

saoj

Não Vini. O enunciado está bem claro. Não faz cabimento descartar um estudante que é DIFERENTE dentro de um SET. :wink:

ViniGodoy

Não concordo. Em momento nenhum o enunciado diz para que serve esse set, ou o critério de desempate.

Como eu falei, se um avaliador realmente queria isso, estava testando se o programador estava atento a pegadinha. Na prática, qualquer um que estivesse programando o método saberia se pode ou não eliminar estudantes, ou qual seria o critério de desempate, caso as idades fossem iguais (isso também não foi informado).

Mas claro, se eu estivesse fazendo a prova e parasse mais de 5 minutos para pensar na questão, eu provavelmente teria dado as duas respostas. Vivo fazendo sistemas com sets desse tipo, com mais de um critério de ordenação/eliminação.

Disso eu sabia. :slight_smile:

ViniGodoy

By the way, não usaria um equals dentro do compare. É lento, ineficiente, e há um campo que pode não só diferencia-los, mas também fornecer um segundo critério de ordenação, que é o id. Veja minha segunda resposta lá. Quando vc respondeu que não funciona, deduzi que não queria eliminar estudantes e corrigi a resposta.

A resposta com o segundo algorítmo foi:
[Student id=2;age=18]
[Student id=1;age=20]
[Student id=4;age=20]
[Student id=3;age=22]

Zeed01

Boa noite galera !

saoj

Porque meu código esta errado ?
Qual é afinal a finalidade ?
A saída para o código que enviei foi:

[Student id=2;age=18]
[Student id=1;age=20]
[Student id=4;age=20]
[Student id=3;age=22]

Se não é isso que o exercício queria, então não entendi o enunciado…
Pode me explicar por favor ?

Obrigado.

[]s

saoj

Não concordo. Em momento nenhum o enunciado diz para que serve esse método, ou o critério de desempate.

Como eu falei, se um avaliador realmente queria isso, estava testando se o programador estava atento a pegadinha. Na prática, qualquer um que estivesse programando o método saberia se pode ou não eliminar estudantes, ou qual seria o critério de desempate, caso as idades fossem iguais (isso também não foi informado).

Não Vini. O método EQUALS estava lá para te dizer que dois estudantes com ID diferentes são objetos DIFERENTES. Esse é o critério que te diz se um estudante é igual ao outro ou não. O seu comparator tinha que obrigatoriamente respeitar isso. Um set armazena estudantes DIFERENTES. O seu set ficou totalmente quebrado porque ele passou a eliminar um estudante de forma totalmente errado. O método equals está ali para te dizer isso.

Sabia mas criou uma inconsistencia braba entre o equals e o compareTo. Isso é um erro grave. :slight_smile:

Eu ainda acho a sua reposta não ideal. Eu prefiro usar equals claramente dentro do compareTo, para não deixar margem para inconsistencias. A regra é clara: compareTo tem que ser consistente com equals. :wink:

Vai ser 3 ticks do clock mais lento… tem razão… alguns nanosegundos… não muitos… :wink:

@Override
		public int compare(Student s1, Student s2) {

			if (s1.equals(s2)) return 0;

			if (s1.age > s2.age) {
				return 1;
			} else if (s1.age < s2.age) {
				return -1;
			}
		
			return s1.id - s2.id;
			
		}
saoj

Zeed01:
Boa noite galera !

saoj

Porque meu código esta errado ?
Qual é afinal a finalidade ?
A saída para o código que enviei foi:

[Student id=2;age=18]
[Student id=1;age=20]
[Student id=4;age=20]
[Student id=3;age=22]

Se não é isso que o exercício queria, então não entendi o enunciado…
Pode me explicar por favor ?

Obrigado.

[]s

O seu programa pode até funcionar, mas vc criou um comparator estranho que te dá:

s1 > s2 AND s2 > s1

para o caso de vc ter dois estudantes diferentes com a mesma idade.

:wink:

Pergunta de entrevista trivial, hein? :slight_smile:

Zeed01

Boa noite Galera !

Ok saoj, você utilizou o equals ao invés de repetir a comparação dos id´s… mas no final o seu codigo funciona da mesma forma que o meu.

[]s

S

saoj:
Zeed01:
Boa noite galera…

Considerando que esta usando o Set, como o Vini já falou, a resposta dele me parece correta, para mim também não ficou claro no enunciado o caso de idades iguais…
De qualquer forma uma solução, digamos, mais didática seria:

if (s1.age == s2.age) {
            		if (s1.id != s2.id) {
            			return 1;
            		}
            		return 0;
            	} else if (s1.age > s2.age) {
            		return 1;
            	} else { 
            		return -1;
            	}

[]s

Isso tá errado. :-)

Esta errado porque? No resultado aparecem os 4 malandros:
[Student id=2;age=18]
[Student id=1;age=20]
[Student id=4;age=20]
[Student id=3;age=22]

[edit]Putz, demorei pra responder! Agora vi a resposta ali em cima [/edit]

ViniGodoy

Não vejo porque a regra do equals e do compareTo tenha que ser idêntica. O próprio Effective Java ressalta que devemos ter cuidado, pois a condição de que objetos “equals” terem compareTo == 0 é apenas no caso de ordenação natural, e pode não ser válida sempre.

Não há qualquer regra no Java, explícita ou implícita, que exija que um comparator retorne 0 em dois objetos que são equals. Esse critério é deixado livre, justamente para que se possa agrupar os objetos em conjuntos de acordo com a sua conveniência, e para que o Comparador crie regras de ordenação e eliminação próprias.

Por isso, ainda acho o enunciado tendencioso e discordo da sua resposta. Se essa era uma regra de negócio, deveria estar explícita no enunciado. Caso contrário, da margem a qualquer interpretação.

saoj

sergiom:
saoj:
Zeed01:
Boa noite galera…

Considerando que esta usando o Set, como o Vini já falou, a resposta dele me parece correta, para mim também não ficou claro no enunciado o caso de idades iguais…
De qualquer forma uma solução, digamos, mais didática seria:

if (s1.age == s2.age) {
            		if (s1.id != s2.id) {
            			return 1;
            		}
            		return 0;
            	} else if (s1.age > s2.age) {
            		return 1;
            	} else { 
            		return -1;
            	}

[]s

Isso tá errado. :-)

Esta errado porque? No resultado aparecem os 4 malandros:
[Student id=2;age=18]
[Student id=1;age=20]
[Student id=4;age=20]
[Student id=3;age=22]

[edit]Putz, demorei pra responder! Agora vi a resposta ali em cima [/edit]

Zeed01

Boa noite galera,

saoj, desculpe minha ignorancia…

Mas pode explicar isso: s1 > s2 AND s2 > s1

E porque ou seu código não cai na mesma situação que o meu…

Lembrando que, concordo que, para mim, utilizar o equals ao inves de repetir a comparação dos id´s é o mais correto

[]s

Zeed01

Boa noite galera !

Concordo com o Vini, fazendo isso: return s1.id - s2.id;
Voce esta considerando que o id é o critério de desempate para o caso de idades iguais, e isso não esta explicito no enunciado, da mesma forma que outra regra qq poderia ser considerada, por exemplo a de que o primeiro objeto a ser inserido deve vir primeiro na ordenação.

[]s

saoj

Fonte: http://download.oracle.com/javase/1.5.0/docs/api/java/lang/Comparable.html

Fonte: http://download.oracle.com/javase/6/docs/api/java/util/Comparator.html

A regra é clara. Quebrar o contrato do objeto para EQUALITY no seu comparator vai quebrar o seu programa inteiro porque um TreeSet ou um TreeMap vão ficar quebrados. Acho que até um HashMap vai se ferrar porque ele vai usar o compareTo do comparator para decidir sobre equality, e NÃO o equals, ou seja, vai se ferrar bonito. (Essa última afirmacao do HashMap eu não tenho certeza, mas apostaria…) :wink:

UPDATE: Num HashMap não tem problema nenhum (caso vc use a interface Comparable). Ele vai sempre pelo hashCode and equals. O problema é realmente só nas collections ordenadas, TreeMap e TreeSet, porque eles ignoram o hashCode e o equals e usam APENAS o compareTo ou o comparable.

saoj

Zeed01:
Boa noite galera,

saoj, desculpe minha ignorancia…

Mas pode explicar isso: s1 > s2 AND s2 > s1

[]s

Isso é uma aberracao lógica. Mas o teu código gera essa expressão, ou seja, s1 é maior que s2 ao mesmo tempo que s2 é maior que s1. Tem que usar os IDs para desempatar idades iguais.

Zeed01

Boa noite Galera,

saoj, utlizar o id para desempatar não é o que você esta fazendo aqui:

return s1.id - s2.id;

?

Eu entendi o que você quis dizer, e concordo, só não concordo em afirmar que esta claro no enunciado que o Id é o critério de desempate…

[]s

saoj

Zeed01:
Boa noite Galera,

saoj, utlizar o id para desempatar não é o que você esta fazendo aqui:

return s1.id - s2.id;

?

Eu entendi o que você quis dizer, e concordo, só não concordo em afirmar que esta claro no enunciado que o Id é o critério de desempate…

[]s

Aí o cara tem que ser esperto para sacar que como critério de desempate vc tem que usar algum campo ÚNICO, se nao vc pode sempre cair novamente num empate. O ID é esse campo. Se fosse uma String vc teria problemas, porque hashCode não é obrigatoriamente único. E note também que como as idades são iguais, não interessa muito quem é maior que quem, desde que vc não crie uma aberracao lógica não-deterministica.

Zeed01

Boa noite galera !

Bom parabéns pela sua esperteza.
Aproveitando, acho que não precisa ser tão esperto para entender que o intuito do forum é a troca de informações e a ajuda mutua, passando sempre pela boa educação…

[]s

saoj

Zeed01:
Boa noite galera !

Bom parabéns pela sua esperteza.
Aproveitando, acho que não precisa ser tão esperto para entender que o intuito do forum é a troca de informações e a ajuda mutua, passando sempre pela boa educação…

[]s

Eu não só fui educado como falei que errei essa questão quando ela me foi apresentada. Compartilhei para outros aprenderem com o meu erro.

saoj

Tentando entender o contra-argumento do pessoal aqui, vcs querem dizer que não ficou claro no enunciado se o Set deveria armazenar APENAS idades diferentes? Poderia até ser mas é muita forcacao de barra vcs não acham? O enunciado diz:

Em nenhum momento ele fala que o set teria apenas IDADES DIFERENTES. Ele fala que o set precisa ser ordenado por idade, e só. Se eu vou colocar estudantes num set, vc tem que assumir que vc tá usando um set para não ter estudantes repetidos ali dentro, certo? Nada a ver com idade e set. :slight_smile:

romarcio

ViniGodoy:
Seria

return s1.age - s2.age;

?

Não entendi porque essa solução do Vini estaria errada, pra mim parece certa. :?:

ViniGodoy

De qualquer forma, é como falei para você. Provavelmente se eu tivesse respondido a prova, com mais calma, eu teria sim colocado a observação de que isso excluiria alunos de mesma idade, e provavelmente teria feito também o método com ordenação por id. E teria errado, pois seria aquele segundo método que, embora seja mais consistente, ainda não respeita o equals totalmente (dois objetos com idades diferentes e mesmo id ainda seriam diferentes no meu comparador, mas geralmente, uma situação dessas representa uma inconsistência mais forte).

ViniGodoy

Só em defesa do SaoJ, também não achei que ele deu uma de esperto. Na verdade, achei bem legal ele ter trazido a questão ao fórum. A discussão também foi muito produtiva.

Eu achei bastante interessante essa questão.

Primeiro, me fez relembrar alguns conceitos, como por exemplo, a paridade entre o compareTo e o equals. Reforço que ele, apesar de recomendado, não é um conceito obrigatório como o caso do HashCode e equals.

De acordo com essa artigo: http://download.oracle.com/javase/tutorial/collections/interfaces/order.html o tal "comportamento estranho" citado anteriormente é justamente eliminar elementos que são duplicados pelo Comparador, mas que não são considerados iguais pelo equals. Curiosamente, eu considero essa uma das grandes, se não uma das maiores, utilidades de um TreeSet, desde que você esteja bem ciente que funciona assim (cansei de eliminar duplicatas por um critério próprio, que não tem nada a ver com o equals, tal como manter as últimas mensagens de um determinado grupo). Mas claro, o fato de eu usar o set assim não indica que seja uma boa prática no caso dessa questão.

Realmente não gostei muito da forma que a questão foi formulada, embora deva admitir que é uma questão bastante inteligente. Acho que ela poderia ser um pouco mais explícita, mas não tira o seu mérito. O interessante é que faz com que uma resposta que parece óbvia não seja tão óbvia assim.

É como a questão que eu gostava de propor para os meus canditatos:

Aponte os problemas que você encontra nessa classe, e diga o que faria para corrigi-los:

public class Turma {
   private List<Aluno> alunos;
   public void Turma(List<Aluno> alunos) {
      for (Aluno a : alunos) {
          if (a.getIdade() < 15) {
             throw new IllegalArgumentException("Você deve ter pelo menos 15 anos para entrar na turma!");
          }
      }
      this.alunos = alunos;
   }

   public void addAluno(Aluno aluno) {
          if (aluno.getIdade() < 15) {
             throw new ArgumentException("Você deve ter pelo menos 15 anos para entrar na turma!");
          }
          alunos.add(aluno);
   }

   public List<Aluno> getAlunos() {
      return alunos;
   }
}

A maioria dos candidatos só indicava o "void" no construtor. Esse erro de sintaxe bobo e facilmente visível fazia com que muitos candidatos imediatamente passassem para a próxima questão, sem nem sequer ver os erros mais graves. Outros falavam da duplicidade da varificação do aluno e citavam que isso clamava por uma refatoração.

Mas os dois problemas mais graves, quase nenhum apontava. Aliás, alguém aqui sabe quais são?

PS: Eu já perguntei isso mais vezes no GUJ, aqueles que já viram a resposta e sabem só por causa disso, por favor, não dêem uma de espertões respondendo. ;)

J

Eu vi duas coisas além as que o Vini apontou. Mas não sei se são essas:

Primeiro, a lista pode não ser inicializada no construtor, de passarmos uma lista como null. Ai na hora de adicionar o aluno é NullPointerException. A mesma coisa na hora de adicionar um aluno, não verifica se ele é nulo, e na hora de chamar o método getIdade, vai dar outro NullPointerException.

Tem outra, que também é essa exceção ArgumentException. Ela não existe na API do java (até onde eu sei né?), então se ela foi criada e estende Exception, ela tem que ser tratada, seja com o try catch, seja passando para o throws na assinatura do método addAluno.

Foi o que eu consegui ver.

josue_carrecon

Hrerere, essa e daquelas que pra quem ta indo, quem vem vindo eh que tah indo.

gomesrod

Sobre a questão do Vini, outra falha é no método getAlunos(), que retorna uma referência da lista que pode ser modificada à vontade.

List<Aluno> alunos = turma.getAlunos();
alunos.add(new Aluno("Joaozinho", 10)); // Adicionei um aluno abaixo da idade sem passar pela validaçao

@SuppressWarnings("unchecked")
List alunosRaw = alunos; // Indo um pouco além, podemos até passar para uma referência de lista sem tipos e ...
alunosRaw.add(new Cachorro("Toto")); //Nao é permitida a presença de animais na sala de aula!

O método getAlunos deveria retornar uma cópia defensiva, ou deveria encapsula-la em uma UnmodifiableList.
Claro que em 99.99% das vezes não fazemos isso…

E uma solução alternativa para o problema do saoj:

int diff = s1.age - s2.age; if (diff != 0) { return diff; } else { boolean randBool = new Random().nextBoolean(); return randBool ? 1 : -1; } :smiley:
[EDIT: Já vi que desse jeito nenhum elemento será considerado repetido, gerando duplicidades no Set… mas fica aí só pela brincadeira]

pmlm

Não sei se erro de copy / paste ou erro propositado mas:

for (Aluno a : alunos) {  
          if (aluno.getIdade() < 15) {
gomesrod

pmlm:
Não sei se erro de copy / paste ou erro propositado mas:

for (Aluno a : alunos) { if (aluno.getIdade() < 15) {


Todos são de propósito :slight_smile:

E eu não tinha percebido… o difícil nesse tipo de problema é que nós automaticamente ajustamos a cabeça para um dos modos “Erros básicos de compilação” ou “Erros de design, validação, segurança, etc”. Sem querer escolhemos um dos “grupos” e só damos atenção a ele.

S

Não seria interessante também criar uma exception específica para a validação da idade e indicar que o método pode lança-la (além de criar um método específico para ela) ?

ViniGodoy

O erro mesmo, mais importante é a ausência de cópias defensivas. Note que ela ocorre em dois casos.

O primeiro, onde o gomesRod ressaltou, é no método getAlunos(). Pode ser resolvido de duas formas:

a) Retornar uma cópia da lista:

public List<Aluno> getAlunos() { return new List<Aluno>(alunos); }

b) Retornar uma versão imodificável da lista:

public List<Aluno> getAlunos() { return new Collections.unmodifiableList(alunos); }

O segundo, é a ausência da cópia defensiva no set. Note que no finalzinho do construtor eu faço
this.alunos = alunos;

O que me permite fazer:

List<Aluno> alunos = new List<Aluno>(); Turma t = new Turma(alunos); t.add(new Aluno("Igor", 10));

A solução é fazer a cópia defensiva da lista também na atribuição:

this.alunos = new ArrayList<Aluno>(alunos);

Quando a exception, não é um erro. Ela seria uma exception não-verificada.
Mas isso foi vício do C#, na minha questão original eu uso IllegalArgumentException.

O a do foreach é intencional também, e trata-se apenas de outro erro de sintaxe onde os candidatos pulam feito abelhas.

Geralmente experamos de candidatos júniores que só vem os erros de sintaxe, e os que estão concorrendo a uma vaga senior que enxerguem pelo menos uma das cópias defensivas. Como desenvolvemos muitas APIs aqui, esse tipo de erro não pode ser tolerado.

O null também não é um problema. A forma que o método é criado garante que ele dispare uma NullPointerException, o que é o comportamento correto de um método que não aceita nulos. Uma opção melhor seria testar o null explicitamente e jogar uma IllegalArgumentException, mas não descontamos sobre quem sugere isso.

Uma outra versão dessa questão faz uso do addAluno no construtor, com a lista previamente inicializada:

public Turma(List<Alunos> alunos) { for (Aluno aluno : alunos) { addAluno(aluno); } }

Mas isso também é um erro. Alguém saberia dizer o porque? E como corrigir?

ViniGodoy

Seria uma opção, mas a ausência dela não constitui um erro, principalmente se você usou uma exception da API geral que é compatível com o problema (como IllegalArgumentException).

J

O método addAluno tem que ser private ou final, para proteger caso alguma subclasse o sobrescreva.

A

ViniGodoy:

Uma outra versão dessa questão faz uso do addAluno no construtor, com a lista previamente inicializada:

public Turma(List<Alunos> alunos) { for (Aluno aluno : alunos) { addAluno(aluno); } }

Mas isso também é um erro. Alguém saberia dizer o porque? E como corrigir?

O fato do método addAluno poder ser sobrescrito numa classe filha?

Hoje em dia tem IDE (o Netbeans pelo menos) que já acusa esse tipo de coisa como warning… facilita seguir boas práticas.

Uma solução seria tornar o método final.

ViniGodoy

Sim, mas isso é especialmente catastrófico nesse caso. Porque esse método está sendo chamado no construtor da classe pai. Se você se lembrar da ordem de construção, classes são construídas de cima para baixo. O que significa que você estará chamando o método de uma parte do objeto que não foi construída ainda.

Isso pode gerar um bug extremamente complicado de se corrigir, principalmente se esse método utilizar algum atributo que só existe no filho.

O comportamento desse atributo é indefinido. A JVM da Oracle faz o seguinte:

  1. Ele vai até o método filho;
  2. Percebe que o atributo não está inicializado;
  3. Inicializa com seu valor padrão (ou o que está na inicialização direta);
  4. Ao terminar o construtor do pai, atinge o construtor do filho;
  5. Re-inicializa o atributo com o valor padrão.

Se você tem um objeto nesse atributo, você acabará com duas cópias do mesmo objeto. Esse tipo de erro é muito comum em Swing, quando a pessoa pensa que pode deixar um template méthod na classe filha para a criação de um painel em algum canto da tela, definida no pai, e chama o “add” desse painel no método initialize, a partir do construtor.

A correção para esse caso é declarar o método addAluno como final. Métodos só podem ser chamados no construtor se forem finais, ou privados.

saoj

ViniGodoy:
O erro mesmo, mais importante é a ausência de cópias defensivas. Note que ela ocorre em dois casos.

O primeiro, onde o gomesRod ressaltou, é no método getAlunos(). Pode ser resolvido de duas formas:

a) Retornar uma cópia da lista:

public List<Aluno> getAlunos() { return new List<Aluno>(alunos); }

b) Retornar uma versão imodificável da lista:

public List<Aluno> getAlunos() { return new Collections.unmodifiableList(alunos); }

O segundo, é a ausência da cópia defensiva no set. Note que no finalzinho do construtor eu faço
this.alunos = alunos;

O que me permite fazer:

List<Aluno> alunos = new List<Aluno>(); Turma t = new Turma(alunos); t.add(new Aluno("Igor", 10));

A solução é fazer a cópia defensiva da lista também na atribuição:

this.alunos = new ArrayList<Aluno>(alunos);

Não vi o problema do construtor. :slight_smile: Mas vi o do getter. Eu prefiro manter DUAS listas internas. Uma mutável e outra imutável, ou seja, toda vez que addAluno é chamado vc adiciona e cria uma nova lista imutável para ser retornada pelo getter. Ficar criando uma nova lista a cada chamada do getter cria muito lixo. Nenhum problema se baixa-latencia não é uma exigencia do seu projeto.

Nesse código aqui:

List<Aluno> alunos = new List<Aluno>(); Turma t = new Turma(alunos); t.add(new Aluno("Igor", 10));

Acho que vc quiz dizer alunos.add(new Aluno(“Igor”, 10)) ao invés de t.add(new Aluno(“Igor”, 10). Um typo. :slight_smile:

saoj

Essa solucao funciona mas não é correta porque não é determinística. Esse seu código aí vai dizer que as vezes s1 > s2 e outras vezes s1 < s2. Isso não pode ser aleatório.

maior_abandonado

edit… quando vi ja tinham respondido isso a 2 paginas atras… fail…rs

ViniGodoy

Esse do rand nem é solução, fere completamente o contrato do compareTo.

Então, eu geralmente retorno a lista imutável chamando o Collections.unmodifiableList. Esse método gera apenas um proxy da lista, que lança exception quando a lista tenta ser alterada, e chama os métodos da lista que ele encapsula quando ela é lida.

maior_abandonado

lendo o que o vinigodoy postou ali atras de que o treeset não adiciona o item quando o compareto retorna 0 com algum de seus itens(eu achei que usava o equals para ver se deveria adicionar ou não e depois saia comparando da maneira mais conveniente, começando pelo meio por exemplo para posicionar), cheguei a uma conclusão bem simples, que nenhum de vocês dois estão errados, quem está errada é a API !!! :lol:.

Vamos a algumas premissas:

um set não deve conter itens iguais;

quando um item comparado com o outro retorna 0, esse é aparentemente ignorado;

compareto retornar 0 não significa que os dois itens sejam equivalentes, apenas que na ordenação ambos podem aparecer qualquer um dos dois na frente do outro, são iguais apenas para ordenação”, mas não são equivalentes.

Vocês concordam que o comportamento que deveria ser outro?

gomesrod

saoj:
gomesrod:

int diff = s1.age - s2.age; if (diff != 0) { return diff; } else { boolean randBool = new Random().nextBoolean(); return randBool ? 1 : -1; } :smiley:

Essa solucao funciona mas não é correta porque não é determinística. Esse seu código aí vai dizer que as vezes s1 > s2 e outras vezes s1 < s2. Isso não pode ser aleatório.


Foi uma brincadeira com a idéia de “se os requisitos não dizem qual o critério de desempate, então podemos usar qualquer coisa”… Mas entendo que tem um monte de falhas, além de ser completamente infame.

saoj

Concordo. Deve haver algum detalhe de implementacao desses algoritmos que fez com que quem implementou fosse para esse lado. É CONTRA-INTUITIVO !

maior_abandonado

me parece até pior do que ser contra intuitivo, ele usa um critério de comparação para verificar equivalencia que simplesmente não garante que os dois objetos são equivalentes ou não equivalentes… apenas diz como devem ser ordenados. Pra mim isso pode até ser considerado bug…

editando aqui… parei para pensar qual seria o impacto em uma alteração no método add de TreeSet, tomemos os dois exemplos de compareTo postados pelo vinigodoy la na primeira página:

return s1.age - s2.age;

e

int diff = s1.age - s2.age; if (diff != 0) return diff; return s1.id - s2.id;

uma suposta utilização do segundo código para evitar esse problema não traria problemas se o método add fosse corrigido para dar um equals no começo e caso false adiciona no set (em casos de compareTo retornando 0 vale a ordem de inserção). Eu penso se não estou sendo inocente ao dizer isso mas o unico caso onde imagino que seria comprometido o funcionamento de um software ja desenvolvido é no caso dele ter sido feito para não adicionar no set mesmo nestes casos, onde era esperada essa não inserção no Set, mesmo com um equals retornando false, o que me parece improvavel alguém ter feito isso (nunca se sabe).

ViniGodoy

Meus softwares certamente iam quebrar.

Utilizar o Set na forma de conjuntos, usando o Comparator como critério de decisão é uma ferramenta muito poderosa. É muito comum fazer isso em estatística ou em métodos de otimização, na construção de índices únicos.

A vantagem é que vc passa a ter um critério obrigatório e passa a usar o Comparator implementando o critério que você quiser. Mas com diz o tio Ben, “with a great power comes a great responsability”. Ou seja, é bom ler atentamente a documentação das APIs (coisa que venho pedindo para o povo aqui no GUJ fazer faz tempo).

maior_abandonado

ViniGodoy:
Meus softwares certamente iam quebrar.

Utilizar o Set na forma de conjuntos, usando o Comparator como critério de decisão é uma ferramenta muito poderosa. É muito comum fazer isso em estatística ou em métodos de otimização, na construção de índices únicos.

A vantagem é que vc passa a ter um critério obrigatório e passa a usar o Comparator implementando o critério que você quiser. Mas com diz o tio Ben, “with a great power comes a great responsability”. Ou seja, é bom ler atentamente a documentação das APIs (coisa que venho pedindo para o povo aqui no GUJ fazer faz tempo).

se tem casos onde o software iria mudaar seu comortamento isso seria um problema…

vinigodoy, a nivel de curiosidade mesmo, você teria um exemplo de código onde a lógica seria quebrada se houvesse essa alteração no add?

luistiagos

algo que todos esqueceram e costuma dar problema, é caso o objeto seja nulo… se não fizer esta verificação é nullpointer na cara…

ViniGodoy

Sim.

Num protocolo precisavamos guardar qual foi a primeira mensagem recebida em cada grupo de mensagens.
A mensagem tinha 5 grupos distintos. Nada mais era do que um TreeSet, ordenado pelo grupo da mensagem.

O equals da mensagem não considerava nenhum dos dois dados, apenas o número do pacote.
O descarte automático servia justamente para só manter a primeira mensagem do set.

luistiagos

Esse tipo de pergunta é complicado… pois vc não sabe o que o examinador pensou ao elaborar a prova… pode ser q seja a alternativa do vini ou a do saoj a correta… depende muito da cabeça do examinador… se ele é esperto o suficiente para observar certos detalhes…

uma vez fiz uma prova que a pergunta era: pq a tampa do bueiro tem q ser redonda.

a reposta certa seria que a tampa não precisar ser exatamente redonda, a menos que a tampa e o bueiro tivessem o mesmo diametro.

e a resposta da cabeça do examinador era: é pra ela não cair no bueiro…

ou seja tudo depende do nível de inteligencia de quem elaborou a prova, como vc não sabe isso, fica complicado…

saoj

Eu continuo achando que a pergunta não é nem um pouco ambigua. Para melhorar, eu colocaria sem mudar em nada o seu significado:

Só que falar uma coisa dessa é totalmente redundante quando se tem um Set. Logo eu realmente não sei qual é a confusão em relacao a pergunta apresentada.

E a questão do desempate não pode ser dada de mão beijada, algo como, use o ID para o desempate. Isso o cara tem que deduzir pelo objeto apresentado.

É complicado mesmo, principalmente quando o cara culpa quem perguntou pelo seu erro. A minha resposta estava certa, a pergunta é que estava errada.

Eu errei essa questão e não me incomodo em admitir isso. É errando que se aprende, não?

rmendes08

Essa postura é legal. Particularmente, eu também ia rodar nessa. Até então, eu era convicto que qualquer implementação de Set usaria o equals para testar se o objeto já está na coleção e que um Comparator seria usado somente para a ordenação.

Acho que o ponto polêmico aqui é se toda implementação do Comparator deve ser coerente com o equals. Como a documentação que o saoj citou, a coerência tem que existir entre o equals e a ordenação natural da classe, apenas. Geralmente, a ordenação natural é obtida com a implementação de Comparable, e não em um Comparator específico. Assim, outros critérios de ordenação não precisam ser coerentes com equals.

O fato, é que quem acompanhou a thread nunca mais vai esquecer desse detalhe …

maior_abandonado

ViniGodoy:
Sim.

Num protocolo precisavamos guardar qual foi a primeira mensagem recebida em cada grupo de mensagens.
A mensagem tinha 5 grupos distintos. Nada mais era do que um TreeSet, ordenado pelo grupo da mensagem.

O equals da mensagem não considerava nenhum dos dois dados, apenas o número do pacote.
O descarte automático servia justamente para só manter a primeira mensagem do set.

intendi… realmente esse seria um caso onde ia quebrar o funcionamento do programa mesmo…

Eu continuo achando que a pergunta não é nem um pouco ambigua. Para melhorar, eu colocaria sem mudar em nada o seu significado:

Só que falar uma coisa dessa é totalmente redundante quando se tem um Set. Logo eu realmente não sei qual é a confusão em relacao a pergunta apresentada.

E a questão do desempate não pode ser dada de mão beijada, algo como, use o ID para o desempate. Isso o cara tem que deduzir pelo objeto apresentado.

É complicado mesmo, principalmente quando o cara culpa quem perguntou pelo seu erro. A minha resposta estava certa, a pergunta é que estava errada.

Eu errei essa questão e não me incomodo em admitir isso. É errando que se aprende, não?

Eu acredito no bom senso… se aprende desse jeito? com certeza, como acabaram de falar ai quem acompanhou a thread desde o começo nunca mais vai esquecer mas… isso não exatamente um erro, essa é uma pergunta que desclassificaria quase todos os programadores que eu ja vi, inclusive otimos profissionais, inclusive seria bem possivel desclassificar alguém que conhecesse o funcionamento certinho da API, como no exemplo do vinigodoy e ainda por cima alguém que seja classificado não necessáriamente seria muito bom… apenas conhece muito bem o funcionamento das coleções no java mesmo o que rola por baixo dos planos… é provavel que essa pessoa senha bem experiênte, mas não necessáriamente será um bom profissional…

Acredito que essa pergunta pode ter multiplas interpretações sim, depende como vocês estavam conversando de se deve ou não interpretar ou pré considerar as informações contidas na questão (uma pessoa que saiba que determinado item não seria incluido no set poderia responder da mesma forma que uma pessoa que não saiba, se ela considerar que isso é irrelevante, não estando no enunciado), o que torna uma questão ineficiente em avaliar o conhecimento do candidato (uma próxima questão perguntando o que uma iteração imprimindo via sysout exibiria resolveria esse problema). Sendo assim não acho um erro “responder errado”…

saoj

Concordo, até porque eu errei. :lol: Falando sério agora, essa pergunta não é para eliminar ninguém. Claro que se o cara acerta ganha muitos pontos. Essa é aquela pergunta EXTRA no final da prova, concorda? Ela é boa para o cara nunca mais esquecer a coisa, não para dizer quem é bom ou não.

ViniGodoy

Pelo sim e pelo não, essa não seria a unica questão da prova. Se vc vir que o perfil geral do candidato é bom, mas ele só escorregou nessa questão, nada impede de vc perguntar para ele numa entrevista posterior à prova se ele percebeu o erro e, se percebeu, como faria para corrigir.

No caso, dependendo da resposta, mesmo que não seja 100% correta, vai dar para sentir que o candidato pelo menos sabe do que está falando, e o quanto sabe.

Criado 11 de julho de 2011
Ultima resposta 12 de jul. de 2011
Respostas 61
Participantes 13