黑马程序员技术交流社区

标题: 这段代码,有些不懂。 [打印本页]

作者: lpc4276139    时间: 2014-7-23 23:11
标题: 这段代码,有些不懂。
new Thread(new Runnable()
                {
                        public void run()
                        {
                                System.out.println("runnable run");
                        }
                })
                {
                        public void run()
                        {
                                System.out.println("subThread run");
                        }
                }.start();
为啥此时运行的是子类的方法为主,
作者: springing    时间: 2014-7-24 04:11
哪里有子类?怎么看着头晕?   把那个RUN传入了Thread ,然后一个代码块就调用Start了,怎么回事。。那个线程对象怎么和后面的代码块衔接上的。做成了一个方法了?
作者: lpc4276139    时间: 2014-7-24 19:18
springing 发表于 2014-7-24 04:11
哪里有子类?怎么看着头晕?   把那个RUN传入了Thread ,然后一个代码块就调用Start了,怎么回事。。那个线 ...

new thread()
{

}
注意这个,这是匿名内部类,这不过调用了两次匿名内部类,你可以看看
作者: 小毛怪    时间: 2014-7-24 20:58
看不懂,确定没写错
作者: zeus00456    时间: 2014-7-25 21:26
本帖最后由 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方法中所夹带的线程任务会被确定,后来者不可覆盖修改
//这一条只有在第一第二条都不正确时才可能成立

我勒个去,好吧,下面还有一截



作者: lpc4276139    时间: 2014-7-25 22:22
zeus00456 发表于 2014-7-25 21:26
这个问题比较复杂,结合了多线程外加匿名内部类这两个难度较大的知识点,难为了lz不知道从哪里淘来的代码 ...

大哥,这不是淘来的代码,这是毕老师视频的代码,我理解不了
作者: lpc4276139    时间: 2014-7-25 22:24
zeus00456 发表于 2014-7-25 21:26
这个问题比较复杂,结合了多线程外加匿名内部类这两个难度较大的知识点,难为了lz不知道从哪里淘来的代码 ...

先说句谢谢,但是你的解释不是很明白,毕老师讲的是 子类覆盖的run()方法,肯定运行子类的,如果没有子类,就要运行任务,毕老师的意思是new runnable()属于任务。
作者: zeus00456    时间: 2014-7-25 23:12
本帖最后由 zeus00456 于 2014-7-25 23:23 编辑
lpc4276139 发表于 2014-7-25 22:24
先说句谢谢,但是你的解释不是很明白,毕老师讲的是 子类覆盖的run()方法,肯定运行子类的,如果没有子 ...

因为你看的时候我还没有回复完,实际上,我在回复你这一句的时候也没回复完而且,很可能,你也没有仔细看

作者: zeus00456    时间: 2014-7-26 09:00
本帖最后由 zeus00456 于 2014-7-26 09:10 编辑
zeus00456 发表于 2014-7-25 21:26
这个问题比较复杂,结合了多线程外加匿名内部类这两个难度较大的知识点,难为了lz不知道从哪里淘来的代码 ...

-------------------------------------

接着上面的回帖

让我们对上面的几条猜测进行验证吧

使用下面的代码

class A{
int i =1;
public void show(){  System.out.println(i); }
}
class B extends A{
int i=2;
public void show(){  System.out.println(i); }
}
class C extends B{//C是B的子类,但是B是A的子类
int i = 3;
C(){  change(); }
public void change(){  i=6; }
public void show(){  System.out.println(i); }
}
class D extends A{//D是A的子类
int i=4;
D(C c){change();show();}
public void change(){  i =8; }
public void show(){  System.out.println(i); }
}

new D(new C()).show();

在这一段代码中,A是一个父类,相当于Runnable;B是A的子类,相当于Thread;C是B的子类,相当于Thread的子类对象;D是A的子类,相当于Runnable的实现类对象。
现在我们要创建一个A的直接子类(D),但是用一个A的子类的子类(C)对象  作为  A的直接子类(D)的构造器参数。注意,C类的对象中的方法对于D类来讲更加子类。在注意,我们要创建的是D类而不是“最子类"的C类的对象。

如果上面第一条是正确的,输出结果应该为两个“6”
因为C是子类的子类,其方法的覆盖优先级更高,C中的change方法将写入D类的字节码对象,并成为最终的change方法,这之后,无论是在构造器中change还是在对象创建之后,i的值都应该是6。

如果上面第二条是正确的,输出结果应该为两个“8”
因为创建的是D的实例,D类中定义的change方法将写入D类的字节码对象,并成为最终的change方法,这之后,无论是在构造器中change还是在对象创建之后,i的值都应该是8。



运行上面的代码,得到结果两个“8”

-------------------------------------

结论:一个类的对象会优先使用 定义在这个对象的构造类(哪个类创建了这个对象,哪个类就是其构造类)中的方法













作者: 王石    时间: 2014-7-26 22:08
你在那看的代码呀,看不懂

作者: lpc4276139    时间: 2014-7-29 17:15
王石 发表于 2014-7-26 22:08
你在那看的代码呀,看不懂

毕老师,视频中的一段代码
作者: 王松朝    时间: 2014-7-31 18:57
LS 的回答真的……好长
我来说个简单的吧

首先LZ的代码有个误区:
   // 伪代码
   //new Thread(Runnable){
   //        void run(){} // 1
  // }.start();

Thread 是子线程,调用start()方法是开始执行子线程
Thread执行,会开启一个子线程然后调用内部变量的run方法,而这个变量恰恰是初始化时传入的Runnable,
所以这样看来就很明显了 : 上面标注的 1 处,是不会被执行到的
或者看文字是有点模模糊糊的,那来看code吧:
Thread的源码是类似这样的
    Thread{
           ......
        private Runnable runnable;
        public Thread(Runnable rr){
               this.runnable = rr;
       }
           ....
       public void start(){
             //  申请子线程资源
             // 子线程初始化操作
             // 转到子线程执行
                runnable.run();
             //   释放资源。。。。
      }
       ......
}
我想这样应该足够清晰了吧。。
------------------------------------------------------

解决了这个问题,再说说说继承关系中的子类与父类方法调用顺序
       假设有几个类
      class A99{    void   a(){}}
      class A88 extends A99
      class A7 extends A88

子类继承父类方法
         new A7().a();
执行顺序是怎样的呢?
         A7.a();
         但是A7 并没有实现自己的a方法, 只能按照父类一层一层去找,最有执行的结果是A99 的方法

如果中间我们截断一下,会怎样呢?
比如:在A88 里写一个方法a,
        再次执行     new A7().a();      
        A7 还是没有实现自己的a方法, 只能按照父类一层一层去找,最有执行的结果是A88 的方法

如果想要A88和A99的方法都执行,应该怎么写呢
应该这样写;
        class   A99{
            public void a(){
            }
        }

        class A88 {
             public void a(){
                     super.a();   // 这里
            }
        }
作者: 李利威    时间: 2014-8-6 21:34
代码能完整点吗??
作者: wangbiao    时间: 2014-12-29 09:29
好流弊的样子~
作者: 小徐_y8nUx    时间: 2014-12-29 13:31
学习了……
作者: yy306525121    时间: 2015-1-12 22:19
帮顶,,,,,,
作者: 一米一光年    时间: 2015-3-17 23:59
这个问题比较复杂楼上哪位大神说的对!!
作者: okandy520    时间: 2015-3-19 12:04
17楼的哥们说的非常好,其实你查看源代码你会看到Thread默认调用的就是runnable接口的中的run方法,但是你也可以自己重写run方法,覆盖它。
eg:执行你实现的runnable接口中的run方法:
new Thread(new Runnable() {

                        public void run() {
                                System.out.println("runnable run");
                        }

                }) /*{
                        public void run() {
                                System.out.println("subThread run");
                        }

                }*/.start();

将输出:runnable run

若覆盖run方法:
        new Thread(new Runnable() {

                        public void run() {
                                System.out.println("runnable run");
                        }

                }) {

                        public void run() {
                                System.out.println("subThread run");
                        }

                }.start();
将输出:subThread run
楼主可以看看源码在自己测试一下。

一切为了技术分。我要进黑马!!:'(




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2