Alguém pode me explicar o que o raciocínio por trás passando por “valor” e não por “referência” em Java é?

StackOverflow https://stackoverflow.com/questions/514478

Pergunta

Eu sou bastante novo para Java (escrito outras coisas por muitos anos) e menos que eu estou faltando alguma coisa (e eu estou feliz por estar errado aqui) O seguinte é uma falha fatal ...

String foo = new String();
thisDoesntWork(foo);
System.out.println(foo);//this prints nothing

public static void thisDoesntWork(String foo){
   foo = "howdy";
}

Agora, eu estou bem ciente do (bastante mal formulada) conceito que em java tudo é passado por "valor" e não "de referência", mas String é um objeto e tem todos os tipos de sinos e assobios, assim, seria de esperar que ao contrário de um int um usuário seria capaz de operar na coisa que é passado para o método (e não ser preso com o valor definido pelo sobrecarregado =).

Alguém pode me explicar o que o raciocínio por trás dessa escolha de design foi? Como eu disse, eu não estou olhando para estar aqui, e talvez eu estou faltando algo óbvio?

Foi útil?

Solução

Este discurso explica isso melhor do que eu jamais poderia sequer tentar:

Em Java, primitivos são passados ??por valor. No entanto, os objetos não são passado por referência. A afirmação correta seria referências de objeto são passados ??por valor.

Outras dicas

Quando você passar "foo", você está passando o referência para "foo" como um valor para ThisDoesntWork (). Isso significa que quando você faz a atribuição para dentro "foo" do seu método, você está apenas definindo uma variável local (foo) de referência a ser uma referência para a sua nova cadeia.

Outra coisa a ter em mente quando se pensa em como as strings se comportam em Java é que strings são imutáveis. Ele funciona da mesma forma em C #, e para algumas boas razões:

  • Segurança : Ninguém pode jam dados em sua seqüência e causar um erro de buffer overflow se ninguém pode modificá-lo
  • velocidade : Se você pode ter certeza que suas cordas são imutáveis, você sabe o seu tamanho é sempre o mesmo e você não sempre tem que fazer um movimento da estrutura de dados na memória quando você manipulá-la. Você (o designer da linguagem) também não precisa se preocupar com a implementação do String como uma lista ligada lento, também. Esta faca de dois gumes, no entanto. Anexando cordas apenas usando o operador + pode ser caro memory-sábio, e você terá que usar um objeto StringBuilder para fazer isso de uma maneira de alta performance, memória-eficiente.

Agora, em sua grande questão. Por que são objetos passaram por este caminho? Bem, se Java passou sua seqüência como o que você tradicionalmente chamar "por valor", ele teria que realmente copiar toda a cadeia antes de passá-lo para a sua função. Isso é bastante lento. Se ele passou a cadeia por referência e deixá-lo mudá-lo (como C faz), você teria os problemas que acabei de listar.

Uma vez que a minha resposta original era "Por que isso aconteceu" e não "Por que era a língua projetada assim aconteceu:" Eu vou dar a esta outra vez.

Para simplificar as coisas, vou livrar-se da chamada de método e mostrar o que está acontecendo de outra maneira.

String a = "hello";
String b = a;
String b = "howdy"

System.out.print(a) //prints hello

Para obter a última declaração para imprimir "Olá", b tem que apontam para o mesmo "buraco" na memória que a aponta para (um ponteiro). Isto é o que você quiser, quando quiser passagem por referência. Há um par de razões Java decidiu não ir nesta direção:

  • Os ponteiros são confusas Os designers de Java tentou remover algumas das mais confusas coisas sobre outras línguas. Os ponteiros são um dos mais mal compreendida e impropriamente construções de C / C ++ utilizada juntamente com a sobrecarga de operador.

  • Os ponteiros são os riscos de segurança Ponteiros causar muitos problemas de segurança quando mal utilizado. Um programa malicioso cessionários algo a que parte da memória, então o que você pensou que era o seu objeto é realmente outra pessoa. (Java já se livrou do maior problema de segurança, buffer overflows, com matrizes verificados)

  • Abstraction Vazamento Quando você começar a lidar com "o que está na memória e onde" exatamente, sua abstração torna-se menos de uma abstração. Enquanto vazamento abstração quase certamente se arrasta em uma língua, os designers não queria cozê-lo diretamente.

  • Objetos são tudo o que importa Em Java, tudo é um objeto, não o espaço um objeto ocupa. Adicionando ponteiros iria tornar o espaço um objeto ocupa importantant, embora .......

Você poderia imitar o que você quer através da criação de um objeto "Hole". Você poderia até usar os genéricos para torná-lo tipo seguro. Por exemplo:

public class Hole<T> {
   private T objectInHole;

   public void putInHole(T object) {
      this.objectInHole = object;
   }
   public T getOutOfHole() {
      return objectInHole;
   }

   public String toString() {
      return objectInHole.toString();
   }
   .....equals, hashCode, etc.
}


Hole<String> foo = new Hole<String)();
foo.putInHole(new String());
System.out.println(foo); //this prints nothing
thisWorks(foo);
System.out.println(foo);//this prints howdy

public static void thisWorks(Hole<String> foo){
   foo.putInHole("howdy");
}

A sua questão como solicitado não tem realmente a ver com a passagem por valor, passando por referência, ou o fato de que as cordas são imutáveis ?? (como outros já referiram).

Dentro do método, você realmente criar uma variável local (vou chamá que um "localFoo") que aponta para a mesma referência como a variável original ( "originalFoo").

Quando você atribui "Olá" para localFoo, você não alterar o local onde originalFoo está apontando.

Se você fez algo como:

String a = "";
String b = a;
String b = "howdy"?

Você esperaria:

System.out.print(a)

para imprimir "Olá"? Ele imprime "".

Você não pode mudar o que originalFoo pontos para mudando o que pontos localFoo para. Você pode modificar o objeto que ambos apontam para (se não fosse imutável). Por exemplo,

List foo = new ArrayList();
System.out.println(foo.size());//this prints 0

thisDoesntWork(foo);
System.out.println(foo.size());//this prints 1

public static void thisDoesntWork(List foo){   
    foo.add(new Object);
}

Em java todas as variáveis ??passadas são realmente repassados ??por valor- mesmo objetos. Todas as variáveis ??passadas para um método são na verdade cópias do valor original. No caso do seu exemplo seqüência o ponteiro originais. (Sua realmente uma referência - mas para evitar confusão doente usar uma palavra diferente) é copiado para uma nova variável que se torna o parâmetro para o método

Seria uma dor se foi tudo por referência. Seria necessário fazer cópias privadas em todo o lugar que com certeza gostaria de ser uma dor real. Todo mundo sabe que o uso de imutabilidade para tipos de valor etc faz com que seus programas infinitamente mais simples e escalável.

Alguns benefícios incluem: - Não há necessidade de fazer cópias defensivas. - Threadsafe -. Não precisa se preocupar sobre o bloqueio apenas no caso de alguém quiser mudar o objeto

O problema é que você está instanciar um tipo de referência Java. Então você passar esse tipo de referência a um método estático, e atribuí-la a uma variável com escopo localmente.

Não tem nada a ver com a imutabilidade. Exatamente a mesma coisa teria acontecido para um tipo de referência mutáveis.

Se quisermos fazer uma áspera C e assembler analogia:

void Main()
{ 
     // stack memory address of message is 0x8001.  memory address of Hello is 0x0001.  
     string message = "Hello"; 
     // assembly equivalent of: message = "Hello";
     // [0x8001] = 0x0001

     // message's stack memory address
     printf("%d", &message); // 0x8001

     printf("%d", message); // memory pointed to of message(0x8001): 0x0001
     PassStringByValue(message); // pass the pointer pointed to of message.  0x0001, not 0x8001
     printf("%d", message); // memory pointed to of message(0x8001): 0x0001.  still the same

     // message's stack memory address doesn't change
     printf("%d", &message); // 0x8001
}

void PassStringByValue(string foo)
{
    printf("%d", &foo); // &foo contains foo's *stack* address (0x4001)

    // foo(0x4001) contains the memory pointed to of message, 0x0001
    printf("%d", foo);  // 0x0001
    // World is in memory address 0x0002
    foo = "World";  // on foo's memory address (0x4001), change the memory it pointed to, 0x0002
    // assembly equivalent of: foo = "World":
    // [0x4001] = 0x0002

    // print the new memory pointed by foo
    printf("%d", foo); // 0x0002

    // Conclusion: Not in any way 0x8001 was involved in this function.  Hence you cannot change the Main's message value.
    // foo = "World"  is same as [0x4001] = 0x0002

}

void Main()
{
     // stack memory address of message is 0x8001.  memory address of Hello is 0x0001.  
     string message = "Hello"; 
     // assembly equivalent of: message = "Hello";
     // [0x8001] = 0x0001

     // message's stack memory address
     printf("%d", &message); // 0x8001

     printf("%d", message); // memory pointed to of message(0x8001): 0x0001
     PassStringByRef(ref message); // pass the stack memory address of message.  0x8001, not 0x0001
     printf("%d", message); // memory pointed to of message(0x8001): 0x0002. was changed

     // message's stack memory address doesn't change
     printf("%d", &message); // 0x8001
}


void PassStringByRef(ref string foo)
{
    printf("%d", &foo); // &foo contains foo's *stack* address (0x4001)

    // foo(0x4001) contains the address of message(0x8001)
    printf("%d", foo);  // 0x8001
    // World is in memory address 0x0002
    foo = "World"; // on message's memory address (0x8001), change the memory it pointed to, 0x0002
    // assembly equivalent of: foo = "World":
    // [0x8001] = 0x0002;


    // print the new memory pointed to of message
    printf("%d", foo); // 0x0002

    // Conclusion: 0x8001 was involved in this function.  Hence you can change the Main's message value.
    // foo = "World"  is same as [0x8001] = 0x0002

}

Uma possível razão por que tudo é passado por valor em Java, seus pais designer da linguagem quer simplificar a linguagem e fazer tudo feito de forma OOP.

Eles sim teria de projetar um inteiro swapper usando objetos que eles fornecem um suporte de primeira classe para o by-referência de passagem, o mesmo para delegado (Gosling sente icky com ponteiro para função, ele prefere empinar essa funcionalidade aos objetos) e enum.

Eles excesso de simplificar (tudo é objeto) a linguagem em detrimento de não ter suporte de primeira classe para a maioria das construções de linguagem, por exemplo, passagem por referência, os delegados, enum, propriedades vem à mente.

Você tem certeza que imprime nulo? Acho que vai ser apenas em branco como quando inicializado a variável foo você forneceu cadeia vazia.

A atribuição de foo na thisDoesntWork método não está a mudar a referência da variável foo definida na classe de modo a foo na System.out.printlnl (foo) ainda irá apontar para o velho objecto cadeia vazia.

Dave, você tem que me perdoar (bem, acho que você não "tem que", mas eu prefiro que você fez), mas essa explicação não é muito convincente. Os ganhos de segurança são bastante mínimo desde quem precisa alterar o valor da corda vai encontrar uma maneira de fazê-lo com alguma solução feio. E velocidade ?! Você mesmo (muito corretamente) afirmam que todo o negócio com o + é extremamente caro.

O resto vocês, por favor, entenda que eu entendo como ele funciona, eu estou perguntando por que ele funciona dessa maneira ... por favor pare de explicar a diferença entre as metodologias.

(e eu sinceramente não estou à procura de qualquer tipo de briga aqui, btw, eu só não vejo como esta foi uma decisão racional).

@Axelle

Companheiro você realmente sabe a diferença entre passando por valor e por referência?

Em java mesmo referências são passados ??por valor. Quando você passar uma referência a um objeto que você está recebendo uma cópia do ponteiro de referência na segunda variável. Tahts porque a segunda variável podem ser alterados sem afetar o primeiro.

É porque, ele cria uma variável local dentro do método. o que seria uma maneira fácil (o que eu tenho certeza que iria trabalhar) seria:

String foo = new String();    

thisDoesntWork(foo);    
System.out.println(foo); //this prints nothing

public static void thisDoesntWork(String foo) {    
   this.foo = foo; //this makes the local variable go to the main variable    
   foo = "howdy";    
}

Se você pensar em um objeto como apenas os campos no objeto, em seguida, os objetos são passados ??por referência em Java porque um método pode modificar os campos de parâmetro e um visitante pode observar a modificação. No entanto, se você também pensa de um objeto, uma vez que a identidade então os objetos são passados ??por valor porque um método não pode alterar a identidade de um parâmetro de uma forma que o chamador pode observar. Então, eu diria Java é passar por valor.

Isto porque dentro "thisDoesntWork", você está efetivamente destruindo o valor local de foo. Se você quiser passar por referência, desta forma, pode sempre encapsular a string dentro de outro objeto, digamos, em um array.

class Test {

    public static void main(String[] args) {
        String [] fooArray = new String[1];
        fooArray[0] = new String("foo");

        System.out.println("main: " + fooArray[0]);
        thisWorks(fooArray);
        System.out.println("main: " + fooArray[0]);
    }

    public static void thisWorks(String [] foo){
        System.out.println("thisWorks: " + foo[0]);
        foo[0] = "howdy";
        System.out.println("thisWorks: " + foo[0]);
    }
}

Os resultados no seguinte resultado:

main: foo
thisWorks: foo
thisWorks: howdy
main: howdy

Referência digitado argumentos são passados ??como referências a objetos em si (não menções a outros variáveis ?? que se referem a objetos). Você pode chamar métodos no objeto que foi passado. No entanto, em sua amostra de código:

public static void thisDoesntWork(String foo){
    foo = "howdy";
}

só está armazenando uma referência ao "howdy" corda em uma variável que é local para o método. Essa variável local (foo) foi inicializado com o valor de foo do chamador quando o método foi chamado, mas não tem nenhuma referência à variável do chamador em si. Após a inicialização:

caller     data     method
------    ------    ------
(foo) -->   ""   <-- (foo)

Após a atribuição em seu método:

caller     data     method
------    ------    ------
(foo) -->   ""
          "hello" <-- (foo)

Você tem mais problemas lá: casos String são imutáveis ??(por design, para a segurança) para que você não pode modificar o seu valor

.

Se você realmente quer que seu método para fornecer um valor inicial para a cadeia (ou pelo qualquer momento em sua vida, para que o assunto), em seguida, ter o seu método retorno um valor String que você atribui a variável do chamador no ponto da chamada. Algo como este, por exemplo:

String foo = thisWorks();
System.out.println(foo);//this prints the value assigned to foo in initialization 

public static String thisWorks(){
    return "howdy";
}

Vai fazer o realmente grande tutorial no site sóis.

Você parece não entender variáveis ??a diferença escopos pode ser. "Foo" é local para o seu método. Nada fora desse método pode mudar o que "foo" aponta também. O "foo" a ser referido o seu método é um campo completamente diferente - é um campo estático em sua classe delimitador.

Escopo é especialmente importante porque você não quer que tudo seja visível para todo o resto do seu sistema.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top