Защо параметър ArrayList е модифициран, но не и параметър String?

public class StackOverFlow {
    public static void main(String[] args) {
        ArrayList<String> al = new ArrayList<String>();
        al.add("A");
        al.add("B");
        markAsNull(al);
        System.out.println("ArrayList elements are "+al);

        String str = "Hello";
        markStringAsNull(str);
        System.out.println("str "+ str);
    }
    private static void markAsNull(ArrayList<String> str){
        str.add("C");
        str= null;
    }
    private static void markStringAsNull(String str){
        str = str + "Append me";
        str = null;
    }
}

Това извежда:

ArrayList elements are [A, B, C]
str Hello

В случая на ArrayList добавените елементи се извличат. В случай на String извикването на метод няма ефект върху предавания низ. Какво точно прави JVM? Някой може ли да обясни по-подробно?


person Naresh    schedule 08.04.2013    source източник
comment
Присвояването на null на променлива не засяга обект.   -  person default locale    schedule 08.04.2013
comment
Вижте също stackoverflow. com/questions/8798403/   -  person Raedwald    schedule 30.04.2014
comment
Читатели от бъдещето: IMO, Направо към отговора на Sikorski!   -  person aderchox    schedule 28.02.2021


Отговори (5)


В случай на низови обекти Arraylist, добавените елементи се извличат. В случай на String извикването на метод няма ефект върху предавания низ.

Това се случва, защото Java е Pass-by-Value и Strings са неизменни

Когато се обадите

markAsNull(ArrayList<String> str)

Създава се нова препратка по име str за същото ArrayList, посочено от al. Когато add елемент на str, той се добавя към същия обект. По-късно поставяте str до null, но обектът има добавени нови стойности и е насочен от a1.

Когато се обадите

markStringAsNull(String str)
{
    str = str + "Append me";
    // ...
}

Редът str = str + "Append me"; създава нов обект String чрез добавяне на даден низ и го присвоява на str. но отново това е просто препратка към действителния низ, който сега сочи към новосъздадения низ. (поради неизменност) и оригиналният низ не се променя.

person Azodious    schedule 08.04.2013
comment
Не защото низовете са неизменни, а поради str = <something> срещу list.<something>. - person user253751; 14.06.2014
comment
@immibis, моля, обосновете за по-добро разбиране - person Sandeep Rana; 19.05.2016
comment
@SandeepSinghRana Какво не разбираш? - person user253751; 19.05.2016
comment
' това е заради str = ‹something› срещу list.‹something›' какво точно означава това - person Sandeep Rana; 19.05.2016
comment
НЕ е заради неизменността. опитайте по-долу за пояснение: int t = 9; марка(t); System.out.println(int +t); - person Raúl; 11.11.2016

Методите markXAsNull задават локалните препратки да бъдат null. Това няма ефект върху действителната стойност, съхранявана на това място. Методът main все още има свои собствени препратки към стойностите и може да извика println, като ги използва.

Освен това, когато се прави конкатенацията на низове, toString() се извиква на обекта и затова ArrayList се извежда като списък с неговите стойности в скоби.

person Whymarrh    schedule 08.04.2013

От книгата: SCJP - Sun Certified Programmer for Java 6 Учебно ръководство (Katty Sierra - Bert Bates) Глава 3 Цел 7.3 - Предаване на променливи в методи

Java всъщност е предаване по стойност за всички променливи, работещи в рамките на една виртуална машина. Pass-by-value означава предаване на стойност на променлива. А това означава, предаване на копие на променливата!

Изводът за предаване по стойност: извиканият метод не може да промени променливата на извикващия, въпреки че за променливите за референтни обекти извиканият метод може да промени обекта, към който се отнася променливата. Каква е разликата между промяната на променливата и промяната на обекта? За препратки към обекти това означава, че извиканият метод не може да преназначи оригиналната референтна променлива на извикващия и да я накара да препраща към различен обект или нула. Например в следния кодов фрагмент,

void bar() {
Foo f = new Foo();
doStuff(f);
}
void doStuff(Foo g) {
g.setName("Boo");
g = new Foo();
}

преназначаването на g не преназначава f! В края на метода bar() са създадени два Foo обекта, единият, рефериран от локалната променлива f, и един, рефериран от локалната (аргумент) променлива g. Тъй като методът doStuff() има копие на референтната променлива, той има начин да стигне до оригиналния Foo обект, например да извика метода setName(). Но методът doStuff() няма начин да стигне до референтната променлива f. Така че doStuff() може да променя стойностите в обекта, към който се отнася f, но doStuff() не може да променя действителното съдържание (битов модел) на f. С други думи, doStuff() може да промени състоянието на обекта, към който f препраща, но не може да накара f да препраща към различен обект!

person Sikorski    schedule 08.04.2013
comment
Не можах да избегна коментара на благодарността си под вашия отговор въпреки конвенцията на SO против това :) - person aderchox; 28.02.2021

Java следва концепцията за стойността на преминаване (няма преминаване по референция в java). Така че, когато подадете низ към функцията, тя изпраща „копие на препратка“ към този низ към функцията. Така че дори ако зададете променливата на null във функцията, когато тя се върне към извикващия, тя препраща само към първоначалната си стойност. Ето защо оригиналният низ няма ефект.

В случай на Arraylist, копието на препратката се отнася до оригиналния Arraylist (което също е същото в случай на низ). Но когато добавите нещо в ArrayList, то се отнася до оригиналния обект и така можете да видите ефекта. (опитайте в метода arraylist=new ArrayList(), вашият оригинален arraylist ще остане такъв, какъвто е).

В случай на низ, когато го направите

str=str + "abc";

Java създава нов String обект, който ще има препратка към низ "xyzabc" (напр. str="xyz") и "xyz" ще отговаря на условията за събиране на боклук. Но тъй като "xyz" все още има променлива, която се отнася до него, (оригинален низ) няма да бъде събиран боклук. Но веднага след като извикването на функцията надхвърли "xyzabc" отива за събиране на боклук.

Обобщението на дискусията е, че докато препратката се отнася към същия обект, можете да правите промени във функцията, но когато се опитате да промените препратката ( str=str+"abc"), вие препращате към новия обект в метода така че вашият оригинален обект да остане такъв, какъвто е.

person Ankur Trapasiya    schedule 08.04.2013

В Java можете да създадете един обект и да се препращате от множество указатели. Извикването на метод за мутация на който и да е указател ефективно ще промени единствения обект, като по този начин актуализира всички други препратки.

Но ако извикате оператори за присвояване на променливи на препратка, само този указател ще бъде променен, тъй като той не извършва никаква работа от страна на обекта (това е най-доброто, което мога да обясня...).

Предаването на обект към параметър ефективно ще копира препратката, което ще доведе до един обект с два указателя - един глобален, а другият локален.

Още един момент, тъй като String е неизменен, вие всъщност ще получите нов обект, който е различен от оригинала (от факта, че трябва да кажете a = a + "a"), ето защо той няма да промени оригиналния низ.

person Mordechai    schedule 08.04.2013
comment
това е много насоки в отговор на Java :) - person Sikorski; 08.04.2013