Heim  >  Artikel  >  Java  >  Verständnis für Java-Programmierideen

Verständnis für Java-Programmierideen

零下一度
零下一度Original
2017-06-25 10:52:171612Durchsuche

Java-Programmierideen, ein Muss für das Java-Lernen. Es lohnt sich, es zu lesen, egal ob Sie ein Anfänger oder ein Experte sind. Dieses Wissen kommt nicht nur oft in der Schrift vor Testinterviews mit großen namhaften Unternehmen, aber auch Es ist auch häufig verwendetes Wissen in der Entwicklung großer Projekte, einschließlich einfacher Fragen zum konzeptionellen Verständnis (z. B. dem Unterschied zwischen „Ist-eine-Beziehung“ und „Hat-eine-Beziehung“) sowie in - Umfassendes Wissen über die zugrunde liegende Dekompilierung von RTTI und JVM.


1. Polymorphismus in Java verstehen (beachten Sie, dass er sich von C++ unterscheidet)

  • Zusätzlich zu statischen Methoden und endgültigen Methoden In Java (Private Methoden sind im Wesentlichen endgültige Methoden, da Unterklassen nicht auf sie zugreifen können) sind alle anderen Methoden dynamisch gebunden, was bedeutet, dass wir normalerweise nicht entscheiden müssen, ob eine dynamische Bindung durchgeführt werden soll – dies geschieht automatisch.

    • Die endgültige Methode sorgt dafür, dass der Compiler effizienteren Code generiert, weshalb die Deklaration einer endgültigen Methode die Leistung bis zu einem gewissen Grad verbessern kann (der Effekt ist nicht offensichtlich).

    • Wenn eine Methode statisch ist, ist ihr Verhalten nicht polymorph:

      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());}}


      Ausgabe:

      Basis staticGet()
      Abgeleitetes dynamischesGet()

  • Der Konstruktor ist nicht polymorph. Es handelt sich tatsächlich um statische Methoden, sondern um statische Methoden Die Deklaration ist implizit. Daher können Konstruktoren nicht überschrieben werden.

  • Der Aufruf einer Funktion mit polymorphem Verhalten im Konstruktor der übergeordneten Klasse führt zu unvorhersehbaren Ergebnissen, da das Unterklassenobjekt zu diesem Zeitpunkt noch nicht initialisiert wurde und der Aufruf der Unterklassenmethode zu diesem Zeitpunkt jedoch nicht initialisiert wurde nicht die gewünschten Ergebnisse erzielen.

    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);}}


    Ausgabe:

    Glyph() vor draw()
    RoundGlyph.draw() = 0
    Glyph() nach draw()
    RoundGlyph.RoundGlyph() = 5

Warum wird es so ausgegeben? Dies erfordert ein klares Verständnis der Aufrufsequenz von Konstruktoren in Java:

(1) Bevor etwas anderes passiert, initialisieren Sie den dem Objekt zugewiesenen Speicherplatz auf binär 0; (2) Aufruf der Basisklassenkonstruktor. Rekursion von der Wurzel aus, da der Polymorphismus zu diesem Zeitpunkt die von der Unterklasse abgedeckte Methode draw () aufruft (diese muss vor dem Aufruf des RoundGlyph-Konstruktors aufgerufen werden). Aufgrund von Schritt 1 werden wir feststellen, dass der Wert von radius zu diesem Zeitpunkt 0 ist.
(3) Rufen Sie die Mitgliedsinitialisierungsmethode in der Reihenfolge der Deklaration auf.
(4) Rufen Sie schließlich den Konstruktor der Unterklasse auf.

  • Nur ​​nicht private Methoden können überschrieben werden, Sie müssen jedoch genau auf das Phänomen des Überschreibens privater Methoden achten, obwohl der Compiler zu diesem Zeitpunkt keinen Fehler meldet , wird es unseren Anweisungen nicht folgen. Es wird erwartet, dass das Überschreiben der privaten Methode sie zu einer neuen Methode für die Unterklasse und nicht zu einer überladenen Methode macht. Daher ist es in Unterklassen am besten, nicht denselben Namen wie die private Methode der Basisklasse zu haben (obwohl dies keine Rolle spielt, kann es leicht zu Missverständnissen kommen, wenn man denkt, dass es die private Methode der Basisklasse überschreiben kann).

  • Zugriffsoperationen auf Attributfelder in Java-Klassen werden vom Compiler geparst, sind also nicht polymorph. Attribute mit demselben Namen in übergeordneten und untergeordneten Klassen weisen wie folgt unterschiedliche Speicherplätze zu:

    // 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());}}


    输出:

    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());}}


    输出:

    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);}}


    输出:

    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) {

Das obige ist der detaillierte Inhalt vonVerständnis für Java-Programmierideen. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn