2

As I'm going though the debugger it looks like it is working at first, however when I try calling getCoordinates() in my shipSunk() method, it returns a null value almost every time. What is wrong here?

public boolean shipSunk(ShipDerived[] s){
    ArrayListMultimap<Integer, Integer> temp;
    ArrayList<Integer> temp2 = new ArrayList<>();
    boolean sunk = false;
    for(int i=s.length-1; i>=0;i--){
        Ship st = s[i];
        temp = s[i].getCoordinates();  //returns null almost every time???
        temp2 = s[i].getCoordinatesList();


        for (Map.Entry<Integer, Integer> entry : temp.entries()){
            //System.out.println(entry.getKey() + ", " + entry.getValue());
            if(grid[entry.getKey()][entry.getValue()]=='x'){
                sunk = true;
            }
            else{
                sunk = false;
            }
        }

        if(sunk==true){
            System.out.println("Ship has been sunk!");
        }
    temp.clear();
    }
    return sunk;
}

And here is my Ship class (extended from abstract class) methods:

import java.util.ArrayList;

public class ShipDerived extends Ship{

    private String type;
    private int length;
    private ArrayListMultimap<Integer, Integer> coordinates = ArrayListMultimap.create();
    private ArrayList<Integer> c2 = new ArrayList<>();

    public ShipDerived(){
        this.type = type;
        this.length = length;
        this.coordinates = getCoordinates();
        this.c2 = getCoordinatesList();
    }

    public ShipDerived(String t, int l){
        this.type = t;
        this.length = l;
        this.coordinates = getCoordinates();
        this.c2 = getCoordinatesList();
    }

    @Override
    void setType(String t) {
        type = t;
    }

    @Override
    void setLength(int l) {
        length = l;
    }

    @Override
    String getType() {
        return type;
    }

    @Override
    int getLength() {
        return length;
    }

    @Override
    ArrayListMultimap<Integer, Integer> getCoordinates() {
        return this.coordinates;
    }

    ArrayList<Integer> getCoordinatesList() {
        return this.c2;
    }

    @Override
    void setCoordinates(int i, int j) {
        //coordinates.putAll(i, Collections.singleton(j));
        this.coordinates.put(i,j);
        this.c2.add(i);
        this.c2.add(j);
    }
}

Here is my Ship (abstract) class:

public abstract class Ship {
private int length;
private String type;
private ArrayListMultimap<Integer, Integer> coordinates;

Ship(){
    this.length = length;
    this.type = type;
}

abstract void setType(String t);

abstract void setLength(int l);

abstract String getType();

abstract int getLength();

abstract ArrayListMultimap<Integer, Integer> getCoordinates();

abstract void setCoordinates(int i, int j);

}

And this is what I am passing into my shipSunk() method. I an using getters/setters to create my ships:

p1board.shipSunk(p1.getShips()); //player 1
p2board.shipSunk(p2.getShips()); //player 2

These seem to work but here's some code into these as well:

public class Player {
private String name;
private ShipDerived[] ships = new ShipDerived[5];

public Player(){
    this.name = name;
    this.ships = ships;
}

public String getName(){
    return name;
}

public void setName(String x){
    name = x;
}

public void setShips(){
    int a = 0;
    for(int i=5; i>=1;i--){
        ShipDerived s = new ShipDerived("null", 0);
        Array.set(ships, a, s);
        a++;
    }
    ships[0].setType("carrier");
    ships[0].setLength(5);
    ships[1].setType("battleship");
    ships[1].setLength(4);
    ships[2].setType("destroyer");
    ships[2].setLength(3);
    ships[3].setType("submarine");
    ships[3].setLength(3);
    ships[4].setType("patrol boat");
    ships[4].setLength(2);
}

public ShipDerived[] getShips(){
    return ships;
}
  • 5
    I think you mean to call `setCoordinates()` in your ShipDerived constructor instead of `getCoordinates()`? Otherwise the logic doesn't make sense. You are assigning `this.corrdinates` the return of `getCoordinates()` which returns `this.coordinates` - which is `null` and the cause of your problem. – Randy Casburn May 17 '21 at 03:31
  • Well, that would depend on what you're passing in for the `ShipDerived[] s` (i.e. where you're calling `shipSunk`- show that. – dominicoder May 17 '21 at 03:47
  • Can you show the code of the `Ship` class? – Thomas Kläger May 17 '21 at 05:27
  • @RandyCasburn I think in a perverse way the code could work, because the declaration for the `coordinates` field has an initializer. That means that `this.coordinates` is initialized after the implizit `super();` call but before the remaining constructor runs. – Thomas Kläger May 17 '21 at 06:00
  • @ThomasKläger I have added the class! – China Sanders May 17 '21 at 09:03
  • @dominicoder I have added the code! – China Sanders May 17 '21 at 09:03
  • @RandyCasburn I meant to call getCoordinates() because they are supposed to be set already. Should I change what I set this.coordinates to? – China Sanders May 17 '21 at 09:22
  • @ChinaSanders - "_they are supposed to be set already_" - Where/When? This is the constructor, nothing is "set" on the instance before the constructor finishes running. You have written the `coordinates` variable is if it is a derived/calculated instance member, yet your getter doesn't derive or calculate anything (so this is the origin of the code smell). Based on your comment above, it sounds like you need to either: 1. Set the coordinates in the constructor or 2. derive/calculate the value of coordinates inside the getter and return the derived value.. – Randy Casburn May 17 '21 at 12:49
  • OK - now ... are you calling `setShips` anywhere ... ? You see where this is going? A field is null in your array - where is the array set? In a function - where is that function called? From another function ... where is _that_ one called ... ? Follow the breadcrumbs and use logs and / or your debugger to step through and make sure everything is called correctly. – dominicoder May 17 '21 at 14:49

3 Answers3

0

You initialize this.coordinates twice. First as an inline field initializer and second in the constructor. The statement this.coordinates = getCoordinates(); does not make sense because the getter returns this.coordinates, so you effectively get this.coordinates = this.coordinates;

This shouldn't effect your issue, but my recommendation for easier debugging is to just set all fields that you can to final to avoid issues such as this and remove the superfluous call from the constructor.

Then I would investigate the ArrayListMultimap.create(); method, if there is any way that one returns null.

Konrad Höffner
  • 11,100
  • 16
  • 60
  • 118
0

You have @Override annotation on getCoordinates() so I'm guessing your Ship class also have coordinates field. In shipSunk method you cast your object to Ship class so most likely it operate on field Ship.coordintes whis is null because you are initializing only DerivedShip.coordinates.

Check this for simple example of hiding fields: If you overwrite a field in a subclass of a class, the subclass has two fields with the same name(and different type)?

Mateusz D.
  • 726
  • 5
  • 7
0

There are so many strange things in your code:

  • Why has the Ship class private fields if you don't provide access methods for those fields? Without accessor methods nobody can use those fields, so just delete them!
  • Why do the ShipDerived constructors initialize the coordinates and c2 fields if you already initialize them at the declaration?
  • Why does Player.setShips() use some obscure Array.set(ships, a, s); when ships[a] = s; would be sufficient?
  • Why do your constructors (for Ship, ShipDerived and Player) contain nonsense-code like this.type = type; (in ShipDeriveds default constructor) that seems important but does nothing?
  • Why does the Player.setShips() method create empty ShipDerived instances and only afterwards sets their values?

After some cleanup your classes could look like:

Ship.java:

import com.google.common.collect.ArrayListMultimap;

public abstract class Ship {

    Ship(){
    }

    abstract void setType(String t);

    abstract void setLength(int l);

    abstract String getType();

    abstract int getLength();

    abstract ArrayListMultimap<Integer, Integer> getCoordinates();

    abstract void setCoordinates(int i, int j);
}

ShipDerived.java:

import java.util.ArrayList;
import com.google.common.collect.ArrayListMultimap;

public class ShipDerived extends Ship{

    private String type;
    private int length;
    private ArrayListMultimap<Integer, Integer> coordinates = ArrayListMultimap.create();
    private ArrayList<Integer> c2 = new ArrayList<>();

    public ShipDerived(){
    }

    public ShipDerived(String t, int l){
        this.type = t;
        this.length = l;
    }

    @Override
    void setType(String t) {
        type = t;
    }

    @Override
    void setLength(int l) {
        length = l;
    }

    @Override
    String getType() {
        return type;
    }

    @Override
    int getLength() {
        return length;
    }

    @Override
    ArrayListMultimap<Integer, Integer> getCoordinates() {
        return this.coordinates;
    }

    ArrayList<Integer> getCoordinatesList() {
        return this.c2;
    }

    @Override
    void setCoordinates(int i, int j) {
        this.coordinates.put(i, j);
        this.c2.add(i);
        this.c2.add(j);
    }
}

Player.java:

public class Player {
    private String name;
    private ShipDerived[] ships = new ShipDerived[5];

    public Player(){
    }

    public String getName(){
        return name;
    }

    public void setName(String x){
        name = x;
    }

    public void setShips(){
        ships[0] = new ShipDerived("carrier", 5);
        ships[1] = new ShipDerived("battleship", 4);
        ships[2] = new ShipDerived("destroyer", 3);
        ships[3] = new ShipDerived("submarine", 3);
        ships[4] = new ShipDerived("patrol boat", 2);
    }

    public ShipDerived[] getShips(){
        return ships;
    }
}

These changes might not yet solve your issue, but at least make your code easier to read and understand.


Actually, since the Ship class no longer contains any fields and has only abstract methods you could even turn it into an interface (but that would mean that your methods are now public instead of package private, which would mean that you would also need to declare them as public in ShipDerived).

Ship.java, interface version:

import com.google.common.collect.ArrayListMultimap;

public interface Ship {

    void setType(String t);

    void setLength(int l);

    String getType();

    int getLength();

    ArrayListMultimap<Integer, Integer> getCoordinates();

    void setCoordinates(int i, int j);
}
Thomas Kläger
  • 17,754
  • 3
  • 23
  • 34