web-dev-qa-db-fra.com

À Scala; dois-je utiliser le trait App?

Je viens de commencer à apprendre Scala et de nombreux tutoriels que je suis en train d'utiliser utilisent une combinaison de différentes représentations pour une méthode main. Mis à part la méthode principale familière; il y a aussi l'utilisation des traits App ou Application. Il semble que Application a été déprécié et n'est pas recommandé, mais je ne trouve aucune information qui explique bien au-delà de cela sur chacune de ces façons de définir un point d'entrée.

Je me demande donc si quelqu'un pourrait m'expliquer:

  • Comment fonctionnent les traits App et Application?
  • Pourquoi le trait Application n'est-il plus recommandé et que fait le trait App qui est différent?
  • Où dois-je utiliser la méthode principale traditionnelle et quand dois-je utiliser App pour démarrer mon programme? Quelle est la différence entre les deux approches?
34
Micheal Hill

Le problème avec le trait Application est en fait décrit dans sa documentation:

(1) Le code fileté qui fait référence à l'objet sera bloqué jusqu'à ce que l'initialisation statique soit terminée. Cependant, étant donné que l'exécution complète d'un objet étendant l'application a lieu pendant l'initialisation statique, le code simultané se bloquera toujours s'il doit se synchroniser avec l'objet englobant.

Ceci est délicat. Si vous étendez le trait Application, vous créez essentiellement une classe Java:

class MyApplication implements Application {
  static {
    // All code goes in here
  }
}

La machine virtuelle Java exécute l'initialiseur de classe ci-dessus synchronisé implicitement sur la classe MyApplication. De cette façon, il est garanti qu'aucune instance de MyApplication n'est créée avant l'initialisation de sa classe. Si vous générez un thread à partir de votre application qui doit à nouveau accéder à une instance de MyApplication, votre application se verrouille à mort car l'initialisation de classe n'est terminée qu'après l'exécution de l'ensemble du programme. Cela implique un paradoxe car aucune instance ne peut être créée tant que votre programme est en cours d'exécution.

(2) Comme décrit ci-dessus, il n'y a aucun moyen d'obtenir les arguments de ligne de commande car tout le code dans le corps d'un objet étendant Application est exécuté dans le cadre de l'initialisation statique qui se produit avant même que la méthode principale d'Application ne commence son exécution.

Un initialiseur de classe ne prend aucun argument. En outre, il est exécuté en premier, avant que des valeurs puissent être transmises à la classe, car l'initialiseur de classe doit être exécuté avant que vous puissiez même attribuer une valeur de champ statique. Ainsi, les args que vous recevez normalement sur une méthode main sont perdues.

(3) Les initialiseurs statiques ne sont exécutés qu'une seule fois pendant l'exécution du programme, et les auteurs de la JVM supposent généralement que leur exécution est relativement courte. Par conséquent, certaines configurations JVM peuvent devenir confuses, ou tout simplement ne pas optimiser ou JIT le code dans le corps d'une application d'extension d'objet. Cela peut entraîner une dégradation importante des performances.

La JVM optimise le code qui est exécuté fréquemment. De cette façon, il garantit qu'aucun temps d'exécution n'est perdu sur des méthodes qui ne sont pas vraiment un goulot d'étranglement performant. Cependant, il suppose en toute sécurité que les méthodes static ne sont exécutées qu'une seule fois car elles ne peuvent pas être appelées manuellement. Ainsi, il n'optimisera pas le code qui est exécuté à partir d'un initialiseur de classe qui est cependant le code de méthode main de votre application si vous utilisez le trait Application.

Le trait App corrige tout cela en étendant DelayedInit . Ce trait est explicitement connu du compilateur Scala de sorte que le code d'initialisation n'est pas exécuté à partir d'un initialiseur de classe mais à partir d'une autre méthode. Notez le pour le nom référence utilisée pour la seule méthode du trait:

trait Helper extends DelayedInit {
  def delayedInit(body: => Unit) = {
    println("dummy text, printed before initialization of C")
    body
  }
}

Lors de l'implémentation de DelayedInit, le compilateur Scala encapsule tout code d'initialisation de sa classe ou objet d'implémentation dans un pour le nom qui est ensuite passée à la méthode delayedInit. Aucun code d'initialisation n'est exécuté directement. De cette façon, vous pouvez également exécuter du code avant d'exécuter un initialiseur ce qui permet Scala par exemple, pour imprimer les métriques d'exécution d'une application sur la console qui est enroulée autour du point d'entrée et de sortie du programme. Cependant, il y a quelques mises en garde de cette approche et l'utilisation de DelayedInit est donc déconseillée. Vous ne devez vraiment compter que sur le trait App qui résout les problèmes imposés par le trait Application. Vous ne devez pas implémenter DelayedInit directement.

Vous pouvez toujours définir une méthode main si vous le souhaitez, tant que vous la définissez dans un object. C'est surtout une question de style:

object HelloWorld {
  def main(args: Array[String]) {
    println("Hello, world!")
  }
}
30