一文带你深入Java核心技术:对象克隆+接口与回调,还有这种操作
lipiwang 2024-10-15 18:43 11 浏览 0 评论
对象克隆
当拷贝一个变量时,原始变量与拷贝变量引用同一个对象,如图6-1所示。这就是说,改变一个变量所引用的对象将会对另一个变量产生影响。
Employee original = new Employee("John Public", 50000);
Employee copy = original;
copy.raiseSalary(10); // oops--also changed original如果创建一个对象的新拷贝(copy),它的最初状态与original一样,但以后将可以各自改变各自的状态,就需要使用clone方法。
Employee copy = original.clone( );
copy.raiseSalary(10); //OK--original unchanged
不过,事情并没有这么简单。clone是Object类的一个proteced方法,也就是说,在用户编写的代码中不能直接调用它。只有Employee类才能够克隆Employee对象。这种限制有一定的道理。让我们查看一下Object类实现的clone方法。
由于这个类对具体的类对象一无所知,所以只能将各个域进行对应的拷贝。如果对象中的所有数据域都属于数值或基本类型,这样拷贝域没有任何问题。但是,如果在对象中包含了子对象的引用,那么拷贝的结果会使得两个域引用同一个子对象,因此原始对象与克隆对象共享这部分信息。
为了能够说明这种现象,请再看一下第4章中介绍的Employee类。图6-2显示了使用Object类的clone方法克隆Employee对象的结果。可以看到,默认的克隆操作是“浅拷贝”,它并没有克隆包含在对象中的内部对象。
如果进行浅拷贝会发生什么呢?这要根据具体情况而定。如果原始对象与浅克隆对象共享的子对象是不可变的,将不会产生任何问题。也确实存在这种情形,比如,子对象属于像String类这样的不允许改变的类;也有可能子对象在其生命周期内不会发生变化,既没有更改它们的方法,也没有创建对它引用的方法。
然而,更常见的情况是子对象可变,因此必须重新定义clone方法,以便实现克隆子对象的深拷贝。在我们列举的例子中,hireDay域属于Date类,这就是一个可变的子对象。
对于每一个类,都需要做出下列判断:
1)默认的clone方法是否满足要求。
2)默认的clone方法是否能够通过调用可变子对象的clone得到修补。
3)是否不应该使用clone。
实际上,选项3是默认的。如果要选择1或2,类必须:
1)实现Cloneable接口。
2)使用public访问修饰符重新定义clone方法。
注意:在Object类中,clone方法被声明为protected,因此无法直接调用anObject.clone( )。但是,不是所有子类都可以访问受保护的方法吗?不是每个类都是Object的子类吗?值得庆幸的是,受保护访问的规则更为微妙(参阅第5章)。子类只能调用受保护的clone方法克隆它自己。为此,必须重新定义clone方法,并将它声明为public,这样才能够让所有的方法克隆对象。
在这里,Cloneable接口的出现与接口的正常使用没有任何关系。尤其是,它并没有指定clone方法,这个方法是从Object类继承而来的。接口在这里只是作为一个标记,表明类设计者知道要进行克隆处理。
如果一个对象需要克隆,而没有实现Cloneable接口,就会产生一个已检验异常(checked exception)。
注意:Cloneable接口是Java提供的几个标记接口(tagging interface)之一。(有些程序员将它们称为标记接口(marker interface)。)我们知道,通常使用接口的目的是为了确保类实现某个特定的方法或一组特定的方法,Comparable接口就是这样一个例子。而标记接口没有方法,使用它的唯一目的是可以用instanceof进行类型检查:if (obj instanceof Cloneable) . . .
建议在自己编写程序时,不要使用这种技术。
即使clone的默认实现(浅拷贝)能够满足需求,也应该实现Cloneable接口,将clone重定义为public,并调用super.clone( )。下面是一个例子:
注意:在JDK 5.0以前的版本中,clone方法总是返回Object类型,而在JDK 5.0中,允许克隆方法指定返回类型。
刚才看到的clone方法并没有在Object.clone提供的浅拷贝基础上增加任何新功能,而只是将这个方法声明为public。为了实现深拷贝,必须克隆所有可变的实例域。
下面是一个建立深拷贝clone方法的一个例子:
只要在clone中含有没有实现Cloneable接口的对象,Object类的clone方法就会抛出一个CloneNotSupportException异常。当然,Employee和Date类都实现了Cloneable接口,因此不会抛出异常。但是编译器并不知道这些情况,因此需要声明异常:
如果将上面这种形式替换成捕获异常呢?
这种写法比较适用于final类,否则最好还是在这个地方保留throws说明符。如果不支持克隆,子类具有抛出CloneNotSupportException异常的选择权。
必须谨慎地实现子类的克隆。例如,一旦为Employee类定义了clone方法,任何人都可以利用它克隆Manager对象。Employee的克隆方法能够完成这项重任吗?答案是,这将取决于Manager类中包含哪些域。在前面列举的例子中,由于bonus域属于基本类型,所以不会出现任何问题。但是,在Manager类中有可能存在一些需要深拷贝的域,或者包含一些没有实现Cloneable接口的域。没有人能够保证子类实现的clone一定正确。鉴于这个原因,应该将Object类中的clone方法声明为protected。但是,如果想让用户调用clone方法,就不能这样做。
在自己定义的类中应该实现clone方法吗?如果客户需要深拷贝就应该实现它。有些人认为应该全面地避免使用clone,并通过实现其他的方法达到此目的。我们同意这种观点,clone的确显得有点笨拙,但改用其他方法实现这项操作也会遇到同样的问题。至少,克隆的应用并不像人们想象的那样普遍。在标准类库中,只有不到5%的类实现了clone。
在例6-2的程序中,克隆了一个Employee对象,然后,调用了两个改变域值的方法。raiseSalary方法改变了salary域值,setHireDay方法改变了hireDay域值。由于clone实现的是深拷贝,所以对这两个域值的改变并没有影响原始对象。
注意:将在第12章中介绍另一种克隆对象的机制,其中使用了Java的序列化功能。这种机制很容易实现并且也很安全,但效率较低。
例6-2 CloneTest.java
接口与回调
回调(callback)是一种常见的程序设计模式。在这种模式中,可以指出某个特定事件发生时应该采取的动作。例如,可以指出在按下鼠标或选择某个菜单项时应该采取什么行动。然而,由于至此还没有介绍如何实现用户接口,所以只能讨论一些与上述操作类似,但比较简单的例子。
在java.swing包中有一个Timer类,可以使用它在到达给定的时间间隔时发出通告。例如,假如程序中有一个时钟,那么就可以请求每秒钟获得一个通告,以便更新时钟的画面。
在构造定时器时,需要设置一个时间间隔,并告知定时器,当到达时间间隔时需要做些什么操作。
如何告知定时器做什么呢?在很多程序设计语言中,可以提供一个函数名,定时器周期性地调用它。但是,在Java标准类库中的类采用的是面向对象方法。它将某个类的对象传递给定时器,然后,定时器调用这个对象的方法。由于对象可以携带一些附加的信息,所以传递一个对象比传递一个函数要灵活得多。
当然,定时器需要知道调用哪一个方法,并要求传递的对象所属的类实现了java.awt.event包的ActionListener接口。下面是这个接口:
当到达指定的时间间隔时,定时器就调用actionPerformed方法。
假设希望每隔10秒种打印一条信息“At the tone, the time is . . .”,然后响一声(beep),就应该定义一个实现ActionListener接口的类,然后将需要执行的语句放在actionPerformed方法中。
需要注意actionPerformed方法的ActionEvent参数。这个参数提供了事件的相关信息,例如,产生这个事件的源对象。有关这方面的详细内容请参阅第8章。在这个程序中,事件的信息并不重要,因此,可以放心地忽略这个参数。
接下来,构造这个类的一个对象,并将它传递给Timer构造器。
ActionListener listener = new TimePrinter( );
Timer t = new Timer(10000, listener);
Timer构造器的第一个参数是发出通告的时间间隔,它的单位是毫秒。我们希望每隔10秒通告一次。第二个参数是监听器对象。
最后,启动定时器:
t.start( );
每隔10秒钟,下列信息显示一次,然后响一声铃。
At the tone, the time is Thu Apr 13 23:29:08 PDT 2000
在例6-3中,给出了定时器和监听器的操作行为。在定时器启动以后,程序将弹出一个消息对话框,并等待用户点击Ok按钮来终止程序的执行。在程序等待用户操作的同时,每隔10秒显示一次当前的时间。
运行这个程序时要有一些耐心。程序启动后,将会立即显示一个包含“Quit program?”字样的对话框,10秒钟之后,第1条定时器消息才会显示出来。
需要注意,这个程序除了导入javax.swing.*和java.util.*外,还通过类名导入了javax.swing.Timer。
这就消除了javax.swing.Timer与java.util.Timer之间产生的二义性。这里的java.util.Timer是一个与本例无关的类,它主要用于调度后台任务。
例6-3 TimerTest.java
javax.swing.JOptionPane 1.2
? static void showMessageDialog(Component parent,Object message)
显示一个包含一条消息和OK按钮的对话框。这个对话框将位于parent组件的中央。如果
parent为null,对话框将显示在屏幕的中央。
javax.swing.Timer 1.2
? Timer(int interval, ActionListener listener)
构造一个定时器,每隔interval毫秒通告listener一次。
? void start( )
启动定时器。一旦启动成功,定时器将调用监听器的actionPerformed。
? void stop( )
终止定时器。一旦终止成功,定时器将不再调用监听器的actionPerformed。
javax.awt.Toolkit 1.0
? static Toolkit getDefaultToolkit( )
获得默认的工具箱。工具箱包含有关GUI环境的信息。
? void beep( )
发出一声铃响。
觉得文章不错的话,可以转发此文关注小编!!!
每天好技术,大家乐学习!!!
相关推荐
- 软件测试|MySQL CROSS JOIN:交叉连接的详细解析
-
简介在MySQL数据库中,CROSSJOIN是一种用于生成两个或多个表的笛卡尔积的连接方法。CROSSJOIN不需要任何连接条件,它将左表的每一行与右表的每一行进行组合,从而生成一个包含所...
- 「MySQL笔记」left join-on-and 与 left join-on-where 的区别
-
1.摘要关于这两种写法的重要知识点摘要如下:left-join时,即使有相同的查询条件,二者的查询结果集也不同,原因是优先级导致的,on的优先级比where高on-and是进行韦恩运算连接...
- MySQL中的JOIN——联合查询的基本语法
-
MySQL中的JOIN指令用来将两个或多个表中的数据进行联合查询,根据连接条件来匹配记录,从而得到需要的结果集。在MySQL中,常见的JOIN类型包括INNERJOIN、LEFTJOIN和RIGH...
- MySQL 中的 CROSS JOIN:强大的连接工具
-
CROSSJOIN在MySQL里是一种挺特别的连接操作,它能弄出连接表的笛卡尔积。这就是说,要是表A有m行,表B有n行,那ACROSSJOINB的结果就会有m*n...
- 大厂必问:MySQL 三表 JOIN 操作的解析与性能优化,效率又如何?
-
大厂必问:MySQL三表JOIN操作的解析与性能优化策略,效率又如何?点击关注,开启技术之旅!大家好,这里是互联网技术学堂,无论你是一名程序员、设计师、还是对技术充满好奇心的普通人,都欢迎你加入...
- 面试题:MySQL 的 JOIN 查询优化(mysql查询优化方法)
-
MySQL的JOIN查询优化是提升数据库性能的关键环节。以下是综合多个技术文档的核心优化策略,按优先级和实现难度分类:一、索引优化:性能提升的基础为连接字段建立索引确保参与JOIN的列(通常...
- Flink中处理维表关联技术实现路径
-
在Flink中处理维表关联大体氛围TableSQLLookupJoin和DataStream算子函数,主要技术实现路径:I.FlinkSQL/TableAPI中的Lookup...
- 深入剖析Zookeeper原理(一)整体设计
-
1.ZK集群架构设计与特性1.ZK集群架构设计:ZK主要分为三种角色:Leader(领导者):一个Zookeeper集群同一时间只会有一个实际工作的Leader,它会发起并维护与各Follwer及...
- 多种负载均衡算法及其Java代码实现
-
首先给大家介绍下什么是负载均衡负载均衡建立在现有网络结构之上,它提供了一种廉价有效透明的方法扩展网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。负载均衡,英...
- 一分钟了解SpringCloud中的ribbon到底是什么,原理是啥?
-
1.概念ribbon是一款客户端负载均衡器,用于微服务之间的负载均衡。首先,什么是客户端负载均衡?如图,ribbon可以通过注册中心获取服务列表,然后自己执行自己的负载均衡策略来决定要访问哪个微服务,...
- Step by Step之腾讯云短信-验证码实践
-
在商城小程序和前端上线用了一阵子之后,用户提出了体验提升的需求,如忘记密码、绑定用户、快捷注册等,作为业界最佳实践的短信验证码登录、重置密码和注册等功能开发也就提上日程了,本文就以重置密码为例,将验证...
- 10分钟入门响应式:Springboot整合kafka实现reactive
-
Springboot引入Reactor已经有一段时间了,笔者潜伏在各种技术群里暗中观察发现,好像scala圈子的同仁们,似乎对响应式更热衷一点。也许是因为他们对fp理解的更深吧,所以领悟起来障碍性更少...
- 使用java随机生成有个性的用户名,LOL地名+水浒传,合计2808个
-
*随机生成用户名*取水浒传108好汉名字*取LOL地名26个,组合而成*一共可以生成2808个不同特色的用户名如果你在上网的时候,用户名难取的话,这里有很多可选择的用户名,现提供100个...
- 深入理解Math.random()的概率分布特性
-
直接上源码/***Returnsa{@codedouble}valuewithapositivesign,*返回一个带符号的double类型的数字,说人话就是返回一个非负...
- 编程英文 - 创建/生成/构建 (create/generate/build)
-
在软件开发中,create、generate和build这三个词经常被用到,它们都与"创造"或"产生"某些东西有关,但在具体使用场景和含义上有所不同。基本含义creat...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- 软件测试|MySQL CROSS JOIN:交叉连接的详细解析
- 「MySQL笔记」left join-on-and 与 left join-on-where 的区别
- MySQL中的JOIN——联合查询的基本语法
- MySQL 中的 CROSS JOIN:强大的连接工具
- 大厂必问:MySQL 三表 JOIN 操作的解析与性能优化,效率又如何?
- 面试题:MySQL 的 JOIN 查询优化(mysql查询优化方法)
- Flink中处理维表关联技术实现路径
- 深入剖析Zookeeper原理(一)整体设计
- 多种负载均衡算法及其Java代码实现
- 一分钟了解SpringCloud中的ribbon到底是什么,原理是啥?
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)