At long last, we have finally reached the point where we can start writing our own code. To begin, we are going to learn class structure by implementing a couple of programs: Hello World in Java and a two-dimensional point class.
Table of Contents
- Concepts
- Hello World Class Structure Example
- How to Run Hello World
- Point2D Class Structure Example
- Extra Practice
Concepts
Before we do anything, let’s talk theory.
Scope
When we introduced methods, we talked about how instance methods could use external variables to accomplish a task. At the time, we avoided this notion of classes by using the interactions pane and Strings. Now that we plan to tackle an actual class, it might help to understand scope a bit.
Scope defines a region where a variable can be accessed. For instance, a class can contain variables much like a method. These variables are called fields, and they can be accessed by any method within the class.
Sometimes fields are called global variables because their scope encompasses the entire class. Likewise, a local variable inside a method is only available inside that method.
If we think back to the lesson on stacks, scope starts to make a lot of sense. Once a method is complete, it is popped off the stack. All local variables associated with that method are lost as well.
Scope will have some pretty interesting consequences as we move forward with control flow and loops. For now, scope has one very important feature: it allows us to have multiple variables of the same name.
As we will see later, a local variable can share a name with a global variable. The way we differentiate the two is with a special keyword called this
. The this
keyword lets us modify the global variable while a local variable shares the same name.
Much like method overloading, this
allows us to maintain clean code. However, this
can sometimes be confusing and should only be used in certain scenarios.
Object Initialization
Now that we’re comfortable with scope, we should shed some light on how object initialization is actually performed.
When we first learned about objects, we experimented with Strings. At the time, Strings offered a nice transition from the primitive types, and they served as a great introduction to methods.
However, Strings are bad examples of objects because they borrow the primitive type syntax for variable initialization. For instance:
int batCount = 7; String batName = "Wayne";
In this example, we see that we can create a String object in the same way we can create an integer. The syntax is type name = value
.
Meanwhile, objects are typically defined using the new
operator. For example:
String batName = new String("Wayne");
In this snippet, we have created the same string variable using a slightly different syntax. However, for Strings this syntax can become quite cumbersome.
For instance, let’s say we wanted to print a detailed error message—a sentence should suffice. The first syntax allows us to shave off some of the boilerplate code to make the line more readable. While the second syntax is completely valid, we are likely to only see the shorthand syntax in real source code.
Let’s back up a second. The new
operator that we just mentioned is exactly how every object will be initialized moving forward. In fact, we will shortly go through an example class where we can create an instance of a point using the following syntax:
Point2D point = new Point2D(5, 7);
Now that we’ve covered the basics, let’s dive right into it!
Garbage Collection
In addition to scope and object initialization, we should briefly cover garbage collection. When we declare a new object, a reference to that object gets placed on the stack. The object itself is placed in a different memory location called the heap.
As long as the object is within scope, we are free to manipulate the object as needed through its reference. Once we are finished with the object, the Garbage Collector destroys it.
At that point, we can no longer access our object. However, as long as we maintain at least one reference to the object, the Garbage Collector will leave our object alone.
Garbage collection is an excellent perk of Java development because it allows us to focus on concepts and algorithms rather than implementation details. However, not all languages come equipped with a Garbage Collector.
In C++, objects must be cleaned up manually. That means that the object must be deleted through code before the last reference to the object goes out of scope. Otherwise, the object continues to hold space in memory—also known as a memory leak and here’s an example of what they can do.
Over time, memory leaks can result in a reduction of the overall memory supply. In the worst case, the memory leak can cause the program to slow down and ultimately freeze or crash.
Overloading
In a previous tutorial, we talked briefly about method overloading which allowed us to have multiple methods with the same name but different sets of parameters. As it turns out, overloading extends beyond methods. In fact, it’s something we can even do with constructors—more on those later.
Hello World Class Structure Example
If you are not familiar with Hello World, it usually involves printing the phrase “Hello, World!” to the console. It is widely used as a way to introduce a language because it is simple to implement. In fact, I’ve launched a whole series titled Hello World in Every Language where you can see many examples of this program in action.
In languages like C and Python, we can accomplish Hello World in just a couple of lines. However, in Java it requires quite a bit of basic knowledge. Had we chosen to introduce Java in this way, we would have probably scared off a lot of new developers. But at any rate, let’s get started!
In Java, Hello World requires basic knowledge of class structure, the main method, static methods, arrays, and strings. The following code snippet is the Java implementation of Hello World. See if you can figure out how it works based on what you already know.
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } }
Now that we’ve seen the program, let’s dissect it a bit.
The Print Statement
Starting from the innermost scope, we’ll notice the following line of code:
System.out.println("Hello, World!");
In this line, we have a method called println
which takes a String in as input. We can probably imagine that println
prints a line using the input.
For the purposes of this lesson, System.out
retrieves the console for printing. Therefore, line 3 must print our expected string to the console.
The Main Method
Next, let’s take a look at the method wrapping our print statement:
public static void main(String[] args) { // ... }
In Java, the main method serves as the entry point to a program. We can specify all the behavior of a class, but it won’t accomplish anything until we include a main method. Otherwise, the program will throw an error at run-time.
If we look carefully, we’ll notice that the main method is static which means the method belongs to the class and not an instance of the class. In other words, we can run this program using HelloWorld.main(…)
. In the parentheses, we would specify an input that matches the expected type.
We might think we could pass a String
in as input, but we can’t. That’s because the input is expecting an array of strings (note the brackets, []
). We won’t worry about arrays for now.
Instead, let’s revisit this idea of calling the main method using a normal static method call. Typically, we can’t run a program like this outside of DrJava. That’s because DrJava’s interactions pane is essentially a living main method.
As we add lines to the interactions pane, the interactions pane picks those lines up and runs them. This is super convenient for learning, but it hides how Java actually works.
In reality, an executable Java program must contain one and only one main method. This tells the Java Virtual Machine where to start executing code.
The Class Declaration
The last piece of information we need to make our own class is the outermost block:
public class HelloWorld { // ... }
Notice how it wraps everything in the class with two brackets and declares the class as follows:
public
: indicates the access modifier for the class (the same as methods)class
: declares that the code block is a classHelloWorld
: specifies the name of the class
This class structure is exactly how we would make our own class. In fact, to prove this, we’ll try making our own Point
class below.
As a small note on style, be aware of indentation, semicolons, parentheses, and braces. Note their location and format. Java will allow us to write an entire program in one line, but that is not helpful to us or anyone who works with our code. In the future, we will discuss style more deeply. For now, follow the code snippets as a guide.
How to Run Hello World
Back before we had nice IDEs to write and run code, we had text editors and command line interfaces. The nice thing about a command line interface is that it typically gives us a better idea of how our code is run. For instance, we can run our HelloWorld.java file using the following command line syntax:
javac HelloWorld.java java HelloWorld
Using a command line interface, we can navigate to the directory containing our code. Then, we can run the chunk of code above. If all goes well, the console should print “Hello, World!”.
Compilation
It may seem a little weird that it takes two commands to execute our program. As it turns out, there’s an intermediate stage between coding and execution called compilation:
javac HelloWorld.java
Compilation is the act of converting source code to something more useful to the computer. For Java in particular, compilation is the act of converting source code to bytecode which can then be fed to the Java Virtual Machine for execution.
How compilation works is a bit out of the scope of this lesson, but here are some of the basic steps.
The compiler:
- Verifies the source code has the proper syntax
- Ensures that all the variable types line up
- Converts the source code to bytecode
Of course, the steps are more complicated than that. For instance, the compiler will often perform some optimizations to make the program run faster or use less space. Again, all of this is a bit out of the scope of this lesson.
Execution
Most IDEs like DrJava mask the compilation and execution commands with buttons. However, even at the command line level, compilation and execution is pretty abstract.
In reality, compilation generates a file, HelloWorld
, which contains Java bytecode. Bytecode is an intermediate representation of the original code. As a result, it is much closer to the processor, but it does not have information about the processor’s architecture.
Once we’ve finished compiling, we can execute the solution:
java HelloWorld
At this point, the JVM is responsible for converting the bytecode to the processor-specific binary on-the-fly.
If we recall our first lesson, we stated that the power of Java is in its portability. It’s the truth. Compile a program once, and we can run it almost anywhere.
Using an IDE
If you are using DrJava still, go ahead and copy the source code for Hello World into the window above the interactions pane. This window is the code editor.
Navigate to the upper right portion of the window and select compile. Once compiled, we can simply hit run to execute our first program.
If you have already migrated to another IDE like Eclipse or VS Code, you’re on your own. If you’re already using Eclipse, I might suggest migrating to VS Code at this point. It’ll serve you better in the long run.
Now that we know how to run the program, let’s move on to another example.
Point2D Class Structure Example
With Hello World out of the way, let’s try writing something a little more complicated. In fact, we’re going to create a class to model a two-dimensional point:
/** * The Point2D class represents a two dimensional point. */ public class Point2D { // The x value of the point private double x; // The y value of the point private double y; /** * The class constructor. */ public Point2D(double x, double y) { this.x = x; this.y = y; } /** * Retreives the x value of this point. */ public double getX() { return x; } /** * Retrieves the y value of this point. */ public double getY() { return y; } }
Take a moment to look over this code. Based on what we’ve covered so far, we should be able to recognize the class syntax. Inside the class, we should also be able to point out the two methods: getX()
and getY()
.
We can probably even figure out what those methods do just by peeking at the comments. However, there is one section of code that should appear a little new to us. That section of code is called the constructor.
Constructors
In object-oriented languages, objects are created using a constructor. A constructor is a special method that shares the name of the class but lacks the return type keyword.
Typically, constructors are used to assign some initial state to an object. For instance, our Point2D
example has a constructor that takes in both coordinates and assigns them to our new Point2D
instance:
public Point2D(double x, double y) { this.x = x; this.y = y; }
We can test this out by creating our own Point2D
object just like before:
Point2D p1 = new Point2D(5, 7);
This creates a point where the x-coordinate is 5 and the y-coordinate is 7. To prove it, we can check the values of the x
and y
fields—we’ll get to those shortly.
For an extra challenge, try adding more behaviors to Point2D
much like we did with the HelloWorld.java file. For instance, it might make sense to add an instance method to Point2D
which computes the distance between itself and another point:
p1.distanceTo(p2);
If you’re having trouble, don’t be afraid to use the comments below to ask questions.
Fields
Above, we briefly mentioned that the constructor stores its inputs in the class fields, but what are fields exactly?
A field is a member of a class much like a local variable is a member of a method. In terms of Point2D
, we have two main fields:
private double x; private double y;
These fields serve as the coordinates for the class, and we interact with these fields through instance methods.
When a Point2D
is created using the constructor, these two fields are populated. Then, we can access these fields using the getX()
and getY()
methods. These methods are called getters—also known as accessors—and we’ll cover them next.
Getters
Now that we’re familiar with constructors and fields, let’s talk getters.
As long as fields are public
, we can access them directly. For example, let’s say we wanted to know the value of the x-coordinate of the following point:
Point2D point = new Point2D(1, 2);
We could try calling point.x
where x is the name of the x-coordinate field. However, this fails because x
is a private
field. In other words, we have chosen to hide x
for the sake of encapsulation (more on this in the future).
Instead, we can use the public
getter method, getX()
, which allows us to indirectly access the private
x
field. This is the normal way of exposing a field for reading to a user. We call these types of methods getters.
Setters
Being able to read a private field is nice, but sometimes we’d like to change a private field. To do so, we use what’s called a setter.
Currently, our Point2D
instances are read-only which means we have no mechanism for changing the coordinates. As a result, we’re stuck creating a new point every time we want new coordinates.
Alternatively, we could create what is known as a public setter method to expose one of the coordinates for writing. We might do so using the following method:
public void setX(int x) { this.x = x; }
Setters introduce quite a bit of new syntax. First off, we’ll notice the return type is something we haven’t looked at yet. Our setX()
method seems to return void
which actually means we don’t return anything. This is pretty typical for methods that don’t need to return anything.
Next we’ll notice that the method takes an integer input. If we want to set our x-coordinate, we’re going to need a value from the user.
Unfortunately, the confusing bit is what is happening in the method body: this.x = x
. We seem to be storing x
in a variable called this.x
. If we recall, the this
keyword refers to an instance of itself. In other words, this
refers to the current instance of the point. Here we are able to set the point’s x value to the value of the x input.
We could have made the setter a bit more explicit had we written the method as follows:
public void setX(int inputX) { x = inputX; }
Here we can clearly see that our point’s x value is being overwritten by the input parameter. Regardless, both methods accomplish the same task.
For clarity, it may be better to use option two. However, most constructors and setters will use option one. Many IDEs will handle this for us, so we won’t have to worry too much about it.
Extra Practice
Now, we have two complete classes created: HelloWorld
and Point2D
.
Since Point2D
can’t be run on its own, try creating a main method which modifies a few points with getters and setters. Once you’re comfortable with that, you’ll have a pretty solid understanding of classes on the whole.
Unfortunately, we have only covered the very surface of classes at this point. At some point, we’ll need to cover class hierarchies, but we’ll be able to navigate quite a bit of code regardless.
With this lesson on classes in the books, why don’t we had back to the Strings API. We should now be much more equipped to read through the documentation. Take a look at each section such as fields, constructors, and methods.
In addition, make note of the extensive use of overloading both for the constructors as well as the methods. Don’t forget to check for the handful of static methods as well.
Get comfortable with the layout of the API. In the future, we will be referencing various Java libraries in addition to Strings. Once we get to loops, we will likely start using the StringBuilder
library.
Up next, we will start to tackle control flow. In the meantime, thanks for learning Java with The Renegade Coder. As always, feel free to reach out either through email or the comments below. If you want to stay up to date with the latest tutorials, please subscribe. Until next time!
Recent Posts
Recently, I was thinking about the old Pavlov's dog story and how we hardly treat our students any different. While reflecting on this idea, I decided to write the whole thing up for others to read....
In the world of programming languages, expressions are an interesting concept that folks tend to implicitly understand but might not be able to define. As a result, I figured I'd take a crack at...