Reputation: 19
I have an ArrayList with a certain class (Piece) from which I want to pick items with a subclass (Miner and Bomb). However, when I pick some items it will use the methods of the super class (Piece), and not it's subclasses.
Since ArrayList<Pieces>
will contain several subclass objects of pieces, it is not possible to define a more specific class for ArrayList. Is it possible to pick some elements from ArrayList and still use the methods from the subclasses?
(I found it a little hard to find the solution for this simple problem. I searched for overriding, arrayList, methods along some other things, but couldn't find it. If it was too obvious to found, I'd like to know where you found it.)
Class Main:
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Piece> pieceList = new ArrayList<>();
pieceList.add(new Miner());
pieceList.add(new Bomb());
// create new classes to test canDefeat
if (new Miner().canDefeat(new Bomb())) {
System.out.println("Yes! This line will be printed.");
}
System.out.println();
// test canDefeat with pieces from the pieceList
if (pieceList.get(0).canDefeat(pieceList.get(1))) {
System.out.println("No, this line will not be printed");
}
}
}
Class Piece:
public abstract class Piece {
public boolean canDefeat(Piece opponent) {
return false;
}
}
Class Miner:
public class Miner extends Piece {
public boolean canDefeat(Bomb opponent) {
return true;
}
}
Class Bomb:
public class Bomb extends Piece {
}
Upvotes: 1
Views: 239
Reputation: 1074148
The why of this is easy:
Your Miner
doesn't override canDefeat(Piece)
, it adds a new method with the signature canDefeat(Bomb)
.
In the code pieceList.get(0).canDefeat(pieceList.get(1))
, all the compiler has to work with is the type Piece
, and so it resolves that to Piece#canDefeat(Piece)
.
The what to do instead is more tricky.
I would suggest that what kinds of pieces can defeat what other kinds of pieces isn't an attribute of each piece; for one thing, that will get brittle fairly quickly if you change the rules.
Instead, I would look at alternatives like:
Perhaps each piece can have a strength level, where higher strength means the piece can defeat a piece with lower strength. (Example below.)
You could have a BattleLogic
class that can compare types of pieces to determine the outcome, which would either work with strength per the above, or by type using instanceof
, etc. Normally, when you reach for instanceof
you need to stop and think about what you're doing and whether it's really the right tool for the job (it usually isn't), but it may well be the right tool for this job. The advantage to a BattleLogic
class is that none of the pieces knows anything about the other pieces; instead, you have one thing that understands the game as a whole and how the pieces rank when they fight. (Example below.)
If you want each Piece
to know what other types of pieces it can defeat, you can do that, but I think it will tend to lead to an unmaintainable mess. Two ways come to mind:
Having a list of the types it can defeat. (Example below.)
Using reflection to achieve what you were trying to do originally. (Example below.)
Here's the strength suggestion in code:
import java.util.ArrayList;
class Piece {
private int strength;
public Piece(int strength) {
this.strength = strength;
}
public int getStrength() {
return this.strength;
}
public boolean canDefeat(Piece opponent) {
return this.getStrength() > opponent.getStrength();
}
}
class Miner extends Piece {
public Miner() {
super(10);
}
}
class Bomb extends Piece {
public Bomb() {
super(5);
}
}
public class Main {
public static void main(String[] args) {
ArrayList<Piece> pieceList = new ArrayList<>();
pieceList.add(new Miner());
pieceList.add(new Bomb());
// create new classes to test canDefeat
if (new Miner().canDefeat(new Bomb())) {
System.out.println("Yes! This line will be printed.");
}
System.out.println();
// test canDefeat with pieces from the pieceList
if (pieceList.get(0).canDefeat(pieceList.get(1))) {
System.out.println("Yes! This line will also be printed");
}
}
}
And the BattleLogic
suggestion in code:
import java.util.ArrayList;
abstract class Piece {
}
class Miner extends Piece {
}
class Bomb extends Piece {
}
class BattleLogic {
/**
* Does a battle between the two pieces.
*
* @param a The first piece
* @param b The second piece
* @return 0 for a tie, a negative number if a beats b, or a positive number if b beats a
*/
public static int battle(Piece a, Piece b) {
if (a instanceof Miner && b instanceof Bomb) {
return -1;
}
// ...further tests here...
return 0;
}
}
public class Main {
public static void main(String[] args) {
ArrayList<Piece> pieceList = new ArrayList<>();
pieceList.add(new Miner());
pieceList.add(new Bomb());
// create new classes to test canDefeat
if (BattleLogic.battle(new Miner(), new Bomb()) < 0) {
System.out.println("Yes! This line will be printed.");
}
System.out.println();
// test canDefeat with pieces from the pieceList
if (BattleLogic.battle(pieceList.get(0), pieceList.get(1)) < 0) {
System.out.println("Yes! This line will also be printed");
}
}
}
Here's that third suggestion, having each type of piece know what pieces it can defeat, in case. Again, though, I don't suggest this; I think having the pieces know this much about each other is going to be a maintenance problem:
// Not really recommended
import java.util.List;
import java.util.ArrayList;
abstract class Piece {
private List<Class> iDefeat;
public Piece(Class... defeats) {
this.iDefeat = new ArrayList<Class>();
for (Class c : defeats) {
this.iDefeat.add(c);
}
}
public boolean canDefeat(Piece piece) {
return this.iDefeat.contains(piece.getClass());
}
}
class Bomb extends Piece {
}
class Miner extends Piece {
public Miner() {
super(Bomb.class);
}
}
public class Main {
public static void main(String[] args) {
ArrayList<Piece> pieceList = new ArrayList<>();
pieceList.add(new Miner());
pieceList.add(new Bomb());
// create new classes to test canDefeat
if (new Miner().canDefeat(new Bomb())) {
System.out.println("Yes! This line will be printed.");
}
System.out.println();
// test canDefeat with pieces from the pieceList
if (pieceList.get(0).canDefeat(pieceList.get(1))) {
System.out.println("Yes! This line will also be printed");
}
}
}
Or you could make the list static if you're going to have millions of pieces and memory is an issue; this is just a sketch.
And finally, the way using reflection:
// Not reall recommended
import java.lang.reflect.Method;
import java.util.ArrayList;
class Piece {
public Piece() {
}
private static Method getMethodOrNull(Class cls, String name, Class... paramTypes) {
try {
return cls.getMethod(name, paramTypes);
}
catch (NoSuchMethodException nsme) {
return null;
}
}
public boolean canDefeat(Piece opponent) {
try {
Class thisClass = this.getClass();
Class oppClass = opponent.getClass();
Method m = null;
while (oppClass != Piece.class && (m = getMethodOrNull(thisClass, "canDefeat", oppClass)) == null) {
oppClass = oppClass.getSuperclass();
}
if (m != null) {
return (boolean)m.invoke(this, opponent);
}
return false;
}
catch (Exception iae) {
throw new RuntimeException("Can't access method", iae);
}
}
}
class Miner extends Piece {
public boolean canDefeat(Bomb b) {
return true;
}
}
class Bomb extends Piece {
}
public class Main {
public static void main(String[] args) {
ArrayList<Piece> pieceList = new ArrayList<>();
pieceList.add(new Miner());
pieceList.add(new Bomb());
// create new classes to test canDefeat
if (new Miner().canDefeat(new Bomb())) {
System.out.println("Yes! This line will be printed.");
}
System.out.println();
// test canDefeat with pieces from the pieceList
if (pieceList.get(0).canDefeat(pieceList.get(1))) {
System.out.println("Yes! This line will also be printed");
}
}
}
Upvotes: 2
Reputation: 313
In addition to the proper answer of @T.J. Crowder I would recommend you to also think about implementing this whole stuff within a more data-oriented approach instead of an object-oriented one. This looks like a perfect problem that can be implemented within an Entity-System framework (like "Artemis") where your Piece is an entity having a strength component and the whole logic goes to a BattleSystem.
Such an approach is height adaptable on changing rules. For example, if your defeat is not only related to strength but also to health it is easy to add also a health parameter to the strength-component or even create a additional health component and the logic in the BattleSystem can than deal with it. The more difficult thing within this approach is the right design of entities and component and systems and the inter-system communication for your specific needs.
read more about Entity-Component-Systems also here: Wiki : Artemis
Upvotes: 0
Reputation: 1169
You just need to change the signature of canDefeat
to accept generic types, and make sure you do the overriding in the right way, both signatures are always the same
Piece class
public abstract class Piece {
public boolean canDefeat(Piece opponent) {
return false;
}
}
Miner class
public class Miner extends Piece {
@Override
public boolean canDefeat(Piece opponent) { // Piece not Bomb
return true;
}
}
Update to let
Miner
defeats onlyBomb
Piece class
public abstract class Piece<T> {
public boolean canDefeat(T opponent) {
return false;
}
}
Miner class
public class Miner extends Piece<Bomb> {
@Override
public boolean canDefeat(Bomb opponent) { // Defeats Bomb
return true;
}
}
Upvotes: 0
Reputation: 18123
Miner
does not override the canDefeat
-method of Piece
because the parameter type is narrowed. You can check this easily by adding
@Override
to the method you think you are overriding to verify it at compile time.
Furthermore, it is not good OO style to provide default behavior in abstract super classes but rather move the behavior into concrete children where possible because otherwise you may involuntary inherit behavior you otherwise would like to overwrite. In your case, make canDefeat
abstract and let Bomb
implement it with the behavior currently present in Piece
.
Upvotes: 0