Java Programming Ideas Study Notes (7)

Reusable classes

Reusing code is one of the capabilities of Java.

In Java code reuse is revolved around the class, do not need to create a new class, to use this code, without having to start over to write this function, as long as the reference and call someone else to write good, debugging good class, usually have two kinds of methods, to note that these two methods are without breaking existing code, but direct call, or use inheritance:

  • The first method, which creates an object of an existing class in a new class, is called composition. This method simply re-invokes the functionality of the existing program code.

  • The second method creates a new class based on the type of an existing class. Instead of changing the form of an existing class, take the form of an existing class and add new code to it. This approach is called inheritance, and the compiler can do most of the work. Inheritance is one of the cornerstones of object-oriented programming, which reduces a lot of work for developers and achieves resource sharing and extension to a certain extent.

Both methods, in terms of composition and inheritance, generate new types from existing types.

combination

Composition techniques, which simply place object references in a new class.

Example:

class WaterSource {`private String s;
    WaterSource() {
    System.out.println("WaterSource()");
    s = "Constructed";`
}
    public String toString(a) { returns; }}Copy the code
public class SprinklerSystem {
        private String valve1, valve2, valve3, valve4;
        private WaterSource source = new WaterSource();
        private int i;
        private float f;
        public String toString(a) {
        return"valve1 = " + valve1 + "" +
        "valve2 = " + valve2 + "" +
        "valve3 = " + valve3 + "" +
        "valve4 = " + valve4 + "\n" +
        "i = " + i + "" + "f = " + f + "" +
        "source = " + source;
        }
        public static void main(String[] args) {
            SprinklerSystem sprinklers = newSprinklerSystem(); System.out.println(sprinklers); }}Copy the code

The compiler does not simply create a default object for each reference if it wants to initialize it. This can be done in the following places in the code:

  • 1 where objects are defined, which means they can always be initialized before the constructor is called.

  • 2 is in the constructor of the class

  • 3 Just before you are about to use these objects.

  • 4 Initialize the vm

Example:

public class Soap {

    private String s;
    Soap() {
        print("Soap() Constructed");
        s = "Constructed";
    }
    public String toString(a) { returns; }}Copy the code
public class Bath {

    private String // Initialize the object where it is defined
            s1 = "Happy",
            s2 = "Happy",
            s3, s4;
    private Soap castille;
    private int i;
    private float toy;
    public Bath(a) {
        print("Inside Bath()");
        s3 = "Joy";  // Initialize in the constructor of the class
        toy = 3.14 f;
        castille = new Soap();
    }
    // Example Initialization
    { i = 47; }
    public String toString(a) {
        if(s4 == null) // Lazy initialization
            s4 = "Joy";
        return
                "s1 = " + s1 + "\n" +
                        "s2 = " + s2 + "\n" +
                        "s3 = " + s3 + "\n" +
                        "s4 = " + s4 + "\n" +
                        "i = " + i + "\n" +
                        "toy = " + toy + "\n" +
                        "castille = " + castille;
    }
    public static void main(String[] args) {
        Bath b = newBath(); print(b); }}Copy the code

inheritance

Composite syntax is more straightforward, but inheritance uses a special syntax that is done with the extends keyword.

Example:

public class Cleanser {

    private String s = "Cleanser";

    public void append(String a) {
        s += a;
    }

    public void dilute(a) {
        append(" dilute()");
    }

    public void apply(a) {
        append(" apply()");
    }

    public void scrub(a) {
        append(" scrub()");
    }

    public String toString(a) {
        return s;
    }

    public static void main(String[] args) {
        Cleanser x = newCleanser(); x.dilute(); x.apply(); x.scrub(); print(x); }}Copy the code
public class Detergent extends Cleanser{

In Cleaner there is a set of methods: Append (),dilute(),apply(), Scrub (),toString()
// Because Detergent is inherited from Cleanser, it automatically acquires these methods,
// Although you don't see the definition of these methods in the Detergent


    // It is possible to use the method defined in the base class and modify it
    public void scrub(a) {
        append(" Detergent.scrub()");

// The Scrub method cannot be called directly because doing so would generate recursion
// Java uses the super keyword to mean a superclass
        super.scrub(); // Call the method of the original base class
    }

// In the inheritance process, you do not have to use the methods of the base class. You can also add new methods to the derived class
    public void foam(a) { append(" foam()"); }


    // Test the new class:
    public static void main(String[] args) {
        Detergent x = new Detergent();
        x.dilute();
        x.apply();
        x.scrub();
        x.foam();
        print(x);
        print("Testing base class:");
        System.out.println("-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --"); Cleanser.main(args); }}Copy the code

Initialize the base class

Inheritance is not simply duplicate the base class interface, when creating a derived class object, the object contains a base class subobject, the child object with the objects you create directly with the base class is the same, the difference between the two the latter comes from the outside, and the base class subobject wrapped inside the derived class object.

Proper initialization of base-class subobjects is also important, guaranteed by calling the base-class constructor in the constructor to perform the initialization.

Java automatically inserts calls to the base class constructor in the constructor summary of the exported class.

Example:

class Art {
    Art() { print("Art constructor"); }}class Drawing extends Art {
    Drawing() { print("Drawing constructor"); }}public class Cartoon extends Drawing {
        public Cartoon(a) { print("Cartoon constructor"); }
        public static void main(String[] args) {
        Cartoon x = newCartoon(); }}Copy the code

As you can see, the build process spreads out from the base class, so the base class is initialized before the exported class constructor can access it.

With argument constructor

For a constructor with arguments, you must write a statement that calls the base class constructor using the keyword super.

Example:


class Game {
        Game(int i) {
            print("Game constructor"); }}class BoardGame extends Game {
        BoardGame(int i) {
    // An error is reported if the display call is not made, because there is no default constructor in Game
        super(i);
        print("BoardGame constructor"); }}public class Chess extends BoardGame {
        Chess() {
        super(11);
        print("Chess constructor");
        }
        public static void main(String[] args) {
        Chess x = newChess(); }}Copy the code

The agent

Agency is a happy medium between inheritance and combination. Because when we place a member object in the class to be constructed (such as composition), we also expose all the methods of that member object (such as inheritance) in the new class.

The proxy is: to use A method of class A, without changing its structure, create object A of class A in A new class B, and create method fb in B. Inside the method, A calls the method of class A, but when used, the object of B calls its own method fb.

Example:

public class SpaceShip extends SpaceShipControls{

// Construct spacecraft by inheritance
    
But at this time SpaceShipControls is contained in SpaceShipControls, all of SpaceShipControls
// Methods were exposed in SpaceShip
    
    
    private String name;
    public SpaceShip(String name) { this.name = name; }
    public String toString(a) { return name; }
    public static void main(String[] args) {
        SpaceShip protector = new SpaceShip("NSEA Protector");
        protector.forward(100); }}Copy the code
public class SpaceShipControls {

// The control module of the spacecraft
    void up(int velocity) {}
    void down(int velocity) {}
    void left(int velocity) {}
    void right(int velocity) {}
    void forward(int velocity) {}
    void back(int velocity) {}
    void turboBoost(a) {}}Copy the code
public class SpaceShipDelegation {

// Use proxies to resolve problems


    private String name;
    private SpaceShipControls controls =
            new SpaceShipControls();
    public SpaceShipDelegation(String name) {
        this.name = name;
    }
    // Delegated methods:
    public void back(int velocity) {
        controls.back(velocity);
    }
    public void down(int velocity) {
        controls.down(velocity);
    }
    public void forward(int velocity) {
        controls.forward(velocity);
    }
    public void left(int velocity) {
        controls.left(velocity);
    }
    public void right(int velocity) {
        controls.right(velocity);
    }
    public void turboBoost(a) {
        controls.turboBoost();
    }
    public void up(int velocity) {
        controls.up(velocity);
    }
    public static void main(String[] args) {
        SpaceShipDelegation protector =
                new SpaceShipDelegation("NSEA Protector");
        protector.forward(100); }}Copy the code

The name of the block

If Java’s base class has a method name that has been overloaded many times, redefining that method name in the exported class does not mask any version of that method in the base class. Therefore, the overloading mechanism works whether a method is defined in that layer or its base class.

Example:

class Homer {
        char doh(char c) {
            print("doh(char)");
            return"D"; }float doh(float f) {
                print("doh(float)");
                return 1.0 f; }}class Milhouse {}


class Bart extends Homer {
        void doh(Milhouse m) {
                print("doh(Milhouse)"); }}public class Hide {
        public static void main(String[] args) {
                Bart b = new Bart();
                b.doh(1); B.d oh (" x "); b.doh(1.0 f);
                b.doh(newMilhouse()); }}Copy the code

Protected keyword

The keyword protected indicates that “as far as the class user is concerned, this is private,” but it is accessible to any exported classes that inherit from this class or any other classes that reside in the same package.

Example:

public class Villain {

    private String name;
    
    protected void set(String nm) { name = nm; }
    
    public Villain(String name) { this.name = name; }
    
    public String toString(a) {
        return "I'm a Villain and my name is"+ name; }}Copy the code
public class Orc extends Villain{

    private int orcNumber;

    public Orc(String name, int orcNumber) {
        super(name);
        this.orcNumber = orcNumber;
    }

// As you can see, change can access set because it is protected
    public void change(String name, int orcNumber) {
        set(name); // Available because it’s protected
        this.orcNumber = orcNumber;
    }

    public String toString(a) {
        return "Orc " + orcNumber + ":" + super.toString();
    }

    public static void main(String[] args) {
        Orc orc = new Orc("Limburger".12);
        print(orc);
        orc.change("Bob".19); print(orc); }}Copy the code

The final keyword

The final data

Sometimes it is useful to have data that is constant, for example:

  • A compile-time constant that never changes

  • 2 A value that is initialized at run time and that you do not want to change

For compile-time constants, the compiler can substitute the constant into any calculation it may use, that is, it can execute at compile time. In Java, such constants must be basic data types and be represented as final. When this constant is defined, it must be assigned.

A domain that is both static and final occupies only a portion of storage that cannot be changed

For primitive types, final holds the value constant, while for object references, final holds the reference constant. Once a reference has been initialized to one object, it cannot be changed to another. However, the object itself can be modified.

Example:

public class Value {

    int i; // Package access permission

    public Value(int i) { this.i = i; }}Copy the code
public class FinalData {

    private static Random rand = new Random(47);


    private String id;


    public FinalData(String id) { this.id = id; }

    // Can be used as compile-time constants
    private final int valueOne = 9;


    // By convention, fields that are static and final are capitalized
// Can be used as compile-time constants
    private static final int VALUE_TWO = 99;

    // The more typical way to define constants is to use the public modifier
    public static final int VALUE_THREE = 39;

    // Cannot be used as compile-time constants
    private final int i4 = rand.nextInt(20);

    static final int INT_5 = rand.nextInt(20);

    private Value v1 = new Value(11);

    private final Value v2 = new Value(22);

    private static final Value VAL_3 = new Value(33);

    // Arrays:
    private final int[] a = { 1.2.3.4.5.6 };

    public String toString(a) {
        return id + ":" + "i4 = " + i4 + ", INT_5 = " + INT_5;
    }


    public static void main(String[] args) {

        FinalData fd1 = new FinalData("fd1");

/ /! fd1.valueOne++; // Error: can't change value

        fd1.v2.i++; / / Object isn 't constant!

        fd1.v1 = new Value(9); // OK -- not final

        for(int i = 0; i < fd1.a.length; i++)
            fd1.a[i]++; / / Object isn 't constant!
/ /! fd1.v2 = new Value(0); / / Error: Can 't
/ /! fd1.VAL_3 = new Value(1); // change reference
/ /! fd1.a = new int[3];

        print(fd1);

        print("Creating new FinalData");

        FinalData fd2 = new FinalData("fd2");

        print(fd1);

        print(fd2);
    }


// Just because data is final does not mean that its value is known at compile time
This is illustrated by initializing i4 and INT_5 at run time with randomly generated values

In fd1 and fd2, the value of i4 is unique, but the value of INT_5 cannot be changed by creating a second object,
// This is because it is static and is initialized when it is printed.

}
Copy the code

Blank final

Java allows the generation of “blank final,” which is a field that is declared final but not given an initial value. In either case, the compiler ensures that a blank final must be initialized before it can be used.

Final fields ina class can vary from object to object, yet remain constant.

Example:

public class Poppet {

    private int i;
    Poppet(intii) { i = ii; }}Copy the code

public class BlankFinal {

    private final int i = 0; // Initialized final

    private final int j; // Blank final

    private final Poppet p; // Blank final reference

    // Blank finals MUST be initialized in the constructor:
    public BlankFinal(a) {
        j = 1; // Initialize blank final
        p = new Poppet(1); // Initialize blank final reference
    }
    public BlankFinal(int x) {
        j = x; // Initialize blank final
        p = new Poppet(x); // Initialize blank final reference
    }
    public static void main(String[] args) {
        new BlankFinal();
        new BlankFinal(47); }}Copy the code

Final must be assigned by expression either at the definition of the field or in each constructor

The final parameter

Java allows parameters to be specified as final declaratively in parameter lists. This means that you cannot change the object to which a parameter reference refers in a method.

Example:

public class Gizmo {

    public void spin(a) {}}Copy the code

public class FinalArguments {

    void with(final Gizmo g) {
/ /! g = new Gizmo(); // Illegal -- g is final
    }
    void without(Gizmo g) {
        g = new Gizmo(); // OK -- g not final
        g.spin();
    }
    // void f(final int i) { i++; } / / Can 't change
// You can only read from a final primitive:
    int g(final int i) { return i + 1; }
    public static void main(String[] args) {
        FinalArguments bf = new FinalArguments();
        bf.without(null);
        bf.with(null); }}Copy the code

The final method

The purpose of using final methods is to lock the method in case any inherited classes change its meaning.

Final and private

All private methods in the class are implicitly final and cannot be overridden because they are not available.

Example:



class WithFinals {
        // Identical to "private" alone:
        private final void f(a) { print("WithFinals.f()"); }
        // Also automatically "final":
        private void g(a) { print("WithFinals.g()"); }}class OverridingPrivate extends WithFinals {
        private final void f(a) {
        print("OverridingPrivate.f()");
    }
        private void g(a) {
        print("OverridingPrivate.g()"); }}class OverridingPrivate2 extends OverridingPrivate {
        public final void f(a) {
        print("OverridingPrivate2.f()");
        }
        public void g(a) {
        print("OverridingPrivate2.g()"); }}public class FinalOverridingIllusion {
        public static void main(String[] args) {
                OverridingPrivate2 op2 = new OverridingPrivate2();
                op2.f();
                op2.g();
                // You can upcast:
                OverridingPrivate op = op2;
                // But you can’t call the methods:
                / /! op.f();
                / /! op.g();
                // Same here:
                WithFinals wf = op2;
                / /! wf.f();
                / /! wf.g();}}Copy the code

Overwriting is only possible if a method is part of the interface of the base class; if a method is private, it is not part of the interface of the base class.

Final class

When the whole of a class is defined as final, it indicates that you do not intend to inherit the class and do not allow others to do so.

Initialization and class loading

The compiled code for each class in Java exists in its own separate file, which is loaded only when the program needs to be used. In general, “the code for a class is loaded on first use.” This usually means that loading occurs when the first object of the class is created, but loading also occurs when a static field or static method is accessed.

Inheritance and initialization

Example:

public class Insect {

    private int i = 9;

    protected int j;

    Insect()
    {
        print("i = " + i + ", j = " + j);
        j = 39;
    }


    private static int x1 =
            printInit("static Insect.x1 initialized");

    static int printInit(String s)
    {
        print(s);
        return 47; }}Copy the code

public class Beetle extends Insect{



    private int k = printInit("Beetle.k initialized");


    public Beetle(a) {
        print("k = " + k);
        print("j = " + j);
    }


    private static int x2 =
            printInit("static Beetle.x2 initialized");


    public static void main(String[] args) {
        print("Beetle constructor");
        Beetle b = new Beetle();
    }
// The first thing that happens when you run the program is you try to access Beetle. Main, and the loader starts to find Beetle
// Class. While loading it, the compiler notices that it has a base class, so it continues loading regardless of whether you have one
// To generate an object of the base class, this has to happen.
// Static initialization (i.e. Insect) in the base class is performed, then the next derived class, and so on.
}


Copy the code