The second part of JAVA learning --- method construction

Keywords: Java Eclipse

1, Construction method

When creating an instance, we often need to initialize the fields of the instance at the same time, for example:

Person wang = new Person();
wang.setName("Xiao Wang");
wang.setAge(15);

It takes three lines of code to initialize the object instance. Moreover, if you forget to call setName() or setAge(), the internal state of the instance is incorrect.

Can all internal fields be initialized to appropriate values when creating an object instance?

Absolutely.

At this time, we need to construct methods.

When you create an instance, you actually initialize the instance by constructing a method. Let's first define a construction method. When creating a Person instance, we can pass in name and age at one time to complete initialization:

public class Test04 {
    public static void main(String[] args) {
        Person p = new Person("Xiao wang", 15);
        System.out.println(p.getName());
        System.out.println(p.getAge());
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

 

Because the constructor is so special, the name of the constructor is the class name. There are no restrictions on the parameters of the construction method. Any statement can also be written inside the method. However, compared with ordinary methods, the constructor has no return value (nor void). To call the constructor, you must use the new operator.

Default construction method

Does any class have a constructor? yes.

We didn't write a constructor for the Person class. Why can we call new Person()?

The reason is that if a class does not define a construction method, the compiler will automatically generate a default construction method for us. It has no parameters and no execution statements, like this:

class Person {
    public Person() {
    }
}

It should be noted that if we customize a constructor, the compiler will no longer automatically create the default constructor:

public class Test04 {
    public static void main(String[] args) {
        Person p = new Person();//Compilation error: this constructor was not found
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

  If you want to use the construction method with parameters and keep the construction method without parameters, you can only define both construction methods.

public class Main {
    public static void main(String[] args) {
        Pople p1 = new Pople("Xiao wang", 20); // You can call the constructor with parameters
        Pople p2 = new Pople(); // You can also call a parameterless constructor
    }
}

class Pople {
    private String name;
    private int age;

    public Pople() {
    }

    public Pople(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

When the field is not initialized in the constructor, the field of reference type is null by default, the field of numeric type uses the default value, the default value of int type is 0, and the default value of boolean type is false:

class Person {
    private String name; // Default initialization is null
    private int age; // Default initialization is 0

    public Pople() {
    }
}

You can also initialize fields directly:

class Pople {
    private String name = "wangwu";
    private int age = 12;
}

Then the problem arises: both initialize the field and initialize the field in the construction method:

class Person {
    private String name = "Wang";
    private int age = 13;

    public Pople(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

When we create an object, what is the initial value of the object instance and field obtained by new pop ("Wang Wu", 13)?

In Java, when creating an object instance, initialize it in the following order:

  1. Initialize the field first, for example, int age = 11; Indicates that the field is initialized to 11, double salary; Indicates that the field is initialized to 0 by default, String name; Indicates that the reference type field is initialized to null by default;
  2. The code that executes the construction method is initialized.

Therefore, since the code of the construction method runs later, the field value of new pop ("Wang Wu", 14) is finally determined by the code of the construction method.

2, Multi construction method

Multiple construction methods can be defined. When called through the new operator, the compiler will automatically distinguish by the number, position and type of parameters of the construction method:

class Pople {
    private String name;
    private int age;

    public Pople(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Pople(String name) {
        this.name = name;
        this.age = 13;
    }

    public Pople() {
    }
}

If you call new pop ("Wang Wu", 10);, It will be automatically matched to the constructor public pop (string, int).

If you call new pop ("Wang Wu");, It will be automatically matched to the constructor public pop (string).

If you call new pop();, It will be automatically matched to the constructor public pop().

One constructor can call other constructors to facilitate code reuse. The syntax for calling other constructor methods is this(...):

class Pople {
    private String name;
    private int age;

    public Pople(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Pople(String name) {
        this(name, 17); // Call another constructor pop (string, int)
    }

    public Pople() {
        this("Wang"); // Call another constructor pop (string)
    }
}

3, Method overloading

In a class, we can define multiple methods. If there are a series of methods with similar functions and different parameters, this group of method names can be made into methods with the same name. For example, in the HelloWrold class, define multiple hellowrold() methods:

class HelloWrold {
    public void helloWrold() {
        System.out.println("Hellowrold, world!");
    }

    public void helloWrold(String name) {
        System.out.println("Hellowrold, " + name + "!");
    }

    public void helloWrold(String name, int age) {
        if (age < 18) {
            System.out.println("Hi, " + name + "!");
        } else {
            System.out.println("Hellowrold, " + name + "!");
        }
    }
}

This method has the same name but different parameters. It is called method Overload.

Note: the return value types of method overloads are usually the same.

The purpose of method overloading is that methods with similar functions use the same name, which is easier to remember, so it is easier to call.

For example, the String class provides multiple overloaded methods indexOf(), which can find substrings:

  • int indexOf(int ch): search according to the Unicode code of the character;
  • int indexOf(String str): search by string;
  • int indexOf(int ch, int fromIndex): search according to characters, but specify the starting position;
  • int indexOf(String str, int fromIndex) looks up according to the string, but specifies the starting position.

4, Inherit

In the previous chapter, we have defined the pop class:

class Pople {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
}

Now, suppose you need to define a Student class, and the fields are as follows:

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

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
    public int getScore() { ... }
    public void setScore(int score) { ... }
}

After careful observation, it is found that the Student class contains the existing fields and methods of the pop class, but there is an additional score field and the corresponding getScore() and setScore() methods.

Can you not write duplicate code in Student?

At this time, inheritance comes in handy.

Inheritance is a very powerful mechanism in object-oriented programming. It can reuse code first. When we let students inherit from pop, students get all the functions of pop. We only need to write new functions for students.

Java uses the extends keyword to implement inheritance:

class Pople {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
}

class Student extends Pople {
    // Do not repeat the name and age fields / methods,
    // You only need to define the new score field / method:
    private int score;

    public int getScore() { ... }
    public void setScore(int score) { ... }
}

It can be seen that through inheritance, students only need to write additional functions and do not need to repeat code.

  Note: the subclass automatically obtains all fields of the parent class. It is strictly prohibited to define fields with the same name as the parent class!

In OOP terms, we call pop super class, parent class, base class, and Student subclass and extended class.

5, Inheritance tree

Notice that we didn't write extends when defining pop. In Java, if you do not explicitly write the extends class, the compiler will automatically add the extends Object. Therefore, any class, except Object, will inherit from a class. The following figure shows the inheritance tree of pop and Student:

   

Java only allows a class to inherit from a class, so a class has and only has a parent class. Only Object is special. It has no parent class.

Similarly, if we define a Teacher inherited from pop, their inheritance tree relationship is as follows:

 

protected

Inheritance has a feature that subclasses cannot access the private field or private method of the parent class. For example, the Student class cannot access the name and age fields of the pop class:

class Pople {
    private String name;
    private int age;
}

class Student extends Pople {
    public String hello() {
        return "Hello, " + name; // Compilation error: unable to access the name field
    }
}

This weakens the role of inheritance. In order for the subclass to access the fields of the parent class, we need to change private to protected. Fields decorated with protected can be accessed by subclasses:

class Pople {
    protected String name;
    protected int age;
}

class Student extends Pople {
    public String hello() {
        return "Hello, " + name; // OK!
    }
}

Therefore, the protected keyword can control the access rights of fields and methods within the inheritance tree. A protected field and method can be accessed by its subclasses and subclasses, which will be explained in detail later.

super

Super keyword indicates a parent class (superclass). When a subclass references a field of a parent class, you can use super.fieldName. For example:

class Student extends Pople {
    public String hello() {
        return "Hello, " + super.name;
    }
}

In fact, if you use super.name, or this.name, or name here, the effect is the same. The compiler automatically navigates to the name field of the parent class.

However, in some cases, super must be used. Let's take an example:

public class Test04 {
    public static void main(String[] args) {
        Student s = new Student("Wang Wu",15,90);

    }
}

class Pople {
    protected String name;
    protected int age;

    public Pople(String name,int age) {
        this.name = name ;
        this.age = age ;
    }
}

class Student extends Pople {
    protected int score;

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

 

  Running the above code will get a compilation error to the effect that the constructor of pop cannot be called in the constructor of Student.

This is because in Java, the first line of any class constructor must call the constructor of the parent class. If the constructor of the parent class is not explicitly called, the compiler will automatically add a super();, Therefore, the construction method of Student class is actually as follows:

class Student extends Pople {
    protected int score;

    public Student(String name, int age, int score) {
        super(); // Automatically call the constructor of the parent class
        this.score = score;
    }
}

However, the pop class does not have a parameterless constructor, so compilation fails.

The solution is to call a constructor that exists in the pop class. For example:

class Student extends Pople {
    protected int score;

    public Student(String name, int age, int score) {
        super(name, age); // Call the constructor pop (string, int) of the parent class
        this.score = score;
    }
}

So you can compile normally!

Therefore, we conclude that if the parent class does not have a default constructor, the child class must explicitly call super() and give parameters so that the compiler can locate an appropriate constructor of the parent class.

This also leads to another problem: that is, the subclass will not inherit the constructor of any parent class. The default construction method of subclasses is automatically generated by the compiler, not inherited.

Upward transformation

If the type of a reference variable is Student, it can point to an instance of Student type:

Student s = new Student();

If a variable of reference type is pop, it can point to an instance of pop type:

Pople p = new Pople();

Now the question arises: if Student inherits from pop, can a variable with reference type pop point to an instance of Student type?

Pople p = new Student(); // ???

Test it and you can find that this direction is allowed!

This is because Student inherits from pop, so it has all the functions of pop. If a variable of pop type points to an instance of Student type, there is no problem operating on it!

This assignment that safely changes a subclass type to a parent type is called upcasting.

The upward transformation is actually to safely change a subtype into a more abstract parent type:

Student s = new Student();
Pople p = s; // upcasting, ok
Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok

Downward transformation

Contrary to upward transformation, if a parent type is forcibly transformed into a child type, it is downward casting. For example:

Pople p1 = new Student(); // upcasting, ok
Pople p2 = new Pople();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!

If you test the above code, you can find:

Pop type p1 actually points to the Student instance, and pop type variable p2 actually points to the pop instance. During the downward transformation, the transformation from p1 to Student will succeed, because p1 does point to the Student instance, and the transformation from p2 to Student will fail, because the actual type of p2 is pop, and the parent class cannot be changed into a child class, because the child class has more functions than the parent class, and more functions cannot be changed out of thin air.

Therefore, the downward transformation is likely to fail. In case of failure, the Java virtual opportunity reports ClassCastException.

In order to avoid downward transformation errors, Java provides the instanceof operator, which can first judge whether an instance is of a certain type:

Pople p = new Pople();
System.out.println(p instanceof Pople); // true
System.out.println(p instanceof Student); // false

Student s = new Student();
System.out.println(s instanceof Pople); // true
System.out.println(s instanceof Student); // true

Student n = null;
System.out.println(n instanceof Student); // false

Instanceof actually determines whether the instance pointed to by a variable is a specified type or a subclass of this type. If a reference variable is null, the judgment of any instanceof is false.

Using instanceof, you can judge the following before the downward Transformation:

Pople p = new Student();
if (p instanceof Student) {
    // Only by judging success can we transition downward:
    Student s = (Student) p; // It will succeed
}

Starting from Java 14, after judging instanceof, it can be directly transformed into a specified variable to avoid forced transformation again. For example, for the following code:

Object obj = "hello";
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase());
}

It can be rewritten as follows:

public class Main {
    public static void main(String[] args) {
        Object obj = "hello";
        if (obj instanceof String s) {
            // You can directly use the variable s:
            System.out.println(s.toUpperCase());
        }
    }
}

When using instanceof variable to judge and convert to the syntax of the specified type variable, you must turn on the compiler switches -- source 14 and -- enable preview.

Distinguish between inheritance and composition

When using inheritance, we should pay attention to logical consistency.

Consider the following Book classes:

class Book {
    protected String name;
    public String getName() {...}
    public void setName(String name) {...}
}

This Book class also has a name field, so can we let students inherit from Book?

class Student extends Book {
    protected int score;
}

Obviously, logically speaking, this is unreasonable. Student s should not inherit from Book, but from pop.

The reason is that students are a kind of pop. They are related to is, while students are not books. In fact, the relationship between Student and Book is a has relationship.

The has relationship should not use inheritance, but use combination, that is, students can hold a Book instance:

class Student extends Pople {
    protected Book book;
    protected int score;
}

Therefore, inheritance is an is relationship and composition is an has relationship.

5. Abstract class

Due to polymorphism, each subclass can override the methods of the parent class, for example:

class Pople {
    public void run() { ... }
}

class Student extends Pople {
    @Override
    public void run() { ... }
}

class Teacher extends Pople {
    @Override
    public void run() { ... }
}

Both Student and Teacher derived from the pop class can override the run() method.

If the run() method of the parent class Pop has no practical significance, can the execution statement of the method be removed?

class Pople {
    public void run(); // Compile Error!
}

The answer is no, which will lead to compilation errors, because when defining a method, the statement of the method must be implemented.

Can you remove the run() method of the parent class?

The answer is still no, because removing the run() method of the parent class will lose its polymorphism. For example, runTwice() cannot compile:

public void runTwice(Pople p) {
    p.run(); // Pop does not have a run() method, which will cause compilation errors
    p.run();
}

If the method of the parent class does not need to implement any function, but only to define the method signature so that the child class can override it, the method of the parent class can be declared as an abstract method:

class Pople {
    public abstract void run();
}

Declaring a method as abstract means that it is an abstract method and does not implement any method statements. Because the abstract method itself cannot be executed, the Person class cannot be instantiated. The compiler will tell us that the Person class cannot be compiled because it contains abstract methods.

The Person class itself must also be declared abstract to compile it correctly:

abstract class Pople {
    public abstract void run();
}

abstract class

If a class defines a method but does not specifically execute code, the method is an abstract method, which is decorated with abstract.

Because the abstract method cannot be executed, this class must also be declared as an abstract class.

A class decorated with abstract is an abstract class. We cannot instantiate an abstract class:

Pople p = new Pople(); // Compilation error

What is the use of abstract classes that cannot be instantiated?

Because the abstract class itself is designed to be inherited only, the abstract class can force its subclass to implement its defined abstract methods, otherwise the compilation will report an error. Therefore, the abstract method is actually equivalent to defining the "specification".

For example, if the pop class defines the abstract method run(), the run() method must be overridden when implementing the subclass Student:

public class Test04 {
    public static void main(String[] args) {
        Pople p =new Student();
        p.run();
   }
}

abstract  class Pople {
    public abstract void run();
}

class Student extends Pople{
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

 

Abstract oriented programming

When we define the abstract class pop and the specific Student and Teacher subclasses, we can refer to the specific subclass instances through the abstract class pop type:

Pople s = new Student();
Pople t = new Teacher();

The advantage of this kind of reference to abstract classes is that we make method calls on them and don't care about the specific subtypes of pop type variables:

// Do not care about the specific subtype of the pop variable:
s.run();
t.run();

If the same code refers to a new subclass, we still don't care about the specific type:

// Similarly, I don't care how the new subclass implements the run() method:
Pople e = new Employee();
e.run()

This way of referring to high-level types as much as possible and avoiding referring to actual subtypes is called abstract oriented programming.

The essence of abstract oriented programming is:

  • The upper layer code only defines specifications (e.g. abstract class Person);
  • Business logic can be implemented without subclasses (normal compilation);
  • The specific business logic is implemented by different subclasses, and the caller does not care

Posted by CMellor on Thu, 16 Sep 2021 19:36:37 -0700