[java basic syntax] analyze the polymorphic, abstract classes and interfaces of Java in 20000 words


The previous section introduced the package and inheritance of Java. If you are a little confused about this kind of knowledge, you can go Package and inheritance of 10000 word parsing Java This chapter may help you solve some doubts!

Today's chapter mainly introduces polymorphic and abstract classes. I hope the next content will be helpful to you!

1, Polymorphism

Before understanding polymorphism, we first understand the following knowledge points

1. Upward transformation

What is upward transformation? Simply put

The subclass object is assigned to the reference of the parent object

What does this mean? We can look at the following code

// Suppose Animal is the parent class and Dog is the child class
public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Animal("animal");
        Dog dog=new Dog("Erha");
        animal=dog;
    }
}

The object referenced by the subclass dog is assigned to the reference of the parent class, and the above code can also be simplified to

public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Dog("Erha");
    }
}

This is actually the same as the above code. This writing method is called "upward transformation", which assigns the reference of the subclass object to the reference of the parent class

In fact, more may be used after upward transformation, so when do we need to use it?

  • Direct assignment
  • Method transmission parameter
  • Method return

Direct assignment is what the above code looks like. Next, let's take a look at an example of method parameter passing

// Suppose Animal is the parent class and Dog is the child class
public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Dog("Erha");
        func(animal);
    }
    public static void func1(Animal animal){
        
    }
}

We wrote a function. The formal parameter is the reference of the parent class, and the passed argument is the object referenced by the child class. It can also be written as

public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Animal("animal");
        Dog dog=new Dog("Erha");
        func(dog);
    }
    public static void func1(Animal animal){
        
    }
}

So what is method return like? In fact, it is also very simple, such as

// Suppose Animal is the parent class and Dog is the child class
public class TestDemo{
    public static void main(String[] args){
        
    }
    public static Animal func2(){
        Dog dog=new Dog("Erha");
        return dog;
    }
}

In the func2 method, the object of the child class is returned to the reference of the parent class. There is also a method return

public class TestDemo{
    public static void main(String[] args){
        Animal animal=func2();
    }
    public static Dog func2(){
        Dog dog=new Dog("Erha");
        return dog;
    }
}

The return value of the method is the reference of the subclass, and then assign it to the object of the parent class. This writing is also called "upward transformation".

So since the reference of our parent class points to the object referenced by the child class, can the parent class use some methods of the child class? have a try

class Animal{
    public String name;
    public Animal(String name){
        this.name=name;
    }
    public void eat(){
        System.out.println(this.name+"Eat something"+"(Animal)");
    }
}
class Dog extends Animal{
    public Dog(String name){
        super(name);
    }
    public void eatDog(){
        System.out.println(this.name+"Eat something"+"(Dog)");
    }
}
public class TestDemo{
    public static void main(String[] args){
        Animal animal1=new Animal("animal");
        Animal animal2=new Dog("Erha");
        animal1.eat();
        animal2.eatdog();
    }
}

The result is No

Because the reference type of Animal is essentially Animal, you can only use the members and methods in your own class

2. Dynamic binding

Can animal2 use the eatDog method in the Dog class? In fact, we can, as long as we change the name of eatDog to eat

class Dog extends Animal{
    public Dog(String name){
        super(name);
    }
    public void eat(){
        System.out.println(this.name+"Eat something"+"(Dog)");
    }
}

The modified part of the code is as above. At this time, animal2 directly calls eat to get the following results

This means that at this time

  • animal1.eat() actually calls the method of the parent class
  • What animal2.eat() actually calls is the method of the subclass

So why does animal2.eat call subclass methods after changing eatDog to eat?

This is what we're going to talk about rewriting

3. Method rewriting

What is rewriting?

The subclass implements the method with the same name as the parent class, and

  • Same method name
  • The return value of the method is generally the same
  • The parameter list of the method is the same

If the above conditions are met, it is called rewriting, overwriting, and overriding

matters needing attention:

  • The overridden method cannot be a sealed method (that is, a method modified by final). We have learned about the keyword final before, and the method modified by it is called the sealing method, which can no longer be rewritten, such as

    // Suppose this is a method in the parent class
    public final void eat(){
        System.out.println(this.name+"Want to eat");
    }
    

    Such methods cannot be overridden

  • The access qualifier permission of a subclass must be greater than or equal to the permission of the parent class, but the parent class cannot be modified by private

  • Methods cannot be modified by static

  • Generally, for overridden methods, you can use the @ Override annotation to display the specified. What's the advantage of adding him? Look at the code below

    // Suppose the following eat is the overridden method
    class Dog extends Animal{
        @Override
        private void eat(){
            // ...
        }
    }
    

    If eat is written as ate, the compiler will find that there is no ate method in the parent class, and will compile and report an error, indicating that rewriting cannot be formed

  • When overridden, the return value can be modified, and the method name, parameter type and number cannot be modified. Only when the return value is a class type, the overridden method can modify the return value type, and must be a subclass of the return value of the parent method; Or it will not be modified, which is the same as the return value type of the parent class

Knowing this, we must have a concept of rewriting. At this point, let's recall the overloading we learned before. We can make a table for comparison

differenceOverloadOverride
conceptThe method name is the same, the parameter list is different, and the return value is not requiredThe method name is the same, the parameter list is the same, and the return type is generally the same
RangeA classInheritance relationship
limitNo permission requirementsOverridden methods cannot have more stringent access control permissions than the parent class

The result of the comparison is that there is no relationship between the two

At this point, it seems that we haven't explained what the title dynamic binding in the previous section is

So what is dynamic binding? The conditions for occurrence are as follows

  1. Upward transformation occurs (the parent class reference needs to reference the child class object)
  2. Through the parent class reference, the override method with the same name of the child class and the parent class is called

Why is it called dynamic? After disassembly, we can find

  • Compile time: the method of the parent class is called
  • But at run time: what is actually called is the method of the subclass

Therefore, this is actually a dynamic process, which can also be called runtime binding

4. Downward transformation

Since the upward transformation has been introduced, there must be downward transformation! When is the downward transition? Think about the upward transformation, and you can guess that it is

The parent class object is assigned to the reference of the child class object

So code is

// Suppose Animal is the parent class and Dog is the child class
public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Animal("animal");
        Dog dog=animal;
    }
}

However, it is not enough just to write in this way, and errors will be reported

Why? We can think about it like this

Dogs are animals, but animals cannot be said to be dogs, which is equivalent to an inclusive relationship.

Therefore, the object of the dog can be assigned directly to the animal, but the object of the animal cannot be assigned to the dog

We can use cast so that the above code will not report an error

public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Animal("animal");
        Dog dog=(Dog)animal;
    }
}

We then run the eat method with a dog reference

public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Animal("animal");
        Dog dog=(Dog)animal;
        dog.eat();
    }
}

An error occurred after running

Animals cannot be converted into dogs!

What should we do? One thing to remember:

The premise of using downward transformation is that upward transformation must occur

public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Dog("Erha");
        Dog dog=(Dog)animal;
        dog.eat();
    }
}

That's no problem!

As mentioned above, the premise of using downward transformation is upward transformation. In fact, we can understand that when we use the upward transformation, some functions cannot be achieved, so we use the downward transformation to improve the code (emmm, it's purely a personal fool). Like

// Suppose I have a housekeeping method guard in my Dog class
public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Dog("Erha");
        animal.guard();
    }
}

The above code will report an error because there is no guard method in the Animal class. Therefore, we need to borrow the downward transformation

public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Dog("Erha");
        Dog dog =animal;
        dog.guard();
    }
}

be careful:

In fact, downward transformation is not often used. Using it may accidentally make some mistakes. If we continue to use some unique methods of other animals in the above code, we will report an error if we forget that they have not undergone upward transformation.

To avoid this error: we can use instanceof

instanceof: you can determine whether a reference is an instance of a class. If yes, it returns true. If not, it returns false, such as

public class TestDemo{
    public static void main(String[] args){
        Animal animal=new Dog("Erha");
        if(animal instanceof Bird){
            Bird bird=(Bird)animal;
            bird.fly();
        }
    }
}

The above code first judges whether the reference of Animal is an instance of Bird. We know that it should be an instance of Dog, so false is returned

5. Keyword super

In fact, the super keyword was explained in the previous chapter. Here I use a table to compare this and super for easy understanding

differencethissuper
conceptAccess properties and methods in this classAccess to properties and methods in the parent class by subclasses
Search rangeFind this class first. If this class does not, call the parent classCall parent class directly
expressRepresents the current objectnothing
Commonality 1Cannot be placed in static decorated methodsCannot be placed in static decorated methods
Commonness 2To be placed on the first line (cannot be used with super)To be placed on the first line (cannot be used with this)

6. call the rewrite method (PIT) in the construction method.

Next, let's look at a piece of code. You can guess what the result is!

class Animal{
    public  String name;
    public Animal(String name){
        eat();
        this.name=name;
    }
    public void eat(){
        System.out.println(this.name+"Eating food( Animal)");
    }
}
class Dog extends Animal{
    public Dog(String name){
        super(name);
    }
    public void eat(){
        System.out.println(this.name+"Eating food( Dog)");
    }
}
public class TestDemo{
    public static void main(String[] args){
        Dog dog=new Dog("Erha");
    }
}

The result is

If you don't guess right, you usually have two doubts:

  • The eat method is not called, but why is the result like this?
  • Why is null?

answer:

  • Doubt 1: because a subclass inherits from the parent class and needs to construct methods for the parent class, when a subclass creates an object, it constructs the construction method of the parent class and executes the eat method of the parent class
  • Doubt 2: because the parent class construction method executes the eat method first, and the assignment of name is in the next step, most of the name at this time is null

Conclusion:

The overridden method can be called in the construction method, and dynamic binding occurs

7. Understanding polymorphism

That's all. We're finally going to officially introduce one of our key points today! What is polymorphism? In fact, it's the same idea as inheritance. We can look at a piece of code first

class Shape{
    public void draw(){

    }
}
class Cycle extends Shape{
    @Override
    public void draw() {
        System.out.println("Draw a circle⚪");
    }
}
class Rect extends Shape{
    @Override
    public void draw() {
        System.out.println("Draw a square piece♦");
    }
}
class Flower extends Shape{
    @Override
    public void draw() {
        System.out.println("Draw a flower❀");
    }
}
public class TestDemo{
    public static void main(String[] args) {
        Cycle shape1=new Cycle();
        Rect shape2=new Rect();
        Flower shape3=new Flower();
        drawMap(shape1);
        drawMap(shape2);
        drawMap(shape3);
    }
    public static void drawMap(Shape shape){
        shape.draw();
    }
}

We found that when the drawMap method is used by the caller, the draw method is called through the parent class, and the final expression is different. This idea is called polymorphism.

More simply, polymorphism is

A reference can show many different forms

Polymorphism is an idea, and there are two preconditions to realize it

  • Upward transformation
  • Call an override method with the same name

The inheritance of an idea always has its unique benefits, so what are the benefits of using polymorphism?

1) The cost of using classes by class callers is further reduced

  • Encapsulation means that the caller of a class does not need to know the implementation details of the class
  • Polymorphism allows the caller of a class not to know what the type of the class is, but only that the object has some method

2) It can reduce the "circle complexity" of the code and avoid using a large number of if else statements

Cycle complexity:

Is a way to describe the complexity of a piece of code. The number of conditional statements and loop statements in a piece of code can be regarded as "cycle complexity". The more this number, the more complex it is.

We can look at a piece of code

public static void drawShapes(){
    Rect rect = new Rect(); 
    Cycle cycle = new Cycle(); 
    Flower flower = new Flower(); 
    String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"}; 
    for (String shape : shapes) { 
    	if (shape.equals("cycle")) { 
     		cycle.draw(); 
     	} else if (shape.equals("rect")) { 
     		rect.draw(); 
     	} else if (shape.equals("flower")) { 
     		flower.draw(); 
 		}
    }
}

The meaning of this code is to print circle, square piece, circle, square piece and flower respectively. If polymorphism is not used, we will generally write the above method. Using polymorphism, the code will appear very simple, such as

public static void drawShapes() { 
    // We created an array of Shape objects 
    Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()}; 
    for (Shape shape : shapes) { 
    	shape.draw(); 
    } 
}

We can understand the above code through the following diagram

On the whole, it is much simpler to use polymorphic code

3) Strong scalability

As in the above drawing code, if we want to add a new shape, the cost of changing it in a polymorphic way is also relatively low, such as

// Add triangle
class Triangle extends Shape { 
    @Override 
    public void draw() { 
    	System.out.println("△"); 
    } 
}

Using polymorphism, we can add a new class to our extended code. If polymorphism is not used, the if else statement needs to be modified, so the cost of modification will be higher

8. Summary

So far, the three characteristics of object-oriented: encapsulation, inheritance and polymorphism have been introduced. Because my personal understanding is also limited, I may not speak well and insufficient. I hope you can understand more.

Next, we will introduce abstract classes and interfaces, which will also be further applied to polymorphism. You can practice more and deepen your understanding of ideas.

2, Abstract class

1. Concept

We just wrote a drawing code above, in which the definition of the parent class is as follows

class Shape{
    public void draw(){

    }
}

We found that there is no content in the draw method in the parent class, and the drawing is completed through the draw method of each sub class.

Like the above code, this method without actual work can be designed into an abstract method through abstract, and the class containing the abstract method is an abstract class

This is the code after design

abstract class Shape{
    public abstract void draw();
}

2. Precautions

  • Methods and classes should be modified by abstract

  • Other data members and member methods can be defined in abstract classes, such as

    abstract class Shape{
        public int a;
        public void b(){
            // ...
        }
        public abstract void draw();
    }
    

    However, to use these members and methods, you need to use subclasses through super

  • Abstract classes cannot be instantiated

  • Abstract methods cannot be private decorated

  • Abstract methods cannot be modified by final, and they cannot coexist with abstract

  • If the subclass inherits the abstract class but does not need to override the abstract method of the parent class, the subclass can be modified with abstract, such as

    abstract class Shape{
        public abstract void draw();
    }
    abstract Color extends Shape{
        
    }
    

    At this time, both ordinary methods and abstract methods can be defined in this subclass

  • An abstract class A can be inherited by another abstract class B, but if other ordinary classes inherit abstract class B, the ordinary class needs to override all abstract methods in A and B

3. Meaning of abstract class

We should know that the meaning of abstract classes is to be inherited

We know from the precautions that abstract classes themselves cannot be instantiated. If you want to use them, you can only create subclasses to inherit, for example

abstract class Shape{
    public int a;
    public void b(){
        // ...
    }
    public abstract void draw();
}
class Cycle extends Shape{
    @Override
    public void draw(){
        System.out.println("Draw one⚪");
    }
}
public class TestDemo{
    public static void main(String[] args){
        Shape shape=new Cycle();
    }
}

Note that the subclass needs to override all the abstract methods of the parent class, otherwise the code will report an error

3. Role of abstract classes

So why use an abstract class if it can't be instantiated?

The use of abstract classes is equivalent to an additional level of compiler validation

What do you mean? For example, according to the above drawing code, the actual work is actually completed by the subclass. If you accidentally misuse the parent class, the parent class will not report an error if it is not an abstract class. Therefore, if you design the parent class as an abstract class, it will report an error when the parent class is instantiated, so let's find the error as early as possible

3, Interface

We introduced abstract classes above. In addition to abstract methods, abstract classes can also contain ordinary methods and members.

The interface can also contain methods and fields, but only abstract methods and static constants.

1. Grammar rules

We can rewrite the above Shape into an interface, and the code is as follows

interface IShape{
    public static void draw();
}

The specific syntax rules are as follows:

  • Interfaces are defined using interface s

  • Interface naming generally begins with the capital letter I

  • The methods in the interface must be abstract and public modified methods, so the abstract methods can be simplified into

    interface IShape{
        void draw();
    }
    

    Written like this, the default is public abstract

  • The interface can also contain static constants modified by public, and public static final can be omitted, such as

    interface IShape{
        public static final int a=10;
        public static int b=10;
        public int c=10;
        int d=10;
    }
    
  • Interfaces cannot be instantiated separately. Like abstract classes, they need to be inherited and used by subclasses, but implements inheritance is used in interfaces, such as

    interface IShape{
        public static void draw();
    }
    class Cycle implements IShape{
        @Override
        public void draw(){
            System.out.println("Draw a circle⚪");
        }
    }
    

    Unlike extensions, implements means "implementation", which means that there is nothing at present and everything needs to be constructed from scratch

  • The class of the underlying interface needs to override all abstract methods in the interface

  • A class can use implements to implement multiple interfaces, and each interface can be separated by commas, such as

    interface A{
        void func1();
    }
    interface B{
        void func2();
    }
    class C implements A,B{
        @Override
        public void func1(){
            
        }
        @Override
        public void func2{
            
        }
    }
    

    Note that this class needs to override all abstract methods of all inherited interfaces. Use ctrl + i in the IDEA to quickly implement the interface

  • The relationship between interfaces can be maintained using extensions, which means "extension", that is, an interface extends the functions of other interfaces, such as

    interface A{
        void func1();
    }
    interface B{
        void func2();
    }
    interface D implements A,B{
        @Override
        public void func1(){
              
        }
        @Override
        public void func2{
              
        }
        void func3();
    }
    

be careful:

Starting from JDK1.8, the methods in the interface can be ordinary methods, but the premise is that this method is modified by default (that is, the default method of the interface), such as

interface IShape{
    void draw();
    default public void func(){
        System.out.println("Default method");
    }
}

2. Implement multiple interfaces

As we mentioned earlier, inheritance in Java is single inheritance, that is, a class can only inherit one parent class

However, multiple interfaces can be implemented at the same time, so we can achieve the similar effect of multiple inheritance through multiple interfaces

Next, understand it through the code!

class Animal{
    public String name;
    public Animal(String name){
        this.name=name;
    }
}
class Bird extends Animal{
    public Bird(String name){
        super(name);
    }
}

At this time, the child class Bird inherits the parent class Animal, but can no longer inherit other classes, but can continue to implement other interfaces, such as

class Animal{
    public String name;
    public Animal(String name){
        this.name=name;
    }
}
interface ISwing{
    void swing();
}
interface IFly{
    void fly();
}
class Bird extends Animal implements ISwing,IFly{
    public Bird(String name){
        super(name);
    }
    @Override
    public void swing(){
        System.out.println(this.name+"Swimming");
    }
    @Override
    public void fly(){
        System.out.println(this.name+"Flying");
    }
}

The above code is equivalent to implementing multiple inheritance, so the emergence of the interface solves the problem of Java single inheritance

Moreover, we can feel that the interface seems to have some attributes. Therefore, after having the interface, the user of the class does not have to pay attention to the specific type, but only to whether the class has certain capabilities, such as

public class TestDemo {
    public static void fly(IFly flying){
        flying.fly();
    }
    public static void main(String[] args) {
        IFly iFly=new Bird("bird");
        fly(iFly);
    }
}

Because birds have the attribute of flying, we don't need to pay attention to specific types, because as long as those who can fly can realize the attribute of flying. For example, Superman can also fly, so we can define a superman class

class SuperMan implements IFly{
    @Override
    public void fly(){
        System.out.println("Superman is flying");
    }
}
public class TestDemo {
    public static void fly(IFly flying){
        flying.fly();
    }
    public static void main(String[] args) {
        fly(new SuperMan());
    }
}

be careful:

Subclasses inherit the parent class before implementing the interface

3. Interface inheritance

As described in the syntax rules, interfaces and interfaces can be maintained using extensions, so that an interface can extend the functions of other interfaces

I won't repeat it here

Let's learn more about interfaces to deepen our understanding of interfaces

4. Comparable interface

We previously introduced the sort method in the Arrays class, which can help us sort, such as

public class TestDemo {
    public static void main(String[] args) {
        int[] array={2,9,4,1,7};
        System.out.println("Before sorting:"+Arrays.toString(array));
        Arrays.sort(array);
        System.out.println("After sorting:"+Arrays.toString(array));

    }
}

Next, I want to sort the attributes of a student.

First, I implemented a Student class and overridden the toString method

class Student{
    private String name;
    private int age;
    private double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

Next, I wrote an array and gave some properties to the student array

public class TestDemo {
    public static void main(String[] args) {
        Student[] student=new Student[3];
        student[0]=new Student("Zhang San",18,96.5);
        student[0]=new Student("Li Si",19,99.5);
        student[0]=new Student("Wang Wu",17,92.0);
    }
}

So can we sort directly through the sort function? Let's write the following code first

public class TestDemo {
    public static void main(String[] args) {
        Student[] student=new Student[3];
        student[0]=new Student("Zhang San",18,96.5);
        student[1]=new Student("Li Si",19,99.5);
        student[2]=new Student("Wang Wu",17,92.0);
        System.out.println("Before sorting:"+student);
        Arrays.sort(student);
        System.out.println("After sorting:"+student);
    }
}

The end result is
Let's analyze it

ClassCastException: type conversion exception, saying that Student cannot be converted to java.lang.Comparable

What does this mean? We think that since Student is a user-defined type and contains multiple types, how can the sort method sort it? There seems to be no basis.

At this time, I found Comparable by reporting an error

You can know that this should be an interface. Then we can try to inherit this interface from our Student class. The following < T > actually means generic. Here, just change it to < Student >

class Student implements Comparable<Student>{
    public String name;
    public int age;
    public double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

But not at this time, because inheritance needs to rewrite the abstract method of the interface, so after searching, we found it

The added rewrite method is

@Override
public int compareTo(Student o) {
    // New comparison rules
}

This should be where the comparison rules are set. Let's take a look at the exchange in the sort method

That is, if the left value is greater than the right value, the exchange is performed

So if I want to sort the students' ages, the rewritten method should be

@Override
public int compareTo(Student o) {
    // New comparison rules
    return this.age-o.age;
}

Run the code again and the result is

Here, we can more deeply feel that the interface is actually a certain attribute or capability, and the above Student class inherits the comparison interface and has the comparison capability

Disadvantages:

  • When we compare the names of the above code, we need to change the rewriting method to

    @Override
    public int compareTo(Student o) {
        // New comparison rules
        return this.name.compareTo(o.name);
    }
    
  • When we compare the scores of the above code, we need to change the rewriting method to

    @Override
    public int compareTo(Student o) {
        // New comparison rules
        return int(this.score-o.score);
    }
    

We found that when we want to modify the comparison, we may have to modify the rewriting method again. This limitation is relatively large

In order to solve this defect, the following interface Comparator appears

4. Comparator interface

When we enter the definition of sort method, we can also see a comparison method, in which there are two parameter arrays and Comparator objects

The Comparator interface is used here

What about this interface? We can first define an age comparison class AgeComparator, which is specifically used to compare ages and let him inherit this class

class AgeCompartor implements Comparator<Student>{

}

By holding down ctrl and clicking it, we can jump to its definition. At this time, we can find that there is a method in it

This is different from the compareTo in the above Comparable. Let me rewrite it first

class AgeCompartor implements Comparator<Student>{
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age-o2.age;
    }
}

We then write the following code according to the description of the sort method

public class TestDemo {
    public static void main(String[] args) {
        Student[] student=new Student[3];
        student[0]=new Student("Zhang San",18,96.5);
        student[1]=new Student("Li Si",19,99.5);
        student[2]=new Student("Wang Wu",17,92.0);
        
        System.out.println("Before sorting:"+Arrays.toString(student));
        AgeComparator ageComparator=new AgeComparator();
        Arrays.sort(student,ageComparator);
        System.out.println("After sorting:"+Arrays.toString(student));
    }
}

In this way, we can compare the ages of students normally. At this time, we need to sort the names, and we can create a name comparison class NameComparator

class NameComparator implements Comparator<Student>{
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}

We only need to change the parameter ageComparator of the sort method to nameComparator

We can understand the AgeComparator and NameComparator above as comparators, and the limitation of using Comparator is much smaller than that of Comparable. If we want to compare a certain attribute, we just need to add its Comparator

5. Clonable interface and deep copy

First, we can look at such code

class Person{
    public String name ="LiXiaobo";

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}
public class TestDemo {
    public static void main(String[] args) {
        Person person=new Person();
    }
}

What is cloning? It should be to make a copy, for example

Now that I'm talking about clonable interface this time, I'll inherit it!

class Person implements Cloneable{
    public String name ="LiXiaobo";

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
}

But we found that even after inheritance, we can't find a cloning method through the created reference. At this point, we can click the definition of clonable

Great, nothing!

  • We found that clonable is an empty interface (also known as tag interface), and the function of this interface is to prove that a class can be cloned if it implements this interface
  • Before using it, we also need to override the Object cloning method (all classes inherit from the Object class by default)

How to rewrite the cloning method? By ctrl + o, you can see

Select clone again 🆗, The rewritten code becomes

class Person implements Cloneable{
    public String name ="LiXiaobo";

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

At this point, we can see a clone method

After clicking, we found that we still reported an error

The reason is that the overridden clone method will throw exceptions. There are two methods for this. Today, I will introduce a simpler method

  • Method 1: put the mouse on the clone, press and hold Alt + enter, and you will see

    Just click the red box, but you will find that an error is still reported after clicking. This is because the return value of the rewritten method is Object, and the compiler will think it is unsafe, so it can be forcibly converted to Person. At this point, we will output the cloned copy and find that the result is OK

And by printing the address, the copy is different from the original address

So far, the simple cloning process has been introduced. But next, let's think more deeply and add a shaping a to the original code of the Person class

class Person implements Cloneable{
    public String name ="LiXiaobo";
    public int a=10;
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

At this point, we print through person and person respectively. The code is as follows

public class TestDemo3 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person=new Person();
        Person person1=(Person)person.clone();
        System.out.println(person.a);
        System.out.println(person1.a);
        System.out.println("#############");
        person1.a=50;
        System.out.println(person.a);
        System.out.println(person1.a);
    }
}

give the result as follows

We found that in this case, person1 is completely a copy, and its modification has nothing to do with person.

But let's look at the following situation. We define a Money class and create it in Person

class Money{
    public int money=10;
}
class Person implements Cloneable{
    public String name ="LiXiaobo";
    public Money money=new Money();
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                '}';
    }
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

Then we modify the value of money in person1. The code is as follows

public class TestDemo3 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person=new Person();
        Person person1=(Person)person.clone();
        System.out.println(person.money.money);
        System.out.println(person1.money.money);
        System.out.println("#############");
        person.money.money=50;
        System.out.println(person.money.money);
        System.out.println(person1.money.money);
    }
}

The result this time is

Why? We can analyze the following pictures

Because the cloned object is the person object, only the money of (0x123) is cloned, while the money of (0x456) is not cloned. Therefore, even if the cloned copy of the previous money points to it, the money of the copy will be changed

The above situation is actually called shallow copy, so how to turn it into deep copy?

We just need to clone a copy of the object pointed to by the money reference

Steps:

  1. The Money class also implements the clonable interface and overrides the cloning method

    class Money implements Cloneable{
        public int money=10;
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
  2. Modify the cloning method in Person

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person personClone=(Person)super.clone();
        personClone.money=(Money)this.money.clone();
        return personClone;
    }
    

This is a deep copy!

4, Summary

The above is my personal understanding of polymorphism, abstract classes and interfaces. It may not be very good, but I have tried my best to explain it. I hope it will be helpful to you!

Keywords: Java JavaSE

Added by TCovert on Mon, 20 Sep 2021 17:25:20 +0300