聊聊 Java 内部类

1,069 阅读4分钟
原文链接: www.jianshu.com

一.磨叽磨叽Java值传递与引用传递

“在Java里面参数传递都是按值传递”
即:按值传递是传递的值的拷贝,按引用传递其实传递的是引用的地址值,所以统称按值传递。

简单的说,基本数据类型是按值传递的,方法的实参是一个原值的复本。类对象是按对象的引用地址(内存地址)传递地址的值,那么在方法内对这个对象进行修改是会直接反应在原对象上的(或者说这两个引用指向同一内存地址)。注意String也是值传递。

public static void main(String[] args) { 
    String x = new String("now"); 
    User user = new User("now");
    change(x); 
    System.out.println("1:"+x);
    change(user);
    System.out.println("2:"+user.name);
     User user1 = new User("now");
     System.out.println("3:"+user1 .name);
}
public static void change(String x) { 
    x = "even";
}
public static void change(User x) { 
    x.name = "even";
}
public static void change(User x) { 
    x = new User("even");
}

得出的结果

1:even
2:even
3:now

二.回归正题,聊聊Java内部类的这些事

Java允许在一个类里面定义另一个类,类里面的类就是内部类,也叫做嵌套类

简单的内部类如下:

class OuterClass { 
    class InnerClass{ 
    }
}

经常用到的内部类:

public class PrivateToOuter { 
    Runnable mRunnable = new Runnable(){ 
        private int x=10; 
        @Override 
        public void run() { 
            System.out.println(x); 
        } 
    }; 
}

以上代码mRunnable让人并不觉得是内部类,它并不像InnerClass那样形象,但是其实以下句柄实现一个继承Runable的类,也就是自定义了一个类,那么明显它就是一个内部类。其实它是属于内部类一种:匿名内部类Anonymous Inner Class

{ 
        private int x=10; 
        @Override 
        public void run() { 
            System.out.println(x); 
        } 
}

内部类需要注意的以下几点:

(1)内部类的获取

内部类可以访问包装他的外部类的所有,方法和属性,包括私有方法。但是对其他类来说,它是隐藏的,哪怕对同一个包下面其他类来说,内部类都是隐藏的,也就是说,如果要访问内部类,就必须先要得到他的外部包装类,得到那个句柄之后在new 内部类,就可以调用他的方法了,如下:

OuterClass outer = new OuterClass();
InnerClass innerClass = outer.new InnerClass();

所以内部类是可以很好的解决单继承的问题。如果对于private在此逻辑为何失效的话,可以查看细话Java:"失效"的private修饰符

(2)静态内部类

如果用static 将内部内静态化,那么内部类就只能访问外部类的静态成员变量,具有局限性

class Out {
    private static int age = 12;
    private int time = 14;
    static class In {
        public void print() {
            System.out.println(age);
        }
    }
}

public class Demo {
    public static void main(String[] args) {
        Out.In in = new Out.In();
        in.print();
    }
}

也就是说in只能访问age,而非time

(3)私有内部类

如果一个内部类只希望被外部类中的方法操作,那么可以使用private声明内部类

class Out {
    private int age = 12;

    private class In {
        public void print() {
            System.out.println(age);
        }
    }
    public void outPrint() {
        new In().print();
    }
}

public class Demo {
    public static void main(String[] args) {
        //此方法无效
        /*
        Out.In in = new Out().new In();
        in.print();
        */
        Out out = new Out();
        out.outPrint();
    }
}

需要特别告诉大家重要的point就是:

在内部类构造的时候,会将外部类的引用传递进来,并且作为内部类的一个属性,所以内部类会持有一个其外部类的引用。

在Java中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用。静态的内部类不会持有外部类的引用。所以point3中的内部类是无法获取到外部类的成员变量与方法。

回归到android:

public class SampleActivity extends Activity { 
    private final Handler handler= new Handler() { 
        @Override 
        public void handleMessage(Message msg) { 
            // ...  
        } 
    }
}

这是我们获取handler实例经常所做的操作,但是回归到point得知,handler的匿名内部类中有SampleActivity的引用,所以这是有可能导致OOM的隐患,比如以下代码:

public class SampleActivity extends Activity { 
    private final Handler mLeakyHandler = new Handler() { 
        @Override 
        public void handleMessage(Message msg) { // ... } 
    } 
    @Override 
    protected void onCreate(Bundle savedInstanceState) {         
        super.onCreate(savedInstanceState); 
        mLeakyHandler.postDelayed(new Runnable() { 
            @Override 
            public void run() { /* ... */ } 
        }, 1000 * 60 * 10); 
        finish(); 
    }
}

SampleActivity对象已经finish,但是mLeakyHandler的消息要在10分钟后执行操作,而Handler是一个匿名内部类的实例,其持有外面的SampleActivity的引用(而且Runable也是匿名内部类,其内部也是持有SampleActivity的引用),所以这导致了SampleActivity无法回收,进行导致SampleActivity持有的很多资源都无法回收,这就是我们常说的内存泄露。解决方法如下:

public class SampleActivity extends Activity { 
    private static class MyHandler extends Handler { 
        private final WeakReference<SampleActivity> mActivity; 
        public MyHandler(SampleActivity activity) { 
            mActivity = new WeakReference<SampleActivity>(activity); 
        } 
        @Override 
        public void handleMessage(Message msg) { 
            SampleActivity activity = mActivity.get(); 
            if (activity != null) { // ... } 
        } 
    } 
    private final MyHandler mHandler = new MyHandler(this); 
    private static final Runnable sRunnable = new Runnable() { 
        @Override 
        public void run() { /* ... */ } 
    };
    @Override 
    protected void onCreate(Bundle savedInstanceState) {     
        super.onCreate(savedInstanceState); 
        mHandler.postDelayed(sRunnable, 1000 * 60 * 10); 
        finish(); 
    }
}