1。オブジェクトとメモリコントロールに関する知識ポイント
1.ローカル変数、メンバー変数(インスタンス変数、クラス変数)を含むJava変数の初期化プロセス。
2。継承関係では、コンパイル時間タイプとランタイムタイプが使用されるオブジェクト参照変数のコンパイル時間タイプと異なる場合、オブジェクトにアクセスするプロパティと方法に違いがあります。
3。最終的な修飾子特性。
2。Java変数の分割および初期化プロセス
Javaプログラムの変数は、メンバー変数とローカル変数にほぼ分割できます。メンバー変数は、インスタンス変数(非静的変数)およびクラス変数(静的変数)に分割できます。一般に、遭遇するローカル変数は、次の状況に表示されます。
(1)正式なパラメーター:メソッド署名で定義されているローカル変数は、発信者によって割り当てられ、メソッドが終了すると消えます。
(2)メソッド内のローカル変数:メソッドで定義されているローカル変数は、メソッドの初期化(初期値を割り当てて)する必要があり、変数の初期化が始まり、終了すると消えます。
(3)コードブロックのローカル変数:コードブロックで定義されているローカル変数は、コードブロックに表示する必要がある初期化(初期値を割り当てる)する必要があります。初期化が完了すると有効になり、コードブロックが終了すると死にます。
パッケージcom.zlc.array;パブリッククラスのテストフィールド{{string b; //初期化されていない場合、コンパイラはローカル変数bが初期化されていない可能性があると報告します。 } public static void main(string [] args){int a; //初期化されていない場合、コンパイラはローカル変数Aを報告します。Aout.Out.Println(a); }}静的で変更されたメンバー変数は、クラス自体に属するクラス変数です。静的で変更されていないメンバー変数は、インスタンス変数です。このクラスに属するインスタンスは、同じJVMで、各クラスは1つのクラスオブジェクトにのみ対応できますが、各クラスは複数のJavaオブジェクトを作成できます。 (つまり、クラス変数にはメモリスペースの1つのみが必要であり、クラスがインスタンスを作成するたびに、インスタンス変数にスペースを割り当てる必要があります)
インスタンス変数の初期化プロセス:構文の観点から、プログラムは3つの場所でインスタンス変数の初期化を実行できます。
(1)インスタンス変数を定義するときに初期値を指定します。
(2)非静的ブロックのたとえば変数の初期値を指定します。
(3)コンストラクターのたとえば変数の初期値を指定します。
その中で、2つの方法(1)と(2)の初期化時間は、コンストラクターの(3)の初期時間よりも早く、2つの初期化順序(1)と(2)は、ソースコードに配置されている順序で決定されます。
パッケージcom.zlc.array;パブリッククラスのテストフィールド{public testfield(int age){system.out.println( "constructor ="+this.age)の初期化this.age); this.age = age; } {system.out.println( "非静的なブロックで初期化");年齢= 22; } // int age = 15を初期化します。 public static void main(string [] args){testfield field = new testfield(24); System.out.println( "final age ="+field.age); }}実行の結果は次のとおりです。初期化。
最終年齢= 24
Javapの使用方法がわかっている場合は、Javap -C xxxx(classファイル)を使用して、Javaクラスのコンパイル方法を確認できます。
インスタンス変数を定義するときは、初期値を指定します。初期化ブロックでは、インスタンス変数の初期値を指定するステートメントのステータスが等しくなります。コンパイラがコンパイルされ、処理された後、それらはすべてコンストラクターで言及されます。上記のINT年齢= 15は、次の2つのステップに分割されます。
1)int age; Javaオブジェクトを作成するとき、システムはステートメントに従ってオブジェクトにメモリを割り当てます。
2)年齢= 15;このステートメントは、Javaクラスのコンストラクターに抽出され、実行されます。
クラス変数の初期化プロセス:構文の観点から見ると、プログラムは2つの場所からクラス変数に初期化および割り当てを割り当てることができます。
(1)クラス変数を定義するときに初期値を指定します。
(2)静的ブロック内のクラス変数の初期値を指定します。
2つの実行命令は、ソースコードでのアレンジメントと同じです。小さな異常な例を見てみましょう:
パッケージcom.zlc.array; class testStatic {//クラスメンバーテストスタティックインスタンス最終的な静的テストスタティックデモ= new TestStatic(15); //クラスメンバーStatic Int Age = 20; //インスタンス変数校長intキュレージ。 Public TestStatic(int Year){// TODO自動生成コンストラクタースタブCurage = Year -Year; }} public class test {public static void main(string [] args){system.out.println(teststatic.demo.curage); testStatic staticDemo = new TestStatic(15); system.out.println(staticdemo.curage); }}出力の結果は2行に印刷されます。 1つは、testattic class属性デモのインスタンス変数を印刷することであり、2番目はJavaオブジェクトのStaticDemoを介してtestStaticのインスタンス属性を出力することです。上記で分析したインスタンス変数とクラス変数の初期化プロセスに従って、次のように推測できます。
1)初期化の最初の段階では、クラスをロードするときに、クラス変数のデモと年齢にメモリスペースを割り当てます。現時点では、デモと年齢のデフォルト値はそれぞれ無効です。
2)初期化の第2段階では、プログラムは初期値をデモに割り当て、順番に年齢を付けます。 TestStatic(15)は、testStaticのコンストラクターを呼び出す必要があります。この時点で、年齢= 0なので、印刷の結果は-15です。 StaticDemoが初期化されると、年齢は20に割り当てられているため、出力の結果は5です。
3.メンバー変数を継承することと、相続関係におけるメンバーのメソッドを継承することの違い
Javaオブジェクトを作成するとき、プログラムは常に最初に親クラスの非静的ブロックおよび親クラスコンストラクターを呼び出し、最後にこのクラスの非静的ブロックとコンストラクターを呼び出します。サブクラスのコンストラクターを介して親クラスのコンストラクターを呼び出すことは、一般に2つの状況に分割されます。1つは暗黙の呼び出しであり、もう1つは親クラスのコンストラクターを呼び出すスーパーディスプレイです。
子クラスの方法は、親クラスのインスタンス変数を呼び出すことができます。これは、子クラスが親クラスを継承し、親クラスのメンバー変数と方法を取得するためです。ただし、親クラスは、親クラスがどのクラスを継承するか、どの種類のメンバー変数がサブクラスを追加するかを知らないため、子クラスのインスタンス変数にアクセスできません。もちろん、いくつかの極端な例では、親クラスは引き続きチャイルドクラス変数を呼び出すことができます。たとえば、子クラスは親クラスの方法を書き換え、一般にデフォルト値を印刷します。これは、子クラスのインスタンス変数が現時点で初期化されていないためです。
パッケージcom.zlc.array; class father {int age = 50; public father(){// todo auto-enerated constructor stub.out.println(this.getClass()); //this.sonmethod(); info()を呼び出すことはできません; } public void info(){system.out.println(age); }}パブリッククラスの息子は父親を拡張します{int age = 24;パブリックソン(int age){// todo auto-enerated constructor stub this.age = age; } @Override public void info(){// todo auto-enerated method stub.err.println(age); } public static void main(string [] args){new Son(28); } //サブクラス固有の方法public void sonmethod(){system.out.println( "son method"); }}通常の推論によれば、親クラスのコンストラクターは暗黙的にサブクラスを介して呼び出され、情報()メソッドは親クラスコンストラクターで呼び出されます(注:親クラスが呼ばれているとは言わなかった)。理論的には、親クラスの年齢インスタンス変数を出力します。印刷の結果は50になると予想されますが、実際の出力結果は0です。理由の分析:
1)Javaオブジェクトのメモリ割り当ては、コンストラクターで完了していません。コンストラクターは、初期化割り当てプロセスのみを完了します。つまり、親クラスのコンストラクターを呼び出す前に、JVMはSONオブジェクトのメモリスペースを分類しています。このスペースには2つの年齢の属性があり、1つはサブクラスの年齢であり、もう1つは親クラスの年齢です。
2)新しい息子を呼び出すとき(28)、現在のこのオブジェクトは、サブクラスの息子であるオブジェクトを表します。 Object.getClass()を印刷し、クラスcom.zlc.array.sonの結果を取得できます。ただし、現在の初期化プロセスは、親クラスのコンストラクターで実行され、このコンパイルタイプは父親であるため、this.sonmethod()を介して呼び出すことはできません。
3)変数のコンパイル時間タイプがランタイムタイプと異なる場合、変数を介して参照オブジェクトのインスタンス変数にアクセスするとき、インスタンス変数の値は宣言された変数のタイプによって決定されます。ただし、オブジェクトのインスタンスメソッドが変数を介して参照する場合、メソッドの動作は実際に参照するオブジェクトによって決定されます。したがって、サブクラスの情報方法はここで呼び出されるため、サブクラスの年齢が印刷されます。年齢はまだ緊急に初期化されていないため、デフォルト値は0です。
素人の用語では、宣言されたタイプが実際の新しいタイプと矛盾する場合、使用される属性は親クラスであり、呼ばれる方法は子クラスです。
Javap -Cを介して、属性とメソッドの継承の間に大きな違いがある理由をより直接的に理解できます。上記の例でサブクラスの息子の情報を書き換える情報を削除すると、この時点で親クラスの情報方法が呼び出されます。コンパイルすると、親クラスの情報方法がサブクラスに転送され、評判メンバー変数は親クラスに残され、転送されません。このように、サブクラスと親クラスには、同じ名前のインスタンス変数があります。サブクラスが同じ名前で親クラスの方法を書き換えた場合、サブクラス法は親クラスの方法を完全に上書きします(Javaがこのように設計されている理由については、私はあまり明確ではありません)。同じ名前の変数が存在する可能性があり、同時に上書きされません。同じ名前のメソッドのサブクラスは、親クラスの同じ名前の方法を完全に上書きします。
一般に、参照変数の場合、変数を介して参照するオブジェクトのインスタンス変数にアクセスするとき、インスタンス変数の値は変数が宣言されたときに型に依存し、変数を介して参照されるオブジェクトのメソッドにアクセスするとき、メソッドの動作は実際に参照するオブジェクトのタイプに依存します。
最後に、小さなケースでレビューします。
パッケージcom.zlc.array; class animal {int age; public Animal(){} public Animal(int age){// todo auto-enerated constructor stub this.age = age; } void run(){system.out.println( "Animal Run"+age); }}授業犬は動物を拡張します{int age;文字列名; public Dog(int age、string name){// todo auto-enerated constructor stub this.age = age; this.name = name; } @Override void run(){system.out.println( "dog run"+age); }} public class testextends {public static void main(string [] args){動物動物= new Animal(5); System.out.println(Animal.age); Animal.run();犬の犬= new Dog(1、 "Xiaobai"); System.out.println(dog.age); dog.run(); Animal Animal2 = New Dog(11、 "Wangcai"); System.out.println(animal2.age); Animal2.run();動物動物3; Animal3 = dog; System.out.println(Animal3.age); animal3.run(); }}親クラスのメソッドを呼び出す場合:スーパーを介して呼び出すことはできますが、スーパーキーワードはオブジェクトを参照せず、実際の参照変数として使用することはできません。興味のある友達は自分でそれを勉強することができます。
上記は、変数と方法の例です。クラス変数とクラスメソッドははるかに単純なため、クラス名を直接使用します。方法ははるかに便利で、それほどトラブルに遭遇することはありません。
4。最終的な修飾子の使用(特にマクロ交換)
(1)INALは変数を変更できます。ファイナルによって変更された変数に初期値が割り当てられた後、再度割り当てることはできません。
(2)INALはメソッドを変更でき、最終的な変更方法は書き換えられません。
(3)INALはクラスを変更でき、ファイナルによって変更されたクラスはサブクラスを導き出すことはできません。
最終的に変更された変数を指定する初期値を表示する必要があります。
たとえば、最終的な変更された変数では、初期値は次の3つの指定された位置でのみ割り当てることができます。
(1)最終インスタンス変数を定義するときに初期値を指定します。
(2)非静的ブロックで最終インスタンス変数の初期値を指定します。
(3)コンストラクターの最終インスタンス変数の初期値を指定します。
それらは最終的に初期化のためにコンストラクターで言及されます。
最終的に指定されたクラス変数の場合:初期値は、指定された2つの場所でのみ割り当てることができます。
(1)最終クラス変数を定義するときに初期値を指定します。
(2)静的ブロックで最終クラス変数の初期値を指定します。
また、インスタンス変数とは異なり、コンパイラによって処理され、クラス変数はすべて静的ブロックに初期値を割り当てるために言及され、インスタンス変数はコンストラクターに言及されます。
最終的に変更されたクラス変数には、「マクロ交換」という別の機能があります。変更されたクラス変数が変数を定義するときに初期値を満たす場合、コンピレーション中に初期値を決定できる場合(たとえば、18、「AAAA」、16.78およびその他の直接数量)、最終的に変更されたクラス変数は「マクロ変数」として扱います(これは頻繁に呼ばれるものです)。コンピレーション中に初期値を決定できる場合、初期化の静的ブロックでは言及されず、初期値はクラス定義の最終変数に直接置き換えられます。年齢を差し引いた年の例を挙げましょう:
パッケージcom.zlc.array; class testStatic {//クラスメンバーテストスタティックインスタンス最終的な静的テストスタティックデモ= new TestStatic(15); //クラスメンバー年齢最終static int age = 20; //インスタンス変数校長intキュレージ。 Public TestStatic(int Year){// TODO自動生成コンストラクタースタブCurage = Year -Year; }} public class test {public static void main(string [] args){system.out.println(teststatic.demo.curage); testStatic static1 = new TestStatic(15); System.out.println(static1.curage); }}この時点で、年齢は最終的に変更されているため、コンパイルすると、親クラスのすべての年齢が20になり、変数ではありません。
特に文字列を比較する場合は、さらに表示できます
パッケージcom.zlc.array;パブリッククラスのテストストリング{static string static_name1 = "java"; static string static_name2 = "me"; static string static statci_name3 = static_name1+static_name2; final static string final_static_name1 = "java"; final static string final_static_name2 = "me"; //ファイナルを追加するかどうか、前面のマクロに置き換えることができます。 final string final_statci_name3 = final_static_name1+final_static_name2; public static void main(string [] args){string name1 = "java";文字列name2 = "me";文字列name3 = name1+name2; //(1)system.out.println(name3 == "javame"); //(2)system.out.println(testString.statci_name3 == "javame"); //(3)system.out.println(testString.final_statci_name3 == "javame"); }}最終的な変更方法とクラスを使用することについて何も言うことはありません。一方はサブクラス(プライベートなど)で書き直すことができず、もう1つはサブクラスを導き出すことができません。
最終的にローカル変数を変更する場合、Javaは、内部クラスからアクセスされるローカル変数が最終的に変更されることを要求します。理由があります。通常のローカル変数の場合、それらのスコープはメソッド内に残ります。メソッドが終了すると、ローカル変数が消えますが、内部クラスは暗黙の「閉鎖」を生成する可能性があります。これにより、ローカル変数はその場所から分離されたままになります。
時には、スレッドがメソッドで新しくなり、メソッドのローカル変数が呼び出されます。この時点で、変更変数は最終的な変更として宣言する必要があります。
5。オブジェクト占有メモリの計算方法
java.lang.runtimeクラスでfreememory()、totalmemory()、およびmaxmemory()メソッドを使用して、Javaオブジェクトのサイズを測定します。この方法は通常、多くのリソースを正確に決定する必要がある場合に使用されます。この方法は、生産システムキャッシュを実装するのにほとんど役に立たない。この方法の利点は、データ型がサイズに依存しないことであり、異なるオペレーティングシステムが占有されているメモリを取得できることです。
反射APIを使用して、オブジェクトのメンバー変数の階層をトラバースし、すべての元の変数のサイズを計算します。このアプローチでは、それほど多くのリソースを必要とせず、キャッシュされた実装に使用できます。欠点は、元のタイプサイズが異なり、JVMの実装が異なることは異なる計算方法を持っていることです。
JDK5.0の後、インストルメンテーションAPIは、オブジェクトが占めるメモリサイズを計算するためのGetObjectSizeメソッドを提供します。
デフォルトでは、参照されるオブジェクトのサイズは計算されません。参照されるオブジェクトを計算するために、反射を使用してそれを取得できます。次の方法は、参照オブジェクトのサイズを計算する上記の記事で提供される実装です。
public class sizeofagent {staticインストゥルメンテーションInst; / ** Agentを初期化*/ public static void premain(string agentargs、Instrumation instp){inst = instp; } /***メンバーのサブオブジェクトなしでオブジェクトサイズを返します。 * @param oオブジェクト * @return object size */public static long sizeof(object o){if(inst == null){新しいIllegalStateException( "スローインストゥルメンテーション環境にアクセスできません。/N" + "sizefagentクラスを含むJARファイルがIS/n" + "を含むJARファイルがjava's/" -javaagent/"command/") } inst.getObjectsize(o); } /** *階層グラフを介してオブジェクトのフルサイズを計算します。 * @paramオブジェクト * @returnオブジェクトサイズ */ public static long fullsizeof(object obj){map <object、object> visited = new IdentityHashmap <object、object>(); stack <object> stack = new stack <object>(); long result = internalsizeof(obj、stack、訪問); while(!stack.isempty()){result += internalsizeof(stack.pop()、stack、訪問); } visited.clear();返品結果; } private static boolean skipobject(object obj、map <object、object>訪問){if(obj instanceof string){//インターン文字列if(obj ==((string))obj).intern()){return true; }} return(obj == null)//訪問したオブジェクトをスキップ|| visited.containskey(obj); } private static long internalsizeof(object obj、stack <object> stack、map <object、object>訪問){if(skipobject(obj、visited)){return 0; } visited.put(obj、null);長い結果= 0; //オブジェクトのサイズ +プリミティブ変数 +メンバーポイントresult + = sizeofagent.sizeof(obj); //すべての配列要素クラスclazz = obj.getClass()を処理します。 if(clazz.isarray()){if(clazz.getname()。length()!= 2){//プリミティブタイプarray int length = array.getLength(obj); for(int i = 0; i <length; i ++){stack.add(array.get(obj、i)); }} return result; } //オブジェクトのすべてのフィールドを処理しますwhile(clazz!= null){field [] fields = clazz.getDeclaredfields(); for(int i = 0; i <fields.length; i ++){if(!modifier.isstatic(fields [i] .getModifiers())){if(fields [i] .getType()。isprimitive())){継続; //プリミティブフィールドをスキップ} else {fields [i] .setAccessible(true); try {//推定されるオブジェクトは、オブジェクトオブジェクトをスタックすることに配置されます= fields [i] .get(obj); if(objectToAdd!= null){stack.add(objectToAdd); }} catch(Illegalaccessexception ex){assert false; }}}}} clazz = clazz.getsuperclass(); } return result; }}