类间关系之—内部类及在Android应用的初步延伸
2017-11-23 22:11
260 查看
我们在设计自己的类时,通常可以通过内部类的特性来构造很多的模式实现。
比如辅助类、回调、内部代理、赋予外部类更多的继承方向和功能等。
通常在用好一个东西的时候,首先要理解这个东西的实现机制。
对于内部类种类来说:
1 有static 修饰 为嵌套类;无static修饰就是咱们一般所称的内部类
2 直接在域内或者方法内通过类名或者接口名new的称为匿名内部类(或者叫直接new一个类定义)
3 在方法域内定义的并使用的 叫局部内部类
再来看和外围类的联系:
静态内部类 (嵌套类) 和外部类没有实例引用关系
内部类总有个外围类引用(编译器隐式赋值) ,多层嵌套内部类同理,即都可以看到外围类域
内部类域和外围类在一个层次上 只有外部类new出实例才能new内部类
我们借用java 反编译命令来看看我们定义的内部类在编译器的处理下的隐含变化:
cmd–>Javap命令
我们先简单的用javap file.class 来看下上图的编译:
class Outer$Inner {
Outer$Inner(Outer);
void goToSchool();
}
Static inner class
public class Outer$Inner {
public Outer$Inner();
void goToSchool();
}
我们发现非静态内部类的编译文件默认构造内比嵌套类多了个 Outer
心想有点意思!
别急~
我们来看个具体例子:
编译的class 文件:
注意$+数字 表示第几个匿名内部类
静态类和非静态类都是 $+名字
内部类执行javap –c 后
静态内部类执行javap –c 后
我们 javap -c 一下匿名内部类 Child$1
(^o^)/~ 也有 this \$0 (指向外围类)
我们还能得到一个信息 Child$1 implements Boys 编译后 按说就不该叫匿名内部类了
改叫Child\$1
搞完Intereface的匿名内部类 我们再来看一下Class 的匿名内部类
javap -c 他的class
匿名内部类注意事项【也可以看开头的写法注释】:
1 匿名内部类 即可以扩展类也可以实现接口
2 匿名内部类 扩展类时候可以 有初始化 块 来代替构造器,可以拓展方法
3 如果要使用外部对象 方法参数对象应为final
4 和局部内部类区别:局部内部类是方法内的类,不能有访问控制符,不过也持有外围类隐士引用
重点:
内部类和匿名内部类都可以向上转型 发挥更多设计模式的作用
话说 这个 Javap 命令可以帮助我们窥探更多类编译背后的秘密。Java研究的路上多用哈。
我们看下接口和类内部接口的编译:
好了我们在看一个 Android开发中内存溢出的典型:
如果我们开leakcanary会发现:
后台持有Activity的 Handler 引用使得 Activity销毁后不能被 GC掉。
一般我们都会在Activity内:
解决办法:
1 OnDestory内主动移除 和界面有关的Message
主动关闭后台相关的一异步任务
2 新建一个类Extends Handler 让其持有 用这个Handler类的来(比如Activity) 从其他进程回传动作的发起者 的弱引用 。但这个设计 会使得Handler 和 创建Hanlder的类关系疏远,即创建者可以忽略(这个时候创建者已经不存在了,,此处应有判空逻辑)Hanler的回传动作。
如果希望Handler的每个回调都得到处理,在UI显示的情况下是肯定满足的,但是当在回调不涉及UI情况下,则应该考虑将这部分逻辑从Activity中拆分出去。比如建立一个Presenter辅助Activity ,Presenter依然持有Activity弱引用。
我喜欢画个图 直观地看:
有人可能嫌presenter 麻烦 可以省掉 ,个人设计爱好~~
比如辅助类、回调、内部代理、赋予外部类更多的继承方向和功能等。
通常在用好一个东西的时候,首先要理解这个东西的实现机制。
对于内部类种类来说:
1 有static 修饰 为嵌套类;无static修饰就是咱们一般所称的内部类
2 直接在域内或者方法内通过类名或者接口名new的称为匿名内部类(或者叫直接new一个类定义)
3 在方法域内定义的并使用的 叫局部内部类
Interface IInter { int tag; void value(); } public [abstract] class Callback{ [abstract] void call(); }
public class Child { private String name; private Control control; private CallBack callBack = new Callback() { private int i;//扩展域 { // 匿名内部类 初始化域块 i = 9; } @Override //指示覆写方法 void call() { print("Child,s name is " + name); } void action() { //匿名内部类 扩展的方法 print("Child will do action"); } }; protected CallBack say(String something) { return new IInter() { @Override void value() { print("Child say" + something);//如果something在匿名内部类内可能被用 则应该加final say(final String something) } }; } protected void run([final]String something) { control.setRunListener(new IInter() { @Override void value() { print("Child say" + something);//如果something在匿名内部类内可能被用 则应该加final say(final String something) } }); } protected void gotoSchool() { class teacher{ //局部内部类 void teach(){ print("Teacher teach " + name); } } new teacher().teach(); } static class inners { //嵌套类 和 外围类仅有逻辑上的联系 没有引用上的关系 //内部的内部类嵌套关系和外部类完全一样 //只能调用外围static方法 } class inner {//内部类 只有在外围类创建对象之后才能创建 因为它会隐士包含指向外围类的引用 //不能有静态域 和 嵌套类 } }
再来看和外围类的联系:
静态内部类 (嵌套类) 和外部类没有实例引用关系
内部类总有个外围类引用(编译器隐式赋值) ,多层嵌套内部类同理,即都可以看到外围类域
内部类域和外围类在一个层次上 只有外部类new出实例才能new内部类
我们借用java 反编译命令来看看我们定义的内部类在编译器的处理下的隐含变化:
cmd–>Javap命令
Java -[param] file -help 输出 javap 的帮助信息。 -l 输出行及局部变量表。 -b 确保与 JDK 1.1 javap 的向后兼容性。 -public 只显示 public 类及成员。 -protected 只显示 protected 和 public 类及成员。 -package 只显示包、protected 和 public 类及成员。这是缺省设置。 -private 显示所有类和成员。 -J[flag] 直接将 flag 传给运行时系统。 -s 输出内部类型签名。 -c 输出类中各方法的未解析的代码,即构成 Java 字节码的指令。 -verbose 输出堆栈大小、各方法的 locals 及 args 数,以及class文件的编译版本 -classpath[路径] 指定 javap 用来查找类的路径。如果设置了该选项,则它将覆盖缺省值或 CLASSPATH 环境变量。目录用冒号分隔。 -bootclasspath[路径] 指定加载自举类所用的路径。缺省情况下,自举类是实现核心 Java 平台的类,位于 jrelib下面。 -extdirs[dirs] 覆盖搜索安装方式扩展的位置。扩展的缺省位置是 jrelibext。
我们先简单的用javap file.class 来看下上图的编译:
class Outer$Inner {
Outer$Inner(Outer);
void goToSchool();
}
Static inner class
public class Outer$Inner {
public Outer$Inner();
void goToSchool();
}
我们发现非静态内部类的编译文件默认构造内比嵌套类多了个 Outer
心想有点意思!
别急~
我们来看个具体例子:
//忽略类名字的逻辑关系 仅看内部类使用和编译 Interface Boys { void goToSchool() ; } public class Child { private int id = 8900; protected int age; public [static] Boys name = new Boys () { public void goToSchool() { // TODO Auto-generated method stub } }; public Child() { } protected void say() { System.out.println("Child say"); } protected Boys run(){ return new Boys(){ public void goToSchool() { } }; } protected void say(String string) { System.out.println("Child say" + string); } Public [static] class Myclass implements Boys { public void goToSchool() { // TODO Auto-generated method stub } } }
编译的class 文件:
注意$+数字 表示第几个匿名内部类
静态类和非静态类都是 $+名字
内部类执行javap –c 后
class Child$Myclass implements Boys { final Child this$0; //原来你在这里 抓住了 有个指向Child外围类的常量 Child$3(Child); Code: 0: aload_0 1: aload_1 2: putfield #12 // Field this$0:LChild; 5: aload_0 6: invokespecial #14 // Method java/lang/Object."<init>": ()V 9: return public void goToSchool(); Code: 0: return }
静态内部类执行javap –c 后
class Child$Myclass implements Boys { Child$Myclass(); Code: 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>": ()V 4: return public void goToSchool(); Code: 0: return }
我们 javap -c 一下匿名内部类 Child$1
(^o^)/~ 也有 this \$0 (指向外围类)
我们还能得到一个信息 Child$1 implements Boys 编译后 按说就不该叫匿名内部类了
改叫Child\$1
搞完Intereface的匿名内部类 我们再来看一下Class 的匿名内部类
public Callback name = new Callback() { public void call() { } }
javap -c 他的class
class Child$1 extends Callback{ //和接口匿名内部类对比一下 关注下区别 final Child this$0; // 又抓住一只 Child$Myclass(); Code: 0: aload_0 1: invokespecial #10 // Method java/lang/Object."<init>": ()V 4: return public void call(); Code: 0: return }
匿名内部类注意事项【也可以看开头的写法注释】:
1 匿名内部类 即可以扩展类也可以实现接口
2 匿名内部类 扩展类时候可以 有初始化 块 来代替构造器,可以拓展方法
其实际是 Outer$1 extends|implements 接口或者类
3 如果要使用外部对象 方法参数对象应为final
目的是为了防止参数逻辑歧义发生 。因为传参会在匿名内部类内有个临时拷贝。如果在匿名内部类内更改了参数,但又发现外部参数没有改变那么逻辑歧义就发生了,为了消除这个小歧义,干脆就声明为final的好了。
4 和局部内部类区别:局部内部类是方法内的类,不能有访问控制符,不过也持有外围类隐士引用
重点:
内部类和匿名内部类都可以向上转型 发挥更多设计模式的作用
话说 这个 Javap 命令可以帮助我们窥探更多类编译背后的秘密。Java研究的路上多用哈。
我们看下接口和类内部接口的编译:
接口 Interface CallBack{ Int i =3; void Call(); } Compiled from "CallBack.java" interface CallBack { public static final int i; //域都是 public static final 自动补全 真体贴 public abstract void Call();// 方法 public abstract 自动补全 真体贴 } 类内接口 多了一个static {}; Compiled from " Outer.java" interface Outer$Inner { static {};//想想咋回事呢?? public abstract void goToSchool(); }
好了我们在看一个 Android开发中内存溢出的典型:
如果我们开leakcanary会发现:
后台持有Activity的 Handler 引用使得 Activity销毁后不能被 GC掉。
一般我们都会在Activity内:
Public Handler eventHandler = new Handler(){//匿名内部类都属于内部类 持有外部引用 @Override public void handleMessage(Message msg) { } } }; 可以不持有外部引用(不好用) public static class MyHandler implements Handler{ @Override public void handleMessage(Message msg) { //处理异步任务回传消息 //不过静态内部类只能调用外部类静态域(限制) } } } Public Handler eventHandler = new MyHandler();//就和组合类一样 新建嵌套类型 不持有外部类引用
解决办法:
1 OnDestory内主动移除 和界面有关的Message
主动关闭后台相关的一异步任务
2 新建一个类Extends Handler 让其持有 用这个Handler类的来(比如Activity) 从其他进程回传动作的发起者 的弱引用 。但这个设计 会使得Handler 和 创建Hanlder的类关系疏远,即创建者可以忽略(这个时候创建者已经不存在了,,此处应有判空逻辑)Hanler的回传动作。
如果希望Handler的每个回调都得到处理,在UI显示的情况下是肯定满足的,但是当在回调不涉及UI情况下,则应该考虑将这部分逻辑从Activity中拆分出去。比如建立一个Presenter辅助Activity ,Presenter依然持有Activity弱引用。
我喜欢画个图 直观地看:
有人可能嫌presenter 麻烦 可以省掉 ,个人设计爱好~~
相关文章推荐
- cocos2d-x 游戏嵌入到ios/android应用内部 - android篇
- android应用内部通过跳转微博指定用户页面
- android 应用内部获取本应用或者对应包名的应用的SHA1签名的办法
- Android——Google应用移植时的包依赖关系
- 在Android应用内部,完全退出应用
- 在Android系统外部和内部读取Android应用的签名
- 应用中调用系统的搜索UI,Android Search Framework的初步了解
- WebView 基本应用示例——Android 使用WebView在应用内部打开web页面
- Android中 Lottie库初步实践与应用场景分析
- android获取APK文件,及应用内部签名信息方法
- Android应用进程和SurfaceFlinger的关系
- Android 内部系统应用的调用Intent
- Android移动应用开发初步——关于android studio的使用
- Android应用的R类与资源文件的关系说明
- Android 4.4 Settings 应用初步分析
- Android应用开发多线程基础之Handler,Looper,Message,MessageQueue,Runnable之间的关系
- Android Binder 机制初步学习 笔记(四,完结)—— Binder 简单应用示例
- 从头开始构建开源的Android应用研发ALM解决方案(一)缘起和初步规划
- Android 应用内部打开PDF文件