Javaの「最終」キーワードはどのように機能しますか?(私はまだオブジェクトを変更できます。)


480

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


fooは表示されないため、どのようにコンパイルできますか?
ビョルン・ハルストレム

5
真実ではない@therealprashant。プライベート静的変数は有効であり、それらが定義されているクラス内の静的メソッドからアクセスできます。静的変数は、変数が一度だけ存在し、クラスのインスタンスにバインドされていないことを意味します。
mbdavis

3
@mbdavisああはい!ありがとうございました。しかし、それでも、私と同じように考えている人々を助けるためにコメントを削除することはありません。そうすれば、あなたのコメントは彼らを正しい方向に考えさせるでしょう。
therealprashant 2015年

@therealprashant大丈夫大丈夫です!
mbdavis

回答:


518

変数はいつでも初期化できfinalます。コンパイラーは、1度だけ実行できることを確認します。

final変数に格納されたオブジェクトのメソッドを呼び出すことは、のセマンティクスとは関係がないことに注意してくださいfinal。つまり、:finalは参照自体に関するものであり、参照されるオブジェクトの内容に関するものではありません。

Javaにはオブジェクトの不変性の概念はありません。これは、オブジェクトを注意深く設計することによって実現され、簡単なことではありません。


12
t.foo = new ArrayList();を実行しよう mainメソッドではコンパイルエラーが発生します...参照fooはArrayListの最後の1つのオブジェクトにのみ
バインドさ

50
うーん。値ではなく参照に関するすべて。ありがとう!
GS

2
質問があります。私が知っている誰かが「最終的な」と主張すると、変数はスタックに格納されます。これは正しいです?私はあらゆる場所を検索しましたが、この申し立てを承認または却下できる参照を見つけることができませんでした。JavaとAndroidの両方のドキュメントを検索しました。「Javaメモリモデル」も検索。多分それはC / C ++でこのように動作しますが、Javaではこのように動作するとは思いません。私は正しいですか?
Android開発者

4
@androiddeveloper Javaでスタック/ヒープの配置を明示的に制御できるものはありません。具体的には、HotSpot JITコンパイラによって決定されたスタック配置はエスケープ分析の対象となります。これは、変数がであるかどうかをチェックするよりもはるかに複雑ですfinal。可変オブジェクトもスタックに割り当てることができます。finalフィールドエスケープ分析に役立ちます、それは非常に間接的なルートです。また、実質的に最終的な変数はfinal、ソースコードでマークされたものと同じ扱いをすることに注意してください。
Marko Topolnik 2014年

5
finalクラスファイルに存在し、最適化ランタイムに意味上の重要な影響を与えます。また、JLSにはオブジェクトのフィールドの一貫性について強力な保証があるため、コストが発生する可能性もfinalあります。たとえば、ARMプロセッサは、finalフィールドを持つクラスの各コンストラクタの終わりに明示的なメモリバリア命令を使用する必要があります。ただし、他のプロセッサではこれは必要ありません。
Marko Topolnik 14

574

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

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」メソッドを指定しています。

コンストラクターについて:コンストラクターは、キーワードを使用して、オブジェクトの作成ごとに1回だけ呼び出すことができますnew。コンストラクターはそのように設計されていないため、コンストラクターを複数回呼び出すことはできません。

メソッドについて:メソッドは何度でも(絶対に)呼び出すことができ、コンパイラーはそれを認識します。

シナリオ1

private final List foo;  // 1

fooあるインスタンス変数は。Testクラスオブジェクトを作成すると、インスタンス変数fooTestクラスのオブジェクト内にコピーされます。fooコンストラクター内で割り当てる場合、コンパイラーはコンストラクターが1回だけ呼び出されることを認識しているため、コンストラクター内で割り当てることは問題ありません。 メソッド内

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

シナリオ2

private static final List foo = new ArrayList();

foo現在は静的変数です。Testクラスのインスタンスを作成すると、静的であるfooため、オブジェクトにコピーされませんfoo。現在foo、各オブジェクトの独立したプロパティではありません。これはTestクラスのプロパティです。しかしfoo、複数のオブジェクトから見ることができ、複数のオブジェクトの作成時に値を変更するコンストラクターをnew最終的に呼び出すキーワードを使用して作成されたすべてのオブジェクト(すべてのオブジェクトにコピーされるのではなく、複数のオブジェクト間で共有されることをTest思い出してくださいstatic foo) 。)

シナリオ3

t.foo.add("bar"); // Modification-2

上記Modification-2はあなたの質問からです。上記の場合、最初に参照されるオブジェクトを変更するのではなく、foo許可されているコンテンツを内部に追加します。あなたが割り当てしようとした場合、コンパイラは文句を言いnew ArrayList()foo参照変数。
ルールfinal変数を初期化した場合、別のオブジェクトを参照するように変数を変更することはできません。(この場合ArrayList

finalクラスはサブクラス化できません
finalメソッドはオーバーライドできません。(このメソッドはスーパークラスにあります)
finalメソッドはオーバーライドできます。(これを文法的に読んでください。このメソッドはサブクラスにあります)


1
ただ明確にします。シナリオ2 では、がTestクラスで設定され、Testの複数のインスタンスが作成されたfoo場合、最終的な指定にもかかわらず複数回設定されると言っていfooますか?
Raw 12

シナリオ2の最後の行が理解できませんでした。ただし、foo複数のオブジェクトである可能性があります。)つまり、一度に複数のオブジェクトを作成した場合、最終オブジェクトを初期化しているオブジェクトは、実行に依存しますか?
Saumya Suhagiya 2016

1
シナリオ3について考えるのに役立つ方法は、ArrayListがfinal参照するメモリアドレスに割り当てることfooです。finalの最初の要素foo(またはその要素)が参照するメモリアドレスに割り当てていません。したがって、変更することはできませんが、変更fooすることはできますfoo[0]
ピンカートン2017

以下のため、現状では@Rawrは、シナリオ2は、コンパイル時にエラーが発生しますfoo = new ArrayList();- foo私たちは、同じクラスの中にいるため、静的変数を指します。
flow2k 2017

私はJavaを学ぶC ++開発者です。C ++のキーワードfinalと同じ変数を考えるのは安全constですか?
Doug Barbieri 2017

213

最後のキーワードには、さまざまな使用方法があります。

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

その他の使用法:

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

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


24
これは断然私のお気に入りの答えです。単純明快で、これは私がJavaに関するオンラインドキュメントで読むことを期待するものです。
RAnders00

静的変数では、何回でも初期化できますか?
ホルヘサライバ2017年

1
@jorgesaraivaはい、静的変数は定数ではありません。
czupe 2017

1
@jorgesaraivaあなたがすることができます割り当てる(ない初期化staticフィールド(長いそうでないようにfinalしたいと何回も)。割り当て初期化の違いについては、このWikiを参照してください。

56

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

値の種類:についてはintS、doubleSなど、それは、値が変更できないことを保証します

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

そのため、final List<Whatever> foo;foo常に同じリストを参照していることを確認しますが上記のリストの内容は時間とともに変化する可能性があります。


23

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

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

private static final List foo;

static
{
   foo = new ArrayList();
}

列をなして:

private static final List foo = new ArrayList();

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

final修飾子は、時間によって(あなたは、コンストラクタでそれを初期化する必要がありますすなわち)あなたのコンストラクタが完了するまでコールをご参照の初期化を実施します。

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

  • 場合foostaticfoo = new ArrayList()前に実行されますstatic{}あなたがあなたのクラスに定義されているコンストラクタが実行されます
  • 場合fooではないstaticfoo = new ArrayList()あなたのコンストラクタが実行される前に実行されます

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

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

このstatic{}ブロックは、型のオブジェクトのコンストラクターと考えてくださいClass。ここで、static finalクラス属性の初期化を行う必要があります(インラインでない場合)。

サイドノート:

final修飾子分確保はプリミティブ型と参照のためネスをCONST。

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

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


8

これは非常に良いインタビューの質問です。最終オブジェクトと不変オブジェクトの違いは何かと尋ねられることさえあるでしょう。

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

2)不変オブジェクトとは、状態を変更することできませんが、参照を変更することができるオブジェクトです。例:

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

参照変数xは別の文字列を指すように変更できますが、「abc」の値は変更できません。

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

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

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


7

finalJava のキーワードは、ユーザーを制限するために使用されます。java finalキーワードは多くのコンテキストで使用できます。最終は次のいずれかです。

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

finalキーワードは、変数を適用することができfinal、値を持たない変数を空白と呼ばれるfinal変数または初期化されていないfinal変数。コンストラクタでのみ初期化できます。空のfinal変数はstaticstaticブロックでのみ初期化することもできます。

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、それを拡張することはできません

最終クラスの例

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メソッド:

finalとしてメソッドを作成した場合、それをオーバーライドすることはできません

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


7

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

クラス/メソッド

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

変数

一度final変数が初期化されている、それは常に同じ値が含まれています。

final 状況に応じて、基本的には何でも(サブクラス、変数 "reassign")で上書き/上書きしないようにします。


1
変数に関する最終的な定義は少し短いと思います。「Javaでは、finalキーワードがプリミティブデータ型(int、float、..など)の変数で使用される場合、変数の値は変更できませんが、finalが非プリミティブ変数で使用される場合(非プリミティブ変数に注意)は常にJavaのオブジェクトへの参照です。参照されるオブジェクトのメンバーは変更できます。非プリミティブ変数のfinalは、他のオブジェクトを参照するように変更できないことを意味します。」geeksforgeeks.org/g-fact-48
ceyun

また、特にプリミティブおよび非プリミティブのケースとして言及する場合にも有効です。Tks。
ivanleoncz

4

finalは、ユーザーを制限するためのJavaの予約済みキーワードであり、メンバー変数、メソッド、クラス、およびローカル変数に適用できます。final変数は、多くの場合static、Java のキーワードで宣言され、定数として扱われます。例えば:

public static final String hello = "Hello";

final変数宣言でキーワードを使用すると、その変数内に格納されている値を後で変更することはできません。

例えば:

public class ClassDemo {
  private final int var1 = 3;
  public ClassDemo() {
    ...
  }
}

:finalとして宣言されたクラスは、拡張または継承できません(つまり、スーパークラスのサブクラスは存在できません)。finalとして宣言されたメソッドは、サブクラスによってオーバーライドできないことに注意してください。

finalキーワードを使用する利点については、 このスレッドで説明します


2
the value stored inside that variable cannot be changed latter部分的に正しい。これは、プリミティブデータタイプにのみ当てはまります。オブジェクトがfinalarraylistのようにとして作成されている場合、その値は変更できますが、参照は変更できません。ありがとうございました!
GS

3

赤と白の2つの貯金箱があるとします。あなたはこれらの貯金箱に2人の子供だけを割り当て、彼らは彼らの箱を交換することはできません。したがって、赤または白のマネーボックス(最終)を持っている場合、ボックスを変更することはできませんが、ボックスにお金を入れることはできます。誰も気にしません(変更2)。


2

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

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

public void showCaseFinalArgumentVariable(final int someFinalInt){

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

}

変更すべきでない変数に使用できます。


1

静的なfinalにすると、静的な初期化ブロックで初期化する必要があります

    private static final List foo;

    static {
        foo = new ArrayList();
    }

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

1

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

foo = new ArrayList();

静的フィールドはクラスのインスタンスなしで利用可能でなければならないため、クラスのロード時に変数を初期化する必要があり、インスタンス化(コンストラクターの呼び出し)に依存して初期化できないため、宣言するfoostatic finalfoostaticフィールドを使用する前にコンストラクターが呼び出されるという保証はありません。

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


1

まず、コード内のfooを初期化(つまり、最初に割り当てる)する場所は次のとおりです。

foo = new ArrayList();

fooはオブジェクト(リスト型)であるため、参照型であり、型(intなど)ではありません。そのため、List要素が格納されているメモリの場所(0xA7D2A834など)への参照を保持しています。このような行

foo.add("foo"); // Modification-1

fooの値を変更しないでください(これも、単なるメモリロケーションへの参照です)。代わりに、参照されたメモリ位置に要素を追加するだけです。最後のキーワードに違反するには、もう一度次のようにfooを再割り当てする必要があります。

foo = new ArrayList();

それはですあなたのコンパイルエラーを与えます。


さて、これで、staticキーワードを追加するとどうなるか考えてみましょう。

staticキーワードがない場合、クラスをインスタンス化する各オブジェクトには独自のfooのコピーがあります。したがって、コンストラクターは、foo変数の空の新鮮なコピーに値を割り当てますが、これはまったく問題ありません。

ただし、staticキーワードがある場合、クラスに関連付けられているメモリにはfooが1つだけ存在します。2つ以上のオブジェクトを作成する場合、コンストラクターは毎回その1つのfooを再割り当てしようとして、最後のキーワードに違反します。


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

1

以下は、finalが使用されるさまざまなコンテキストです。

最終変数最終変数は一度だけ割り当てることができます。変数が参照の場合、これは、変数を再バインドして別のオブジェクトを参照することができないことを意味します。

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変数には後で値を割り当てることができますが(宣言時に値を割り当てる必要はありません)、1回だけです。

最終クラス最終クラスは拡張できません(継承)

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

1

ここで更新された詳細な回答を書くことを考えました。

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

  1. クラス

A final classは、他のクラスがその最終クラスを拡張できないことを意味します。Javaランタイム(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キャッシュにロードします。それがあるので必要としないためにバックロードから何度も何度もメインメモリ。それ以外の場合は、L2キャッシュに読み込まれ、メインメモリからの読み込みが時々行われます。したがって、パフォーマンスの向上にもなります。

したがって、上記の3つのシナリオすべてで、final使用できる場所にキーワードを指定していなくても、心配する必要はありません。コンパイラーの最適化がそれを行います。コンパイラーの最適化によって他にも多くのことが行われます。:)


0

何よりも正しいです。さらに、他の人が自分のクラスからサブクラスを作成したくない場合は、自分のクラスをfinalとして宣言します。次に、それをクラスツリー階層のリーフレベルにして、それをそれ以上拡張できないようにします。クラスの巨大な階層を回避することは良い習慣です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.