Java内部类this$0字段产生的一个bug
首先查看下面一段代码,我指出了问题代码的所在,读者先自己思考一下这段代码会有什么问题。
这是用clone方法完整拷贝一个二项堆(BinomialHeap)结构的代码。二项堆中包含一个内部类BinomialHeapEntry,这个内部类的对象即二项堆中的每一个结点,除了包含结点对应的关键字外,还记录父节点parent,下一个兄弟结点sibling和第一个孩子结点child三个指针。二项堆的根表通过每棵二项树根节点的sibling指针链接。
cloneBinomialTree(BinomialHeapEntry root)方法递归拷贝一个根节点(含根节点)下的整个二项树,clone方法中遍历根表中每个树根结点,拷贝对应的二项树,然后将新的head指针赋给拷贝后的新堆。
public class BinomialHeap<E> implements Cloneable {
transient BinomialHeapEntry head; // 根表中的第一个元素
int size; // 整个二项堆的大小(结点数)
private class BinomialHeapEntry {
E element;
transient BinomialHeapEntry parent = null, child = null,
sibling = null;
transient int degree = 0;
private BinomialHeapEntry(E element) {
this.element = element;
}
// 获得孩子结点的迭代器
private Iterable<BinomialHeapEntry> children {...}
}
@Override
public BinomialHeap<E> clone() {
// TODO Auto-generated method stub
BinomialHeap<E> clone;
try {
clone = (BinomialHeap<E>) super.clone();
} catch (CloneNotSupportedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new InternalError();
}
BinomialHeapEntry newHead = null, curRoot = null;
// 遍历根表
Iterator<HeapEntry<E>> rootIt = this.roots().iterator();
while(rootIt.hasNext()){
BinomialHeapEntry root = (BinomialHeapEntry) rootIt.next();
// 拷贝根节点下的整棵二项树
// BUG引入的地方
BinomialHeapEntry newRoot = cloneBinomialTree(root);
if(curRoot == null){
newHead = newRoot;
} else {
curRoot.sibling = newRoot;
}
curRoot = newRoot;
}
clone.head = newHead;
return clone;
}
// 拷贝根节点(含根节点)下的整棵子树
private BinomialHeapEntry cloneBinomialTree(BinomialHeapEntry root){
BinomialHeapEntry newRoot = new BinomialHeapEntry(root.element());
BinomialHeapEntry curChild = null;
// 遍历根节点的所有孩子结点
Iterator<HeapEntry<E>> childIt = root.children().iterator();
while(childIt.hasNext()){
BinomialHeapEntry child = (BinomialHeapEntry) childIt.next();
// 递归拷贝孩子节点下的子树
BinomialHeapEntry newChild = cloneBinomialTree(child);
newChild.parent = newRoot;
if(curChild == null){
newRoot.child = newChild;
} else {
curChild.sibling = newChild;
}
curChild = newChild;
}
newRoot.degree = root.degree;
return newRoot;
}
}
先回顾一下Java内部类的一些知识,非静态的Java内部类作为外部类的一个成员,只能通过外部类的对象来访问,要创建一个内部类对象,也需要通过外部类对象来创建,即:outerObject.new InnerClass()。这时,所创建的内部类对象会产生名称为this$0的一个字段,该字段保存的是创建这个内部类对象的外部类对象引用,即刚才的outerObject。这个引用使得一个内部类对象可以自由的访问外部类的成员变量。
在回顾上面二项堆拷贝的代码,在调用cloneBinomialTree方法拷贝二项树时,我们试图通过new BinomialHeapEntry()来创建一个新的结点,把新的结点作为新二项堆中的结点,但事实上我们实际调用的是this.new BinomialHeapEntry(),也就是说创建的新结点中,this$0是指向老的二项堆(this)而不是新的正在拷贝的二项堆(clone)。这使得我们得到拷贝的新二项堆之后,通过新二项堆的任一结点访问和修改整个堆的信息时,修改的都是老的二项堆,最后产生了一系列奇怪的结果。
要想修复这个bug很简单,把clone方法中的BinomialHeapEntry newRoot = cloneBinomialTree(root)即红色注释下的语句,修改为BinomialHeapEntry newRoot = clone.cloneBinomialTree(root)。这样cloneBinomialTree方法中创建的结点都是通过clone对象创建,问题也就解决了。
问题其实比较简单,这确实是以后在编程中值得注意的一点:拷贝内部类对象时考虑清楚拷贝后的对象this$0字段是否指向的是你希望指向的外部类对象。
Java内部类this$0字段产生的一个bug的更多相关文章
- A fatal error has been detected by the Java Runtime Environment(jdk 1.6的一个BUG)
几天做项目,生成一堆注解的实体,当实体数超过86个时,jvm报错: # # A fatal error has been detected by the Java Runtime Environmen ...
- java内部类的作用
java内部类的作用 定义: 放在一个类的内部的类我们就叫内部类. 二. 作用: 1.内部类可以很好的实现隐藏 一般的非内部类,是不允许有 private 与protected权限的,但内部类可以 2 ...
- 给JDK提的一个bug(关于AbstractQueuedSynchronizer.ConditionObject)
1. 背景 之前读JUC的AQS源码,读到Condition部分,我当时也写了一篇源码阅读文章--(AbstractQueuedSynchronizer源码解读--续篇之Condition)[http ...
- Java内部类总结 分类: 原理 2015-06-28 09:51 9人阅读 评论(0) 收藏
内部类是指在一个外部类的内部再定义一个类.内部类作为外部类的一个成员,并且依附于外部类而存在的. 内部类可为静态,可用protected和private修饰(而外部类只能使用public和缺省的包访问 ...
- TypeLoadException: 未能从程序集“ECS.GUI.Define, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null”中加载类型“ECS.GUI.Define.ArmgAimPos”,因为它在 4 偏移位置处包含一个对象字段,该字段已由一个非对象字段不正确地对齐或重叠
TypeLoadException: 未能从程序集"ECS.GUI.Define, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null ...
- java题求代码,4、现在有如下的一个数组: int oldArr[]={1,3,4,5,0,0,6,6,0,5,4,7,6,7,0,5} 要求将以上数组中值为0的项去掉,将不为0的值存入一个新的数组,生成的新数组为: int newArr[]={1,3,4,5,6,6,5,4,7,6,7,5}
public class TEST { public static void main(String[] args) { int [] oldArr= {1,3,4,5,0,0,6,6,0,5,4,7 ...
- Java内部类final语义实现
本文描述在java内部类中,经常会引用外部类的变量信息.但是这些变量信息是如何传递给内部类的,在表面上并没有相应的线索.本文从字节码层描述在内部类中是如何实现这些语义的. 本地临时变量 基本类型 fi ...
- java 内部类 *** 最爱那水货
注: 转载于http://blog.csdn.net/jiangxinyu/article/details/8177326 Java语言允许在类中再定义类,这种在其它类内部定义的类就叫内部类.内部类又 ...
- java内部类、接口、集合框架、泛型、工具类、实现类
.t1 { background-color: #ff8080; width: 1100px; height: 40px } 一.内部类 1.成员内部类. (1)成员内部类的实例化: 外部类名.内部类 ...
随机推荐
- 【nginx网站性能优化篇(2)】反向代理实现Apache与Nginx的动静分离(LNMPA)
为什么要使用反向代理 具体请参考这篇博文:[Linux常识篇(1)]所谓的正向代理与反向代理 在虚拟机上配置反向代理的步骤 首先假设你已经假设好了LNMP架构了,这时我们还要安装Apache和php, ...
- Linux资源监控命令/工具(网络)
1.手动/自动设定与启动/关闭IP参数:ifconfig,ifup,ifdown 这三个指令的用途都是在启动网络接口,不过,ifup与ifdown仅能就/etc/sysconfig/netw ...
- 计算XX年的某月某日是当年的第多少天?是星期几?
/** * 编写程序,计算2013年的某月某日是当年的第多少天?是星期几?已知2013年元旦是星期二. */ import java.util.Scanner; public class Date { ...
- MySQL登录报错"Access denied for user 'root'@'localhost' (using password: YES)"
最近登录MySQL时候总报错: # mysql -uroot -p Enter password: ERROR (): Access denied for user 'root'@'localhost ...
- 如何用Maven创建一个普通Java项目
一下内容包括:用Maven创建一个普通Java项目,并把该项目转成IDEA项目,导入到IDEA,最后把这个项目打包成一个jar文件. 有时候运行mvn命令失败,重复运行几次就OK了,无解(可能因为网络 ...
- Linux下jvm、tomcat、mysql、log4j优化配置
小菜一直对操作系统心存畏惧,以前也很少接触,这次创业购买了Linux云主机,由于木有人帮忙,只能自己动手优化服务器了.... 小菜的云主机配置大致为:centeos6(32位),4核心cpu,4G内存 ...
- jboolean
bool为C中变量类型,jboolean 为JNI中变量类型,boolean为Java中变量类型:jboolean在C语言的定义为:typedef unsigned char jboolean;uns ...
- 进程控制的一些api
转自:http://blog.chinaunix.net/uid-26833883-id-3222794.html 1.fork() ,vfork() 创建进程 2‘ exec()类 在进程中执行其他 ...
- oracle索引,索引的建立、修改、删除
索引,索引的建立.修改.删除 2007-10-05 13:29 来源: 作者: 网友评论 0 条 浏览次数 2986 索引索引是关系数据库中用于存放每一条记录的一种对象,主要目的是加快数据的读取速度和 ...
- hdu 1885 Key Task (三维bfs)
题目 之前比赛的一个题, 当时是崔老师做的,今天我自己做了一下.... 还要注意用bfs的时候 有时候并不是最先到达的就是答案,比如HDU 3442 这道题是要求最小的消耗血量伤害,但是并不是最先到 ...