まず、文字列を見てみましょう。Javaでは、文字列は不変文字列として表現されますが、通常の文字列の値は変更できますか?
import java.lang.reflect.Field;
public class FinalTest {
public static void main(String[] args) {
String str = "ABCD";
System.out.println("str = " + str);
System.out.println("hashCode = " + str.hashCode());
try {
Field valueField = String.class.getDeclaredField("value");
//value域是final private的,这里设置可访问
valueField.setAccessible(true);
char[] valueCharArr = (char[]) valueField.get(str);
valueCharArr[0] = 'G';
//此处输出第一组结果
System.out.println("str = " + str);
System.out.println("hashCode = " + str.hashCode());
valueField.set(str, new char[] {'1', '2'});
//此处输出第二组结果
System.out.println("str = " + str);
System.out.println("hashCode = " + str.hashCode());
} catch (NoSuchFieldException | SecurityException
| IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
出力結果:
文字列は不変ですが、不変とは、文字列が作成されるとその値を変更できないことを意味します。文字列のソースコードのすべてのメンバー変数は、プライベートな変更であり、その値を変更する方法を提供していません。興味がある人は誰でも、Stringのソースコードを見ることができます。
ただし、他の方法、つまりリフレクションを使用してその属性値を変更できます。上記のコードでは、Stringの値を変更できることがはっきりとわかりますが、これはStringが変数であることを意味しません。プロパティはchar []への参照を格納します。このオブジェクトの値を変更できますが、この参照のアドレスを変更することはできません。
しかし、この文字列にfinalを追加するとどうなりますか?
import java.lang.reflect.Field;
public class FinalTest {
public static void main(String[] args) {
final String str = "ABCD";
System.out.println("str = " + str);
System.out.println("hashCode = " + str.hashCode());
try {
Field valueField = String.class.getDeclaredField("value");
//value域是final private的,这里设置可访问
valueField.setAccessible(true);
char[] valueCharArr = (char[]) valueField.get(str);
valueCharArr[0] = 'G';
//此处输出第一组结果
System.out.println("str = " + str);
System.out.println("hashCode = " + str.hashCode());
valueField.set(str, new char[] {'1', '2'});
//此处输出第二组结果
System.out.println("str = " + str);
System.out.println("hashCode = " + str.hashCode());
} catch (NoSuchFieldException | SecurityException
| IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
結果:
値を再度変更できないのはなぜですか、リフレクションは無効ですか?
次に、Stringの出力メソッドをtoString()を呼び出すように変更して、次を参照します。
import java.lang.reflect.Field;
public class FinalTest {
public static void main(String[] args) {
final String str = "ABCD";
System.out.println("str = " + str.toString());
System.out.println("hashCode = " + str.hashCode());
try {
Field valueField = String.class.getDeclaredField("value");
//value域是final private的,这里设置可访问
valueField.setAccessible(true);
char[] valueCharArr = (char[]) valueField.get(str);
valueCharArr[0] = 'G';
//此处输出第一组结果
System.out.println("str = " + str.toString());
System.out.println("hashCode = " + str.hashCode());
valueField.set(str, new char[] {'1', '2'});
//此处输出第二组结果
System.out.println("str = " + str.toString());
System.out.println("hashCode = " + str.hashCode());
} catch (NoSuchFieldException | SecurityException
| IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
値が再び変わったようですが、その理由は何ですか?
finalを使用して基本データ型とStringのデータを変更すると、代入時に右側が定数式である限り、コンパイラーが最適化されることがわかります。印刷すると、
System.out.println("str = " + str);
この文は直接コンパイルされます
System.out.println("str = " + "ABCD");
したがって、2番目のコードでは、JVMがコンパイル時に最適化されているため、それを変更することはできません。また、toString()メソッドを呼び出すと、リフレクションによって変更された新しい値が取得されます。
結論:
基本データ型のデータおよび文字列として定義されている場合、値を割り当てるときに右側が定数式である限り、コンパイラーの最適化が実行されます。コンパイラーによってインラインで最適化されない最後のプロパティがリフレクションによって効果的に反映される限り、修正
それでは、JVMのコンパイル時の最適化を回避する方法は何ですか?
1。割当て、キャンセルの自動最適化は、三元式の評価として、コンパイルされた方法を変更します
final String str= (null!=null?"":"42");
方法2. 最初に定義してから値を割り当て、最後に空白を使用する
static final int primitiveInt;
static final Integer i;
static String str;
static {//这里改为用静态代码块赋值
primitiveInt = 42;
wrappedInt = 42;
stringValue = "42";
}