本帖最后由 zeus00456 于 2014-7-26 09:00 编辑
这个问题比较复杂,结合了多线程外加匿名内部类这两个难度较大的知识点,难为了lz不知道从哪里淘来的代码:)
我们来一步一步的分析一下这一坨代码吧
我不仅想尝试回答lz的问题,还试图让看不懂这坨代码的筒子们能在看完我的回复后明白一些,
故,书写啰嗦,见谅。
==============================
首先,匿名内部类
下面是楼主原本的代码
new Thread(new Runnable()
{public void run()
{ System.out.println("runnable run");} })
{public void run()
{System.out.println("subThread run");}
}.start();
我们把碍眼的东西都删掉
new Thread(/*位置A*/){
位置B
}.start();
诶,这样是不是稍微清楚点了?
回想一下,格式: new 一个类名(){复写其中的方法}.上面类中的方法的方法名();
这样的形式是:先定义一个匿名内部类,复写其“一个类名”所对应的类的方法,然后直接调用它的方法
----------------------------------------------
需要澄清下面几点
首先,这个匿名内部类是“一个类名”所对应的类的子类。
其次,这个所谓的“匿名内部类”其实是一个匿名内部类的对象,注意它的本质是一个对象(只有对象才能调用非静态成员方法,这一点解释了源代码最后的“.start();”部分)
比如,new Person(){
public void show(){
/*随便什么语句,输出个“hello”好了*/
} };
由于我们在Person(){}的大括号中已经复写了Person类中的方法(如果Person类中本身存在show()方法),或者它定义了一个子类特有方法(如果Person类中不存在show()方法),所以,我们现在new 出来的已经不是Person的对象了(因为Person中本来没有show方法,或者有show方法但是那个show的功能不是现在这个我们已经复写的show)。那么我们现在new出来的是个什么东西呢?它是Person类的子类,复写父类Person中的Show方法或定义了其特有的show方法。
--------------------------------
实质上,它是下面一段代码的简化版(如果谁非要理解成阉割版,我个人表示没有意见)
class Person{
/*这里没有show方法*/
/*当然也可以理解成有,但是和咱一会定义的不一样*/
}
class XX extends Person{
class Student extends Person{//定义成内部类的形式
show(){/*这里定义了一个子类方法,或复写父类方法*/}}
}
new Student();//所谓的匿名内部类,匿的就是Student(子类)的名字
}
====================
其次,多线程
多线程的两种开线程的方式,其一,继承Thread类,复写其run方法;其二,实现Runnable接口中的run方法
注意,Thread本身就是Runnable的一个实现类
=====================
让我们回到最一开始的问题,那个代码整体就是一个匿名内部类的形式,
new Thread(/*位置A*/){
位置B
}.start();
位置B定义了 Thread的子类对象(匿名内部类对象,也是此时new出来的对象)象复写后的父类的run方法
位置A是 Thread类的子从其父类Thread继承的带参数的构造方法的参数。这个参数是一个Runnable的子类对象,子类对象同样复写了run方法。
这样,lz的代码可以看做是
new Thread(这里面Runnable接口的实现类对象){
这里复写了Thread的run方法
}.start();-----------------
查阅API,关于此段代码用到的Thread构造方法的描述为
public Thread(Runnable target)分配新的 Thread 对象。这种构造方法与 Thread(null, target,gname) 具有相同的作用(我擦,好吧,我再查)
public Thread( ThreadGroup group,Runnable target,String name)//上面的构造方法实际上是这个方法的特殊形态
分配新的 Thread 对象,以便将 target 作为其运行对象。
也就是说,创建一个Runnable类型的对象,对象的Run中定义了线程的任务(线程需要执行的代码),将这个Runnable类型的对象作为参数构造Thread类的对象,使用这种方式构造的Thread对象会开一条线程,然后运行Runnable对象中run方法定义的任务
也就是说,这是一种直接在线程对象构造时就规定线程任务的方式,无需复写run方法
-----------------------------
这段代码矛盾的地方出现了,在构造Thread的子类对象时,使用了可以直接定义线程任务的构造形式,但是同时还复写了run方法
源代码中System.out.println("runnable run")是Runnable的对象定义的线程任务,是Thread类的对象在构造时就被指派的任务System.out.println("subThread run")是Thread类复写run方法时,被指派的任务那么虚拟街是如何处理这两个任务的呢?(JVM说:你构造的时候你传来一个任务,然后你还复写了一个任务,都让我新建出来的线程执行,耍我呢?)
其实这个问题就是:这两个任务是谁后被写入到Thread类对象的,因为同一个线程只能执行一个run方法指派的任务,理所当然的,后写入的任务会覆盖先写入的任务,而作为Thread的线程最终执行的任务。
那好,一个任务是构造方法时定义的,一个是复写方法时定义的,问题变成了:复写已有方法,这个动作jvm是什么时候执行的?是否是在构造函数执行完毕后?
很明显,从结果上看确实貌似是这样。那么这段代码就应该是这么工作的:
需要创建thread的子类对象,使用了父类的Thread(Runnable实现类对象)的构造方法
方法需要一个Runnable实现类对象作为参数
创建一个Runnable实现类对象,使用Runnable的空参构造函数,复写其中的run方法,此时,run还没被设置为线程任务此对象作为参数传入Thread(Runnable实现类对象)执行Thread 的这个构造方法,创建线程对象,然后***构造方法会将runnable对象中的run设置为Thread对象的任务***(猜测原理就是用作为构造器参数的Runnable类对象的run方法复写Thread子类对象从父类Thread中继承的run方法),这之后,Thread的构造方法执行完毕
用用户定义的Thread类子类方法复写父类方法,Thread子类对象之前通过带有runnable对象的构造器获取的run任务被覆盖
因此,运行时,执行System.out.println("subThread run")这句话
--------------------------------------
我们不妨用下面的代码验证一下我们的结论
class A{
A(){
System.out.println(toString());
}
public String toString() {
return "复写后";
}
}
System.out.println(new A().toString());
如果我们刚刚的猜测是正确的,那么结果应该是一个哈希值和一个“复写后”,因为构造器中的toString是从Object类中继承的。
然而,很遗憾,结果是两个 “复写后”。
也就是说,jvm是现将从父类中继承的方法复写后,再使用构造器进行初始化的。
其实这也好理解:System.out.println(toString())一句等同于System.out.println(this.toString())(要不,是用谁调用的方法啊)
而this代表着本类实例的引用,注意,实例的引用,引用时已经有实例了。
很有可能,JVM在子类的字节码对象被创建时就已经复写好父类方法了,构造器只是在此字节码对象的基础上进行初始化操作。
-------------------------------------
如此一来,lz的问题就只有三种解释方式了
其一,Thread的子类对象复写了run方法作为任务,Runnable实现类作为参数传入Thread子类携带了一个任务,但是,Thread类本身就是Runnable接口的实现类,也就是说,Thread的子类比Runnable的实现类***更子类***。更子类就意味着更具体,功能更全面,其中定义的方法应该具有更高的覆盖优先级。换言之,子类的子类的方法 相对于 子类的方法 更具有优越性,所以前者会覆盖后者
其二,在lz提供的代码中,我们最终创建的是一个Thread的子类对象,所以,此对象的方法会优先使用 定义在这个对象的构造类中的方法(我们要的是Thread子类对象,所以我们用的也是Thread子类中提供的方法)
其三,多线程特性,最先写入线程对象的run方法中所夹带的线程任务会被确定,后来者不可覆盖修改
//这一条只有在第一第二条都不正确时才可能成立
我勒个去,好吧,下面还有一截
|