13

Scalaでは、avalはをオーバーライドできますがdef、 aはをオーバーライドdefできませんval

したがって、たとえば次のような特性を宣言することには利点があります。

trait Resource {
  val id: String
}

これよりも?

trait Resource {
  def id: String
}

フォローアップの質問は、コンパイラが実際にsとsの呼び出し をどのように異なる方法で処理し、実際にsでどのような最適化を行うかということです。コンパイラーは、sが安定しているという事実を主張します—コンパイラーにとって実際にはどういう意味ですか?サブクラスが実際にを実装していると仮定します。トレイトでaとして指定することに対するペナルティはありますか?valdefvalvalidvaldef

コード自体がメンバーの安定性を必要としない場合、これらの場合idは常にsを使用し、ここでパフォーマンスのボトルネックが特定された場合にのみsdefに切り替えることをお勧めしますが、そうなる可能性は低いでしょうか。val

4

4 に答える 4

16

短い答え:

私の知る限り、値は常にアクセサーメソッドを介してアクセスされます。Usingdefは、値を返す単純なメソッドを定義します。Usingは、アクセサメソッドを使用してプライベート[*]最終フィールドをval定義します。したがって、アクセスに関しては、2つの間にほとんど違いはありません。違いは概念的なものであり、毎回再評価され、一度だけ評価されます。これは明らかにパフォーマンスに影響を与える可能性があります。defval

[*]Javaプライベート

長い答え:

次の例を見てみましょう。

trait ResourceDef {
  def id: String = "5"
}

trait ResourceVal {
  val id: String = "5"
}

ResourceDef&はResourceVal、初期化子を無視して、同じコードを生成します。

public interface ResourceVal extends ScalaObject {
    volatile void foo$ResourceVal$_setter_$id_$eq(String s);
    String id();
}

public interface ResourceDef extends ScalaObject {
    String id();
}

生成された補助クラス(メソッドの実装を含む)の場合、ResourceDef生成は予想どおりであり、メソッドは静的であることに注意してください。

public abstract class ResourceDef$class {
    public static String id(ResourceDef $this) {
        return "5";
    }

    public static void $init$(ResourceDef resourcedef) {}
}

valの場合、包含クラスのイニシャライザーを呼び出すだけです。

public abstract class ResourceVal$class {
    public static void $init$(ResourceVal $this) {
        $this.foo$ResourceVal$_setter_$id_$eq("5");
    }
}

拡張を開始するとき:

class ResourceDefClass extends ResourceDef {
  override def id: String = "6"
}

class ResourceValClass extends ResourceVal {
  override val id: String = "6"
  def foobar() = id
}

class ResourceNoneClass extends ResourceDef

オーバーライドすると、期待どおりのことを実行するメソッドがクラスに取得されます。defは単純な方法です:

public class ResourceDefClass implements ResourceDef, ScalaObject {
    public String id() {
        return "6";
    }
}

valは、プライベートフィールドとアクセサメソッドを定義します。

public class ResourceValClass implements ResourceVal, ScalaObject {
    public String id() {
        return id;
    }

    private final String id = "6";

    public String foobar() {
        return id();
    }
}

foobar()フィールドを使用せずid、アクセサメソッドを使用することに注意してください。

そして最後に、オーバーライドしない場合、トレイト補助クラスの静的メソッドを呼び出すメソッドを取得します。

public class ResourceNoneClass implements ResourceDef, ScalaObject {
    public volatile String id() {
        return ResourceDef$class.id(this);
    }
}

これらの例ではコンストラクターを切り取っています。

したがって、アクセサメソッドが常に使用されます。これは、同じメソッドを実装できる複数のトレイトを拡張する際の複雑さを回避するためだと思います。それは本当にすぐに複雑になります。

さらに長い答え:

Josh Suerethは、Scala Days2012でBinaryResilienceについて非常に興味深い講演を行いましたこれは、この質問の背景をカバーしています。このための要約は次のとおりです。

この講演では、JVMでのバイナリ互換性と、バイナリ互換であるとはどういう意味かについて説明します。Scalaでのバイナリ非互換性の策略の概要が 詳細に説明され、その後、開発者が独自のライブラリリリースがバイナリ互換でバイナリ耐性があることを確認するのに役立つ一連のルールとガイドラインが続きます。

特に、この講演では以下を取り上げます。

  • 特性とバイナリ互換性
  • Javaシリアル化と匿名クラス
  • 怠惰なvalsの隠された創造物
  • バイナリ耐性のあるコードの開発
于 2012-10-30T09:11:19.667 に答える
2

違いは主に、valを使用してdefを実装/オーバーライドできることですが、その逆はできません。さらに、valは1回だけ評価され、defは使用されるたびに評価されます。抽象定義でdefを使用すると、トレイトを混合するコードに、実装の処理方法や最適化方法に関する自由度が高まります。したがって、私のポイントは、valを強制する明確な正当な理由がない場合は常にdefsを使用することです。

于 2012-10-29T17:04:05.133 に答える
0

val式は変数宣言で一度評価され、厳密で不変です。

Adefは、呼び出すたびに再評価されます

于 2012-10-29T17:05:13.103 に答える
-2

def名前とval値で評価されます。これは多かれ少なかれval常に実際の値を返さなければならないことを意味defしますが、それを評価するときに値を取得できるプロムのようなものです。たとえば、関数がある場合

def trace(s: => String ) { if (level == "trace") println s } // note the => in parameter definition

ログレベルがトレースに設定されていて、オブジェクトをログに記録する場合にのみ、イベントをログに記録しますtoString。値でオーバーライドtoStringした場合は、その値をtrace関数に渡す必要があります。toStringただし、がである場合はdef、ログレベルがトレースであることが確認された場合にのみ評価されます。これにより、オーバーヘッドをいくらか節約できます。 def柔軟性が向上する一方valで、潜在的に高速になります

コンパイラーに関してtraitsは、Javaインターフェースにコンパイルされるため、でメンバーを定義するときに、traitそのavarまたは。のどちらでも違いはありませんdef。パフォーマンスの違いは、実装方法によって異なります。

于 2012-10-29T17:18:56.607 に答える