thinking in java 学习笔记7 复用类
第七章 复用类
?
对于java而言,很重要的都是在讨论复用,高内聚,低耦合,其中复用是我们平时接触的比较多的一大块,先说一下我个人的代码变化,刚开始时复制别人的代码,然后是复制修改,接着是自己写的大部分,部分复制,后来就是参考别人,衍生出自己要的东西,现在就是写成模块,然后自己用的舒服,提供接口这样,这里少不了代码的复用性
?
?
?
?
.复用的方法
1.组合:在新的类中产生现有类的对象,直接让这些现有的对象为新类服务
2.继承:在现有类中添加新的方法,无需改变现有类
3.代理:把一个成员对象于新类中(组合,has-a),但是在新类中编写方法暴露该成员对象的所有方法.
?
?
.组合
这个我们平时用得最多,也就是那回事,写一个类,里面有好多功能,然后在一个新类里面实例化,然后调用其方法
下列有4种初始化的方法
?
1.在定义对象的地方,它们总能在构造器被调用之前初始化
String s=“aa”;
下面是构造器
?
2.在类的构造器里面
String s;
constructor(){
s=“aa”;
}
?
3.在正要使用这些对象之前,这种方法称为惰性初始化,在种方法可以减少额外负担
在子类中
public String toString(){
?
?
if(s==null){
s=“aa”;
}
return s;
}
?
4.使用实例初始化
bath b=new bath
?
组合在我们的编程中是比较普遍的,我个人也很喜欢,意思就是我会大量使用
?
.继承
老祖宗是Object类,子类继承父类只要用extented就可以了
注:当继承时,会自动继承基类中所有的域和方法,private是可以继承的,但是不可以访问,我觉得继承也就是那老
这里有待商榷,明天再解决
?
建议在每一个类里面写main方法,方便测试
?
初始化基类
在初始化子类的对象时,实际上已经初始化了一个基类对象
所以初始化顺序是这样的
基类构造器,子类构造器(具体初始化顺序,在我的第五章有说,而且在后面章节,我们还会继续探讨)
注:其实在子类的构造器第一行都是调用基类构造器的,所以当无参数的构造器时,可以不用写出来,但是如果基类的构造器是带参数的,必须显示写出来,super(i)这样
?
?
?
.代理
其实就是组合模仿继承,呵呵,
因为需要控制方法的公开,如果使用继承,比如说太空城控制器类,和太空船类,这明显不是继承的关系,可以说为了逻辑上的安慰,用代理吧,先组合new对象,然后再用类的方法包装对象的一些方法,这样就可以为外面提供且控制接口了
?
总结一下,继承很容易写死,我个人认为,组合灵活性好,简单方便
?
?
.清理问题
try finally块清理,注意的是依赖对象问题,先子,再老爸
注意啦,不要依赖finnalize和垃圾回收器
?
?
.名称屏蔽
子类可以重载父类的方法,也可以重写,如果要重写建议加一个便签 @override ,这样编译器就知道你是重写,如果你不小心重载了,会报错的,有些人说同类里面的才叫重载,但是经过我翻查资料与询问老师,得到的结论是,基类的方法会被copy到子类中(形式外表与子类的本身方法无区别),所以必须重载区别..
?
?
.向上转型
为新的类提供方法不是继承中最重要的一面,而是用来表现新类与基类直接的关系,这种关系可以表达为,新类是现有类的一种类型,其实通常我们用于外部接口为基类类型,然后你可以用新类传入,这样就能实现低耦合性,到后面我们还会介绍接口,新类转换为基类,这种叫做向上转型,是安全的,唯一可能不好的就是会丢失新类的新方法
?
?
.判断应该使用组合还是继承:是否需要向上转型,这是很重要的一点哦
?
?
.final关键字
final就是不变的东西,需要作为一个常量时,常与static搭配,并且大写与下划线
final也分为静态与非静态,区别就是只有当数值在运行时内被初始化是才会显现
空白final,作用是可以做到根据对象而有所不同,却有保持恒定不变的特性,怎么理解呢,意思就是你可以在代码在控制你需要常量化的对象,灵活性增加了,不过不过特别需要注意的是,必须要在构造器中用表达式对空白final赋值,否则会出错
?
final方法,把方法锁定,防止继承类修改覆盖,变量也是同样奏效的
注意,不要指望final会为你带来什么性能优化,只有明确禁止覆盖方法,才会使用final
?
?
.final和private关键字
private方法都隐式指定为final的,
?
.final类
表示不能继承的类,所有方法被隐式指定为final,不过变量没有指定为final,final慎用.
n?? 并非程序一运行就全部加载,而是用到的时候再加载(动态加载)
n?? 触发类加载的时机
u??类的代码在初次使用时加载
l?? new对像
l?? 访问static成员或者方法
?
总结,优先选择组合...
?
?
?
?
注:java的类加载内幕?http://blog.csdn.net/aspdao/article/details/5309350
?
?
?
?
图1. 在同一个JVM中多个类加载器加载同一个目标类
关于类加载、定义和链接的更多解释,请参考Andreas Schaefer的"Inside Class Loaders."
为什么我们需要我们自己的类加载器
原因之一为研发者写自己的类加载器来控制JVM中的类加载行为,java中的类靠其包名和类名来标识,对于实现了java.io.Serializable接口的类,serialVersionUID扮演了一个标识类版本的重要角色。这个唯一标识是个类名、接口名、成员方法及属性等组成的一个64位的哈希字段,而且也没有其他快捷的方式来标识一个类的版本。严格说来,如果以上的都匹配,那么则属于同一个类。
不过让我们思考如下情况:我们需要研发一个通用的执行引擎。能执行实现某一特定接口的所有任务。当任务被提交到这个引擎,首先需要加载这个任务的代码。假设不同的客户对此引擎提交了不同的任务,凑巧,这些所有的任务都有一个相同的类名和包名。目前面临的问题就是这个引擎是否能针对不同的用户所提交的信息而做出不同的反应。这一情况在下文的参考一节有可供下载的代码样例,samepath 和 differentversions,这两个目录分别演示了这一概念。
图2 显示了文件目录结构,有三个子目录samepath, differentversions, 和 differentversionspush,里边是例子:
?
图2. 目录结构组织示例
在samepath 中,类version.Version保存在v1和v2两个子目录里,两个类具有同样的类名和包名,唯一不同的是下边这行:
?
?
图3. 在类路径中samepath测试排在最前面的version 1
再次运行,类路径做如下微小改动。
set CLASSPATH=.;%CURRENT_ROOT%v2;%CURRENT_ROOT%v1
%JAVA_HOME%binjava Test
控制台的输出变为图4。对应着Version.fx(2)的代码被加载,因为类加载器在classpath中首先找到他的路径。
?
图4. 在类路径中samepath测试排在最前面的version 2
根据以上例子能非常明显地看出,类加载器加载在类路径中被首先找到的元素。如果我们在v1和v2中删除了version.Version,做一个非version.Version形式的.jar文件,如myextension.jar,把他放到对应java.ext.dirs的路径下,再次执行后看到version.Version不再被AppClassLoader加载,而是被扩展类加载器加载。如图5所示。
图5. AppClassLoader及ExtClassLoader
继续这个例子,目录differentversions包含了一个RMI执行引擎,客户端能提供给执行引擎所有实现了common.TaskIntf接口的任务。子目录client1 和 client2包含了类client.TaskImpl有个细微不同的两个版本。两个类的差别在以下几行:
图6. 执行引擎服务器控制台
图6显示了服务端的控制台,加载并执行两个不同的客户端的请求,如图7,8所示。需要注意的是,代码只被加载了一次(从静态初始化块的日志中也能明显看出),但对于客户端的调用这个方法被执行了两次。
图7. 执行引擎客户端 1控制台
图7中,客户端VM加载了含有client.TaskImpl.class.getClassLoader(v1)的日志内容的类TaskImpl的代码,并提供给服务端的执行引擎。图8的客户端VM加载了另一个TaskImpl的代码,并发送给服务端。
图8. 执行引擎客户端 2控制台
在客户端的VM中,类client.TaskImpl被分别加载,初始化,并发送到服务端执行。图6还揭示了client.TaskImpl的代码只在服务端的VM中加载了一次,但这“唯一的一次”却在服务端创造了许多实例并执行。或许客户端1该不高兴了因为并不是他的client.TaskImpl(v1)的方法调用被服务端执行了,而是其他的一些代码。怎么解决这一问题?答案就是实现制定的类加载器。
制定类加载器
要较好地控制类的加载,就要实现制定的类加载器。所有自定义的类加载器都应继承自java.lang.ClassLoader。而且在构造方法中,我们也应该设置父类加载器。然后重写findClass()方法。differentversionspush目录包含了一个叫做FileSystemClassLoader的自订制的类加载器。其结构如图9所示。
图9. 制定类加载器关系
以下是在common.FileSystemClassLoader实现的主方法:
?
图10. 制定类加载器执行引擎
图10显示的是制定的类加载器控制台。我们能看到client.TaskImpl的代码被多次加载。实际上针对每一个客户端,类都被加载并初始化。
图11. 制定类加载器,客户端1
图11中,含有client.TaskImpl.class.getClassLoader(v1)的日志记录的类TaskImpl的代码被客户端的VM加载,然后送到服务端。图12 另一个客户端把包含有client.TaskImpl.class.getClassLoader(v1)的类代码加载并送往服务端。
图12. 制定类加载器,客户端1
这段代码演示了我们怎么利用不同的类加载器实例来在同一个VM上执行不同版本的代码。
J2EE的类加载器
J2EE的服务器倾向于以一定间隔频率,丢弃原有的类并重新载入新的类。在某些情况下会这样执行,而有些情况则不。同样,对于一个web服务器如果要丢弃一个servlet实例,可能是服务器管理员的手动操作,也可能是此实例长时间未相应。当一个JSP页面被首次请求,容器会把此JSP页面翻译成一个具有特定形式的servlet代码。一旦servlet代码被创建,容器就会把这个servlet翻译成class文件等待被使用。对于提交给容器的每次请求,容器都会首先检查这个JSP文件是否刚被修改过。是的话就重新翻译此文件,这能确保每次的请求都是及时更新的。企业级的部署方案以.ear, .war, .rar等形式的文件,同样需要重复加载,可能是随意的也可能是依照某种设置方案定期执行。对所有的这些情况??类的加载、卸载、重新加载……全部都是建立在我们控制应用服务器的类加载机制的基础上的。实现这些需要扩展的类加载器,他能执行由其自身所定义的类。Brett Peterson已在他的文章 Understanding J2EE Application Server Class Loading Architectures给出了J2EE应用服务器的类加载方案的周详说明,详见网站TheServerSide.com。
结要
本文探讨了类载入到虚拟机是怎么进行唯一标识的,及类如果存在同样的类名和包名时所产生的问题。因为没有一个直接可用的类版本管理机制,所以如果我们要按自己的意愿来加载类时,需要自己订制类加载器来扩展其行为。我们能利用许多J2EE服务器所提供的“热部署”功能来重新加载一个新版本的类,而不改动服务器的VM。即使不涉及应用服务器,我们也能利用制定类加载器来控制java应用程式载入类时的具体行为。Ted Neward的书Server-Based Java Programming中周详阐述java的类加载,J2EE的API及使用他们的最佳途径。
?
?
?
?
?
?
?
?
java美女7
?
?
?
?
?
?
?
?
?
?
?
?