web-dev-qa-db-ja.com

Javaの "final"キーワードはどのように機能しますか? (私はまだオブジェクトを修正することができます。)

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は本当にどのように機能しますか?

437
G.S

あなたはいつも initialize final変数に許可されています。コンパイラは、あなたが一度だけできることを確認します。

final変数に格納されているオブジェクトのメソッドを呼び出すことは、finalのセマンティクスとは無関係です。言い換えれば、finalは参照そのものに関するものであり、参照されるオブジェクトの内容に関するものではありません。

Javaにはオブジェクト不変性の概念はありません。これはオブジェクトを注意深く設計することによって達成され、そして些細な試みではありません。

471
Marko Topolnik

これはお気に入りのインタビューの質問です。この質問により、インタビュアーは、コンストラクター、メソッド、クラス変数(静的変数)、およびインスタンス変数に関してオブジェクトの動作をどの程度理解しているかを見つけようとします。

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クラスオブジェクトを作成すると、インスタンス変数fooTestクラスのオブジェクト内にコピーされます。コンストラクター内でfooを割り当てた場合、コンパイラーはコンストラクターが1回だけ呼び出されることを知っているため、コンストラクター内で問題なく割り当てることができます。

メソッド内でfooを割り当てた場合、コンパイラはメソッドを複数回呼び出すことができることを知っています。つまり、値を複数回変更する必要があり、final変数。そのため、コンパイラはコンストラクタが適切であると判断します! 最終変数に値を割り当てることができるのは1回だけです。

シナリオ2

private static final List foo = new ArrayList();

foostatic変数になりました。 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メソッドはオーバーライドできます。 (文法的にこれを読んでください。このメソッドはサブクラスにあります)

552
AmitG

Final キーワードにはたくさんの使い方があります。

  • 最後の クラス はサブクラス化できません。
  • 最後の メソッド はサブクラスでオーバーライドできません
  • 最後の 変数 は一度だけ初期化できます

その他の用法

  • メソッドの本体内で無名内部クラスが定義されている場合、そのメソッドのスコープ内でfinalと宣言されたすべての変数は、内部クラス内からアクセス可能です

静的クラス変数はJVMの最初から存在し、クラス内で初期化する必要があります。こうしてもエラーメッセージは表示されません。

196
czupe

finalキーワードは、それが何に使われているかに応じて、2つの異なる方法で解釈することができます。

値の型: ints、doublesなどの場合、値が変更できないようにします。

参照型: オブジェクトへの参照の場合、final reference が決して変更されないことを保証します。つまり、常に同じオブジェクトを参照します。参照されているオブジェクト内の値が同じであることについては一切保証されません。

そのため、final List<Whatever> foo;fooが常に 同じ listを参照することを保証しますが、 このリストの内容 は時間の経過とともに変わる可能性があります。

49
Smallhacker

fooを静的にする場合は、次の例のようにクラスコンストラクタで初期化する(または定義する場所でインラインにする)必要があります。

クラスコンストラクタ(インスタンスではない):

private static final List foo;

static
{
   foo = new ArrayList();
}

列をなして:

private static final List foo = new ArrayList();

ここでの問題は、final修飾子がどのように機能するのかではなく、むしろstatic修飾子がどのように機能するのかということです。

final修飾子はあなたのコンストラクタへの呼び出しが完了するまでにあなたの参照の初期化を強制します(すなわち、あなたはそれをコンストラクタで初期化しなければなりません)。

属性をインラインで初期化すると、コンストラクタに定義したコードが実行される前に属性が初期化されるため、次のような結果になります。

  • foostaticの場合、foo = new ArrayList()はクラスに定義したstatic{}コンストラクタが実行される前に実行されます。
  • foostaticでなければ、foo = new ArrayList()はコンストラクタが実行される前に実行されます

属性をインラインで初期化しない場合、final修飾子は、属性を初期化することと、コンストラクターで初期化することを強制します。 static修飾子もある場合は、属性を初期化する必要があるコンストラクタはクラスの初期化ブロック、static{}です。

コードにエラーが発生するのは、そのクラスのオブジェクトをインスタンス化する前に、クラスがロードされたときにstatic{}が実行されるためです。したがって、クラスが作成されたときにfooは初期化されていません。

static{}ブロックをClass型のオブジェクトのコンストラクタと考えてください。これがstatic finalクラスの属性の初期化を行わなければならない場所です(インラインで行われていない場合)。

サイドノート:

final修飾子はプリミティブ型と参照に対してのみ制約を保証します。

finalオブジェクトを宣言すると、そのオブジェクトへのfinal reference が得られますが、オブジェクト自体は定数ではありません。

final属性を宣言するときにあなたが本当に達成しているのは、あなたが特定の目的のためにオブジェクトを宣言すれば(あなたが宣言したfinal Listのように)、そのオブジェクトだけがその目的に使われるということです。 List fooを別のListに変更しますが、それでもアイテムを追加/削除することでListを変更できます(使用しているListは内容が変更されている場合のみ同じです)。

22

これは非常に良いインタビューの質問です。最終的なオブジェクトと不変のオブジェクトの違いを尋ねられることもあります。

1)誰かが最終オブジェクトに言及するとき、参照は変更できないが、その状態(インスタンス変数)は変更できることを意味します。

2)不変オブジェクトとは、状態をnotに変更できるが、その参照を変更できるオブジェクトです。例:

    String x = new String("abc"); 
    x = "BCG";

ref変数xは、異なる文字列を指すように変更できますが、「abc」の値は変更できません。

3)コンストラクターが呼び出されると、インスタンス変数(非静的フィールド)が初期化されます。そのため、コンストラクター内で変数の値を初期化できます。

4)「しかし、クラスのコンストラクター/メソッドの値を変更できることがわかります」。 -メソッド内で変更することはできません。

5)クラスのロード中に静的変数が初期化されます。そのため、コンストラクター内で初期化することはできません。コンストラクターの前でも初期化する必要があります。したがって、静的変数に値を割り当てる必要がありますduring宣言自体。

7
user892871

いくつかの簡単な定義を言及する価値があります。

クラス/メソッド

メソッドをサブクラスでオーバーライドできないことを示すために、クラスメソッドの一部または全部をfinalとして宣言できます。

変数

一旦final変数が初期化されると、それは常に同じ値を含みます。

finalは基本的に、場合に応じて何でも(サブクラス、変数 "reassign")で上書きや上書きを避けます。

6
ivanleoncz

Javaのfinalキーワードは、ユーザーを制限するために使用されます。 Javaのfinalキーワードは、さまざまな場面で使用できます。最終的にすることができます:

  1. 変数
  2. 方法
  3. クラス

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();  
   }  
}  

共有元: http://www.javatpoint.com/final-keyword

4
Ali Ziaee

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)。

3
huseyin

すべての答えを読んでください。

finalキーワードを使用できるという別のユーザーケース、つまりメソッドの引数があります。

public void showCaseFinalArgumentVariable(final int someFinalInt){

   someFinalInt = 9; // won't compile as the argument is final

}

変更してはいけない変数に使用できます。

2
Pritam Banerjee

ここで最新の詳細な回答を書くことを考えました。

finalキーワードはいくつかの場所で使用できます。

  1. クラス

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.

したがって、そのオブジェクトの任意のメソッドを実行すると、そのメソッド不要は実行時に仮想テーブル)を使用して解決されます。時間多態性を適用することはできませんので、実行時間はそれについて気にしません。

  1. 方法

任意のクラスのfinal methodは、そのクラスを拡張する任意の子クラスを意味しますオーバーライドできませんその最後のメソッド。したがって、このシナリオでの実行時の動作は、クラスについて前述した動作とまったく同じです。 。

  1. フィールド、ローカル変数、メソッドパラメータ

上記のいずれかを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キーワードを指定していない場合は、心配する必要はありません。 コンパイラの最適化 はこれを行います。コンパイラの最適化によって私たちにできることは他にもたくさんあります。 :)

1
  1. 最後の変数は静的ではないので、コンストラクタで初期化できます。しかし、静的にするとコンストラクタによって初期化できません(コンストラクタは静的ではないため)。
  2. リストへの追加は、リストを最終的なものにすることによって止まるとは思われません。 finalは参照を特定のオブジェクトにバインドするだけです。そのオブジェクトの「状態」は自由に変更できますが、オブジェクト自体は変更できません。
1
Ankit

静的ファイナルにするときは、静的初期化ブロックで初期化する必要があります。

    private static final List foo;

    static {
        foo = new ArrayList();
    }

    public Test()
    {
//      foo = new ArrayList();
        foo.add("foo"); // Modification-1
    }
1

finalキーワードは、変数が一度だけ初期化できることを示します。コードでは、finalの初期化を1回だけ実行しているため、条件が満たされています。このステートメントは、fooの初期化を実行します。 final!=不変であり、参照が変更できないことを意味するだけであることに注意してください。

foo = new ArrayList();

foostatic finalとして宣言する場合、クラスをロードするときに変数を初期化する必要があり、インスタンス化(別名コンストラクターの呼び出し)に依存してfooを初期化する必要はありません。クラスのインスタンス。静的フィールドを使用する前にコンストラクターが呼び出されたという保証はありません。

static finalシナリオの下でメソッドを実行すると、Testクラスがインスタンス化される前にロードされますtこの時点ではfooのインスタンス化はないため、初期化されていませんfooは、すべてのオブジェクトのデフォルトのnullに設定されます。この時点で、リストに項目を追加しようとすると、コードがNullPointerExceptionをスローすると仮定します。

1
Kevin Bowersox

以下は、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();
    }
}
0
roottraveller

まず第一に、あなたが初期化している(すなわち、最初に代入している)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 キーワードに違反して再割り当てしようとします。

0
Niko Bellic

とりわけ正しいです。さらに、他の人が自分のクラスからサブクラスを作成したくない場合は、自分のクラスをfinalとして宣言してください。そしてそれはあなたのクラスツリー階層構造のリーフレベルになります。クラスの巨大な階層構造を避けるのは良い習慣です。

0
Shehan Simen