Table des matières
1. Comprendre le polymorphisme en Java (notez qu'il est différent du C++)
2. is-a关系和is-like-a关系
3. 运行时类型信息(RTTI + 反射)
Maison Java javaDidacticiel Compréhension des idées de programmation Java

Compréhension des idées de programmation Java

Jun 25, 2017 am 10:52 AM
java 思想 笔记 编程

Les idées de programmation Java, un classique incontournable pour l'apprentissage Java, valent la peine d'être lu que vous soyez débutant ou expert. Voici un résumé des connaissances clés du livre. Ces connaissances n'apparaissent pas seulement souvent dans l'examen écrit. des entretiens avec de grandes entreprises bien connues, mais aussi des connaissances couramment utilisées dans le développement de projets à grande échelle, y compris des questions simples de compréhension conceptuelle (telles que la différence entre est-une relation et a-une relation), ainsi que dans- connaissance approfondie de la décompilation sous-jacente RTTI et JVM.


1. Comprendre le polymorphisme en Java (notez qu'il est différent du C++)

  • En plus des méthodes statiques et des méthodes finales en Java (les méthodes privées sont essentiellement des méthodes finales car elles ne sont pas accessibles aux sous-classes), toutes les autres méthodes sont liées dynamiquement, ce qui signifie que généralement, nous n'avons pas à décider si la liaison dynamique doit être effectuée - cela se fera automatiquement.

    • La méthode finale permettra au compilateur de générer du code plus efficace, c'est pourquoi déclarer une méthode finale peut améliorer les performances dans une certaine mesure (l'effet n'est pas évident).

    • Si une méthode est statique, son comportement n'est pas polymorphe :

      class StaticSuper {public static String staticGet() {return "Base staticGet()";}public String dynamicGet() {return "Base dynamicGet()";}}class StaticSub extends StaticSuper {public static String staticGet() {return "Derived staticGet()";}public String dynamicGet() {return "Derived dynamicGet()";}}public class StaticPolymorphism {public static void main(String[] args) {StaticSuper sup = new StaticSub();System.out.println(sup.staticGet());System.out.println(sup.dynamicGet());}}
      Copier après la connexion


      Sortie :

      Base staticGet()
      Derived DynamicGet()

  • Le constructeur n'est pas polymorphe, ce sont en fait des méthodes statiques, mais les méthodes statiques la déclaration est implicite. Par conséquent, les constructeurs ne peuvent pas être remplacés.

  • L'appel d'une fonction avec un comportement polymorphe à l'intérieur du constructeur de la classe parent conduira à des résultats imprévisibles, car l'objet de la sous-classe n'a pas été initialisé à ce moment, et l'appel de la méthode de la sous-classe à ce moment entraînera pas obtenir les résultats souhaités.

    class Glyph {void draw() {System.out.println("Glyph.draw()");}Glyph() {System.out.println("Glyph() before draw()");draw();System.out.println("Glyph() after draw()");}}class RoundGlyph extends Glyph {private int radius = 1;RoundGlyph(int r) {radius = r;System.out.println("RoundGlyph.RoundGlyph(). radius = " + radius);}void draw() {System.out.println("RoundGlyph.draw(). radius = " + radius);}}public class PolyConstructors {public static void main(String[] args) {new RoundGlyph(5);}}
    Copier après la connexion


    Sortie :

    Glyph() avant draw()
    RoundGlyph.draw() rayon = 0Glyph() après draw()
    RoundGlyph.RoundGlyph() radius = 5

Pourquoi est-il affiché comme ça ? Cela nécessite une compréhension claire de la séquence d'appel des constructeurs en Java :

(1) Avant que quoi que ce soit d'autre ne se produise, initialisez l'espace de stockage alloué à l'objet au binaire 0

(2) Appelez ; le constructeur de la classe de base. Récursif à partir de la racine, car le polymorphisme appelle la méthode draw() couverte par la sous-classe à ce moment (à appeler avant d'appeler le constructeur RoundGlyph). En raison de l'étape 1, nous constaterons que la valeur du rayon est 0 à ce moment-là ;
(3) Appelez la méthode d'initialisation des membres dans l'ordre de déclaration ;
(4) Enfin, appelez le constructeur de la sous-classe.

  • Seules les méthodes non privées peuvent être remplacées, mais vous devez prêter une attention particulière au phénomène de substitution des méthodes privées, bien que le compilateur ne signale pas d'erreur pour le moment. , il ne suivra pas nos instructions. Ce qui est attendu, c'est que le remplacement de la méthode privée en fera une nouvelle méthode pour la sous-classe plutôt qu'une méthode surchargée. Par conséquent, dans les sous-classes, il est préférable de ne pas avoir le même nom que la méthode privée de la classe de base (même si cela n'a pas d'importance, il est facile de se tromper en pensant qu'elle peut remplacer la méthode privée de la classe de base).

  • Les opérations d'accès aux champs d'attributs dans les classes Java sont analysées par le compilateur, elles ne sont donc pas polymorphes. Les attributs portant le même nom dans le parent et les sous-classes alloueront différents espaces de stockage, comme suit :

    // Direct field access is determined at compile time.class Super {public int field = 0;public int getField() {return field;}}class Sub extends Super {public int field = 1;public int getField() {return field;}public int getSuperField() {return super.field;}}public class FieldAccess {public static void main(String[] args) {Super sup = new Sub();System.out.println("sup.filed = " + sup.field + ", sup.getField() = " + sup.getField());Sub sub = new Sub();System.out.println("sub.filed = " + sub.field + ", sub.getField() = " + sub.getField() + ", sub.getSuperField() = " + sub.getSuperField());}}
    Copier après la connexion


    输出:

    sup.filed = 0, sup.getField() = 1
    sub.filed = 1, sub.getField() = 1, sub.getSuperField() = 0

    Sub子类实际上包含了两个称为field的域,然而在引用Sub中的field时所产生的默认域并非Super版本的field域,因此为了得到Super.field,必须显式地指明super.field。

    2. is-a关系和is-like-a关系

    • is-a关系属于纯继承,即只有在基类中已经建立的方法才可以在子类中被覆盖,如下图所示:

      基类和子类有着完全相同的接口,这样向上转型时永远不需要知道正在处理的对象的确切类型,这通过多态来实现。

    • is-like-a关系:子类扩展了基类接口。它有着相同的基本接口,但是他还具有由额外方法实现的其他特性。

      缺点就是子类中接口的扩展部分不能被基类访问,因此一旦向上转型,就不能调用那些新方法。

    3. 运行时类型信息(RTTI + 反射)

    • 概念
      RTTI:运行时类型信息使得你可以在程序运行时发现和使用类型信息。

    • 使用方式
      Java是如何让我们在运行时识别对象和类的信息的,主要有两种方式(还有辅助的第三种方式,见下描述):

      • 一种是“传统的”RTTI,它假定我们在编译时已经知道了所有的类型,比如Shape s = (Shape)s1;

      • 另一种是“反射”机制,它运行我们在运行时发现和使用类的信息,即使用Class.forName()

      • 其实还有第三种形式,就是关键字instanceof,它返回一个bool值,它保持了类型的概念,它指的是“你是这个类吗?或者你是这个类的派生类吗?”。而如果用==或equals比较实际的Class对象,就没有考虑继承—它或者是这个确切的类型,或者不是。

    • 工作原理
      要理解RTTI在Java中的工作原理,首先必须知道类型信息在运行时是如何表示的,这项工作是由称为Class对象的特殊对象完成的,它包含了与类有关的信息。Java送Class对象来执行其RTTI,使用类加载器的子系统实现。

    无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用,获取方式有三种:
    (1)如果你没有持有该类型的对象,则Class.forName()就是实现此功能的便捷途,因为它不需要对象信息;
    (2)如果你已经拥有了一个感兴趣的类型的对象,那就可以通过调用getClass()方法来获取Class引用了,它将返回表示该对象的实际类型的Class引用。Class包含很有有用的方法,比如:

    package rtti;interface HasBatteries{}interface WaterProof{}interface Shoots{}class Toy {Toy() {}Toy(int i) {}}class FancyToy extends Toyimplements HasBatteries, WaterProof, Shoots {FancyToy() {super(1);}}public class RTTITest {static void printInfo(Class cc) {System.out.println("Class name: " + cc.getName() + ", is interface? [" + cc.isInterface() + "]");System.out.println("Simple name: " + cc.getSimpleName());System.out.println("Canonical name: " + cc.getCanonicalName());}public static void main(String[] args) {Class c = null;try {c = Class.forName("rtti.FancyToy"); // 必须是全限定名(包名+类名)} catch(ClassNotFoundException e) {System.out.println("Can't find FancyToy");System.exit(1);}printInfo(c);for(Class face : c.getInterfaces()) {printInfo(face);}Class up = c.getSuperclass();Object obj = null;try {// Requires default constructor.obj = up.newInstance();} catch (InstantiationException e) {System.out.println("Can't Instantiate");System.exit(1);} catch (IllegalAccessException e) {System.out.println("Can't access");System.exit(1);}printInfo(obj.getClass());}}
    Copier après la connexion


    输出:

    Class name: rtti.FancyToy, is interface? [false]
    Simple name: FancyToy
    Canonical name: rtti.FancyToy
    Class name: rtti.HasBatteries, is interface? [true]
    Simple name: HasBatteries
    Canonical name: rtti.HasBatteries
    Class name: rtti.WaterProof, is interface? [true]
    Simple name: WaterProof
    Canonical name: rtti.WaterProof
    Class name: rtti.Shoots, is interface? [true]
    Simple name: Shoots
    Canonical name: rtti.Shoots
    Class name: rtti.Toy, is interface? [false]
    Simple name: Toy
    Canonical name: rtti.Toy

    (3)Java还提供了另一种方法来生成对Class对象的引用,即使用类字面常量。比如上面的就像这样:FancyToy.class;来引用。
    这样做不仅更简单,而且更安全,因为它在编译时就会受到检查(因此不需要置于try语句块中),并且它根除了对forName方法的引用,所以也更高效。类字面常量不仅可以应用于普通的类,也可以应用于接口、数组以及基本数据类型。


    注意:当使用“.class”来创建对Class对象的引用时,不会自动地初始化该Class对象,初始化被延迟到了对静态方法(构造器隐式的是静态的)或者非final静态域(注意final静态域不会触发初始化操作)进行首次引用时才执行:。而使用Class.forName时会自动的初始化。

    为了使用类而做的准备工作实际包含三个步骤:
    - 加载:由类加载器执行。查找字节码,并从这些字节码中创建一个Class对象
    - 链接:验证类中的字节码,为静态域分配存储空间,并且如果必需的话,将解析这个类创建的对其他类的所有引用。
    - 初始化:如果该类具有超类,则对其初始化,执行静态初始化器和静态初始化块。

    这一点非常重要,下面通过一个实例来说明这两者的区别:

    package rtti;import java.util.Random;class Initable {static final int staticFinal = 47;static final int staticFinal2 = ClassInitialization.rand.nextInt(1000);static {System.out.println("Initializing Initable");}}class Initable2 {static int staticNonFinal = 147;static {System.out.println("Initializing Initable2");}}class Initable3 {static int staticNonFinal = 74;static {System.out.println("Initializing Initable3");}}public class ClassInitialization {public static Random rand = new Random(47);public static void main(String[] args) {// Does not trigger initializationClass initable = Initable.class;System.out.println("After creating Initable ref");// Does not trigger initializationSystem.out.println(Initable.staticFinal);// Does trigger initialization(rand() is static method)System.out.println(Initable.staticFinal2);// Does trigger initialization(not final)System.out.println(Initable2.staticNonFinal);try {Class initable3 = Class.forName("rtti.Initable3");} catch (ClassNotFoundException e) {System.out.println("Can't find Initable3");System.exit(1);}System.out.println("After creating Initable3 ref");System.out.println(Initable3.staticNonFinal);}}
    Copier après la connexion


    输出:

    After creating Initable ref
    47
    Initializing Initable
    258
    Initializing Initable2
    147
    Initializing Initable3
    After creating Initable3 ref
    74


    • RTTI的限制?如何突破? — 反射机制
      如果不知道某个对象的确切类型,RTTI可以告诉你,但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI识别它,也就是在编译时,编译器必须知道所有要通过RTTI来处理的类。

    可以突破这个限制吗?是的,突破它的就是反射机制。
    Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了FieldMethod以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()/set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()、getMethods()和getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确定下来,而在编译时不需要知道任何事情。


    ####反射与RTTI的区别
    当通过反射与一个未知类型的对象打交道时,JVM只是简单地检查这个对象,看它属于哪个特定的类(就像RTTI那样),在用它做其他事情之前必须先加载那个类的Class对象,因此,那个类的.class文件对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络取得。所以RTTI与反射之间真正的区别只在于:对RTTI来说,编译器在编译时打开和检查.class文件(也就是可以用普通方法调用对象的所有方法);而对于反射机制来说,.class文件在编译时是不可获取的,所以是在运行时打开和检查.class文件。

    下面的例子是用反射机制打印出一个类的所有方法(包括在基类中定义的方法):

    package typeinfo;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.util.regex.Pattern;// Using reflection to show all the methods of a class.// even if the methods are defined in the base class.public class ShowMethods {private static String usage = "usage: \n" + "ShowMethods qualified.class.name\n" +"To show all methods in class or: \n" +"ShowMethods qualified.class.name word\n" +"To search for methods involving 'word'";private static Pattern p = Pattern.compile("\\w+\\.");public static void main(String[] args) {if(args.length < 1) {System.out.println(usage);System.exit(0);}int lines = 0;try {Class<?> c = Class.forName(args[0]);Method[] methods = c.getMethods();Constructor[] ctors = c.getConstructors();if(args.length == 1) {for(Method method : methods) {System.out.println(p.matcher(method.toString()).replaceAll(""));}for(Constructor ctor : ctors) {System.out.println(p.matcher(ctor.toString()).replaceAll(""));}lines = methods.length + ctors.length;} else {for(Method method : methods) {if(method.toString().indexOf(args[1]) != -1) {
    Copier après la connexion

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn

Outils d'IA chauds

Undresser.AI Undress

Undresser.AI Undress

Application basée sur l'IA pour créer des photos de nu réalistes

AI Clothes Remover

AI Clothes Remover

Outil d'IA en ligne pour supprimer les vêtements des photos.

Undress AI Tool

Undress AI Tool

Images de déshabillage gratuites

Clothoff.io

Clothoff.io

Dissolvant de vêtements AI

Video Face Swap

Video Face Swap

Échangez les visages dans n'importe quelle vidéo sans effort grâce à notre outil d'échange de visage AI entièrement gratuit !

Outils chauds

Bloc-notes++7.3.1

Bloc-notes++7.3.1

Éditeur de code facile à utiliser et gratuit

SublimeText3 version chinoise

SublimeText3 version chinoise

Version chinoise, très simple à utiliser

Envoyer Studio 13.0.1

Envoyer Studio 13.0.1

Puissant environnement de développement intégré PHP

Dreamweaver CS6

Dreamweaver CS6

Outils de développement Web visuel

SublimeText3 version Mac

SublimeText3 version Mac

Logiciel d'édition de code au niveau de Dieu (SublimeText3)

Résolution de problèmes avec Python : débloquez des solutions puissantes en tant que codeur débutant Résolution de problèmes avec Python : débloquez des solutions puissantes en tant que codeur débutant Oct 11, 2024 pm 08:58 PM

Python permet aux débutants de résoudre des problèmes. Sa syntaxe conviviale, sa bibliothèque complète et ses fonctionnalités telles que les variables, les instructions conditionnelles et les boucles permettent un développement de code efficace. De la gestion des données au contrôle du flux du programme et à l'exécution de tâches répétitives, Python fournit

Break or Return of Java 8 Stream Forach? Break or Return of Java 8 Stream Forach? Feb 07, 2025 pm 12:09 PM

Java 8 présente l'API Stream, fournissant un moyen puissant et expressif de traiter les collections de données. Cependant, une question courante lors de l'utilisation du flux est: comment se casser ou revenir d'une opération FOREAK? Les boucles traditionnelles permettent une interruption ou un retour précoce, mais la méthode Foreach de Stream ne prend pas directement en charge cette méthode. Cet article expliquera les raisons et explorera des méthodes alternatives pour la mise en œuvre de terminaison prématurée dans les systèmes de traitement de flux. Lire plus approfondie: Améliorations de l'API Java Stream Comprendre le flux Forach La méthode foreach est une opération terminale qui effectue une opération sur chaque élément du flux. Son intention de conception est

La clé du codage : libérer la puissance de Python pour les débutants La clé du codage : libérer la puissance de Python pour les débutants Oct 11, 2024 pm 12:17 PM

Python est un langage d'introduction à la programmation idéal pour les débutants grâce à sa facilité d'apprentissage et ses fonctionnalités puissantes. Ses bases incluent : Variables : utilisées pour stocker des données (nombres, chaînes, listes, etc.). Type de données : Définit le type de données dans la variable (entier, virgule flottante, etc.). Opérateurs : utilisés pour les opérations mathématiques et les comparaisons. Flux de contrôle : contrôlez le flux d'exécution du code (instructions conditionnelles, boucles).

Libérez votre programmeur intérieur : C pour les débutants absolus Libérez votre programmeur intérieur : C pour les débutants absolus Oct 11, 2024 pm 03:50 PM

C est un langage idéal pour les débutants qui souhaitent apprendre la programmation, et ses avantages incluent l'efficacité, la polyvalence et la portabilité. L'apprentissage du langage C nécessite : Installer un compilateur C (tel que MinGW ou Cygwin) Comprendre les variables, les types de données, les instructions conditionnelles et les instructions de boucle Ecrire le premier programme contenant la fonction principale et la fonction printf() S'entraîner à travers des cas pratiques (comme le calcul de moyennes) Connaissance du langage C

Démystifier C : un chemin clair et simple pour les nouveaux programmeurs Démystifier C : un chemin clair et simple pour les nouveaux programmeurs Oct 11, 2024 pm 10:47 PM

C est un choix idéal pour les débutants qui souhaitent apprendre la programmation système. Il contient les composants suivants : fichiers d'en-tête, fonctions et fonctions principales. Un simple programme C capable d'imprimer "HelloWorld" a besoin d'un fichier d'en-tête contenant la déclaration de fonction d'entrée/sortie standard et utilise la fonction printf dans la fonction principale pour imprimer. Les programmes C peuvent être compilés et exécutés à l'aide du compilateur GCC. Après avoir maîtrisé les bases, vous pouvez passer à des sujets tels que les types de données, les fonctions, les tableaux et la gestion des fichiers pour devenir un programmeur C compétent.

Programme Java pour trouver le volume de la capsule Programme Java pour trouver le volume de la capsule Feb 07, 2025 am 11:37 AM

Les capsules sont des figures géométriques tridimensionnelles, composées d'un cylindre et d'un hémisphère aux deux extrémités. Le volume de la capsule peut être calculé en ajoutant le volume du cylindre et le volume de l'hémisphère aux deux extrémités. Ce tutoriel discutera de la façon de calculer le volume d'une capsule donnée en Java en utilisant différentes méthodes. Formule de volume de capsule La formule du volume de la capsule est la suivante: Volume de capsule = volume cylindrique volume de deux hémisphères volume dans, R: Le rayon de l'hémisphère. H: La hauteur du cylindre (à l'exclusion de l'hémisphère). Exemple 1 entrer Rayon = 5 unités Hauteur = 10 unités Sortir Volume = 1570,8 unités cubes expliquer Calculer le volume à l'aide de la formule: Volume = π × r2 × h (4

Créer l'avenir : programmation Java pour les débutants absolus Créer l'avenir : programmation Java pour les débutants absolus Oct 13, 2024 pm 01:32 PM

Java est un langage de programmation populaire qui peut être appris aussi bien par les développeurs débutants que par les développeurs expérimentés. Ce didacticiel commence par les concepts de base et progresse vers des sujets avancés. Après avoir installé le kit de développement Java, vous pouvez vous entraîner à la programmation en créant un simple programme « Hello, World ! ». Une fois que vous avez compris le code, utilisez l'invite de commande pour compiler et exécuter le programme, et « Hello, World ! » s'affichera sur la console. L'apprentissage de Java commence votre parcours de programmation et, à mesure que votre maîtrise s'approfondit, vous pouvez créer des applications plus complexes.

Comment exécuter votre première application Spring Boot dans Spring Tool Suite? Comment exécuter votre première application Spring Boot dans Spring Tool Suite? Feb 07, 2025 pm 12:11 PM

Spring Boot simplifie la création d'applications Java robustes, évolutives et prêtes à la production, révolutionnant le développement de Java. Son approche "Convention sur la configuration", inhérente à l'écosystème de ressort, minimise la configuration manuelle, allo

See all articles