Methods, method parameters, interaction and overloading
A method is a set of commands which allow you to perform a specific operation in a program. In other words, a method is a function; something that your class is able to do.
In other programming languages, methods are often called "functions", but in Java the word "method" is more common. :)
If you remember, in the last lesson we created simple methods for a Cat
class, so that our cats could say meow and jump:
public class Cat {
String name;
int age;
public void sayMeow() {
System.out.println("Meow!");
}
public void jump() {
System.out.println("Pounce!");
}
public static void main(String[] args) {
Cat smudge = new Cat();
smudge.age = 3;
smudge.name = "Smudge";
smudge.sayMeow();
smudge.jump();
}
}
sayMeow()
and jump()
are methods of our class.
And running these methods results in the following console output:
Meow!
Pounce!
Our methods are quite simple: they simply output text to the console.
But in Java, methods have an important task: they perform actions on an object's data. They change the object's data, transform it, display it, and do other things with it.
Our current methods don't do anything with the Cat
object's data. Let's look at a more illustrative example:
public class Truck {
int length;
int width;
int height;
int weight;
public int getVolume() {
int volume = length width height;
return volume;
}
}
For example, here we have a class representing a Truck
.
The semi truck has a length, width, height, and weight (which we will need later). In the getVolume()
method, we perform calculations, converting our object's data into a number representing its volume (we multiply the length, width, and height).
This number will be the result of the method.
Note that the method's declaration is written as public int getVolume
. That means that this method must return an int
.
We calculated the method's return value, and now we must return it to the program that called our method.
To return a method's result in Java, we use the keyword return.
return volume;
Method parameters
We can pass values called "arguments" to a method when calling it. A method's declaration includes a list of variables which tell us the type and order of variables that the method can accept. This list is called the "method parameters".
Our Truck
class's getVolume()
method doesn't currently define any parameters, so let's try extending our truck example.
Create a new class called BridgeOfficer
. This is a police officer on duty at a bridge, who checks all passing trucks to see if their load exceeds the allowed weight.
public class BridgeOfficer {
int maxWeight;
public BridgeOfficer(int normalVolume) {
this.maxWeight = normalVolume;
}
public boolean checkTruck(Truck truck) {
if (truck.weight > maxWeight) {
return false;
} else {
return true;
}
}
}
The checkTruck
method accepts one argument, a Truck
object, and determines whether or not the officer will allow the truck on the bridge. Inside the method, the logic is simple enough: if the truck's weight exceeds the maximum allowed, then the method returns false. It'll have to find another road :(
If the weight is less than or equal to the maximum, it can pass, and the method returns true.
If you don't fully understand the phrases "return" or "the method returns a value" yet, let's take a break from programming and consider them using a simple example from real life. :)
Let's say you get sick and stay home from work for a few days. You go to the accounting department with your doctor's note, because sick leave is supposed to be paid.
If we compare this situation with methods, then the accountant has a paySickLeave()
method. You pass a doctor's note as an argument to this method (without it, the method won't work and you won't get paid!). Then the necessary calculations are made inside the method using your note (the accountant uses it to calculate how much the company should pay you), and the result of your work (an amount of money) is returned to you.
Our program works in a similar way. It calls a method, passes data to it, and ultimately receives a result. Here's our BridgeOfficer
program's main()
method:
public static void main(String[] args) {
Truck first = new Truck();
first.weight = 10000;
Truck second = new Truck();
second.weight = 20000;
BridgeOfficer officer = new BridgeOfficer(15000);
System.out.println("Truck 1! Can I go, officer?");
boolean canFirstTruckGo = officer.checkTruck(first);
System.out.println(canFirstTruckGo);
System.out.println();
System.out.println("Truck 2! And can I?");
boolean canSecondTruckGo = officer.checkTruck(second);
System.out.println(canSecondTruckGo);
}
We create two trucks with loads of 10,000 and 20,000. And the bridge where the officer works has a maximum weight of 15,000.
The program calls the officer.checkTruck(first)
method. The method calculates everything and then returns true, which the program then saves boolean
variable canFirstTruckGo
. Now you can do whatever you want to with it (just like you can with the money the accountant gave you).
At the end of the day, the code
boolean canFirstTruckGo = officer.checkTruck(first);
boils down to
boolean canFirstTruckGo = true;
Here's very important point: the return
statement doesn't just return the method's return value, it also stops the method from running! Any code that comes after the return
statement will not be executed!
public boolean checkTruck(Truck truck) {
if (truck.weight > maxWeight) {
return false;
System.out.println("Turn around, you're overweight!");
} else {
return true;
System.out.println("Everything looks good, go ahead!");
}
}
The officer's comments will not be displayed, because the method has already returned a result and terminated! The program returns to the place where the method was called.
You don't have watch for this: the Java compiler is smart enough to generate an error when you try to write code after a return
statement.
Avengers: Parameter War
There are situations when we'll want several ways of calling a method.
Why not create our own artificial intelligence? Amazon has Alexa, Apple has Siri, so why shouldn't we have one? :)
In the movie Iron Man, Tony Stark creates his own incredible artificial intelligence, Jarvis.
Let's pay tribute to that awesome character and name our AI in his honor. :)
The first thing we need to do is to teach Jarvis say hello to people who enter the room (it would be weird if such an amazing intellect turned out to be impolite).
public class Jarvis {
public void sayHi(String name) {
System.out.println("Good evening, " + name + ". How are you?");
}
public static void main(String[] args) {
Jarvis jarvis = new Jarvis();
jarvis.sayHi("Tony Stark");
}
}
Console output:
Good evening, Tony Stark. How are you?
Very good! Jarvis is now able to welcome guests. Of course, more often than it will be his master, Tony Stark.
But what if he doesn't come alone! But our sayHi()
method only accepts one argument. And so it can only greet one person entering the room, and will ignore the other. Not very polite, agreed? :/
In this case, we can solve the problem by simply writing 2 methods with the same name, but different parameters:
public class Jarvis {
public void sayHi(String firstGuest) {
System.out.println("Good evening, " + firstGuest + ". How are you?");
}
public void sayHi(String firstGuest, String secondGuest) {
System.out.println("Good evening, " + firstGuest + " and " + secondGuest + ". How are you?");
}
}
This is called method overloading. Method overloading lets our program be more flexible and accommodate various ways of working. Let's review how it works:
public class Jarvis {
public void sayHi(String firstGuest) {
System.out.println("Good evening, " + firstGuest + ". How are you?");
}
public void sayHi(String firstGuest, String secondGuest) {
System.out.println("Good evening, " + firstGuest + " and " + secondGuest + ". How are you?");
}
public static void main(String[] args) {
Jarvis jarvis = new Jarvis();
jarvis.sayHi("Tony Stark");
jarvis.sayHi("Tony Stark", "Captain America");
}
}
Console output:
Good evening, Tony Stark. How are you?
Good evening, Tony Stark and Captain America. How are you?
Excellent, both versions worked. :)
But we didn't solve the problem! What if there are three guests? We could, of course, overload the sayHi()
method again, so that it accepts three guest names. But there could be 4 or 5. All the way to infinity.
Isn't there a better way to teach Jarvis to handle any number of names, without overloading the sayHi()
method a million times? :/
Of course there is! If there wasn't, do you think Java would be the most popular programming language in the world? ;)
public void sayHi(String...names) {
for (String name: names) {
System.out.println("Good evening, " + name + ". How are you?");
}
}
When (String... names
) is used as a parameter, it indicates that a collection of Strings will be passed to the method. We don't have to specify in advance how many there will be, so now our method is much more flexible:
public class Jarvis {
public void sayHi(String...names) {
for (String name: names) {
System.out.println("Good evening, " + name + ". How are you?");
}
}
public static void main(String[] args) {
Jarvis jarvis = new Jarvis();
jarvis.sayHi("Tony Stark", "Captain America", "Black Widow", "Hulk");
}
}
Console output:
Good evening, Tony Stark. How are you?
Good evening, Captain America. How are you?
Good evening, Black Widow. How are you?
Good evening, Hulk. How are you?
Some code here will be unfamiliar to you, but don't worry about it.
It's simple at its core: the method takes each name in turn and greets each guest! Plus, it will work with any number of passed strings! Two, ten, even a thousand—the method will work properly with any number of guests. Way more convenient than overloading the method for all the possibilities, don't you think? :)
Here's another important point: the order of the arguments matters!
Let's say our method takes a String and a number:
public class Person {
public static void sayYourAge(String greeting, int age) {
System.out.println(greeting + " " + age);
}
public static void main(String[] args) {
sayYourAge("My age is ", 33);
sayYourAge(33, "My age is "); // Error!
}
}
If the Person
class's sayYourAge
method takes a string and a number as inputs, then the program must pass them in that specific order! If we pass them in a different order, the compiler will generate an error and the person will not be able to say his age.
By the way, constructors, which we covered in the last lesson, are also methods! You can also overload them (i.e. create several constructors with different sets of parameters) and the order of passed arguments is fundamentally important for them too. They're real methods! :)
Yet again regarding parameters
Yep, sorry, we haven't finished with them yet. :)
The topic that we will study now is very important.
There is a 90% chance that you will be asked about this at every future interview!
Let's talk about passing arguments to methods.
Consider a simple example:
public class TimeMachine {
public void goToFuture(int currentYear) {
currentYear = currentYear+10;
}
public void goToPast(int currentYear) {
currentYear = currentYear-10;
}
public static void main(String[] args) {
TimeMachine timeMachine = new TimeMachine();
int currentYear = 2018;
System.out.println("What year is it?");
System.out.println(currentYear);
timeMachine.goToPast(currentYear);
System.out.println("How about now?");
System.out.println(currentYear);
}
}
The time machine has two methods. They both take the number representing the current year as an input, and either increase or decrease its value (depending on whether we want to go to the past or the future).
But, as you can see from the console output, the method doesn't work!
Console output:
What year is it?
2018
How about now?
2018
We passed the currentYear
variable to the goToPast()
method, but its value didn't change.
We were in 2018, and here we've stayed. But why? :/
Because primitives in Java are passed to methods by value.
What does that mean?
When we call the goToPast()
method and pass the int
variable currentYear (=2018)
to it, the method doesn't get the currentYear
variable itself, but rather a copy of it.
Of course, the value of this copy is also 2018, but any changes to the copy don't affect our original currentYear
variable in any way!
Let's make our code more explicit and watch what happens with currentYear
:
public class TimeMachine {
public void goToFuture(int currentYear) {
currentYear = currentYear+10;
}
public void goToPast(int currentYear) {
System.out.println("The goToPast method has started running!");
System.out.println("currentYear inside the goToPast method (at the beginning) = " + currentYear);
currentYear = currentYear-10;
System.out.println("currentYear inside the goToPast method (at the end) = " + currentYear);
}
public static void main(String[] args) {
TimeMachine timeMachine = new TimeMachine();
int currentYear = 2018;
System.out.println("What was the year when the program started?");
System.out.println(currentYear);
timeMachine.goToPast(currentYear);
System.out.println("And what year is it now?");
System.out.println(currentYear);
}
}
Console output:
What was the year when the program started?
2018
The goToPast method has started running!
currentYear inside the goToPast method (at the beginning) = 2018
currentYear inside the goToPast method (at the end) = 2008
And what year is it now?
2018
This clearly shows that the variable passed to the goToPast()
method is only a copy of currentYear
. And changing the copy doesn't affect the "original" value.
"Pass by reference" means the exact opposite.
Let's practice on cats!
I mean, let's see what passing by reference looks like using a cat example. :)
public class Cat {
int age;
public Cat(int age) {
this.age = age;
}
}
Now with the help of our time machine we'll send Smudge
, the world's first time-traveling cat, into the past and the future!
Let's modify the TimeMachine
class so that it works with Cat
objects;
public class TimeMachine {
public void goToFuture(Cat cat) {
cat.age += 10;
}
public void goToPast(Cat cat) {
cat.age -= 10;
}
}
Now the methods don't just changed the passed number. Rather, they change that specific Cat
's age
field.
You'll recall that this didn't work for us with primitives, because the original number didn't change.
Let's see what will happen!
public static void main(String[] args) {
TimeMachine timeMachine = new TimeMachine();
Cat smudge = new Cat(5);
System.out.println("How old was Smudge when the program started?");
System.out.println(smudge.age);
timeMachine.goToFuture(smudge);
System.out.println("How about now?");
System.out.println(smudge.age);
System.out.println("Holy smokes! Smudge has aged 10 years! Back up quickly!");
timeMachine.goToPast(smudge);
System.out.println("Did it work? Have we returned the cat to its original age?");
System.out.println(smudge.age);
}
Console output:
How old was Smudge when the program started running?
5
How about now?
15
Holy smokes! Smudge has aged 10 years! Back up quickly!
Did it work? Have we returned the cat to its original age?
5
Wow! Now the method did something different: our cat aged drastically, but then it got young again! :)
Let's try to figure out why.
Unlike the example with primitives, when objects are passed to a method they are passed by reference. A reference to the original smudge
object was passed to the changeAge()
method.
So, when we change smudge.age
inside the method, we're referencing the same area of memory where our object is stored.
It's a reference to the same Smudge that we created initially.
This is called "passing by reference"!
However, not everything with references is that easy. :)
Let's try changing our example:
public class TimeMachine {
public void goToFuture(Cat cat) {
cat = new Cat(cat.age);
cat.age += 10;
}
public void goToPast(Cat cat) {
cat = new Cat(cat.age);
cat.age -= 10;
}
public static void main(String[] args) {
TimeMachine timeMachine = new TimeMachine();
Cat smudge = new Cat(5);
System.out.println("How old was Smudge when the program started?");
System.out.println(smudge.age);
timeMachine.goToFuture(smudge);
System.out.println ("Smudge went to the future! Has his age changed?");
System.out.println(smudge.age);
System.out.println ("And if you try going back?");
timeMachine.goToPast(smudge);
System.out.println(smudge.age);
}
}
Console output:
How old was Smudge when the program started running?
5
Smudge went to the future! Has his age changed?
5
And if you try going back?
5
It doesn't work again! О_О
Let's figure out what happened. :)
It has everything to do with the goToPast
/goToFuture
methods and how references work.
Now, your attention, please! This is the most important thing to understand about how references and methods work.
The fact is, when we call the goToFuture(Cat cat)
method, it's a copy of the reference to the cat object that gets passed, not the reference itself.
Thus, when we pass an object to a method, there are two references to the object.
This is very important for understanding what's happening.
This is precisely why the cat's age didn't change in our last example.
In the previous example, when changing the age, we simply took the reference passed to the goToFuture()
method, and used it to find the object in memory and change its age (cat.age += 10
).
But now, inside the goToFuture()
method, we're creating a new object (cat = new Cat(cat.age)
), and this object is assigned the same reference copy that was passed to the method.
As a result:
The first reference (
Cat smudge = new Cat (5)
) points to the original cat (with age 5)After that, when we passed the cat variable the
goToPast()
method and assigned it a new object, the reference was copied.
And this brought us to the final outcome: two references pointing to two different objects. But we only changed the age of one of them (the one created inside the method).
cat.age += 10;
And of course, in the main()
method we can see on the console that the cat's age, smudge.age
, has not changed. After all, smudge
is a reference variable that still points to the old, original object with age 5, and we didn't do anything with that object. All our age changes were performed on the new object.
So, it turns out that objects are passed to methods by reference. Copies of objects are never created automatically. If you pass a cat object to a method and change its age, you will change its age.
But reference variables are copied when assigning values and/or calling methods!
Let's repeat here what we said about passing primitives:
"When we call the changeInt()
method and pass the int
variable x (=15)
, the method doesn't get the x
variable itself, but rather a copy of it. Therefore, any changes made to the copy don't affect our original x
variable in any way."
When copying references, everything works exactly the same way!
You pass the cat object to the method. If you do something to the cat itself (that is, with the object in memory), all your changes will be successfully applied, since we only had one object and still have only one object. But, if you create a new object inside the method and assign it to the reference variable passed to the method as an argument, you'll just be assigning the new object to a copy of the reference variable. From that moment on, we'll have two objects and two reference variables.
That's it! That wasn't so easy. You might even have had to read the lesson several times. But, the important thing is that you've mastered this super-important topic. You'll still end up arguing more than once about how arguments are passed in Java (even among experienced developers). But, now you know exactly how it works. Keep it up! :)
The article was published on <a href="https://codegym.cc/groups/posts/34-methods">CodeGym blog</a>.