A class can be defined in another class or method. Such classes are called inner classes. Thinking in Java
Inner classes are familiar, and are often used when instantiating containers. But the specific details of the inner class syntax, principle and implementation can be quite strange to many people, here is a summary, I hope to improve the understanding of the inner class through this summary.
What is an inner class?
An inner class is defined as a class defined in another class or method. Inner classes can be divided into four types: member inner classes, local inner classes, anonymous inner classes and static inner classes. Each has different features and considerations, which we’ll explain below.
Member inner class
As the name implies, a member inner class is a class defined inside a class as a member of a class. As follows:
public class Outer {
public class Inner{}}Copy the code
Features are as follows:
- Member inner classes can be modified by permission modifiers (eg.
Public, private, etc
) modified - Member inner classes can access all members of the outer class, including
private
Members) - A member inner class contains a reference to an external class object by default
- Like to use
this
Similarly, when a member name or method name is overridden, you can use the name of the external class plus.this to specify access to the external class member. Such as:Outer.this.name
- Member inner classes cannot be defined
static
Members of the - Syntax for creating intra-member classes:
Outer outer=new Outer();
Outer.Inner inner=outer.new Inner();
Copy the code
Local inner class
A local inner class is a class defined in a method or scope that differs from a member inner class only in terms of access rights.
public class Outer{
public void test(a){
class Inner{}}}Copy the code
Features are as follows:
-
Local inner classes cannot have access modifiers
-
Local inner classes cannot be defined as static
-
Local inner classes cannot define static members
-
Local inner classes by default contain references to objects of the outer class
-
Local inner classes can also specify access to Outer class members using Outer. This syntax
-
A local inner class wants to use a variable ina method or field, which must be final
In JDK1.8, effectively final can be achieved without final modifications. What does that mean? There is no final modifier, but the compiler will not report if final is added.
Anonymous inner class
Anonymous inner classes are nameless inner classes merged with inheritance
public class Outer{
public List<String> list=new ArrayList<String>(){
{
add("test"); }}; }Copy the code
This is the grammar we use most often. The anonymous inner class has the following characteristics:
- Anonymous inner classes use separate blocks to represent initialization blocks
{}
- If an anonymous inner class wants to use a variable in a method or domain, the variable must be
final
Modified after JDK1.8effectively final
Can also be - Anonymous inner classes by default contain references to objects of the outer class
- Anonymous inner classes represent classes on which inheritance depends
Nested classes
A nested class is a member inner class decorated static
public class Outer {
public static class Inner{}}Copy the code
Features are as follows:
-
A nested class is the only inner class of the four that does not contain a reference to an external class object
-
Nested classes can define static members
-
A nested class can access any static data members and methods of an external class.
Constructors can be viewed as static methods and therefore accessible.
Why inner classes?
As you can see above, inner classes have similar properties to class squares, but inner classes have a lot of verbose syntax. With so many details to pay attention to, why does Java support inner classes at all?
1. Perfect multiple inheritance
- In the early days of C++ as an object-oriented programming language, the most difficult thing to deal with is multiple inheritance. Multiple inheritance is not friendly to code coupling, code users’ understanding, and also the famous death diamond multiple inheritance problem. So Java does not support multiple inheritance.
- Later, Java designers found that without multiple inheritance, some code-friendly design and programming problems became very difficult to solve. Hence the inner class. Inner class has the feature of implicitly containing and communicating with external class objects, which perfectly solves the problem of multiple inheritance.
2. Solve multiple implementations/inheritance problems
-
Sometimes in a class, need many times in different ways to achieve the same interface, if there is no inner class, must define a different number of classes for many times, but using the inner class can be very good solution to this problem, each inner class can implement the same interface, which implements the encapsulation of the code, and different implementation implements the same interface.
-
Inner classes can encapsulate a composite implementation inside.
Why is the inner class syntax so cumbersome
This point is the focus of this article. The inner class syntax is so cumbersome because it is a combination of new data types and syntactic sugar. To understand inner classes, you have to start from the bottom up.
Internal classes are classified into four types according to different application scenarios. The application scenario can be compared to class methods. Below we through the class method contrast pattern one by one to answer why the inner class will have such characteristics
Member inner classes — > member methods
Member inner classes are designed exactly like member methods. Call member methods :outer. GetName () to create Inner class objects :outer. New Inner() they are called depending on the object. As mentioned in Thinking in Java, outer.getName() really looks like outer.getName(outer), passing the calling object as an argument to the method. The same goes for creating a new Inner class: Outer. New Inner(Outer)
Create a new class that contains an inner class:
public class Outer {
private int m = 1;
public class Inner {
private void test() {// Access the external class private member System.out.println(m); }}}Copy the code
Outer. Class and Outer$Inner. Class.
PS: Don’t know why Java always andComposition:)
Outer$Inner. Class = Outer$Inner.
public class Outer$Inner {
public Outer$Inner(Outer this$0) {
this.this$0 = this$0;
}
private void test() {
System.out.println(Outer.accessThe $000(this.this$0)); }}Copy the code
As you can see, the compiler has automatically generated a default constructor, which is a parameter constructor with an external type reference.
You can see the reference to the Outer class member object: Outer is modified by final.
Therefore:
- Member inner classes are class-level members and therefore can be modified by access modifiers
- A member inner class contains a reference to an external class object when the inner class was created, so the member inner class can access all members of the outer class.
- The syntax says: because it is part of an external class, even if
private
Object, the inner class can also access. Access via the Outer. Access $directive - Just as non-static methods cannot access static members, non-static inner classes are designed not to have static variables, so inner classes cannot be defined
static
Objects and methods.
However, static final variables can be defined, and this is not a conflict because final fields must be defined at compile time, and the corresponding variable is replaced with a specific value when the class is compiled, so the inner class is not accessed from the JVM’s point of view.
Local inner class — > local code block
Local inner classes can be understood with local code blocks. Its most important feature is that only external final variables can be accessed. Before you ask why. Define a local inner class:
public class Outer {
private void test() {
int m= 3;
class Inner {
private void print() { System.out.println(m); }}}}Copy the code
Outer$1Inner. Class Outer$1Inner. Class Outer$1Inner.
class Outer$1Inner {
Outer$1Inner(Outer this$0, int var2) {
this.this$0 = this$0;
this.val$m = var2;
}
private void print() {
System.out.println(this.val$m); }}Copy the code
As you can see, the compiler automatically generates a default constructor with two parameters. Seeing this, it should probably be clear: Let’s convert the code:
public class Outer {
private void test() {
int m= 3;
Inner inner=new Outer$1Inner(this,m); inner.print(); }}}Copy the code
So in Inner, we’re actually copying the value of m into the Inner class. The print() method simply prints m, if we write code like this:
private void test() {
int m= 3;
class Inner {
private void print() {
m=4;
}
}
System.out.println(m);
}
Copy the code
In our opinion, the value of m should be changed to 4, but what it really does is:
private void test(){
int m = 3;
print(m);
System.out.println(m);
}
private void print(int m){
m=4;
}
Copy the code
M is copied into the method as a parameter. Therefore, changing its value has no effect, so m must be final in order not to confuse programmers by arbitrarily changing m without achieving any effect.
Why does the compiler produce this effect after going all the way around? In fact, anyone who knows the concept of closures should know why. The weird syntax in Java is usually influenced by the life cycle. Outer$1Inner(this,m); new Outer$1Inner(this,m); The generated objects are defined on the heap. If m is not copied into the object as a member variable, then outside the scope of M, the Inner object points to an invalid address. Therefore, the compiler automatically generates members for all the arguments used by the local class.
Why doesn’t this happen in other languages? This comes back to the classic question of whether Java is passed by value or by reference. Because Java always pass-by-value, real references cannot be passed by Java. The core of the above problem is that if m is changed, then other copies of M cannot be perceived. Other languages solve this problem in other ways. This is a pointer problem for C++.
By understanding the real reason, you can also know when final is needed and when final is not.
public class Outer {
private void test() {
class Inner {
int m=3;
private void print() { System.out.println(m); // Pass as a parameter, which itself is already pass-by-value. Final int c=m+1; // Use m directly, adding final}}}}Copy the code
In Java 8, the policy has been relaxed to allow precisely final variables, which essentially means that the compiler will add final variables to your database during compilation. You should be sure to allow the compiler to add final without errors.
-
Another feature of local inner classes is that they cannot have permission modifiers. Just like local variables can’t have access modifiers
-
As you can see above, external objects are also passed into the local class, so the local class can access external objects
Nested classes — > static methods
There’s nothing wrong with nested classes. Just like static methods, they can be accessed directly, and they can define static variables. Non-static members cannot be accessed at the same time. It’s worth noting in Think in Java that constructors can be thought of as static methods, so nested classes can access the constructors of external classes.
Anonymous classes — > local methods + inherited syntactic sugar
Anonymous classes can be seen as an extension of the first three. To be specific, anonymous classes can be regarded as:
- Member inner class + inheritance
- Local inner class + inheritance
- Nested inner class + inheritance
The syntax for anonymous classes is:
New inherited class name (){//Override Override Override method}Copy the code
The returned result is transformed upward into an inherited class.
Declare an anonymous class:
public class Outer {
private List<String> list=new ArrayList<String>(){
{
add("test"); }}; }Copy the code
This is a classic use of anonymous classes. Outer$1.class = Outer$1.class; Outer$1.class = IDEA;
class OuterThe $1 extends ArrayList<String> {
OuterThe $1(Outer this$0) {
this.this$0 = this$0;
this.add("1"); }}Copy the code
You can see that the full syntax for anonymous classes is inheritance + inner class. Since an anonymous class can be declared as a member variable, a local variable, or a static member variable, its combination is the syntactic sugar of several inner classes plus inheritance, which is not proved here. It’s worth noting here that anonymous classes, because they don’t have class names, can’t declare constructors like normal classes through syntactic sugar, but the compiler can recognize {} and put code into constructors at compile time.
{} can be multiple and are executed in order in the generated constructor.
How to use inner classes correctly
In section 2, we discussed the application scenarios of inner classes, but how to use them elegantly and in the right application scenarios? This section will discuss it in detail.
1. Pay attention to memory leaks
Section 24 of Effective Java makes this clear. Static inner classes are preferred. Why is that? From the above analysis, we can see that, except for nested classes, all inner classes implicitly contain external class objects. This is the source of Java memory leaks. Look at the code:
Define the Outer:
public class Outer{
public List<String> getList(String item) {
returnnew ArrayList<String>() { { add(item); }}; }}Copy the code
Use Outer:
public class Test{
public static List<String> getOutersList(){
Outer outer=new Outer();
//do something
List<String> list=outer.getList("test");
return list;
}
public static void main(String[] args){
List<String> list=getOutersList();
//do something with list
}
}
Copy the code
Believe that such code must be written by students, which involves a problem of habit:
Methods that do not involve class member methods and variables are best defined as static
Looking at the code above, the biggest problem is the memory leak: in use, we define the Outer object to perform a series of actions
- use
outer
Got aArraList
object - will
ArrayList
It’s returned as a result.
Normally, in the getOutersList method, we new out two objects: outer and list, and when we leave the method, we just pass out the reference to the list object, and outer’s reference is destroyed as the method stack exits. Logically, the Outer object should be useless at this point and should be destroyed in the next memory reclamation.
However, this is not the case. As mentioned above, the new List object contains a reference to the Outer object by default, so as long as the list is not destroyed, the Outer object will always exist, but we do not need the Outer object, which is a memory leak.
How can this be avoided?
Simple: Methods that do not involve class member methods and variables are best defined as static
public class Outer{
public static List<String> getList(String item) {
returnnew ArrayList<String>() { { add(item); }}; }}Copy the code
The resulting class is a nested class + inheritance, with no references to external classes.
2. Apply to an implementation class that implements only one interface
- The elegant factory method pattern
We can see that in the factory method pattern, each implementation needs to implement a Fractory to implement the interface that generates the object, and this interface is actually very relevant to the original class, so we can define Fractory in a concrete class, as an inner class
- Simple implementation interface
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("test");
}
}
).start();
}
Copy the code
Try not to use Thread directly. This is just a demonstration. Using Java 8, we recommend lambda instead
- Implement multiple interfaces simultaneously
public class imple{
public static Eat getDogEat() {return new EatDog();
}
public static Eat getCatEat() {return new EatCat();
}
private static class EatDog implements Eat {
@Override
public void eat() {
System.out.println("dog eat");
}
}
private static class EatCat implements Eat{
@Override
public void eat() {
System.out.println("cat eat"); }}}Copy the code
3. Elegant singleton classes
public class Imple {
public static Imple getInstance() {returnImpleHolder.INSTANCE; } private static class ImpleHolder{ private static final Imple INSTANCE=new Imple(); }}Copy the code
4. Deserialize JSON Accepted Javabeans sometimes need to deserialize nested JSON
{
"student": {"name":""."age":""}}Copy the code
Something like this. We can directly define nested classes for deserialization
public JsonStr{
private Student student;
public static Student{
private String name;
private String age;
//getter & setter
}
//getter & setter
}
Copy the code
Note, however, that nested classes should be used here because we don’t need to exchange data with external classes.
Core ideas:
- Nested classes can access the constructors of external classes
- Place the first access to the inner class in a method so that the inner class is only accessed when the method is called, implementing lazy loading
There are many other uses for inner classes, not listed here.
conclusion
Inner classes can be understood by methods, but many of the features of inner classes must be peeled away from the syntax and understand why they are needed to fully understand them. In order to use inner classes, remember: Use nested classes when you can, and use inner classes only if they need to communicate with outer classes. Finally, methods that do not involve class member methods and variables are best defined as static to prevent internal class memory leaks.
Respect the fruits of labor, reproduced please mark the source.
If you think it is well written, welcome to follow the wechat public account: Yiyou Java, every day from time to time to release some articles about Java dry goods, thank you for your attention
Reference article: What does it mean to introduce inner classes in Java? Why can’t there be static members and methods in a member inner class?