hi 大家好。先预告一下,下文中仅涉及java语法的讨论,和Android源码关系不大,请不要有阅读压力。我发现在Android的源码中很多地方对final关键字的用法很是“别出心裁”,之所以这么说是因为我从没看过是这么使用final关键字的,一个典型的例子是View类中onScrollChanged方法(不妨将其成为方案一):    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        mBackgroundSizeChanged = true;        final AttachInfo ai = mAttachInfo;
        if (ai != null) {
            ai.mViewScrollChanged = true;
        }
    }看到了吗?此处mAttachInfo是View类的一个成员变量,而在这个方法中Android的程序员并没有直接操作mAttachInfo变量,而是先赋值给一个标明为final的局部变量ai,然后再操作这个ai。这个写法我很是想不通,这不是多此一举吗?但是仔细想想又觉得没这么简单,身经百战的Android开发小组这么写应该不会是空穴来风,难道这种写法真的有其他的目的?想了很久也猜了很久,有个念头突然蹦了出来,难道这种写法是因为多线程编程的需要?考虑下面这种写法(不妨将其成为方案二):    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
        mBackgroundSizeChanged = true;        if (mAttachInfo != null) { // #1
            mAttachInfo.mViewScrollChanged = true; // #2
        }
    }在上面这种写法中,取消了final的局部变量ai而直接操作mAttachInfo。考虑这样一种场景,假设线程A执行完#1将要执行#2时,突然有另外一个线程B在其他地方对mAttachInfo做了修改,将其指向了另外一个对象,那么线程A执行到#2时,操作的将是这个新的对象而不是原对象,而在方案一中,则可以避免这种现象。以上只是一种猜测,我认为方案一中的写法是一种简单有效的同步方式,大家认为呢?

解决方案 »

  1.   

    貌似这根本解决不了多线程问题,final的意思不是不可重新赋值的意思吗?实际上指向的还是那个对象,在C++中这种代码一定会被编译器优化掉的。这里这种用法,很可能是修改代码的需要,这个例子下面只要一行,如果有很多行使用mAttachInfo变量,那么一旦需要指向另一个变量就需要逐行更改,而这里只需要改下ai指向就行了。final 会被认为是常量,实际上编译器会优化掉它,和直接使用mAttachInfo是一样的,这才是final的真正意义。
      

  2.   

    很感谢这位同学的回复!说说我的看法。首先,在java中我不认为编译器会对定义为 final 的对象引用进行优化,因为这是引用不是基本数据类型,要在运行时才能确定其指向的对象,编译器只能保证在运行时一旦这个引用被确定下来,它就不能更改。其次你提到到方案一中可能是修改代码的需要,我觉得不太可能。mAttachInfo 本身就是引用类型,“一旦需要指向另一个变量”这种说法我难以理解,你的这个想法应该来源于对常量的使用上吧?常量的使用确实满足你说的这个需求。最后,其实我想说明的是,我主贴中提到的方案一有可能是多线程编程的需要,并不是指的能解决所有同步的问题,但是能解决某些多线程编程中的特殊问题,比如有这样一个释放连接的方法:    protected void closeConnection() {
            final Connection currentConnection = mConnection; // #1
            if (currentConnection != null) {
                currentConnection.close; // 省略 try-catch 代码块
            }
        }当执行完#1时,无论 mConnection 是否已经指向一个新的连接(比如其他线程在其他地方又重新创建了一个),我都能保证在执行#1时 mConnection 指向的连接能够正确关闭(此处的赋值操作是一个原子操作)。
      

  3.   

    神马多线程啊 这又不是web,怎么会有2人同时操作呢
      

  4.   

    旧事重提,我今天突然想到个问题要推翻我之前的推测,Android中控件类的操作不会出现多线程争用的场景,因为所有对UI的操作都必须要在mian线程中操作。楼上碰巧说对了,但跟web没有关系,不是web的程序也存在多线程的应用场景。其实这个问题可以分成两个子问题:
    1、为什么在方法里要先将全局变量赋给一个局部变量?
    2、为什么要给这个局部变量添加final关键字?我有个同事说,使用final关键字可能可以提高效率,如果被他言中,首先解释了为什么第2个问题为什么使用final关键字,其实解释了第1个问题为什么要将全局变量赋给局部变量(因为只有先赋给局部变量,这个局部变量才有机会声明成final)。可是,将一个局部变量声明成final真的可以提高性能么?
      

  5.   

    这跟性能没关系,我来告诉大家吧!
    第一步:将全局变量赋给一个局部变量:
            只是为了取得 在程序运行过程中,该全局变量的当前时刻的值!
    但这样是不够的!因为设置 变量local=global,在JAVA里只是将局部的一个引用指向这个全局变量,当全局变量发生改变时,引用它的这个局部变量值会随之改变,倘若局部变量在这个局部代码块里正在运行,又中途被改变了,这会发生多么严重的错误!第二部:在局部变量前添加final:
            这样的话,在该局部代码块中,就不能以任何方式去更改这个引用变量的值了,这样,下面代码的运行安全就有了保障!但在其他代码块里,仍然可以有其他语句去更改那个全局变量的值!总结:
         这段小插曲的作用,其实就是对全局变量取一次样,就像在特定时刻取一次样品,来做实验一样,来做一些事情。取得的样品当然不希望中途变质,要让它final,才能正常使用。而全局变量就任由他继续变化去吧
      

  6.   

    楼上解释非常到位,不用final 要报错!
      

  7.   

    报错倒是不会,编译能顺利通过,但可能发生运行时错误。
    另外,如果在这段代码里创建内部类,并在内部类里使用这个局部变量的话,确实会报错。
    Cannot refer to a non-final variable inside an inner class defined in a different method
      

  8.   

    说下我的看法:首先,我认为9#说的不是完全错误。编译器对final关键字进行优化,这是没有问题的,但关键不是优化没了,而是直接通过寄存器,而非内存获取对象引用。从这点上说,效率有所提高。
    另外,楼主发现的解决线程同步的问题,也是正解。
      

  9.   

    哪有那么玄乎?final AttachInfo ai 这个只是声明了一个指针。实际读数据的操作还是从原来对象保存数据的内存里读出来的。 所以根本没有什么同步的功能。而且一般来说,读数据根本也就不需要同步。2个线程同时对一块内存进行写操作,才需要同步。避免因为次序问题而造成先写的数据把后写的数据覆盖了。
      

  10.   

       final 是锁定内存地址,   这样就是保证数据同一个内存中读取
       
       比如  final 集合 a=new 集合();    
             集合  增删该插  都能操作
       但是  就是不能改变内存地址     也就是不能    a=new 集合();  了  
      

  11.   

    well well study,day day up 
      

  12.   

    手贱多写了个final而已
    就楼主的这个代码段来看,写不写final没有任何本质的改变,不会因为写了final而影响任何玩意方法参数,及局部变量使用 final 修饰,最重要的作用就是可以给下面定义的内部类使用
    个别时候是为了强制约束变量不可更改
      

  13.   


    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    final int aaa = position;
    System.out.println("---------" + aaa);
    ViewHolder holder = null;

    if(convertView == null){
    holder = new ViewHolder();

    convertView = mInflater.inflate(R.layout.items, null);
    holder.tv1 = (TextView)convertView.findViewById(R.id.textView1);
    holder.tv2 = (TextView)convertView.findViewById(R.id.textView2);
    holder.btn1 = (Button)convertView.findViewById(R.id.button1);
    holder.btn1 = (Button)convertView.findViewById(R.id.button1);
    convertView.setTag(holder);
    }else {
    holder = (ViewHolder)convertView.getTag();
    }

    holder.tv1.setText((String)list.get(position).getName());
    holder.tv2.setText(list.get(position).getNum()+"");
    holder.btn1.setText(list.get(position).getNum()+"a");
    holder.btn1.setOnClickListener(
    new OnClickListener() {
    @Override
    public void onClick(View v) {
    //按钮点击事件
    System.out.println("this is aaa:"+aaa);
    }
    });看看这个例子中如果不用final的话就会报错
      

  14.   

    用 final 可以保证至少在这个方法里面,每个时刻这个变量的状态都是一致的,很多情况下是”我不需要同步,但一定要一致“。就好像 CopyOnWrite 的集合一样。
      

  15.   

    同意20楼的说法。
    因为如果有别的线程把mAttachInfo改变了。那后面的ai改变就变的毫无意义。
    那这样说来final就是锁定内存地址,在这段时间其他线程读写内存的访问都要排队等候。
    这样其实也是一个同步锁的作用。
    效果上来看与synchronized(mAttachInfo)是一样的。但是由于synchronized还是有点性能消耗,所以这里加上final真的是很nice。
      

  16.   

    晕,我一年以后才报道:a》android中的代码有把对象的成员变量保存到局部变量的习惯。因为调用局部变量(stack)和远程调用成员变量的性能有较大差异。
    b》关于final局部变量是习惯吧。。比如经常如果在一个函数内部的局部内部类的参数有局部变量的话,该参数经常会加final,因为局部类对象的生命周期和局部变量不一致,会出现函数退出栈后,内部变量死了,局部内部类对象健在,指向一个不存在的“尸体”。
    同时呢,也可能出于逻辑意义上提醒coder不改变值的意思。因为把外地对象的变量传递给局部变量以后,局部变量的改变不会回馈给外部对象,所以对其值的修改没有意义。
    以上,个人理解~
      

  17.   

    这个代码就真的很扯淡了,直接引用position就行了,这里报错是语法错误,都不让你运行的