Por que a criação de instância é do jeito que é?

17

Eu aprendi C # nos últimos seis meses ou mais e agora estou investigando Java. Minha pergunta é sobre a criação de instâncias (em qualquer idioma, na verdade) e é mais sobre: ​​Eu me pergunto por que eles fizeram dessa maneira. Veja este exemplo

Person Bob = new Person();

Existe um motivo para o objeto ser especificado duas vezes? Alguma vez haveria um something_else Bob = new Person()?

Parece que se eu estivesse seguindo a convenção, seria mais como:

int XIsAnInt;
Person BobIsAPerson;

Ou talvez um destes:

Person() Bob;
new Person Bob;
new Person() Bob;
Bob = new Person();

Suponho que estou curioso para saber se há uma resposta melhor do que "é assim que se faz".

Jason Wohlgemuth
fonte
26
E se Person for um subtipo de LivingThing? Você poderia escrever LivingThing lt = new Person(). Procure por herança e interfaces.
Xlecoustillier
2
Person Bobdeclara uma variável do tipo "referência a Person" chamada Bob. new Person()cria um Personobjeto. Referências, variáveis ​​e objetos são três coisas diferentes!
user253751
5
Você está irritado com a redundância? Então por que não escrever var bob = new Person();?
200_success
4
Person Bob();é possível em C ++ e significa quase a mesma coisa quePerson Bob = Person();
user60561
3
@ user60561 não, declara uma função sem argumentos e retornando Person.
Nikolai

Respostas:

52

Alguma vez haveria algo mais = Bob = new Person ()?

Sim, por causa da herança. E se:

public class StackExchangeMember : Person {}

Então:

Person bob = new StackExchangeMember();
Person sam = new Person();

Bob também é uma pessoa e, caramba, ele não quer ser tratado de maneira diferente de qualquer outra pessoa.

Além disso, poderíamos dotar Bob de super poderes:

public interface IModerator { }
public class StackOverFlowModerator : StackExchangeMember, IModerator {}

IModerator bob = new StackOverFlowModerator();

E assim, por Deus, ele não aceitará ser tratado de maneira diferente de qualquer outro moderador. E ele gosta de esgueirar-se pelo fórum para manter todos na fila enquanto estão no modo anônimo:

StackExchangeMember bob = new StackOverFlowModerator();

Então, quando ele encontra um pobre primeiro pôster, ele joga sua capa de invisibilidade e ataca.

((StackOverFlowModerator) bob).Smite(sam);

E então ele pode agir de forma inocente e outras coisas depois:

((Person) bob).ImNotMeanIWasJustInstantiatedThatWay();
radarbob
fonte
20
Isso seria muito mais claro se você aplicasse as letras minúsculas nos nomes dos objetos.
Lightness Races com Monica
38
Bom exemplo da especificação; mau exemplo de herança. Para qualquer pessoa que esteja lendo isso, por favor, não tente resolver as funções de usuário usando herança.
Aaronaught
8
@Aaronaught está correto. Não crie classes separadas para diferentes tipos de pessoas. Use um campo de bits enum.
Cole Johnson
1
@Aaronaught Está tudo muito bem dizendo o que não fazer, mas não ajuda muito sem dizer o que as pessoas deveriam fazer.
Pharap
5
@ Sharap: Eu fiz exatamente isso, em várias outras perguntas . A resposta simples é que os usuários (autenticação / identidade) e a política de segurança (autorização / permissões) devem ser tratados como preocupações separadas, e os modelos padrão para a política de segurança são baseados em funções ou em declarações. A herança é mais útil para descrever o objeto que realmente faz a autenticação, por exemplo, uma implementação LDAP e uma implementação SQL.
Aaronaught
34

Vamos pegar sua primeira linha de código e examiná-la.

Person Bob = new Person();

A primeira Personé uma especificação de tipo. Em C #, podemos dispensar isso simplesmente dizendo

var Bob = new Person();

e o compilador inferirá o tipo da variável Bob da chamada do construtor Person().

Mas você pode escrever algo assim:

IPerson Bob = new Person();

Onde você não está cumprindo todo o contrato da API da Pessoa, mas apenas o contrato especificado pela interface IPerson.

Robert Harvey
fonte
2
+1: darei o IPersonexemplo no meu código para garantir que não use acidentalmente nenhum método privado ao escrever um código que deve ser copiado / colável em outra IPersonimplementação.
Cort Ammon - Restabelece Monica
@CortAmmon Acho que você tem um erro de digitação lá, é evidente que você quis dizer "trabalho com o polimorfismo", em vez de copiar / colável em que o código: D
Benjamin Gruenbaum
@BenjaminGruenbaum certeza, por alguma definição de polimorfismo ;-)
Cort Ammon - Reintegrar Monica
@CortAmmon, como você chamaria acidentalmente um método privado ? Certamente você quis dizer internal?
Cole Johnson
@ColeJohnson de qualquer maneira. Eu escrevi que pensar em um caso específico que eu deparei onde privateé significativo: métodos de fábrica que fazem parte da classe sendo instanciados. Na minha situação, o acesso a valores privados deve ser a exceção, não a norma. Eu trabalho no código que vai me sobreviver por muito tempo. Se eu fizer dessa maneira, não só não é provável que eu use métodos particulares, mas quando o próximo desenvolvedor copia / cola alguns locais e o desenvolvedor depois os copia, diminui as chances de alguém ver uma oportunidade de use métodos privados como comportamento "normal".
Cort Ammon - Restabelece Monica
21
  1. Essa sintaxe é praticamente um legado do C ++, que, a propósito, possui os dois:

    Person Bob;

    e

    Person *bob = new Bob();

    O primeiro a criar um objeto dentro do escopo atual, o segundo a criar um ponteiro para um objeto dinâmico.

  2. Você definitivamente pode ter something_else Bob = new Person()

    IEnumerable<int> nums = new List<int>(){1,2,3,4}

    Você está fazendo duas coisas diferentes aqui, declarando o tipo da variável local numse diz que deseja criar um novo objeto do tipo 'Lista' e colocá-lo lá.

  3. O C # concorda com você, porque na maioria das vezes o tipo da variável é idêntico ao que você coloca nela:

    var nums = new List<int>();
  4. Em alguns idiomas, você faz o possível para evitar especificar os tipos de variáveis ​​como em F # :

    let list123 = [ 1; 2; 3 ]
AK_
fonte
5
Provavelmente é mais preciso dizer que seu segundo exemplo cria um ponteiro para um novo objeto Bob. A maneira como as coisas são armazenadas é tecnicamente um detalhe de implementação.
Robert Harvey
4
"O primeiro a criar um objeto local na pilha, o segundo a criar um objeto na pilha." Oh cara, não essa desinformação novamente.
Lightness Races com Monica
@LightnessRacesinOrbit melhor?
AK_
1
@AK_ Embora "dinâmico" signifique praticamente "heap" (quando heap é o modelo de memória usado pela plataforma), "automático" é muito distinto de "pilha". Se o fizer new std::pair<int, char>(), os membros firste secondo par terão duração de armazenamento automático, mas provavelmente serão alocados no heap (como membros do pairobjeto dynamic-storage-duration ).
Restabeleça Monica
1
@AK_: Sim, esses nomes implicam exatamente o que eles significam, enquanto esse absurdo "empilhar" vs "empilhar" não . Esse é o ponto inteiro. O fato de você os achar confusos apenas reforça a necessidade de ensiná-los, para que você não confie na terminologia imprecisa / incorreta apenas porque é familiar!
Lightness Races com Monica
3

Há uma enorme diferença entre int xe Person bob. An inté um inté um inte deve sempre ser um inte nunca pode ser outra coisa senão um int. Mesmo se você não inicializar intquando o declarar ( int x;), ele ainda será intdefinido como o valor padrão.

Quando você declara Person bob, no entanto, há uma grande flexibilidade quanto ao que o nome bobpode realmente se referir a qualquer momento. Pode se referir a a Person, ou pode se referir a alguma outra classe, por exemplo Programmer, derivada de Person; poderia até ser null, referindo-se a nenhum objeto.

Por exemplo:

  Person bob   = null;
  Person carol = new Person();
  Person ted   = new Programmer();
  Person alice = personFactory.functionThatReturnsSomeKindOfPersonOrNull();

Os designers de linguagem certamente poderiam ter feito uma sintaxe alternativa que teria conseguido a mesma coisa que Person carol = new Person()em menos símbolos, mas eles ainda teriam que permitir Person carol = new Person() (ou fazer alguma regra estranha que tornasse ilegal esse particular dos quatro exemplos acima). Eles estavam mais preocupados em manter a linguagem "simples" do que em escrever códigos extremamente concisos. Isso pode ter influenciado sua decisão de não fornecer a sintaxe alternativa mais curta, mas, em qualquer caso, não era necessário e eles não a forneceram.

David K
fonte
1

As duas declarações podem ser diferentes, mas geralmente são as mesmas. Um padrão comum recomendado em Java é semelhante a:

List<String> list = new ArrayList<>();
Map<String, Integer> map = new HashMap<>();

Estas variáveis liste mapsão declaradas usando as interfaces Liste Mapenquanto o código instancia implementações específicas. Dessa forma, o restante do código depende apenas das interfaces e é fácil escolher diferentes classes de implementação para instanciar, como TreeMap, pois o restante do código não pode depender de nenhuma parte da HashMapAPI que esteja fora da Mapinterface.

Outro exemplo em que os dois tipos diferem é em um método de fábrica que escolhe uma subclasse específica para instanciar e depois a retorna como o tipo base, para que o chamador não precise estar ciente dos detalhes da implementação, por exemplo, uma opção de "política".

A inferência de tipo pode corrigir a redundância do código-fonte. Por exemplo, em Java

List<String> listOne = Collections.emptyList();

construirá o tipo certo de lista graças à inferência de tipo e à declaração

static <T> List<T> emptyList(); 

Em algumas linguagens, a inferência de tipo vai além, por exemplo, em C ++

auto p = new Person();
Jerry101
fonte
BTW, a linguagem Java define uma convenção forte para o uso de nomes de identificadores em minúsculas, como bob, não Bob. Isso evita muita ambiguidade, por exemplo, package.Class vs. Class.variable.
precisa saber é o seguinte
... e é por isso que você tem clazz.
um CVn
Não, clazzé usado porque classé uma palavra-chave, portanto não pode ser usado como um identificador.
precisa saber é o seguinte
... o que não seria um problema se as convenções de nomenclatura não fossem como são. Classé um identificador perfeitamente válido.
cHao 09/02
... como são cLaSs, cLASSe cLASs.
9135 el.pescado
1

Nas palavras do leigo:

  • Separar a declaração da instanciação ajuda a dissociar quem usa os objetos e quem os cria
  • Quando você faz isso, o poliporfismo é ativado, pois, desde que o tipo instanciado seja um subtipo do tipo de declaração, todo o código que usa a variável funcionará
  • Em linguagens fortemente tipadas, você deve declarar uma variável declarando seu tipo, simplesmente var = new Process()não declarando a variável primeiro.
Tulains Córdova
fonte
0

É também sobre o nível de controle sobre o que está acontecendo. Se a declaração de um objeto / variável chamar automaticamente um construtor, por exemplo, se

Person somePerson;

era automaticamente o mesmo que

Person somePerson = new Person(blah, blah..);

nunca seria possível usar (por exemplo) métodos estáticos de fábrica para instanciar objetos em vez de construtores padrão, ou seja, haverá momentos em que você não deseja chamar um construtor para uma nova instância de objeto.

Este exemplo é explicado em Joshua Bloch 's Effective Java (ponto 1 ironicamente!)

David Scholefield
fonte
Em relação aos livros, tanto o livro em C # quanto o livro em Java eram de Joyce Farrell. Isso é exatamente o que o curso especificou. Eu também tenho complementado ambos com vários vídeos do youtube em C # e Java.
Jason Wohlgemuth
Eu não estava criticando, apenas dando uma referência ao local onde este veio :)
David Scholefield