2010-08-10 13 views
28

¿Cuál es la diferencia entre declarar un campo como val, lazy val y object dentro de una clase Scala, como en el siguiente fragmento:val y objeto dentro de una clase scala?

class A 

class B { 
    val a1 = new A  { def foo = 1 } 
    object a2 extends A { def foo = 1 } 
    lazy val a3 = new A { def foo = 1 } 
} 
+0

Resulta que 'lazy val a3 = new A {def foo = 1}' también debería haberse agregado a la pregunta. – PeWu

+0

Véase también [Scala - nuevo vs objeto se extiende] (http://stackoverflow.com/q/16182735/1048572) – Bergi

Respuesta

18

En el primero, cualquier código incluido se ejecuta tan pronto como se crea la clase B. En este último caso, sin embargo, hasta que realmente use el objeto, no se creará una instancia.

Se puede ver la diferencia aquí:

class A { println("Creating a new A") } 
class B { 
    val a1 = new A { println("a1"); def foo = 1 } 
    object a2 extends A { println("a2"); def foo = 1 } 
} 

scala> val b = new B 
Creating a new A 
a1 
b: B = [email protected] 

scala> b.a2.foo 
Creating a new A 
a2 
res0: Int = 1 

también hay diferencias ocultas en lo que los archivos creados .class son nombrados y tal; y por supuesto, los dos tienen diferentes tipos.

+1

Así 'objeto' funciona como' perezoso val'. ¿Hay alguna _práctica_ diferencias entre los dos? – PeWu

+1

Inherentemente, no, hasta donde yo sé. El bytecode para 'object' es un poco más compacto. No estoy seguro de si eso significa que 'lazy val' está escrito de manera ineficiente, o si' object' podría no ser seguro bajo ciertas condiciones de subprocesamiento, o ambas cosas. –

+0

O no tan lejos como lo recordé en ese momento. ¡Vea el comentario de Alex Boisvert para una diferencia crítica! –

3

supongo que una diferencia es que a1 será de un subtipo de A mientras a2 será de otro subtipo de A es decir, a2.type.

scala> class A 
defined class A 

scala> val a1 = new A {def foo = 1} 
a1: A{def foo: Int} = [email protected] 

scala> object a2 extends A {def foo = 1} 
defined module a2 

scala> a1 
res0: A{def foo: Int} = [email protected] 

scala> a2 
res1: a2.type = [email protected] 

scala> 
+0

No exactamente. Ambas serán subclases de 'A'. – PeWu

+0

Claro, pero no serán del mismo tipo. – aioobe

+1

Para ser más precisos a2 tendrá un tipo a2.type. – venechka

16

Una diferencia importante es que val's se puede anular mientras que los objetos no.

class C extends B {       
    override val a1 = new A { def foo = 2 }  
    override object a2 extends A { def foo = 2 } 
} 

conduce a:

<console>:9: error: overriding object a2 in class B of type object C.this.a2; 
object a2 cannot be used here - classes and objects cannot be overridden 
override object a2 extends A { def foo = 2 } 
16

No estoy seguro de que aioobe reconoció la importancia de his answer, pero los diferentes tipos de hecho representan una diferencia crítica entre vals y objects. En particular, el val y lazy val tienen un tipo estructural (por ejemplo, A{def foo: Int}), mientras que el object tiene un tipo de singleton. Como resultado, las llamadas al método foo en los val s implican la reflexión, mientras que las llamadas al método foo en la object no:

class A 

class B { 
    val a1 = new A  { def foo = printStack } 
    object a2 extends A { def foo = printStack } 
    lazy val a3 = new A { def foo = printStack } 

    def printStack() = 
    new Exception().getStackTrace take 3 foreach println 
} 

scala> val b = new B 
b: B = [email protected] 

scala> b.a1.foo // the val 
line124$object$$iw$$iw$B.printStack(<console>:12) 
line124$object$$iw$$iw$B$$anon$1.foo(<console>:7) 
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 

scala> b.a2.foo // the object 
line124$object$$iw$$iw$B.printStack(<console>:12) 
line124$object$$iw$$iw$B$a2$.foo(<console>:8) 
line128$object$$iw$$iw$.<init>(<console>:9) 

scala> b.a3.foo // the lazy val 
line124$object$$iw$$iw$B.printStack(<console>:12) 
line124$object$$iw$$iw$B$$anon$2.foo(<console>:9) 
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
+0

Zoiks! No sabía que las clases anónimas usaban la reflexión. Creo que usarlas para el patrón de chulo-mi-biblioteca es mejor evitarlo: mejor definir una clase con nombre. Las clases anónimas se usan extensamente en el cambio de escala, pero la velocidad no es crítica. –

+2

@Luigi Plinge Las clases anónimas usan ref. solo para métodos que no están definidos en la clase/interfaz principal. Si 'A' hubiera definido' foo', incluso de manera abstracta, no se habría necesitado ninguna reflexión. –

0

Ésta es no un tipo estructural: val a = new A { def foo = 1}

Crea una subclase anónima única; a.foo invoca foo en esa clase.

x aquí es un tipo estructural: def bar (x: {def graves: Int})

x.bass se introspección x (de tipo desconocido) para encontrar un método con el nombre de 'graves'. Funcionará con peces o instrumentos musicales. ;)

Una diferencia entre el val perezoso y el objeto es la siguiente:

var someA = (new B).a3 
someA = (new B).a3 // ok 

var anotherA = (new B).a2 
anotherA = = (new B).a2 // compile error 
+3

La razón por la que obtienes un error de compilación es porque tienes dos signos iguales uno después del otro –

2

Otra diferencia importante es que los objetos conocen su propio nombre y Val de no lo hacen.

2

La primera diferencia práctica es que el vals y objetos perezosos son perezosos, mientras Vals están ansiosos.

La principal diferencia entre los objetos y los vagos perezosos es que un objeto es, desde la perspectiva de los idiomas considerado como un "singleton", que desde la perspectiva de jvm generalmente se trata como un miembro estático.La definición del objeto en el ejemplo dado no puede ser anulada, como otros han demostrado, por la misma razón que los miembros estáticos no pueden ser reemplazados: sin estar atados a una instancia, no hay forma concebible de hacer una búsqueda de función virtual.

object Foo { object Bar extends A; } 

está vagamente como el siguiente código java:

class Foo { 
    private static class Bar extends A{} 
    public static Bar Bar = new Bar; 
} 

Si en el ejemplo anterior, si se extiende se definió una subclase C Foo, no sería capaz de anular la definición de Bar. Se accederá a la barra de instancias estáticas en Java como Foo.Bar. C.Bar no significa lo mismo que (nueva C) .Bar. Puede que esté un poco equivocado, en realidad no he intentado descifrar el código scala, esto es solo un ejemplo para ilustrar el concepto general de objetos como miembros estáticos.

lazy vals puede ser un poco menos eficiente. La última vez que revisé, se implementaron al mantener un campo oculto en la clase que mantenía un registro de los valores perezosos que se habían inicializado. Mantener este campo requiere bloqueo, lo que puede causar problemas de rendimiento.

Una importante diferencia práctica entre val perezoso y el objeto es el tratamiento de la insuficiencia:

Si tengo:

class Foo() { throw new Exception("blah!"); } 
object Stuff { object Bar extends Foo { val x = "hi" } } 
Stuff.Bar 
//exception "blah!" thrown. 
Stuff.Bar.x 
//NoClassDefFoundError: Could not initialize Stuff$Bar$ 

mientras que si lo hago:

object Stuff2 { lazy val Bar = new Foo() { val x = "hi" } } 
Stuff2.Bar 
// "blah!" 
Stuff2.Bar.x 
// "blah!" 

El "NoClassDefFoundError" puede ser realmente confuso, y dado que es un error y no una excepción, puede romper el código de manejo de errores que (apropiadamente) atrapa/registra "Excepción" pero permite la propagación de errores. Incluso podría considerar este tipo de error en el lenguaje Scala, ya que este caso de uso indica una condición excepcional, no un verdadero error de JVM. He visto NoClassDefFoundErrors al acceder a objetos que dependían de recursos externos (por ejemplo, conexiones de bases de datos o archivos en el disco). Solo el primer acceso registra la causa subyacente, por lo que la correcta depuración de dicho problema generalmente requiere el reinicio de su servidor de aplicaciones.

Cuestiones relacionadas