Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Comment coder un enum via une classe « normale »

59 réponses
Avatar
Francois
Bonjour à tous,

Ma question n'a pas un grand intérêt pratique, c'est juste pour
comprendre les enum. En fait, je comprends bien l'utilité des enum et la
façon dont on s'en sert. Mais, si j'ai bien compris, le mot clé "enum" a
été créé dans la version 1.5 de Java pour avoir un type énuméré en deux
coup de cuillère à pot, sachant qu'avant la version 1.5 il fallait coder
soi-même sa propre classe donnant le type énuméré souhaité.

Justement, on codait comment en version 1.4 (ou antérieure) ? Par
exemple, comment avec une classe « normale » peut-on coder un équivalent
de cet exemple très simple de type enum :

public enum Etat {
DEHORS,
DEDANS
}

Merci d'avance.

--
François Lafont

9 réponses

2 3 4 5 6
Avatar
Wykaaa
Alain Ketterlin a écrit :
Samuel Devulder writes:

Mais au final, dans mon Tetris, dès fois je vais tomber sur un carré
et dès fois sur un demi-T de mamière aléatoire. Donc, au final,
forcémentx quelque part dans le code j'aurais au moins une fois un
swith (ou des if), non ?


En fait oui,



Non.

pour la simple et bonne raison qu'un processeur standard
n'a fondamentalement pas de notion de d'heritage et de polymorphisme,
et que la seule chose qu'il sache faire ce sont du calcul
arithméthique, et des gotos +/- conditionnels.



Et des branchements indirects. L'équivalent d'appeler une fonction en
C via un pointeur, d'une certaine façon. C'est comme cela que la
liaison dynamique est faite.

-- Alain.



Les branchements sont indirects (pointeur sur la table des fonctions
virtuelles) mais il y a aussi une indexation pour appeler la "bonne"
fonction. Ceci est la génération "générique" du polymorphisme mais en
fonction des options d'optimisation et du nombre de fonctions
virtuelles, on peut aussi mettre la table dans chaque classe (on perd en
mémoire mais on gagne l'indirection à chaque fois).
Tous les assembleurs ne permettent pas forcément de faire un branchement
indirect ET indexé en une seule instruction.
Dans les assembleurs qui le permettent, on peut aussi faire générer les
switch de cette façon par le compilo dans le cas où le tableau du switch
est "creux" par rapport au nombre de cas possibles (pointeur sur la
table et index sur le bon cas).
C'est là qu'on voit qu'on peut remplacer les switch par le polymorphisme
car le principe de génération est pratiquement le même (sauf la gestion
du pointeur sur la table).
Avatar
Alain Ketterlin
Francois writes:

Alain Ketterlin a écrit :

Mais au final, dans mon Tetris, dès fois je vais tomber sur un carré
et dès fois sur un demi-T de mamière aléatoire. Donc, au final,
forcémentx quelque part dans le code j'aurais au moins une fois un
swith (ou des if), non ?


En fait oui,


Non.



Ah, serait-ce possible d'avoir un bout de code java (même un peu
elliptique par endroit), où l'on crée dès fois une pièce de type
Carre, dès fois de type demi-T, sans utiliser le moindre switch ou if
équivalent ?



Note que ce n'est pas à cela que je répondais (voir plus bas pour la
réponse), la partie pertinente du message initial est :

J'imagine qu'il y aura une classe abstraite Piece qui contient une
méthode abstraite faireTourner(). Puis, on aura deux classes Carre et
DemiT qui hériteront de la classe Piece et qui implémenteront à leur
manière la méthode faireTourner(). Ok.



(à placer juste avant la partie citée plus haut).

Ce que je contredis, c'est "tôt ou tard il faut faire un switch pour
décider la méthode à appeler" (faireTourner() ici). Il n'y a jamais de
switch pour la liaison dynamique (ce que je disais dans mon message).

Car là, je ne vois pas du tout comment c'est possible.



Ah. Pourtant ce n'est pas très dur. Par exemple si tu as une méthode
clone(), tu déclares un tableau de "modèles" de tes pièces. Tu crées
une pièce en faisant :

modeles[r.nextInt()%N].clone()

(où r est un java.util.Random). Si tu ne veux pas de clone(), tu crées
une classe de fabriques abstraite et autant de classes de fabriques
que tu as de modèles de pièces, chaque fabrique produisant un pièce du
type correspondant.


Je vois maintenant qu'il s'agit d'utiliser des enum... Dans ce cas
c'est encore plus simple : il suffit de définir une méthode abstraite
dans l'enum, et une méthode dans chaque valeur (Une enum est en gros
équivalente à une classe abstraite et N sous-classes singletons.) En
gros :

enum Formes {
abstract Forme make();
CARRE {
Forme make() { return new Carre(); }
}
... // idem pour les autres formes
}

Donc, si tu as la valeur, tu peux appeler la méthode.

-- Alain.
Avatar
Samuel Devulder
Francois a écrit :
Wykaaa a écrit :.

J'a... doooooore !
Mais je ne voulais pas effrayer François ;-)



C'est gentil d'avoir pensé à moi. :-)

Je garde ce code dans un coin pour essayer de le potasser, car là comme
ça c'est trop dur.



Ben en fait c'est tout simple. C'est quoi un objet de type True par
rapport à un objet de Type false? Ben je regarde:

1. les operateurs logiques
True or other vaut toujours True
donc je définie True.or(other) = True
True and other vaut toujours other,
donc True.and(other) = other
(pareil pour False)

2. les executions contidtionnelles.
if(True) code()
execute le code alors que
if(False) code()
ne l'execute pas.

Donc je définie True.ifTrue(code) = code.run() et
False.ifTrue(code) = {rien}.


On pousse l'objet à l'extrême avec cela, mais du coup on a plus besoin
de supporter la syntaxe if(), vu que tout passe par le dispatching: on
demande à l'expression booleeene de s'evaluer et du coup d'executer ou
pas le code passé:
a.eq(b) // Retourne True ou False
.ifTrue(code) // execute code si c'est True

Après j'ai ajouté du sucre syntaxique en faisant en sorte de cascader
les operarations (ifTrue(), ifFalse() retournent this) et voila.

En java c'est chiant à écrire à cause des new Runnable() {} qui sont
verbeux, mais si on avait le support pour avoir des objets typés de type
block, on pourrait avoir du code assez plaisant.
Avatar
Alain Ketterlin
Wykaaa writes:

Alain Ketterlin a écrit :



Et des branchements indirects. L'équivalent d'appeler une fonction en
C via un pointeur, d'une certaine façon. C'est comme cela que la
liaison dynamique est faite.
-- Alain.





Les branchements sont indirects (pointeur sur la table des fonctions
virtuelles) mais il y a aussi une indexation pour appeler la "bonne"
fonction.



C'est connu par le compilateur, il n'y rien à rechercher. Ou alors je
comprends mal ce que tu dis.

Ceci est la génération "générique" du polymorphisme mais en
fonction des options d'optimisation et du nombre de fonctions
virtuelles, on peut aussi mettre la table dans chaque classe (on perd
en mémoire mais on gagne l'indirection à chaque fois).



Je ne connais pas de cas où la table n'est pas associée à la classe.
On peut éviter de consulter la table si tout est connu à la
compilation, mais en Java c'est fortement compromis par le chargement
dynamique de classes.

Tous les assembleurs ne permettent pas forcément de faire un
branchement indirect ET indexé en une seule instruction.



Dans ce cas on en fait deux.

Dans les assembleurs qui le permettent, on peut aussi faire générer
les switch de cette façon par le compilo dans le cas où le tableau du
switch est "creux" par rapport au nombre de cas possibles (pointeur
sur la table et index sur le bon cas).



Dans le cas d'un appel de méthode, le tableau ne peut jamais être
"creux". Mais c'est un peu compliqué par l'héritage. Voir la
définition de "invokevirtual" dans la ref. JVM.

-- Alain.
Avatar
Samuel Devulder
Alain Ketterlin a écrit :

Et des branchements indirects. L'équivalent d'appeler une fonction en
C via un pointeur, d'une certaine façon. C'est comme cela que la
liaison dynamique est faite.



Perso je n'oserai pas parler de polymorphisme en parlant de
jsr @a0
sur un 680x0:) Mais bon, ca se discute. Cela dit, dans le calcul de
l'adresse que l'on met dans a0, il y aura bien des gotos et des tests
sur des valeurs numériques.
Avatar
Alain Ketterlin
Samuel Devulder writes:

Alain Ketterlin a écrit :

Et des branchements indirects. L'équivalent d'appeler une fonction en
C via un pointeur, d'une certaine façon. C'est comme cela que la
liaison dynamique est faite.



Perso je n'oserai pas parler de polymorphisme en parlant de
jsr @a0
sur un 680x0:)



C'est dans l'autre sens : c'est la liaison dynamique qui est implantée
à l'aide de jsr ou équivalent.

Mais bon, ca se discute. Cela dit, dans le calcul de l'adresse que
l'on met dans a0, il y aura bien des gotos et des tests sur des
valeurs numériques.



Mais pour calculer quoi ? Un pointeur et un index dans un tableau
suffisent, le pointeur est conservé explicitement et l'index est
déterminé à la compilation. Il n'y a plus rien à faire à l'exécution.

-- Alain.

P/S: on s'éloigne quand même un peu de java... qui, dans le cas
"standard" d'une JVM laisse à celle-ci la liberté de faire comme elle
l'entend.
Avatar
Wykaaa
Alain Ketterlin a écrit :
Wykaaa writes:

Alain Ketterlin a écrit :



Et des branchements indirects. L'équivalent d'appeler une fonction en
C via un pointeur, d'une certaine façon. C'est comme cela que la
liaison dynamique est faite.
-- Alain.





Les branchements sont indirects (pointeur sur la table des fonctions
virtuelles) mais il y a aussi une indexation pour appeler la "bonne"
fonction.



C'est connu par le compilateur, il n'y rien à rechercher. Ou alors je
comprends mal ce que tu dis.



Excuse-moi, emporter dans mon élan et parce que tu parlais de C, ce que
je décrivais était l'appel d'une fonction virtuelle en C++ !

Ceci est la génération "générique" du polymorphisme mais en
fonction des options d'optimisation et du nombre de fonctions
virtuelles, on peut aussi mettre la table dans chaque classe (on perd
en mémoire mais on gagne l'indirection à chaque fois).



Je ne connais pas de cas où la table n'est pas associée à la classe.
On peut éviter de consulter la table si tout est connu à la
compilation, mais en Java c'est fortement compromis par le chargement
dynamique de classes.

Tous les assembleurs ne permettent pas forcément de faire un
branchement indirect ET indexé en une seule instruction.



Dans ce cas on en fait deux.



Oui mais dans ce cas, c'est moins... élégant.

Dans les assembleurs qui le permettent, on peut aussi faire générer
les switch de cette façon par le compilo dans le cas où le tableau du
switch est "creux" par rapport au nombre de cas possibles (pointeur
sur la table et index sur le bon cas).



Dans le cas d'un appel de méthode, le tableau ne peut jamais être
"creux". Mais c'est un peu compliqué par l'héritage. Voir la
définition de "invokevirtual" dans la ref. JVM.

-- Alain.



Exact, c'est une différence. Je connais la JVM.
Avatar
Samuel Devulder
Alain Ketterlin a écrit :
Mais pour calculer quoi ? Un pointeur et un index dans un tableau
suffisent, le pointeur est conservé explicitement et l'index est
déterminé à la compilation. Il n'y a plus rien à faire à l'exécution.



Ca marche pas avec le bytecode java. Le invoke prend en argument une
chaine décrivant la méthode, donc il faut aller consulter une table et
comparer des chaines, ce qui implique des tests et des sauts au bas
(très bas) niveau.

Il faut s'y résigner, tout objet que soit la conception vue d'en haut,
le codeur aura beau éviter les switchs et autre if() en utilisant le
dispatching, au bas niveau du processeur (x86, ppc) il y aura toujours
au final des tests et des sauts dans le calcul (direct ou indirect) de
l'adresse à empiler.

sam.
Avatar
Alain Ketterlin
Samuel Devulder writes:

Alain Ketterlin a écrit :
Mais pour calculer quoi ? Un pointeur et un index dans un tableau
suffisent, le pointeur est conservé explicitement et l'index est
déterminé à la compilation. Il n'y a plus rien à faire à l'exécution.



Ca marche pas avec le bytecode java. Le invoke prend en argument une
chaine décrivant la méthode, donc il faut aller consulter une table et
comparer des chaines, ce qui implique des tests et des sauts au bas
(très bas) niveau.



Exact pour le parcours de la hiérarchie de classe uniquement (mais
pas des différents types possibles), j'avais oublié qu'on parlais du
calcul de l'adresse de la méthode.

Par contre, la JVM ne fait jamais de comparaison de chaînes dans sa
résolution (les chaînes sont internées bien avant). Invokevirtual ne
prend pas une chaîne mais un index, et l'ensemble revient à traverser
des tables (qui gardent un cache des résultats). Le gros du boulot est
de remonter la hiérarchie des classes.

-- Alain.
2 3 4 5 6