コード例
まず、この調査の内容を説明するために例を見てみましょう。
public class StringTest {
private String aa = "1111";
private StringBuilder bb = new StringBuilder("bbbb");
private char ca = 'y';
private Character cr = 'z';
private char[] ar = {
'a', 'b', 'c'};
{
System.out.println("这是一个考研");
}
static {
System.out.println("这是一个考验");
}
public static void main(String[] args) {
try {
StringTest test = new StringTest();
test.change(test.bb, test.aa, test.cr, test.ca, test.ar, test);
System.out.print(test.bb);
System.out.print(test.aa);
System.out.print(test.ca);
System.out.print(test.cr);
System.out.println(test.ar);
Field field = StringTest.class.getDeclaredField("aa");
field.setAccessible(true);
field.set(test, "0000");
System.out.print(test.bb);
System.out.print(test.aa);//0 修改String值成功
System.out.print(test.ca);
System.out.print(test.cr);
System.out.println(test.ar);
} catch (Exception e) {
e.printStackTrace();
}
}
public void change(StringBuilder bb, String aa, Character cr, char ca, char[] ar, StringTest st) {
bb.append(" is Change");
aa = "1111 AND ";
cr = new Character('m');
ca = 'n';
ar[0] = 'g';
st.aa = "3333 AND";
}
}
出力結果:
この結果は、メソッド内でString型の変数を再割り当てしても、String変数のプロトタイプに影響がないことを示しています。さて、この例のロジックと実行結果が明確に表示されています。次に、この小さなプログラムを分析してみましょう。その前に、Javaのいわゆる「値渡し」と「参照渡し」の問題を確認しましょう。
Javaの「値渡し」と「参照渡し」の問題
これは、Java言語ではいわゆる「C言語の値渡しおよびポインタ渡しの問題」であるため、Javaを初めて使用する多くのプログラマーはこの問題について考えています。
最終的な結論は次のとおりです。
***Javaでは、基本型がパラメーターとしてメソッドに渡されると、メソッド内でパラメーターがどのように変更されても、外部変数が内部にあるため、外部変数のプロトタイプは常に変更されません。メソッドのコピー、およびこのコピーへの変更は、外部変数の値を変更しません。***コードは上記の例に似ています:
int number = 0;
changeNumber(number) {
number++}; //改变送进的int变量
System.out.println(number); //这时number依然为0
これは「値の受け渡し」と呼ばれます。つまり、メソッドはパラメーター変数(つまり、プロトタイプ変数の値のコピー)を操作し、変数自体ではなく、プロトタイプ変数のコピーのみを変更します。したがって、可変プロトタイプはそれによって変更されません。
ただし、メソッドに渡されるパラメーターが非基本型(つまり、オブジェクト型の変数)の場合、メソッドでパラメーター変数が変更されると、変数のプロトタイプも変更されます。コードは次のとおりです。上記の例にも似ています:
StringBuffer strBuf = new StringBuffer(“original”);
changeStringBuffer(strBuf) {
strbuf.apend(“ is changed!”)} //改变送进的StringBuffer变量
System.out.println(strBuf); //这时strBuf的值就变为了original is changed!
この機能は「参照渡し」と呼ばれ、参照渡しとも呼ばれます。つまり、メソッドがパラメーター変数を操作すると、変数の参照がコピーされます。メソッドに渡されるパラメーターは変数への参照であることに注意してください。 、これは実際にはポインタであり、この参照を使用して変数(この場合はオブジェクト)の実際のアドレスを見つけて操作します。メソッドが終了すると、メソッド内のパラメーター変数は消えます。ただし、この変数はオブジェクトへの単なる参照であり、オブジェクト自体ではなく、オブジェクトの実際のアドレスを指しているだけなので、その消失によって悪影響が生じることはありません。プロトタイプ変数を振り返ると、プロトタイプ変数は基本的にそのオブジェクトへの参照です(パラメーター変数と同じです)。パラメーター変数が指すオブジェクトへの元の変更は、プロトタイプが指すオブジェクトへの変更です。変数。そのため、プロトタイプ変数で表されるオブジェクトが変更され、変更が保持されます。
この古典的な問題を理解した後、多くの注意深い読者はすぐに新しい質問をします。「しかし、文字列型はJava言語では非プリミティブ型です!メソッドの変更が保持されないのはなぜですか!」確かに、これは質問です。この新しい質問は、その古典的な質問のほとんどすべての結論を覆します。そうですか?では、分析に移りましょう。
文字列パラメータの受け渡しに関する誤解の1つ-直接代入とオブジェクト代入
文字列型の変数をパラメータとして使用する場合、基本型の変数のように値で渡すにはどうすればよいですか?何人かの友人はこの問題について説明しました。
1つの説明は、String型の変数を割り当てる場合、新しいオブジェクトはありませんが、文字列が直接割り当てられるため、JavaはString型のこの変数を基本型として扱うということです。つまり、String str = "original";ではなく、String str = new String( "original");です。文字列型変数への割り当てが異なるため、これは問題ですか?前の例に少し変更を加えて、実行後の結果を見てみましょう。変更されたコードは次のとおりです。
private void testB() {
String originalStr = new String("original");
System.out.println("Test B Begin:");
System.out.println("The outer String: " + originalStr);
changeNewString(originalStr);
System.out.println("The outer String after inner change: " + originalStr);
System.out.println("Test B End:");
System.out.println();
}
public void changeNewString(String original) {
original = new String(original + " is changed!");
System.out.println("The changed inner String: " + original);
}
この実行の結果がどのようになるか見てみましょう。
Test B Begin:
The outer String: original
The changed inner String: original is changed!
The outer String after inner change: original
Test B End.
実践は、この声明が間違っていることを証明しました。
実際、文字列の直接割り当てとnewを使用したオブジェクトの割り当ての違いは、ストレージメソッドにのみあります。
簡単に説明
します。文字列が直接割り当てられると、String型の変数によって参照される値は、クラスの定数プールに格納されます。一方、「original」自体は文字列定数であるため、Stringは不変型であるため、このString型変数は定数への参照と同等です。この場合、変数のメモリスペースのサイズはコンパイル時に決定されます。
新しいオブジェクトの方法は、「オリジナル」をStringオブジェクトのメモリヒープスペースに格納することであり、この格納アクションは実行時に実行されます。この場合、Javaは「元の」文字列を定数として扱いません。これは、文字列オブジェクトを作成するためのパラメータとして表示されるためです。
したがって、Stringの割り当て方法は、パラメーターの受け渡しの問題とは直接関係ありません。要するに、この説明は正しくありません。
4.文字列パラメータの受け渡しの問題の2番目の誤解-「=」変数値とメソッド割り当ての違い。
一部の友人は、変数値の非同期の問題は値の変更の妨げになっていると考えています。
このステートメントは次のように考えています。「Javaでは、パラメータの値を変更する状況は2つあります。1つは割り当て番号「=」を使用して値を直接割り当てて変更する方法です。2つ目は、オブジェクト自体のmemberメソッドなどを使用して、メンバーデータを変更します。最初のケースでは、変更はパラメータ変数に渡されたメソッド以外のデータに影響を与えないか、直接変更されないと考えられます。逆に、2番目の方法は、ソースデータに影響を与えます。参照が指すオブジェクトは変更されず、メンバーデータも変更されるため、実際に変更されるのはオブジェクトです。」このビューは、クラスのメンバー変数を使用する必要があります。メンバーデータを変更して、メンバーデータを正常に変更します。
このメソッドは少し聞こえます...、まだ古いメソッドを使用し、デモを作成し、小さなテストを行います。コードは次のとおりです。
private void testC() {
String originalStr = new String("original");
System.out.println("Test C Begin:");
System.out.println("The outer String: " + originalStr);
changeStrWithMethod(originalStr);
System.out.println("The outer String after inner change: " + originalStr);
System.out.println("Test C End.");
System.out.println();
}
private static void changeStrWithMethod(String original) {
original = original.concat(" is changed!");
System.out.println("The changed inner String: " + original);
}
結果は次のとおりです。
Test C Begin:
The outer String: original
The changed inner String: original is changed!
The outer String after inner change: original
Test C End.
どのように、これは問題がここにないことを証明します、それで
この状況の理由は何ですか?
さて、これが私の説明です。
この問題の本当の理由は、Stringクラスの格納が、結果を格納するために最終的に変更されたchar[]配列を介して行われるためです。変えられない。したがって、外部からのString型の参照がメソッドに渡されるたびに、外部からのString型の変数の参照がメソッドパラメーター変数に渡されるだけです。正しい。外部文字列変数とメソッドパラメータ変数は、実際のchar[]配列への単なる参照です。したがって、メソッド内でこのパラメーターの参照を変更すると、char []配列は変更できないため、新しい変数を作成するたびに、新しいStringインスタンスが作成されます。明らかに、外側のString型変数は新しいStringインスタンスを指していません。したがって、新しい変更は取得されません。
次のプログラムルーチンは、tStringがAメモリ空間を指し、Aメモリ空間が文字列 "hello"を格納し、modst関数を呼び出してtString参照をテキスト参照に割り当てることを前提としています。これは参照であることに注意してください。これは確かに参照渡しです。Stringは不変であり、それを変更する操作を行うと、新しいStringインスタンスが生成されることがわかっています。したがって、このメソッドでは、テキストはBスペースを指し、Bスペースは「sdf」文字列を格納しますが、この時点では、tStringは引き続きAスペースを指し、Bスペースを指していません。
文字列のソースコードから、次の情報を知ることができます。a
)文字列は最終的に変更されたクラスであるため、継承できず、書き換えなどはありません。
b)文字列の実際のストレージは配列であり、最終的に変更され、スペースが割り当てられた後、メモリアドレスは変更されません。
c)すべてのメンバー変数はプライベート最終変更であり、対応するXXXSetterメソッドは提供されていません。これらのフィールドは外部から変更できず、一度だけ割り当てることができます。
d)値配列(上記のソースコードの一部のみ)に関連する操作はすべて、配列の要素をコピーする方法を使用します。これにより、文字配列を内部で変更できない
ため、文字列は初期化後に不変になります。
初期化された文字列の値を変更する方法
不変のクラスでも、リフレクションによってそのプロパティの値を変更できます。Javaリフレクションは、従来のすべてのJava理論を覆します。IllegalArgumentException-
指定されたオブジェクトが、基になるフィールド(またはそのサブクラスまたは実装者)を宣言するクラスまたはインターフェイスのインスタンスでない場合、または解凍変換が失敗する場合。JVMはコンパイル時に文字列の最終タイプを最適化するため、コンパイル時に文字列を定数として扱います。したがって、String aa = "1111"の値を直接変更することはできませんが、基になるフィールド(またはそのサブクラスまたは実装者)を宣言してString aa="1111"を変更するクラスまたはインターフェイスのインスタンスによって変更できます。
不変クラスをカスタマイズするにはどうすればよいですか?
要約すると、不変クラスをカスタマイズするにはどうすればよいですか?
1)クラスはfinal修飾子で変更され
ます2)クラスのすべてのフィールドはprivate finalで変更されます
3)XXXSetterメソッドは提供されず、getXXXメソッドはオブジェクト自体ではなくコピーされたオブジェクトを返します。
4)コンストラクターがメンバー変数を初期化するときは、ディープコピーを使用します。
ディープコピーとは何ですか?
'ディープコピーは、オブジェクトの完全に独立したコピーであり、ディープコピーはすべての属性をコピーし、属性が指す動的に割り当てられたメモリをコピーします。ディープコピーは、オブジェクトが参照するオブジェクトと一緒にコピーされるときに発生します。深いコピーは浅いコピーよりも遅く、高価です。つまり、ディープコピーは、コピーされるオブジェクトによって参照されるすべてのオブジェクトをコピーします。
ディープコピーを実装する方法は?
オブジェクトのコピーを実装するクラスは、Cloneableインターフェースを実装し、clone()をオーバーライドする必要があります。
注:Cloneableインターフェースが実装されていない場合、CloneNotSupportedExceptionランタイム例外が発生します。
例:
/*
* 实现深拷贝
* */
class Teacher implements Cloneable {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Object clone() throws CloneNotSupportedException
{
return super.clone();
}
}
class Student_One implements Cloneable{
private String name;
private Teacher teacher;//添加教师的引用
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();//调用obeject的clone默认为浅拷贝
}
}
class Student_Two implements Cloneable{
private String name;
private Teacher teacher;//添加教师的引用
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public Object clone() throws CloneNotSupportedException {
//return super.clone();//调用obeject的clone默认为浅拷贝
Student_Two student = (Student_Two) super.clone();
student.setTeacher((Teacher) student.getTeacher().clone());//T复制一份eacher对象并重新set进来
return student;
}
}
public class StringDemo1 {
public static void main(String[] args) throws CloneNotSupportedException {
//新建一个老师对象
Teacher teacher = new Teacher();
teacher.setAge(25);
teacher.setName("李华");
Student_One student_one = new Student_One();
student_one.setName("同学甲");
System.err.println();
student_one.setTeacher(teacher);
//拷贝一个Student_One对象(浅拷贝)
Student_One student_one1 = (Student_One)student_one.clone();
System.err.println(student_one1.getTeacher().getName());//李华 原拷贝对象
//修改老师的名字,会把拷贝的对象的老师名称也一同修改了,因为它们指向的是同一块地址,也就是同一个对象
teacher.setName("黄珊");
System.err.println(student_one1.getTeacher().getName());//黄珊
//重新设置老师名为为李华
teacher.setName("李华");
Student_Two student_two = new Student_Two();
student_two.setTeacher(teacher);
student_two.setName("同学乙");
//拷贝一个Student_Two对象
Student_Two student_two1 = (Student_Two) student_two.clone();
System.err.println(student_two1.getTeacher().getName());//李华 原拷贝对象
//修改老师的名字,打印发现并没有影响原拷贝对象的值,所以为深拷贝,是不同的两个对象
teacher.setName("黄珊");
System.err.println(student_two1.getTeacher().getName());//李华
}
}