Search code examples
javaclassconstructorargumentsvariadic-functions

Using varargs to create an object in a class


I am trying to create a class called GameCharacter which represents a game character and has the following properties: • name (a String) • powers (a set of Power objects)

How do I set up an object like this, within a class? The constructor must use varargs as Powers can have different parameters. This is my attempt, but clearly is does not work! This is from a past 2019 assignment question that I don't have solutions for, and I'm a beginner at java. Any help understanding this is appreciated!

    class GameCharacter{
    private String name;
    private int cost;
    class Powers{
        public Powers(Power... powers) {
            for (int i: powers) {
                this.i = powers
                
            }
        }
    }
    
    //constructor
    public GameCharacter(String name, int cost, Power... powers) {
        this.name = name;
    
    }
}

Solution

  • For a set of Power objects, use Set < Power >

    If you want to track a set of Power objects, use the Set class. No need for varargs, just pass a Set object.

    To make this very simple, let's use the Record feature coming to Java 16. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString. A record is appropriate when the main purpose of the class is to transparently and immutably carry data. Using records makes this example code much shorter.

    package work.basil.example.Game;
    
    import java.util.Set;
    
    public record GameCharacter (String name, Set <Power> powers)
    {
    }
    

    For an unmodifiable Set, use the Set.of methods.

    package work.basil.example.Game;
    
    public record Power(String description)
    {
    }
    
    package work.basil.example.Game;
    
    import java.util.Set;
    
    public class App
    {
        public static void main ( String[] args )
        {
            App app = new App();
            app.demo();
        }
    
        private void demo ( )
        {
            GameCharacter susan = new GameCharacter(
                    "Susan" ,
                    Set.of(
                            new Power( "invisibility" ) ,
                            new Power( "force field" )
                    )
            );
            GameCharacter ben = new GameCharacter(
                    "Ben" ,
                    Set.of(
                            new Power( "physical strength" ) ,
                            new Power( "rock-like hide" )
                    )
            );
    
            System.out.println( "susan = " + susan );
            System.out.println( "ben = " + ben );
        }
    }
    

    When run:

    susan = GameCharacter[name=Susan, powers=[Power[description=force field], Power[description=invisibility]]]
    ben = GameCharacter[name=Ben, powers=[Power[description=rock-like hide], Power[description=physical strength]]]
    

    If you were to define conventional classes rather than record, you would define two member fields on your GameCharacter class: String for name, and Set < Power > powers for the collection of Power objects. And you would add a constructor taking two arguments for those two fields.

    package work.basil.example.Game;
    
    import java.util.Objects;
    import java.util.Set;
    
    public final class GameCharacter
    {
        // Member fields
        private final String name;
        private final Set < Power > powers;
    
        // Constructor
        public GameCharacter ( String name , Set < Power > powers )
        {
            this.name = name;
            this.powers = powers;
        }
    
        public String name ( ) { return name; }
    
        public Set < Power > powers ( ) { return powers; }
    
        @Override
        public boolean equals ( Object obj )
        {
            if ( obj == this ) return true;
            if ( obj == null || obj.getClass() != this.getClass() ) return false;
            var that = ( GameCharacter ) obj;
            return Objects.equals( this.name , that.name ) &&
                    Objects.equals( this.powers , that.powers );
        }
    
        @Override
        public int hashCode ( )
        {
            return Objects.hash( name , powers );
        }
    
        @Override
        public String toString ( )
        {
            return "GameCharacter[" +
                    "name=" + name + ", " +
                    "powers=" + powers + ']';
        }
    }
    

    Varargs

    Now we can get back to your specific Question: how to handle varargs in your constructor.

    Here we add a second constructor to the class seen above. This second one takes a varargs of type Power.

    The varargs arrives as an array of Power objects. We can loop the elements of that array with the for-each syntax. We add each element to a new Set implementation of our choice, in this case the HashSet class.

        public GameCharacter ( String name , Power... powers )
        {
            this.name = name;
            Set<Power> s = new HashSet <>();    // New empty `Set` implementation.
            for ( Power power : powers )        // `powers` is the varargs array of `Power` objects.
            {
                s.add(power);                   // Add each element of array, each `Power` object, to our `Set` collection.
            }
            this.powers = s;
        }
    

    To use this constructor, we tweak our App class to drop the calls to Set.of.

    package work.basil.example.Game;
    
    import java.util.Set;
    
    public class App
    {
        public static void main ( String[] args )
        {
            App app = new App();
            app.demo();
        }
    
        private void demo ( )
        {
            GameCharacter susan = new GameCharacter(
                    "Susan" ,
                    new Power( "invisibility" ) ,
                    new Power( "force field" )
            );
            GameCharacter ben = new GameCharacter(
                    "Ben" ,
                    new Power( "physical strength" ) ,
                    new Power( "rock-like hide" )
            );
    
            System.out.println( "susan = " + susan );
            System.out.println( "ben = " + ben );
        }
    }
    

    Here is the source code for the entire class of our revised GameCharacter, now offering two constructors.

    package work.basil.example.Game;
    
    import java.util.HashSet;
    import java.util.Objects;
    import java.util.Set;
    
    public final class GameCharacter
    {
        // Member fields
        private final String name;
        private final Set < Power > powers;
    
        // Constructors
    
        // First constructor
        public GameCharacter ( String name , Set < Power > powers )
        {
            this.name = name;
            this.powers = powers;
        }
    
        // Second constructor
        public GameCharacter ( String name , Power... powers )
        {
            this.name = name;
            Set<Power> s = new HashSet <>();
            for ( Power power : powers )
            {
                s.add(power);
            }
            this.powers = s;  
        }
    
        // Getters
    
        public String name ( ) { return name; }
    
        public Set < Power > powers ( ) { return powers; }
    
        // `Object` overrides
    
        @Override
        public boolean equals ( Object obj )
        {
            if ( obj == this ) return true;
            if ( obj == null || obj.getClass() != this.getClass() ) return false;
            var that = ( GameCharacter ) obj;
            return Objects.equals( this.name , that.name ) &&
                    Objects.equals( this.powers , that.powers );
        }
    
        @Override
        public int hashCode ( )
        {
            return Objects.hash( name , powers );
        }
    
        @Override
        public String toString ( )
        {
            return "GameCharacter[" +
                    "name=" + name + ", " +
                    "powers=" + powers + ']';
        }
    }
    

    Tip: If you want your member field powers to be an unmodifiable set, you can reduce that second constructor to this:

        // Second constructor
        public GameCharacter ( String name , Power... powers )
        {
            this.name = name;
            this.powers = Set.of( powers ) ;  // Make an unmodifiable set from the varargs array.
        }