有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

安卓理解Java内部类中的作用域

在一个Android项目中,我使用下面的代码。我得到错误:variable tts might not have been initialized。如果我更改了变量tts的声明位置,我就不会再得到错误。如果我注释掉内部OnInitListener类中引用tts的两行,我也不会再得到错误(但也不会发生任何有趣的事情)

因此,我推断,如果tts变量是在封闭方法中声明的(即使它被声明为final),则内部类无法“看到”它,但当它被声明为封闭类的实例变量时,它可以看到它

我来自JavaScript背景;显然,Java在此上下文中以不同的方式处理变量范围。如果您能解释一下Java在引擎盖下做了什么,我将不胜感激,以便我能够理解这些差异

package com.example.texttospeech;

import 安卓.speech.tts.TextToSpeech;
import 安卓.support.v7.app.ActionBarActivity;
import 安卓.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);
                        }
                    }
                });
    }
}

共 (3) 个答案

  1. # 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;
    }
    
  2. # 2 楼答案

    在内部,TextToSpeech.OnInitListener实例将对tts的引用传递给它的隐式构造函数,但此时,tts还没有初始化,只是声明了(并且不能将未初始化的变量(位于堆栈上的局部变量将始终是)传递给任何方法,因为它没有值)。将新创建的类型为TextToSpeech的对象分配给变量tts,该变量依赖于TextToSpeech.OnInitListener(构造函数)的实例,该实例依赖于tts->;循环依赖

    通过将tts声明为成员变量,它将自动用null初始化。由于tts是一个成员变量,TextToSpeech.OnInitListener实例在传递对MainActivity外部实例的隐式引用时,始终能够访问tts的当前值

  3. # 3 楼答案

    -#1方法中的所有最终变量都隐式复制为onInitClass的字段。它对外部类也有一个隐式字段。所有这些都可能是意外内存泄漏的一个来源,所谓泄漏,我们指的是意外创建一个阻止垃圾收集器执行其工作的引用,因此在声明final时需要小心

    -#2它之所以说tts可能尚未初始化,是因为您在给它赋值之前使用了它。Final使引用不可变,因此只能在声明Final时为其赋值。如果不赋值,这意味着占用内存空间的很可能是上次使用内存空间存储值时的随机垃圾。您可以显式地设置tts=null,但将最终变量设置为null是完全无用的(它不能更改),因此您需要在声明它的同一行中设置它的实际值

    例如,final TextToSpeech tts=new TextToSpeech(getApplicationContext(),listener)

    -#3您的内部类可以引用外部类中的变量。你所要做的就是在外面写。这麦菲尔德

    因此,这可能是一种重新编写代码的可行方法

    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);
                        }
                    }
                }); 
    }