Javaでは、変数にfinal
キーワードを使用して、その値を変更しないように指定します。しかし、私はあなたがクラスのコンストラクタ/メソッドの値を変更できることを見ます。繰り返しますが、変数がstatic
の場合、それはコンパイルエラーです。
これがコードです:
import Java.util.ArrayList;
import Java.util.List;
class Test {
private final List foo;
public Test()
{
foo = new ArrayList();
foo.add("foo"); // Modification-1
}
public static void main(String[] args)
{
Test t = new Test();
t.foo.add("bar"); // Modification-2
System.out.println("print - " + t.foo);
}
}
上記のコードは正常に動作し、エラーはありません。
今度は変数をstatic
に変更します。
private static final List foo;
今はコンパイルエラーです。このfinal
は本当にどのように機能しますか?
あなたはいつも initialize final
変数に許可されています。コンパイラは、あなたが一度だけできることを確認します。
final
変数に格納されているオブジェクトのメソッドを呼び出すことは、final
のセマンティクスとは無関係です。言い換えれば、final
は参照そのものに関するものであり、参照されるオブジェクトの内容に関するものではありません。
Javaにはオブジェクト不変性の概念はありません。これはオブジェクトを注意深く設計することによって達成され、そして些細な試みではありません。
これはお気に入りのインタビューの質問です。この質問により、インタビュアーは、コンストラクター、メソッド、クラス変数(静的変数)、およびインスタンス変数に関してオブジェクトの動作をどの程度理解しているかを見つけようとします。
import Java.util.ArrayList;
import Java.util.List;
class Test {
private final List foo;
public Test() {
foo = new ArrayList();
foo.add("foo"); // Modification-1
}
public void setFoo(List foo) {
//this.foo = foo; Results in compile time error.
}
}
上記の場合、「Test」のコンストラクターを定義し、「setFoo」メソッドを指定しました。
コンストラクターについて:コンストラクターのみを呼び出すことができますonenew
キーワードを使用して、オブジェクト作成ごとの時間。コンストラクターは複数回呼び出すことはできません。コンストラクターはそのように設計されていないためです。
メソッドについて:メソッドは何度でも呼び出すことができ(一度も呼び出されず)、コンパイラはそれを認識します。
シナリオ1
private final List foo; // 1
foo
は、インスタンス変数です。 Test
クラスオブジェクトを作成すると、インスタンス変数foo
がTest
クラスのオブジェクト内にコピーされます。コンストラクター内でfoo
を割り当てた場合、コンパイラーはコンストラクターが1回だけ呼び出されることを知っているため、コンストラクター内で問題なく割り当てることができます。
メソッド内でfoo
を割り当てた場合、コンパイラはメソッドを複数回呼び出すことができることを知っています。つまり、値を複数回変更する必要があり、final
変数。そのため、コンパイラはコンストラクタが適切であると判断します! 最終変数に値を割り当てることができるのは1回だけです。
シナリオ2
private static final List foo = new ArrayList();
foo
はstatic変数になりました。 Test
クラスのインスタンスを作成すると、foo
は静的であるため、foo
はオブジェクトにコピーされません。 foo
は、各オブジェクトの独立したプロパティではありません。これは、Test
クラスのプロパティです。ただし、foo
は複数のオブジェクトで見ることができ、すべてのオブジェクトがnew
キーワードを使用して作成され、最終的に複数のオブジェクトの作成時に値を変更するTest
コンストラクターを呼び出します。 (static foo
はすべてのオブジェクトにコピーされるわけではなく、複数のオブジェクト間で共有されることに注意してください。)
シナリオ
t.foo.add("bar"); // Modification-2
上記Modification-2
はあなたの質問からです。上記の場合、最初に参照されるオブジェクトを変更するのではなく、許可されているfoo
内にコンテンツを追加しています。 new ArrayList()
をfoo
参照変数に割り当てようとすると、コンパイラはエラーを出します。
ルールfinal
変数を初期化した場合、別のオブジェクトを参照するように変更することはできません。 (この場合ArrayList
)
最終クラスはサブクラス化できません
finalメソッドはオーバーライドできません。 (このメソッドはスーパークラスにあります)
finalメソッドはオーバーライドできます。 (文法的にこれを読んでください。このメソッドはサブクラスにあります)
Final キーワードにはたくさんの使い方があります。
その他の用法
静的クラス変数はJVMの最初から存在し、クラス内で初期化する必要があります。こうしてもエラーメッセージは表示されません。
final
キーワードは、それが何に使われているかに応じて、2つの異なる方法で解釈することができます。
値の型: int
s、double
sなどの場合、値が変更できないようにします。
参照型: オブジェクトへの参照の場合、final
は reference が決して変更されないことを保証します。つまり、常に同じオブジェクトを参照します。参照されているオブジェクト内の値が同じであることについては一切保証されません。
そのため、final List<Whatever> foo;
はfoo
が常に 同じ listを参照することを保証しますが、 このリストの内容 は時間の経過とともに変わる可能性があります。
foo
を静的にする場合は、次の例のようにクラスコンストラクタで初期化する(または定義する場所でインラインにする)必要があります。
クラスコンストラクタ(インスタンスではない):
private static final List foo;
static
{
foo = new ArrayList();
}
列をなして:
private static final List foo = new ArrayList();
ここでの問題は、final
修飾子がどのように機能するのかではなく、むしろstatic
修飾子がどのように機能するのかということです。
final
修飾子はあなたのコンストラクタへの呼び出しが完了するまでにあなたの参照の初期化を強制します(すなわち、あなたはそれをコンストラクタで初期化しなければなりません)。
属性をインラインで初期化すると、コンストラクタに定義したコードが実行される前に属性が初期化されるため、次のような結果になります。
foo
がstatic
の場合、foo = new ArrayList()
はクラスに定義したstatic{}
コンストラクタが実行される前に実行されます。foo
がstatic
でなければ、foo = new ArrayList()
はコンストラクタが実行される前に実行されます属性をインラインで初期化しない場合、final
修飾子は、属性を初期化することと、コンストラクターで初期化することを強制します。 static
修飾子もある場合は、属性を初期化する必要があるコンストラクタはクラスの初期化ブロック、static{}
です。
コードにエラーが発生するのは、そのクラスのオブジェクトをインスタンス化する前に、クラスがロードされたときにstatic{}
が実行されるためです。したがって、クラスが作成されたときにfoo
は初期化されていません。
static{}
ブロックをClass
型のオブジェクトのコンストラクタと考えてください。これがstatic final
クラスの属性の初期化を行わなければならない場所です(インラインで行われていない場合)。
サイドノート:
final
修飾子はプリミティブ型と参照に対してのみ制約を保証します。
final
オブジェクトを宣言すると、そのオブジェクトへのfinal
reference が得られますが、オブジェクト自体は定数ではありません。
final
属性を宣言するときにあなたが本当に達成しているのは、あなたが特定の目的のためにオブジェクトを宣言すれば(あなたが宣言したfinal List
のように)、そのオブジェクトだけがその目的に使われるということです。 List foo
を別のList
に変更しますが、それでもアイテムを追加/削除することでList
を変更できます(使用しているList
は内容が変更されている場合のみ同じです)。
これは非常に良いインタビューの質問です。最終的なオブジェクトと不変のオブジェクトの違いを尋ねられることもあります。
1)誰かが最終オブジェクトに言及するとき、参照は変更できないが、その状態(インスタンス変数)は変更できることを意味します。
2)不変オブジェクトとは、状態をnotに変更できるが、その参照を変更できるオブジェクトです。例:
String x = new String("abc");
x = "BCG";
ref変数xは、異なる文字列を指すように変更できますが、「abc」の値は変更できません。
3)コンストラクターが呼び出されると、インスタンス変数(非静的フィールド)が初期化されます。そのため、コンストラクター内で変数の値を初期化できます。
4)「しかし、クラスのコンストラクター/メソッドの値を変更できることがわかります」。 -メソッド内で変更することはできません。
5)クラスのロード中に静的変数が初期化されます。そのため、コンストラクター内で初期化することはできません。コンストラクターの前でも初期化する必要があります。したがって、静的変数に値を割り当てる必要がありますduring宣言自体。
いくつかの簡単な定義を言及する価値があります。
クラス/メソッド
メソッドをサブクラスでオーバーライドできないことを示すために、クラスメソッドの一部または全部を
final
として宣言できます。
変数
一旦
final
変数が初期化されると、それは常に同じ値を含みます。
final
は基本的に、場合に応じて何でも(サブクラス、変数 "reassign")で上書きや上書きを避けます。
Javaのfinal
キーワードは、ユーザーを制限するために使用されます。 Javaのfinal
キーワードは、さまざまな場面で使用できます。最終的にすることができます:
final
キーワードは、変数、つまり値を持たないfinal
変数と一緒に適用することができ、ブランクfinal
変数または未初期化final
変数と呼ばれます。コンストラクター内でのみ初期化できます。空白のfinal
変数もstatic
にすることができ、これもstatic
ブロックでのみ初期化されます。
Java最終変数:
変数をfinal
にすると、 は変数final
の値 を変更できません(定数になります)。
final
変数の例
最終的な変数speedlimitがあります、我々はこの変数の値を変更しようとしています、しかしそれは変更されることができません。
class Bike9{
final int speedlimit=90;//final variable
void run(){
speedlimit=400; // this will make error
}
public static void main(String args[]){
Bike9 obj=new Bike9();
obj.run();
}
}//end of class
Java最終クラス:
クラスをfinal
にすると、 は itを拡張できません。
最終クラスの例
final class Bike{}
class Honda1 extends Bike{ //cannot inherit from final Bike,this will make error
void run(){
System.out.println("running safely with 100kmph");
}
public static void main(String args[]){
Honda1 honda= new Honda();
honda.run();
}
}
Javaの最終メソッド:
あなたが何らかの方法を最終的なものにするならば、あなたは を上書きすることはできません それ。
final
メソッドの例(Hondaのrun()はBikeのrun()をオーバーライドできません)
class Bike{
final void run(){System.out.println("running");}
}
class Honda extends Bike{
void run(){System.out.println("running safely with 100kmph");}
public static void main(String args[]){
Honda honda= new Honda();
honda.run();
}
}
final
は、ユーザーを制限するためのJavaの予約キーワードで、メンバー変数、メソッド、クラス、およびローカル変数に適用できます。 Javaでは、最終変数はしばしばstatic
キーワードで宣言され、定数として扱われます。例えば:
public static final String hello = "Hello";
変数宣言でfinal
キーワードを使用すると、その変数内に格納されている値を後で変更することはできません。
例えば:
public class ClassDemo {
private final int var1 = 3;
public ClassDemo() {
...
}
}
注 :finalとして宣言されたクラスは拡張または継承できません(つまり、スーパークラスのサブクラスは存在できません)。 finalとして宣言されたメソッドはサブクラスでオーバーライドできないことにも注意してください。
最後のキーワードを使用する利点は このスレッドで で説明されています。
赤と白の2つの貯金箱があるとします。あなたはこれらの貯金箱に2人の子供だけを割り当てます、そして、彼らは彼らの箱を交換することが許されません。だからあなたは赤または白の貯金箱を持っています(最終)あなたは箱を変更することはできませんがあなたはあなたの箱にお金を入れることができます。誰も気にしません(変更-2)。
すべての答えを読んでください。
final
キーワードを使用できるという別のユーザーケース、つまりメソッドの引数があります。
public void showCaseFinalArgumentVariable(final int someFinalInt){
someFinalInt = 9; // won't compile as the argument is final
}
変更してはいけない変数に使用できます。
ここで最新の詳細な回答を書くことを考えました。
final
キーワードはいくつかの場所で使用できます。
final class
は、no他のクラスがextendその最後のクラスにできることを意味します。JavaRun Time(JRE)がオブジェクト参照を知っている場合)が最終クラス(Fとする)の型である場合、その参照の値はFの型にしかならないことがわかっています。
例:
F myF;
myF = new F(); //ok
myF = someOther; //someOther cannot be in type of a child class of F.
//because F cannot be extended.
したがって、そのオブジェクトの任意のメソッドを実行すると、そのメソッド不要は実行時に仮想テーブル)を使用して解決されます。時間多態性を適用することはできませんので、実行時間はそれについて気にしません。
任意のクラスのfinal method
は、そのクラスを拡張する任意の子クラスを意味しますオーバーライドできませんその最後のメソッド。したがって、このシナリオでの実行時の動作は、クラスについて前述した動作とまったく同じです。 。
上記のいずれかをfinal
として指定した場合は、値がすでに確定されていることを意味するので、値は変更できませんです。
例:
フィールドの場合、ローカルパラメータ
final FinalClass fc = someFC; //need to assign straight away. otherwise compile error.
final FinalClass fc; //compile error, need assignment (initialization inside a constructor Ok, constructor can be called only once)
final FinalClass fc = new FinalClass(); //ok
fc = someOtherFC; //compile error
fc.someMethod(); //no problem
someOtherFC.someMethod(); //no problem
メソッドパラメータ
void someMethod(final String s){
s = someOtherString; //compile error
}
これは単にfinal
参照値の値が変更できないことを意味します。つまり、初期化は1回だけ許可されています。このシナリオでは、実行時に、JREが値を変更できないことを知っているので、(最終参照の)これらすべての最終値をL1 cache)にロードします。 必要ではありませんからメインメモリへと何度も何度もロードバックに)メインメモリからのロード時間が長いので、パフォーマンスも向上しています。
したがって、上記の3つのシナリオすべてで、使用できる場所にfinal
キーワードを指定していない場合は、心配する必要はありません。 コンパイラの最適化 はこれを行います。コンパイラの最適化によって私たちにできることは他にもたくさんあります。 :)
final
は参照を特定のオブジェクトにバインドするだけです。そのオブジェクトの「状態」は自由に変更できますが、オブジェクト自体は変更できません。静的ファイナルにするときは、静的初期化ブロックで初期化する必要があります。
private static final List foo;
static {
foo = new ArrayList();
}
public Test()
{
// foo = new ArrayList();
foo.add("foo"); // Modification-1
}
final
キーワードは、変数が一度だけ初期化できることを示します。コードでは、finalの初期化を1回だけ実行しているため、条件が満たされています。このステートメントは、foo
の初期化を実行します。 final
!=不変であり、参照が変更できないことを意味するだけであることに注意してください。
foo = new ArrayList();
foo
をstatic final
として宣言する場合、クラスをロードするときに変数を初期化する必要があり、インスタンス化(別名コンストラクターの呼び出し)に依存してfoo
を初期化する必要はありません。クラスのインスタンス。静的フィールドを使用する前にコンストラクターが呼び出されたという保証はありません。
static final
シナリオの下でメソッドを実行すると、Test
クラスがインスタンス化される前にロードされますt
この時点ではfoo
のインスタンス化はないため、初期化されていませんfoo
は、すべてのオブジェクトのデフォルトのnull
に設定されます。この時点で、リストに項目を追加しようとすると、コードがNullPointerException
をスローすると仮定します。
以下は、finalが使用されるさまざまなコンテキストです。
最終変数 最終変数は1回しか割り当てられません。変数が参照の場合、これは変数が別のオブジェクトを参照するように再バインドできないことを意味します。
class Main {
public static void main(String args[]){
final int i = 20;
i = 30; //Compiler Error:cannot assign a value to final variable i twice
}
}
最終変数には後で値を代入することができます(宣言時に値を代入することは必須ではありません)が、一度だけです。
最終クラス 最終クラスは拡張できません(継承)
final class Base { }
class Derived extends Base { } //Compiler Error:cannot inherit from final Base
public class Main {
public static void main(String args[]) {
}
}
最終メソッド 最終メソッドはサブクラスでオーバーライドできません。
//Error in following program as we are trying to override a final method.
class Base {
public final void show() {
System.out.println("Base::show() called");
}
}
class Derived extends Base {
public void show() { //Compiler Error: show() in Derived cannot override
System.out.println("Derived::show() called");
}
}
public class Main {
public static void main(String[] args) {
Base b = new Derived();;
b.show();
}
}
まず第一に、あなたが初期化している(すなわち、最初に代入している)fooの中のコードの場所はここにあります:
foo = new ArrayList();
fooは(List型の)オブジェクトなので、(intのように) value typeではなく reference typeです。そのように、それはあなたのリスト要素が格納されているメモリ位置(例えば0xA7D2A834)への参照を保持します。こんな行
foo.add("foo"); // Modification-1
fooの値を変更しないでください(これもまた、単にメモリ位置への参照です)。代わりに、それらは参照されたメモリ位置に要素を追加するだけです。 final キーワードに違反するには、もう一度次のようにfooを割り当て直す必要があります。
foo = new ArrayList();
それは だろう あなたにコンパイルエラーを与える。
それでは、 static キーワードを追加するとどうなるか考えてみましょう。
Staticキーワードがない場合、クラスをインスタンス化する各オブジェクトは独自のfooのコピーを持ちます。したがって、コンストラクターは、空白の新鮮なfoo変数のコピーに値を割り当てます。これは完全に問題ありません。
ただし、staticキーワードがある場合、クラスに関連付けられているメモリに存在するfooは1つだけです。 2つ以上のオブジェクトを作成する場合、コンストラクタは毎回その1つのfooを final キーワードに違反して再割り当てしようとします。
とりわけ正しいです。さらに、他の人が自分のクラスからサブクラスを作成したくない場合は、自分のクラスをfinalとして宣言してください。そしてそれはあなたのクラスツリー階層構造のリーフレベルになります。クラスの巨大な階層構造を避けるのは良い習慣です。