2010-08-10 6 views
28

Quelle est la différence entre déclarer un champ comme val, lazy val et object dans une classe scala, comme dans l'extrait suivant:val et objet dans une classe de 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

Il s'avère que 'valent val a3 = nouveau A {def foo = 1}' aurait dû aussi être ajouté à la question. – PeWu

+0

Voir aussi [Scala - new vs object extends] (http://stackoverflow.com/q/16182735/1048572) – Bergi

Répondre

18

Dans le premier cas, tout code inclus est exécuté dès que la classe B est créée. Dans ce dernier cas, cependant, jusqu'à ce que vous utilisiez réellement l'objet, il ne sera pas instancié.

Vous pouvez voir la différence ici:

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 

Il y a aussi des différences cachées dans ce que les fichiers créés .class sont nommés et tels; et bien sûr les deux ont des types différents.

+1

Donc 'object' fonctionne comme' valse paresseux '. Y a-t-il des différences pratiques entre les deux? – PeWu

+1

Intrinsèquement, non, pas autant que je sache. Le bytecode pour 'object' est un peu plus compact. Je ne suis pas sûr que cela signifie que 'lazy val' est écrit de façon inefficace, ou que' object' pourrait être dangereux dans certaines conditions de threading, ou les deux. –

+0

Ou pas aussi loin que je me souvenais à ce moment-là. Voir le commentaire d'Alex Boisvert pour une différence critique! –

3

Je suppose qu'une différence est que a1 sera d'un sous-type de A en a2 sera d'un autre sous-type de A à savoir 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

Pas exactement. Les deux seront des sous-classes de 'A'. – PeWu

+0

Bien sûr, mais ils ne seront pas du même type. – aioobe

+1

Pour être plus précis, a2 aura un type a2.type. – venechka

16

Une différence majeure est que val de peut être surchargée alors que les objets ne peuvent pas.

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

conduit à:

<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

Je ne suis pas sûr que aioobe a reconnu l'importance de his answer, mais les différents types représentent en fait une différence essentielle entre vals et objects. En particulier, le val et lazy val ont un type de structure (par exemple A{def foo: Int}), tandis que le object a un type singleton. En conséquence, les appels à la méthode foo sur les val s impliquent la réflexion, alors que les appels à la méthode foo sur le object ne le font pas:

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! Je ne connaissais pas les classes anonymes utilisées pour la réflexion. Il est donc préférable d'éviter le pattern pimp-my-library: mieux définir une classe nommée Les classes anonymes sont largement utilisées dans scala-swing mais la vitesse n'y est pas critique –

+2

@Luigi Plinge Les classes anonymes utilisent ref lection uniquement pour les méthodes qui ne sont pas définies dans la classe parente/l'interface. Si 'A' avait défini' foo', même de façon abstraite, aucune réflexion n'aurait été nécessaire. –

0

C'est pas un type de structure: val = new A { def foo = 1}

Il crée une sous-classe anonyme unique, a.foo invoque foo dans cette classe.

x ici est un type de structure: def bar (x: {def basse: Int})

x.bass sera x introspect (de type inconnu) de trouver une méthode avec le nom 'bass'. Il travaillera avec du poisson ou des instruments de musique. ;)

Une différence entre le val paresseux et l'objet est le suivant:

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

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

La raison pour laquelle vous obtenez une erreur de compilation est parce que vous avez deux signes égaux l'un après l'autre –

2

Une autre différence majeure est que les objets connaissent leur propre nom et val de ne pas.

2

La première différence pratique est que vals paresseux et les objets sont paresseux, alors que vals sont impatients. La principale différence entre les objets et les vals paresseux est qu'un objet est, du point de vue des langages, considéré comme un "singleton", qui, du point de vue de jvm, est généralement considéré comme un membre statique.La définition d'objet dans l'exemple donné ne peut pas être redéfinie, comme d'autres l'ont démontré, pour la même raison que les membres statiques ne peuvent pas être remplacés: sans être lié à une instance, il n'y a pas moyen de faire une recherche de fonction virtuelle.

object Foo { object Bar extends A; } 

est vaguement comme le code java suivant:

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

Si dans l'exemple ci-dessus, si une sous-classe C étend Foo a été définie, il ne serait pas en mesure de passer outre la définition de Bar. La barre d'instance statique en Java serait accessible en tant que Foo.Bar. C.Bar ne veut pas dire la même chose que (nouveau C) .Bar. Je pourrais être un peu hors ici, je n'ai pas réellement essayé de dé-compiler le code de scala, ceci est juste un exemple pour illustrer le concept général des objets en tant que membres statiques.

les vals paresseux peuvent être un peu moins efficaces. La dernière fois que j'ai vérifié, ils ont été implémentés en maintenant un champ caché dans la classe qui gardait la trace des vals paresseux qui avaient été initialisés. La maintenance de ce champ nécessite un verrouillage, ce qui peut entraîner des problèmes de performances.

Une grande différence pratique entre val paresseux et l'objet est le traitement de l'échec:

Si j'ai:

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$ 

alors que si je fais:

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

Le "NoClassDefFoundError" peut être vraiment déroutant, et puisque c'est une erreur et non une exception, il peut casser le code de gestion des erreurs qui attrape (logiquement)/logs "Exception" mais permet aux erreurs de se propager. Je pourrais même considérer ce genre de bug dans le langage Scala, car ce cas d'utilisation indique en fait une condition exceptionnelle, pas vraiment une erreur JVM. J'ai vu NoClassDefFoundErrors lors de l'accès aux objets qui dépendaient de ressources externes (par exemple, les connexions à la base de données ou les fichiers sur le disque). Seul le premier accès enregistre la cause sous-jacente. Par conséquent, le débogage correct d'un tel problème nécessite généralement de redémarrer votre serveur d'applications.

Questions connexes