Learn Java Fast: Primer

0

Overview

This primer will introduce the fundamental concepts of the Java programming language. This is just a primer, but it will familiarize you with the concepts needed to follow along with the rest of the tutorials in this series. Let’s get started!

What Will We Cover?

  • Data types
  • Data structures
  • Classes/Objects
  • Constructors
  • Methods
  • Conditionals
  • Loops

These are the base concepts required to perform any type of Object Oriented Programming (OOP).

Data Types

In programming you create variables that store data. This data can be manipulated, displayed, and even saved for later use. Not all data is considered the same by a computer and so different “types” of variables must exist. These variable types are referred to as “data types.” A simple example of why this is necessary can be expressed in the concept of calculations. If I have five apples and add five more apples, how many total apples do I now have? Ten, of course, but how does that demonstrate anything important? You see, what your brain did there is convert the String type of “five” into the Integer type 5 so that you could add them together. Literally adding the two Strings of “five” would produce fivefive which is not a numeric value. We mentally convert the String five into the integer 5 so that we can add them to produce 10. This is, essentially, the reason that different data types are required in programming. Even the number 5 is not a real integer if it is contained within a String data type. Here are some examples:

        // Declare the String variables
        String five;
        String anotherFive;
        
        // Assign text values to the Strings
        five = "five";
        anotherFive = "five";
        System.out.println(five + anotherFive);
        
        // Assign number values to the Strings
        five = "5";
        anotherFive = "5";
        System.out.println(five+ anotherFive);
        
        // Declare the Integer variables
        int realFive;
        int anotherRealFive;
        
        // Assign integer values to the Integers
        realFive = 5;
        anotherRealFive = 5;
        System.out.println(realFive + anotherRealFive);

Which produces the following output:

fivefive
55
10

As you can see, the only operation that produced an actual sum was the last one which added the two variables declared as int. The String values of five and five become fivefive and the String values of 5 and 5 become 55. When String values are added they are joined together. This joining of Strings is called concatenation. Only the integer values of 5 and 5 become 10 when added together. Strings are defined by “quotation marks” and anything within those quotations is a String. When you add two Strings together they concatenate. When you add two integers together you get a sum.

There are even different data types for different numbers. Signed numbers in the integer {…-3,-2-1,0,1,2,3…} can be held in the int data type. Rational numbers, like those with decimal places, require a floating point data type. A double data type is simply a floating point with double the precision and is generally preferred to float.

        // Double data type
        double myDecimal = 1.34; // This works
        int testDecimal = 1.34; // This fails and throws an incompatible types exception

This should give you a basic understanding of data types and why they are important, but is not meant to be an exhaustive explanation.

Data Structures

Data structures are used to store multiple variables in a single reference. Types of data structures include arrays, linked lists, hashmaps, and many other variations. You can store any data type or object in a data structure once properly defined. Below are some simple examples of commonly used data structures.

// Create an String array of names
String namesArray[] = {"Joseph", "Susan", "John", "Megan"};

// Find 'John' in the array. O(n) time.
for (String name : namesArray) {
    if ( (name.equals("John") ) {
        System.out.println("Found John!");
    }
}

// Create a linked list of type String and fill with the same names
LinkedList<String> listOfNames = new LinkedList<>();
listOfNames.add("Joseph");
listOfNames.add("Susan");
listOfNames.add("John");
listOfNames.add("Megan");

// Check if the list contains John. O(n) time
if ( listOfNames.contains("John") ) {
    System.out.println("List contains John!");
}

// Create a hashmap with a String key and String value
HashMap<String, String> namesAndNumbers = new HashMap<>();
namesAndNumbers.put("Joseph", "555-555-4321");
namesAndNumbers.put("Susan", "555-555-1234");
namesAndNumbers.put("John", "555-555-2143");
namesAndNumbers.put("Megan", "555-555-4132");

// Find John's number. O(1) time
System.out.println(namesAndNumbers.get("John"));

There are some significant differences between these different data structures that will be address as they come up, but some important things to note are that arrays are of a fixed size whereas linked lists are dynamically sized. Once you create an array, you cannot add any additional elements. A linked list has the benefit of being able to add elements after it has been created which makes it useful when you do not know how many elements you will need.

Why wouldn’t you just use a linked list every time then? Arrays take less memory and have a faster linear search than a linked list. With an array, all of the elements are located adjacent to one another in physical memory. The elements in a linked list, however, can be located in different locations within the physical memory. Because linked lists do not have to have the elements located adjacently they require knowledge of the location of the previous and next elements (aka nodes) which requires additional memory usage. There is also a difference in search, insertion, and deletion speeds which is a part of Big O time complexity.

Hashmaps have a key/value relationship and thus have a faster lookup time than arrays. Arrays and linked lists need to traverse each element in order to find a desired value which is linear O(n) time. In contrast, if you know the desired key in a hashmap the value can be accessed directly with constant O(1) time because it does not need to traverse all of the elements; it uses the key as an index to go straight to the desired value. If you are dealing with tens of thousands of elements, this can be a huge savings in run-time.

We will be working with data structures in the next lesson.

Classes/Objects

What’s so great about Objects?

Well, I’m glad you asked. Objects are extremely useful in creating the structure of a program. They can contain functionality for specific purposes, which allows you to keep your application’s architecture organized. Grouping like functionality within a class can be thought of as a “component” within your application. This helps when you want to make changes to particular functionality or you are looking to correct a bug within your application. For example, if you have an application that requires users to login you can create an Authentication class which contains all the methods and functionality associated with the login process. Any time you need the user to login you can use this same class instead of rewriting all of that code. If you discover a bug in the login process you know exactly where to look to fix it and only have to update it in one file.

Another excellent usage of objects is to create your own data types. What if you wanted to hold multiple different types of information in a variable, like for a contacts application? You could create a class to hold relevant information like a person’s name, address, phone number, and even age. This could be useful for storing customer information or even patient data within a medical application.

There are a few key concepts to understand in regards to objects which will be demonstrated below.

// Define the class
public class MyObject {
    // Pre-defined instance variables
    private int hiddenInteger = 5;
    private String hiddenString = "My hidden String";
    // Undefined instance variables
    private int undefinedInteger;
    private String undefinedString;

    // Constructor
    public MyObject(int undefinedInteger, String undefinedString) {
        this.undefinedInteger = undefinedInteger;
        this.undefinedString = undefinedString;
    }

    // Getters &amp; Setters
    public int getHiddenInteger() {
        return hiddenInteger;
    }

    public void setHiddenInteger(int hiddenInteger) {
        this.hiddenInteger = hiddenInteger;
    }

    public String getHiddenString() {
        return hiddenString;
    }

    public void setHiddenString(String hiddenString) {
        this.hiddenString = hiddenString;
    }

    public int getUndefinedInteger() {
        return undefinedInteger;
    }

    public void setUndefinedInteger(int undefinedInteger) {
        this.undefinedInteger = undefinedInteger;
    }

    public String getUndefinedString() {
        return undefinedString;
    }

    public void setUndefinedString(String undefinedString) {
        this.undefinedString = undefinedString;
    }
}

There are a few things to note in the above code. In lines 3-8 the instance variables for the object are defined. This means that when the object is instantiated, these variables will be unique to that particular instance. If you have two instances, MyObject1 and MyObject2, changing the value of hiddenString in MyObject2 will not affect MyObject1 at all. The instance variables are also declared with an access specifier of “private.” This means that these variables cannot be directly modified outside of the class itself. Instead, there we have created public “getter and setter” methods that allow us to get and set these values. Keeping the instance variables private in this way is known as data-hiding or encapsulation and is best practice when defining your classes. Nothing should be public unless it is absolutely necessary.

Constructor

The next thing that should be observed is the constructor method below on line 11. In Java the constructor method is a public method with the same name as the class, but no return type. The constructor method is automatically called when you create an object. If the constructor is not defined a default empty constructor is called automatically. If you look at lines 7-8 you can see that these instance variables have not been assigned values. The constructor can take arguments to be used during the instantiation of objects. In lines 12-13 the values passed by the constructor are assigned the instance variables of the object. The “this” keyword identifies the following variable as the instance variable belonging to “this” object and separates it from the the variables being passed by the constructor parameters.

        MyObject myObject1 = new MyObject(7, "Seven");
        MyObject myObject2 = new MyObject(2, "Two");

        System.out.println("myObject1: " + myObject1.getUndefinedInteger());
        System.out.println("myObject1: " + myObject1.getUndefinedString());
        System.out.println("myObject2: " + myObject2.getUndefinedInteger());
        System.out.println("myObject2: " + myObject2.getUndefinedString());
        
        System.out.println("myObject1: " + myObject1.getHiddenString());
        System.out.println("myObject2: " + myObject2.getHiddenString());
        
        myObject2.setHiddenString("Hidden string changed");
        
        System.out.println("myObject1: " + myObject1.getHiddenString());
        System.out.println("myObject2: " + myObject2.getHiddenString());

Output:
myObject1: 7
myObject1: Seven
myObject2: 2
myObject2: Two
myObject1: My hidden String
myObject2: My hidden String
myObject1: My hidden String
myObject2: Hidden string changed

As you can see the constructors were given values for the two required arguments. These values were stored in the objects when they were created on lines 1-2. Lines 4-7 print the values that were assigned to the objects by the constructors. In our object there were also variables that were predefined. The predefined value for the hiddenString variables of each object are printed in lines 9-10. The hiddenString variable for myObject2 is then changed on line 12. Then Lines 14-15 print the hiddenString values for each of the objects and show that only myObject2 was modified. This is what is meant by instance variables, they are specific to each instance of an object.

Methods

A method is simply a function that belongs to a class. In the previous examples we have getter and setter methods that are used manipulate the private instance variables within the class definition. Methods have a signature that is made up of the access specifier, return type, function name, and input parameters.

public String getHiddenString()

The above method signature is a publicly accessible method that returns a String data type.

public void setHiddenString(String hiddenString)

The above method signature is a publicly accessible method that returns nothing (void) and takes a String as its input.

Conditionals

One of the most common practices in programming is performing an action based on a comparison. These comparisons are known as conditionals. If statements are probably the most commonly known conditional statements. Switch statements are another and conditionals are also used with loops. First let us look at If and Switch statements.

        // If 5 is greater than 1
        System.out.println("Is 5 greater than 1?");
        if (5 > 1) {
            System.out.println("True");
        } else {
            System.out.println("False");
        }
        
        // If 5 is less than 1
        System.out.println("Is 5 less than 1");
        if (5 > 1) {
            System.out.println("True");
        } else {
            System.out.println("False");
        }
        
        // If 5 does not equal 1
        System.out.println("If 5 is not equal to 1");
        if (5 != 1) {
            System.out.println("True");
        } else {
            System.out.println("False");
        }
        
        int a = 5;
        // If a == 1 Else If a equals 5
        System.out.println("If a is equal to 1 else if a is equal to 5");
        if (a == 1) {
            System.out.println("a is equal to 1");
        } else if (a == 5) {
            System.out.println("a equals 5");
        }
        
        // Switch on varible 'a' possible cases
        System.out.println("Switch cases for 'a'");
        switch (a) {
            case 1: // in case a equals 1
                System.out.println("the case is that 'a' equals 1");
                break;
            case 2: // in case a equals 2
                System.out.println("the case is that 'a' equals 2");
                break;
            case 5: // in case a equals 5
                System.out.println("the case is that 'a' equals 5");
                break;
        }

Output:
Is 5 greater than 1?
True
Is 5 less than 1?
False
If 5 is not equal to 1?
True
If a is equal to 1 else if a is equal to 5
a equals 5
Switch cases for ‘a’
the case is that ‘a’ equals 5

As you can see by the above output, each of these conditional statements perform a specific action based on the outcome of the conditional comparisons. These conditional comparisons are also used in loops as will be demonstrated in the next section.

Loops

There are many different types of loops including While, Do While, For, Foreach, etc… The purpose of this section is not to explain in detail all of the different types of loops, but rather give a demonstration the concepts.

While Loop

        int i = 0;
        while (i < 100) {
            i++;
        }
        System.out.println("i now equals " + i);

Output:
i now equals 100

We have defined i as being equal to 0 and then created a while loop. The conditional in the while loop is the part in the parenthesis (i < 100), which means that the body of the loop, which is the part between the { } braces will be repeated until i is no longer less than 100. The same thing can be accomplished using a for loop as show below.

For Loop

        int j = 0;
        for (int i = 0; i < 101; i++) {
            j = i;
        }
        System.out.println("j is now " + j);

Output:
j is now 100

The for loop defines the counter variable, then creates the loop condition (i < 101) and then increments the counter variable. The loop will execute the body of the loop between the { } braces until the conditional is satisfied. Notice that in the while loop our conditional statement was i < 100, but in this for loop our conditional is i < 101. The reason for this is because the for loop executes the increment after the loop body executes. This means that once i is no longer less than 101 the loop will no longer run. This means that the next iteration of the loop will test the conditional and see that i is no longer less than 101 and the loop will exit before performing the j = i statement. This means that if we used the same conditional as the while loop (i < 100), j would be assigned 99 instead of 100.

Conclusion

This has been an extremely abbreviated primer for the Java programming language. This should give you enough prerequisite coverage to complete the Learn Java Fast tutorial. If you need more details of the specifics on the language or topics covered, look for related articles on this site, see the Java Docs from Oracle, or comment below.

Share.

About Author

James Cathcart has a Bachelors of Science for Software Application Programming and is currently pursuing his Masters of Science for Cyber-Security Engineering. He became a computer enthusiast before his teenage years and is passionate about software development and security.

Leave A Reply