De ponteiros para referência

15 respostas
A

Estou com problema quando tento passar valores por referência.
Os objetos em Java são passados por ref., mas nesse caso tento mudar o valor de uma variável, mas após a execução o valor não se altera, ou seja, ficou como temporário.

O arquivo ABB.java mostra o problema.

Gostaria de saber qual o problema do código e como funciona uma referência em Java.

15 Respostas

E

Dah uma olhada neste post [list]http://www.guj.com.br/posts/list/46065.java[/list]

A

Pelos tópicos dar para notar que :

Se alteramos uma variável de instância ou executarmos um método teremos uma alteração no valor passado por referência, mas se instanciarmos novamente teremos , então, uma variável local. Seria isso ?
Mas veja, no método insere, alteramos uma variável de instância, mas instanciamos novamente… :?:

sergiotaborda

acao11:
Estou com problema quando tento passar valores por referência.
Os objetos em Java são passados por ref., mas nesse caso tento mudar o valor de uma variável, mas após a execução o valor não se altera, ou seja, ficou como temporário.

Em Java todas as passagens de argumentos são por valor.
Em Java nada é passado por referencia. Nunca!

Esta regra não tem exceções ou casos especiais e é uma das razões que me fazem preferir Java.

Algumas pessoas fazem distinção entre passagem de valores primitivos e passagem de objetos. Isso não existe.

Então ao passar uma variável de referencia a objeto (chamada de variável de objeto mas) que não é o objeto em si mesmo, isso é importante lembrar essa referencia é copiada. Como saberá a copia de valor implica copiar o valor da variável original para a variável de argumento. Alterar a variável de argumento não mudar em nada a variável original.
Então a referencia original é igual à nova,e ambas apontam o mesmo objeto.
Todas as operações feitas no objeto serão direcionadas ao objeto correto. Entenda que o objeto existe fora do mecanismo
de passagem de argumentos e por isso o estado permanece alterado depois que retorna do método.

Nos casos que não queremos isso temos que clonar o objeto ( e para isso que serve o Object.clone())

Tudo é bem simples em Java.

Mais detalhes em:
http://javadude.com/articles/passbyvalue.htm

P.S. Pela mesma ordem de ideias que faz as pessoas distinguir passagem de primitivos e de objetos é comum
se ouvir falar em colocar um final no argumento para que o argumento não possa ser alterado.
Isso é irrelevante já que a variável original nunca será modificada. Colocar final pode ser util para o programador muito iniciante
mas é um erro conceptual porque alterar a variável não provoca nenhum efeito fora do método e , muitas vezes, é interessante reaproveitar a variável de argumento.

M

CAro sérgio, em OO, as passagem são sempre por Referência! O que você descreveu é passagem por referência e não por valor!

rafaelglauber

malsan, olha esse link aqui. Você programa em alguma outra linguagem orientada a objeto? Quem migra de uma outra linguagem OO normalmente passa por essa confusão.

sergiotaborda
malsan:
CAro sérgio, em OO, as passagem são sempre por Referência! O que você descreveu é passagem por referência e não por valor!

Não , não é. Passar por referencia significa que a referencia original é passada e se for altera a referencia original muda
Exemplo

public void alteraRef(Object obj){

      obj = "oi";
}


// codigo

String a = "olá" 
alteraRef(a)

assertFalse (a.equals("oi");

Em java vai dar false. porque a ref original não se modificou. Isso significa que houve copia. E isso signiifica que é passado por valor. (o valor é passado, não a variável)

Leia os links que foram deixados. Faça os exemplos.

Dieval_Guizelini

Não estou querendo gerar um flame,

mas vou defender meu ponto de vista.

tecnicamente o Java não implementa completamente o mecanismo de passagem de parâmetros, se aplicado o conceito literal. Na realidade, esse mecanismo foi literalmente copiado da linguagem C, vejamos:

Parâmetros, segundo Carlo Ghezzi e Mehdi Jazayeri em Conceitos de Linguagem de Programação, são informações de entrada, saída ou entrada/saída para rotinas e funções.
Segundo os mesmos, existem dois tipos de parâmetros:

  1. Formal (Parâmetro) - É aquele que, na definição do módulo, está na lista de parâmetros. Este parâmetro não é a variável em si, mas apenas um sinônimo ao qual o módulo fará referência.
  2. Real (argumento) - É auqle que, na chamada ao módulo, está na lista de parâmetros. Este parâmetro é a variável que envia e/ou recebe informações do módulo.

Ainda o autor, afirma, ter duas formas de passagem de parâmetro:

  1. Por valor: O conteúdo da variável é passado para o módulo. Só permite a leitura da variável, sendo que o parâmetro formal define uma variável local.
  2. Por referência: O endereço da variável é passada para o módulo. Permite tanto a leitura quanto a alteração do conteúdo da variável.

Isso mesmo para as linguagens de programação estruturadas são implementadas de várias formas e algumas jamais chegaram a implementar se quer parcialmente este conceito. Por exemplo o COBOL que possui apenas uma área de memória compartilhada por todos os módulos (DATA DIVISION).
A própria linguagem C não possui um identificador para classificar a forma da passagem de parâmetro, e mesmo porque ela implementa única e exclusivamente a passagem de parâmetro por valor, quando se precisa passar o parâmetro por referência em C, faz se uso de ponteiros. Algo similar (para não falar idêntico) ao mecanismo utilizado pelo Java.

Naturalmente, a implementação do Pascal (para o modelo de linguagem estruturada) é uma das mais formais e aplica este conceito na integra. Não devemos esquecer que a linguagem Pascal foi criada para ensinar os programados a programar de forma estruturada e evitar as más práticas da época, como os comandos de desvio.

Tecnicamente, já expliquei anteriormente como as linguagens implementam a passagem de parâmetro e os respectivos modelos de memória, transcrevo a seguir:

a memória em praticamente todas as linguagens são dividias em três segmentos: código, heap e stack.
A área de código é onde fica os programas propriamente ditos.

O heap e o stack em java trabalham da seguinte forma:

Todos os tipos primitivos são alocados no stack (como se fossem variáveis locais e são).
Todos os objetos sempre são alocados no heap e a variável que os referência no stack.

fonte: http://www.guj.com.br/posts/list/46065.java

Agora se para as linguagens estruturadas é controverso a implementação deste conceito, como ficam as linguagens do paradigma Orientado a Objetos em que classificam os argumentos apenas como “mensagens” sendo trocadas pelos/entre os objetos.
Para Yourdon e Coad, uma mensagem significa: “Um princípio para administração da complexidade - notadamente para interfaces - é a comunicação com mensagens”.

Bom neste ponto, faz-se necessário questionar: “se em java não existissem tipos primitivos, permaneceria a dúvida e a discussão com relação a forma de passar as mensagens?”

Eu acredito que não. Porque ao copiarmos ou clonarmos um objetos (recurso necessário para passagem por valor), estaremos sempre produzindo dois objetos com o mesmo conteúdo, mas serão sempre dois objetos. O que difere de termos duas ou mais referências para o mesmo objeto.

Vamos por, os conceitos em prática para explicar melhor o que estou querendo demonstrar:

void swap(int a, int b) { int aux = a; a = b; b = aux; }

Aqui é fácil demonstrar e de aceitar que os tipos primitivos são passados por valor. Mas e aqui:

void metodoQualquer(StringBuilder sb) { sb.append(" sufixo "); } public static void main(String[] args) { StringBuilder s = new StringBuilder("casa "); System.out.println(s); metodoQualquer(s); System.out.println(s); }

Alguém pode afirmar que aconteceu uma passagem de parâmetro por valor? É possível afirmar que ocorreu uma cópia do conteúdo do objeto representado por s na chamada do método “metodoQualquer” e quando o mesmo foi finalizado a variável s permaneceu com o mesmo conteúdo.
Naturalmente que não. Mas então foi passagem por referência?
Se aplicarmos a definição ao pé da letra, a resposta continua sendo não. Porque java copia o endereço do objeto e não da variável, mas o efeito obtido para o objeto é equivalente a passagem por referência no que tange a possibilidade de ler e modificar o conteúdo.
Tanto que podemos afirmar: O objeto passado para um método poderá ser lido e ter seu conteúdo modificado tanto quanto o nível de acesso a seus recursos (propriedades e métodos) permitirem.
Naturalmente, modificar o conteúdo de um objeto não é criar uma nova instância e associar essa instância a referência original (ao argumento passado ao método, a variável original).

E por quê os objetos não são passados por valor? Resposta direta: por causa do custo de memória e do tempo de processamento para realização de operações de clonagem.

E por quê em java não existe passagem por referência?

Primeiro temos que ver que os objetos já são passados por referência (o endereço deles são copiados dos argumentos para os parâmetros dos métodos - no exemplo de s para sb), apenas o endereço das variáveis que faz (mantém) a referência ao objeto não é passada.
Segundo que se java implementasse a passagem de parâmetros por referência dos tipos primitivos ela teria que tratar os problemas e complexidade dos vazamentos de memória que ocorrem nas pilhas do C/C++. O ganho cabaria sendo muito pequeno perto da complexidade de administrar uma realocação ou redimensionamento de uma informação preservada no stack.
Devemos lembrar que em java, todos os objetos (inclusive os vetores de tipos primitivos) são mantidos no heap.

Portanto senhores, se forem perguntados tecnicamente se java passa por parametros por valor ou por referência, devemos afirmar que o mecanismo de passagem de parâmetros é sempre por valor, porém o efeito obtido para objetos são equivalentes da passagem por referência.

A afirmação da SUN com relação a isso esta descrita em: [url]http://java.sun.com/docs/books/tutorial/java/javaOO/arguments.html{/url]

Passing Primitive Data Type Arguments
Primitive arguments, such as an int or a double, are passed into methods by value.
(…)
Passing Reference Data Type Arguments
Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object’s fields can be changed in the method, if they have the proper access level.

No Delphi, o problema equivalente também existe e a implementação da linguagem apresenta problemas similares. Por exemplo, quando um objeto é passado por valor ele não é clonado e todas as alterações são refletidas no mesmo objeto. A diferença é que na passagem por referência ele permite criar novas instâncias e associar a variável do método chamador. Diga-se de passagem, a própria Borland declarou que isso possibilitava erros de alocação de memória, uma vez que quando o programador criasse uma nova instância sem liberar a antiga, a antiga permaneceria em memória (ausência do mecanismo de gc).

A sim, acredito que não menos relevante, seja o funcionamento de uma operação de swap entre dois objetos:
Supomos que temos os objetos A e B, a operação de swap seria fazer com que o conteúdo de A fosse para B e de B para A, então a implementação correta para isso seria algo assim:

void swap(Object a, Object b) { Object aux = a.clone(); a.assign( b ); b.assign( aux ); }

E com relação a isso:

P.S. Pela mesma ordem de ideias que faz as pessoas distinguir passagem de primitivos e de objetos é comum
se ouvir falar em colocar um final no argumento para que o argumento não possa ser alterado.
Isso é irrelevante já que a variável original nunca será modificada. Colocar final pode ser util para o programador muito iniciante
mas é um erro conceptual porque alterar a variável não provoca nenhum efeito fora do método e , muitas vezes, é interessante reaproveitar a variável de argumento.

Novamente eu tenho de discordar, apesar do efeito final poder ser o que sergiotaborda descreveu, a utilização de final ma declaração evita a criação implicita de novas instância a atribuição ao parâmetro, ou seja, você assegura que na última linha do método, o objeto referênciado pelo identificador definido no parâmetro é o mesmo do início do método.

Finalmente, volto a afirmar, que acredito que o conceito de passagem de parametro por valor ou por referência é algo importante ao paradigma de programação estruturado e que no paradigma orientado a objetos (conceitualmente) isso tem pouca relevância. Na prática, é fundamental saber que o objeto que se está sendo utilizando em um método e que foi passado na lista de parâmetros é o mesmo objeto do código que o chamou.

falou, e feliz ano novo para todos.

sergiotaborda

Está havendo uma confusão entre ser capaz de alterar o estado do objeto e a passagem por referencia.
Mudar o estado do objeto é natural e funciona independentemente do tipo de passagem. O exemplo do Stringbuilder
funciona com qq dois tipos de passagem. Logo, isso não demonstra o uso de nenhum dos mecanismo.
É importante entender isto primeiro. O sistema de referencia é muito simples e não importa como o método ganha essa referencia
ao objeto. Uma vez obtida todas as operações acontecem no objeto. Lembre-se que o operador ‘.’ significa "procura o objeto e invoca … ". builder.append(“oi”) procura o objeto no heap que builder aponta e invoca append() nele.
Tenha builder sido passado por valor ou por referencia não ha nenhuma alteração no mecanismo de procura a invocação.

A diferença está quando vc manipula a variável em si mesma. Lembre-se que “builder” não é um objeto. Falamos isso para simplificar a conversa mas o que queremos dizer é “variável de referencia a objeto” logo, como builder é passado para métodos é a variável de referencia que está em causa e nunca o objeto. O objeto nunca é passado para lugar algum. ele fica sossegado no heap todo o tempo (lembre-se da metafora da casa e da carta. A casa é fixa o endereço pode ser usado em muitas cartas diferentes")

Já que o objeto nunca é passado e sim a referencia a ele falta agora saber se o método recebe a variável original ou não.
Se sim, é uma passagem por referencia. Se não é por valor.
Variável é um container, uma “caixa” e a referencia está dentro da caixa. È a caixa ela própria que se transfere ao método ou só o valor ?

A resposta em Java é : Só o valor . Apenas o Valor, Nunca nada mais que o valor. E isto é um fato. Não é discutivel. É a especificação do Java. Em outras linguagens é diferente. Em VB por exemplo vc pode escolher qual quer usar e o default é por referencia. Não interessa. Em Java é por valor. Sempre!

Não tem essa de “por valor ou por referência”. não ha ‘ou’. É sempre por valor. Como se sabe a diferença ?

A diferença é simples. Se a caixa é passada e eu mudo o que está la dentro, quando o método retornar o valor dentro da caixa terá sido alterado. Isso nunca acontece em Java. O valor da variável antes e depois de chamar o método é sempre o mesmo. Mesmo quando ele é alterado pelo método.

Acho que se isto não é obvio para quem está a ler então tem que ser obvio antes de continuar a tentar entender o tema de passae de argumentos em Java. Não é flare, é apenas que isso é algo indiscutivel. É só fazer a experiência.

Novamente eu tenho de discordar, apesar do efeito final poder ser o que sergiotaborda descreveu, a utilização de final ma declaração evita a criação implicita de novas instância a atribuição ao parâmetro, ou seja, você assegura que na última linha do método, o objeto referênciado pelo identificador definido no parâmetro é o mesmo do início do método.

[/quote]

Isso pode ser feito por questão de defesa do programador ou da equipe quando se trabalha com jovens programadores.
Ou até por uma claresa de código. Mas nunca porque é necessário. É desnecessário fazer isso.
É como indentar. É desnecessário. A gente o faz para claresa, mas não altera o funcionamento do codigo. Colocar final no argumento tb não.

Não impede a criação implicita já que isso não existe. toda a criação é explicita. Vc tem que dar new.

às vezes vc precisa “limpar” o argumento, isso é muito comum com strings.
Vc pode usar o buffer ou um builder, mas é mais eficiente simplesmente usar o proprio string

public void processa (String str){

    str = str.trim();
}

Aqui não está sendo usada outra string nem se está criando mais variáveis.
Java dá esta colher se sopa porque a passagem é sempre por valor. Então eu posso esculhambar os parametros como eu quiser que isso não provoca nenhum problema. ( Isto está ligado com a segurança que está embutida no java , no mesmo de nivel de pq o java obriga a inicialização de variáveis)

Não ha porque dar explicações confusas e rebuscadas para algo que não existe. A passagem em java é SEMPRE por valor.
Sem tretas, sem complicação, sem truques, sem “ou isto ou aquilo”, sem “se”. não sei mais como explicar o óbvio.

Java foi inventado para ser simples. E ele é muito simples. não compliquem. :wink:

Mauricio_Linhares

Ô Sérgio, não é mais simples dizer que em Java tudo se passa por cópia de referência?

Como já foi dito e redito anteriormente, em Java não existe passagem real por referência (como acontece em C, por exemplo), mas quando um objeto é passado por parâmetro ou atribuido a alguma variável é criada uma referência que aponta pro objeto em questão (uma cópia da referência na qual ela foi baseada).

pcalcado

Como alguns milhões de tópicos no GUJ e links por aí aofra explicam a passagem de tipos pirmitivos em Java é por valor enquanto a passagem de referências à objetos é por cópia de referência. Não é passagem de referência mas não é passagem por valor (que implicaria em termos uma cópia temporátia do objeto).

Um site que diz que Java tem ponteiros não deve ser levado como referência sobre este tema.

eliziario

A passagem de argumentos é sempre por valor. As variáveis de tipo objeto em java são sempre referências. Coloca a porra da referência na pilha, tira a porra da referência da pilha. Catzo! qual a dificuldade em se entender isso?

A

Não é bem assim. Objetos sempre são alocados no heap. Primitivos podem ser alocados na stack (se forem variáveis locais) ou no heap (se forem, por exemplo, atributos de um objeto alocado no heap).

De resto, o Elizario ja falou tudo o que precisava ser falado (e em 3 linhas, veja só).

cv1

Faltou a punhetagem sobre troca de mensagens vs chamada de metodos, e de uma possivel implementacao de um interpretador Java que nao tivesse pilha, so pra gerar um flamewarzinho :mrgreen:

sergiotaborda

Maurício Linhares:
Ô Sérgio, não é mais simples dizer que em Java tudo se passa por cópia de referência?

Não é mais fácil pelo simples motivo que está errado.
Veja, se quem lê este topico tem o seu proprio conceito do que é passagem por valor e por referencia e tenta driblar as regras para que o seu conceito encaixe , eu não posso fazer nada por isso. O máximo que eu posso fazer é explicar porque a passagem não é por referencia. Mostrar codigo que testa que realmente não e por referencia. E explicar que a mecânica de passagem não interfere com o funcionamento da localização e invocação de métodos no objeto.


Como já foi dito e redito anteriormente, em Java não existe passagem real por referência (como acontece em C, por exemplo), mas quando um objeto é passado por parâmetro ou atribuido a alguma variável é criada uma referência que aponta pro objeto em questão (uma cópia da referência na qual ela foi baseada).

Entenda que “quando um objecto é passado” não singifica que ha um objeto sendo passado. O que é passado é o endereço do heap.
Esse endereço é um inteiro que está dentro de uma variável. Esse inteiro é um endereço. A variável não é o objeto.
A diferença de java para C é que esse int é inacessivel. Vc nunca saberá qual é o valor desse int. não importa. O que importa é que o java usará esse numero para direcionar todas as invocações de métodos e atributos ao objeto correto. Esse tipo especial de variável que não permite a leitura do seu valor, mas que permite invocação de método em cima do valor da variável é a variável de referencia.

então “quanto uma variável de referencia é passada” não é criada uma referencia que aponta o objeto. O que é criado é outra variável.

A frase certa seria:

“quando uma variável de referencia a objeto é passado como argumento é criada uma nova variável de referência a objeto e o valor da variável anterior é copiado. O valor do endereço que aponta para o objeto em questão continua sendo o mesmo, mas a variável onde ele está contido é outra.”

Porquê insistir numa diferença que não existe ?


Imagine que [] representa um container. Representa a variável em si mesma. O que está dentro de [] representa o valor da variável. Para deixar mais claro o tipo de variável vamos colocar uma letra.

um int seria assim : [32]I
um long seria [1024]L
uma variável de referencia seria [58947467]R

Então o que acontece é simples
Vc tem uma varíavel x que passa para um método que espera um argumento y

void main (){

    Object x = new Object (); (1)
   executaMetodo(x); (2)(3)(4)

}

void executaMetodo(Object y){

    y = new Object (); (5)
}

O codigo funciona assim:
Cria um variável x e preenche com o endereço de um objecto novo

x = [58947467]R (1)

A passagem acontece assim :

Cria uma variável para y

y = []R (2)

copia o valor de x para y

y = [58947467]R (3)

envia y ao método (não x) (4)

atribui a y o endereço de um novo objeto criado no momento

y = [129467098]R (5)

Isto é cópia de valores de variáveis. Não do objeto. A variável x nunca é passada ao método. Apenas o valor da variável é passado. Por isso que se chama passagem por valor.

Entenda que o valor da variável é um endereço e nunca o objeto em si mesmo. E que passagem por valor, em java, não implica em duplicação do objeto. Em outras linguagem pode ser diferente. Mas em Java é assim. E é isso que interessa.

louds

Faltou a punhetagem sobre troca de mensagens vs chamada de metodos, e de uma possivel implementacao de um interpretador Java que nao tivesse pilha, so pra gerar um flamewarzinho :mrgreen:

Que catzo subsumption polymorphism tem com uma coisa tão obvia quando o fato de Java usar passagem somente por valor? Não não tem passagem por referencia por que simplesmente não precisa, não possui value types, que exigem passagem por referencia p/ serem usados de forma eficiente. Fora que é perfeitamente possivel construir um sistema com passagem por referência que seja type safe e suporte análise precisa da pilha - tanto que já existe.

Quanto a implementar java, ou qualquer linguagem que usa chamada de métodos sem pilha, isso é improvavel, para não dizer impossivel. O que é possivel, e existem protótipos de alta qualidade, é utilizar formatos diferentes da pilha tradicional. Já lí sobre estudos usando cactus stacks ou mesmo spaguetti stacks, mas isso não é assunto para se discutir neste tópico.

Faltou sim alguém dizer que o negocio é passagem de parâmetros por nome, como o Algol faz. Isso sim presta, não a porcaria que as linguagens C like tem.

Criado 29 de dezembro de 2007
Ultima resposta 31 de dez. de 2007
Respostas 15
Participantes 12