Currently I’m working on this part of the tutorial and one other surprise page …

Classes and Objects

Classes are definitions that encapsulate data and behavior into a single bundle. The language runtime/virtual-machine uses those definitions to create Objects during the execution of the program.

  • Class is the description and implementation
  • Objects are the physical instantiation of the Class definition
  • Method is a function aware of Object variables/attributes
  • Attributes are variables belonging to a Class or an Object
    • Class variables are accessible by all the Objects instantiated from the Class
    • Object variables are accessible only to the object

Methods

Methods provide the behavior of a class. The format to create them is :

<access> <return type> <method-name>(<type> <arg>, ….) { ….. }

public String blah(String str, int arg2) { ….. }

First we specify the access/visibility descriptor, next is the type of the return value if the method returns one, followed by the name of the method and the arguments that it accepts.

Java have the capability to limit the visibility and access to attributes and methods. Here is list of them and a description of how they behave.

Visibilitynonepublicprivateprotected
in the same classyesyesyesyes
in the same packageyesyesnoyes
ancestor classnoyesnoyes
non-ancestor in another packagenoyesnono

Why complicate the language by adding those ?

The reason is to make coding harder and your life miserable …. I’m joking of course 😉 ….. or m’I ?

Many times there a legitimate case you would like to hide portions of your class, for example

  • having private attributes allows you to “channel” access to them via public methods, thus allowing you to do processes like validation, pre or post processing, before committing the change to the attribute.
  • hiding unfinished or changing methods
  • providing access only to the actual class clean API omitting/hiding the detailed implementation
  • and so on …

Lets now explore Classes by creating a Class that implement Binary Tree Structure.

Binary tree is a tree where every node has maximum of two children.

I selected this example because it is simple enough, will need only one class and also because we haven’t discussed data structures yet.

So as you’ve seen so far the definition starts with specifier public class, followed by the class name.
Then after the already familiar say(), we define three attributes.

  • the first one “name” will be publicly accessible and will store the name of the node.
  • then we declare as private references to the children of the node. They are both of a Node class. The way we build a Tree structure, is to create a Node that has a Nodes as a children. By making them private we make sure that only methods of the Node class can access them.

Because the nodes are not initialized and also can’t be accessed from outside we have to provide methods to do this with names .add_left() and .add_right(). Declaring them void means that the methods will not have a return value. If on the other hand the attributes were public we did not have to create those methods.

(will discuss the constructor in the next section)

We are pretty much done … what is left is to create a method to walk() the tree and print the node names.

We do that by using recursion. First thing always to remember about recursion is to figure what and where to put the limiting condition. In our case we flip the logic i.e. we “recurse”‘ only when the current node has a child. By default a node has no children so the walk will tend to end of the recursion.
Also check the formatting trick, on every call to walk() we append a space character thus the deeper the node is in the hierarchy the further to the right the node name will be printed. >>

public class Node {
  
  //shortcut print
  public static <ARG> void say(ARG arg) { System.out.println(arg); }

  public String name;
  private Node left;
  private Node right;
  
  // ** constructor **
  public Node(String name) {
    this.name = name;
  }
  
  public void add_left(Node node) { left = node; }
  public void add_right(Node node) { right = node; }
  
  public void walk(String prefix) {
    say(prefix + name);
    //calling left.walk, not the root.walk
    if (left != null) left.walk(prefix + " ");
    if (right != null) right.walk(prefix + " ");
  }
  
  public static void main(String[] args) {
    //create the root node
    Node root = new Node("root");
    //this time create them on the fly
    root.add_right(new Node("-node2"));
    //this one we want to extend, so we need the variable
    Node left = new Node("+node3");
    root.add_left(left);
    
    left.add_left(new Node("+node4"));
    left.add_right(new Node("-node5"));
        
    root.walk(" ");
  }
}

-----

Output:

 root
  +node3
   +node4
   -node5
  -node2

Finally we use the main() method to test the class.

A tree starts with a root-node, so that is what we will do. The format to create/instantiate an Object is :

ClassName variable = new ClassName(arguments)

Once we have the root node cerated we would add children nodes by building them on the fly.

After that lets call the .walk() method to print the tree.

1


-----
Output:

Constructors

Constructors are special methods in a class definition used to initialize an Object upon its creation.

In the class above we used :

  public Node(String name) {
    this.name = name;
  }

where this keyword is used to refer to the object itself. In general we do not need to use this, Java assumes it implicitly. But in this case where the constructor argument and object attribute have the same name we need a way to distinguish between them. In general it is good practice to use this as a visual cue, to make the code more readable.

One more thing to mention is that when you define a constructor you do not specify a return type, because the return type is always an object of the current class.

But we could have used instead :

  public Node(String name, Node left, Node right) {
    this.name = name;
    this.left = left;
    this.right = right;
  }

Which would have required to pass the children nodes on creation, but we can’t do that because they have to be created in advance which requires to pass children nodes … it seems this will need infinite recursion, but there is an escape patch, you could just pass null value instead.

But wait, there is a third option. What if we define both constructors. Yes, we can do that, it is called overloading.

overloading

When two methods have the same name but different arguments at compile time Java decides which is the correct method to call. I really like this feature.

The example below declares methods to add two numbers. By overloading the add() method and writing implementation for every type we keep the method name the same even for numbers disguised as Strings. Otherwise if overloading is not supported (f.e. Python) we had to write method with different name for every type add_int(), add_float()… and so on.

public class Overloading {
  public static <ARG> void say(ARG arg) { System.out.println(arg); }

  public int add(int a, int b) { return a + b; }
  // float instead of int
  public float add(float a, float b) { return a + b; }
  //single arg, instead of two
  public int add(int b) {return 5 + b; }
  // string like numbers
  public int add(String a, String b) {
    return Integer.parseInt(a) + Integer.parseInt(b); 
  }

  public static void main(String[] args) {
    overloading o = new overloading();
    say("Add 5 + 7 : " + o.add(5,7));
    say("Add 5.0f + 7.0f : " + o.add(5.0f,7.0f));
    say("Add 5.add(7) : " + o.add(7));
    say("Add '5' + '7' : " + o.add("5","7"));        
  }
}

-----
Output:

Add 5 + 7 : 12
Add 5.0f + 7.0f : 12.0
Add 5.add(7) : 12
Add '5' + '7' : 12

overriding

overriding is what it sounds. This is the case when in a subclass we write a method with the same name and arguments as it is in the parent.

What happens is the new method will be executed in place of the parent method, when a call is made.

Generics/Templates

Java allows you to treat the “type-signature” of methods and classes as “type-variables” and then use those in the definitions later. But it is easier to show, rather than explain. Below I wrote two Functions print() and say() which produce the same result but are implemented differently.

print() uses the overloading technique that we just discussed. The disadvantage of this approach is that we have to list every possible scenario. (Mind you that is probably how println() is implemented internally by Java).

Then we use Generics to implement say(). In brackets <ARG> we define the type-variable and then we can use it in all the places where we specify types. Later the compiler will fill the exact type based on the argument type.
As you see it is just one line and covers not only the three cases that print() covers, but also any other type supported by println(). The advantage is obvious.

Note: Using Generics with arithmetic operations does not work because in Java arithmetic’s is not implemented as operators.

public class Generics {
  // OVERRIDING
  public static void print(Integer arg) { System.out.println(arg); }
  public static void print(Float arg) { System.out.println(arg); }
  public static void print(String arg) { System.out.println(arg); }
  // GENERICS
  public static <ARG> void say(ARG arg) { System.out.println(arg); }

  public static void main(String[] args) {
    
    print("This is a string");
    print(555);
    print(55.678f);
    
    say("This is a string");
    say(555);
    say(55.678f);
  }
}

-----
This is a string
555
55.678
This is a string
555
55.678

Did you notice the static keyword in the declaration of the “Functions”. This is a non-access modifier. When it is used in definition of a method it allows us to call the method w/o creating the object first.

Data Structures

So far we worked with scalar values but complex problems requires richer data structures.

Arrays

Arrays hold elements of the same type and can have multiple dimensions. The main advantage is that you can access any element fast and directly by index. The disadvantage is that once declared you can not add new elements or resize the array.

Fist I demonstrate how to create an array. When defining an array add square brackets [] at the end of the type.

I also demonstrate how you can modify elements and loop over the array both with for and foreach loop.

public class Multi {
  
  public static <ARG> void say(ARG arg) { System.out.println(arg); }

  public static void main(String[] args) {

    //initialize array
    int[] ints = {1,2,3,4,5};
    String[] strs = {"one", "two", "three", "four", "five"};
    float[] floats = new float[5];
   
    //loop over the array 
    for (int i = 0; i < ints.length; i++) {
      say(ints[i] + " : " + strs[i]);
    }
    
    //change value by index
    ints[3] = 7;
    
    int sum = 0;
    //foreach loop to calculate the sum of all elements
    for (int el : ints) { sum += el; }
    
    say("Sum : " + sum);
  }
}

-----

1 : one
2 : two
3 : three
4 : four
5 : five
Sum : 18

An Array can have multiple dimensions. This allows you to represent matrices, tensors or data that have tabular structure.

As you may suspect you add as many [] as you need to tell the compiler how many dimensions do you want.

Initialization of Arrays is done in similar way, you just need more brackets.

If the arrays are the same type and size you can copy them as shown in the example below.

It’s a simple : we create 2D array, copy it in another one and then print it as 2D square.

public class MultiDim {
  
  public static <ARG> void say(ARG arg) { System.out.print(arg); }
  public static void nl() { System.out.println(""); }

  public static void main(String[] args) {

    //initialization
    int[][] ints = { {1,2,3},{4,5,6},{7,8,9} };
    int[][] multi = new int[3][3];
    
    //copy arrays
    multi = ints;
    
    //print the 2D array as a square
    for (int i = 0; i < multi.length; i++) {//loop rows first
        for (int j = 0; j < multi[i].length; j++) {
          say(multi[i][j] + " ");
        }
        nl();//new line after we print a full row
    }
  }
}

-----

1 2 3 
4 5 6 
7 8 9


Lists

Lists are dynamic sequential structure, where you can add or delete elements.

Java has 3 List types : ArrayList, Vector and LinkedList. We will only use ArrayList in the examples.

As the name implies this type has characteristics of both Array and List.

When you define ArrayList you must also specify the type of the elements it contains in angle brackets, like this new ArrayList<String>. Now you have the object but the List is still empty, so we will .add() some elements.
Then I show you how to .remove() one of them and some other common operations.

And very importantly at the end you can see how to convert between Array and ArrayList.

import java.util.*;

public class Lists {
  
  public static <ARG> void say(ARG arg) { System.out.println(arg); }

  public static void main(String[] args) {
    
    //create List of String's
    ArrayList<String> list=new ArrayList<String>();
    
    //populate ...
    list.add("one");
    list.add("two");
    list.add("abc");
    list.add("three");
    list.add("four");
    list.add("five");
    
    String abc = list.remove(2);
    say("Removed element : " + abc);
    
    say("Element No. 3 : " + list.get(3));
    
    say("Found 'five' at position : " + list.indexOf("five"));
    
    //loop over elements
    for (String num : list) { say(num); }
    
    //convert between Array <===> List
    String[] ary = list.toArray(new String[0]);
    List<String> list2 = Arrays.asList(ary);
  }
}

-----

Removed element : abc
Element No. 3 : four
Found 'five' at position : 4
one
two
three
four
five

We discussed how to create a BinaryTree, but now that we know how to use Generics and ArrayList we can extend it to support many children and also hold data.

If there was no Generics we had to create separate Node class for every data type we need.
But now we can define it as Node<T> where T behaves like variable, so in runtime we can instantiate providing the type we need and only have single class definition f.e. new Node<String>, new Node<Integer>

We keep the .name attribute and add .data attribute of type T

The children this time are stored in ArrayList<Node>, thus we can have multiple children. So now instead of .add_left() and .add_right() we have only one method .add_child()

Walking the tree is still recursive but this time the limit condition uses another ArrayList method to check if the children list .isEmpty().

Finally we create Tree with nodes of heterogeneous data and because we added data aware constructor we can create the node on the fly.

import java.util.*;

public class Node<T> {
  
  //shortcut print
  public static <ARG> void say(ARG arg) { System.out.println(arg); }

  public String name;
  public T data;// generics variable type
  public ArrayList<Node> children = new ArrayList<Node>();

  // base constructor  
  public Node(String name) {
    this.name = name;
  }
  
  // data aware constuctor
  public Node(String name, T data) {
    this.name = name;
    this.data = data;
  }
  
  public void add_child(Node node) { children.add(node); }
  
  public void walk(String prefix) {
    say(prefix + name + " : " + data);
    if (!children.isEmpty()) {
      for (Node child : children) { 
        child.walk(prefix + " ");
      }
    }
  }
  
  public static void main(String[] args) {
    //create the root node
    Node root = new Node<Integer>("root", 5);
    root.add_child(new Node<String>("node2", "first-child"));
    Node node = new Node<String>("node3", "snd-child");
    root.children.add(node);
    
    node.add_child(new Node<String[]>("node4", new String[]{"John","Doe"}));
    node.add_child(new Node("node5"));
        
    root.walk(" ");
  }
}

-----

root : 5
  node2 : first-child
  node3 : snd-child
   node4 : [Ljava.lang.String;@534df152
   node5 : null

sets

You can use sets if :

  • You want to store distinct elements without duplication i.e. unique elements.
  • You don’t care about the order of elements.

There three types of sets :

  • HashSet : Standard set only guarantees uniqueness
  • LinkedHashSet : In addition remembers the element insertion order
  • TreeSet : Order by the set values or other custom ordering via Comparator

1

import java.util.*;

public class Sets {
  
  //shortcut print
  public static <ARG> void say(ARG arg) { System.out.println(arg); }

  
  public static void main(String[] args) {
    //create and add elements
    HashSet<Integer> set = new HashSet<Integer>() {{
      add(5);add(7);add(15);
      add(5);add(7); //duplicates
    }};
    
    //... add more elements
    set.add(42);
    set.add(43);
    
    for (Integer num : set) {
      say(num);
    }
    
  }
}

-----

5
7
42
43
15

1


-----
Output:


Hashes

1


1


-----
Output: