Variablen final String Variable als Attribut veränderbar?

  • Themenstarter Themenstarter rattosch
  • Beginndatum Beginndatum
R

rattosch

Gast
Hallo,

angenommen ich habe als Attribut

Java:
private final String name;

dann bedeutet das doch, dass ich den Variablenwert nicht ändern kann. Da es sich hier um eine Referenzvariable handelt, dürfte also der Referenzwert nicht verändert werden können. Dieser ist doch defaultmässig null. Das würde doch bedeuten das ich die null Referenz nicht mehr ändern kann, oder?

Warum funktioniert es aber wenn ich einen Konstruktor schreibe ala

Java:
public Test(String name) { this.name = name; }

In diesem Fall ändere ich doch den Wert bzw. die Referenz der als final deklarierten Variable. Was ja eigentlich nicht gehen dürfte, da das Attribut ja schon mit null belegt ist. Bin momentan etwas verwirrt. Evtl. kann mir jemand die Frage beantworten.
 
Du kannst den Wert genau einmal setzen (einmal initialisieren), und der Compiler wird eine Fehlermeldung ausgeben wenn du auch nur ansatzweise versuchst die Variable zweimal zu ändern (probier es aus, schreib "this.name = ..." an mehr als einer Stelle hin).
 
Du kannst den Wert genau einmal setzen (einmal initialisieren)

Man muss diesen Wert sogar genau einmal setzen. Allerdings geht das auch nur in bestimmten Situationen. Bei Klassenvariablen ist dies entweder der Konstruktor oder Initializer. Es muss zur Compilezeit bewiesen werden können, dass die Variable durch jeden möglichen Pfad (if-else-Zweige, switch, etc.) genau einmal gesetzt wird.
 
und zur ergaenzung.
Java:
public final List<String> list = new ArrayList();

// some code
list.add("foo");
list.remove("foo");
d.h. der Inhalt einer finalen Variable laesst sich immer aendern (wenn dieser nicht selbst immutable ist).
 
Hallo,

genau einmal setzen. Das müsste mit null ja passieren und trotzdem kann ich, nachdem der Wert genau einmal auf null gesetzt wurde noch eine Referenz setzen.
 
Das müsste mit null ja passieren und trotzdem kann ich, nachdem der Wert genau einmal auf null gesetzt wurde noch eine Referenz setzen.
Wo setzt du den Wert auf null? In deinem Codeausschnitt steht davon nichts.

d.h. der Inhalt einer finalen Variable laesst sich immer aendern (wenn dieser nicht selbst immutable ist).
Nicht der Inhalt. Der Zustand des Objekts, das in einer finalen Variable gespeichert ist, lässt sich ändern.
 
Wo setzt du den Wert auf null? In deinem Codeausschnitt steht davon nichts.

Naja, Klassenvariablen sind per Default null/0/0.0/false, das stimmt schon. Das mit final ist eben ein Sonderfall. Initialisierung und Zuweisung sind in der JVM zwei Verschiedene Dinge. final erzwingt eine Zuweisung, die über die Initialisierung hinausgeht.

Nicht der Inhalt. Der Zustand des Objekts, das in einer finalen Variable gespeichert ist, lässt sich ändern.

Ich denke, das war gemeint. Über exakte Bezeichnungen können wir sicher stundenlang diskutieren ohne Inhalt beizusteuern.
 
Naja, Klassenvariablen sind per Default null/0/0.0/false, das stimmt schon.

[EDIT]ach du meinst nicht final variablen, okay. falsch verstanden[/EDIT]

also mein compiler streikt wen ich die nicht initialisiere

Code:
e:\w2\testarea\src>javac Test2.java
Test2.java:19: error: variable i might not have been initialized
        System.out.println(i);
                           ^
Test2.java:20: error: variable b might not have been initialized
        System.out.println(b);
                           ^
Test2.java:21: error: variable s might not have been initialized
        System.out.println(s);
                           ^
Test2.java:22: error: variable o might not have been initialized
        System.out.println(o);
                           ^
4 errors
Java:
public class Test2 {
    private final Object o;
    private final int i;
    private final boolean b;
    private final String s;

    public Test2() {
        System.out.println(i);
        System.out.println(b);
        System.out.println(s);
        System.out.println(o);
    }

    public static void main(String[] args) throws IOException {
        Test2 test = new Test2();

    }

oder mache ich was falsch?
 
Hallo,

also verstehe ich es jetzt so richtig:

Das Attribut

private final String name; wird default mit null initialisiert. Jetzt muss ich aber eine Zuweisung einmalig vornehmen, da final eine Zuweisung erzwingt. Nach dieser Zuweisung (Referenz) kann ich keine neue Referenz zuweisen.
 
Das Attribut

private final String name; wird default mit null initialisiert.
Nein, und warum, hast du eigentlich schon selbst geschrieben (und tfa):
Jetzt muss ich aber eine Zuweisung einmalig vornehmen, da final eine Zuweisung erzwingt.
Genau!
Nach dieser Zuweisung (Referenz) kann ich keine neue Referenz zuweisen.
So kann man das sagen, ja.

Auf allen möglichen Wegen durch die Anweisungen jedes Konstruktors gibt es eine und nur eine Zuweisung für (in diesem Fall) name.

Spricht eigentlich was dagegen, dies einfach mit einem Compiler auszuprobieren? Er wird schon meckern, wenn ihm was nicht passt.

Ark
 
Meines Erachtens stimmt die Aussage, dass die Variable immer mit einem Defaultwert initialisiert wird:
Each class variable, instance variable, or array component is initialized with a default value when it is created (§15.9, §15.10):
4.12.5. Initial Values of Variables

Java:
public class VariableTest {
	private final String name;

	public VariableTest() {
		System.out.println(this.name);
		name = "name";
	}
	
	public static void main(final String... args) {
		new VariableTest();
	}
}
Das liefert mir
Code:
null
.

Interessanterweise kriege ich einen Compilerfehler, wenn ich das
Code:
this
weglasse:
Java:
public class VariableTest {
	private final String name;

	public VariableTest() {
		System.out.println(name); // variable name might not have been initialized
		name = "name";
	}
	
	public static void main(final String... args) {
		new VariableTest();
	}
}

Gleiches Problem mit Klassenvariablen:
Java:
public class VariableTest {
	private static final String name;

	static {
		System.out.println(VariableTest.name);
		name = "name";
	}
	
	public static void main(final String... args) {}
}
Liefert
Code:
null
.

Compilerfehler ohne
Code:
VariableTest
:
Java:
public class VariableTest {
	private static final String name;

	static {
		System.out.println(name); // variable name might not have been initialized
		name = "name";
	}
	
	public static void main(final String... args) {}
}

Kann mir das jemand erklären?
 
Du gibst dir die Variable aus, bevor du sie initialisierst.
Kann ja dann nur null sein, weil's zum Zeitpunkt des ausgebens keinen Wert hat.

Tausch' sie um, und du wirst "name" erhalten.
 
Zuletzt bearbeitet:
Du hast meinen Beitrag missverstanden.
Es ging mir darum, zu zeigen, dass Instanz- und Klassenvariablen bei ihrer Erstellung mit einem Defaultwert (in diesem Fall
Code:
null
) initialisiert werden.
Die Zuweisung an name habe ich nur gemacht, um einen Compilerfehler zu verhindern.
Während ich an dem Code arbeitete, fiel mir auf, dass es einen Compilerfehler gibt ("variable name might not have been initialized"), wenn man nicht voll qualifiziert (
Code:
this.name
bei der Instanz- und
Code:
VariableTest.name
bei der Klassenvariable) auf die Variable zugreift. Eine Erklärung habe ich dafür bisher nicht gefunden.
 
Kann mir das jemand erklären?
Tippe auf einen Compilerbug.

IMHO sollte beides, also mit egal ob mit oder ohne this/Klassenname Qualifizierung, einen Compilerfehler erzeugen.

Der "Trick" an final ist u.a., dass bestimmte Annahmen gemacht werden können, zB. dass solche Variablen nicht uninitialisiert sein können (offensichtlich schon??) und dass alle Threads dasselbe zusehen bekommen.
 
Tippe auf einen Compilerbug.

IMHO sollte beides, also mit egal ob mit oder ohne this/Klassenname Qualifizierung, einen Compilerfehler erzeugen.

Hmm... der Compiler kann aber natürlich nicht immer in allen Fällen überprüfen, ob gerade das aktuelle Objekt angesprochen wird.
Wenn der Compiler hier meckern würde:
Java:
public class VariableTest {
    private final String name;
 
    public VariableTest() {
        System.out.println(this.name);
        name = "name";
    }
    
    public static void main(final String... args) {
        new VariableTest();
    }
}
Sollte er dann nicht eigentlich auch hier meckern?
Java:
public class VariableTest {
    private final String name;
 
    public VariableTest() {
        VariableTest foo = this;
        System.out.println(foo.name);
        name = "name";
    }
    
    public static void main(final String... args) {
        new VariableTest();
    }
}
Und was ist dann damit?
Java:
public class VariableTest {
    private final String name;
 
    public VariableTest() {
        VariableTest foo = lengthyComputationWhichAlwaysReturnsTrue() ? this : null;
        System.out.println(foo.name);
        name = "name";
    }
    
    public static void main(final String... args) {
        new VariableTest();
    }
}
 
Nebenbei werden die Variablem bei erzeugen des Objekts mit Defaults initialisiert. Und das passiert beim Konstruktoraufruf (einfach mal debuggen, da sieht man es)
Final-Variablen werden auch nicht per Default mit null initialisiert sondern einmalig auf den Wert gesetzt bei der Zuweisung.
 
Die Variablen werden schon vor dem Aufruf des Konstruktors/Instanzinitialisierers mit Defaultwerten initialisiert. Unabhängig davon, ob sie
Code:
final
sind oder nicht. Das haben die Ausgabe des Testcodes und auch der Debugger bei mir gezeigt.

Dazu dann auch ein Zitat aus der JVMS:
Memory for a new instance of that class is allocated from the garbage-collected heap, and the instance variables of the new object are initialized to their default initial values (§2.3, §2.4).
[…]
The new instruction does not completely create a new instance; instance creation is not completed until an instance initialization method (§2.9) has been invoked on the uninitialized instance.
Chapter 6.5 The Java Virtual Machine Instruction Set, Instructions, new

Ein
Code:
new Foo(bar)
in Java wird dann quasi zu
Code:
new Foo
und
Code:
invokespecial <init>(bar)
im Bytecode. Zwischen diesen Aufrufen beinhalten die Variablen dann die Defaultwerte.
 
Hmm... der Compiler kann aber natürlich nicht immer in allen Fällen überprüfen, ob gerade das aktuelle Objekt angesprochen wird.
Wenn der Compiler hier meckern würde:
...snip...
Sollte er dann nicht eigentlich auch hier meckern?
...snip...
Und was ist dann damit?
...snip...

In den ersten beiden Fällen sollte der Compiler eigentlich alle Informationen haben (beim Dritten scheiterts am Halteproblem). Über eine Datenflussanalyse sollte das eigentlich trivial sein. Allerdings zeigen sowohl javac als auch der JDT Compiler das gleiche Verhalten, was darauf schließen lässt, dass es wahrscheinlich aus der Spec hervorgeht. Ich habe jetzt allerdings eine ganze Weile danach gesucht (mir klingelt gerade der Kopf vor lauter "definite assignment" etc.) aber ich habe nichts gefunden. Hast Du ne Idee, woraus das hervorgehen könnte?
 
Man kann den Compilerfehler ebenfalls über eine Methode umgehen:
Java:
public class VariableTest {
    private final String name;

    private VariableTest() {
        name();
        name = "name";
        name();
    }

	private void name() {
		System.out.println(name); // liefert trotz final nicht die gleiche Ausgabe
	}

    public static void main(final String... args) {
        new VariableTest();
    }
}
Code:
null
name
 

Zurück
Oben