Let's create our own classes now. Python lets you get away with a lot of things here that you can't do in Java, but it's not terribly different. Some important things to keep in mind:
Penguin
class must be defined in Penguin.java.this
, instead of self
.self
).Let's create a new class to represent monkeys. Our class will be named Monkey
, so we need to create a new text file, Monkey.java. Create that file, and add the following code to it:
public class Monkey { } //end of class Monkey
We can compile this to make sure it works:
$ javac *.java Note: CodeToRun.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. $
Right now our object has no fields, so it's not representing much. Let's add some fields for basic stats about each Monkey object.
public class Monkey { //age of this monkey, in years public int age; //height of the monkey, from feet to head, in meters public double height; //the monkey's name public String name; } //end of class Monkey
In Python, the __init__
method is the constructor, which initializes all the fields. In Java, the syntax for this is quite a bit different. It still looks like a method, except the return type isn't specified and the "name" is replaced by the class name (in PascalCase). Here's the class with the constructor added:
public class Monkey { //age of this monkey, in years public int age; //height of the monkey, from feet to head, in meters public double height; //the monkey's name public String name; //constructor public Monkey(int age, double height, String name) { this.age = age; this.height = height; this.name = name; } } //end of class Monkey
Notice that this
is the reference to the subject instead of Python's self
. Since Java has only methods and no non-method functions, this identifier isn't listed as the first parameter.
Create an instance of this class in the main method in CodeToRun.java: (We don't need to import Monkey.java as long as both .java files are in the same folder.)
public static void main(String[] args) { Monkey monkey = new Monkey(3, .65, "Banano"); System.out.println("Banano's age: " + monkey.age); System.out.println("Banano's height: " + monkey.height); System.out.println("Banano's name: " + monkey.name); }
toString
__repr__
and __str__
become toString
. Java uses toString
as the default method for displaying objects, so you definitely want to write that next. This method should always take no parameters and return a string. Here I've added toString
to Monkey.java:
//constructor public Monkey(int age, double height, String name) { this.age = age; this.height = height; this.name = name; } //toString public String toString() { String string = ""; string += "Monkey named " + this.name + ":\n"; string += " age: " + this.age + " years\n"; string += " height: " + this.height + " m"; return string; }
Now we can test this by adding a line to our main method in CodeToRun:
public static void main(String[] args) { Monkey monkey = new Monkey(3, .65, "Banano"); System.out.println("Banano's age: " + monkey.age); System.out.println("Banano's height: " + monkey.height); System.out.println("Banano's name: " + monkey.name); System.out.println("monkey: \n" + monkey.toString()); }
Now run that code:
$ javac *.java Note: CodeToRun.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. $ java CodeToRun Banano's age: 3 Banano's height: .65 Banano's name: Banano monkey: Monkey named Banano: age: 3 years height: .65 m $
Recall that Java automatically calls toString
in most cases where it's looking for a string, so we can remove the .toString()
in the second-to-last line:
public static void main(String[] args) { Monkey monkey = new Monkey(3, .65, "Banano"); System.out.println("Banano's age: " + monkey.age); System.out.println("Banano's height: " + monkey.height); System.out.println("Banano's name: " + monkey.name); System.out.println("monkey: \n" + monkey); return string; }
The output will be the same as before.
Unfortunately, we can do the following thing:
public static void main(String[] args) { Monkey monkey = new Monkey(3, .65, "Banano"); System.out.println("Banano's age: " + monkey.age); System.out.println("Banano's height: " + monkey.height); System.out.println("Banano's name: " + monkey.name); monkey.age = -17; System.out.println("monkey: \n" + monkey); }
-17 doesn't make a lot of sense for the age. How can we fix this? We can change the constructor so that negative ages don't work:
//constructor public Monkey(int age, double height, String name) { this.age = age; if (this.age < 0) { //an illegal age was given, so set the age to zero. // (should be improved by throwing an exception instead) this.age = 0; } this.height = height; this.name = name; }
This won't solve the entire problem, though. We want to make it so that other code doesn't have direct access to the fields of any Monkey object. We can do this by changing the declarations to specify that these fields are private instead of public. Modify your Monkey class to:
public class Monkey { //age of this monkey, in years private int age; //height of the monkey, from feet to head, in meters private double height; //the monkey's name private String name;
Try compiling your code again. You'll see these errors:
$ javac *.java CodeToRun.java:6: error: age has private access in Monkey System.out.println("Banano's age: " + monkey.age); ^ CodeToRun.java:7: error: height has private access in Monkey System.out.println("Banano's height: " + monkey.height); ^ CodeToRun.java:8: error: name has private access in Monkey System.out.println("Banano's name: " + monkey.name); ^ 3 errors $
Any member (field or a method) of a class has an access modifier, which can be set to one of four things:
public
: Any other class has access to this member.private
: Only code in the class itself has access to the member.protected
: Only code in this class, its subclasses, and any inner classes has access to the member.//age of this monkey, in years int age;
There are some general rules to follow when choosing access modifiers, in order to best protect your code:
final
in the declaration. (Constants should be named in ALL_CAPS instead of camelCase.) Here's an example of a constant that might exist in a class that does some unit conversions:
public class UnitConversions { //the number of ounces in one pound public static final int OUNCES_PER_POUND = 16;Recall that the
static
identifier means that we can access this member without needing an instance of the class. Another class could use this constant like this:
double ounces = UnitConversions.OUNCES_PER_POUND * pounds;
We might still want some access to the fields. For example, it seems reasonable to get a hold of the age of a monkey. We can do that by adding another method to Monkey.java, a "getter":
public int getAge() { return this.age; }
Then, in CodeToRun.java, we can use this in the main method:
public static void main(String[] args) { Monkey monkey = new Monkey(3, .65, "Banano"); int yearsOld = monkey.getAge(); System.out.println("Banano's age: " + yearsOld); yearsOld = 15; System.out.println("yearsOld: " + yearsOld); System.out.println("Banano's age: " + monkey.getAge()); }
Running this, we see:
$ javac *.java Note: CodeToRun.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. $ java CodeToRun Banano's age: 3 yearsOld: 15 Banano's age: 3 $
Notice that we get access to the value of the age, but can't change the actual value in the object.
Sometimes we still want to be able to change the value, though maybe to a limited extent. We can do this by adding a "setter":
public void setAge(int age) { if (age >= 0) { //change the age if positive this.age = age; } }
Notice that this allows us to change the value, but still protects against rogue code trying to assign it a negative number.
Create a new class, Horse
in its own file. Do each of the following steps:
toString
method.