1

I have a class called Questions which has two subclasses multipleChoice and FillInBlank. These two sub-classes each have unique instance variables.

Now let's say I have an ArrayList <Questions> questions which store both types of questions (multipleChoice and FillInBlank).

My question is: When I iterate through the questions ArrayList, how do I access particular instance variables from the multipleChoice class and the FillInBlank class? I am having trouble because the type of the arraylist is type questions.

forkbun882
  • 115
  • 7
  • 1
    You will need to explain more about what you want to do when iterating. Generally, a consumer of a class does not access instance variables directly. – Gene McCulley Apr 08 '21 at 20:50
  • 2
    Besides the "main" answer in the pointed topic, also check the other one with `instanceof`, that's how you can check which kind of question you are looking at. – tevemadar Apr 08 '21 at 20:54
  • If you have problems like that, I don't believe that you're using the right abstractions. – NomadMaker Apr 09 '21 at 00:27

2 Answers2

5

As mentioned in the comments, instanceof should be used to identify which subclass is the current element, and then this element should be cast to this subclass:

for (Questions question : questions) {
    if (question instanceof MultipleChoice) {
        doSomething((MultipleChoice) question);
    } else if (question instanceof FillInBlank) {
        doSomething((FillInBlank) question);
    } else {
        System.out.println("Unexpected class: " + question.getClass());
    }
}

// ...
private void doSomething(MultipleChoice mc) {
    // do something
    System.out.println(mc.getChoice());
}

private void doSomething(FillInBlank fib) {
    // do something
    System.out.println(fib.getFilledAnswer());
}
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42
3

Automatic casting for instanceof

The Answer by Alex Rudenko is correct.

Java 16 simplifies that code, with the arrival of JEP 394: Pattern Matching for instanceof. The downcast becomes automatic.

Since casting immediately after a call to instanceof is so common:

if ( obj instanceof String ) {
    String s = (String) obj;    // grr...
    ...
}

… pattern matching technology now allows for implicit casting. Notice the introduction of a variable name within the if ( instanceof ) test.

if ( obj instanceof String s ) {  // <-- Implicit downcast to a named variable.
    // Let pattern matching do the work!
    ...
}

(By the way, your Questions class should be named as singular rather than plural given your description: Question.)

Using this feature, the code seen in that other Answer becomes:

for ( Question question : questions ) {
    if ( question instanceof MultipleChoice q ) {
        doSomething( q ) ;
    } else if ( question instanceof FillInBlank q ) {
        doSomething( q ) ;
    } else {
        System.out.println( "Unexpected class: " + question.getClass() ) ;
    }
}
…
private void doSomething( MultipleChoice mc ) {
    // do something
    System.out.println( mc.getChoice() );
}

private void doSomething( FillInBlank fib ) {
    // do something
    System.out.println( fib.getFilledAnswer() );
}

Full example code

Here is a complete example to run, in a single file.

package com.work.demo.casting;

import java.util.List;
import java.util.UUID;

public class App2
{
    public static void main ( String[] args )
    {
        App2 app = new App2();
        app.demo();
    }

    private void demo ( )
    {
        List < Question > questions =
                List.of(
                        new MultipleChoice() ,
                        new FillInBlank() ,
                        new FillInBlank()
                );
        //System.out.println( "questions = " + questions );

        for ( Question question : questions )
        {
            if ( question instanceof MultipleChoice q )
            {
                doSomething( q );
            } else if ( question instanceof FillInBlank q )
            {
                doSomething( q );
            } else
            {
                System.out.println( "Unexpected class: " + question.getClass() );
            }
        }
    }

    private void doSomething ( MultipleChoice mc )
    {
        // … do something …
        System.out.println( mc.getChoice() );
    }

    private void doSomething ( FillInBlank fib )
    {
        // … do something …
        System.out.println( fib.getFilledAnswer() );
    }
}

abstract class Question
{
    public final UUID id = UUID.randomUUID();

    @Override
    public String toString ( ) { return "Question[id=" + id + "]"; }
}

final class MultipleChoice extends Question
{
    public String getChoice ( ) { return "some choice response. ID: " + this.id; }
}

final class FillInBlank extends Question
{
    public String getFilledAnswer ( ) { return "some filled answer response. ID: " + this.id; }
}

When run.

some choice response. ID: bf418995-2d33-47c8-8276-06442330337a
some filled answer response. ID: 3fef17a3-9f46-463f-8e80-5361df6089bc
some filled answer response. ID: 1f2cb1e0-6142-49f2-96b8-888ded1210db

Sealed classes

In the future, the upcoming sealed classes feature may help further simplify such code.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154