Class Structure Introduction in Java

Class Structure Introduction in Java Featured Image

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

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 codeOpens in a new tab. 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:

  1. public: indicates the access modifier for the class (the same as methods)
  2. class: declares that the code block is a class
  3. HelloWorld: 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:

  1. Verifies the source code has the proper syntax
  2. Ensures that all the variable types line up
  3. 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 APIOpens in a new tab.. 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 subscribeOpens in a new tab.. Until next time!

Java Basics (10 Articles)—Series Navigation

The Java Basics series is a beginner friendly tutorial series which covers topics such as binary, logic, control flow, and loops. By the end of the series, students should feel confident enough to write a simple application. In addition, students can head straight into the data structures series.

As with any series, I’m always looking for feedback. If you felt like something was missing or everything was excellent, let me know in the comments below each lesson. As always, if there’s anything else you want to see, I’m happy to write an article about it.

Jeremy Grifski

Jeremy grew up in a small town where he enjoyed playing soccer and video games, practicing taekwondo, and trading Pokémon cards. Once out of the nest, he pursued a Bachelors in Computer Engineering with a minor in Game Design. After college, he spent about two years writing software for a major engineering company. Then, he earned a master's in Computer Science and Engineering. Today, he pursues a PhD in Engineering Education in order to ultimately land a teaching gig. In his spare time, Jeremy enjoys spending time with his wife and kid, playing Overwatch 2, Lethal Company, and Baldur's Gate 3, reading manga, watching Penguins hockey, and traveling the world.

Recent Posts