JavaRush /Blogue Java /Random-PT /A história de uma entrevista: questões interessantes
GuitarFactor
Nível 30
Санкт-Петербург

A história de uma entrevista: questões interessantes

Publicado no grupo Random-PT
Recentemente tive a oportunidade de participar de uma entrevista para uma vaga de estagiário em uma das grandes empresas de TI. A história de uma entrevista: questões interessantes - 1Esta foi minha primeira entrevista de TI e, na minha opinião, acabou sendo interessante. No total, fui “interrogado” por mais de 3 horas (isso foi precedido de um dever de casa e de um teste no escritório no computador). Quero prestar homenagem ao entrevistador, que não desistiu quando respondi incorretamente à pergunta, mas com a ajuda de suas perguntas norteadoras me obrigou a pensar e chegar à resposta correta. A seguir apresentarei vários “esboços” - na minha opinião, questões bastante interessantes, algumas das quais me deram uma compreensão mais profunda de certos aspectos de Java. Talvez estas coisas pareçam óbvias para alguns, mas penso que haverá aqueles para quem isto será útil. Abaixo, as frases estão destacadas nas seguintes fontes: Entrevistador - em negrito Explicações de narração e meus pensamentos - em itálico Minhas respostas - em fonte normal Terminamos com o plano de fundo, vamos ao que interessa)

Esboço 1. “Um método aparentemente simples”

Escreva como você implementaria um método que retorna o resultado da divisão do número a pelo número b. O entrevistador escreve em um pedaço de papel
int divide(int a, int b) {
}
*Olhei incrédula para o pedaço de papel com a assinatura do método. Qual é o truque?* Eu escrevo:
int divide(int a, int b) {
    return a/b;
}
Há algum problema com este método? *Estou pegando um idiota muito idiota* Aparentemente não.. Em seguida vem uma pergunta legítima: E se b=0? *Uau, estou prestes a ser expulso deste escritório se continuar assim!* Ah, sim, claro. Aqui temos argumentos do tipo int, então uma Exceção Aritmética será lançada. Se os argumentos fossem do tipo float ou double, o resultado seria Infinity. O que vamos fazer sobre isso? Estou começando a escrever try/catch
int divide(int a, int b) {
    try {
        return a/b;
    } catch (Exception e) {
        e.printStackTrace();
        return ... // ??? what the hack?
    }
}
*Posso retornar e congelar: algo precisa ser retornado em caso de erro. Mas como distinguir este “algo” do resultado do cálculo?* O que retornaremos? Hm... eu mudaria o tipo da variável de retorno para Inteiro e em caso de exceção retornaria nulo. Vamos imaginar que não podemos mudar o tipo. Podemos de alguma forma sair? Talvez possamos fazer outra coisa com exceção? *Aí vem* Também podemos encaminhá-lo para o método de chamada! Certo. Como será?
int divide(int a, int b) throws ArithmeticException{
    return a/b;
}

void callDivide(int a, int b) {
    try {
        divide(a, b);
    } catch (ArithmeticException e) {
        e.printStackTrace();
    }
}
É necessário tratar a exceção? Sim, porque o encaminhamos explicitamente a partir do método divide. (*Eu estava errado aqui! O que se segue são perguntas importantes do entrevistador para chegar à resposta correta*) E Exceção Aritmética - que tipo de exceção é essa - marcada ou desmarcada? Esta é uma exceção de tempo de execução, o que significa desmarcada. *Aí vem a pergunta matadora* Então acontece que, em suas palavras, se especificássemos lançamentos de exceção aritmética na assinatura do método, então ele se tornaria uma exceção verificada? *Ugh!* Provavelmente... não. Sim, desapareceu. Se indicarmos throws /exceção não verificada/ na assinatura, apenas avisaremos que o método pode lançar uma exceção, mas não é necessário tratá-la no método de chamada. Isso está resolvido. Há mais alguma coisa que possamos fazer para evitar erros? *Depois de pensar um pouco* Sim, também podemos verificar se (b==0). E execute alguma lógica. Certo. Então podemos seguir 3 caminhos:
  • tentar/pegar
  • throws – encaminhando para o método de chamada
  • verificação de argumentos
Nesse caso, dividequal método você acha preferível?
Eu escolheria encaminhar a exceção para o método de chamada, porque... no método divide não está claro como processar essa exceção e que tipo de resultado intretornar em caso de erro. E no método de chamada, eu usaria o argumento b para verificar se é igual a zero. Parece que esta resposta satisfez o entrevistado, mas para ser sincero, não tenho certeza se esta resposta é inequívoca))

Esboço 2. “Quem é mais rápido?”

Após a pergunta padrão, como um ArrayList difere de um LinkedList, veio o seguinte: O que acontecerá mais rápido - inserir um elemento no meio ArrayListou no meio LinkedList? *Aqui eu pulei, lembrei que em todos os lugares eu lia algo como “use LinkedListpara inserir ou remover elementos do meio da lista”. Em casa até verifiquei novamente as palestras do JavaRush, tem uma frase: “se você vai inserir (ou deletar) muitos elementos no meio de uma coleção, então é melhor usar LinkedList. Em todos os outros casos.... ArrayList” Respondido automaticamente* Será mais rápido com LinkedList. Esclareça por favor
  1. Para inserir um elemento no meio ArrayList, encontramos o elemento da lista em tempo constante, e a seguir recalculamos os índices dos elementos à direita do inserido, em tempo linear.
  2. Para LinkedList.. Primeiro chegamos ao meio em tempo linear e depois inserimos um elemento em tempo constante, trocando links para elementos vizinhos.
Acontece que o que é mais rápido? Hm... Acontece a mesma coisa. Mas quando é LinkedListmais rápido? Acontece que quando o inserimos na primeira metade da lista. Por exemplo, se você inseri-lo logo no início, ArrayListterá que recalcular todos os índices até o final, mas LinkedListsó terá que alterar a referência do primeiro elemento. Moral: não acredite literalmente em tudo o que está escrito, mesmo em JavaRush!)

Esboço 3. “Onde estaríamos sem iguais e código hash!”

A conversa sobre equals e hashcode foi muito longa - como substituí-lo, qual implementação está em Object, o que acontece nos bastidores, quando um elemento é inserido em HashMap, etc. Vou apenas dar alguns pontos que são interessantes na minha opinião* Imagine que criamos uma classe
public class A {
    int id;

    public A(int id) {
        this.id = id;
    }
}
E eles não substituíram equalse hashcode. Descreva o que acontecerá quando o código for executado
A a1 = new A(1);
A a2 = new A(1);
Map<A, String> hash = new HashMap<>();
hash.put(a1, "1");
hash.get(a2);
*É bom que antes da entrevista eu tenha passado alguns dias especificamente entendendo os algoritmos básicos, sua complexidade e estruturas de dados - ajudou muito, obrigado CS50!*
  1. Crie duas instâncias da classe A

  2. Criamos um mapa vazio, que por padrão possui 16 cestas. A chave é um objeto da classe A, no qual os métodos equalse não são substituídos hashcode.

  3. Coloque a1no mapa. Para fazer isso, primeiro calculamos o hash a1.

    A que será igual o hash?

    O endereço de uma célula na memória é uma implementação de um método de uma classeObject

  4. Com base no hash, calculamos o índice da cesta.

    Como podemos calculá-lo?

    *Infelizmente, não dei uma resposta clara aqui. Você tem um número longo - um hash e há 16 buckets - como definir um índice para que objetos com hashes diferentes sejam distribuídos uniformemente pelos buckets? Eu poderia imaginar que o índice seja calculado assim:

    int index = hash % buckets.length

    Já em casa vi que a implementação original no código fonte é um pouco diferente:

    static int indexFor(int h, int length)
    {
        return h & (length - 1);
    }
  5. Verificamos se não há colisões e inserimos a1.

  6. Vamos passar para o método get. É garantido que as instâncias a1 e a2 tenham um endereço diferente hash(endereço diferente na memória), portanto não encontraremos nada para esta chave

    E se o redefinirmos apenas hashcodena classe A e tentarmos inserir no hashmap primeiro um par com a chave a1 e depois com a2?

    Então primeiro encontraremos a cesta desejada hashcode- esta operação será realizada corretamente. A seguir, vamos começar a examinar os objetos Entrydo LinkedList anexado ao carrinho e comparar as chaves por equals. Porque equalsnão é substituído, então a implementação base é retirada da classe Object- comparação por referência. É garantido que a1 e a2 tenham links diferentes, então “perderemos” o elemento inserido a1 e a2 será colocado no LinkedList como um novo nó.

    Qual é a conclusão? É possível usar como chave em HashMapum objeto não substituído equalshashcode?

    Não, você não pode.

Esboço 4. “Vamos quebrar de propósito!”

Após as perguntas sobre Erro e Exceção, seguiu-se a seguinte pergunta: Escreva um exemplo simples onde uma função lançará StackOverflow. *Então me lembrei de como esse erro me atormentava quando eu estava tentando escrever alguma função recursiva* Isso provavelmente acontecerá no caso de uma chamada recursiva, se a condição para sair da recursão for especificada incorretamente. *Aí comecei a tentar algo inteligente, no final o entrevistador ajudou, acabou ficando tudo simples*
void sof() {
    sof();
}
Como esse erro é diferente de OutOfMemory? *Não respondi aqui, só depois percebi que se tratava de uma pergunta sobre conhecimento Stackde Heapmemória Java (chamadas e referências a objetos são armazenadas na pilha, e os próprios objetos são armazenados na memória Heap). Conseqüentemente, StackOverflow é descartado quando não há mais espaço na Stackmemória para a próxima chamada de método e OutOfMemoryo espaço para objetos acaba na Heapmemória*
Esses são os momentos da entrevista que me lembro. No final fui aceite para um estágio, pelo que tenho pela frente 2,5 meses de formação e, se tudo correr bem, um emprego na empresa) Se houver interesse, posso escrever outro artigo, desta vez mais pequeno, com uma análise de um problema simples, mas ilustrativo que me foi concedido em uma entrevista em outra empresa. Isso é tudo para mim, espero que este artigo ajude alguém a aprofundar ou organizar seus conhecimentos. Feliz aprendizado a todos!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION