2022.2.17
problem
-
As you will see in Chapter 8, when inheritance is introduced, the class (subclass) derived from inheritance can access the protected members of the parent class and the public members (but not the private members). Only when two classes are in the same package can it access the package access members of the parent class. But now don't worry about inheritance and protected. 7.2.1
I didn't understand what this sentence meant
A: it simply means to emphasize the access rights of the default package
-
However, just because an object's reference is private in a class does not mean that other objects cannot have a public reference to the same object. (see Chapter 2 of the advanced volume for alias issues.) 7.2.3
I didn't understand that either
Chapter 7 implementation of hiding
-
The same is true for methods that are only used to implement classes but are not provided to customer programmers for direct use.
The method here should correspond to our private method
-
When compiling a Java file, each class in the file will have an output file. The name of the output file is where it is The name of the corresponding class in the java file, but the extension is class. So you can start with a small amount Java files get quite a lot of Class file. If you have written a program in a compiled language, you may be used to the compiler outputting an intermediate form (usually an obj file), and then using a linker or library generator to package it with other similar files to create an executable file. Java is not like this. In Java, a runnable program is a pile of Class files, which can be packaged and compressed into a Java archive file (jar) using a jar archive. The Java interpreter is responsible for finding, loading, and interpreting these files.
-
import static onjava.Range.*;
This way of writing allows you to call static methods directly
fun()
Instead of writing like this
Class name.Method name(),
-
It's amazing that the use of protected is to use the methods of inherited base classes outside the package, which really surprises me
-
It is not a reference to a public class. If it is used outside the package, the compiler cannot find it
-
Also, the protected modifier cannot be used for classes
protected class one { protected void f(){ System.out.println("hello"); } }
I can't compile like this
-
Sometimes the creator of the base class wants to assign the access rights of specific members to subclasses instead of all classes. At this time, protected can play a role. Protected also provides package access, that is, other classes in the same package can also access protected elements.
If you review the file cookie Java, you will know that the following classes cannot call the package access permission member bite():
// hiding/ChocolateChip.java // A member of package access cannot be invoked in another package import hiding.dessert.*; public class ChocolateChip extends Cookie { public ChocolateChip() { System.out.println("ChocolateChip constructor"); } public void chomp() { //- bite(); // Unable to access bite } public static void main(String[] args) { ChocolateChip x = new ChocolateChip(); x.chomp(); } } /* Output: Cookie constructor ChocolateChip constructor */
If there is a method bite() in the Cookie class, this method also exists in any class that inherits the Cookie. But bite() has only package access and is in another package, so it cannot be used in the current package. You can change it to public, but then everyone can access it, which may not be what you want. If you change the class Cookie as follows:
// hiding/cookie2/Cookie.java package hiding.cookie2; public class Cookie { public Cookie() { System.out.println("Cookie constructor"); } protected void bite() { System.out.println("bite"); } }
In this way, any class that inherits cookies can access bite():
// hiding/ChocolateChip2.java import hiding.cookie2.*; public class ChocolateChip2 extends Cookie { public ChocolateChip2() { System.out.println("ChocolateChip2 constructor"); } public void chomp() { bite(); } // protected method public static void main(String[] args) { ChocolateChip2 x = new ChocolateChip2(); x.chomp(); } } /* Output: Cookie constructor ChocolateChip2 constructor bite */
At this time, although bite() also has package access rights, it is not public.
-
To sum up, if different packages are regarded as users, then public is a bus If the class is regarded as a user, the default class is the bus. The default class at least ensures that the references in the same package are still available, so that references cannot be created (note that I didn't mention the constructor here)
-
Then, it doesn't make sense that the access rights of variables are greater than those of classes
-
Note that the class cannot be private (which will make it inaccessible to any class other than the class) or protected 5. Therefore, there are only two options for class access: package access and public. If you want to prevent access to this class, you can set all its constructors to private to prevent others from creating objects of this class, and you can create objects in the static methods of this class:
In fact, inner classes can be private or protected, but this is a special case. These topics are covered in Chapter 11.
-
The default variable can only be used casually in the same package, that is, it is the bus of the class under the same package
package example; public class one { int i=9; public static void main(String[] args){ System.out.println(new two().i); } } class two { int i=0; }
package Test; import example.one; public class test { public static void main(String[] args){ one a = new one(); System.out.println(a.i); } }
You will find that the following code doesn't compile well
-
Note that access control focuses on the relationship between library developers and external customers of the library, which is also a way of communication. However, this is not the case in many cases. For example, you write all the code yourself, or you work closely with a small team and put all the content in the same package. These situations are a different way of communication, and strict compliance with access rules may not be the best choice. The default (package) access may be sufficient.
Chapter 8 reuse
-
There are four ways to initialize references.
- When defining objects. This means that they will always be initialized before calling the constructor.
- In the constructor of this class.
- Before the object is actually used. This is commonly referred to as lazy initialization. It can reduce overhead when object creation is expensive and does not need to be created every time.
- Use instance initialization.
The following are examples of these four methods:
// reuse/Bath.java // Constructor initialization using composition class Soap { private String s; Soap() { System.out.println("Soap()"); s = "Constructed"; } @Override public String toString() { return s; } } public class Bath { private String // Initialize at definition time s1 = "Happy", s2 = "Happy", s3, s4; private Soap castile; private int i; private float toy; public Bath() { System.out.println("Inside Bath()"); s3 = "Joy"; toy = 3.14f; castile = new Soap(); } // Instance initialization { i = 47; } @Override public String toString() { if(s4 == null) // Delay initialization s4 = "Joy"; return "s1 = " + s1 + "\n" + "s2 = " + s2 + "\n" + "s3 = " + s3 + "\n" + "s4 = " + s4 + "\n" + "i = " + i + "\n" + "toy = " + toy + "\n" + "castile = " + castile; } public static void main(String[] args) { Bath b = new Bath(); System.out.println(b); } } /* Output: Inside Bath() Soap() s1 = Happy s2 = Happy s3 = Joy s4 = Joy i = 47 toy = 3.14 castile = Constructed */
-
If the access permission modifier is omitted, the permission of the member is package access permission by default, and only members in the package are allowed to access. Therefore, within this package, anyone can use these methods if there is no access modifier. For example, Detergent has no problem. However, if a class from another package wants to inherit cleaner, it can only access public members. Therefore, considering inheritance, as a general rule, you should set all fields to private and all methods to public (as you will learn later, protected members also allow subclass access). In certain circumstances, you have to make adjustments, but in general, this is a useful guideline.
Note that the protected modifier is specifically used for the public modifier, and the default modifier has little effect on it
public class test extends two { public static void main(String[] args){ one a = new one(); System.out.println(a.i); two b = new two(); } }
package example; public class one { int i=9; public static void main(String[] args){ System.out.println(new two().i); } } class two { int i=0; protected void f(){ System.out.println("hello"); } }
Example. Is displayed Two is not public in example; It cannot be accessed from the external package, and the default variables cannot be accessed outside the package. In addition, an error will be reported if you import the two class
package Test; import example.one; public class test extends one { public static void main(String[] args){ one a = new one(); new test().f(); } }
package example; public class one { int i=9; public static void main(String[] args){ System.out.println(new two().i); } protected void f(){ System.out.println("hello"); } } class two { int i=0; protected void f(){ System.out.println("hello"); } }
As you can see in scrub(), you can use and modify the methods defined in the base class. In this example, you may want to call the inherited base class method from the new version of the method. But in scrub(), you can't simply call scrub(), because it produces recursive calls. To solve this problem, Java provides the super keyword to refer to the "super class" (base class) inherited by the current class. Therefore, the expression super scrub() called the scrub() method of the base class version.
This is mainly because it is written like this
f(){ f() }
If not, it's not necessary, but it's better to block it. After all, ambiguity may arise
-
There are now two classes involved: the base class and the subclass. It can be confusing to imagine an object generated by a subclass. Externally, the new class has the same interface as the base class, and there may be some additional methods and fields. But inheritance is more than just copying the interface of the base class. When creating a subclass object, it contains a subobject of the base class. This sub object is the same as the object created directly from the base class. Just from the outside, the sub objects of the base class are wrapped in the sub class objects.
-
A call to a base class constructor must be the first operation of a subclass constructor
-
// reuse/SpaceShipDelegation.java public class SpaceShipDelegation { private String name; private SpaceShipControls controls = new SpaceShipControls(); public SpaceShipDelegation(String name) { this.name = name; } // Entrustment method: 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() { 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); } }
public class SpaceShipControls { 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() {} }
// reuse/DerivedSpaceShip.java public class DerivedSpaceShip extends SpaceShipControls { private String name; public DerivedSpaceShip(String name) { this.name = name; } @Override public String toString() { return name; } public static void main(String[] args) { DerivedSpaceShip protector = new DerivedSpaceShip("NSEA Protector"); protector.forward(100); } }
After reading his code for a long time, I finally understood what he meant, that is, to hide a class. The defined public is actually similar to an empty shell, in which the private object actually controls the method. How to say, this is written in logic. The realm is too high, so I can only understand this
In fact, I feel that the entrustment model seems to be the predecessor of the agency model, although they are very different
-
I didn't understand 8.4.1. I thought, do you want to clean up according to the order of the stack? It looks too much like entering and leaving the stack
other
-
Now think about it. In fact, as a static method, the constructor is really magical. It can access non-static variables. Now the understanding of the constructor should be a special method, because it can modify the private variables of the class, which will lead to a certain degree of package damage, so its use is relatively strict
When you think about it, it is precisely because the constructor is actually a static method that you can call another constructor method in the constructor method, although you can't call two constructors at the same time, and the constructor must appear at the top of the constructor method
public class test { int i ,j,m,n; public test(int i){ this.i=i; } public test(int i,int j,int m){ this(i); this.m=m; this.j=j; } public test(int i,int j){ this(i); this.j=j; } void f(){ System.out.println("i="+i+"j="+j+"m="+m); } public static void main(String[] args){ new test(5,3,2).f(); } }
Here I focus on understanding this method and this static method In fact, if you understand the concept of static method, you will understand the essence of this() method. This constructor method is really ingenious
-
Although static methods can generate internal classes, there is no need to think about accessing the outside of this internal class
public class test { public static void f(){ class a { public a(){ System.out.println("hello"); } } new a(); } public static void main(String[] args){ a one = new a(); test.f(); } }
The above code will show that object a cannot be found, so even if I use the constructor of a as a public object, the outside world will not want to access this object, not because initialization cannot be done, but because this class cannot be found
2022.2.18
Chapter 8 reuse
-
Both composition and inheritance place child objects in a new class (composition does this explicitly, while inheritance does it implicitly). You may want to know the difference between the two and how to make a choice between the two.
Composition should be used when you want to use the functionality of an existing class in a new class instead of its interface. That is, embed an object (usually private) in the new class to implement its own characteristics. Users of the new class see the interface defined by the new class, not the interface of the embedded object.
It is sometimes reasonable to allow users of the class to directly access the members obtained through composition in a new class. To do this, you can set the member object to public (you can think of it as a "semi delegate"). The member object hides its own implementation, so this approach is safe. When users know that you are assembling a bunch of components, it will be easier to understand your interface. The car object is a good example:
// reuse/Car.java // Use common objects to implement composition class Engine { public void start() {} public void rev() {} public void stop() {} } class Wheel { public void inflate(int psi) {} } class Window { public void rollup() {} public void rolldown() {} } class Door { public Window window = new Window(); public void open() {} public void close() {} } public class Car { public Engine engine = new Engine(); public Wheel[] wheel = new Wheel[4]; public Door left = new Door(), right = new Door(); // Coupe public Car() { for(int i = 0; i < 4; i++) wheel[i] = new Wheel(); } public static void main(String[] args) { Car car = new Car(); car.left.window.rollup(); car.wheel[0].inflate(72); } }
-
// reuse/Orc.java // protected keyword class Villain { private String name; protected void set(String nm) { name = nm; } Villain(String name) { this.name = name; } @Override public String toString() { return "I'm a Villain and my name is " + name; } } public class Orc extends Villain { private int orcNumber; public Orc(String name, int orcNumber) { super(name); this.orcNumber = orcNumber; } public void change(String name, int orcNumber) { set(name); // Method is available because it is protected this.orcNumber = orcNumber; } @Override public String toString() { return "Orc " + orcNumber + ": " + super.toString(); } public static void main(String[] args) { Orc orc = new Orc("Limburger", 12); System.out.println(orc); orc.change("Bob", 19); System.out.println(orc); } } /* Output: Orc 12: I'm a Villain and my name is Limburger Orc 19: I'm a Villain and my name is Bob */
I'm surprised that super() is not used in line 20, so the one in advance should be to prevent the following ambiguous problem, because there is f() in the f() method of this class
f(){ f(); }
Also, here's how to call the toString() of the base class;
-
Constants are useful for two reasons:
- It can be a compile time constant that will never change;
- It can be a value initialized at run time and you don't want it to be changed.
For compile time constants, the compiler can "fold" the constant value into the calculation; That is, the calculation can be done at compile time, which saves some runtime overhead. In Java, these constants must be basic types and represented by the final keyword. You must provide a value when defining a constant.
-
When the final keyword is used with object references rather than basic types, its meaning can be confusing. For basic types, final makes the value constant, but for object references, final makes the reference constant. Once a reference is initialized as an object, it can never be changed to point to another object. However, the object itself can be modified. Java does not provide a way to make objects constant. (however, you can write classes to make objects have constant effects.) This restriction also applies to arrays, which are also objects.
-
public class one { String s1 = "hello"; public one (String s1){ this.s1 = s1; } public static void main(String[] args){ System.out.println(new two("second")); } }
public class two extends one{ public two (String s1){ super(s1); this.s1="other"; } @Override public String toString() { return s1; } }
The final output is other, but add this before s1, or it is second
-
// reuse/FinalArguments.java // Use final in method parameters class Gizmo { public void spin() {} } 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++; } // Cannot change // Only read operations can be performed on a final basic type int g(final int i) { return i + 1; } public static void main(String[] args) { FinalArguments bf = new FinalArguments(); bf.without(null); bf.with(null); } }
-
Override occurs only if the method is part of the base class interface. In other words, you must be able to transform an object up to its base class type and call the same method (you will understand this more in the next chapter). If a method is private, it is not part of the base class interface. It's just code hidden in the class, but it just happens to have the same name. Even if a public, protected or package access method with the same name is created in the subclass, it has no connection with the method with the same name in the base class. You didn't rewrite the method, you just created a new method. The private method is inaccessible and can effectively hide itself, so it won't affect anything except the code organization of the class that defines it.
-
The field of final class can be final or not, depending on individual choice. Whether the class is defined as final or not, the same rules apply to the final definition of the field. However, because the final class prohibits inheritance, all its methods are implicit final because they cannot be overridden. You can include the final modifier in the method of the final class, but it doesn't add any meaning.
-
// reuse/Beetle.java // The whole process of initialization class Insect { private int i = 9; protected int j; Insect() { System.out.println("i = " + i + ", j = " + j); j = 39; } private static int x1 = printInit("static Insect.x1 initialized"); static int printInit(String s) { System.out.println(s); return 47; } } public class Beetle extends Insect { private int k = printInit("Beetle.k initialized"); public Beetle() { System.out.println("k = " + k); System.out.println("j = " + j); } private static int x2 = printInit("static Beetle.x2 initialized"); public static void main(String[] args) { System.out.println("Beetle constructor"); Beetle b = new Beetle(); } } /* Output: static Insect.x1 initialized static Beetle.x2 initialized Beetle constructor i = 9, j = 0 Beetle.k initialized k = 47 j = 39 */
When you run java Beetle, you will first try to access the static method Beetle Main(), so the loader will go to Beetle The compiled code of the Beetle class is found in the class file. When loading its code, the loader notices that there is a base class, and then it will load the base class. This happens whether or not you create an object of the base class. (you can try to comment out the object creation to verify it.)
If the base class has its own base class, the second base class will also be loaded, and so on. Next, the static initialization in the base class (in this case, insight) will be performed, followed by the next subclass, and so on. This is important because static initialization of subclasses may depend on proper initialization of base class members.
Now that all the necessary classes are loaded, you can create the object. First, all basic types in the object are set to their default values, and the object reference is set to null -- this is achieved by setting the memory in the object to binary zeros. Then call the base class constructor. The call here is automatic, but you can also specify the call of the base class constructor through the super keyword (which needs to be the first operation in the Beetle constructor). The base class constructor goes through the same process in the same order as the subclass constructor. After the base class constructor is completed, the instance variables of subclasses are initialized in text order. Finally, execute the rest of the subclass constructor.
Chapter 9 polymorphism
-
The action of associating a method call with a method body is called binding. Binding is performed before the program runs (if there are compilers and linkers, they will implement them), which is called pre binding. You may not have heard of this term before, because in process oriented languages, the default is early binding. For example, there is only one method call in C language, that is early binding.
The reason why the above program is confusing is mainly due to early binding. This is because when the compiler has only one Instrument reference, it cannot know which is the correct method to call.
The solution to this problem is called late binding, which means that binding occurs at run time and is based on the type of object. Late binding is also called dynamic binding or runtime binding. When a language implements late binding, there must be some mechanism at runtime to determine the type of object and call the appropriate methods. In other words, the compiler still does not know the type of object, but the method calling mechanism can find and call the correct method body. The late binding mechanism varies from language to language, but it is conceivable that some type of information must be placed in the object.
-
All method bindings in Java are late binding, unless the method is static or final (private method is implicitly final).
-
// polymorphism/shape/RandomShapes.java // A factory that randomly generates shapes package polymorphism.shape; import java.util.*; public class RandomShapes { private Random rand = new Random(47); public Shape get() { switch(rand.nextInt(3)) { default: case 0: return new Circle(); case 1: return new Square(); case 2: return new Triangle(); } } public Shape[] array(int sz) { Shape[] shapes = new Shape[sz]; // Fill the array with shapes for(int i = 0; i < shapes.length; i++) shapes[i] = get(); return shapes; } }
// polymorphism/Shapes.java // Polymorphism in Java import polymorphism.shape.*; public class Shapes { public static void main(String[] args) { RandomShapes gen = new RandomShapes(); // Perform polymorphic method calls for(Shape shape : gen.array(9)) shape.draw(); } } /* Output: Triangle.draw() Triangle.draw() Square.draw() Triangle.draw() Square.draw() Triangle.draw() Square.draw() Triangle.draw() Circle.draw() */
-
As a result, only non private methods can be rewritten, but pay attention to the illusion of rewriting private methods. It will not generate compiler warnings, but it will not perform the operations you may expect. For clarity, use a different name in the subclass than the private method of the base class.
-
// polymorphism/FieldAccess.java // Direct access to fields is determined at compile time class Super { public int field = 0; public int getField() { return field; } } class Sub extends Super { public int field = 1; @Override public int getField() { return field; } public int getSuperField() { return super.field; } } public class FieldAccess { public static void main(String[] args) { Super sup = new Sub(); // Upward transformation System.out.println("sup.field = " + sup.field + ", sup.getField() = " + sup.getField()); Sub sub = new Sub(); System.out.println("sub.field = " + sub.field + ", sub.getField() = " + sub.getField() + ", sub.getSuperField() = " + sub.getSuperField()); } } /* Output: sup.field = 0, sup.getField() = 1 sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0 */
-
Here, each class in the hierarchy contains member objects of types Characteristic and Description, which must also be destroyed. The disposal order should be opposite to the initialization order to prevent sub objects from relying on other objects. For fields, this means the reverse of the declaration order (because fields are initialized in the declaration order). For base classes (following the form of destructors in C + +), subclass cleaning is performed first, followed by base class cleaning. This is because subclasses may call some methods in the base class during cleanup. These methods may require the base class components to be in a living state, so they cannot be destroyed prematurely. The output shows that all parts of the Frog object are destroyed in the reverse order of creation.
I can understand why it is necessary to clean up in the reverse order to prevent some objects from being associated with other objects, such as the subclass of the base class. Deleting the subclass of the base class will lead to disability
-
// polymorphism/PolyConstructors.java // Constructors and polymorphisms // Will not produce the results you expect class Glyph { void draw() { System.out.println("Glyph.draw()"); } Glyph() { System.out.println("Glyph() before draw()"); draw(); System.out.println("Glyph() after draw()"); } } class RoundGlyph extends Glyph { private int radius = 1; RoundGlyph(int r) { radius = r; System.out.println( "RoundGlyph.RoundGlyph(), radius = " + radius); } @Override void draw() { System.out.println( "RoundGlyph.draw(), radius = " + radius); } } public class PolyConstructors { public static void main(String[] args) { new RoundGlyph(5); } } /* Output: Glyph() before draw() RoundGlyph.draw(), radius = 0 Glyph() after draw() RoundGlyph.RoundGlyph(), radius = 5 */
-
The actual initialization process is shown below.
- The storage space allocated to the object is initialized to binary zeros before anything else happens.
- Call the constructor as described above. At this time, the overridden draw() method will be called (yes, this occurs before the RoundGlyph constructor is called). Due to step 1, it will be found that the radius value is zero.
- Initialize members in the declared order.
- The body code of the subclass constructor is executed.
What I focus on here is to initialize to binary 0, because I have learned a little assembly and a little reverse. In my understanding, c language directly delimits one side area as the value of a variable, which is not necessarily what it is. java sets the value inside to 0 after delimiting the area
-
When writing constructors, there is a good rule: "use as few operations as possible to make the object enter the normal state. If it can be avoided, please do not call any other methods in this class." Only the final methods in the base class can be safely called in the constructor (this also applies to private methods, which are final by default). These methods cannot be rewritten, so this surprising problem does not arise.
-
Java 5 adds a covariant return type, which means that the return value of the rewritten method in the subclass can be a subtype of the return value of the base class method:
// polymorphism/CovariantReturn.java class Grain { @Override public String toString() { return "Grain"; } } class Wheat extends Grain { @Override public String toString() { return "Wheat"; } } class Mill { Grain process() { return new Grain(); } } class WheatMill extends Mill { @Override Wheat process() { return new Wheat(); } } public class CovariantReturn { public static void main(String[] args) { Mill m = new Mill(); Grain g = m.process(); System.out.println(g); m = new WheatMill(); g = m.process(); System.out.println(g); } } /* Output: Grain Wheat */
The main difference between Java 5 and its previous version is that its previous version forces the rewritten version of process() to return Grain instead of Wheat. Even though Wheat inherits from Grain, it is also a legal return type. Covariant return types allow more specific Wheat return types.
This is actually very interesting
-
// polymorphism/Transmogrify.java // Dynamically change the behavior of objects through combination (state design pattern) class Actor { public void act() {} } class HappyActor extends Actor { @Override public void act() { System.out.println("HappyActor"); } } class SadActor extends Actor { @Override public void act() { System.out.println("SadActor"); } } class Stage { private Actor actor = new HappyActor(); public void change() { actor = new SadActor(); } public void performPlay() { actor.act(); } } public class Transmogrify { public static void main(String[] args) { Stage stage = new Stage(); stage.performPlay(); stage.change(); stage.performPlay(); } } /* Output: HappyActor SadActor */
The Stage object contains a reference to an actor, which is initialized as a HappyActor object. This means that the performPlay() method produces specific behavior. Because references can be rebound to different objects at run time, references in actors can be replaced with references to SadActor objects, and the behavior produced by performPlay() can change accordingly. Therefore, you gain dynamic flexibility at runtime (also known as state mode). On the contrary, you can't decide to inherit in different ways at run time, which must be completely determined during compilation.
"Use inheritance to express differences in behavior and use fields to express changes in state". In the previous example, both are used: two different classes are obtained through inheritance to express the differences of act() methods, while Stage uses combination to allow its state to change. Here, the change of state just leads to the change of behavior.
I have to say, it's a wonderful idea