Understand JAVA inner classes
A few days ago, I wrote an article about the struggle plan in 2018. In fact, I have been doing Android development for some time. What I wrote in the article is also the foundation or advanced knowledge that needs to be consolidated after encountering various problems in daily development. So this article starts with inner classes.
This article will be summarized from the following parts:
- Why do inner classes exist
- Relationships between inner classes and outer classes
- Internal classification and the detailed use of several classification matters needing attention
- There are problems with inner classes in real development
Why do inner classes exist
Inner class: a class defined in another class
Why do we need inner classes? Or why do inner classes exist at all? The main reasons are as follows:
- Inner class methods can access data in the scope in which the class is defined, including private data decorated by private
- Inner classes can be hidden from other classes in the same package
- Inner classes can implement the pitfalls of Java single inheritance
- When we want to define a callback function without writing a lot of code, we can choose to use anonymous inner classes
Inner class methods can access data in the scope in which the class is defined
For Android, we sometimes write various adapters directly into our activities, such as:
class MainActivity extends AppCompatActivity{
....
private List<Fragment> fragments = new ArrayList();
private class BottomPagerAdapter extends FragmentPagerAdapter{
....
@Override
public Fragment getItem(int position) {
returnfragments.get(position); }... }... }Copy the code
The BottomPagerAdapter described above is an inner class of MainActivity. You can also see that the BottomPagerAdapter can directly access the private variables of fragments defined in the MainActivity. If the BottomPagerAdapter is not defined as an inner class to access fragments private variables, it cannot be done without the getXXX method. That’s the first benefit of inner classes.
But why should an inner class have free access to a member of an outer class? How does it work?
When an object of the outer class creates an object of the inner class, the inner class object must secretly capture a reference to the object of the outer class, and then use that reference to select the member of the outer class when accessing the member of the outer class. Of course, these editors have taken care of that for us.
Also note that inner classes are a compiler phenomenon, not a virtual machine. The compiler will codify the inner class into a regular file with the outer class name $inner class name, which the virtual machine knows nothing about.
Inner classes can be hidden from other classes in the same package
The second benefit about inner classes is that we all know that outer classes, ordinary classes, cannot be modified with private protected access characters, whereas inner classes can be modified with private and protected. When we use private to decorate an inner class, the class is hidden from the outside. This doesn’t seem to work, but when an inner class implements an interface, it transitions upward and completely hides the implementation of the interface from the outside. Such as:
public interface Incrementable{ void increment(); Public class Example {private class InsideClass implements InterfaceTest{public void implements InterfaceTesttest(){
System.out.println("This is a test.");
}
}
public InterfaceTest getIn() {returnnew InsideClass(); } } public class TestExample { public static void main(String args[]){ Example a=new Example(); InterfaceTest a1=a.getIn(); a1.test(); }}Copy the code
All I know from this code is that Example’s getIn() method returns an InterfaceTest instance, but I didn’t know it was implemented that way. And since InsideClass is private, we can’t see the name of the concrete class without looking at the code, so it’s a good way to hide.
Inner classes can implement the pitfalls of Java single inheritance
We know that Java does not allow the use of extends to inherit multiple classes. The introduction of inner classes solves this problem perfectly. Here’s a quote from Thinking In Java:
Each inner class can inherit from a team (interface) to implement, so whether the peripheral class has inherited a (interface), has no effect for the inner class Provided, if there is no inner class can inherit the ability of multiple concrete or abstract class, some design and programming problem difficult to solve. Interfaces solve part of the problem. A class can implement multiple interfaces, and inner classes can inherit multiple non-interface types (classes or abstract classes).
I understand that Java can only inherit from one class as anyone who has learned basic syntax knows, and that before there were inner classes it did multiple inheritance through interfaces. But there are a lot of inconveniences to using interfaces. For example, when we implement an interface, we must implement all the methods in it. Inner classes are different. It can make our class inherit from multiple concrete or abstract classes. Take the following example:
Public class class a {public Stringname() {return "liutao";
}
public String doSomeThing() {/ /doPublic class class b {public intage() {return25. Public class MainExample{private class Test1 extends ClassA{public Stringname() {return super.name();
}
}
private class Test2 extends ClassB{
public int age() {return super.age();
}
}
public String name() {return new Test1().name();
}
public int age() {return new Test2().age();
}
public static void main(String args[]){
MainExample mi=new MainExample();
System.out.println("Name:"+mi.name());
System.out.println("Age."+mi.age()); }}Copy the code
As you can see from the above example, the MainExample class inherits both ClassA and ClassB from the inner class. You don’t have to worry about the doSomeThing method implementation in ClassA. This is where it gets more interesting than interface implementation.
Simple interface implementations are “optimized” with anonymous inner classes
As for the anonymous inner class, we are familiar with it. The common click event is written like this:
. view.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(){
// ... doXXX... }})...Copy the code
I personally hated this before Java8 introduced lambda expressions, because the onClick method can be very complex, and there can be a lot of judgment logic, which makes the code very cumbersome. Therefore, I prefer to use anonymous inner classes to do some simple operations. With lambda expressions, the code is easier to read
view.setOnClickListener(v -> gotoVipOpenWeb());
Copy the code
Relationships between inner classes and outer classes
- For non-static inner classes, the creation of the inner class depends on the instance object of the outer class, and the inner class cannot be created without an instance of the outer class
- The inner class is a relatively independent entity that is not is-A related to the outer class
- The moment the inner class is created does not depend on the creation of the outer class
The moment the inner class is created does not depend on the creation of the outer class
This sentence is from Thinking In Java. Most people will take this sentence out of context and assume that the creation of inner classes does not depend on the creation of outer classes. This is a mistake.
In fact, static inner classes “nested classes” do not depend on the creation of external classes, because static does not depend on instances, but on the Class itself.
For a normal inner class, however, it must rely on the creation of an instance of an external class. As stated in the first relation: for a non-static inner class, the creation of an inner class depends on the creation of an instance object of the external class.
There are two common inner class creation methods:
public class ClassOuter {
public void fun(){
System.out.println("External class method"); } public class InnerClass{}} public class TestInnerClass {public static void main(String[] args) {// Create method 1 ClassOuter.InnerClass innerClass = new ClassOuter().new InnerClass(); ClassOuter outer = new ClassOuter(); ClassOuter.InnerClass inner = outer.new InnerClass(); }}Copy the code
Because of this dependency, ordinary inner classes are not allowed to have static members, including nested classes (static inner classes of inner classes). And since a non-static inner class is always generated by an external object, there are no static fields and methods associated with the object. Of course, static inner classes do not depend on outer classes, so static members are allowed inside them.
Now let’s go back to the title. The English version of the sentence actually says:
The point of creation of the inner-class objects not tied to the creation of the outer-class object.
You don’t have to create an inner class to create an outer class.
Taking the Adapter example at the beginning of this article, we can’t say that creating an Activity automatically creates a Adapter (assuming that the creation of the Adapter depends on a condition being true). It will only be created if the condition is met.
The inner class is a relatively independent entity that is not is-A related to the outer class
First understand what an “IS-A relationship” is: An IS-A relationship is an inheritance relationship. Knowing what is an IS-A relationship, it is easy to understand that inner classes and outer classes are not IS-A relationships.
For an inner class to be a relatively independent entity, we can understand this statement in two ways:
- An external class can have multiple inner class objects that are independent of each other.
- From the compilation, the inner class is represented as “outer class $inner class.class”, so there is no difference to the virtual machine as a single class. But we know they are related because the inner class holds a reference to the outer class by default.
Classification of inner classes
Inner classes can be divided into static inner classes (nested classes) and non-static inner classes. Non-static inner classes can be divided into member inner classes, method inner classes, and anonymous inner classes. I believe we are already familiar with these types of writing, so this section mainly explains the differences between these types:
The difference between static and non-static inner classes
- Static inner classes can have static members, while non-static inner classes cannot have static members.
- A static inner class can access static variables of an external class, but not non-static variables of an external class.
- Non-static members of a non-static inner class can access non-static variables of the outer class.
- The creation of a static inner class does not depend on the creation of an external class, whereas a non-static inner class must depend on the creation of an external class.
The differences can be best understood through an example:
public class ClassOuter {
private int noStaticInt = 1;
private static int STATIC_INT = 2;
public void fun() {
System.out.println("External class method"); } public class InnerClass { //static int num = 1; A non-static inner class cannot have static members public voidfun(){// Non-static members of a non-static inner class can access non-static variables of the outer class. System.out.println(STATIC_INT); System.out.println(noStaticInt); } } public static class StaticInnerClass { static int NUM = 1; // Static inner classes can have static members public voidfun(){ System.out.println(STATIC_INT); //System.out.println(noStaticInt); Public class TestInnerClass {public static void main(String[] args) {public static void main(String[] args) ClassOuter.InnerClass innerClass = new ClassOuter().new InnerClass(); ClassOuter outer = new ClassOuter(); ClassOuter.InnerClass inner = outer.new InnerClass(); / / develop a static inner class way ClassOuter StaticInnerClass StaticInnerClass = new ClassOuter. StaticInnerClass (); }}Copy the code
Local inner class
If an inner class is used in only one method, then we can define the inner class inside the method. This inner class is called a local inner class. Its scope is limited to this method.
There are two things worth noting about local inner classes:
- Access modifiers are not allowed for local classes. Public Private protected None
- A local inner class is completely hidden from the outside world and is not allowed to be accessed except by the method that created it.
- A local inner class differs from a member inner class in that it can refer to a member variable, but the member must be declared final and the value of the variable cannot be changed internally. (This is not accurate, because it is not allowed to modify the reference to the object, and the object itself can be modified.)
public class ClassOuter {
private int noStaticInt = 1;
private static int STATIC_INT = 2;
public void fun() {
System.out.println("External class method");
}
public void testFunctionClass(){
class FunctionClass{
private void fun(){
System.out.println("Output of a local inner class"); System.out.println(STATIC_INT); System.out.println(noStaticInt); System.out.println(params); //params ++ ; // Params is immutable so this sentence is compiled incorrectly}} FunctionClassfunctionClass = new FunctionClass();
functionClass.fun(); }}Copy the code
Anonymous inner class
- Anonymous inner classes do not have access modifiers.
- An anonymous inner class must inherit an abstract class or implement an interface
- There cannot be any static members or methods in an anonymous inner class
- An anonymous inner class has no constructor because it has no class name.
- Like local inner classes, anonymous inner classes can also reference local variables. This variable must also be declared final
Public class Button {public void click(final int params){// An anonymous inner class that implements ActionListener interface newActionListener(){
public void onAction(){
System.out.println("click action..."+ params); } }.onAction(); } // An anonymous inner class must inherit or implement an existing interface public interface ActionListener{public void onAction(); } public static void main(String[] args) { Button button=new Button(); button.click(); }}Copy the code
Why do local variables need final modifications
The reason: because local variables and anonymous inner classes have different life cycles.
Anonymous inner classes are created and stored in the heap, while local variables in methods are stored in the Java stack. When the method completes execution, the local variables are unstacked and disappear. So where is the anonymous inner class going to find this local variable?
To solve this problem, the compiler automatically creates a backup of local variables in the anonymous inner class, meaning that there is still a backup in the anonymous inner class even after the method execution ends.
But here’s the problem again. If a in the local variable keeps changing. So don’t you have to keep the backup variable A changing all the time. To keep local variables consistent with the backup domain in the anonymous inner class. The compiler has to specify that these local fields must be constant and cannot be changed once assigned. This is why the fields where anonymous inner classes apply external methods must be constant fields.
Special note: the final modifier has been removed from Java8, but the variable will automatically become final whenever it is used in an anonymous inner class (only used, not assigned).
Problems that inner classes might cause in real development
Inner classes cause memory leaks in your program
The Handler that we use regularly gives us this warning all the time. Let’s start by looking at why inner classes cause memory leaks.
To understand why internal classes cause memory leaks, we need to understand the Java virtual machine’s reclamation mechanism, but we won’t go into detail here, we need to understand the Java memory reclamation mechanism through “reachable analysis”. That is, the Java virtual machine uses a memory reclamation mechanism to determine whether references are reachable, and if they are not, it will reclaim them at some point.
So when does an inner class create the potential for a memory leak?
If an anonymous inner class is not held by any reference, then the anonymous inner class object has a chance to be recycled when it runs out.
If the inner class is only referenced in the outer class, both the outer class and the inner class can be reclaimed by GC when the outer class is no longer referenced.
If a reference to an inner class is referenced by a class other than the outer class, then the inner class and the outer class cannot be collected by GC, even if the outer class is not referenced, because the inner class holds a reference to the outer class.
public class ClassOuter {
Object object = new Object() {
public void finalize() {
System.out.println("inner Free the occupied memory..."); }}; public voidfinalize() {
System.out.println("Outer Free the occupied memory...");
}
}
public class TestInnerClass {
public static void main(String[] args) {
try {
Test();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void Test() throws InterruptedException {
System.out.println("Start of program.");
ClassOuter outer = new ClassOuter();
Object object = outer.object;
outer = null;
System.out.println("Execute GC");
System.gc();
Thread.sleep(3000);
System.out.println("End of program."); }}Copy the code
The running program finds that the object object is not reclaimed by GC, even if the outer class is not referenced by any variable, as long as its inner class is held by a variable other than the outer class. In particular, we need to pay attention to the case where the inner class is referenced by other classes outside of it, which makes it impossible for the outer class to be freed, leading to memory leaks.
In Android, when the Hanlder is used as an internal class, its object is held by the Message sent by the Hanlder in MessageQueue, the Message queue managed by the main thread Looper. A memory leak occurs when a large number of messages need to be processed in the message queue, or when delayed messages need to be executed. The Activity that created the Handler has already exited, and the Activity object cannot be released.
When the message queue has finished processing the message carried by the Hanlder, it will call msK.recycleUnchecked () to release Handler references to the message.
There are two ways to handle a Hanlder memory leak in Android:
- Disable onDestry for activities/fragments and unqueue messages:
mHandler.removeCallbacksAndMessages(null);
Copy the code
- Create a Hanlder as a static inner class and use a soft reference
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> mActivity;
public MyHandler(MainActivity activity) {
mActivity = new WeakReference<MainActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = mActivity.get();
if (activity == null || activity.isFinishing()) {
return; } / /... }}Copy the code
conclusion
This article from the existence of inner class reason, the relationship between the internal and external class, inner class classification and developing inner class may cause memory leak issues, summarizes the problems associated with inner classes, forgive myself pretty, before this article want to use “thoroughly understand Java inner class” full article but when I write, I just found out that, All kinds of knowledge can be extended through Java inner classes, so in the end the word “thorough” was dropped, and the summary can be quite incomplete. I hope you can help me out in time.
The similarities and differences between inner class classification, static inner class and non-static inner class, and local inner class and anonymous inner class are likely to be asked in an interview, and it would be a plus if this extended to the memory leak caused by inner class.
For this article, see Thinking in Java, The Java core technology volume 1 “http://blog.csdn.net/mcryeasy/article/details/54848452 http://blog.csdn.net/mcryeasy/article/details/53149594 https://www.zhihu.com/question/21373020 http://daiguahub.com/2016/09/08/java%E5%86%85%E9%83%A8%E7%B1%BB%E7%9A%84%E6%84%8F%E4%B9%89%E5%92%8C%E4%BD%9C%E7%94%A8/ https://www.zhihu.com/question/20969764