web-dev-qa-db-ja.com

最終的な静的メソッドを宣言することは悪い考えですか?

私はこのコードでそれを理解しています:

_class Foo {
    public static void method() {
        System.out.println("in Foo");
    }
} 

class Bar extends Foo {
    public static void method() {
        System.out.println("in Bar");
    }
}
_

.. Barの静的メソッドは、ポリモーフィズムの意味でオーバーライドするのではなく、Fooで宣言された静的メソッドを「非表示」にします。

_class Test {
    public static void main(String[] args) {
        Foo.method();
        Bar.method();
    }
}
_

...出力されます:

フーに
バーで

finalFooとしてmethod()として再定義すると、Barがそれを非表示にする機能が無効になり、main()を再実行します出力されます:

フーに
フー

Edit:メソッドをfinalとしてマークするとコンパイルが失敗し、削除したときにのみ再度実行されますBar.method()

静的メソッドをfinalとして宣言することは、サブクラスが意図的または誤ってメソッドを再定義することを停止する場合、悪い習慣と見なされますか?

this は、finalを使用した場合の動作をよく説明しています。)

38
brasskazoo

staticメソッドをfinalとしてマークすることは悪い習慣だとは思いません。

ご存知のように、finalは、メソッドがサブクラスによって隠されないようにしますこれは非常に良いニュースです

私はあなたの発言にかなり驚いています:

Fooでmethod()をfinalとして再定義すると、Barがそれを非表示にする機能が無効になり、main()を再実行すると出力されます。

フーに
フー

いいえ、finalでメソッドをFooとしてマークすると、Barがコンパイルできなくなります。少なくともEclipseでは私は得ています:

スレッド「メイン」の例外Java.lang.Error:未解決のコンパイルの問題:Fooからfinalメソッドをオーバーライドできません

また、クラス自体の内部でも、クラス名で修飾するstaticメソッドを常に呼び出す必要があると思います。

class Foo
{
  private static final void foo()
  {
    System.out.println("hollywood!");
  }

  public Foo()
  {
    foo();      // both compile
    Foo.foo();  // but I prefer this one
  }
}
38
Gregory Pakosz

静的メソッドは、Javaの最も紛らわしい機能の1つです。これを修正するためのベストプラクティスがあり、すべての静的メソッドfinalを作成することがこれらのベストプラクティスの1つです!

静的メソッドの問題は、

  • これらはクラスメソッドではなく、クラス名が前に付いたグローバル関数です。
  • それらがサブクラスに「継承」されているのは奇妙です
  • オーバーライドすることはできませんが、非表示にすることは驚くべきことです
  • レシーバーとしてインスタンスを使用して呼び出すことができることは完全に壊れています

したがって、あなたはすべきです

  • 常にクラスを受信者として呼び出します
  • 常に宣言クラスをレシーバとしてのみ呼び出す
  • 常にそれら(または宣言クラス)を作成しますfinal

そして、あなたはすべきです

  • neverインスタンスをレシーバーとして呼び出します
  • never宣言クラスのサブクラスをレシーバーとして呼び出します
  • neverそれらをサブクラスで再定義します

注意:プログラムの2番目のバージョンではコンパイルエラーが発生します。IDEがこの事実を隠していると思います!

32
akuhn

私がpublic staticメソッドを持っている場合、そのメソッドは、多くの場合、いわゆるユーティリティクラスにあり、staticメソッドのみが含まれています。自己説明的な例は、StringUtilSqlUtilIOUtilなどです。これらのユーティリティクラスは、finalとして既に宣言されており、privateコンストラクターで提供されます。例えば。

public final class SomeUtil {

    private SomeUtil() {
        // Hide c'tor.
    }

    public static SomeObject doSomething(SomeObject argument1) {
        // ...
    }

    public static SomeObject doSomethingElse(SomeObject argument1) {
        // ...
    }

}

この方法では、それらを上書きすることはできません。

あなたのユーティリティクラスの種類にない場合は、public修飾子の値を質問します。 privateにすべきではありませんか?それ以外の場合は、ユーティリティクラスに移動します。 「通常の」クラスをpublic staticメソッドで乱雑にしないでください。このように、それらにfinalをマークする必要もありません。

もう1つのケースは、一種の抽象ファクトリクラスで、public staticメソッドを介してselfの具象実装を返します。そのような場合、メソッドにfinalをマークすることは完全に意味があります。具体的な実装がメソッドをオーバーライドできるようにしたくない場合です。

6
BalusC

通常、ユーティリティクラス(静的メソッドのみを含むクラス)では、継承を使用することは望ましくありません。このため、クラスをfinalとして定義して、他のクラスがそれを拡張しないようにすることができます。これは、ユーティリティクラスメソッドにfinal修飾子を配置することを無効にします。

4
mR_fr0g

コードはコンパイルされません:

Test.Java:8:Barのmethod()はFooのmethod()をオーバーライドできません。オーバーライドされたメソッドはstatic final public static void method(){

静的メソッドは定義上、オーバーライドできないため、このメッセージは誤解を招く可能性があります。

コーディング時に次のことを行います(常に100%とは限りませんが、ここで「間違っている」ことはありません。

(「ルール」の最初のセットはほとんどの場合に行われます-いくつかの特別なケースは後でカバーされます)

  1. インターフェースを作成する
  2. インターフェースを実装する抽象クラスを作成する
  3. 抽象クラスを拡張する具象クラスを作成する
  4. インターフェースを実装するが抽象クラスを拡張しない具象クラスを作成する
  5. 常に、可能であれば、インターフェイスのすべての変数/定数/パラメータを作成します

インターフェイスには静的メソッドを含めることができないため、問題を解決する必要はありません。抽象クラスまたは具象クラスの静的メソッドをプライベートにする必要がある場合、それらをオーバーライドしようとする方法はありません。

特殊なケース:

ユーティリティクラス(すべての静的メソッドを持つクラス):

  1. クラスをfinalとして宣言する
  2. 偶発的な作成を防ぐためにプライベートコンストラクター

プライベートではない具象クラスまたは抽象クラスに静的メソッドが必要な場合は、代わりにユーティリティクラスを作成することをお勧めします。

値クラス(Java.awt.Pointのように、本質的にデータを保持するために非常に特殊化されたクラスで、x値とy値をほぼ保持しています):

  1. インターフェースを作成する必要はありません
  2. 抽象クラスを作成する必要はありません
  3. クラスは最終でなければなりません
  4. 非プライベート静的メソッドは、キャッシングを実行したい場合があるため、特に構築には問題ありません。

上記のアドバイスに従うと、責任がかなり明確に分離されたかなり柔軟なコードが作成されます。

値クラスの例は、次のLocationクラスです。

import Java.util.ArrayList;
import Java.util.HashMap;
import Java.util.List;
import Java.util.Map;


public final class Location
    implements Comparable<Location>
{
    // should really use weak references here to help out with garbage collection
    private static final Map<Integer, Map<Integer, Location>> locations;

    private final int row;    
    private final int col;

    static
    {
        locations = new HashMap<Integer, Map<Integer, Location>>();
    }

    private Location(final int r,
                     final int c)
    {
        if(r < 0)
        {
            throw new IllegalArgumentException("r must be >= 0, was: " + r);
        }

        if(c < 0)
        {
            throw new IllegalArgumentException("c must be >= 0, was: " + c);
        }

        row = r;
        col = c;
    }

    public int getRow()
    {
        return (row);
    }

    public int getCol()
    {
        return (col);
    }

    // this ensures that only one location is created for each row/col pair... could not
    // do that if the constructor was not private.
    public static Location fromRowCol(final int row,
                                      final int col)
    {
        Location               location;
        Map<Integer, Location> forRow;

        if(row < 0)
        {
            throw new IllegalArgumentException("row must be >= 0, was: " + row);
        }

        if(col < 0)
        {
            throw new IllegalArgumentException("col must be >= 0, was: " + col);
        }

        forRow = locations.get(row);

        if(forRow == null)
        {
            forRow = new HashMap<Integer, Location>(col);
            locations.put(row, forRow);
        }

        location = forRow.get(col);

        if(location == null)
        {
            location = new Location(row, col);
            forRow.put(col, location);
        }

        return (location);
    }

    private static void ensureCapacity(final List<?> list,
                                       final int     size)
    {
        while(list.size() <= size)
        {
            list.add(null);
        }
    }

    @Override
    public int hashCode()
    {
        // should think up a better way to do this...
        return (row * col);
    }

    @Override
    public boolean equals(final Object obj)
    {
        final Location other;

        if(obj == null)
        {
            return false;
        }

        if(getClass() != obj.getClass())
        {
            return false;
        }

        other = (Location)obj;

        if(row != other.row)
        {
            return false;
        }

        if(col != other.col)
        {
            return false;
        }

        return true;
    }

    @Override
    public String toString()
    {
        return ("[" + row + ", " + col + "]");
    }

    public int compareTo(final Location other)
    {
        final int val;

        if(row == other.row)
        {
            val = col - other.col;
        }
        else
        {
            val = row - other.row;
        }

        return (val);
    }
}
3
TofuBeer

特に他の人が拡張することを期待するフレームワークを開発している場合は特に、静的メソッドをfinalとしてマークすることは良いことかもしれません。これにより、ユーザーが誤って静的メソッドをクラスに隠してしまうことがなくなります。ただし、フレームワークを開発している場合は、最初から静的メソッドの使用を避けたい場合があります。

1
Aditya

このfinalの問題のほとんどは、VMが非常に愚かで保守的であったときまで遡ります。当時、メソッドfinalをマークした場合、(とりわけ)VMはメソッドをインライン化できるため、メソッドの呼び出しを回避できます。これは、long-long(またはlong double: P)時間: http://Java.Sun.com/developer/technicalArticles/Networking/HotSpot/inlining.html

私はguessfinal最適化のためのキーワードと彼らはあなたがそれが現代のVMで不要であるという事実に気づいていないと思います.

ちょうど私の2セント...

1

SpringのAOPとMVCを使用してfinalメソッドを使用することに1つの問題が発生しました。最終的に宣言されたAbstractFormControllerのメソッドの1つにセキュリティフックを配置したSpringのAOPを使用しようとしました。春はクラスでのインジェクションにbcelライブラリを使用していたと思いますが、そこにはいくつかの制限がありました。

0
BillMan

静的メソッドはクラスのプロパティであり、オブジェクトではなくクラスの名前で呼び出されるためです。親クラスのメソッドもfinalにすると、finalメソッドはメモリの場所を変更できないため、オーバーロードされませんが、同じメモリの場所で最終データメンバーを更新できます...

0
Aasif Ali

純粋なユーティリティクラスを作成するときは、プライベートコンストラクターで宣言して、拡張できないようにします。通常のクラスを作成するとき、クラスインスタンス変数を使用していない場合は、メソッドを静的に宣言します(または、場合によっては、使用している場合でも、メソッドに引数を渡して静的にすると、見やすくなりますメソッドが何をしているか)。これらのメソッドは静的であると宣言されていますが、プライベートでもあります。これらは、コードの重複を回避したり、コードを理解しやすくするためにあります。

そうは言っても、静的なパブリックメソッドを持つクラスがあり、それを拡張できる、または拡張する必要がある場合に遭遇したことを覚えていません。しかし、ここで報告された内容に基づいて、静的メソッドをfinalと宣言します。

0
Ravi Wallau