Home >Java >javaTutorial >Detailed introduction and code examples of Java inheritance, polymorphism and class reuse

Detailed introduction and code examples of Java inheritance, polymorphism and class reuse

黄舟
黄舟Original
2017-03-14 11:47:342083browse


Abstract
This article combines the reuse of Java classes to object-orientedtwo major featuresInheritance and polymorphism are comprehensively introduced. First, we introduced the essence and significance of inheritance, and explored the similarities and differences between inheritance, composition, and proxy in terms of class reuse. Next, we introduced polymorphism based on inheritance and introduced its implementation mechanism and specific applications. In addition, to better understand inheritance and polymorphism, we provide a comprehensive introduction to the final keyword. On this basis, we introduced the loading and initialization sequence of classes in Java. Finally, we gave a detailed explanation of three very important concepts in object-oriented design -Overloading, covering and hiding.


Key points:

  • Inheritance

  • Composition, inheritance, proxy

  • Polymorphism

  • final keyword

  • ##Class loading and initialization sequence

  • Overloading, overriding and hiding

1. Inheritance

Inheritance is an indispensable part of all OOP languages. In java, use

extendsKeyword to indicate inheritance relationship. When a class is created, it is always inherited. If the class to be inherited is not explicitly stated, it is always inherited implicitly from the root class Object. If there is an inheritance relationship between two classes, the subclass will automatically inherit the methods and variables of the parent class, and the subclass can directly call the methods and variables of the parent class. It should be pointed out that in java, only allows single inheritance, that is, a class can only explicitly inherit from one parent class at most. However, A class can be inherited by multiple classes, that is, a class can have multiple subclasses. In addition, we need to pay special attention to the following points:

1.

Inheritance of member variables When a subclass inherits a certain class, it can be used Member variables in the parent class, but not all member variables of the parent class are completely inherited. The specific principles are as follows:

  • Subclasses can inherit the parent class’s public and protected member variables , cannot inherit the private member variables of the parent class, but can pass the corresponding getter/setter method of the parent class For access;

  • For the package access permission member variable of the parent class,If If the subclass and the parent class are in the same package, the subclass can inherit. Otherwise, the subclass cannot inherit;

  • For the parent that the subclass can inherit Class member variables,

    If a member variable with the same name appears in a subclass, the hidden phenomenon will occur, that is, the subclass The member variables of the parent class will block the member variables of the parent class with the same name. If you want to access a member variable with the same name in the parent class in a subclass, you need to use the super keyword to reference.


2.

Inheritance of member methods Similarly, when a subclass inherits a certain class, it You can use member methods in the parent class, but the subclass does not completely inherit all methods of the parent class. The specific principles are as follows:

  • Subclasses can inherit the parent class’s public and protected member methods , cannot inherit the private member method of the parent class;

  • For the parent class’s package access member methods,If the subclass and the parent class are in the same package, the subclass can inherit, otherwise, the subclass Cannot be inherited;

  • For parent class member methods that subclasses can inherit, If a member method with the same name appears in the subclass, it is called Overwriting means that the member methods of the subclass will override the member methods of the parent class with the same name. If you want to access a member method with the same name in the parent class in a subclass, you need to use the super keyword for reference.

Program example:

class Person {    
public String gentle = "Father";
}public class Student extends Person {

    public String gentle = "Son";    
    public String print(){        
    return super.gentle;       
    // 在子类中访问父类中同名成员变
    }    public static void main(String[] args) throws ClassNotFoundException {
        Student student = new Student();
        System.out.println("##### " + student.gentle);
        Person p = student;
        System.out.println("***** " + p.gentle);    //隐藏:编译时决定,不会发生多态

        System.out.println("----- " + student.print());
        System.out.println("----- " + p.print());    //Error:Person 中未定义该方法
    }
}/* Output:
        ##### Son
        ***** Father
        ----- Father
 *///:~

 Hiding and overwriting are different. Hiding is for member variables and static methods , and Override is for the ordinary method .


3. Initialization and constructor of base class

We know that the exported class is like an interface that has the same interface as the base classThe new class may also have some additional methods and fields. However, inheritance does not just copy the interface of the base class. When you create a derived class object, the object will contain a child object of the base class. This sub-object is the same as the object we create directly using the base class. The difference between the two is that the latter comes from the outside, while the sub-objects of the base class are wrapped inside the derived class object.
 
Therefore, The correct initialization of base class sub-objects is crucial, and Java also provides corresponding methods to ensure this: The derived class must call the base class constructor in the constructor to perform initialization, and the base class constructor has all the knowledge and capabilities required to perform base class initialization. When the base class contains a default constructor, Java will automatically insert a call to the default constructor of the base class in the constructor of the derived class, because the compiler does not have to consider what parameters to pass. However, If the parent class does not contain a default constructor, or the derived class wants to call a parent class constructor with parameters, then the super keyword must be used to explicitly call it in the constructor of the derived class. The constructor of the corresponding base class, and the calling statement must be the first statement of the derived class constructor.


2. Combination, inheritance, and proxy

In Java, Composition, inheritance, and proxy can all be used to implement code of reuse.

(1) Combination (has-a)

 By adding the existing class to the new class Objects can be combined. That is, the new class is composed of objects of existing classes. This technique is usually used when you want to use the functionality of an existing class in a new class rather than its interface. In other words, embed an object in a new class to allow it to achieve the required functions, but users of the new class only see the interface defined for the new class. rather than the interface of the embedded object.


(2) Inheritance (is-a)

 Inheritance allows us to Create new class. That is, we take the form of an existing class and add new code in it. Usually, This means that we are taking a general class and specializing it for some special need. Essentially, Both composition and inheritance allow placing sub-objects in a new class. Composition does this explicitly, while inheritance does it implicitly.


(3) Proxy (a golden mean between inheritance and composition: use the functions of existing classes like composition, and use them like inheritance at the same time Interface of existing class)

 Proxy is a golden mean between inheritance and composition,Java does not provide direct support for it . In a proxy, we place a member object in the class being constructed (like composition), but at the same time we expose the interface/methods of that member object in the new class (like inheritance ).
 
 Program example:

// 控制模块public class SpaceShipControls {
    void up(int velocity) {
    }    void down(int velocity) {
    }    void left(int velocity) {
    }    void right(int velocity) {
    }    void forward(int velocity) {
    }    void back(int velocity) {
    }    void turboBoost() {
    }
}

 The spacecraft needs a control module, then,Construction One way to do that with spaceships is to use inheritance:

public class SpaceShip extends SpaceShipControls { 
    private String name; 
    public SpaceShip(String name) { this.name = name; } 
    public String toString() { return name; } 
    public static void main(String[] args) { 
        SpaceShip protector = new SpaceShip("NSEA Protector"); 
        protector.forward(100); 
    } 
}

  然而,SpaceShip 并不是真正的 SpaceShipControls 类型,即便你可以“告诉” SpaceShip 向前运动(forward())。更准确的说,SpaceShip 包含 SpaceShipControls ,与此同时, SpaceShipControls 的所有方法在 SpaceShip 中都暴露出来。 代理(SpaceShip 的运动行为由 SpaceShipControls 代理完成) 正好可以解决这种问题:

// SpaceShip 的行为由 SpaceShipControls 代理完成public class SpaceShipDelegation { 
    private String name; 
    private SpaceShipControls controls = new SpaceShipControls(); 

    public SpaceShipDelegation(String name) { 
        this.name = name; 
    } 

    // 代理方法: 
    public void back(int velocity) { 
        controls.back(velocity); 
    } 
    public void down(int velocity) { 
        controls.down(velocity); 
    } 
    public void forward(int velocity) { 
        controls.forward(velocity); 
    } 
    public void left(int velocity) { 
        controls.left(velocity); 
    } 
    public void right(int velocity) { 
        controls.right(velocity); 
    } 
    public void turboBoost() { 
        controls.turboBoost(); 
    } 
    public void up(int velocity) { 
        controls.up(velocity); 
    } 

    public static void main(String[] args) { 
        SpaceShipDelegation protector = new SpaceShipDelegation("NSEA Protector"); 
        protector.forward(100); 
    } 
}

  实际上,我们使用代理时可以拥有更多的控制力,因为我们可以选择只提供在成员对象中方法的某个子集。


三. final关键字

  许多编程语言都需要某种方法来向编译器告知一块数据是恒定不变的。有时,数据的恒定不变是很有用的,比如:

  • 一个永不改变的编译时常量

  • 一个在运行时被初始化的值,而你不希望它被改变。

      对于编译期常量这种情况,编译器可以将该常量值带入任何可能用到它的计算式中,也即是说,可以在编译时执行计算式,这减轻了一些运行时负担。在Java中,这类常量必须满足两个条件:

  • 是基本类型,并且用final修饰;

  • 在对这个常量进行定义的时候,必须对其进行赋值。

      此外,当用final修饰对象引用时,final使其引用恒定不变。一旦引用被初始化指向一个对象,就无法再把它指向另一个对象。然而,对象本身是可以被修改的,这同样适用于数组,因为它也是对象。

      特别需要注意的是,我们不能因为某数据是final的,就认为在编译时就可以知道它的值。例如:

public class Test { 
    final int i4 = rand.nextInt(20);
}

1、空白final

  Java允许生成 空白final , 即:声明final但又未给定初值的域。但无论什么情况,编译器都会确保空白final在使用前被初始化。但是,空白final在关键字final的使用上提供了更大的灵活性: 一个类中的 final域 就可以做到根据对象而有所不同,却又保持其恒定不变的特性。例如,
         blank final.png-58.9kB
         
  必须在域的定义处或者每个构造器中使用表达式对final进行赋值,这正是 final域 在使用前总是被初始化的原因所在。


2、final参数

  final参数 主要应用于局部内部类和匿名内部类中,更多详细介绍请移步我的另一篇文章:Java 内部类综述。


3、final方法

  final关键字作用域方法时,用于锁定方法,以防任何继承类修改它的含义。这是出于设计的考虑:想要确保在继承中使方法行为保持不变,并且不会被覆盖。

  对于成员方法,只有在明确禁止覆盖时,才将方法设为final的。


4、final类

 When you define a class as final, it indicates that you do not intend to inherit the class, and you do not allow others to do so. In other words, For some reason, you never need to make any changes to the design of this class, or for security reasons, you don’t want it to have subclasses. kind.
 
It should be noted that the domain of the final class can be chosen to be final according to the actual situation. Regardless of whether it is defined as final, the same rules apply to fields that are defined as final. However, Since final classes prohibit inheritance, all methods in final classes are implicitly designated as final because they cannot be overridden. You can add final modification to methods in a final class, but this does not add any meaning.


5. Final and private

All private methods in the class are implicitly designated as final. Since the private method cannot be accessed, it cannot be overridden. You can add final modification to a private method, but this does not add any additional meaning to the method.
 
It is important to note that override will only appear when a method is part of the base class interface. If a method is private, it is not part of the base class interface, but is just some program code hidden in the class. But if a non-private method is generated with the same name in the exported class, we do not overwrite the method at this time, but only generate a new method. Since the private method is inaccessible and can be effectively hidden, there is no need to consider it in any other situation except that it exists due to the organizational structure of the class to which it belongs.


6. final and static

  • When static modifies a variable, itshas a default value, and can change , and its can only modify member variables and member methods.

  • A static final field only occupies a storage space that cannot be changed, and Initialization can only be done at declaration. Because it is final, it has no default value; and it is static, so it has been assigned a value when the class is not instantiated, so it can only be initialized when it is declared.


IV. Polymorphism

We know that inheritance allows an object to be treated as its own type or its base Types are processed so that the same code can run on these different types without any difference. Among them, polymorphic method calls allow one type to behave differently from other similar types, as long as these types are derived from the same base class. So, The role of polymorphism is mainly reflected in two aspects:

  • Polymorphism passes through separation What to do and how to do it, separate the interface and implementation from another perspective, so as to separate the changed things from the unchanged things;

  • Eliminate the coupling relationship between types (Similarly, in Java, generics are also used to eliminate the coupling relationship between classes or methods and the types used).


1. Implementation mechanism

We know that method overriding embodies polymorphism very well, but when using a base class reference to call an overriding method, which method should be called correctly?

Associating a method call with the same method body is called binding. If binding is performed before program execution, it is called early binding . However, obviously, this mechanism cannot solve the above problem, because the compiler does not know which object the above base class reference points to at compile time. The solution is late binding (dynamic binding/runtime binding): binding at runtime according to the specific type of the object.

In fact, in Java, except for static methods and final methods (private methods are final methods), all other methods are late binding. In this way, after a method is declared final, it can prevent others from overriding the method, but more importantly: doing so can effectively turn off dynamic binding, or in other words, tell the compiler that it does not need to be modified. Dynamic binding to generate more efficient code for final method calls.

Based on the dynamic binding mechanism, we can write code that only deals with base classes, and these codes can run correctly for all exported classes. In other words, Send a message to an object and let the object decide what to do.


2. Downcasting and runtime type identification

Since upcasting will lose specific type information, we may think, It should also be possible to obtain type information via downcasting. However, we know that upcasting is safe because the base class will not have a larger interface than the derived class. Therefore, the messages we send through the base class interface can be accepted, but for downward transformation, we cannot guarantee it.

To solve this problem, there must be some way to ensure the correctness of the downward cast so that we will not rashly cast to a wrong type and then send a message that the object cannot accept. In Java, the Runtime Type Identification (RTTI) mechanism can handle this problem, which ensures that all transformations in Java will be checked. So, even if we just perform an ordinary bracketed type conversion, it will still be checked when entering the runtime to ensure that it is indeed the type we want. If not, we get a type conversion exception: ClassCastException.


3. Polymorphic application examples


5. Class loading and initialization sequence

First of all, it must be pointed out that the class loading and initialization sequence is: Parent class static code block->Child class static Code block->Parent class non-static code block->Parent classConstructor->Subclass non-static code block->Subclass constructor

That is, first, initialize the static member variables and static code blocks in the parent class in the order they appear in the program; then, initialize the static member variables and static code blocks in the subclass in the order they appear in the program. Initialization; secondly, initialize the common member variables and code blocks of the parent class, and then execute the constructor method of the parent class; finally, initialize the common member variables and code blocks of the subclass, and then execute the constructor method of the subclass.

We explain through the following program:

class SuperClass {    private static String STR = "Super Class Static Variable";    static {
        System.out.println("Super Class Static Block:" + STR);
    }    public SuperClass() {
        System.out.println("Super Class Constructor Method");
    }

    {
        System.out.println("Super Class Block");
    }

}public class ObjectInit extends SuperClass {
    private static String STR = "Class Static Variable";    static {
        System.out.println("Class Static Block:" + STR);
    }    public ObjectInit() {
        System.out.println("Constructor Method");
    }

    {
        System.out.println("Class Block");
    }    public static void main(String[] args) {        @SuppressWarnings("unused")
        ObjectInit a = new ObjectInit();
    }
}/* Output:
        Super Class Static Block:Super Class Static Variable
        Class Static Block:Class Static Variable
        Super Class Block
        Super Class Constructor Method
        Class Block
        Constructor Method
 *///:~

  在运行该程序时,所发生的第一件事就是试图访问 ObjectInit.main() 方法(一个static方法),于是加载器开始启动并加载 ObjectInit类 。在对其加载时,编译器注意到它有一个基类(这由关键字extends得知),于是先进行加载其基类。如果该基类还有其自身的基类,那么先加载这个父父基类,如此类推(本例中是先加载 Object类 ,再加载 SuperClass类 ,最后加载 ObjectInit类 )。接下来,根基类中的 static域 和 static代码块 会被执行,然后是下一个导出类,以此类推这种方式很重要,因为导出类的static初始化可能会依赖于基类成员能否被正确初始化。到此为止,所有的类都已加载完毕,对象就可以创建了。首先,初始化根基类所有的普通成员变量和代码块,然后执行根基类构造器以便创建一个基对象,然后是下一个导出类,依次类推,直到初始化完成。


六. 重载、覆盖与隐藏

1、重载与覆盖

(1) 定义与区别

  重载如果在一个类中定义了多个同名的方法,但它们有不同的参数(包含三方面:参数个数、参数类型和参数顺序),则称为方法的重载。其中,不能通过访问权限、返回类型和抛出异常进行重载。
   
  覆盖子类中定义的某个方法与其父类中某个方法具有相同的方法签名(包含相同的名称和参数列表),则称为方法的覆盖。子类对象使用这个方法时,将调用该方法在子类中的定义,对它而言,父类中该方法的定义被屏蔽了。

  总的来说,重载和覆盖是Java多态性的不同表现。前者是一个类中多态性的一种表现,后者是父类与子类之间多态性的一种表现。


(2) 实现机制

  重载是一种参数多态机制,即通过方法参数的差异实现多态机制。并且,其属于一种 静态绑定机制,在编译时已经知道具体执行哪个方法。
   
  覆盖是一种动态绑定的多态机制。即,在父类与子类中具有相同签名的方法具有不同的具体实现,至于最终执行哪个方法 根据运行时的实际情况而定。


(3) 总结

  我们应该注意以下几点:

  • final 方法不能被覆盖;

  • 子类不能覆盖父类的private方法,否则,只是在子类中定义了一个与父类重名的全新的方法,而不会有任何覆盖效果。

      其他需要注意的地方如下图所示:

             Detailed introduction and code examples of Java inheritance, polymorphism and class reuse


2、覆盖与隐藏

(1) 定义

覆盖:指 运行时系统调用当前对象引用 运行时类型 中定义的方法 ,属于 运行期绑定。

隐藏:指运行时系统调用当前对象引用 编译时类型 中定义的方法,即 被声明或者转换为什么类型就调用对应类型中的方法或变量,属于编译期绑定。


(2) 范围

Override:Only for instance methods;
Hide : Only for static methods and member variables.


(3) Summary

  • ##The instance method of the subclass cannot hide the static method of the parent class. Similarly, the static method of the subclass cannot override the instance method of the parent class, otherwise a compilation error will occur;

  • Both static members and instance members can be hidden by member variables with the same name in subclasses.

    The following program example explains the three concepts of overloading, overwriting and hiding well:      
    Detailed introduction and code examples of Java inheritance, polymorphism and class reuse

The above is the detailed content of Detailed introduction and code examples of Java inheritance, polymorphism and class reuse. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn