The Problem's solution
To make your program compile, you need to declare the arguments to Battle
's constructor as Player
or Enemy
for Java to find the speed
field:
public class Battle {
public Battle(Player a, Player b) {
if (faster(a.speed, b.speed)) {
System.out.println("Faster");
} else {
System.out.println("Slower");
}
}
// ...
}
This does, however, prevent passing in Enemy
instances. You could provide overloaded methods, however the better solution here is to use either a (probably abstract) base class for Player
and Enemy
to inherit from or an interface for them to implement.
Using an Abstract Base Class
Making the base class abstract isn't strictly necessary, however it does prevent it from being instantiated, which we only want to allow for the subclasses Player
and Enemy
. It also highlights that the class is intended to be subclassed rather than to be used by itself.
// Character.java
public abstract class Character {
private int speed;
public Character(int speed) {
this.speed = speed;
}
public int getSpeed() {
return speed;
}
}
// Player.java
public class Player extends Character {
public Player() {
super(5);
}
}
// Enemy.java
public class Enemy extends Character {
public Enemy() {
super(3);
}
}
With this, the constructor of the Battle
could be modified to fit:
// ...
public Battle(Character a, Character b) {
if (faster(a.getSpeed(), b.getSpeed())) {
System.out.println("Faster");
} else {
System.out.println("Slower");
}
}
// ...
Using an Interface
In contrast to (abstract) classes, interfaces usually describe some action which an object implementing the interface supports.
// HasSpeed.java
public interface HasSpeed {
int getSpeed(); // interface methods are public and abstract by default
}
// Player.java
public class Player implements HasSpeed {
@Override
public int getSpeed() {
return 5;
}
}
// Enemy.java
public class Enemy implements HasSpeed {
@Override
public int getSpeed() {
return 3;
}
}
Here, Battle
's constructor would be modified as follows:
// ...
public Battle(HasSpeed a, HasSpeed b) {
if (faster(a.getSpeed(), b.getSpeed())) {
System.out.println("Faster");
} else {
System.out.println("Slower");
}
}
// ...
Why it needs to be this way
The issue you are having is that you are trying to access fields of the Player
and Enemy
classes on variables declared as Object
. This would work in most dynamically typed languages such as Python or JavaScript, however not in a statically typed language such as Java.
Dynamic Typing
In dynamically typed languages, the type of any variable and / or field is determined and checked at runtime. This means that any object can be passed for any argument, in most of these languages anyways. Whether the object possesses a certain field is checked when the value of that field is retrieved.
This way of doing things makes initially writing code somewhat easier, however it also increases the likelyhood of missing errors, because they will only be reported at runtime, when they come up.
As an example, if Java were dynamically typed, your program would run fine until the point where something other than a Player
or Enemy
is passed to Battle
's constructor and the speed
field is not found.
Static Typing
In a statically typed language, types and their field as well as methods are checked at compile-time, so the type of arguments and variables needs to be provided in their declaration. Only these types and their subtypes can then be assigned to them. This makes sure that the object has all the fields and methods you expect of it, although, in the case of subtypes, it may have more.
As you've already figured out, when something other then the declared type is assigned, an error is raised during compilation of the program. It is possible to suppress these errors, e.g. through casts, but a warning will still be given.
This, while being somewhat restrictive, ensures that, unless special action is taken, there will never be a runtime error due to missing fields or methods. Such errors will always be caught during compilation.
Further Reading
This article seems to provide a nice overview of the differences between statically and dynamically typed languages.
In my examples above there is a glimpse of this, but you may want to look into encapsulation to write code that better fits Java conventions. I won't go into this here, as it breaks this answer's scope, however here is some further information.
I have also found that, for learning new programming languages, frameworks or principles, books are an excellent source. Especially if you have a daily commute by public transport, these are an excellent way to kill some time and learn something while doing it. The book which tought me Java is 'Java in a Nutshell' by O'Reilly. I find it well-written, however there are many other options.