Java abstract classes and interfaces

Posted by jawaidpk on Thu, 10 Feb 2022 15:05:29 +0100

I abstract class

1. What is an abstract class

First, let's review an example mentioned in the previous article: printing graphics

class Shape { 
 	public void draw() { 
 		// Don't do anything
 	} 
} 
class Cycle extends Shape { 
 	@Override 
 	public void draw() { 
 		System.out.println("○"); 
 	} 
} 
class Rect extends Shape { 
 	@Override 
 	public void draw() { 
 		System.out.println("□"); 
 	} 
} 
class Flower extends Shape { 
 	@Override 
 	public void draw() { 
 		System.out.println("♣"); 
 	} 
} 
/I am the dividing line// 

public class Test { 
 	public static void main(String[] args) { 
 		Shape shape1 = new Flower(); 
 		Shape shape2 = new Cycle(); 
 		Shape shape3 = new Rect(); 
 		drawMap(shape1); 
 		drawMap(shape2); 
 		drawMap(shape3); 
 	} 
 	// Print a single drawing
 	public static void drawShape(Shape shape) { 
 		shape.draw(); 
 	} 
}

We found that the draw method in the parent class Shape seems to have little practical work. The main drawing graphics are completed by the draw methods of various subclasses of Shape.
For a method that does not actually work, we can design it as an abstract method. The class containing the abstract method is called abstract class

2. Grammar rules

So, how to write abstract classes? See code:

abstract class Shape { 
 	abstract public void draw(); 
}
  • Add the abstract keyword before the draw method to indicate that it is an abstract method. At the same time, the abstract method has no method body (without {}, concrete code cannot be executed)
  • For a class containing abstract methods, the abstract keyword must be added to indicate that it is an abstract class

matters needing attention:

  1. Abstract classes cannot be instantiated directly:
Shape shape = new Shape(); 
// Compilation error
Error:(30, 23) java: Shape It's abstract; Cannot instantiate
  1. Abstract methods cannot be private:
abstract class Shape { 
 	abstract private void draw(); 
} 
// Compilation error
Error:(4, 27) java: Illegal modifier combination: abstract and private
  1. Abstract classes can contain other non abstract methods or fields. The rules of this non abstract method are the same as those of ordinary methods. It can be overridden or directly called by subclasses:
abstract class Shape { 
 	abstract public void draw(); 
 	void func() { 
 		System.out.println("func"); 
 	} 
} 
class Rect extends Shape { 
 
} 
public class Test { 
 	public static void main(String[] args) { 
 		Shape shape = new Rect(); 
 		shape.func(); 
 	} 
} 
// results of enforcement
func

3. The role of abstract classes

Abstract classes exist in order to be inherited
The abstract class itself cannot be instantiated. If you want to use it, you can only create a subclass of the abstract class, and then let the subclass override the abstract methods in the abstract class.

Then you may have a question. Ordinary classes can also be inherited and ordinary methods can also be rewritten. Why do you have to use abstract classes and abstract methods?

This is true, but using abstract classes is equivalent to an extra check of the compiler:
The scenario of using abstract classes is like the above code. The actual work should not be completed by the parent class, but by the child class.
At this time, if you accidentally misuse the parent class, you will not report an error using the ordinary class compiler. However, if the parent class is an abstract class, an error will be prompted when instantiating, so that we can find the problem as soon as possible.

The meaning of many grammars is to "prevent errors". For example, final, which we used to use, is similar. If the user does not modify the created variable, it is equivalent to a constant? But in addition, final can let the compiler remind us in time when it is accidentally modified.
Making full use of compiler verification is very meaningful in practical development.

II Interface

1. What is an interface

Interface is a further step of abstract class. Abstract classes can also contain non abstract methods and fields. The methods contained in the interface are abstract methods, and fields can only contain static constants

2. Grammar rules

In the example of printing graphics just now, our parent class Shape does not contain other non abstract methods, and can also be designed as an interface:

interface IShape { 
 	void draw(); 
} 
class Cycle implements IShape { 
 	@Override 
 	public void draw() { 
 		System.out.println("○"); 
 	} 
} 
public class Test { 
 	public static void main(String[] args) { 
 		IShape shape = new Rect(); 
 		shape.draw(); 
 	} 
}
  • Define an interface using interface
  • The methods in the interface must be abstract methods, so abstract can be omitted
  • The method in the interface must be public, so public can be omitted
  • Cycle inherits the interface using implements. At this time, the meaning of expression is no longer "extension", but "implementation"
  • When calling, you can also create an interface reference corresponding to an instance of a subclass
  • Interfaces cannot be instantiated alone
  • From jdk1 Starting from 8, ordinary methods in the interface can have specific implementation, but this method must be modified by default.

Extensions vs. implementations:

  • Expansion refers to the further expansion of functions when certain functions already exist
  • Implementation means that there is nothing at present and it needs to be constructed from scratch

matters needing attention:

An interface can only contain abstract methods. For static fields, only static fields can contain:

interface IShape { 
 	void draw(); 
 	public static final int num = 10; 
}

The public, static and final keywords can be omitted The omitted num still represents the static constant of public

Summary:

  1. When we create an interface, the name of the interface usually starts with the capital letter I
  2. The naming of interfaces generally uses the word of "adjective"
  3. Alibaba coding specification stipulates that methods and attributes in the interface should not be decorated with any symbols to keep the code concise

A piece of error prone Code:

interface IShape { 
 	abstract void draw() ; // Even if you don't write public, it's public 
} 
class Rect implements IShape { 
 	void draw() { 
 		System.out.println("□") ; //The permission is more strict, so it cannot be overridden
 	} 
}

3. Implement multiple interfaces

Sometimes we need to make a class inherit multiple parent classes at the same time. This is achieved in some programming languages through multiple inheritance.
However, only single inheritance is supported in Java, and a class can only extend one parent class. However, multiple interfaces can be implemented at the same time, and the similar effect of multiple inheritance can be achieved.
Now we represent a group of animals by class:

class Animal { 
 	protected String name; 
 
 	public Animal(String name) { 
 		this.name = name; 
 	} 
}

In addition, we provide another set of interfaces, which respectively mean "those who can fly", "those who can run" and "those who can swim":

interface IFlying { 
 	void fly(); 
} 
interface IRunning { 
 	void run(); 
} 
interface ISwimming { 
 	void swim(); 
}

Next, we create several specific animals
Cats can run:

class Cat extends Animal implements IRunning { 
 	public Cat(String name) { 
 		super(name); 
 	} 
 	@Override 
 	public void run() { 
 		System.out.println(this.name + "Running on four legs"); 
 	} 
}

Fish can swim:

class Fish extends Animal implements ISwimming { 
 	public Fish(String name) { 
 		super(name); 
 	} 
 	@Override 
 	public void swim() { 
 		System.out.println(this.name + "He is swimming with his tail"); 
 	} 
}

Frogs can run and swim:

class Frog extends Animal implements IRunning, ISwimming { 
 	public Frog(String name) { 
 		super(name); 
 	} 
 	@Override 
 	public void run() { 
 		System.out.println(this.name + "Jumping forward"); 
 	} 
 	@Override 
 	public void swim() { 
 		System.out.println(this.name + "He is kicking his legs to swim"); 
 	} 
}

PS: use ctrl + i in idea to quickly implement the interface

There is also a magical animal, which lives in water, land and air. It is called "duck":

class Duck extends Animal implements IRunning, ISwimming, IFlying { 
 	public Duck(String name) { 
 		super(name); 
 	} 
 	@Override 
 	public void fly() { 
 		System.out.println(this.name + "Flying with wings"); 
 	} 
 	@Override 
 	public void run() { 
 		System.out.println(this.name + "Running on two legs"); 
 	} 
 	@Override 
 	public void swim() { 
 		System.out.println(this.name + "Floating on the water"); 
 	} 
}

The above code shows the most common usage in Java object-oriented programming: a class inherits a parent class and implements multiple interfaces at the same time
The meaning of inheritance expression is is - a semantics, while the meaning of interface expression is xxx

A cat is an animal that can run
Frog is also an animal. It can run and swim
Duck is also an animal. It can run, swim and fly

What are the benefits of this design?

Keep in mind the benefits of polymorphism and let's forget about types With an interface, users of a class don't have to focus on specific types, but only on whether a class has certain capabilities

For example, now implement a method called "walking":

public static void walk(IRunning running) { 
 	System.out.println("I took my partner for a walk"); 
 	running.run(); 
}

Inside the walk method, we don't pay attention to which animal it is. As long as the parameters can run:

Cat cat = new Cat("kitten"); 
walk(cat); 
Frog frog = new Frog("Little frog"); 
walk(frog); 
// results of enforcement
 I took my partner for a walk
 The kitten is running on four legs
 I took my partner for a walk
 The little frog is jumping forward

Even the parameter can not be "animal", as long as it can run!

class Robot implements IRunning { 
 	private String name; 
 	public Robot(String name) { 
 		this.name = name; 
 	} 
 	@Override 
 	public void run() { 
 		System.out.println(this.name + "Running on wheels"); 
 	} 
} 
Robot robot = new Robot("robot"); 
walk(robot); 
// results of enforcement
 The robot is running on wheels

4. Inheritance between interfaces

Interface can inherit an interface to achieve the effect of reuse Use the extends keyword:

interface IRunning { 
 	void run(); 
} 
interface ISwimming { 
 	void swim(); 
} 
// Amphibians can run and swim
interface IAmphibious extends IRunning, ISwimming { 

} 
class Frog implements IAmphibious { 
 
}

Create a new interface through interface inheritance. IAmphibious means "amphibious"
At this time, to implement the Frog class created by the interface, you need to continue to implement the run method and the swim method. The inheritance between interfaces is equivalent to merging multiple interfaces

III Use examples of interfaces

1. Comparable interface

The example just now is more abstract. Let's take another more practical example to sort the object array:

Given a student class

class Student { 
 	private String name; 
 	private int score; 
 	public Student(String name, int score) { 
 		this.name = name; 
 		this.score = score; 
 	} 

 	@Override 
 	public String toString() { 
 		return "[" + this.name + ":" + this.score + "]"; 
 	} 
}

Give a student object array and sort the elements in the object array (in descending order of scores):

Student[] students = new Student[] { 
 new Student("Zhang San", 95), 
 new Student("Li Si", 96), 
 new Student("Wang Wu", 97), 
 new Student("Zhao Liu", 92), 
};

According to our previous understanding, we have a ready-made sort method for arrays. Let's try whether we can sort directly with the sort method:

After careful thinking, it is not difficult to find that students are different from ordinary integers. Two integers can be directly compared, and the size relationship is clear How to determine the size relationship between two student objects? We need to specify additional
Let our Student class implement the Comparable interface and the compareTo method:

class Student implements Comparable { 
 	private String name; 
 	private int score; 
 	public Student(String name, int score) { 
 		this.name = name; 
 		this.score = score; 
 	} 
 	@Override 
 	public String toString() { 
 		return "[" + this.name + ":" + this.score + "]"; 
 	} 
 	@Override 
 	public int compareTo(Object o) { 
 		Student s = (Student)o; 
 		if (this.score > s.score) { 
 			return -1; 
 		} else if (this.score < s.score) { 
 			return 1; 
 		} else { 
 			return 0; 
 		} 
 	} 
}

compareTo method will be called automatically in sort method The parameter of compareTo is Object. In fact, what is passed in is an Object of Student type

Then compare the size relationship between the current object and the parameter object (calculated by score):

  • Returns a number less than 0 if the current object should precede the parameter object
  • Returns a number greater than 0 if the current object should rank after the parameter object
  • If the current object and the parameter object are in no order, 0 is returned

Let's do it again:

At this time, the result is in line with our expectation ( ̄▽  ̄)*

compareTo is actually a comparison rule. If we want to customize the comparison type, we must implement the Comparable interface However, the Comparable interface has a big disadvantage, that is, it is very intrusive to classes, so we generally don't change it easily

2.Comparator interface

Just now we mentioned that the Comparable interface is very intrusive to classes. Is there a flexible interface for us to use? The answer is yes, that is the Comparator interface

Let's start by writing a comparator that compares by age:

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

Then write a comparator that compares names:

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

At this time, we instantiate the two comparators and pass in the array to be arranged and the comparator object we write in the sort method:

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

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

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

    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student(12,"af");
        students[1] = new Student(6,"be");
        students[2] = new Student(18,"zhangsan");

        System.out.println("Sort by age:");
        AgeComparator ageComparator = new AgeComparator();
        Arrays.sort(students,ageComparator);
        System.out.println(Arrays.toString(students));
        System.out.println("---------------------------");
        System.out.println("Sort by name:");
        NameComparator nameComparator = new NameComparator();
        Arrays.sort(students,nameComparator);
        System.out.println(Arrays.toString(students));
    }
}

Operation results:

Therefore, the Comparator interface only needs to rewrite the Comparator according to its own needs. It is much more flexible, rather than being written dead directly like the Comparable interface

3.Clonable interface

Some useful interfaces are built in Java, and Clonable is one of them
There is a clone method in the Object class. Calling this method can create a "copy" of the Object However, if you want to call the clone method legally, you must implement the Clonable interface first, otherwise you will throw a clonnotsupportedexception exception

  1. Implement Clonable interface
  2. Don't forget to throw an exception
  3. Override the clone method of Object

Let's take an example:

class Person implements Cloneable{
    public int age;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class TestDemo {
    public static void main(String[] args) throws CloneNotSupportedException{
        Person person = new Person();
        person.age = 99;
        Person person2 = (Person) person.clone();
        System.out.println(person2);
    }
}

Operation results:

At this time, the memory is as follows:

At this time, let's add another Money class and instantiate it in the Person class:

class Money implements Cloneable{
    public double m = 12.5;
    }
}
class Person implements Cloneable{
    public int age;
    public Money money = new Money();

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

We copy a copy of the value of money in person2. At this time, modify the money in person2. Does the money in person1 change?

public class TestDemo {
    
    public static void main(String[] args) throws CloneNotSupportedException{
        Person person = new Person();
        Person person2 = (Person) person.clone();
        System.out.println(person.money.m);
        System.out.println(person2.money.m);
        System.out.println("-------------------------");
        person2.money.m = 13.5;
        System.out.println(person.money.m);
        System.out.println(person2.money.m);
    }
}

The answer is no change!

Does it mean that the Clonable interface can only realize shallow copy?
The answer is also No. what determines the depth of copy is not the purpose of the method, but the implementation of the code!

Let's take a look at the memory distribution diagram at this time:

To realize deep copy, when we copy person, we need to copy the money in the person object, and let the money in person 2 point to the newly copied money. At this time, we realize deep copy
The specific operation implementation only needs to rewrite the clone method of the money class (convenient for cloning), then modify the clone method in Person and copy money

The specific codes are as follows:

class Money implements Cloneable{
    public double m = 12.5;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class Person implements Cloneable{
    public int age;
    public Money money = new Money();

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

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

Let's test:

public class TestDemo {

    public static void main(String[] args) throws CloneNotSupportedException{
        Person person = new Person();
        Person person2 = (Person) person.clone();
        System.out.println(person.money.m);
        System.out.println(person2.money.m);
        System.out.println("-------------------------");
        person2.money.m = 13.5;
        System.out.println(person.money.m);
        System.out.println(person2.money.m);
    }


In this way, the deep copy is successfully realized!

IV summary

  1. Abstract classes and interfaces are common ways of using polymorphism in Java
  2. Abstract classes can contain ordinary methods and fields. Such ordinary methods and fields can be directly used by subclasses (without rewriting), while interfaces cannot contain ordinary methods. Subclasses must override all abstract methods

V Write at the end

Today, I lost sleep again. I suddenly thought that instead of wasting my time brushing videos and playing games, I'd better spend my time learning and improving myself This article has been written from 12 o'clock to nearly 4 o'clock, and is finally completed. I hope it can help you. If there are any mistakes, I hope uu you can correct them
The winter vacation is coming to an end. I wish you all progress and happiness every day (✿◕‿◕✿)

Topics: Java Back-end