Previous lesson Back to the front Next lesson

If You Don't Object...


In this lesson we'll learn a little more about objects, using the car we built in the previous lesson. If you haven't looked at the lesson on methods, or it was a long time ago you did, go back and read it again.

Once you're ready, start off like this:


class Car {
	String colour;
	static int model = 200;
	int mpg;
}

What we have now is a design for a car with a value for the average miles per gallon. Let's construct a car:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car myCar = new Car();
		myCar.colour = "Red";
		myCar.mpg = 30;
	}
}

Now we have a shiny red car with reasonable fuel economy. Cool. All this should be straightforward so far. Suppose we want to be able to work out how much petrol is used on a certain length journey. That sounds like a "particular task" - maybe we could write a method to do it!

Let's add a method to the Car class to do this. Why not the Hello class? Because it relates specifically to cars. Let's make a method that returns the number of gallons used, if we give it the length of the journey in miles.


class Car {
	String colour;
	static int model = 200;
	int mpg;

	int gallonsUsed(int miles) {
	}
}

Before we write the method, take a look at what we have so far. The method returns an int, and has a parameter called miles which is an int. I've made the name of the method gallonsUsed so that it's clear at least roughly what it does. Once you're happy with that, let's make the method do something.


class Car {
	String colour;
	static int model = 200;
	int mpg;

	int gallonsUsed(int miles) {
		return miles / mpg;
	}
}

This method calculates the amount of gallons used (by dividing the distance by the miles per gallon), and returns it. The fact that it does it all in one line shouldn't scare you too much by now (I hope!). I should point out that as both miles and mpg are of type int, the result is rounded down to the nearest integer.

Right, so we've constructed a Car, and set its mpg variable, and the design/class for Car knows how to work out its fuel consumption on a particular journey. Let's compile and run the program:

[nurmes]btg: javac Hello.java
[nurmes]btg: java Hello
Hello, world!
[nurmes]btg: 

Remember that when we ask for the Hello.java file to be compiled, the compiler checks if the Car.java file needs compiling (because the Car class is used by the Hello class). We can see that the program compiles and works OK. Now let's try out our new method. The same way we use the dot with variables belonging to a particular class, we can use it with methods:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car myCar = new Car();
		myCar.colour = "Red";
		myCar.mpg = 30;

		myCar.gallonsUsed(90);
	}
}

We've asked how many gallons are used by driving myCar for 90 miles. The variable miles (belonging to the gallonsUsed method) is set to 90. As you can see, it's exactly the same as using a variable belonging to a class. Easy? Easy!

Now as it stands, this program won't print out the number of gallons used. Can you figure out why? We've not done anything with the return value - it is just thrown away, which is a bit pointless. We made the same mistake when we first learned about return values. So let's print out the number of gallons:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car myCar = new Car();
		myCar.colour = "Red";
		myCar.mpg = 30;

		System.out.println(myCar.gallonsUsed(90));
	}
}

I hope you're happy with printing out the number of gallons directly like that. We could have stored it in a variable first - but I want you to get used to seeing things written the shortest way, because most experienced programmers will write their programs like that.

[nurmes]btg: javac Hello.java
[nurmes]btg: java Hello
Hello, world!
3
[nurmes]btg: 

In the previous lesson I tried (and failed?) to explain what static means when put in front of a variable (like static int model). Remember that this means there is only one copy of the variable for all the cars we make (so all the cars have the same model number although each has a different colour). In fact, we don't even have to construct a single car (or instance) to set the value of the model variable. It could be thought of as belonging to the design, since we can decide on the model number before making any cars.

Now we are going to write a method which is static, but first let me tell you a bit about it. A static method can only access static variables. We can call (use) a static method without creating any instances of the class. This means that if we create a static method belonging to the Car class, we can use that method without constructing any cars. It also means that the method we create can only access the variable model - the only variable which is static. Apart from that, static methods are just like normal methods.

So seeing as we are limited to accessing the model number, let's write a method which changes it. I don't deny that this is a totally pointless exercise.


class Car {
	String colour;
	static int model = 200;
	int mpg;

	int gallonsUsed(int miles) {
		return miles / mpg;
	}

	static void setModel(int newModel) {
		model = newModel;
	}
}

Here's the new method, with the keyword static just before the return type (void, indicating nothing is returned). We pass the new model number as a parameter to the method. The Java code between the curly brackets is just one line, which uses the parameter called newModel to set the static variable model. This code between the curly brackets, which actually makes a method work, is called the method body. The line static void setModel(int newModel) { is called the method header.

Anyway, we now have a static method. It only accesses one variable (model) which is static. So it should work. Let's try it out:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car.setModel(205);

		Car myCar = new Car();
		myCar.colour = "Red";
		myCar.mpg = 30;

		System.out.println(Car.model);
	}
}

Look first at the change to the last statement. You can see that we're accessing the field model using the name of the class (Car) rather than any particular instance of it (such as myCar). We saw this before when we first encountered static fields/variables. Now look at the new line near the top. We've used the static method in a similar way. Just to prove my point, I'm going to remove the lines in the middle, so that no cars are ever constructed:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car.setModel(205);

		System.out.println(Car.model);
	}
}

And the program works fine:

[nurmes]btg: javac Hello.java
[nurmes]btg: java Hello
Hello, world!
205
[nurmes]btg: 

It's important that you understand that we can only do this with static variables and methods. Let's try setting the colour using the name of the class:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car.setModel(205);

		Car.colour = "Green";

		System.out.println(Car.model);
	}
}

 

[nurmes]btg: javac Hello.java
Hello.java:7: Can't make a static reference to nonstatic variable colour in class Car.
                Car.colour = "Green";
                   ^
1 error
[nurmes]btg: 

The error kind of explains itself - the variable colour is non-static, so we can't treat it as if it is static. We have to construct a Car before it can have a colour, but we can have a model number for all the cars even if we haven't built one yet. The same is true of non-static methods:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car.setModel(205);

		Car.gallonsUsed(120);

		System.out.println(Car.model);
	}
}

 

[nurmes]btg: javac Hello.java
Hello.java:7: Can't make static reference to method int gallonsUsed(int) in class Car.
                Car.gallonsUsed(120);
                               ^
1 error
[nurmes]btg: 

If the line we wrote had worked, it would have asked something like "How many gallons does a car use to drive 120 miles?". Of course it's a silly question, like "How long is a piece of string?". It depends how long our string (or car) is. So we have to have a particular car (an instance of the Car class) before we can use this method.

I think you've suffered enough with methods. The last part of this lesson is about constructors. I've talked about "constructing a car" a lot, to get you used to the idea. Here's an example of using a constructor:


		Car myCar = new Car();

Technically, the constructor is just the blue part. Looks a lot like a method, doesn't it? Well yes, and in fact there are lots of things about constructors that are a lot like methods. I suppose you could think of a constructor as a method which returns an instance of some class (like Car). In our example, the constructor returns a new Car, and a reference to this new Car object is copied into the variable myCar by the = operator.

Anyway, that's how to use a constructor. Like a method, a constructor has to be written before you can use it. Well, not quite - there is a default constructor (which does nothing) provided if you don't write your own. Let me show you:


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car() {
	}
}

I've removed both the methods to save some space, but we could have left them in. What we've written is a constructor which does nothing. This is equivalent to the default constructor which is provided automatically. Notice that it looks a lot like a method - there is the name, the round brackets () and a block (a pair of curly brackets). The return type is missing (it should be before the name), but that's OK because we know that it returns a new instance of the class. Also, the name has to be the same as the name of the class, so that the compiler knows it is a constructor.

So what's one of them do then, mister? It allows us to do any "setting up" which the new instance might need. We can insert whatever code in there we like. We can have more than one constructor, as I'll show in a moment. We can require parameters to be passed to the constructor, like this:


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car(int milesPerGallon) {
	}
}

We need to fill in the body of the constructor for it to be useful. We can treat the parameter milesPerGallon just as if the constructor was a method.


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car(int milesPerGallon) {
		mpg = milesPerGallon;
	}
}

Now this constructor includes setting the mpg of the car in the process of constructing it. Makes sense really, as it means we won't forget. Let's try a simple program now, using the new Car class:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car myCar = new Car();
	}
}

Very simple, but unfortunately it no longer works!

[nurmes]btg: javac Hello.java
Hello.java:5: No constructor matching Car() found in class Car.
                Car myCar = new Car();
                            ^
1 error
[nurmes]btg: 

You see, before we hadn't defined any constructor in the file Car.java. So the default constructor was added for us, which has no parameters (did I mention that arguments is sometimes used instead of parameters?). But as soon as we define our own constructor, the default constructor isn't created. So there is not a constructor with no arguments any more, and the program doesn't work. However, there is a constructor with one int argument - let's use it.


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car myCar = new Car(40);
	}
}

Now we've matched the constructor we defined and the program will work. This is actually quite a useful feature, as it means we can't accidentally forget to set the mpg value. Before we run it, let's put in a line to check that the mpg has been set correctly.


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car myCar = new Car(40);

		System.out.println(myCar.mpg);
	}
}

 

[nurmes]btg: javac Hello.java
[nurmes]btg: java Hello
Hello, world!
40
[nurmes]btg: 

It looks like it works OK. Let's add a bit more intelligence to our constructor:


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car(int milesPerGallon) {
		if (milesPerGallon > 0) {
			mpg = milesPerGallon;
		}
	}
}

Now we check if the value given is positive (it's pretty daft if it isn't). We can also add another constructor with two arguments (again, just like methods).


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car(int milesPerGallon) {
		if (milesPerGallon > 0) {
			mpg = milesPerGallon;
		}
	}

	Car(int milesPer, String col) {
	}
}

Suppose we want to use the first constructor to check the value of the milesPer variable is positive. There's not much point repeating that code (especially if we had made it more complex). Methods are supposed to avoid repetition. So there must be a way to call the first constructor, as if it were a method. To do this we use this:


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car(int milesPerGallon) {
		if (milesPerGallon > 0) {
			mpg = milesPerGallon;
		}
	}

	Car(int milesPer, String col) {
		this(milesPer);
	}
}

We have to use this like a method, to call the other constructor. You might think writing Car(milesPer) would be clearer, but Java doesn't let you do that. The word this is a keyword, like int or class, so you're not allowed to have a method or a variable of your own called this.

So the first constructor is called, using the value of milesPer, which sets up the variable mpg. It's just like calling one method from inside another. This means that we don't have to duplicate the code which checks that milesPerGallon is positive.

The only restriction is that we have to put this as the first line of the constructor. Now all we have to do is set the colour.


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car(int milesPerGallon) {
		if (milesPerGallon > 0) {
			mpg = milesPerGallon;
		}
	}

	Car(int milesPer, String col) {
		this(milesPer);
		colour = col;
	}
}

We'd better modify our test program to ensure that the new constructor works as expected:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car myCar = new Car(40, "Red");

		System.out.println(myCar.mpg);
		System.out.println(myCar.colour);
	}
}

 

[nurmes]btg: javac Hello.java
[nurmes]btg: java Hello
Hello, world!
40
Red
[nurmes]btg: 

We can also check that negative numbers are ignored:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car myCar = new Car(-40, "Red");

		System.out.println(myCar.mpg);
		System.out.println(myCar.colour);
	}
}

The default value for an int variable is zero. There are some conditions when the compiler will not warn us that we have forgotten to set it to a value.

[nurmes]btg: javac Hello.java
[nurmes]btg: java Hello
Hello, world!
0
Red
[nurmes]btg: 

As another example, let's make a default constructor of our own.


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car() {
		this(35, "Blue");
	}

	Car(int milesPerGallon) {
		if (milesPerGallon > 0) {
			mpg = milesPerGallon;
		}
	}

	Car(int milesPer, String col) {
		this(milesPer);
		colour = col;
	}
}

In this case the no-argument constructor calls the two-argument constructor, which in turn calls the one-argument constructor. The result of all this is that if we do not specify, we get a 35mpg blue car, as we will show:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car myCar = new Car();

		System.out.println(myCar.mpg);
		System.out.println(myCar.colour);
	}
}

 

[nurmes]btg: javac Hello.java
[nurmes]btg: java Hello
Hello, world!
35
Blue
[nurmes]btg: 

For our last trick, let's simplify things by just having one constructor:


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car(int milesPerGallon) {
		mpg = milesPerGallon;
	}
}

We need to modify the test program again:


class Hello {
	public static void main(String[] args) {
		System.out.println("Hello, world!");

		Car myCar = new Car(40);

		System.out.println(myCar.mpg);
	}
}

Now let's change the name of the parameter:


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car(int mpg) {
		mpg = mpg;
	}
}

But now we have two variables called mpg! You might worry that this could cause some problems, and of course you'd be right:

[nurmes]btg: javac Hello.java
[nurmes]btg: java Hello
Hello, world!
0
[nurmes]btg: 

If we just write mpg, we are referring to the one which belongs to the method or constructor and not the one belonging to the object. In other words, both sides of the = are the same variable, so it sets itself to its own value (ie nothing changes). To change the field (the variable belonging to the object) called mpg when there are two, we have to write this again:


class Car {
	String colour;
	static int model = 200;
	int mpg;

	Car(int mpg) {
		this.mpg = mpg;
	}
}

Now the program will work as we intended. In fact we could have used this all along, but it is unnecessary except where there is ambiguity.

[nurmes]btg: javac Hello.java
[nurmes]btg: java Hello
Hello, world!
40
[nurmes]btg: 

Alright, quiet class! Fill out this questionnaire, then you can go home.

But
what
do
you
think?
This lesson was...
What don't you understand? Other comments?
Excellent
OK
Poor
Your email (optional)
Go on to Itsy Bitty Teeny Weeny

Too patronising? Too complex? Typing error? Offended by traffic cones? Got a question or something I should add? Send an email to ben_golding@yahoo.co.uk !

visits to this site

The contents of this site are copyright of Ben Golding