2014-11-08 59 views
1

在Android项目中,我使用下面的代码。我收到错误:variable tts might not have been initialized。如果我改变变量tts被声明的地方,我不会再出错。如果我注释掉在内部OnInitListener类中引用tts的两行,我也不会再发生错误(但也没有任何有趣的事情发生)。因此,我推论,如果内部类在封闭方法中声明(即使它被声明为final),也不能“看到”tts变量,但是当它被声明为实例变量时它可以看到它封闭类。了解Java内部类中的范围

我来自JavaScript背景;显然Java在这种情况下以不同的方式处理变量作用域。如果你能解释Java在底层做什么,我将不胜感激,以便我能理解这些差异。

package com.example.texttospeech; 

import android.speech.tts.TextToSpeech; 
import android.support.v7.app.ActionBarActivity; 
import android.os.Bundle; 
import java.util.HashMap; 
import java.util.Locale; 


public class MainActivity extends ActionBarActivity { 

    //private TextToSpeech tts; // UNCOMMENT THIS... 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_main); 
     testTextToSpeech(); 
    } 

    private void testTextToSpeech() { 
     final String toSpeak = getString(R.string.hello_world); 
     final int mode = TextToSpeech.QUEUE_FLUSH; 
     final HashMap hashMap = new HashMap<String, String>(); 

     final TextToSpeech tts; // ... AND COMMENT THIS OUT... 

     tts = new TextToSpeech(getApplicationContext(), 
       new TextToSpeech.OnInitListener() { 
        @Override 
        public void onInit(int status) { 
         if (status != TextToSpeech.ERROR) { 
          // ... OR SIMPLY COMMENT OUT THE NEXT TWO LINES 
          tts.setLanguage(Locale.UK); 
          tts.speak(toSpeak, mode, hashMap); 
         } 
        } 
       }); 
    } 
} 

回答

5

- #1的方法,最终所有的变量都隐含复制为您onInitClass的领域。它也对你的外部类有一个隐含的字段。所有这些都可能是意外内存泄漏的来源,而泄漏意味着无意中创建了一个阻止垃圾回收器完成其工作的引用,因此在声明最终内容时需要注意。

- #2它说tts可能没有被初始化的原因是因为您在分配它之前使用它。 Final使得引用不可变,因此只有在声明它时才能为最终赋值。如果你不分配一个值,那意味着占用内存空间的东西很可能是上次使用内存空间存储值时的随机垃圾。你可以明确地设置tts = null,但是设置为null的最终变量是完全无用的(它不能被改变),所以你需要在你声明的同一行中设置它的实际值。

E.g. final TextToSpeech tts = new TextToSpeech(getApplicationContext(),listener);

- #3您的内部类可以引用外部类中的变量。你所要做的就是写OuterClass.this.myfield;

所以这可能是重写代码的可行方法。

TextToSpeech tts; //class field 

private void testTextToSpeech() { 
     final String toSpeak = getString(R.string.hello_world); 
     final int mode = TextToSpeech.QUEUE_FLUSH; 
     final HashMap<String, String> hashMap = new HashMap<String, String>(); 

     tts = new TextToSpeech(getApplicationContext(), new TextToSpeech.OnInitListener() { 

       @Override 
       public void onInit(int status) { 
        if (status != TextToSpeech.ERROR) { 

         tts.setLanguage(Locale.UK); 
         tts.speak(toSpeak, mode, hashMap); 
        } 
       } 
      }); 
} 
+0

我在哪里可以了解隐式字段的创建时间和时间? #2似乎创造了一种鸡与鸡蛋的情况,其中''侦听器'不能被创建,直到'tts'被初始化。至于#3,我可以看到内部类在外部类中声明为'private TextToSpeech tts;'时可以从外部类访问变量。 – 2014-11-08 21:09:09

+0

老实说,我在这里学到了关于stackoverflow的阅读问题和答案:)没有什么可以学习的,它只是内部类对outter类的引用,以及它声明的方法中的所有最终字段。随着内部阶层的生活,这些其他对象不能由GC收集。 – NameSpace 2014-11-08 21:28:20

+0

我是否正确地认为您的可行方法与我在示例代码中提到的“取消注释/注释掉”更改相同?在我看来,这个问题来自这样一个事实,即如果在'testTextToSpeech()'中声明'java'编译器坚持'tts'被设置为'final'。即使我使用'final TextToSpeech tts = new TextToSpeech(...);'编译器仍然不满意。在调用'onInit()'方法时,'tts'将指向一个已初始化的对象,但编译器无法看到它。它看到的只是'最后'。这是一个准确的理解? – 2014-11-08 21:37:09

1

内部,TextToSpeech.OnInitListener实例被传递的参考tts到它的隐式构造,但是 - 在这一点上 - tts尚未初始化,但只能宣布(你不能传递一个未初始化的变量(本地生活在堆栈上的变量将永远是)任何方法,因为它没有值)。将类型TextToSpeech的新创建对象分配给变量tts,该变量取决于TextToSpeech.OnInitListener(构造函数)的实例,该实例取决于tts - >循环依赖性。

通过声明tts作为成员变量,它将自动使用null进行初始化。并且因为tts是成员变量,所以TextToSpeech.OnInitListener实例将始终能够访问当前值tts,因为它会传递对MainActivity外部实例的隐式引用。

1

我认为用稍微不同的方式编写代码就足以了解这里发生了什么。

final TextToSpeech tts; 

TextToSpeech.OnInitListener listener = new TextToSpeech.OnInitListener() { 
    @Override 
    public void onInit(int status) { 
    tts.setLanguage(Locale.UK); 
    tts.speak(toSpeak, mode, hashMap); 
    } 
} 

tts = new TextToSpeech(getApplicationContext(), listener); 

基本上,在当下的listener被实例化tts声明,但它仍然不存在。这是警告的内容。编译器不会向前看,并且如果您将tts作为类变量,那么对于它所知的全部内容,程序可能会在实例化tts之前尝试使用listener。通过将tts声明放在相同的本地范围内,可以避免该风险。


所以,我会建议做的是让testTextToSpeech回到tts参考:

private TextToSpeech tts; 

@Override 
protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_main); 
    tts = testTextToSpeech(); 
} 

private TextToSpeech testTextToSpeech() { 
    final String toSpeak = getString(R.string.hello_world); 
    final int mode = TextToSpeech.QUEUE_FLUSH; 
    final HashMap hashMap = new HashMap<String, String>(); 

    final TextToSpeech tts = new TextToSpeech(getApplicationContext(), 
      new TextToSpeech.OnInitListener() { 
       @Override 
       public void onInit(int status) { 
        if (status != TextToSpeech.ERROR) { 
         tts.setLanguage(Locale.UK); 
         tts.speak(toSpeak, mode, hashMap); 
        } 
       } 
      }); 
    } 
    return tts; 
} 
+0

您的解释很明确,但您对代码的建议修改不能解决问题。创建“侦听器”时,'tts'仍未初始化。 – 2014-11-08 21:03:04

+0

这不是一个建议的修改。这只是我试图展示正在发生的事情。至于建议的修改... [编辑答案]可能是这样的。这不是世界上最美丽的代码,但它应该工作。 – makingthematrix 2014-11-08 21:07:42

+0

我明白这是如何工作的。但是,如果我将'private TextToSpeech tts;'声明为一个实例变量,那么我不需要在'testTextToSpeech()'方法内部创建一个单独的私有变量。而'testTextToSpeech()'需要声明它返回一个TextToSpeech对象,否则脚本将不能编译。 – 2014-11-08 21:15:57