Home | FAQ | Contact me

Call-backs (function- or method pointers) in Java

You're an old C programmer and you're thinking, "Gee, if only I could figure out how to implement the Java equivalent of the C function pointer."

You may be in luck.

And, it's not really that egregiously sinful since it's more or less an implementation of the Observer Pattern. There are four examples here with one concrete illustration (using real, Android code). Let the comments and code speak for this topic.

Table of contents

First example: String validation
Second example: A more generic treatment
Android illustration
Fourth example: Generation of multiple XML paragraphs

First example: String validation

StringValidation.java:
package com.etretatlogiciels.example.callbacks;

/**
 * This is the call-back interface. Use it to define one or more methods that will be
 * the actual call-backs. If you're coming from C, think of these as the prototypes for
 * the function pointers.
 */
public interface StringValidation
{
  /**
   * This examines a String to see if it's not too long.
   *
   * @param string - to be examined.
   * @return true/false that the string is good.
   */
  boolean isStringShortEnough( String string, int maximumLength );

  /**
   * This examines a String to see if it's not too short.
   *
   * @param string - to be examined.
   * @return true/false that the string is good.
   */
  boolean isStringLongEnough( String string, int minimumLength );
}
StringValidationImplementation.java:
package com.etretatlogiciels.example.callbacks;

import org.apache.log4j.Logger;

/**
 * This implements verification of string data. Of course, this is totally bogus since
 * it's only to take up space for showing off the implementation of a call-back system
 * in Java. Just for grins, we throw some logging into the mix.
 *
 * If you're coming from C, think of these as the function definitions.
 */
public class StringValidationImplementation implements StringValidation
{
  private static Logger log = Logger.getLogger( StringValidationImplementation.class );

  @Override
  public boolean isStringShortEnough( String string, int maximumLength )
  {
    if( log.isTraceEnabled() )
      log.trace( "isStringShortEnough( \""
               + string
               + "\", "
               + maximumLength
               + " ) [actual length: "
               + string.length()
               + "]" );
    return( string.length() <= maximumLength );
  }

  @Override
  public boolean isStringLongEnough( String string, int minimumLength )
  {
    if( log.isTraceEnabled() )
      log.trace( "isStringLongEnough( \""
               + string
               + "\", "
               + minimumLength
               + ") [actual length: "
               + string.length()
               + "]" );
    return( string.length() >= minimumLength );
  }
}
DoCallBack.java:
package com.etretatlogiciels.example.callbacks;

import com.etretatlogiciels.example.callbacks.StringValidation;

/**
 * The purpose of this class is only to show off call-backs. This is sort of how
 * to do C function pointers in Java. We have two of them here:
 *
 *   - isStringShortEnough( String string )
 *   - isStringLongEnough( String string )
 */
public class DoCallBack
{
  private StringValidation  callBack = null;

  /**
   * Use this constructor if you want to be able to verify strings.
   *
   * This is a tip of the hat to the thought that maybe you don't always want to
   * validate the strings in an implementation. Maybe, for example, you'd only want
   * to do it as part of JUnit tests, in which case you'd use this constructor, but
   * for production code you'd just use the no-argument constructor.
   *
   * @param doValidation - true/false that validation is to be done.
   */
  public DoCallBack( boolean doValidation )
  {
    if( doValidation )
      callBack = new StringValidationImplementation();
  }

  /**
   * This is the no-argument constructor for use, let's say, in production
   * code where we don't wish to validate strings.
   */
  public DoCallBack() { /* no-argument constructor */ }

  /**
   * This is the method that makes use of the call-back to validate a string it's been
   * given. The action is bogus (enforcing that the string's length fall between 3 and
   * 9 characters in length) obviously, but it demonstrates how to consume the call-back
   * you've set up.
   *
   * This is, I think, the closest we come to consuming function pointers in Java. It
   * more or less follows the Observer (Design) Pattern.
   */
  public void validateString( String string )
  {
    System.out.println( "String is: " + string );

    if( this.callBack != null )
    {
      if( callBack.isStringShortEnough( string, 9 ) )
        System.out.println( "  The string is at most 9 characters in length, so it's good." );
      else
        System.out.println( "  The string is longer than 9 characters in length, so it's bad." );

      if( this.callBack != null && callBack.isStringLongEnough( string, 3 ) )
        System.out.println( "  The string is at least 3 characters in length, so it's good." );
      else
        System.out.println( "  The string is shorter than 3 characters in length, so it's bad." );
    }
  }

  /**
   * This shows how it's used, both with the no-argument constructor and the one that
   * causes strings to be validated.
   *
   * @param args (unused)
   */
  public static void main( String[] args )
  {
    DoCallBack  x = new DoCallBack();

    System.out.println( "You won't see validation here because the call-back mechanism isn't in use" );
    x.validateString( "This is a test." );
    x.validateString( "cat" );
    x.validateString( "A" );
    x.validateString( "This is a test of the emergency broadcast system." );
    x.validateString( "Now is the time for all young men to come to the aid of their country." );

    System.out.println( "\nYou will see validation here because the call-back mechanism is defined" );
    x = new DoCallBack( true );
    x.validateString( "This is a test." );
    x.validateString( "cat" );
    x.validateString( "A" );
    x.validateString( "This is a test of the emergency broadcast system." );
    x.validateString( "Now is the time for all young men to come to the aid of their country." );
  }
}

Here's what you see when the test is run.

You won't see validation here because the call-back mechanism isn't in use String is: This is a test. String is: cat String is: A String is: This is a test of the emergency broadcast system. String is: Now is the time for all young men to come to the aid of their country. You will see validation here because the call-back mechanism is defined String is: This is a test. The string is longer than 9 characters in length, so it's bad. The string is at least 3 characters in length, so it's good. String is: cat The string is at most 9 characters in length, so it's good. The string is at least 3 characters in length, so it's good. String is: A The string is at most 9 characters in length, so it's good. The string is shorter than 3 characters in length, so it's bad. String is: This is a test of the emergency broadcast system. The string is longer than 9 characters in length, so it's bad. The string is at least 3 characters in length, so it's good. String is: Now is the time for all young men to come to the aid of their country. The string is longer than 9 characters in length, so it's bad. The string is at least 3 characters in length, so it's good.

With log4j tracing turned on, you see this:

You won't see validation here because the call-back mechanism isn't in use String is: This is a test. String is: cat String is: A String is: This is a test of the emergency broadcast system. String is: Now is the time for all young men to come to the aid of their country. You will see validation here because the call-back mechanism is defined String is: This is a test. TRACE - isStringShortEnough( "This is a test.", 9 ) [actual length: 15] The string is longer than 9 characters in length, so it's bad. TRACE - isStringLongEnough( "This is a test.", 3) [actual length: 15] The string is at least 3 characters in length, so it's good. String is: cat TRACE - isStringShortEnough( "cat", 9 ) [actual length: 3] The string is at most 9 characters in length, so it's good. TRACE - isStringLongEnough( "cat", 3) [actual length: 3] The string is at least 3 characters in length, so it's good. String is: A TRACE - isStringShortEnough( "A", 9 ) [actual length: 1] The string is at most 9 characters in length, so it's good. TRACE - isStringLongEnough( "A", 3) [actual length: 1] The string is shorter than 3 characters in length, so it's bad. String is: This is a test of the emergency broadcast system. TRACE - isStringShortEnough( "This is a test of the emergency broadcast system.", 9 ) [actual length: 49] The string is longer than 9 characters in length, so it's bad. TRACE - isStringLongEnough( "This is a test of the emergency broadcast system.", 3) [actual length: 49] The string is at least 3 characters in length, so it's good. String is: Now is the time for all young men to come to the aid of their country. TRACE - isStringShortEnough( "Now is the time for all young men to come to the aid of their country.", 9 ) [actual length: 70] The string is longer than 9 characters in length, so it's bad. TRACE - isStringLongEnough( "Now is the time for all young men to come to the aid of their country.", 3) [actual length: 70] The string is at least 3 characters in length, so it's good.


Second example: A more generic treatment

Disappointed in the first example which I concocted from initial research using what had been written here and there, I came up with something better, I think, because it better illustrates numerous "real" examples used in Android code.

Callback.java:
package com.etretatlogiciels.callback.example;

public interface Callback
{
  void methodToCall();
}
CallbackImpl.java:
package com.etretatlogiciels.callback.example;

public class CallbackImpl implements Callback
{
  @Override
  public void methodToCall()
  {
    System.out.println( "I've been called back!" );
  }
}
Caller.java:
package com.etretatlogiciels.callback.example;

/**
 * This is my idea of how a formal call-back mechanism works. There are examples in the
 * real world, very common in Android code, for instance: look for View.OnClickListener().
 */
public class Caller
{
  public Callback callback = null;

  /* register a method to be called back at some future, arbitrary point */
  public void register( Callback callback )
  {
    this.callback = callback;
  }

  /* how Caller invokes the call-back method at the appropriate juncture  */
  public void execute()
  {
    this.callback.methodToCall();
  }

  /* directly execute a method (I don't see this is a "call-back" at all) */
  public void execute( Callback callback )
  {
    callback.methodToCall();
  }

  public static void main( String args[] )
  {
    Caller    caller   = new Caller();
    Callback  callback = new CallbackImpl();

    /* Demonstrate simple calling back.
     */
    caller.register( callback );
    caller.execute();

    /* Demonstrate direct calling back (here, immediate execution) by creating an "on
     * the fly" call-back. In a real world example, caller.execute( Callback callback )
     * would be a mechanism that properly establishes a situation in which, later, the
     * "direct" call would issue. This is very common in Android code as noted above.
     */
    caller.execute( new Callback()
    {
      public void methodToCall()
      {
        System.println( "This is our \"on the fly\" method..." );
      }
    });
  }
}

Here's what you see when the example above is run.

I've been called back! This is our "on the fly" method...


Android illustration

Here is some sample Android code illustrating the call-back; there are two examples, one at line 30 and a clearer one at line 42. The application demonstrates how to juggle locales on Android.

HelloL10N.java:
package com.android.developer;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class HelloL10N extends Activity
{
  /* Called when the activity is first created */
  @Override
  public void onCreate( Bundle savedInstanceState )
  {
    super.onCreate( savedInstanceState );
    setContentView( R.layout.main );

    // assign flag.png to the button, loading correct flag image for current locale
    Button b = ( Button ) findViewById( R.id.flag_button );

    b.setBackgroundDrawable( this.getResources().getDrawable( R.drawable.flag ) );

    // build dialog box to display when user clicks the flag
    AlertDialog.Builder builder = new AlertDialog.Builder( this );

    builder.setMessage( R.string.dialog_text )
      .setCancelable( false )
      .setTitle( R.string.dialog_title )
      .setPositiveButton( "Done", new DialogInterface.OnClickListener()
        {
          public void onClick( DialogInterface dialog, int id )
          {
            dialog.dismiss();
          }
        }
      );

    final AlertDialog alert = builder.create();

    // set click listener on the flag to show the dialog box
    b.setOnClickListener( new View.OnClickListener()
      {
        public void onClick( View v )
        {
          alert.show();
        }
      }
    );
  }
}


Fourth example: Generation of multiple XML paragraphs

Imagine a task that consists of filling in/generating XML into a document that includes multiple paragraphs of relevant content. Only, you know that content will expand over time, but you wish to impact the code that populates the document with such paragraphs over time. I guess this amounts to an elaborate factory-pattern implementation using callbacks.

Let's call these different paragraphs "concepts"; we'll start with JUnit test code that mostly demonstrates how to use this code. Note that outputStream does nothing in our implementation here; it would be used to shoot the content that populates the XML output through in a real situation.

src/test/java/com/windofkeltia/concepts/PopulateCxmlConceptsTest.java:
public class PopulateCxmlConceptsTest
{
  private static String[] CONCEPT_NAMES = { "demographics", "metadata", "extendeddata" };

  @Test
  public void testRegisteringCxmlConcepts()
  {
    OutputStream         outputStream         = new ByteArrayOutputStream();
    PopulateCxmlConcepts populateCxmlConcepts = new PopulateCxmlConcepts();

    for( String name : CONCEPT_NAMES )
    {
      CxmlConcept cxmlConcept;

      switch( name )
      {
        case "demographics" :  cxmlConcept = new Demographics( name, outputStream ); break;
        case "metadata" :      cxmlConcept = new Metadata    ( name, outputStream ); break;
        case "extendeddata" :  cxmlConcept = new ExtendedData( name, outputStream ); break;
        default :                                                                    continue;
      }

      populateCxmlConcepts.registerCxmlConcept( cxmlConcept );
    }

    populateCxmlConcepts.populate();
  }
src/main/java/com/windofkeltia/concepts/CxmlConcept.java:

This is the top-level (interface) whose contract says to implement method populateConcept() in any conforming concept populator.

public interface CxmlConcept
{
  /** Implement the concept by pouring XML into the output stream. */
  void populateConcept();
}
src/main/java/com/windofkeltia/concepts/CxmlConceptImpl.java:
import java.io.OutputStream;

public abstract class CxmlConceptImpl implements CxmlConcept
{
  protected final String       name;
  protected final OutputStream outputStream;

  public CxmlConceptImpl( final String name, OutputStream outputStream )
  {
    this.name         = name;
    this.outputStream = outputStream;
  }
}
src/main/java/com/windofkeltia/concepts/PopulateCxmlConcepts.java:
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

public class PopulateCxmlConcepts
{
  private List< CxmlConcept > cxmlConcepts = new ArrayList<>();

  public PopulateCxmlConcepts() { }

  public void registerCxmlConcept( CxmlConcept cxmlConcept )
  {
    cxmlConcepts.add( cxmlConcept );
  }

  public void populate()
  {
    for( CxmlConcept cxmlConcept : cxmlConcepts )
      cxmlConcept.populateConcept();
  }
}
src/main/java/com/windofkeltia/concepts/Demographics.java:
import java.io.OutputStream;

public class Demographics extends CxmlConceptImpl
{
  public Demographics( String name, OutputStream outputStream )
  {
    super( name, outputStream );
  }

  /** See {@link com.windofkeltia.concepts.CxmlConcept#populateConcept()}. */
  @Override
  public void populateConcept()
  {
    System.out.println( "Populating the " + name + " concept..." );
  }
}

The output from the JUnit test above:

Test: testRegisteringCxmlConcepts ------------------------------------- Populating the demographics concept... Populating the metadata concept... Populating the extendeddata concept...