1

Scala (2.8.1) でオーバーロードされたメソッドを処理する際に奇妙なことに遭遇しました。最初のメソッドは引数なしで、2 番目のメソッドは可変数の引数 (0..N) を取ります。テストコード:

class Test {
  def method1 = println("method1 invoked with zero args")
  def method1(args: Any*) = println("method1 with args " + args)

  def method2() = println("method2 invoked with zero args")
  def method2(args: Any*) = println("method2 with args " + args)
}

object Test {
  def main(args: Array[String]) {
    val t = new Test
    t.method1
    t.method1()
    t.method1(1,2,3)
    println
    t.method2
    t.method2()
    t.method2(1,2,3)
  }
}

コンパイルして実行すると、出力は次のようになります。

method1 invoked with zero args
method1 with args WrappedArray()
method1 with args WrappedArray(1, 2, 3)

method2 invoked with zero args
method2 invoked with zero args
method2 with args WrappedArray(1, 2, 3)

そのため、括弧を付けて引数なしで実行method1すると varargs メソッドに到達しますが、method2の場合は no-args メソッドが呼び出されます。

この奇妙な動作の背後にある説明または理由は何ですか?

4

3 に答える 3

8

では、質問は「なぜ誰かがその行動が論理的だと考えるのでしょうか?」というように再定式化できます。:)

括弧なしで定義されたメソッドは括弧付きで呼び出すことができないため、method1オプションが 1 つしかない場合に備えて。

場合によってはmethod2、 callt.method2()には選択できる 2 つのオーバーロードされたオプションdef method2()があり、より適しています。

最適なオーバーロードを選択する方法の正確な説明については、Scala 言語仕様のセクション 6.26.3 を参照してください。ルールは非常に単純で論理的です。

于 2011-08-11T10:51:41.470 に答える
3

それは実際には非常に論理的です。コンパイラ パターンはメソッド シグネチャで一致し、最も一致するものを採用します。

私はそれをテストしていませんが、さらに一歩進んで、次のように2番目のメソッドをオーバーロードできると確信しています:

def method2(arg: Any) = println("method2 invoked with a single arg " + arg)

したがって、引数を 1 つ指定して呼び出す場合、コンパイラは varargs バージョンではなく、これを選択します。

于 2011-08-11T10:51:37.783 に答える
1

パラメータリストを渡すパラメータリストなしでメソッドを呼び出すことはmethod1()できないため、宣言することはできません。def method =

scala> def method1 = 0
method1: Int

scala> method1()
<console>:9: error: Int does not take parameters
              method1()
                     ^

apply()このエラーは、Scalaが の結果であるmethod1を呼び出そうとしたことに注意してくださいInt

逆に、パラメーター リストを指定して vararg メソッドを呼び出すことはできません。

scala> def vararg(x: Int*) = x.size
vararg: (x: Int*)Int

scala> vararg
<console>:9: error: missing arguments for method vararg;
follow this method with `_' if you want to treat it as a partially applied function
              vararg
              ^

したがって、最初のケースでは、示されている以外に考えられる動作はありません。

2 番目の例では、最初と最後のケースはあいまいではありません。(示されているように) パラメータ リストなしで vararg を呼び出すことはできず、パラメータを渡すパラメータなしでメソッドを呼び出すことはできません。パラメーターなしで空のパラメーター リストを使用してメソッドを呼び出すことができ.toStringます。これは主に、Java API をより "きれいに" するために行われました。たとえば、 .

2 番目のケース (空のパラメーター リストを指定してメソッドを呼び出す) では、あいまいさが生じます。次に Scala は、一方が他方より具体的かどうかを判断しようとします。

ここで少し寄り道をしてみましょう。より具体的な方法を確認する理由 これがあるとします:

object T {
  def f(x: AnyRef) = x.hashCode
  def f(x: String) = x.length
}

この種のことは、Java API では比較的一般的です。誰かがf("abc")を呼び出した場合、当然、コンパイラは最初のメソッドではなく 2 番目のメソッドを呼び出すと予想されますが、どちらも有効です。では、それらを明確にする方法は?おそらく、String渡されたものと完全に一致するので、それを使用するかもしれませんよね? では、次のことを考えてみてください。

object T {
  def f(x: AnyRef) = x.hashCode
  def f(x: java.util.Collection[_]) = x.size
}

そして、私はそれを次のように呼びf(new ArrayList[String])ます。AnyRefはクラスですが、Collectionはインターフェースなので、クラスよりもインターフェースを優先するのでしょうか? そして、期待する別のメソッドがあった場合はどうなるでしょうかList-それもインターフェースです。

したがって、このあいまいさを解決するために、Scala は最も具体的な概念を使用します。これは、実際には非常に簡単に適用できる概念です。

まず、Scala は一方が他方と同じくらい具体的であるかを検証します。型と式に関して定義された 4 つの単純なルールがありますが、その要点は、a のパラメーターで b を呼び出すことができる場合、メソッド a はメソッド b と同じくらい具体的であるということです。

この場合、method2()は と同じくらい具体的です。これは、空のパラメーター リストで呼び出すことができるmethod2(args: Any*)ためです。method2(args: Any*)一方、method2(args: Any*)は ほど具体的ではありませんmethod2()。なぜなら、method2()は 型の引数で呼び出すことができないからAnyです。

次に、Scala は、メソッドの 1 つを定義するクラスの 1 つが、他のメソッドを定義するクラスのサブクラスであるかどうかを検証します。この規則は、この場合には適用されません。

最後に、Scala は、適合する上記の 2 つの基準のそれぞれについて、各メソッドに 1 を追加します。So method2()has weight 1, and method2(args: Any*)has weight 0. 一方の重みが他方よりも大きい場合、そのメソッドが最も具体的であると見なされます。

これまで見てきたように、method2()が最も具体的なものであるため、それが選択されます。

于 2011-08-11T20:05:00.617 に答える