This is the first article I participated in beginners’ introduction.
Break out of your comfort zone and review your Java knowledge regularly to ensure you don’t fall behind.
It’s easy to get caught up in the “everyone else uses it, so I’ll use it” comfort zone at work and over time become so comfortable with the status quo that we lose the ability to think.
It is the most basic quality of an excellent programmer to ask why and find the answers behind everything. I did not do well in the past, but I will do it in the future.
This is the first post, I will keep updating, each module of the blog will continue to supplement and improve, let’s go!
First question: Why does a subclass call the superclass constructor with super go on the first line?
The first step is to understand the loading order of the code:
According to the figure above, the system will call the constructor of the parent class before calling the constructor of the subclass.
When constructing a subclass, the editor first checks to see if the first line shows that the parent class’s constructor was called. If not, the editor defaults to calling the parent class’s no-argument constructor to initialize it.
If we call the parent constructor on the second line, we will repeat the call, so we recommend putting it on the first line to ensure that the subclass is properly initialized.
Second question: Why is there only value passing in Java?
In Java, methods pass essentially copies of parameter values.
(1). For primitive data types, operations on parameters (such as swapping) in methods do not affect the original values;
The test code is as follows:
public class ValueDemo {
public static void main(String[] args) {
int a = 1;
int b = 2;
swap(a, b);
System.out.println("Original parameter: A =" + a);
System.out.println("Original parameter: b =" + b);
}
private static void swap(int a, int b) {
int tmp = a;
a = b;
b = tmp;
System.out.println("Copy parameter in method: a =" + a);
System.out.println("Copy parameter in method: b ="+ b); }}Copy the code
Running results:
As you can see, the basic data types A and B are swapped without changing the original value.
(2). For reference types, the state of reference parameters can be changed, but the object reference itself stored in the variable cannot be changed.
The test code is as follows:
public class ValueDemo {
public static void main(String[] args) {
int[] a = {1.2.3};
int[] b = {4.5.6};
changeStatus(a);
changeObject(a, b);
System.out.println("Original object reference parameter: A =" + Arrays.toString(a));
System.out.println("Original object reference parameter: b =" + Arrays.toString(b));
}
private static void changeStatus(int[] a) {
a[0] = 2;
System.out.println("Change the state of A in the method =" + Arrays.toString(a));
}
private static void changeObject(int[] a, int[] b) {
int[] temp = a;
a = b;
b = temp;
System.out.println("Object reference parameter in method: a =" + Arrays.toString(a));
System.out.println(Object reference parameter in method: b =+ Arrays.toString(b)); }}Copy the code
Running results:
As can be seen, we can modify the value of a[0], that is, change the state of the reference parameter;
However, the exchange of the reference type parameters A and B does not change the object reference itself stored in the variable.
Q: Why do you have to rewrite hashCode to override equals?
First, the general convention of the hashCode method is that if two objects are equal, their hash codes must be equal. However, this is just a convention, and I think the underlying reason is that if not rewritten, it will lead to logic errors in certain application scenarios.
For example, when checking whether elements are equal in a HashMap, to reduce the cost of finding them, hashCode is first checked for equality, and then equals is called to determine whether elements are really equal.
So to avoid situations where we think equals is equal, but logically do not, overriding equals must override the hashCode method.
Fourth question: Why is It so often said that Java is a compilation and interpretation language?
First of all, we know that high-level languages can be divided into compiled language and interpreted language according to the way of program execution. The following is the specific definition:
Compiled language: Before a program is executed, a compiler is used to compile the program and convert it into a machine language that can be directly executed. In this way, the runtime does not require translation and can be executed directly.
Interpreted language: when a program is executed, the interpreter interprets the program line by line and then runs it directly.
Java programs from source code to run generally have the following three steps:
As you can see, Java first compiles the source code into bytecodes (class files) using javac commands, which are then interpreted by the JVM at runtime into the corresponding machine code and finally executed.
Because some methods and blocks of code need to be called frequently (so-called hot code), Java later introduced the JIT (runtime compiler), which can capture hot code in a program, compile it into machine code, and cache it. When you encounter the same code, you don’t have to use the interpreter to interpret it. Instead, you look for the corresponding machine code execution, which avoids the interpreter repeating the execution multiple times.
From this perspective, we would say that Java is a compilation and interpretation language.
Fifth question: why is the “hungry” singleton pattern in Java more recommended to implement “enumeration”?
As we know, the singleton pattern in Java is a design pattern that ensures that only one instance object of a class in the entire system can be retrieved and accessed.
The singleton model can be divided into two types: hungry and full:
Hungry: Create objects immediately, without thread safety issues;
Full: Delay the creation of objects until they are first used.
There are three types of hanhanian creation: direct instantiation, enumeration, and static code blocks;
Direct instantiation:
public Class SingletonDemo1{
private static SingletonDemo1 INSTANCE = new SingletonDemo1();
private SingletonDemo1(a){}public static SingletonDemo1 getInstance(a){
return INSTANCE;
}
public static void main(String[] args){ SingletonDemo1 instance = getInstance(); }}Copy the code
Enumeration:
public enum SingletonDemo2{
INSTANCE;
}
class Test{
public static void main(String[] args){ SingletonDemo2 instance = SingletonDemo2.INSTANCE; }}Copy the code
Static code block:
public class SingletonDemo3{
private static SingletonDemo3 INSTANCE;
static {
INSTANCE = new SingletonDemo3();
// Other operations. }private SingletonDemo3(a){}
public static SingletonDemo3 getInstance(a){
return INSTANCE;
}
public static void main(String[] args){ SingletonDemo3 instance = SingletonDemo3.getInstance(); SingletonDemo3 instance2 = SingletonDemo3.getInstance(); System.out.println(instance == instance2); }}Copy the code
As you can see, the enumeration approach is the least code intensive and is recommended as long as the Java version is older than JDK 1.5.
Sixth question: Why is the implementation of “static inner class” preferred in the “Full Chinese” singleton mode in multi-threaded scenarios?
First of all, there are three types of creation: thread-unsafe creation, thread-safe creation, static inner class;
Thread unsafe creation:
public Class SingletonDemo4{
private static SingletonDemo4 INSTANCE;
private SingletonDemo4(a){}public static SingletonDemo4 getInstance(a){
if(INSTANCE == null){
INSTANCE = new SingletonDemo4();
}
returnINSTANCE; }}Copy the code
Since the requirement is multi-threaded scenario, we can not choose the implementation of this thread unsafe singleton pattern;
Thread-safe creation:
public class SingletonDemo4{
private static SingletonDemo4 INSTANCE;
private SingletonDemo4(a){}public static synchronized SingletonDemo4 getInstance(a){
if(INSTANCE == null){
INSTANCE = new SingletonDemo4();
}
returnINSTANCE; }}Copy the code
It can be seen that synchronized modified getInstance() ensures thread safety, but locking can only ensure singletons, affecting efficiency.
Static inner class:
public class SingletonDemo5 {
private SingletonDemo5(a) {}private static class InnerClass {
private static SingletonDemo5 INSTANCE = new SingletonDemo5();
}
public static SingletonDemo5 getInstance(a) {
return InnerClass.INSTANCE;
}
public static void main(String[] args) { SingletonDemo5 instance1 = SingletonDemo5.getInstance(); SingletonDemo5 instance2 = SingletonDemo5.getInstance(); System.out.println(instance1 == instance2); }}Copy the code
Static inner classes create instance objects only when they are loaded and have no performance issues, so they are recommended.
Q: Why are Integer values equal or unequal in automatic boxing?
There is the following code:
public class Test {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
System.out.println(a == b);
Integer c = 128;
Integer d = 128; System.out.println(c == d); }}Copy the code
After running, the output is as follows:
Obviously there is only a difference of 1, the same is automatic packing, why the result is different?
We all know that strings have a constant pool for all strings, and essentially Integer has a constant pool for all strings.
When we create an Integer class with Integer a = 127, Java calls integer.valueof (), which internally caches Integer classes in the range -128 to 127. When we assign a value to a encapsulated class in this scope, the boxing operation assigns a reference to a cache pool instance. But integer.valueof () creates a new Integer class if you assign a value to an encapsulated class that is outside this range.
Therefore, it is natural to return false when comparing Integer classes outside this range.
Q: deep copy and shallow copy, why said to be careful with shallow copy?
First we need to know what object copy is:
Object Copy: To Copy the properties of an Object to another Object of the same class type. The purpose is to reuse some or all of the Object’s data in the new context.
Let’s look at the definitions of shallow and deep copies:
Shallow copy: Passes values to base data types and makes reference-passing copies to reference data types. That is, for reference data types, the original object and the new object reference the same object, and the change of reference fields in the new object will lead to the change of corresponding fields in the original object.
Deep copy: Passes values to the base data type, creates a new object for the reference data type, and copies the contents.
It’s not clear enough just to define it, so let’s do it in code.
Shallow copy code implementation:
Note: To call the clone method of an object, you must have the class implement the Cloneable interface and override the Clone method.
// Define the Person information class, including the Address class Address;
public class Person implements Cloneable{
private String name;
private Address address = new Address();
public Person(a){}
public Person(String name, String province, String city){
this.name = name;
this.address.setProvince(province);
this.address.setCity(city);
}
public String getName(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
public Address getAddress(a) {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
protected Object clone(a) throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString(a) {
return "Person[name=" + name + ", address=" + getAddress() + "]"; }}class Address{
private String province;
private String city;
public String getProvince(a) {
return province;
}
public void setProvince(String province) {
this.province = province;
}
public String getCity(a) {
return city;
}
public void setCity(String city) {
this.city = city;
}
@Override
public String toString(a) {
return "Address [province=" + province + ", city=" + city + "]"; }}Copy the code
Main function call:
public class TestCopy {
public static void main(String[] args) throws CloneNotSupportedException {
// The original object
Person person1 = new Person("Xiao Zhang"."Shandong Province"."Yantai city");
// Copy objects
Person person2 = (Person) person1.clone();
System.out.println("Original object:" + person1);
System.out.println("Copy object:" + person2);
// Modify the copy object
person2.getAddress().setCity("Qingdao");
System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = after the modification of address property = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
System.out.println("Original object:" + person1);
System.out.println("Copy object:"+ person2); }}Copy the code
Call result:
It can be concluded that:
The shallow copy changes the city property of the reference data type Address of the copy object, and the corresponding fields in the original object also change.
Deep copy code implementation:
Address implements the Cloneable interface and overwrites the Clone method (see Person).
@Override
protected Object clone(a) throws CloneNotSupportedException {
//return super.clone();
// Implement deep copy
Person person = (Person) super.clone();
person.setAddress((Address) address.clone());
return person;
}
Copy the code
Run again:
It can be concluded that:
The deep copy changes the city property of the reference data type Address of the copy object, and the corresponding field in the original object does not change.
In Java, the clone method of objects is shallow copy by default. If you want to achieve deep copy, you need to rewrite the Clone method to achieve copy of property objects.
Therefore, we must be careful to use shallow copy when writing daily code. Do not change the corresponding fields in the original object because of modifying the reference fields in the shallow copy object.
The above are my questions in the review of Java knowledge. You can also leave your questions in the comments section. I will add regular updates to the blog and hope that through this process, we can progress and improve together ~