Programmation orientée objet en Python vs Java – Real Python

By | juin 3, 2019

Expert Python

Les programmeurs Java qui passent à Python ont souvent des difficultés avec l’approche de Python en matière de programmation orientée objet (OOP). L'approche utilisée pour utiliser des objets, des types de variable et d'autres fonctionnalités de langage prises par Python et Java est très différente. Cela peut rendre la commutation entre les deux langues très déroutante.

Cet article compare et contraste la prise en charge de la programmation orientée objet dans Python et Java. À la fin, vous pourrez appliquer vos connaissances en programmation orientée objet à Python, comprendre comment réinterpréter votre compréhension des objets Java vers Python et utiliser les objets de manière Python.

Au cours de cet article, vous allez:

  • Construire une classe de base à la fois en Java et en Python
  • Explorer le fonctionnement des attributs d'objet dans Python vs Java
  • Comparez et contrastez les méthodes Java et les fonctions Python
  • Découvrez les mécanismes d'héritage et de polymorphisme dans les deux langues
  • Enquêter sur la réflexion entre Python et Java
  • Appliquer tout dans une implémentation de classe complète dans les deux langues

Cet article n’est pas une introduction à la programmation orientée objet. Au lieu de cela, il compare les fonctionnalités et les principes orientés objet de Python par rapport à Java. Les lecteurs doivent avoir une bonne connaissance de Java et se familiariser avec le codage Python. Si vous n'êtes pas familier avec la programmation orientée objet, consultez Intro à la programmation orientée objet (OOP) en Python. Tous les exemples Python fonctionneront avec Python 3.6 ou version ultérieure.

Exemples de classes en Python vs Java

Pour commencer, vous allez implémenter la même petite classe en Python et en Java pour illustrer leurs différences. Vous allez les modifier au fur et à mesure de la progression de l'article.

Tout d'abord, supposons que vous ayez les éléments suivants Voiture définition de classe en Java:

    1 Publique classe Voiture 
    2     privé Chaîne Couleur;
    3     privé Chaîne modèle;
    4     privé int année;
    5 
    6     Publique Voiture(Chaîne Couleur, Chaîne modèle, int année) 
    7         ce.Couleur = Couleur;
    8         ce.modèle = modèle;
    9         ce.année = année;
dix     
11 
12     Publique Chaîne getColor() 
13         revenir Couleur;
14     
15 
16     Publique Chaîne getModel() 
17         revenir modèle;
18     
19 
20     Publique int getYear() 
21         revenir année;
22     
23 

Les classes Java sont définies dans des fichiers portant le même nom que la classe. Donc, vous devez enregistrer cette classe dans un fichier nommé Car.java. Une seule classe peut être définie dans chaque fichier.

Un petit similaire Voiture class est écrit en Python comme suit:

    1 classe Voiture:
    2     def __init__(soi, Couleur, modèle, année):
    3         soi.Couleur = Couleur
    4         soi.modèle = modèle
    5         soi.année = année

En Python, vous pouvez déclarer une classe n'importe où, dans n'importe quel fichier, à tout moment. Enregistrer cette classe dans le fichier voiture.py.

En utilisant ces classes comme base, vous pouvez explorer les composants de base des classes et des objets.

Attributs d'objet

Tous les langages orientés objet ont un moyen de stocker des données sur l'objet. En Java et Python, les données sont stockées dans les attributs, qui sont des variables associées à des objets spécifiques.

L'une des différences les plus significatives entre Python et Java réside dans la manière dont ils définissent et gèrent les attributs de classe et d'objet. Certaines de ces différences proviennent de contraintes imposées par les langues, tandis que d’autres découlent des meilleures pratiques.

Déclaration et initialisation

En Java, vous déclarez des attributs dans le corps de la classe, en dehors de toute méthode, avec un type défini. Vous devez définir les attributs de classe avant de les utiliser:

    1 Publique classe Voiture {
    2     privé Chaîne Couleur;
    3     privé Chaîne modèle;
    4     privé int année;

En Python, vous déclarez et définissez des attributs dans la classe. __init __ (), qui est l’équivalent du constructeur de Java:

    1 def __init__(soi, Couleur, modèle, année):
    2     soi.Couleur = Couleur
    3     soi.modèle = modèle
    4     soi.année = année

En préfixant les noms de variables avec soi, vous dites à Python que ce sont des attributs. Chaque instance de la classe aura une copie. Toutes les variables en Python sont typées de manière vague, et ces attributs ne font pas exception.

Vous pouvez également créer des variables d'instance en dehors de .__ init __ (), mais ce n’est pas une bonne pratique car leur portée est souvent source de confusion. Si elles ne sont pas utilisées correctement, les variables d’instance créées en dehors de .__ init __ () peut conduire à des bugs subtils difficiles à trouver. Par exemple, vous pouvez ajouter un nouvel attribut .roues à un Voiture objet comme ceci:

>>>

    1 >>> importation voiture
    2 >>> ma voiture = voiture.Voiture("jaune", "scarabée", 1967)
    3 >>> impression(F"Ma voiture est ma_car.color")
    4 Ma voiture est jaune
    5 
    6 >>> ma voiture.roues = 5
    7 >>> impression(F"Roues: my_car.wheels")
    8 Roues: 5

Cependant, si vous oubliez le my_car.wheels = 5 sur la ligne 6, alors Python affiche une erreur:

>>>

    1 >>> importation voiture
    2 >>> ma voiture = voiture.Voiture("jaune", "scarabée", 1967)
    3 >>> impression(F"Ma voiture est ma_car.color")
    4 Ma voiture est jaune
    5 
    6 >>> impression(F"Roues: my_car.wheels")
    7 Traceback (dernier appel le plus récent):
    8         Fichier "", ligne 1, dans 
    9 AttributeError: L'objet 'voiture' n'a pas d'attribut 'roues'

En Python, lorsque vous déclarez une variable en dehors d’une méthode, elle est traitée comme une variable de classe. Mettre à jour le Voiture classe comme suit:

    1 classe Voiture:
    2 
    3     roues = 0
    4 
    5     def __init__(soi, Couleur, modèle, année):
    6         soi.Couleur = Couleur
    7         soi.modèle = modèle
    8         soi.année = année

Cela change votre façon d'utiliser la variable roues. Au lieu de s'y référer à l'aide d'un objet, vous vous y référez en utilisant le nom de la classe:

>>>

    1 >>> importation voiture
    2 >>> ma voiture = voiture.Voiture("jaune", "scarabée", 1967)
    3 >>> impression(F"Ma voiture est ma_car.color")
    4 Ma voiture est jaune
    5 
    6 >>> impression(F"Il a car.Car.wheels    roues")
    7 Il a 0 roues
    8 
    9 >>> impression(F"Il a my_car.wheels    roues")
dix Il a 0 roues

Vous pouvez vous référer à mes roues ou voiture. roues, mais fais attention. Changer la valeur de la variable d'instance mes roues ne changera pas la valeur de la variable de classe voiture. roues:

>>>

    1 >>> de voiture importation *
    2 >>> ma voiture = voiture.Voiture("jaune", "Scarabée", "1966")
    3 >>> mon_autre_car = voiture.Voiture("rouge", "corvette", "1999")
    4 
    5 >>> impression(F"Ma voiture est ma_car.color")
    6 Ma voiture est jaune
    7 >>> impression(F"Il a my_car.wheels    roues")
    8 Il a 0 roues
    9 
dix >>> impression(F"Mon autre voiture est my_other_car.color")
11 Mon autre voiture est rouge
12 >>> impression(F"Il a my_other_car.wheels    roues")
13 Il a 0 roues
14 
15 >>> # Changer la valeur de la variable de classe
16 ... voiture.Voiture.roues = 4
17 
18 >>> impression(F"Ma voiture a my_car.wheels    roues")
19 Ma voiture a 4 roues
20 >>> impression(F"Mon autre voiture a my_other_car.wheels    roues")
21 Mon autre voiture a 4 roues
22 
23 >>> # Changer la valeur de la variable d'instance pour my_car
24 ... ma voiture.roues = 5
25 
26 >>> impression(F"Ma voiture a my_car.wheels    roues")
27 Ma voiture a 5 roues
28 >>> impression(F"Mon autre voiture a my_other_car.wheels    roues")
29 Mon autre voiture a 4 roues

Vous définissez deux Voiture objets sur les lignes 2 et 3:

  1. ma voiture
  2. mon_autre_car

Au début, les deux ont zéro roues. Lorsque vous définissez la variable de classe à l'aide de car.Car.wheels = 4 sur la ligne 16, les deux objets ont maintenant quatre roues. Cependant, lorsque vous définissez la variable d’instance à l’aide de my_car.wheels = 5 à la ligne 24, seul cet objet est affecté.

Cela signifie qu'il existe maintenant deux copies différentes du roues attribut:

  1. Une variable de classe qui s'applique à tous Voiture objets
  2. Une variable d'instance spécifique applicable à la ma voiture objet seulement

Il n’est pas difficile de se référer accidentellement au mauvais et d’introduire des bugs subtils.

L’équivalent de Java à un attribut de classe est un statique attribut:

Publique classe Voiture 
    privé Chaîne Couleur;
    privé Chaîne modèle;
    privé int année;
    privé statique int roues;

    Publique Voiture(Chaîne Couleur, Chaîne modèle, int année) 
        ce.Couleur = Couleur;
        ce.modèle = modèle;
        ce.année = année;
    

    Publique statique int getWheels() 
        revenir roues;
    

    Publique statique vide setWheels(int compter) 
        roues = compter;
    

Normalement, vous faites référence à des variables statiques à l'aide du nom de la classe Java. Vous pouvez faire référence à une variable statique via une instance de classe telle que Python, mais ce n’est pas une pratique recommandée.

Votre classe Java devient longue. L'une des raisons pour lesquelles Java est plus détaillé que Python est la notion de méthodes et d'attributs publics et privés.

Publique et privée

Java contrôle l’accès aux méthodes et aux attributs en différenciant les Publique données et privé Les données.

En Java, les attributs sont censés être déclarés comme privé, ou protégé si les sous-classes doivent y avoir un accès direct. Cela limite l'accès à ces attributs à partir du code en dehors de la classe. Donner accès à privé attributs, vous déclarez Publique méthodes qui définissent et récupèrent des données de manière contrôlée (plus sur cela plus tard).

Rappelez-vous de votre classe Java ci-dessus que le Couleur la variable a été déclarée comme privé. Par conséquent, ce code Java affichera une erreur de compilation à la ligne en surbrillance:

Voiture ma voiture = Nouveau Voiture("bleu", "Gué", 1972)

// peindre la voiture
ma voiture.Couleur = "rouge";

Si vous ne spécifiez pas de niveau d'accès, l'attribut est défini par défaut sur paquet protégé, ce qui limite l’accès aux classes du même package. Vous devez marquer l'attribut comme Publique si vous voulez que ce code fonctionne.

Toutefois, la déclaration d'attributs publics n'est pas considérée comme une pratique recommandée en Java. Vous devez déclarer les attributs comme privé, et utilise Publique méthodes d'accès, telles que le .getColor () et .getModel () indiqué dans le code.

Python n’a pas la même notion de privé ou protégé données que Java fait. Tout en Python est Publique. Ce code fonctionne parfaitement avec votre classe Python existante:

>>>

>>> ma voiture = voiture.Voiture("bleu", "Gué", 1972)

>>> # Peindre la voiture
... ma voiture.Couleur = "rouge"

Au lieu de privé, Python a la notion de non public variable d'instance. Toute variable commençant par un caractère de soulignement est définie comme non publique. Cette convention de nommage rend plus difficile l’accès à une variable, mais c’est seulement une convention de nommage, et vous pouvez toujours accéder directement à la variable.

Ajoutez la ligne suivante à votre Python Voiture classe:

classe Voiture:

    roues = 0

    def __init__(soi, Couleur, modèle, année):
        soi.Couleur = Couleur
        soi.modèle = modèle
        soi.année = année
        soi._cupholders = 6

Vous pouvez accéder au ._cupholders variable directement:

>>>

>>> importation voiture
>>> ma voiture = voiture.Voiture("jaune", "Scarabée", "1969")
>>> impression(F"Ce fut construit en mon_car.année")
Il a été construit en 1969
>>> ma voiture.année = 1966
>>> impression(F"Ce fut construit en mon_car.année")
Il a été construit en 1966
>>> impression(F"Il a my_car._cupholders    porte-gobelets ")
Il a 6 porte-gobelets.

Python vous permet d'accéder ._cupholders, mais des IDE tels que VS Code peuvent émettre un avertissement via des linters prenant en charge PEP 8. Pour plus d’informations sur PEP 8, lisez Comment écrire un code Python magnifique avec PEP 8.

Voici le code dans VS Code, avec un avertissement en surbrillance et affiché:

Linter soulignant un problème PEP8 en Python.

Python reconnaît en outre l’utilisation de caractères de soulignement double devant une variable pour dissimuler un attribut en Python. Lorsque Python voit une variable à double trait de soulignement, il modifie le nom de la variable en interne pour rendre l'accès direct difficile. Ce mécanisme évite les accidents mais n’empêche pas l’accès aux données.

Pour montrer ce mécanisme en action, changez le code Python Voiture classe à nouveau:

classe Voiture:

    roues = 0

    def __init__(soi, Couleur, modèle, année):
        soi.Couleur = Couleur
        soi.modèle = modèle
        soi.année = année
        soi.__cupholders = 6

Maintenant, lorsque vous essayez d'accéder à la .__ porte-gobelets variable, vous voyez l'erreur suivante:

>>>

>>> importation voiture
>>> ma voiture = voiture.Voiture("jaune", "Scarabée", "1969")
>>> impression(F"Ce fut construit en mon_car.année")
Il a été construit en 1969
>>> ma voiture.année = 1966
>>> impression(F"Ce fut construit en mon_car.année")
Il a été construit en 1966
>>> impression(F"Il a mon_car .__ porte-gobelets    porte-gobelets ")
Traceback (dernier appel le plus récent):
        Fichier "", ligne 1, dans 
AttributeError: L'objet 'Car' n'a pas d'attribut '__cupholders'

Alors pourquoi ne pas le .__ porte-gobelets attribut existe?

Lorsque Python voit un attribut avec des traits de soulignement doubles, il le modifie en préfixant le nom d'origine de l'attribut d'un trait de soulignement, suivi du nom de la classe. Pour utiliser l'attribut directement, vous devez également modifier le nom que vous utilisez:

>>>

>>> impression(F"Il a my_car._Car__cupholders    porte-gobelets ")
Il a 6 porte-gobelets

Lorsque vous utilisez des traits de soulignement doubles pour dissimuler un attribut à l'utilisateur, Python modifie le nom de manière bien documentée. Cela signifie qu'un développeur déterminé peut toujours accéder directement à l'attribut.

Donc, si vos attributs Java sont déclarés privéet vos attributs Python sont précédés de doubles soulignements. Comment pouvez-vous fournir et contrôler l’accès aux données qu’ils stockent?

Contrôle d'accès

En Java, vous accédez à privé attributs en utilisant les setters et Getters. Pour permettre aux utilisateurs de peindre leurs voitures, ajoutez le code suivant à votre classe Java:

Publique Chaîne getColor() 
    revenir Couleur;


Publique Chaîne setColor(Chaîne Couleur) 
    ce.Couleur = Couleur;

Puisque .getColor () et .setColor () sont Publique, tout le monde peut les appeler pour changer ou récupérer la couleur de la voiture. Les meilleures pratiques de Java en matière d’utilisation privé attributs accessibles avec Publique Getters et les setters C’est l’une des raisons pour lesquelles le code Java a tendance à être plus détaillé que Python.

Comme vous l'avez vu ci-dessus, vous accédez aux attributs directement en Python. Puisque tout est Publique, vous pouvez accéder à tout, à tout moment et de n’importe où. Vous définissez et obtenez les valeurs d'attribut directement en vous référant à leurs noms. Vous pouvez même supprimer des attributs en Python, ce qui n’est pas possible en Java:

>>>

>>> ma voiture = Voiture("jaune", "scarabée", 1969)
>>> impression(F"Ma voiture a été construite en mon_car.année")
Ma voiture a été construite en 1969
>>> ma voiture.année = 1966
>>> impression(F"Ce fut construit en mon_car.année")
Il a été construit en 1966
>>> del ma voiture.année
>>> impression(F"Ce fut construit en mon_car.année")
Traceback (dernier appel le plus récent):
  Fichier "", ligne 1, dans 
AttributeError: L'objet 'voiture' n'a pas d'attribut 'année'

Cependant, il peut arriver que vous souhaitiez contrôler l'accès à un attribut. Dans ce cas, vous pouvez utiliser les propriétés Python.

En python, Propriétés fournit un accès contrôlable aux attributs de classe à l'aide de la syntaxe Python Decorator. (Vous pouvez en apprendre davantage sur les décorateurs dans le cours vidéo Python Decorators 101.) Les propriétés permettent de déclarer des fonctions dans des classes Python analogues aux méthodes Java getter et setter, avec l'avantage supplémentaire de vous permettre également de supprimer des attributs.

Vous pouvez voir le fonctionnement des propriétés en en ajoutant un à votre Voiture classe:

    1 classe Voiture:
    2     def __init__(soi, Couleur, modèle, année):
    3         soi.Couleur = Couleur
    4         soi.modèle = modèle
    5         soi.année = année
    6         soi._Tension = 12
    7 
    8     @propriété
    9     def Tension(soi):
dix         revenir soi._Tension
11 
12     @Tension.setter
13     def Tension(soi, volts):
14         impression("Attention: cela peut causer des problèmes!")
15         soi._Tension = volts
16 
17     @Tension.déleter
18     def Tension(soi):
19         impression("Attention: la radio cessera de fonctionner!")
20         del soi._Tension

Ici, vous développez la notion de Voiture inclure les véhicules électriques. Vous déclarez le ._Tension attribut de maintenir la tension de la batterie sur la ligne 6.

Pour fournir un accès contrôlé, vous définissez une fonction appelée Tension() renvoyer la valeur privée sur les lignes 9 et 10. En utilisant le @propriété décoration, vous le marquez comme un getter auquel tout le monde peut accéder directement.

De même, vous définissez une fonction de définition sur les lignes 13 à 15, également appelée Tension(). Cependant, vous décorez cette fonction avec @ tension.setter. Enfin, vous utilisez @ tension.deleter décorer un tiers Tension() lignes 18 à 20, ce qui permet une suppression contrôlée de l'attribut.

Les noms des fonctions décorées sont tous identiques, indiquant qu'ils contrôlent l'accès au même attribut. Les noms de fonction deviennent également le nom de l'attribut que vous utilisez pour accéder à la valeur. Voici comment ces propriétés fonctionnent dans la pratique:

>>>

    1 >>> de voiture importation *
    2 >>> ma voiture = Voiture("jaune", "scarabée", 1969)
    3 
    4 >>> impression(F"Ma voiture utilise mon_car.voltage    volts ")
    5 Ma voiture utilise 12 volts
    6 
    7 >>> ma voiture.Tension = 6
    8 Attention: cela peut causer des problèmes!
    9 
dix >>> impression(F"Ma voiture utilise maintenant mon_car.voltage    volts ")
11 Ma voiture utilise maintenant 6 volts
12 
13 >>> del ma voiture.Tension
14 Attention: la radio cessera de fonctionner!

Notez que vous utilisez .Tension dans les lignes en surbrillance ci-dessus, non ._Tension. Cela indique à Python d'utiliser les fonctions de propriété que vous avez définies:

  • Lorsque vous imprimez la valeur de mon_car.voltage sur la ligne 4, appels Python .Tension() décoré avec @propriété.
  • Lorsque vous attribuez une valeur à mon_car.voltage sur la ligne 7, appels Python .Tension() décoré avec @ tension.setter.
  • Lorsque vous supprimez mon_car.voltage à la ligne 13, appels Python .Tension() décoré avec @ tension.deleter.

le @propriété, @.setter, et @ .deleter les décorations permettent de contrôler l'accès aux attributs sans obliger les utilisateurs à utiliser différentes méthodes. Vous pouvez même donner l’impression que les attributs sont des propriétés en lecture seule en omettant le mot @.setter et @ .deleter fonctions décorées.

soi et ce

En Java, une classe se réfère à elle-même avec ce référence:

Publique Chaîne setColor(Chaîne Couleur) 
    ce.Couleur = Couleur;

ce est implicite dans le code Java: il n’est normalement pas nécessaire de l’écrire, sauf s’il peut y avoir confusion entre deux variables portant le même nom.

Vous pouvez écrire le même setter de cette façon:

Publique Chaîne setColor(Chaîne nouvelle couleur) 
    Couleur = nouvelle couleur;

Puisque Voiture a un attribut nommé .Couleur, et il n’ya pas d’autre variable dans la portée portant le même nom, une référence à ce nom fonctionne. Nous avons utilisé ce dans le premier exemple, pour différencier l'attribut et le paramètre nommés Couleur.

En Python, le mot clé soi sert un but similaire. C’est ainsi que vous vous référez aux variables membres, mais contrairement à Java ce, c’est Champs obligatoires si vous voulez créer ou faire référence à un attribut membre:

classe Voiture:
    def __init__(soi, Couleur, modèle, année):
        soi.Couleur = Couleur
        soi.modèle = modèle
        soi.année = année
        soi._Tension = 12

    @propriété
    def Tension(soi):
        revenir soi._Tension

Python nécessite chacun soi dans le code ci-dessus. Chacun crée ou fait référence aux attributs. Si vous les omettez, alors Python créera une variable locale au lieu d'un attribut.

La différence entre comment vous utilisez soi et ce en Python et Java est due aux différences sous-jacentes entre les deux langages et à la façon dont ils nomment les variables et les attributs.

Méthodes et fonctions

Cette différence entre Python et Java réside dans le fait que Python a des fonctions et que Java n’en a pas.

En Python, le code suivant est parfaitement correct (et très commun):

>>>

>>> def dis salut():
...     impression("Salut!")
... 
>>> dis salut()
Salut!

Tu peux appeler dis salut() de partout, il est visible Cette fonction n'a aucune référence à soiqui indique qu’il s’agit d’une fonction globale et non d’une fonction de classe. Il ne peut ni modifier ni stocker de données dans aucune classe, mais peut utiliser des variables locales et globales.

En revanche, chaque ligne de code Java que vous écrivez appartient à une classe. Les fonctions ne peuvent pas exister en dehors d’une classe et, par définition, toutes les fonctions Java sont des méthodes. En Java, le plus proche d’une fonction pure est d’utiliser une méthode statique:

Publique classe Utils 
    statique vide Dis salut() 
        Système.en dehors.imprimer("Salut!")
    

Utils.SayHi () est appelable de n’importe où sans avoir au préalable créé une instance de Utils. Puisque vous pouvez appeler Dis salut() sans créer d’objet d’abord, le ce référence n'existe pas. Cependant, cela n’est toujours pas une fonction dans le sens où dis salut() est en Python.

Héritage et Polymorphisme

L'héritage et le polymorphisme sont deux concepts fondamentaux de la programmation orientée objet.

L'héritage permet aux objets de dériver des attributs et des fonctionnalités d'autres objets, créant ainsi une hiérarchie allant d'objets plus généraux à plus spécifiques. Par exemple, un Voiture et un Bateau sont les deux types spécifiques de Véhicules. Les objets peuvent hériter de leur comportement d'un seul objet parent ou de plusieurs objets parents. Ils sont alors appelés objets enfants.

Le polymorphisme permet à deux objets ou plus de se comporter comme un autre, ce qui leur permet d'être utilisés de manière interchangeable. Par exemple, si une méthode ou une fonction sait comment peindre un Véhicule objet, alors il peut aussi peindre un Voiture ou Bateau objet, car ils héritent de leurs données et comportement de la Véhicule.

Ces concepts fondamentaux de POO sont implémentés de manière tout à fait différente dans Python et Java.

Héritage

Python prend en charge l'héritage multiple ou la création de classes qui héritent du comportement de plusieurs classes parentes.

Pour voir comment cela fonctionne, mettez à jour le Voiture classe en la divisant en deux catégories, une pour les véhicules et une pour les appareils utilisant l’électricité:

classe Véhicule:
    def __init__(soi, Couleur, modèle):
        soi.Couleur = Couleur
        soi.modèle = modèle

classe Dispositif:
    def __init__(soi):
        soi._Tension = 12

classe Voiture(Véhicule, Dispositif):
    def __init__(soi, Couleur, modèle, année):
        Véhicule.__init__(soi, Couleur, modèle)
        Dispositif.__init__(soi)
        soi.année = année

    @propriété
    def Tension(soi):
        revenir soi._Tension

    @Tension.setter
    def Tension(soi, volts):
        impression("Attention: cela peut causer des problèmes!")
        soi._Tension = volts

    @Tension.déleter
    def Tension(soi):
        impression("Attention: la radio cessera de fonctionner!")
        del soi._Tension

UNE Véhicule est défini comme ayant .Couleur et .modèle les attributs. Puis un Dispositif est défini pour avoir un ._Tension attribut. Depuis l'original Voiture objet avait ces trois attributs, il peut être redéfini pour hériter à la fois du Véhicule et Dispositif Des classes. le Couleur, modèle, et _Tension les attributs feront partie de la nouvelle Voiture classe.

dans le .__ init __ () pour Voiture, vous appelez le .__ init __ () méthodes pour les deux classes parentes pour s’assurer que tout est initialisé correctement. Une fois terminé, vous pouvez ajouter toute autre fonctionnalité souhaitée à votre Voiture. Dans ce cas, vous ajoutez un .année attribut spécifique à Voiture objets, et méthodes getter et setter pour .Tension.

Fonctionnellement, le nouveau Voiture la classe se comporte comme elle l’a toujours fait. Vous créez et utilisez Voiture objets comme avant:

>>>

>>> de voiture importation *
>>> ma voiture = Voiture("jaune", "scarabée", 1969)

>>> impression(F"Ma voiture est ma_car.color")
Ma voiture est jaune

>>> impression(F"Ma voiture utilise mon_car.voltage    volts ")
Ma voiture utilise 12 volts

>>> ma voiture.Tension = 6
Attention: cela peut causer des problèmes!

>>> impression(F"Ma voiture utilise maintenant mon_car.voltage    volts ")
Ma voiture utilise maintenant 6 volts

En revanche, Java ne prend en charge qu'un seul héritage, ce qui signifie que les classes en Java peuvent hériter des données et du comportement d'une seule classe parente. Cependant, les objets Java peuvent hériter du comportement de nombreuses interfaces différentes. Les interfaces fournissent un groupe de méthodes associées qu'un objet doit implémenter et permettent à plusieurs classes enfant de se comporter de la même manière.

Pour voir cela en action, divisez le fichier Java Voiture classe dans une classe parent et un interface:

Publique classe Véhicule 

    privé Chaîne Couleur;
    privé Chaîne modèle;

    Publique Véhicule(Chaîne Couleur, Chaîne modèle) 
        ce.Couleur = Couleur;
        ce.modèle = modèle;
    

    Publique Chaîne getColor() 
        revenir Couleur;
    

    Publique Chaîne getModel() 
        revenir modèle;
    


Publique interface Dispositif 
    int getVoltage();


Publique classe Voiture s'étend Véhicule met en oeuvre Dispositif 

    privé int Tension;
    privé int année;

    Publique Voiture(Chaîne Couleur, Chaîne modèle, int année) 
        super(Couleur, modèle)
        ce.année = année;
        ce.Tension = 12;
    

    @Passer outre
    Publique int getVoltage() 
        revenir Tension;
    

    Publique int getYear() 
        revenir année;
    

Rappelez-vous que chaque classe et interface doit vivre dans son propre fichier.

Comme vous l'avez fait avec Python, vous créez une nouvelle classe appelée Véhicule contenir les données et les fonctionnalités plus générales relatives au véhicule. Cependant, pour ajouter le Dispositif fonctionnalité, vous devez créer un interface au lieu. Ce interface définit une méthode unique pour renvoyer la tension du Dispositif.

Redéfinir le Voiture la classe vous oblige à hériter de Véhicule en utilisant étendreet mettre en œuvre le Dispositif interface utilisant met en oeuvre. Dans le constructeur, vous appelez le constructeur de la classe parent à l’aide de la commande intégrée. super(). Puisqu'il n'y a qu'une seule classe parente, elle ne peut faire référence qu'à la Véhicule constructeur. Mettre en œuvre le interface, vous écrivez getVoltage () en utilisant le @Passer outre annotation.

Plutôt que d’obtenir la réutilisation du code de Dispositif comme Python l’a fait, Java exige que vous implémentiez la même fonctionnalité dans chaque classe qui implémente la interface. Les interfaces définissent uniquement les méthodes – elles ne peuvent pas définir de données d'instance ni de détails d'implémentation.

Alors, pourquoi est-ce le cas pour Java? Tout se résume aux types.

Types et polymorphisme

La vérification de type stricte de Java est ce qui motive sa interface conception.

Chaque classe et interface en Java est un type. Par conséquent, si deux objets Java implémentent le même interface, alors ils sont considérés comme étant du même type par rapport à celui interface. Ce mécanisme permet d'utiliser différentes classes de manière interchangeable, ce qui constitue la définition du polymorphisme.

Vous pouvez implémenter la charge de périphérique pour vos objets Java en créant un .charge() cela prend un Dispositif charger. Tout objet qui implémente le Dispositif l'interface peut être passée à .charge(). Cela signifie également que les classes qui ne mettent pas en œuvre Dispositif va générer une erreur de compilation.

Créez la classe suivante dans un fichier nommé Rhino.java:

Maintenant, vous pouvez créer un nouveau Main.java implémenter .charge() et explorer comment Voiture et Rhinocéros les objets diffèrent:

Publique classe Principale
    Publique statique vide charge(Dispositif dispositif) 
       dispositif.getVoltage();
    

    Publique statique vide principale(Chaîne[] args) jette Exception 
        Voiture voiture = Nouveau Voiture("jaune", "scarabée", 1969)
        Rhinocéros rhinocéros = Nouveau Rhinocéros();
        charge(voiture)
        charge(rhinocéros)
    

Voici ce que vous devriez voir lorsque vous essayez de générer ce code:

Information: 2019-02-02 15:20 - Compilation complétée avec
    1 erreur et 0 avertissements en 4 secondes 395 ms
Main.java
Erreur: (43, 11) java: types incompatibles: Rhino ne peut pas être converti en périphérique.

Depuis le Rhinocéros classe n'implémente pas le Dispositif interface, il ne peut pas être passé dans .charge().

Contrairement au typage strict des variables Java, Python utilise un concept appelé frappe de canardCela signifie en gros que si une variable «marche comme un canard et que les charlatans sont comme un canard, c’est un canard». Au lieu d’identifier les objets par type, Python examine leur comportement. Vous pouvez en apprendre davantage sur le système de types Python et la frappe à l'aide de canards dans le Guide ultime de la vérification des types Python.

Vous pouvez explorer la dactylographie à l'aide de canards en implémentant des fonctionnalités de chargement de périphérique similaires pour votre Python. Dispositif classe:

>>>

>>> def charge(dispositif):
...     si hasattr(dispositif, '_Tension'):
...         impression(F"Charge un device._voltage    appareil volt ")
...     autre:
...         impression(F"Je ne peux pas charger un device .__ class __.__ name__")
... 
>>> classe Téléphone(Dispositif):
...     passer
... 
>>> classe Rhinocéros:
...     passer
... 
>>> ma voiture = Voiture("jaune", "Scarabée", "1966")
>>> mon téléphone = Téléphone()
>>> mon_rhino = Rhinocéros()

>>> charge(ma voiture)
Chargement d'un appareil 12 volts
>>> charge(mon téléphone)
Chargement d'un appareil 12 volts
>>> charge(mon_rhino)
Je ne peux pas charger un rhinocéros

charge() doit vérifier l'existence du ._Tension attribut dans l’objet passé. Depuis le Dispositif classe définit cet attribut, toute classe qui en hérite (telle que Voiture et Téléphone) aura cet attribut et montrera donc qu'ils chargent correctement. Classes qui n'héritent pas de Dispositif (comme Rhinocéros) n’aura peut-être pas cet attribut et ne pourra pas charger (ce qui est bien, car charger des rhinocéros peut être dangereux).

Méthodes par défaut

Toutes les classes Java descendent du Objet class, qui contient un ensemble de méthodes héritées de toutes les autres classes. Les sous-classes peuvent les remplacer ou conserver les valeurs par défaut. le Objet La classe définit les méthodes suivantes:

classe Objet 
    booléen équivaut à(Objet obj)  ...     
    int hashCode()  ...     
    Chaîne toString()  ...     

Par défaut, équivaut à() compare les adresses du courant Objet avec une seconde Objet passé, et hashcode () calcule un identifiant unique qui utilise également l'adresse du courant Objet. Ces méthodes sont utilisées dans de nombreux contextes différents en Java. Par exemple, les classes utilitaires, telles que les collections qui trient les objets en fonction de la valeur, ont besoin des deux.

toString () renvoie un Chaîne représentation de la Objet. Par défaut, il s'agit du nom de la classe et de l'adresse. Cette méthode est appelée automatiquement quand un Objet est passé à une méthode qui nécessite un Chaîne argument, tel que System.out.println ():

Voiture voiture = Nouveau Voiture("jaune", "Scarabée", 1969)
Système.en dehors.imprimer(voiture)

L'exécution de ce code utilisera la valeur par défaut .toString () pour montrer le voiture objet:

Pas très utile, non? Vous pouvez améliorer cela en remplaçant la valeur par défaut. .toString (). Ajouter cette méthode à votre Java Voiture classe:

Publique Chaîne toString() 
    revenir "Voiture: " + getColor() + ":" + getModel() + ":" + getYear();

Lorsque vous exécutez le même exemple de code, les éléments suivants s’affichent:

Voiture: jaune: coléoptère: 1969

Python fournit des fonctionnalités similaires avec un ensemble de méthodes courantes dunder (abréviation de «double soulignement»). Chaque classe Python hérite de ces méthodes et vous pouvez les remplacer pour modifier leur comportement.

Pour les représentations sous forme de chaîne d'un objet, Python fournit __repr __ () et __str __ (), que vous pouvez en apprendre davantage sur la conversion de chaîne de programmation Pythonic OOP: __repr__ contre __str__. La représentation non ambiguë d’un objet est renvoyée par __repr __ (), tandis que __str __ () renvoie une représentation lisible par l'homme. Ce sont à peu près analogues à .hashcode () et .toString () en Java.

Comme Java, Python fournit des implémentations par défaut de ces méthodes dunder:

>>>

>>> ma voiture = Voiture("jaune", "Scarabée", "1966")

>>> impression(repr(ma voiture))

>>> impression(str(ma voiture))

Vous pouvez améliorer ce résultat en remplaçant .__ str __ (), l'ajoutant à votre Python Voiture classe:

def __str__(soi):
    revenir F'Voiture self.color    : self.model    : self.year'

Cela vous donne un bien meilleur résultat:

>>>

>>> ma voiture = Voiture("jaune", "Scarabée", "1966")

>>> impression(repr(ma voiture))

>>> impression(str(ma voiture))
Voiture jaune: Coléoptère: 1966

Déroger à la méthode dunder nous a donné une représentation plus lisible de votre Voiture. Vous voudrez peut-être remplacer le .__ repr __ () aussi, comme il est souvent utile pour le débogage.

Python offre beaucoup plus de méthodes de décompression. À l'aide des méthodes dunder, vous pouvez définir le comportement de votre objet lors de l'itération, de la comparaison, de l'ajout ou de la possibilité d'appeler directement un objet, entre autres.

Surcharge de l'opérateur

La surcharge d'opérateurs consiste à redéfinir le fonctionnement des opérateurs Python lorsqu'ils opèrent sur des objets définis par l'utilisateur. Les méthodes dunder de Python vous permettent d’implémenter la surcharge d’opérateurs, ce que Java ne propose pas du tout.

Modifiez votre Python Voiture classe avec les méthodes supplémentaires suivantes dunder:

classe Voiture:
    def __init__(soi, Couleur, modèle, année):
        soi.Couleur = Couleur
        soi.modèle = modèle
        soi.année = année

    def __str__(soi):
        revenir F'Voiture self.color    : self.model    : self.year'

    def __eq__(soi, autre):
        revenir soi.année == autre.année

    def __lt__(soi, autre):
        revenir soi.année < autre.année

    def __ajouter__(soi, autre):
        revenir Voiture(soi.Couleur + autre.Couleur, 
                   soi.modèle + autre.modèle, 
                   int(soi.année) + int(autre.année))

Le tableau ci-dessous montre la relation entre ces méthodes dunder et les opérateurs Python qu’elles représentent:

Méthode Dunder Opérateur Objectif
__eq__ == Est-ce que ces Voiture les objets ont la même année?
__lt__ < Lequel Voiture est un modèle antérieur?
__ajouter__ + Ajouter deux Voiture objets d'une manière absurde

Lorsque Python voit une expression contenant des objets, il appelle toutes les méthodes dunder définies qui correspondent aux opérateurs de l'expression. Le code ci-dessous utilise ces nouveaux opérateurs arithmétiques surchargés sur quelques Voiture objets:

>>>

>>> ma voiture = Voiture("jaune", "Scarabée", "1966")
>>> ta voiture = Voiture("rouge", "Corvette", "1967")

>>> impression (ma voiture < ta voiture)
Vrai
>>> impression (ma voiture > ta voiture)
Faux
>>> impression (ma voiture == ta voiture)
Faux
>>> impression (ma voiture + ta voiture)
Voiture yellowred: BeetleCorvette: 3933 

Il existe de nombreux autres opérateurs que vous pouvez surcharger à l’aide des méthodes dunder. Elles offrent un moyen d’enrichir le comportement de votre objet de la même manière que les méthodes par défaut de la classe de base commune de Java.

Réflexion

La réflexion consiste à examiner un objet ou une classe à partir de l'objet ou de la classe. Java et Python offrent tous deux des moyens d'explorer et d'examiner les attributs et les méthodes d'une classe.

Examiner le type d’un objet

Les deux langues disposent de méthodes pour tester ou vérifier le type d'un objet.

En Python, vous utilisez type() pour afficher le type d'une variable, et isinstance () pour déterminer si une variable donnée est une instance ou un enfant d'une classe spécifique:

>>>

>>> ma voiture = Voiture("jaune", "Scarabée", "1966")

>>> impression(type(ma voiture))

>>> impression(isinstance(ma voiture, Voiture))
Vrai
>>> impression(isinstance(ma voiture, Dispositif))
Vrai

En Java, vous interrogez l'objet de son type à l'aide de .getClass ()et utiliser le exemple de opérateur pour vérifier une classe spécifique:

Voiture voiture = Nouveau Voiture("jaune", "scarabée", 1969)

Système.en dehors.imprimer(voiture.getClass());
Système.en dehors.imprimer(voiture exemple de Voiture)

Ce code génère les éléments suivants:

classe com.realpython.Car
vrai

Examiner les attributs d’un objet

En Python, vous pouvez afficher tous les attributs et fonctions contenus dans n’importe quel objet (y compris toutes les méthodes dunder) en utilisant dir (). Pour obtenir les détails spécifiques d'un attribut ou d'une fonction donnés, utilisez getattr ():

>>>

>>> impression(dir(ma voiture))
['_Car__cupholders''__add__''__class__''__delattr__''__dict__'['_Car__cupholders''__add__''__class__''__delattr__''__dict__'['_Car__cupholders''__add__''__class__''__delattr__''__dict__'['_Car__cupholders''__add__''__class__''__delattr__''__dict__'
    '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__',
    '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__',
    '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
    '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__',
    '_voltage', 'couleur', 'modèle', 'tension', 'roues', 'année']

>>> impression(getattr(ma voiture, "__format__"))

Java dispose de fonctionnalités similaires, mais le contrôle d’accès et la sécurité de type du langage compliquent la récupération.

.getFields () récupère une liste de tous les attributs accessibles au public. Cependant, aucun des attributs de Voiture sont Publique, ce code retourne un tableau vide:

Champ[] des champs = voiture.getClass()getFields();

Java traite les attributs et les méthodes comme des entités distinctes, donc Publique les méthodes sont récupérées en utilisant .getDeclaredMethods (). Puisque Publique les attributs auront un correspondant .obtenir méthode, un moyen de déterminer si une classe contient une propriété spécifique pourrait ressembler à ceci:

  • Utilisation .getFields () générer un tableau de toutes les méthodes.
  • Boucle à travers toutes les méthodes retournées:
    • Pour chaque méthode découverte, renvoie true si la méthode:
      • Commence avec le mot obtenir OU accepte zéro argument
      • ET ne revient pas vide
      • ET inclut le nom de la propriété
    • Sinon, retourne false.

Voici un exemple simple et rapide:

    1 Publique statique booléen getProperty(String prénom, Objet objet) jette Exception 
    2 
    3     Method[] declaredMethods = objet.getClass().getDeclaredMethods();
    4     pour (Method méthode : declaredMethods) 
    5         si (isGetter(méthode) && 
    6             méthode.getName().toUpperCase().contient(prénom.toUpperCase())) 
    7               revenir vrai;
    8         
    9     
dix     revenir faux;
11 
12 
13 // Helper function to get if the method is a getter method
14 Publique statique booléen isGetter(Method méthode) 
15     si ((méthode.getName().startsWith("get") 

getProperty() is your entry point. Call this with the name of an attribute and an object. It returns vrai if the property is found, and faux if not.

Calling Methods Through Reflection

Both Java and Python provide mechanisms to call methods through reflection.

In the Java example above, instead of simply returning vrai if the property was found, you could call the method directly. Recall that getDeclaredMethods() returns an array of Method objets. le Method object itself has a method called .invoke(), which will call the Method. Instead of returning vrai when the correct method is found on line 7 above, you can return method.invoke(object) au lieu.

This capability exists in Python as well. However, since Python doesn’t differentiate between functions and attributes, you have to look specifically for entries that are appelable:

>>>

>>> pour method_name dans dir(my_car):
...     si appelable(getattr(my_car, method_name)):
...         impression(method_name)
... 
__add__
__class__
__delattr__
__dir__
__eq__
__format__
__ge__
__getattribute__
__gt__
__init__
__init_subclass__
__le__
__lt__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__

Python methods are simpler to manage and call than in Java. Adding the () operator (and any required arguments) is all you need to do.

The code below will find an object’s .__str__() and call it through reflection:

>>>

>>> pour method_name dans dir(my_car):
...     attr = getattr(my_car, method_name)
...     si appelable(attr):
...         si method_name == '__str__':
...             impression(attr())
... 
Car yellow : Beetle : 1966

Here, every attribute returned by dir() est vérifié. You get the actual attribute object using getattr(), and check if it’s a callable function using callable(). If so, you then check if its name is __str__(), and then call it.

Conclusion

Throughout the course of this article, you learned how object-oriented principles differ in Python vs Java. As you read, you:

  • Built a basic class in both Java and Python
  • Explored how object attributes work in Python vs Java
  • Compared and contrasted Java methods and Python functions
  • Discovered inheritance and polymorphism mechanisms in both languages
  • Investigated reflection across Python vs Java
  • Applied everything in a complete class implementation in both languages

If you want to learn more about OOP in Python, be sure to read Object-Oriented Programming (OOP) in Python 3.

Understanding the differences in Python vs Java when handling objects, and the syntax choices each language makes, will help you apply best practices and make your next project smoother.

In order to compare some concrete examples side by side, you can download our sample code to get the complete commented object definitions for the Java Voiture, Device, et Véhicule classes, as well as the complete commented definitions for the Python Voiture et Véhicule classes: