Learn Java for Android Development: More On Inner Classes 8/13

This quick lesson discusses a number of tips for working with inner classes in Java. This lesson is part of an ongoing series of tutorials for developers learning Java in order to develop Android applications.

What are (Anonymous) Inner Classes?

In Java, classes can be nested within one another in order to organize data and functionality in an orderly fashion. Anonymous inner classes are basically developer shorthand, allowing the developer to create, define, and use a custom object all in one go. Anonymous inner classes are frequently used in Android to define handlers for controls, define and launch new threads, and create and return custom objects in methods without cluttering code with unnecessary setup.
For a thorough discussion of nested and inner classes, including anonymous inner classes, see our tutorial called Learn Java for Android Development: Inner Classes

Accessing Outside Variables with the Final Keyword
Sometimes you want to access information available outside of the inner class. Consider the following example. You’ve got a screen with two controls: a Button and a TextView. Each time the user clicks on the Button control, the TextView control is updated with the current time. Within the Activity class associated with this layout, youcould implement this functionality as follows:
Button myButton = (Button) findViewById(R.id.ButtonToClick);
myButton.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        SimpleDateFormat formatter = new SimpleDateFormat("h:mm:ss a");
        String strWhen = formatter.format(new Date());               
        TextView myTextview = (TextView) 
            findViewById(R.id.TextViewToShow);
        myTextview.setText("Clicked at " +  strWhen);
    }
});
The above code will run and behave as expected. However, let’s say you really wanted to declare the TextView control outside the inner class and use it for other purposes as well. Perhaps you’d try to do this instead:
TextView myTextview = (TextView) findViewById(R.id.TextViewToShow);
Button myButton = (Button) findViewById(R.id.ButtonToClick);
myButton.setOnClickListener(new View.OnClickListener() { 
    public void onClick(View v) { 
        SimpleDateFormat formatter = new SimpleDateFormat("h:mm:ss a");
        String strWhen = formatter.format(new Date());
        myTextview.setText("Clicked at " +  strWhen);
    }
});
Unfortunately, this won’t compile. Instead, you’ll get the compile error “Cannot refer to a non-final variable myTextview inside an inner class defined in a different method”. As the error suggests, what you need to do is make the TextView variable final, so that it is accessible within the inner class’s onClick() method:
final TextView myTextview = (TextView) findViewById(R.id.TextViewToShow);
Button myButton = (Button) findViewById(R.id.ButtonToClick);
myButton.setOnClickListener(new View.OnClickListener() {
    public void onClick(View v) {
        SimpleDateFormat formatter = new SimpleDateFormat("h:mm:ss a");
        String strWhen = formatter.format(new Date());
        myTextview.setText("Clicked at " +  strWhen);
    }
});
This code will indeed compile and run as expected.

Working with Final Variables in Java

By making the TextView variable final in the previous example, you’ve made it available to the anonymous inner class (or any inner class defined within its scope, for that matter). Here are some other things to know about final variables:
  • You cannot change the value (r-value) of a final variable. For example, if you tried to assign the myTextview variable to another control within the onClick() method of the inner class, you would get the compile error “The final local variable myTextview cannot be assigned, since it is defined in an enclosing type.”
  • You can call a final variable’s methods, provided you have access. This is what allows us to make the call to the TextView’s setText() method. This does not change the value of the variable myTextview, it simply changes what is displayed to the screen—a subtle but important difference. (The reason for this is simply that the reference to the object can't change, but the object itself can change through its methods.)
  • A final variable’s value need not be known at compile time, whereas a static variable is known at compile time.
  • The final keyword is often paired with the static variable (a field or variable tied to all instances of a class, instead of a single instance) to create a constant for use within your application, as in: public static final String DEBUG_TAG, used for LogCat logging purposes throughout your Activity.

Inner Classes and the "This" variable

In Java, you can use the special this reference to refer to the specific instance of an object. So what happens when you have a class with an inner class, or, for that matter, an anonymous inner class? Well, the inner class can access the special this instance of the enclosing class, and it has a this reference of its own as well. To access the this instance for the inner class, simply use the this syntax. To access the this instance of the enclosing class, you need to tack on the name of the enclosing class, then a dot, then this. For example:
MyEnclosingClass.this
Take a look at the full implementation of the anonymous inner class, as discussed above, in the full context of its enclosing class, here called ClassChaosActivity:
package com.androidbook.classchaos;

import java.text.SimpleDateFormat;
import java.util.Date;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class ClassChaosActivity extends Activity {
	
	public static final String DEBUG_TAG = "MyLoggingTag";
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		final TextView myTextview = (TextView) findViewById(R.id.TextViewToShow);
		Button myButton = (Button) findViewById(R.id.ButtonToClick);
		myButton.setOnClickListener(new View.OnClickListener() {
			public void onClick(View v) {

				SimpleDateFormat formatter = new SimpleDateFormat("h:mm:ss a");
				String strWhen = formatter.format(new Date());
				myTextview.setText("Clicked at " + strWhen);

				Log.v(DEBUG_TAG, "this Class name: " + this.getClass().getName());
				Log.v(DEBUG_TAG, "this extends interface named: " + this.getClass().getInterfaces()[0].getName());
				Log.v(DEBUG_TAG, "this Enclosing class name: " +this.getClass().getEnclosingClass().getName());
				Log.v(DEBUG_TAG, "this Is anonymous class? " + this.getClass().isAnonymousClass());

				Log.v(DEBUG_TAG, "ClassChaosActivity.this Class name: " + ClassChaosActivity.this.getClass().getName());
				Log.v(DEBUG_TAG, "ClassChaosActivity.this Super Class name: " + ClassChaosActivity.this.getClass().getSuperclass().getName());
				Log.v(DEBUG_TAG, "ClassChaosActivity.this Is anonymous class? " + ClassChaosActivity.this.getClass().isAnonymousClass());
			}
		});

	}
}
The log output for the button click proceeds as follows:
10-24 18:18:53.075: VERBOSE/MyLoggingTag(751): this Class name: com.androidbook.classchaos.ClassChaosActivity$1
10-24 18:18:53.085: VERBOSE/MyLoggingTag(751): this extends interface named: android.view.View$OnClickListener
10-24 18:18:53.085: VERBOSE/MyLoggingTag(751): this Enclosing class name:  com.androidbook.classchaos.ClassChaosActivity
10-24 18:18:53.095: VERBOSE/MyLoggingTag(751): this Is anonymous class? true
10-24 18:18:53.095: VERBOSE/MyLoggingTag(751): ClassChaosActivity.this Class name: com.androidbook.classchaos.ClassChaosActivity
10-24 18:18:53.105: VERBOSE/MyLoggingTag(751): ClassChaosActivity.this Super Class name: android.app.Activity
10-24 18:18:53.105: VERBOSE/MyLoggingTag(751): ClassChaosActivity.this Is anonymous class? false
As you can see, the this keyword on its own refers to the “closest” class—the inner class. Although the inner class is anonymous, Java does give it a number ClassChaosActivity$1 to keep track of it. While not useful to developers per se, this shows that the anonymous inner class is treated like any other class internally. Meanwhile, we access the enclosing class instance using the ClassChaosActivity.this syntax.

Conclusion

In this quick lesson you have learned several tips to help you use inner classes and anonymous inner classes more skillfully. You learned that inner classes can access variables declared outside their scope, provided the variables are marked as final and therefore unchangeable. You also learned about the special syntax related to the this keyword when it comes to accessing inner class instance data as well as its enclosing class instance data.

No comments:

Post a Comment

UA-50246500-1