Por que linguagens gerenciadas por memória como Java, Javascript e C # mantiveram a palavra-chave `new`?

139

A newpalavra-chave em linguagens como Java, Javascript e C # cria uma nova instância de uma classe.

Essa sintaxe parece ter sido herdada do C ++, onde newé usada especificamente para alocar uma nova instância de uma classe na pilha e retornar um ponteiro para a nova instância. No C ++, essa não é a única maneira de construir um objeto. Você também pode construir um objeto na pilha, sem usar new- e, de fato, essa maneira de construir objetos é muito mais comum em C ++.

Portanto, vindo de um background em C ++, a newpalavra - chave em linguagens como Java, Javascript e C # parecia natural e óbvia para mim. Então comecei a aprender Python, que não tem a newpalavra - chave. No Python, uma instância é construída simplesmente chamando o construtor, como:

f = Foo()

No começo, isso me pareceu um pouco complicado, até que me ocorreu que não havia razão para o Python ter new, porque tudo é um objeto, então não há necessidade de desambiguar as várias sintaxes dos construtores.

Mas então eu pensei - qual é realmente o sentido disso newem Java? Por que deveríamos dizer Object o = new Object();? Por que não apenas Object o = Object();? No C ++, há definitivamente uma necessidade new, pois precisamos distinguir entre alocar na pilha e alocar na pilha, mas em Java todos os objetos são construídos na pilha, então por que ter a newpalavra - chave? A mesma pergunta pode ser feita para Javascript. Em C #, com o qual estou muito menos familiarizado, acho que newpode ter algum objetivo em termos de distinguir entre tipos de objeto e tipos de valor, mas não tenho certeza.

Independentemente disso, parece-me que muitas linguagens que vieram depois do C ++ simplesmente "herdaram" a newpalavra-chave - sem realmente precisar dela. É quase como uma palavra-chave vestigial . Parece que não precisamos dele por nenhum motivo, e ainda está lá.

Pergunta: Estou correto sobre isso? Ou existe algum motivo convincente que newprecisa estar em linguagens gerenciadas por memória inspiradas em C ++, como Java, Javascript e C #, mas não Python?

Channel72
fonte
13
A questão é: por que qualquer idioma tem a newpalavra - chave? Claro que quero criar uma nova variável, compilador estúpido! A boa linguagem seria, em minha opinião tem uma sintaxe como f = heap Foo(), f = auto Foo().
7
Um ponto muito importante a considerar é a familiaridade de uma linguagem para novos programadores. O Java foi projetado explicitamente para parecer familiar aos programadores de C / C ++.
11
@Lundin: É realmente um resultado da tecnologia de compilador. Um compilador moderno nem precisaria de dicas; descobriria onde colocar o objeto com base no uso real dessa variável. Ou seja, quando fnão escapar da função, aloque espaço na pilha.
MSalters 1/11/11
3
@Lundin: Pode, e de fato as JVM modernas fazem. É apenas uma questão de provar que o GC pode coletar objetos fquando a função envolvente retornar.
MSalters
11
Não é construtivo perguntar por que uma linguagem é projetada do jeito que é, principalmente se esse design nos pede para digitar quatro caracteres extras sem nenhum benefício óbvio? o que estou perdendo?
precisa saber é o seguinte

Respostas:

94

Suas observações estão corretas. C ++ é uma fera complicada, e a newpalavra - chave foi usada para distinguir entre algo que precisava deletemais tarde e algo que seria recuperado automaticamente. Em Java e C #, eles soltaram a deletepalavra - chave porque o coletor de lixo cuidaria dela por você.

O problema então é por que eles mantiveram a newpalavra - chave? Sem falar com as pessoas que escreveram o idioma, é meio difícil de responder. Meus melhores palpites estão listados abaixo:

  • Era semanticamente correto. Se você estava familiarizado com o C ++, sabia que a newpalavra - chave cria um objeto no heap. Então, por que mudar o comportamento esperado?
  • Chama a atenção o fato de que você está instanciando um objeto em vez de chamar um método. Com as recomendações de estilo de código da Microsoft, os nomes dos métodos começam com letras maiúsculas, para que haja confusão.

Ruby está em algum lugar entre Python e Java / C # em seu uso new. Basicamente, você instancia um objeto como este:

f = Foo.new()

Não é uma palavra-chave, é um método estático para a classe. O que isso significa é que, se você quiser um singleton, poderá substituir a implementação padrão new()para retornar sempre a mesma instância. Não é necessariamente recomendado, mas é possível.

Berin Loritsch
fonte
5
A sintaxe do Ruby parece agradável e consistente! A nova palavra-chave em C # também é usada como uma restrição de tipo genérico para um tipo com um construtor padrão.
Steven Jeuris
3
Sim. Não vamos falar sobre genéricos. As versões genéricas de Java e C # são muito obtusas. Não dizendo que C ++ é muito mais fácil de entender. Os genéricos são realmente úteis apenas para idiomas de tipo estaticamente. Linguagens dinamicamente tipadas como Python, Ruby e Smalltalk simplesmente não precisam delas.
Berin Loritsch
2
Delphi tem uma sintaxe semelhante ao Ruby, exceto que os construtores são geralmente (mas só convenção BHY) chamado Criar ( 'f: = TFoo.Create (param1, param2 ...)'
Gerry
No Objective-C, o allocmétodo (aproximadamente o mesmo que o de Ruby new) também é apenas um método de classe.
Mipadi
3
Do ponto de vista do design do idioma, as palavras-chave são ruins por vários motivos, principalmente você não pode alterá-las. Por outro lado, reutilizar o conceito de método é mais fácil, mas precisa de alguma atenção ao analisar e analisar. Smalltalk e seus parentes usam mensagens, C ++ e itskin, por outro lado, palavras
Gabriel Ščerbák
86

Em suma, você está certo. A palavra-chave new é supérflua em linguagens como Java e C #. Aqui estão algumas idéias de Bruce Eckel, que era membro do C ++ Standard Comitee na década de 1990 e depois publicou livros sobre Java: http://www.artima.com/weblogs/viewpost.jsp?thread=260578

precisava haver alguma maneira de distinguir objetos de pilha de objetos de pilha. Para resolver esse problema, a nova palavra-chave foi apropriada do Smalltalk. Para criar um objeto de pilha, você simplesmente o declara, como no Cat x; ou, com argumentos, Cat x ("luvas") ;. Para criar um objeto de heap, use new, como no novo Cat; ou gato novo ("luvas"); Dadas as restrições, esta é uma solução elegante e consistente.

Digite Java, depois de decidir que tudo o que o C ++ é mal executado e excessivamente complexo. A ironia aqui é que o Java pode e tomou a decisão de jogar fora a alocação de pilha (ignorando claramente o desastre de primitivos, que eu já havia abordado em outro lugar). E como todos os objetos estão alocados no heap, não há necessidade de distinguir entre alocação de pilha e heap. Eles poderiam facilmente ter dito Cat x = Cat () ou Cat x = Cat ("luvas"). Ou melhor ainda, a inferência de tipo incorporada para eliminar a repetição (mas isso - e outros recursos como fechamentos - levaria "muito tempo", por isso estamos presos à versão medíocre do Java; a inferência de tipo foi discutida, mas vou há chances de que isso não aconteça, e não deveria, dados os problemas na adição de novos recursos ao Java).

Nemanja Trifunovic
fonte
2
Stack vs Heap ... perspicaz, nunca teria pensado nisso.
WernerCD
11
"inferência de tipo foi discutida, mas eu colocarei chances de que isso não aconteça.", :) Bem, o desenvolvimento do Java ainda segue em frente. Interessante que este seja o único carro-chefe do Java 10. A varpalavra-chave não é nem de longe tão boa quanto autoem C ++, mas espero que melhore.
22418 patrik
15

Há duas razões pelas quais posso pensar:

  • new distingue entre um objeto e um primitivo
  • A newsintaxe é um pouco mais legível (IMO)

O primeiro é trivial. Não acho que seja mais difícil diferenciar os dois. O segundo é um exemplo do argumento do OP, que é meio que newredundante.

Porém, pode haver conflitos de namespace; considerar:

public class Foo {
   Foo() {
      // Constructor
   }
}

public class Bar {
   public static void main(String[] args) {
      Foo f = Foo();
   }

   public Foo Foo() {
      return Foo();
   }
}

Você poderia, sem muito esforço da imaginação, facilmente terminar com uma ligação infinitamente recursiva. O compilador precisaria aplicar um erro "Nome da função igual ao objeto". Isso realmente não faria mal, mas é muito mais simples de usar new, o que afirma que Go to the object of the same name as this method and use the method that matches this signature.Java é uma linguagem muito simples de analisar, e suspeito que isso ajude nessa área.

Michael K
fonte
11
Como isso é diferente de qualquer outra recursão infinita?
DeadMG
Essa é uma ilustração bastante boa de uma coisa ruim que pode acontecer, embora o desenvolvedor que faz isso tenha alguns problemas mentais sérios.
Tjaart
10

Java, por exemplo, ainda tem a dicotomia de C ++: não muito tudo é um objeto. Java possui tipos internos (por exemplo, chare int) que não precisam ser alocados dinamicamente.

Isso não significa que isso newseja realmente necessário em Java - o conjunto de tipos que não precisam ser alocados dinamicamente é fixo e conhecido. Quando você define um objeto, o compilador pode saber (e, de fato, sabe) se é um valor simples que charou um objeto que precisa ser alocado dinamicamente.

Eu evitarei (mais uma vez) opinar sobre o que isso significa sobre a qualidade do design do Java (ou a falta dele, conforme o caso).

Jerry Coffin
fonte
Mais precisamente, os tipos primitivos não precisam de nenhuma chamada de construtor - eles são escritos como um literal, vêm de um operador ou de alguma função de retorno primitivo. Na verdade, você também cria strings (que estão na pilha) sem a new, portanto, esse não é realmente o motivo.
Paŭlo Ebermann 23/09
9

Em JavaScript, você precisa disso porque o construtor se parece com uma função normal; então, como o JS deve saber que deseja criar um novo objeto, se não fosse a nova palavra-chave?

user281377
fonte
4

Curiosamente, VB faz necessário - a distinção entre

Dim f As Foo

que declara uma variável sem atribuí-la, o equivalente Foo f;em C # e

Dim f As New Foo()

que declara a variável e atribui uma nova instância da classe, o equivalente var f = new Foo();em C #, é uma distinção substantiva. No pré-.NET VB, você poderia usar Dim f As Fooou Dim f As New Foo- porque não havia sobrecarga nos construtores.

Richard Gadsden
fonte
4
O VB não precisa da versão abreviada, é apenas uma abreviação. Você também pode escrever a versão mais longa com o tipo e construtor Dim f As Foo = New Foo (). Com a opção Infer On, que é a coisa mais próxima da var do C #, você pode escrever Dim f = New Foo () e o compilador infere o tipo de f.
28412 MarkJ
2
... e Dim f As Foo()declara uma matriz de Foos. Oh alegria! :-)
Heinzi 30/10/12
@ MarkJ: No vb.net, a abreviação é equivalente. No vb6, não era. No vb6, Dim Foo As New Barefetivamente criaria Foouma propriedade que construiria a Barse fosse lida enquanto (ie Nothing). Esse comportamento não se limitou a acontecer uma vez; definindo Fooa Nothinge, em seguida, lê-lo criaria um outro novo Bar.
supercat
4

Eu gosto de muitas respostas e gostaria de acrescentar isso:
facilita a leitura do código
Não que essa situação aconteça com frequência, mas considere que você queira um método no nome de um verbo que compartilhe o mesmo nome que um substantivo. C # e outro idioma naturalmente têm a palavra objectreservada. Mas, se não fosse, seria Foo = object()o resultado da chamada do método de objeto ou instanciaria um novo objeto. Esperemos que a linguagem sem a newpalavra-chave tenha proteções contra essa situação, mas ao ter o requisito da newpalavra - chave antes da chamada de um construtor, você permite a existência de um método com o mesmo nome que um objeto.

Kavet Kerek
fonte
4
Indiscutivelmente, ter que saber que isso é um mau sinal.
DeadMG
11
@DeadMG: "Indiscutivelmente" significa "Vou argumentar isso até o bar fechar" ...
Donal Fellows
3

Há muitas coisas vestigiais em muitas linguagens de programação. Às vezes, existe por design (o C ++ foi projetado para ser o mais compatível possível com o C); às vezes, está lá. Para um exemplo mais flagrante, considere até que ponto a switchinstrução C quebrada se propagou.

Eu não sei Javascript e C # bem o suficiente para dizer, mas não vejo razão para isso newem Java, exceto pelo C ++.

David Thornley
fonte
Acho que o JavaScript foi projetado para "se parecer com Java", tanto quanto possível, mesmo quando está fazendo algo particularmente não-Java. x = new Y()no JavaScript não instancia a Yclasse, porque o JavaScript não tem classes. Mas parece Java, e leva a newpalavra - chave adiante do Java, o que, é claro, a frente do C ++.
MatrixFrog
+1 para o interruptor quebrado (na esperança de repará-lo: D).
Maaartinus 17/10
3

Em C #, permite tipos visíveis em contextos onde, de outra forma, poderiam ser obscurecidos por um membro. O permite que você tenha uma propriedade com o mesmo nome que seu tipo. Considere o seguinte,

class Address { 
    public string Street { get; set; }
    public string City { get; set; }
    public string State { get; set; }
    public string Zip { get; set; }
}

class Person {
    public string Name { get; set; }
    public Address Address { get; set; }

    public void ChangeStreet(string newStreet) {
        Address = new Address { 
            Street = newStreet, 
            City = Address.City,
            State = Address.State,
            Zip = Address.Zip
        };
    }
}

Observe que o uso de newpermite que fique claro que Addressesse Addresstipo não é o Addressmembro. Isso permite que um membro tenha o mesmo nome que seu tipo. Sem isso, você precisaria prefixar o nome do tipo para evitar a colisão de nomes, como CAddress. Isso foi muito intencional, pois Anders nunca gostou da notação húngara ou de algo semelhante e desejou que o C # fosse utilizável sem ela. O fato de também ser familiar era um bônus duplo.

chuckj
fonte
w00t, e Address poderia derivar de uma interface chamada Address .. only, não, não pode. Portanto, não é um grande benefício, poder reutilizar o mesmo nome e diminuir a legibilidade geral do seu código. Portanto, mantendo o I para interfaces, mas removendo o C das classes, eles apenas criaram um cheiro desagradável sem nenhum benefício prático.
Gbjbaanb
Gostaria de saber que eles não usam "Novo" em vez de "novo". Alguém poderia dizer a eles que também existem letras minúsculas, que resolvem esse problema.
Maaartinus 17/10
Todas as palavras reservadas nos idiomas derivados do C, como C #, são minúsculas. A gramática requer uma palavra reservada aqui para evitar ambiguidade, portanto, o uso de "novo" em vez de "Novo".
chuckj
@gbjbaanb Não há cheiro. É sempre totalmente claro se você está se referindo a um tipo ou propriedade / membro ou função. O verdadeiro cheiro é quando você nomeia um espaço para nome o mesmo que uma classe. MyClass.MyClass
Stephen
0

Curiosamente, com o advento do encaminhamento perfeito, a não colocação newtambém não é necessária em C ++. Portanto, sem dúvida, não é mais necessário em nenhum dos idiomas mencionados.

DeadMG
fonte
4
O que você encaminhar para ? Em última análise, std::shared_ptr<int> foo();terá que ligar de new intalguma forma.
MSalters 1/11/11
0

Não tenho certeza se os designers de Java tinham isso em mente, mas quando você pensa em objetos como registros recursivos , você pode imaginar que Foo(123)não retorna um objeto, mas uma função cujo ponto de correção é o objeto que está sendo criado (ou seja, função que retornaria o objeto se for dado como argumento o objeto que está sendo construído). E o objetivo de newé "dar um nó" e retornar esse ponto fixo. Em outras palavras new, tornaria o "objeto a ser" ciente disso self.

Esse tipo de abordagem pode ajudar a formalizar a herança, por exemplo: você pode estender uma função de objeto com outra função de objeto e, finalmente, fornecer a eles comum selfusando new:

cp = new (Colored("Blue") & Line(0, 0, 100, 100))

Aqui &combina duas funções de objeto.

Nesse caso, newpode ser definido como:

def new(objectFunction) {
    actualObject = objectFunction(actualObject)
    return actualObject
}
Aivar
fonte
-2

Permitir 'nulo'.

Juntamente com a alocação baseada em heap, o Java adotou o uso de objetos nulos. Não apenas como um artefato, mas como um recurso. Quando os objetos podem ter um estado nulo, é necessário separar a declaração da inicialização. Daí a nova palavra-chave.

Em C ++, uma linha como

Person me;

Chamaria instantaneamente o construtor padrão.

Kris Van Bael
fonte
4
Hum, o que há de errado Person me = Nilou ter o Person meNil por padrão e usar Person me = Person()para não-Nil? O que quero dizer é que não parece que Nil tenha sido um fator nessa decisão ...
Andres F.
Você tem um ponto. Pessoa me = Pessoa (); funcionaria igualmente bem.
Kris Van Bael
@AndresF. Ou ainda mais simples, Person mesendo nulo e Person me()invocando o construtor padrão. Portanto, você sempre precisará de parênteses para invocar qualquer coisa e, assim, livrar-se de uma irregularidade em C ++.
Maaartinus 3/08
-2

A razão pela qual o Python não precisa disso é por causa do seu sistema de tipos. Fundamentalmente, quando você vê foo = bar()no Python, não importa se bar()é um método que está sendo chamado, uma classe que está sendo instanciada ou um objeto functor; em Python, não há diferença fundamental entre um functor e uma função (porque métodos são objetos); e você pode até argumentar que uma classe sendo instanciada é um caso especial de um functor. Portanto, não há razão para criar uma diferença artificial no que está acontecendo (especialmente porque o gerenciamento de memória é extremamente oculto no Python).

Em uma linguagem com um sistema de digitação diferente, ou na qual métodos e classes não são objetos, existe a possibilidade de uma diferença conceitual mais forte entre criar um objeto e chamar uma função ou functor. Portanto, mesmo que o compilador / intérprete não precise da newpalavra-chave, é compreensível que ela seja mantida em linguagens que não quebraram todos os limites do Python.

Kazark
fonte
-4

Na verdade, em Java, há uma diferença ao usar Strings:

String myString = "myString";

é diferente de

String myString = new String("myString");

Supondo que a string "myString"nunca tenha sido usada antes, a primeira instrução cria um único objeto String no pool de String (uma área especial da pilha) enquanto a segunda instrução cria dois objetos, um na área de heap normal para objetos e outro no pool de String . É bastante óbvio que a segunda declaração é inútil nesse cenário específico, mas é permitida pelo idioma.

Isso significa que new garante que o objeto seja criado nessa área especial do heap, alocado para instanciação normal do objeto, mas pode haver outras maneiras de instanciar objetos sem ele; o exemplo acima é um exemplo e há espaço para extensibilidade.

m3th0dman
fonte
2
Mas essa não era a questão. A pergunta era "por que não String myString = String("myString");?" (observe a ausência da newpalavra - chave)
Andres F.
@AndresF. Tipo de imagem óbvia ... é legal ter um método chamado String que retorne uma String em uma classe String e deve haver uma diferença entre chamá-lo e chamar o construtor. Algo comoclass MyClass { public MyClass() {} public MyClass MyClass() {return null;} public void doSomething { MyClass myClass = MyClass();}} //which is it, constructor or method?
m3th0dman
3
Mas não é óbvio. Primeiro, ter um método regular chamado Stringcontraria as convenções de estilo Java, portanto, você pode assumir que qualquer método que comece com maiúsculas é um construtor. E segundo, se não somos constrangidos pelo Java, o Scala tem "classes de casos" em que o construtor se parece exatamente como eu disse. Então onde está o problema?
Andrés F.
2
Desculpe, eu não sigo o seu argumento. Você está efetivamente discutindo sintaxe , semântica não, e mesmo assim a pergunta foi sobre newpara idiomas gerenciados . Eu mostrei que isso newnão é de todo necessário (por exemplo, Scala não é necessário para as classes de casos) e também que não há ambiguidade (já que você absolutamente não pode ter um método nomeado MyClassna classe MyClass). Não há ambiguidade para String s = String("hello"); não pode ser outra coisa senão um construtor. E, finalmente, discordo: as convenções de código existem para aprimorar e esclarecer a sintaxe, sobre a qual você está discutindo!
Andrés F.
11
Então esse é o seu argumento? "Os designers de Java precisavam da newpalavra - chave porque, caso contrário, a sintaxe do Java teria sido diferente"? Tenho certeza que você pode ver a fraqueza desse argumento;) E sim, você continua discutindo sintaxe, não semântica. Além disso, compatibilidade retroativa com o quê? Se você estiver projetando uma nova linguagem, como Java, já é incompatível com C e C ++!
Andrés F.
-8

Em C #, new é importante distinguir entre objetos criados na pilha versus a pilha. Criamos frequentemente objetos na pilha para melhor desempenho. Veja, quando um objeto na pilha fica fora do escopo, ele desaparece imediatamente quando a pilha se desfaz, exatamente como um primitivo. Não é necessário que o coletor de lixo o controle e não há sobrecarga extra do gerenciador de memória para alocar o espaço necessário no heap.

pds
fonte
5
infelizmente você está confundindo C # com C ++.
Gbjbaanb