Introduction to inheritance, polymorphism, overloading and rewriting in Java

What is polymorphism? What is its implementation mechanism? What is the difference between overloading and rewriting? These are the four very important concepts we will review this time: inheritance, polymorphism, overloading and rewriting.

Inheritance

In short, inheritance is based on an existing type, By adding new methods or redefining existing methods (it will be mentioned below that this method is called rewriting) to generate a new type. Inheritance is one of the three basic characteristics of object-oriented -- encapsulation, inheritance and polymorphism. Every class we write when using Java is inherited, because in the Java language, the java.lang.object class is the most fundamental base class of all classes (or called parent class or super class). If a newly defined class does not explicitly specify which base class to inherit from, Java will default that it inherits from the object class.

We can divide classes in Java into the following three types:

Class: a class that uses class definition and does not contain abstract methods. Abstract class: a class defined using abstract class. It can contain or not contain abstract methods. Interface: a class defined using interface.

The following inheritance rules exist among the three types:

Classes can inherit classes, abstract classes, and interfaces. Abstract classes can inherit classes, abstract classes, and interfaces. Interfaces can only inherit interfaces.

Please note that in the above three rules, the different keywords extends and implements used in each inheritance case can not be replaced at will. As we all know, after an ordinary class inherits an interface, it must implement all the methods defined in the interface, otherwise it can only be defined as an abstract class. The reason why I don't use the term "implementation" for the implements keyword here is that conceptually, it also represents an inheritance relationship, and in the case of an abstract class implements interface, it doesn't have to implement any method defined by the interface, so it's more reasonable to use inheritance.

The above three rules comply with the following constraints at the same time:

Class and abstract class can only inherit at most one class or at most one abstract class, and the two cases are mutually exclusive, that is, they either inherit a class or an abstract class. When inheriting interfaces, classes, abstract classes and interfaces are not subject to quantitative constraints. Theoretically, they can inherit unlimited interfaces. Of course, for a class, it must implement all the methods defined in all the interfaces it inherits. When an abstract class inherits an abstract class or implements an interface, it can partially, completely or completely not implement the abstract methods of the parent abstract class or the interfaces defined in the parent class interface. When a class inherits an abstract class or implements an interface, it must fully implement all the abstract methods of the parent Abstract class or all the interfaces defined in the parent class interface.

The benefit of inheritance to our programming is the reuse of the original classes (reuse) just like modular reuse, class reuse can improve our development efficiency. In fact, modular reuse is the result of massive reuse and reuse. Besides inheritance, we can also reuse classes in a combinatorial way. The so-called combination is to define the original class as an attribute of the new class, and to implement the method by calling the original class in the new class. Reuse. If there is no included relationship between the newly defined type and the original type, that is, from the perspective of abstract concept, the thing represented by the newly defined type is not one of the things represented by the original type. For example, the yellow people are a kind of human beings, and there is an included and included relationship between them, then combination is a better choice to realize reuse. The following example is a simple example of the combination method:

Of course, in order to make the code more efficient, we can also initialize the original type (such as parent P) when it needs to be used.

Using inheritance and composition to reuse the original classes is an incremental development mode. The advantage of this method is that there is no need to modify the original code, so it will not bring new bugs to the original code, and there is no need to retest because of the modification of the original code, which is obviously beneficial to our development. Therefore, if we are maintaining or transforming an original system or module, especially when we don't have a thorough understanding of them, we can choose the mode of incremental development, which can not only greatly improve our development efficiency, but also avoid the risks caused by the modification of the original code.

Polymorphism

Polymorphism is another important basic concept. As mentioned above, it is one of the three basic characteristics of object-oriented. What is polymorphism? Let's take a look at the following examples to help understand:

Operation results:

Model: BMW unit price: 300000 model: cheryqq unit price: 20000 total revenue: 320000

Inheritance is the basis of polymorphism. Literally, Polymorphism is a type (all car types) that shows multiple states (BMW's name is BMW and the selling price is 300000; Chery's name is cheryqq and the selling price is 2000). Call a method to the subject to which the method belongs (that is, objects or classes) are associated and called binding, which can be divided into pre binding and post binding. Their definitions are explained below:

Pre binding: binding before the program runs, which is implemented by the compiler and linker, also known as static binding. For example, the static method and the final method. Note that the private method is also included here because it is implicit final. Late binding: binding according to the type of object at run time is implemented by the method call mechanism, so it is also called dynamic binding or run-time binding. All methods except pre binding belong to post binding.

Polymorphism is implemented on the mechanism of late binding. Polymorphism brings us the advantage of eliminating the coupling relationship between classes and making the program easier to expand. For example, in the above example, to add a new type of car sales, you only need to let the newly defined class inherit the car class and implement all its methods without any modification to the original code. The sellcar (car) method of carshop class can handle the new car model. The new code is as follows:

Overloading and overriding

Both overloading and rewriting are aimed at the concept of methods. Before clarifying these two concepts, Let's first understand what a method's type construction is , although it is widely used, this translation is not accurate). Type construction refers to the composition structure of a method, including the name and parameters of the method, including the number, type and order of parameters, but excluding the return value type, access permission modifier, abstract, static, final and other modifiers of the method. For example, the following two methods have the same structure:

These two methods have different structures:

After understanding the concept of type construction, let's take a look at overloading and rewriting. Please see their definitions:

Override, the English name is overriding, which means that in the case of inheritance, a subclass defines a new method with the same type structure as the method in its base class, which is called a subclass to override the method of the base class. This is a necessary step to achieve polymorphism. Overload, whose English name is overloading, refers to the definition of more than one method with the same name but different types in the same class. It is not allowed to define more than one method with the same structure in the same class.

Let's consider an interesting question: can constructors be overloaded? Of course, the answer is yes. We often do this in actual programming. In fact, the constructor is also a method. The constructor name is the method name, the constructor parameter is the method parameter, and its return value is the instance of the newly created class. However, constructors cannot be overridden by subclasses because subclasses cannot define constructors with the same type as the base class.

Overloading, covering, polymorphism and function hiding

It is often seen that some beginners of C + + have a vague understanding of overloading, coverage, polymorphism and function hiding. Here I write some of my own opinions, hoping to solve the doubts of C + + beginners.

To understand the complex and subtle relationship between overloading, coverage, polymorphism and function hiding, we first need to review the basic concepts such as overload coverage.

First, let's take a very simple example to understand what function hiding is.

/*Functions in different non namespace scopes do not constitute overloads. Subclasses and superclasses are two different scopes. In this example, the two functions are in different scopes, so they are not overloaded unless the scope is a namespace scope*/ In this example, the function does not overload or override, but hides hide.

The next five examples illustrate what hiding is

Example 1

Example 2

Example 3

Example 4

Example 5

Well, let's start with a small summary of the characteristics between overloading and coverage

Characteristics of overload:

N the same range (in the same class); N the same function name but different parameters; N the virtual keyword is optional.

Override means that the derived class function overrides the base class function. The characteristics of override are as follows:

N different ranges (in derived class and base class respectively); N the function name and parameters are the same; N base class functions must have the virtual keyword. (if there is no virtual keyword, it is called hidden)

If the base class has multiple overloaded versions of a function, and you override one or more function versions in the base class in the derived class, or add a new function version in the derived class (the function name is the same and the parameters are different), then all overloaded versions of the base class are masked, which is called hidden here. Therefore, in general, when you want to use the new function version in the derived class and the function version of the base class, you should override all overloaded versions in the base class in the derived class. If you do not want to override the overloaded function version of the base class, you should explicitly declare the namespace scope of the base class in the way of example 4 or example 5. In fact, the C + + compiler believes that there is no relationship between functions with the same function name and different parameters. They are simply two unrelated functions. Only in order to simulate the real world and let programmers deal with problems in the real world more intuitively, C + + language introduces the concepts of overload and coverage. Overloading is under the same namespace scope, while overriding is under different namespace scopes. For example, the base class and derived class are two different namespace scopes. In the process of inheritance, if the derived class has the same name as the base class function, the base class function will be hidden. Of course, the case discussed here is that there is no virtual keyword in front of the base class function. When there is a virtual keyword, we will discuss it separately. An inherited class overrides a function version of the base class to produce an interface for its own functions. At this time, the C + + compiler thinks that since you want to use the self rewritten interface of the derived class, the interface of my base class will not be provided to you (of course, you can use the method of explicitly declaring the namespace scope, see [C + + foundation] overload, coverage, polymorphism and function hiding (1)). The interface of your base class is overloaded. If you want to keep the overloaded feature in the derived class, you can give the interface overloaded feature yourself. Therefore, in the derived class, as long as the function name is the same, the function version of the base class will be ruthlessly shielded. In the compiler, masking is implemented through namespace scope.

Therefore, to maintain the function overloaded version of the base class in the derived class, you should override the overloaded version of all base classes. Overloading is only valid in the current class. Inheritance will lose the characteristics of function overloading. In other words, to put the overloaded function of the base class in the inherited derived class, it must be overridden.

Here, "hidden" means that the function of the derived class shields the base class function with the same name. We will also summarize the specific rules:

N if the function of the derived class has the same name as the function of the base class, but the parameters are different. At this point, if the base class has no virtual keyword, the functions of the base class will be hidden. (note that it should not be confused with overload. Although the function name is the same and the parameters are different, it should be called overload, but it cannot be understood as overload here, because the derived class and the base class are not within the scope of the same namespace. It is understood as hidden here) n if the function of the derived class has the same name as the function of the base class, but the parameters are different. At this time, if the base class has the virtual keyword, the functions of the base class will be implicitly inherited into the VTable of the derived class. At this time, the function in the derived class VTable points to the function address of the base class version. At the same time, this new function version is added to the derived class as the overloaded version of the derived class. However, when the base class pointer implements the polymorphic calling function method, the new derived class function version will be hidden. N if the function of the derived class has the same name as the function of the base class and the parameters are the same, but the base class function does not have the virtual keyword. At this point, the functions of the base class are hidden. (note that it should not be confused with coverage, which is understood as hiding here). N if the function of the derived class has the same name as the function of the base class and the parameters are the same, but the base class function has the virtual keyword. At this point, the functions of the base class are not "hidden". (here, you should understand it as covering oh ^ ^).

Episode: when there is no virtual keyword in front of the base class function, we should rewrite it more smoothly. When there is a virtual keyword, we call coverage more reasonable. Stop this. I also hope you can better understand some subtle things in C + +. Don't bother. Let's give examples.

Example 6

Output results

Derive::fun() Derive::fun(int i) Derive::fun() Derive::fun(int i) Derive::fun(int i,int j) Press any key to continue */

Example 7-1

Example 7-2

Example 8-1

Example 8-2

Example 9

Examples 7-1 and 8-1 are easy to understand. I put these two examples here to make a comparison and help you better understand: N in example 7-1, the derived class does not cover the virtual function of the base class. At this time, the address pointed to by the function pointer in the VTable of the derived class is the virtual function address of the base class. N in example 8-1, the derived class covers the virtual function of the base class. At this time, the address pointed to by the function pointer in the VTable of the derived class is the rewritten virtual function address of the derived class itself. In examples 7-2 and 8-2, it seems a little strange. In fact, if you compare them according to the above principles, the answer is also clear: N in example 7-2, we overloaded a function version for the derived class: void fun (double D). In fact, this is just a cover up. Let's analyze specifically. There are several functions in the base class and several functions in the derived class: type base class derived class VTable part void fun (int i) points to the virtual function void fun (int i) static part void fun (double D) of the base class version

Let's analyze the following three sentences: base * Pb = new derive(); pb->fun(1);// Base::fun(int i) pb->fun((double)0.01);// The first sentence of base:: fun (int i) is the key. The base class pointer points to the object of the derived class. We know that this is a polymorphic call; Next, in the second sentence, the runtime base class pointer finds that it is a derived class object according to the type of runtime object, so first go to the VTable of the derived class to find the virtual function version of the derived class. It is found that the derived class does not cover the virtual function of the base class, and the VTable of the derived class only points to the address of the virtual function of the base class, Therefore, it is natural to call the virtual function of the base class version. In the last sentence, the program still buried itself in looking for the VTable of the derived class. It found that there was no virtual function of this version at all, so it had to call its own virtual function. It is also worth mentioning that if there are multiple virtual functions in the base class, the program compilation will prompt "ambiguous call". Examples are as follows

OK, let's analyze example 8-2 again.

N in example 8-2, we also overloaded a function version for the derived class: void fun (double D), which covers the virtual functions of the base class. Let's analyze more specifically. There are several functions in the base class and several functions in the derived class:

Type base class derived class VTable part void fun (int i) void fun (int i) static part void fun (double D) from the table, we can see that the function pointer in the VTable of the derived class points to its own rewritten virtual function address. Let's analyze the following three sentences: base * Pb = new derive(); pb->fun(1);// Derive::fun(int i) pb->fun((double)0.01);// Derive:: fun (int i) the first sentence is needless to say. The second sentence, of course, calls the virtual function version of the derived class. The third sentence, hey, it feels strange. In fact, the C + + program is very stupid. When running, it buries itself into the VTable table table of the derived class and looks at it. Shit, there is no desired version, Why don't you look around for the base class pointer? Hehe, it turns out that the eyesight is limited. The base class is so old. It must be an old flower. Its eyes can only see its own non VTable part (i.e. static part) and the VTable part to be managed. The void fun (double D) of the derived class is so far away that it can't be seen! Besides, derived classes should manage everything. Don't derived classes have some power of their own? Hey, let's stop arguing and take care of ourselves^_^ Alas! Do you want to sigh? The base class pointer can make polymorphic calls, but it can never make overloaded calls of derived classes (refer to example 6) ~ ~ ~ take another look at Example 9. The effect of this example is the same as that of example 6. After you understand the above examples, this is also little kiss. Summary: overload overload selects the function version to be called according to the parameter list of the function, while polymorphism selects the virtual function version to be called according to the actual type of the runtime object. Polymorphism is realized by overriding the virtual function of the base class through the derived class, If the derived class does not override the virtual function of the base class, the derived class will automatically inherit the virtual function version of the base class. At this time, the virtual function of the base class version will be called regardless of whether the object pointed to by the base class pointer is a base type or a derived type; If the derived class overrides the virtual function of the base class, the virtual function version to be called will be selected according to the actual type of the object at runtime. For example, if the object type pointed to by the base class pointer is a derived type, the virtual function version of the derived class will be called to achieve polymorphism. The original intention of using polymorphism is to declare the function as virtual in the base class and override the virtual function version of the base class in the derived class. Note that the function prototype at this time is consistent with the base class, that is, the same name and parameter type; If you add a new function version to a derived class, you cannot dynamically call the new function version of the derived class through the base class pointer. This new function version is only used as an overloaded version of the derived class. In the same sentence, overloading is only valid in the current class. Whether you overload in the base class or in the derived class, the two are not involved in each other. If we understand this, we will also understand the output results smoothly in examples 6 and 9. Overload is statically bound, polymorphism is dynamically bound. It is further explained that overloading is independent of the object type actually pointed to by the pointer, and polymorphism is related to the object type actually pointed to by the pointer. If the pointer of the base class calls the overloaded version of the derived class, the C + + compiler considers it illegal. The C + + compiler only considers that the pointer of the base class can only call the overloaded version of the base class, and the overload is only valid within the namespace scope of the current class. Inheritance will lose the overload feature. Of course, if the pointer of the base class calls a virtual function at this time, Then it will dynamically select the virtual function version of the base class or the virtual function version of the derived class for specific operations, which is determined by the object type actually pointed to by the base class pointer. Therefore, overloading has nothing to do with the object type actually pointed to by the pointer, and polymorphism is related to the object type actually pointed to by the pointer. Finally, to clarify, virtual functions can also be overloaded, but the overload can only be valid within the current namespace scope. How many string objects have been created? Let's first look at a piece of code: Java code string STR = new string ("ABC"); This code is often followed by this question, that is, how many string objects are created in this line of code? I believe you are no stranger to this question and the answers are well known. There are two. Next, let's start from this topic and review some java knowledge related to creating string objects. We can divide the above line of code into four parts: String STR, =, "ABC" and new string(). String STR only defines a variable of string type named STR, so it does not create an object= It initializes the variable STR and assigns the reference (or handle) of an object to it. Obviously, there is no object created; now there is only new string ("ABC"). So why can new string ("ABC") be regarded as "ABC" and new string()? Let's take a look at the string constructor we called: Java code public string (string original) {/ / other code...} As everyone knows, We often create an instance of a class (object) has the following two methods: create an object using new. Call the newinstance method of class class to create an object using the reflection mechanism. We use new to call the constructor method above the string class to create an object and assign its reference to the str variable. At the same time, we note that the parameter accepted by the called constructor method is also a parameter String object, which is exactly "ABC". Therefore, we will introduce another way to create string objects -- including text in quotation marks. This method is unique to string, and it is very different from new. Java code string STR = "ABC"; There is no doubt that this line of code creates a string object. Java code string a = "ABC"; String b="abc"; What about here? The answer is still one. Java code string a = "ab" + "CD"; Look here again? The answer is still one. Is it a little strange? At this point, we need to introduce a review of string pool related knowledge. In the Java virtual machine (JVM), there is a string pool in which many string objects are stored and can be shared, so it improves efficiency. Since the string class is final, its value cannot be changed once it is created, so we don't have to worry about the confusion of the program caused by string object sharing. The string pool is maintained by the string class, and we can call intern() Method to access the string pool. Let's look back at string a = "ABC";, When this line of code is executed, the Java virtual machine first looks for whether an object with the value "ABC" already exists in the string pool. Its judgment is based on the return value of the string class equals (object obj) method. If yes, no new object will be created, and the reference of the existing object will be returned directly; If not, first create the object, then add it to the string pool, and then return its reference. Therefore, it is not difficult for us to understand why the first two of the first three examples are the answer. For the third example: Java code string a = "ab" + "CD"; Because the value of the constant is determined at compile time. Here, "ab" and "CD" are constants, so the value of variable a can be determined at compile time. The compiled effect of this line of code is equivalent to: Java code string a = "ABCD"; Therefore, only one object "ABCD" is created here, and it is saved in the string pool. Now the question comes again. Will all strings obtained after "+" connection be added to the string pool? We all know that "= =" can be used to compare two variables, It has the following two situations: if you compare two basic types (char, byte, short, int, long, float, double, Boolean), you judge whether their values are equal. If you compare two object variables, you judge whether their references point to the same object. Let's use "= =" Let's do some tests. For the sake of illustration, we regard the object pointing to the existing object in the string pool as being added to the string pool: Java code

The running results are as follows: string a = "ab"; String b = "cd"; The object created by "ab" + "CD" is "added" to the string pool, the object created by "a +" CD "is" not added "to the string pool, the object created by" ab "+ B" is "not added" to the string pool, and the object created by a + B is "not added" to the string pool. It is not difficult to see from the above results, Only the new objects generated by "+" connection between string objects created with quotation marks containing text will be added to the string pool. For all "+" connection expressions containing new objects (including null), the new objects generated by them will not be added to the string pool, which we will not repeat. However, there is a situation that needs our attention. Please see the following code: Java code

The result of this code is as follows: s is equal to t, and they are the same object. Why? The reason is that for a constant, its value is fixed, so it can be determined at compile time, while the value of a variable can only be determined at run time, because this variable can be called by different methods, which may cause value changes. In the above example, both a and B are constants with fixed values, so the value of S is also fixed, which has been determined when the class is compiled. That is: Java code string s = a + B; Equivalent to: Java code string s = "ab" + "CD"; I'll change the above example a little to see what happens: Java code

Its running result is as follows: s is not equal to t, they are not the same object, but they have made a little change, and the result is just the opposite of the example just now. Let's analyze it again. Although a and B are defined as constants (it can only be assigned once), but neither of them is assigned immediately. Before calculating the value of S, when they are assigned and what value they are assigned are variables. Therefore, a and B are similar to a variable before being assigned. Then s cannot be determined at compile time, but can only be created at run time. Due to the sharing of objects in the string pool It can improve efficiency. Therefore, we advocate that you create string objects by including text in quotation marks. In fact, this is often used in programming. Next, let's look at the intern () method, which is defined as follows: Java code public native string intern(); This is a local method. When calling this method, the Java virtual machine first checks whether an object equal to the object value already exists in the string pool. If so, it returns the reference of the object in the string pool; If not, first create a string object with the same value in the string pool, and then return its reference. Let's look at this Code: Java code

Running result: B is not added to the string pool, and a new object is created. If the inter () method of string class adds the current object to the string pool and returns its reference when it does not find an object with the same value, then B and a point to the same object; Otherwise, the object pointed to by B is newly created in the string pool by the Java virtual machine, but its value is the same as a. The running result of the above code just confirms this point. Finally, let's talk about the storage of string objects in Java virtual machine (JVM) and the relationship between string pool and heap and stack. First, let's review the difference between heap and stack: stack: it mainly stores basic types (or built-in types) (char, byte, short, int, long, float, double, Boolean) and object references. Data can be shared. The speed is second only to register and faster than heap. Heap: used to store objects. When we look at the source code of string class, we will find that it has a value attribute that holds the value of string object. The type is char [] , which means that a string is a sequence of characters. When executing string a = "ABC"; Java virtual opportunity creates three char values' a ',' B 'and' C 'in the stack, and then creates a string object in the heap. Its value (value) is the array {a', 'C'} composed of the three char values just created in the stack. Finally, the newly created string object will be added to the string pool. If we then execute string B = new string ("ABC"); Code. Since "ABC" has been created and saved in the string pool, the Java virtual machine will only create a new string object in the heap, However, its value is shared with the three char type values' a ',' B 'and' C 'created in the stack during the execution of the previous line of code. Here, we have made it clear why string STR = new string ("ABC") creates two objects.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>