Stargrasper's Java Basics Tutorial Series Whatever-I-Was-Calling-It-Like-A-Year-And-A-Half-Ago
I posted the last lesson on January 22, 2012. A year-and-a-half later, I'm posting this one. In that time, I've learned a few things about being a programmer. Let's see if all I've learned reflects in this lesson!
Now, I really wanted to just dive into objects. Reading the old lessons, I got really tired of treating them as magic. So, we're not going to do that anymore...okay, yes we will. But not as much. Okay, we actually have to talk about one more thing first. You need to understand methods before you can understand objects.
Lesson 0.6.whatever: Methods
Methods, known as functions or procedures in most other programming languages, allow you to encapsulate code in a manageable space that can be inserted into almost any part of your code just by calling the method. Here's a secret; that thing called main? Yeah, that's a method. A method that's required by Java desktop applications. It tells them where to start executing code. If you're writing a Java applet, the rules are different. Thank you Sun Microsystems, now owned by Oracle, for a consistent environment.
So lets look at a tiny bit of code, shall we?
public static void main(String args[]) {
System.out.println("Hello World!");
}
The line public static void main(String args[]) is the method signature. This means the application tells the compiler what to expect for input and output for that function; as well as other information on how the method can be called. The public keyword reflects visibility of the method. More on that in the objects lesson. static and void are two more keywords that I'll get to shortly. What we really want to talk about is main. This is the name of the method. What's inside the parenthesis tell the method what input to expect. In this case, a String array. This array is actually for command line argument. If you're using an IDE, then getting at the option to pass command line arguments is pretty tricky most of the time. If you're actually running from the command line, then it's trivial, naturally. We talked about Strings and arrays previously, so you can review previous lessons or ask questions if need be. Everything between the opening curly brace ('{') and the closing curly brace ('}') is the method body. The method body contains the code that will be run when a function is called.
Let's write a function!
public static void hello() {
System.out.println("Hello World!");
}
public static void main(String args[]) {
hello();
}
We have two methods here. The hello and main methods. As stated earlier, code execution begins at the beginning of the main method. Other methods do not get called unless you or an object call them. How do you call them? You say their name, of course! To call a function, you write the name of the function you wish to call followed by parenthesis. If the function has any input, it will go inside the parenthesis. Like this:
public static void hello(String motd) {
System.out.println(motd);
}
public static void main(String args[]) {
hello("Hello World!");
}
In the main method, you pass the text string "Hello World!" by placing it within the parenthesis of the function call. In my teaching experience, this next part is a little confusing to people. Maybe I'm bad at explaining it, but it's one of those things few people I tutored caught quickly. Kind of like booleans.
The hello method takes in a string variable called motd as a parameter. When calling the method, the value of the first and only parameter becomes the value of the variable that is the first parameter of the method. So the value of motd becomes "Hello World!" in this example. It is equivalent to writing:
String motd = "Hello World!";
Now more magic I've been glossing over. That System.out.println? That's a method, too. It takes in a String object. In our example, we take the String motd and pass in to println as a method parameter. println is a part of the Java standard library, so we don't have to define it ourselves. We don't even have to include any extra libraries.
Remember that void keyword we skipped a few minutes ago? Methods are designed to perform some set of operations for you. If you want it to just do it and be done, like if we want it to print something, we use void. But what if we want a function to do something and then give us a result? We have to have a return value. In the method signature, we replace void with the data type we want to return.
public static void a() {
// do something...
}
public static int b() {
int result = 0;
// do something...
return result;
}
public static String c() {
String result;
// do something...
return result;
}
Here are three functions. a does not return anything. b returns an int. c returns a String. In order to do this, we simply use the return keyword. Now, it is very important to remember, return ends the function. void functions end when they reach the end of the function. You are allowed to have multiple return statements.
public static int translate(String aString) {
switch(aString) {
case "a": return 0;
case "b": return 1;
case "c": return 2;
default: return -1;
}
}
Do you remember how switch statements work? The switch statement looks at the variable you pass it as a parameter and searches it's cases for that value. If not found, it uses the default case. A minor aside; as of Java 7, you can switch on integers, enumerations, and strings. Do you see how there are four return statements? If a return ends the function, does this method end after the return 0? No. Because if you fail that case statement, you'll never actually hit that particular return statement. But as soon as you hit any of the return statements, it's over. return that value from the function. You can use return statements in void functions as well. It's a useful way to terminate the function early. For example:
public static void foo() {
if(aCondition) return;
// do something...
}
Look closely. We used return without actually returning anything. This works because the function expects us to return void, which as far as Java is concerned, should be nothing. Not Object. Not null. Not anything. In fact, trying is a compile-time error.
Let's go a little crazy with methods.
public static int add(int n1, int n2) {
int result = n1 + n2;
System.out.println(result);
return result;
}
public static int subtract(int n1, int n2) {
int result = n1 - n2;
System.out.println(result);
return result;
}
public static int multiply(int n1, int n2) {
int result = n1 * n2;
System.out.println(result);
return result;
}
public static int divide(int n1, int n2) {
int result = n1 / n2;
System.out.println(result);
return result;
}
public static int equation() {
int result = divide(multiply(add(5, 2), subtract(7, 3)), multiply(8, 2));
return result;
}
public static void basicMath() {
// I want to add 2 + 2
// I wish I had a function to add
// Oh wait, I DO
add(2, 2);
// I want to subtract 5 - 2
// I wish I had a function to subtract
// Oh wait, I DO
subtract(5, 2);
// I want to multiply 8 * 4
// I wish I had a function to multiply
// Oh wait, I DO
multiply(8, 4);
// I want to divide 9 / 3
// I wish I had a function to divide
// Oh wait, I DO
divide(9, 3);
}
public static void multiCalls() {
// 4 + 8 + 2 + 8 + 1 + 6 + 4 + 5
System.out.println(add(add(add(4, 8), add(2, 8)), add(add(1, 6), add(4, 5))));
// 6 + 4 - 4 * 8 / 2 + 4 / 9 - 4
// Note the integer division
// kekekekeke
System.out.println(subtract(add(subtract(add(6, 4), divide(multiply(4, 8), 2)), divide(4, 9)), 4));
}
public static void run() {
// I want a function that demonstrates
// basic mathematical operations
// using functions
// I wish I had a function to do that
// Oh wait, I DO
basicMath();
// I want a function that demonstrates
// calling functions multiple times
// I wish I had a function to do that
// Oh wait, I DO
multiCalls();
// I want to know (5 + 2) * (7 - 3) / (8 * 2)
// I wish I had a function to do that
// Oh wait, I DO
System.out.println(equation());
}
public static void main(String args[]) {
// I want the program to run
// I wish I had a function to run
// Oh wait, I DO
run();
}
Okay, that was a touch more than a little crazy. But it was FunTM! Because some of this is complicated, lets walk through it as painfully slowly as we possibly can.
Now, you can clearly see that there are a lot of functions. We wrote nine functions for this application! In Java, it turns out that it really does not matter what order you place the functions in, unlike other languages like C/C++. Does that make it better or worse? Not really. There's benefits and drawbacks. As far as order goes, I like to place the main method at the very bottom. This is for the sole purpose of making it easy to find. Sure, I could just use a search, but I hate wasting time. For stylistic purposes, there is an order I like to use. It will make more sense to discuss that in the objects lesson, however. Still, style is a matter of personal preference and professional requirements. If your employer says "Do it this way", you darn well better.
Remember that your code begins execution at the main method. Comments get ignored. As far as the compiler is concerned, there's only one line here. A function call. And what does it do? It moves us up into the run method.
The run method has four function calls. But wait, there's only three lines of actual code in there! Well, look closely at the third one. Do you see it? We embedded the equation method call inside of the println method call. But we're getting ahead of ourselves. The first function call steps us into the basicMath function...which contains four more function calls. All of which take two ints, perform an operation on them, then print and return the resultant int. Let's just briefly step into the add function. We get to the end of it. There are no more method calls for us to follow. Now what do we do? Now we step back out to where the function in question was called. At the end of that function, we step out to where basicMath was called and advance to the next function.
In multiCalls, for lack of a better name, we demonstrate that you can keep calling the same methods over and over again. The input parameters take the values of whatever was passed in at that particular method call. Furthermore, this demonstrates embedding functions. Look closely. The add function returns an int. Now we could just say int foo = add(2, 2). But because it returns an int, we can essentially treat it as though it is an int. This lets us pass it as a parameter to the next call to add. For example, add(add(2, 2), 2), adds (2 + 2) + 2. After seven calls to add, we print the result.
Let's walk through the method calls quickly. The first thing the compiler sees is the println. But it can't tell what we want it to print until it calculates all other method calls. The compiler next sees the far left add, but it cannot tell what it is adding until calculating other method calls. Next add, and the compiler still cannot tell what it is adding without calculating more calls. Finally, at the third add, the compiler has a function it can complete. At the end of that call, the call is, effectively, replaced with it's return value. The same is true with the next add. With those two methods complete, the compiler backs off to the second add and completes it. Then the compiler has to do it all over again for the last three adds. When they've all completed, the first add can complete and the end value can be printed.
Now, the next one is a bit complex. We can't just group the numbers into twos anymore because of order of operations. I'll be honest, this one took me a bit of thinking. Mostly because I would never, ever, actually do it this way.
Walking through this nightmare...you know what, let's just do an old fashion trace.
run println(subtract(add(subtract(add(6, 4), divide(multiply(4, 8), 2)), divide(4, 9)), 4))
run subtract(add(subtract(add(6, 4), divide(multiply(4, 8), 2)), divide(4, 9)), 4)
run add(subtract(add(6, 4), divide(multiply(4, 8), 2)), divide(4, 9))
run subtract(add(6, 4), divide(multiply(4, 8), 2))
run add(6, 4)
return add == (10)
run divide(multiply(4, 8), 2)
run multiply(4, 8)
return multiply == (32)
return divide == (32 / 2 = 16)
return subtract == (10 - 16 = -6)
run divide(4, 9)
return divide == (4 / 9 = 0)
return add == (-6 + 0 = -6)
return subtract == (-6 - 4 = -10)
return println(-10)
Woo! That was kind of hard. But if you don't understand what's happening in the code, tracing out what's happening can be a good, if somewhat slow, tool. You could also make it trace for you by putting print statements at the beginning of every function call, but that does not force you to walk through your code and understand it as intimately. Especially if you're using an IDE, you might also consider using the built-in debugger. Somewhere out there is a version of GDB that works for Java. You could use that as well; if you're coding on the command line. Step into every function you wrote and step over everything else. And watch the value of variables while you are doing that.
Do you understand embedding methods yet? If not, come up with some random mathematical formula that utilizes only the basic four operations and try to trace that out. You should be able to make a single line equation with function calls.
Moving on, we're still in the middle of the program. We step into the equation function. This is another, albeit much simpler, example embedding function calls inside other function calls. Let's just look at it quickly.
add(5, 2) = 7
subtract(7, 3) = 4
multiply(7, 4) = 28
multiply(8, 2) = 16
divide(28, 16) = 1
Okay, that was fast, but by now you should be able to understand how that method works. Be sure to ask if you're not following!
After printing the value of that function, the program ends. If you run the complete application, you should get the following output:
4
3
32
3
12
10
22
7
9
16
38
38
10
32
16
-6
0
-6
-10
-10
7
4
28
16
1
1
Not much to look at, but this leads to a good exercise. Trace through the code and work out where each of those values came from.
We talked about scoping, right? Anything declared within a set of curly braces stops existing outside of those braces. For purposes of scoping, input parameters are local to body of the method they are a part of. This is why we can keep using the variable names n1 and n2 for so many different unique variables.
We need a slight break, so let's talk technique for a minute. Look at the code. Do you see how nearly everything is a function call? And how the functions that are not calling other functions are just a few lines long? I wrote this using one of my favorite programming techniques for large projects. Wishful thinking. In short, every time you want your code to do something, you make up a function call. "I wish I had a function that does THIS. Oh wait, I DO." Eventually, you get to the end of your program and have a skeleton worked out. At the end, start filling in functions the same way. "I need a function that does THIS. Oh wait, HERE'S one!" At the end of the day, your code is a large number of function calls that eventually lead to a large number of functions that have only a few lines of code. Let's walk through the example.
I'm in the main. Well, it'd be nice if the main would run the application. Let's write a function that does that. End of main, end of application. Great! Let's start filling in our functions. What do I want run to do? Well, I want it to give examples of using function calls for basic mathematics for the purpose of demonstrating the basic manner of writing and calling functions. Then I want it to demonstrate the more advanced calling of the same function many times and embedding functions. Then I want one more simple embedded function example for good measure. Three things that I want run to do. Gee, I wish I had functions that did that. Wait a second, I DO! Okay, end of run. Step out to main. End of that. End of application. Time to fill in functions again. basicMath. I want it to add, subtract, multiply, and divide with functions; both to demonstrate calling functions and to demonstrate writing them. I wish I had functions that performed each of those operations. Wait, I DO! Step out. Step into multiCalls. I just want it to string together a bunch of adds and then show a complicated equation, using functions. I could, and with wishful thinking, probably should, make that two functions. I elect not to do that because I can actually do both with a single line. And not a stupid single line like in an earlier lesson where I made the entire program one line. This is a single line that, as an experienced programmer, I would write in any of my actual applications. So I'll let this function break the rules of Wishful Thinking and just call the mathematical operator functions. Step out. Step into equation. Not unlike the second example in multiCalls, this is a single line of mathematical operator functions strung together. Assign it to a variable and return it.
I want you to do something. It doesn't make sense right now, but don't question it for the moment. Just do it.
public Methods() {
}
public static void main(String args[]) {
new Methods();
}
Why are we doing this? To get us out of the main and the requirement that everything it touches be static. What we've just done is instantiated a Methods object, but objects are still a bit magical. We'll talk about them in the next lesson. If I ever finish this one. From here, just pretend that Methods is the main function for the time being.
This is actually something I do a lot when I'm coding Java applications. Frequently, if I don't have a driver class, I use the main object to contain the main method. Java requires that if you are in a static function, you can only call other static functions. This makes perfect sense once you understand the difference between static and non-static functions. Unfortunately, we're only going to touch on that. It will make more sense after we talk about objects.
We're now going to write a number of static methods. Wait, what? Yes, I want to show you how you would normally use static functions outside the main method.
public static int add(int n1, int n2) {
return n1 + n2;
}
public static int subtract(int n1, int n2) {
return n1 - n2;
}
public static int multiply(int n1, int n2) {
return n1 * n2;
}
public static int divide(int n1, int n2) {
return n1 / n2;
}
public Methods() {
Methods.add(1, 2);
Methods.subtract(3, 4);
Methods.multiply(5, 6);
Methods.divide(7, 8);
}
public static void main(String args[]) {
new Methods();
}
The static functions look a touch different than last time, but they actually work the same except they no longer print the result in the function. The purpose of static functions is be able to call functions that are not attached to a specific instance of an object. For instance, there's really no reason that the above functions need to be associated with an object. They can stand up completely on their own. We'll talk more on the differences when we talk about objects, but I wanted to at least touch on this here. In order to call a static function from a non-static function, you need to say the name of the class that contains the function, a dot, and then the function name as normal.
I can't believe I'm about to touch on this, but as long as we're here, lets briefly look at recursion, confuse everyone to all Hell, and then move on and talk about it more some other time. You know what they say about recursion.
"In order to understand recursion, you must first understand recursion."
public static int fibonacci(int n) {
if (n == 0)
return 0;
else if (n == 1)
return 1;
else
return fibonacci(n - 1) + fibonacci(n - 2);
}
public static void main(String[] args) {
for (int i = 0; i < 100; ++i) {
System.out.print(fibonacci(i) + " ");
}
}
Take a look at fibonacci. In the else statement, the method calls itself. Yes, this method is, in fact, calling itself. This is recursion. Anything you can do with loops, you can do with recursion. And vise versa. Usually one will be much cleaner than the other. There's a lot of problems with recursively generating fibonacci numbers, but we'll ignore those for now. If you are going to use recursion, you need to create what are called base cases. They are what halt the recursion, similar to a loop's continuation conditions. Remember how you can have an infinite loop? Well, you can infinitely recurse as well. Except when you infinitely recurse, your program crashes with a StackOverflowError. If you want to see that happen, run the following code. I'm not responsible for any damage this causes. In my eclipse, this instantly crashes with a StackOverflowError. Can't imagine why. </sarcasm>
public static int overflow(int n) {
return overflow(n) + overflow(n) + overflow(n) + overflow(n)
+ overflow(n) + overflow(n) + overflow(n) + overflow(n)
+ overflow(n) + overflow(n) + overflow(n) + overflow(n)
+ overflow(n) + overflow(n) + overflow(n) + overflow(n)
+ overflow(n) + overflow(n) + overflow(n) + overflow(n)
+ overflow(n) + overflow(n) + overflow(n) + overflow(n)
+ overflow(n) + overflow(n) + overflow(n) + overflow(n)
+ overflow(n) + overflow(n) + overflow(n) + overflow(n)
+ overflow(n) + overflow(n) + overflow(n) + overflow(n)
+ overflow(n) + overflow(n) + overflow(n) + overflow(n)
+ overflow(n) + overflow(n) + overflow(n) + overflow(n);
}
public static void main(String[] args) {
overflow(Integer.MAX_VALUE);
}
You have to watch your base cases. Usually these will be a few conditionals that stop you from recursing under certain circumstances. Then just make sure that your recursion is always approaching your base cases.
It really is a good idea to split your code up into methods. A single gigantic monolithic main tends to become unwieldy fairly quickly. It also tends to lead to a very large about of duplicate code, which is typically a bad thing. And there's other issues, but they will be easier to talk about after we discuss, you guessed it, objects.
* Somebody count how many times I wrote "function" when I was supposed to write "method". Can tell I've been coding C/C++ and Python for the last year-and-a-half.
PS
I really wanted to talk about objects in this lesson. But I just could not talk about objects without covering methods first. And methods became a relatively lengthy lesson. Then again, it's pretty much on par with my other lessons.
PPS
If anyone has any questions or comments, please feel free to express them.
PPPS
I think this might actually be one of my longest lessons.