I. Initiation à Scala et configuration de l'environnement▲
Il existe de nombreux tutoriels sur Internet qui parlent de la mise en place de Scala, alors je ne vais pas continuer à les aborder.
La configuration que je recommanderais est la suivante.
- Système d'exploitation : Mac / Linux de préférence
- L'édition communautaire Intellij IDEA avec le plugin Scala installé
Si vous avez fini de les configurer, commencez.
I-A. Votre premier Hello World !!▲
Si vous avez des connaissances en Java, notez que scala est légèrement différent dans sa représentation. Passons directement à un code.
2.
3.
4.
5.
object
Test {
def
main
(
args : Array
[String]
){
println
(
"
Hello
world
"
)
}
}
Je vous recommande de mettre ce code dans votre éditeur Intellij et ensuite faites un clique droit pour l'exécution. Si tout va bien, votre console affichera Hello World.
Toutes nos félicitations !! Vous êtes l'auteur de votre premier Hello world.
Maintenant, abordons cela étape par étape.
La première chose à noter, c'est que tout le code se trouve dans un bloc d'objet. Les programmeurs Java pourraient trouver cela confus. Garder cette question pour l'instant, la prochaine section l'abordera de manière plus détaillée. Contrairement à Java, les noms de classe ne doivent pas correspondre aux noms de fichiers, ce n'est pas un grand avantage, mais nous avons cette liberté ici.
La prochaine chose est la syntaxe étrange de def
main (
). Pour commencer, def
est un mot clé pour déclarer les méthodes. Nous traiterons les méthodes plus en détail dans un tutoriel à venir.
S'il existe une méthode, il peut y avoir des arguments. Dans notre cas, c'est Array
[String]
. Ceci est similaire à la méthode principale de java, où un ensemble de chaînes de caratères peut être un argument sur la méthode principale. Ceci peut être utilisé pour les configurations de démarrage ou autre chose, mais l'utilisation est complètement facultative.
Ceci est suivi d'un appel de la méthode println (
) qui affiche les instructions à la console. Si vous utilisez un EDI (vous devriez), vous pouvez tracer tout l'appel en maintenant la touche Ctrl + un clique sur la méthode. Les programmeurs Python devraient trouver cette syntaxe familière et, en fait, pour un programmeur, il n'y a rien d'autre que l'affichage d'une chaîne de caratères dans la console. Les points-virgules sont facultatifs en scala à la différence de java et le compilateur repose sur des sauts de ligne pour identifier le prochain bloc de code .
Mais creusons un peu plus profondément. Pour les programmeurs Java, une impression générale sur la console sera comme System.out.println. Alors, comment est-ce différent ?
La réponse est qu'il n'est pas si différent, println (
) est une méthode de classe appelée Predef.scala, qui appelle alors la méthode println de Console.scala qui appelle out.println sur PrintStream.class ou PrintStream.java si vous avez le Code source joint, sinon le décompilateur d'Intellij montre le code décompilé. L'impression aussi bien pour scala que pour java se retrouve dans le même appel de méthode. Comme indiqué précédemment, scala est construit sur la JVM et peut interagir avec le code Java de façon transparente, il y aurait une raison spécifique si la mise en œuvre était différente, sinon ce serait juste de réinventer la roue.
En fait, il existe de nombreux autres exemples qui utilisent les bibliothèques java dans notre parcours pour apprendre à utiliser Scala.
Un simple Hello world a ouvert de nombreux sujets à aborder, trois en particulier
- Méthodes (couvert dans les tutoriels ultérieurs)
- Objets et classes (couvert dans les tutoriels ultérieurs)
- Interférence de types - La raison pour laquelle scala est une langue dynamique typiquement statique - expliquée ci-dessous
II. Les variables▲
J'aurais dû expliquer les types de données avant de passer aux variables, mais il y a des différences fondamentales dont je veux parler afin que nous puissions mieux les comprendre.
v et val sont deux mots-clés qui sont utilisés pour déclarer des variables dans Scala.
v est utilisé pour déclarer des variables mutables et val déclarer des variables immuables. Mais quel type de variables sont-ils ? Pour les types primitifs de données, d'où vient le concept de mutabilité ? Les données primitives par elles-mêmes sont immuables, c'est-à-dire que leur type ne peut être modifié une fois déclaré, mais leurs valeurs sont mutables, c'est-à-dire qu'elles peuvent être modifiées.
En premier lieu, il est difficile de comprendre pourquoi le concept de mutabilité agit sur les variables et non sur les objets, ceci est expliqué ci-dessous dans la section des types de données, il n'y a pas de types primitifs dans Scala, tous sont des objets.
III. Syntaxe des tableaux▲
Il s'agit simplement d'un bref aperçu de la manière dont les tableaux sont déclarés en Scala, ce qui est légèrement différent de celle de java.
Certains tableaux peuvent être déclarés comme ci-dessous :
val
array =
Array
(
10
,12
,23
,44
)
Ceci est différent de la déclaration java où nous utilisons des accolades pour des valeurs de tableau fixe.
int
[] array =
{
10
,12
,33
,55
}
;
Et pour des valeurs inconnues, nous utilisons des crochets.
int
[] array =
new
int
[4
];
En Scala, les crochets sont utilisés pour indiquer le type.
val
array : Array
[Int]
=
Array
(
10
,12
,23
,44
)
Cela équivaut au symbole <> de java.
ArrayList<
Integer>
rows =
new
ArrayList<
>
(
);
Nous verrons plus en profondeur les collections dans les tutoriels à venir.
IV. Référence contre immutabilité des valeurs▲
Si val est immuable, alors il ne peut pas être modifié ? Est-ce similaire au mot-clé final de Java ou est-ce en rapport à l'immutabilité de la chaîne de caractères ?
Regardons l'exemple de code ci-dessous, pour nous aider à mieux comprendre.
2.
3.
4.
5.
6.
7.
8.
var
myVar =
10
//
fonctionne
correctement
myVar =
myVar +
10
val
myNum =
6
//
Erreur
de
compilation
//
Réaffectation
à
val
myNum =
myNum +
10
Si vous exécutez le code ci-dessus, vous constaterez une erreur lors de la compilation elle-même, causée par la réaffectation à val. Si vous utilisez un EDI, l'erreur s'affiche lorsque vous tapez le code en raison de la précompilation que fait l'IDE .
La première chose n'est certainement pas l'immutabilité de la chaîne de caractères étant donné qu'elle n'est pas visible pour le programmeur et est contrôlée au niveau de la compilation. Question suivante, est-ce semblable à final en Java?
Vue de loin, cela semble signifier qu'une fois qu'une valeur lui est attribuée, elle ne peut pas être modifiée, mais à l'intérieur de la JVM, final n'a rien à voir avec l'immutabilité et elle est utilisée pour que les classes ne puissent pas être étendues et dans le cas des méthodes, elles ne peuvent pas être remplacées.
Considérons le code Java ci-dessous pour démontrer cette différence.
final
ArrayList<
Integer>
arrList =
new
ArrayList<
Integer>
(
);
/*
Cela
n'entraîne
pas
d'erreur,
nous
sommes
en
train
de
muter
l'objet
lui-même.
Si
elle
était
mutable,
cela
entraînerait
une
erreur
à
quelque
chose
comme
ça
ne
peut
pas
être
changé
*/
arrList.add
(
20
);
/*
Cela
entraîne
une
erreur
car
nous
modifions
la
référence
et
non
l'objet
lui-même
*/
arrList =
null
;
Si vous essayez final avec des types primitifs, sa valeur ne peut pas être modifiée. Cela signifie-t-il que les types primitifs sont immuables ? La raison pour laquelle l'erreur survient est parce que Java passe par valeur les types primitifs, il n'est pas logique de passer par référence car ce ne sont pas du tout des objets. Donc, si une variable est modifiée, sa référence (peut être considérée comme emplacement dans la mémoire) change à cause du mécanisme de passage par valeur et non parce que ces primitives sont immuables.
Le point important à comprendre est que l'immutabilité de référence et de valeur importe peu en scala car scala n'a pas de types primitifs et n'a que des objets.
Donc, chaque fois que nous parlons d'immutabilité dans Scala, nous parlons de l'immutabilité des références.
Les variables immuables ont certains bénéfices de performance et s'approchent de la notion d'écriture de code sans effets secondaires.
V. Immuabilité sous le capot▲
Poussons notre compréhension de l'immutabilité encore plus loin en examinant le code d'octet émis en décompilant les fichiers de classe scala générés.
Nous déclarons une classe nommée Parent et un val à l'intérieur.
2.
3.
class
Parent {
val
x =
10
}
En voyant le bytecode émis, nous pouvons voir qu'il se traduit par un type primitif java, il n'y a rien de spécial en ce qui concerne le temps d'exécution.
javap -c filename.class donne le code décompilé ci-dessous :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
public class
Parent {
public int
x
(
);
Code:
0
: aload_0
1
: getfield #13
//
Field
x:I
4
: ireturn
public Parent
(
);
Code:
0
: aload_0
1
: invokespecial #19
//
Method
java/lang/Object."<init>":()V
4
: aload_0
5
: bipush 10
7
: putfield #13
//
Field
x:I
10
: return
}
Il est évident que val est juste une restriction de temps de compilation et n'a rien à voir avec le code d'octet émis.
Nous pouvons utiliser cette approche de la lecture du code d'octet décompilé pour comprendre les choses plus profondément, mais dans la plupart des cas, cela n'est pas nécessaire.
VI. Comparaison et contraste de val et final▲
D'autre part scala a également le mot-clé final qui fonctionne de façon similaire.
Une meilleure façon de visualiser la différence entre val et final est à travers un exemple.
Prenons une simple classe parent :
class
Parent {
val
age =
10
}
Contrairement à java, scala peut également remplacer les variables.
2.
3.
4.
5.
6.
7.
8.
9.
class
Child extends
Parent{
override
val
age =
30
def
printVal =
{
println
(
age)
}
}
Maintenant, si nous déclarons la variable age de type final dans la classe Parent, alors il ne serait pas possible pour la classe Child de la remplacer et cela provoquerait une erreur comme ci-dessous :
class
Child extends
Parent{
override
val
age =
30
println
(
age)
}
C'est un exemple réel de l'endroit où l'on utiliserait final.
Notez que nous ne rompons pas l'immuabilité ici en annulant val dans la classe Child. La classe Child crée une instance propre comme une chaîne de caractère dans le pool de chaînes de caractères java.
VII. Les types de données dans Scala▲
Scala a les mêmes types de données que Java, avec la même mémoire et la même précision.
Tous les types de données sont des objets et des méthodes peuvent être appelées dans ces fichiers comme vous le feriez sur un objet.
2.
3.
4.
5.
6.
7.
val
t =
69
//
Imprime
'E'
la
valeur
ASCII
de
E
est
69
println
(
t.toChar)
val
s =
"
Hello
World
"
//
NDT :
commentaire
confus
pour
moi
println
(
s.charAt
(
2
))
À l'heure actuelle, une autre question aurait été posée ? Où sont les types dans le code scala ?
Contrairement à java, où nous déclarons des variables avec des types de données, puis donnons un nom de variable, scala a quelque chose appelée l'inférence de type (voir les rubriques ci-dessous), mais n'y aller pas encore, il y a une raison pour laquelle je l'ai séparé de la section des variables .
VIII. Inférence de type▲
Si vous ne connaissez pas le terme, ce n'est que la déduction des types au moment de la compilation. Attend, n'est-ce pas ce que signifie le typage dynamique ? Eh bien non, notez que j'ai dit la déduction des types, cela est radicalement différent de ce que font les langages typés dynamiquement, et une autre chose est que cela se fait au moment de la compilation et non au moment de l'exécution.
De nombreuses langages ont cette notion intégrée, mais l'implémentation varie d'un langage à l'autre. Cela pourrait être compliqué au début, mais il sera plus clair avec des exemples de code.
Passons au Scala REPL pour une certaine expérimentation.
De l'image ci-dessus, il est évident qu'il n'y a rien d'extraordinaire, les types des variables sont déduites automatiquement aux meilleurs types jugés appropriés et au moment de la compilation.
Voici un code pour mieux comprendre.
2.
3.
4.
5.
6.
7.
8.
9.
val
x =
20
//
affiche
à
la
console
//
inféré
comme
un
entier
println
(
x+
10
)
//
Quelque
chose
d'aussi
stupide
comme
ci-dessous
causera
une
erreur
de
compilation
val
z =
40
println
(
z *
"
justastring
"
)
Allez-y et jouez avec ces variables, vous êtes protégé par la sécurité du type au moment de la compilation, alors n'hésitez pas à mettre le désordre.
Si vous êtes curieux de savoir dans quelles classes les variables s'étendent, vous pouvez creuser plus profondément et trouver une classe appelée AnyVal. Cela fait partie d'un sujet entièrement différent du système de type unifié Scala, qui n'est que la hiérarchie des classes.
IX. Initialisation des variables▲
En Scala, vous ne pouvez pas simplement créer une variable et la laisser non initialisée.
2.
3.
4.
class
Variables{
val
x //
Mauvaise
déclaration
de
valeur
println (
x)
}
C'est un choix de conception des développeurs de scala. La raison évidente est qu'il ne faut pas laisser les variables non initialisées afin d'éviter des exceptions de pointeur nul.
Le seul endroit où nous n'attribuons pas de valeurs aux variables est au niveau des classes abstraites. Nous approfondirons cette notion lorsque nous aborderons les classes scala.
X. Types d'annotations▲
Scala nous autorise à mentionner le type explicitement.
val
y : Integer
=
20
Ces genres d'annotations de type sont importantes dans les API / méthodes public.
2.
3.
4.
5.
6.
7.
def
getInfoFromBackend
(
) =
{
val
dataList =
List
(
1
,"
Literature
"
,2
,"
Science
"
)
dataList
}
Sans les informations de type explicitement annotées, les développeurs qui les utilisent auront une confusion. N'oubliez pas que tous les langages de la JVM n'ont pas d'inférence de type comme java et, par conséquent, une modification du type peut interrompre le code client (consommant).
Une meilleure version serait comme ci-dessous :
2.
3.
4.
5.
6.
7.
def
getInfoFromBackend
(
) =
List
{
val
dataList =
List
(
1
,"
Literature
"
,2
,"
Science
"
)
dataList
}
Ce concept ne s'applique pas seulement aux paramètres de la méthode, mais aussi aux variables déclarées à l'aide du mot-clé val. L'exemple présente simplement l'idée d'une manière plus compréhensible.
XI. Attribution de type▲
Les attributions de type sont quelque chose de plus complexe. Il s'agit de déclarer au compilateur le type de retour d'une opération que vous allez effectuer.
Un cas d'utilisation typique serait la conversion de type dans java.
2.
3.
4.
5.
6.
7.
8.
int
x =
20
;
//
Conversion
valide
System.out.println
(
(
byte
) x);
//
erreur
d'exécution
Object s =
new
Object
(
);
System.out.println
(
(
byte
) s);
Le code ci-dessus entraînerait une exception / erreur d'exécution comme ci-dessous :
20
Exception in thread "main" java.lang.ClassCastException: java.lang.Object cannot be cast to java.lang.Byte
at JavaExample.main(JavaExample.java:14)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
En scala, si nous utilisons l'attribution de type, le code ne compilerait même pas.
object
Demo extends
App{
val
x : Byte
=
new
Object //
L'expression
du
type
Object
n'est
pas
conforme
au
type
Byte
attendu
Il n'y a pas de différence de syntaxe entre l'annotation de type et l'attribution de type et cela entraîne souvent une confusion entre les deux sujets.
Nous aurions pu faire pareille en java en utilisant la conversion de type lors de l'exécution.
val
x =
new
Object
(
).asInstanceOf[Byte]
Dans le cas ci-dessus, vous pouvez constater qu'il n'y a pas d'erreur au moment de la compilation et il en résultera la même trace de la pile d'exception, c'est-à-dire java.lang.ClassCastException au moment de l'exécution. Les attributions de type peuvent être très utiles lors de la conversion de type. Vous pouvez vérifier la sécurité du type au moment de la compilation plutôt qu'à l'exécution pour vous assurer qu'il n'y a pas de code de bug.
XII. Valeur Paresseuse▲
Comme le suggère le nom, la valeur paresseuse dans scala est similaire à un val, mais sa valeur n'est évaluée que lorsque la variable est utilisée.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
import
scala.io.Source._
object
ReadFileExample extends
App{
println
(
System.getProperty
(
"
user.dir
"
))
lazy
val
lines =
fromFile
(
System.getProperty
(
"
user.dir
"
) +
"
/file1.txt
"
).getLines
println
(
lines)
}
Je vous encourage à essayer cela vous-même. D'abord en commentant println(lines), vous pouvez voir que cela n'a pas entraîné d'erreur même si le fichier n'existait pas.
Mais ceci est un exemple fonctionnel et vous pouvez mettre un fichier réel au niveau supérieur du projet actuel / mettez le à un emplacement spécifique vous-même et que le programme imprime simplement le contenu du fichier.
Ceci est contrôlé au moment de la compilation, car un accès variable peut être connu au moment de la compilation.
Très utile dans des situations telles qu'une fenêtre de chargement de fichier dans un navigateur. L'utilisateur peut ou ne peut pas télécharger le fichier, il est donc préférable de différer / évaluer avec précaution jusqu'à ce que l'événement se produise.
Cela met fin à cette publication, nous avons commencé avec les éléments importants. Je vous encourage à relire le tutoriel pour mieux le comprendre si vous n'avez pas encore pris connaissance de la documentation pertinente sur Internet.
Scala n'est pas facile, mais ce n'est pas difficile non plus si on fait un pas à la fois et qu'on apprend les choses. Mon objectif n'est pas de tout enseigner, mais simplement d'indiquer une direction beaucoup plus spécifique.