Classes and Objects

In addition to primitive types, Java supports objects. In Java classes are organized into packages (each class belongs to exactly). The purpose of packages is to provide namespaces (you can defined two classes with the same name as long as they belong to different packages) and to provide a means to organize your code. Packages are organized hierarchically and elements in the hierarchy are separated by .. The rules what constitutes a valid package name are the same as for class, variable, field, and method names: Naming Rules. Most importantly, identifiers start with an letter followed by a sequenced of numbers and letter. A package name element cannot be the same as any identifiers defined in the language. It is idomatic to only use lower case letters in package names and to ensure worldwide uniqueness of package names by prefixing them with a reverse internet domain name. For instance, packages from www.iit.edu should be prefixed with edu.iit..

  • my_package.my_subpackage (OK)
  • MyPackage.MyOtherPackage (OK, but does not following naming conventions)
  • if (NOT OK, because if is a keyword in Java)
  • 1MyPackage (NOT OK, starts with a number)
  • My1Package (OK, only the first character has to be a letter)
  • Äüö (OK, but maybe not recommendable to use non ASCII letters)

Java defines rules for how you have to organize your source code into directories and files. The source code of a class A has to be stored in a file called A.java. This file has to be stored in a directory structure that reflects the hierarchy of the name of the package a class belongs to. For example, if A belongs to package edu.iit then Java expects the directory structure to be:

/edu/iit/A.java

The file containing the source code of a class has to have the following structure (here ? denotes optional elements:

<package_declaration>
<imports>?

<modifiers> <class_name> <extends>? <implements>? {

}

The package declaration determines what package the class belongs to. It is the form package <package_name>;. For instance, if we define a class in package edu.iit. the package declaration is:

package edu.iit;
  • The <imports> section allows you to import classes from other packages that are then available for use withing the class we are defining. More about that later below.

  • The <modifiers> determine the visibility of a class and other characteristics (more below).

  • The optional <extends> determines the super class of the class we are defining (more about that when we talk about inheritance below.

  • The optional <implements> part specifies which interfaces the class implements. We will dedicate a separate part of the course to interfaces.

In [3]:
package lecture;

public class MyFirstClass {
    
    public static void main(String[] args) {
        System.out.println("hello world I'm here!");
    }

}
Out[3]:
lecture.MyFirstClass
In [4]:
import lecture.MyFirstClass;
MyFirstClass.main(null);
hello world I'm here!
Out[4]:
null

Fields

The fields of a class are used to store data. Fields can be of a primitive type or reference type. Fields are declared as part of the class body (within the curly brackets). For instance, below we define a class A that has two fields of type int. Class fields that are not expicilty initialized are initialized to a default value (see lecture on primitive types).

public class A {
    public int a;
    public int b;   
}
In [5]:
package lecture;

public class A {
    public int a;
    public int b;
}
Out[5]:
lecture.A
In [7]:
import lecture.A;

A x = new A();
x.a = 1;
x.b = 2;

A y = new A();
y.a = 3;
y.b = 4;

return String.format("x is { %d, %d }\n\ny is { %d, %d }", x.a, x.b, y.a, y.b);
Out[7]:
x is { 1, 2 }

y is { 3, 4 }

Now let us consider a class with a field that is of a reference type.

In [8]:
package lecture;

public class B {
    public A a = new A(); // create a new A object
    public int c = -1; // initialize to -1
}
Out[8]:
lecture.B
In [9]:
import lecture.B;

B x = new B();
x.a.a = 3; // access the int a of the field of type A
return x.a.a;
Out[9]:
3

Visibility

In Java fields and classes can be assigned one of four visibilities: public, protected, package (default), and private. A public field can be accessed from anywhere, a protected field can only be accessed from code within the same package as the class and from within subclasses, package from within the package, and private fields can only be accessed from within the class itself. Trying to access a field from outside of its visibility scope results in a compile time error.

In [25]:
package lecture;

public class SecretA {
    private int a;
    private int b;
    
    public int getA() {
        return a;
    }
    
    public void setA(int a) {
        this.a = a;
    }
}
Out[25]:
lecture.SecretA
In [26]:
import lecture.SecretA;

SecretA x = new SecretA();
x.setA(1);
return x.getA();
Out[26]:
1

Static vs. Non-Static

In Jave fields can exist once for the class independent of any instance of the class or once per instance

In [13]:
package lecture;

public class StaticA {
    public static int a;
    public int b;
}
Out[13]:
lecture.StaticA
In [16]:
import lecture.StaticA;
StaticA x = new StaticA ();
StaticA y = new StaticA ();
StaticA.a = 1;
x.b = 10;
StaticA.a = 2; // this overwrites the previous value since "a" exists once for the class StaticA
y.b = 20;
x.a = 4;

return String.format("StaticA.a is { %d }\n\nx.b is { %d }\n\ny.b is {  %d }", StaticA.a, x.b, y.b);
Out[16]:
StaticA.a is { 4 }

x.b is { 10 }

y.b is {  20 }

Methods

Method are used to implemented functionality specific to a class. Similar to fields, methods are declared with an access modifier determining the visibility. Furthermore, methods can be static or non-static. All methods have a return type. For methods that do not return a value the special type void is used. Methods take zero or more arguments which are declared as parameters. The syntax for a method definition is:

<modifiers> <return_type> <method_name> (<parameters>) <throws_declaration>
{
    <method_body>
}

A parameter is declared as <type> <name> just like you declare variables. Here <modifiers> can be one of the access modifiers discussed above. We will discuss the <throws_declaration> which is related to exception handling later in the course.

Methods can be called to execute their method body. When calling a method one has to pass values to the methods that are bound to its parameters. The syntax of a method call is:

<method_name> ( <arguments> )

To call a method within a class, you just use its name. From outside of the class you call a static method by qualifying it by its class name, e.g., if a is static method of a class A then you call it like this: A.a(). Non-static methods are called using a variable, e.g.,

String a = "test";

a.toLower();

To return a value from a method, the return statement is used. For instance, to return the numeric value 1:

return 1;
In [17]:
package lecture;

public class MyFirstMethod {
    
    // a static method that contatenates two Strings and outputs them on the commandline
    public static void myStaticMethod (String a, String b) {
        System.out.println(a + " " + b);
    }

    // a non-static method that contatenates two Strings and outputs them on the commandline
    public void myMethod (String a, String b) {
        System.out.println(a + " " + b);       
    }
    
}
Out[17]:
lecture.MyFirstMethod
In [18]:
import lecture.MyFirstMethod;
MyFirstMethod.myStaticMethod("hello", " world! I am a static method");

MyFirstMethod x = new MyFirstMethod();

x.myMethod("hello", " world! I am a non-static method");
hello  world! I am a static method
hello  world! I am a non-static method
Out[18]:
null
In [19]:
package lecture;

public class MyFirstReturnMethod {
    
    public static String concat (String a, String b) {
        return a + b;
    }
    
}
Out[19]:
lecture.MyFirstReturnMethod
In [21]:
import lecture.MyFirstReturnMethod;


return MyFirstReturnMethod.concat("Hello", " World");
Out[21]:
Hello World

Call Stack

Consider an example illustrating how the call stack works

In [22]:
package lecture;

public class CallStack {
    
    public static void indent(int depth) {
        for(int i = 0; i < depth; i++) {
            System.out.print("\t");
        }
    }
    
    public static int compute(int depth, int maxdepth) {
        int value;
        
        indent(depth);
        System.out.printf("start compute depth %d of %d\n", depth, maxdepth);       
        if (depth == maxdepth) {
            indent(depth);
            System.out.printf("return from compute depth %d of %d\n", depth, maxdepth);
            return 0;
        }
        value = compute(depth + 1, maxdepth) + 1;
        indent(depth);
        System.out.printf("return from compute depth %d of %d\n", depth, maxdepth);
        return value;
    }
}
Out[22]:
lecture.CallStack
In [24]:
import lecture.CallStack;

return CallStack.compute(0, 5);
start compute depth 0 of 5
	start compute depth 1 of 5
		start compute depth 2 of 5
			start compute depth 3 of 5
				start compute depth 4 of 5
					start compute depth 5 of 5
					return from compute depth 5 of 5
				return from compute depth 4 of 5
			return from compute depth 3 of 5
		return from compute depth 2 of 5
	return from compute depth 1 of 5
return from compute depth 0 of 5
Out[24]:
5

The main method

The main method has a special meaning in Java. It is the main entry point for a program. That is whenever you start a Java program, the main method of a class (that you specify) will be executed. The signature of main is

public static void main(String[] args);

Here args is an array (to be explained later) of parameters that are passed to the program.

Method overloading

Method overloading is when a class defines multiple methods with the same name. This is allowed in Java as long as any pair of methods with the same name have different parameters, e.g.,

public void process(String first, String second);
public void process(String first);
public void process(int other, String another);
In [27]:
package lecture;

public class MyOverloading {
    
    public static void printIt(String arg) {
        System.out.println(arg);
    }
    
    public static void printIt(int arg) {
        System.out.printf("%d\n", arg);
    }
    
}
Out[27]:
lecture.MyOverloading
In [28]:
import lecture.MyOverloading;

MyOverloading.printIt("hello");
MyOverloading.printIt(13);
hello
13
Out[28]:
null

Inheritance

An important part of the object-oriented programming paradigm is reuse of functionality without code duplication through the use of inheritance. In a Java a class can extend another class. This is declared in the class declaration, e.g.,

public class MySub extends MySuper {
}

specifies that class MySub extends MySuper. In this context we call MySub the subclass and MySuper the superclass. A subclass inherits all fields and methods from its superclass. For example, any method defined for MySuper also exists for MySub. For example, assume you define a class for sorting information about Vehicles:

In [12]:
package lecture;

public class Vehicle {
    
    private int weightLb;
    private int numWheels = 4;
    
    public Vehicle (int weightLb) {
        this.weightLb = weightLb;
    }
    
    public Vehicle (int weightLb, int numWheels) {
        this.weightLb = weightLb;
        this.numWheels = numWheels;
    }
    
    public int getWeightLb() {
        return weightLb;
    }
    
    public int getNumWheels() {
        return numWheels;
    }
    
    public void setWeightLb(int weightLb) {
        this.weightLb = weightLb;
    }
    
    public void setNumWheels(int numWheels) {
        this.numWheels = numWheels;
    }
    
    public String toString () {
        return String.format("I am a Vehicle that weights %d and has %d wheel(s).", weightLb, numWheels);
    }
}
Out[12]:
lecture.Vehicle
In [13]:
import lecture.Vehicle;

Vehicle x = new Vehicle(4505);
return x.toString();
Out[13]:
I am a Vehicle that weights 4505 and has 4 wheel(s).

Now say that we have motor bikes that have always 2 wheels and which we want to store whether they are sports bikes or now. We could copy the code from our Vehicle class as a starting point. But that leads to code duplication which is messy and can result in bugs, e.g., if we fix a bug in the Vehicle class, then we may forget to fix it the Bike class. A better idea is to let Bike extend Vehicle.

In [14]:
package lecture;

public class Bike extends Vehicle {
    
    private boolean isSports;
    
    public Bike (int weightLb, boolean isSports) {
        super(weightLb);
        this.isSports = isSports;
        setNumWheels(2);
    }
    
    public boolean getIsSports () {
        return isSports;
    }
    
    public void setIsSports (boolean isSports) {
        this.isSports = isSports;
    }
    
    @Override
    public String toString() {
        return String.format("I am a %s bike and I weight %d lb", (isSports) ? "sports": "", getWeightLb());
    }
    
}
Out[14]:
lecture.Bike
In [33]:
import lecture.Bike;

Bike x = new Bike (530, true);
return x.toString();
Out[33]:
I am a sports bike and I weight 530 lb

Here we made use of serval features that require further explanation. First of, in the constructor for Bike we called one of the constructors for Vehicle using the super keyword. Note that if we call the constructor of a superclass within the constructor for a subclass, this has to be the first statment in the constructor of the subclass. Furthermore, we made use of overriding which allows a subclass to redefine a method defined by a superclass. In this particular example, we want the toString method to tell us that the object is a bike and whether it is a sports bike. We have achieved this by overriding the toString method. For understandability you can annotate a overriden class using @Override. This is optional. We will talk more about annotations later.

Polymorphism

Using a variable of a superclass we can assign an object of any direct or indirect subclass. This is called polymorphism. Calling a method that is overridden in the subclass will call the overridden method:

In [17]:
import lecture.Vehicle;
import lecture.Bike;

Vehicle x = new Bike(530, true);
Bike b = (Bike) x;
Vehicle y = new Vehicle(4500);

System.out.println("x.toString: " + x.toString());
System.out.println("b.toString: " + b.toString());
System.out.println("y.toString: " + y.toString());
x.toString: I am a sports bike and I weight 530 lb
b.toString: I am a sports bike and I weight 530 lb
y.toString: I am a Vehicle that weights 4500 and has 4 wheel(s).
Out[17]:
null

Using a variable of the superclass we cannot access methods or fields defined in the subclass.

In [18]:
import lecture.Vehicle;
import lecture.Bike;

Vehicle x = new Bike(530, true);

System.out.println("x.toString: " + (String) x.getIsSports());
cannot find symbol
  symbol:   method getIsSports()
  location: variable x of type lecture.Vehicle
ntln("x.toString: " + (String) x.getIsSports())
                               ^            ^    
In [19]:
import lecture.Vehicle;
import lecture.Bike;

Bike b = new Bike(530, true);

System.out.println("b.isSports: " + b.getIsSports());
b.isSports: true
Out[19]:
null

Scope, Lifetime, and Hidding

The scope of a variable is the part of the program where it is valid. The lifetime of a variable is when it is valid.

Scopes

In Java the following scopes exist:

  • class scope - elements with class scope are valid within the class
  • method scope - elements with method scope are valid within the body of a method
  • local scope - elements with local scope are valid within the block of code (between { and }) they are defined in starting from their definition
  • loop scope - elements with loop scope are valid within a loop

Lifetimes

The lifetime of elements is as follows:

  • class scope - elements start to exist when an instance of the class is created and continue to exist during the objects lifetime
  • method scope - elements start to exist when the method is called and are valid until the method call returns
  • local scope - elements start to exist when the execution of the code block reaches the declaration of the variable and are valid until execution reaches the end of the code block
  • loop scope - elements start to exist when the execution enters the loop and are valid until the loop finishes

What has which scope?

  • non-static fields of a class have class scope
  • method parameters have method scope
  • variables declared in a code block have local scope
  • loop variable have loop scope

Hiding

Multiple variables with the same name, but different scopes may exist at the same time. This is allowed in Java. The name allways refers to the the variable defined in the innermost scope. This is only allowed for the following cases:

  • method parameters or local variables hide a classes field

  • Note: A variable name can only be used once within the scope of a method!

In [1]:
{ 
    int x;
    x = 3;
}
return x; // the scope of x is the block of code between {} -> this leads to an error
cannot find symbol
  symbol:   variable x
  location: class com.twosigma.beaker.javash.bkrc436e148.BeakerWrapperClass1261714175Id4aa06b68b4d044e28094f35308e1e700
 return x
        ^^ 
In [16]:
{
    int x;
    x = 3;
    return x; // this works
}
Out[16]:
3
In [17]:
for(int i = 0; i < 3; i++) { // i has loop scope and is valid within the loop
    System.out.println(i);
}
0
1
2
Out[17]:
null
In [3]:
package lecture;

public class Scopes {
    
    public int x = 15;
    
    public void m (int x) {
        System.out.println(x); // this prints the parameter that hides the class field x
    }
}
Out[3]:
lecture.Scopes
In [4]:
import lecture.Scopes;

Scopes a = new Scopes();
a.m(20); // returns 20
20
Out[4]:
null
In [5]:
{ 
    int x = 1;
    {
        int x = 2; // not allowed since the outer x is still in scope
    }
}
variable x is already defined in method beakerRun()
 int x = 2; }
 ^         ^   
In [26]:
package lecture;

public class BrokenScopes {
      
    public static void m (int x) {
    {
        int x = 40; // parameter is already called x
    }
}
ERROR: java.lang.IllegalStateException: reached end of file while parsing
 }
  ^

The this and super keywords

The this keyword is used to access a field or method of an object's class within the code of the class. The super keyword accesses fields and methods of a super class.

This

An idiomatic use case of the this keyword in Java is passing parameters to a constructor to initialize the values of fields. It is customary to give such parameters the same name as the name of the field. However, this makes it then necessary to use the this keyword, because the constructor parameters hide the fields.

public class X {

    private int a;

    public X(int a ) {
        this.a = a; // use this to refer to the field which is hidden by the parameter of the same name
    }

...   
}

Super

The super keyword is used to refer to a method from a superclass within the context of a subclass.

public class X {

    public int calculate() {
    ...   
    }
}

public class Y extends X {

    public int calculate() {
       ...
       super.calculate(); // we have to use super to be able to access the calculate method of the X class since Y overrides it
    }

}
In [6]:
package lecture;

public class SuperClass {
    
    int x;
     
    public int calculate() {
        return x + x;
    }    
    
    public int getX() {
        return x;
    }
    
    public void setX(int x) {
        this.x = x;
    }
}
Out[6]:
lecture.SuperClass
In [7]:
package lecture;

public class SubClass extends SuperClass {
    
    public int calculate() {
        int y = super.calculate();
        return y * y;
    }    
}
Out[7]:
lecture.SubClass
In [11]:
import lecture.SuperClass;
import lecture.SubClass;

SubClass v = new  SubClass();
v.setX(5);
return v.calculate(); // return (x + x) * (x + x)
Out[11]:
100

Root Class "Object"

In Java all classes are inherited directly or indirectly from the class Object. This class defines several methods that are, thus, through inheritance also available for every class in Java. Here we only review a few of them.

  • public String toString() - returns a representation of the object as a string. The default implementation returns the fully qualified name of the class (including the package name) and an interal id for the opject
  • public boolean equals (Object o) - returns true if o is the same object as our object. Can be overridden to define a domain-specific notion of equality.
  • public long hashCode() - returns a hash code for this object. This is used, e.g., by certain collection data structures that we will talk about later.
  • public Class getClass() - returns an object of class Class that contains a description of the class the object is an instance of (more about this later when we talk about Reflection). This allows us to do interesting things like finding out the fields and methods of an object of an unknown class at runtime.

Constructors and Creating Objects (Instances of a class)

In Java, there are no object variables, but all variables are references. Objects are created by using the new operator, e.g., new Integer(1) creates an instance (object) of class Integer. new calls the appropriate constructor of the class based on the types of arguments passed in parenthesis to the new operator. Constructors are special functions in a class that have the same name as the class and for which no return value is specified. Note that constructors are also subject to inheritance and the root class Object defines a default constructor. That is, for our MyFirstClass calling new MyFirstClass() ended up calling the default constructor.

In [9]:
import lecture.*;

MyFirstClass x = new MyFirstClass();
return x;
Out[9]:
lecture.MyFirstClass@48013589
In [8]:
package lecture;

public class MyMultiConstructor {
    
    public int x;
    public int y;
    
    // first constructor does not take any arguments
    public MyMultiConstructor () {
        x = 0;
        y = 0;
    }
    
    // second one takes x and y as arguments
    public MyMultiConstructor(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
Out[8]:
lecture.MyMultiConstructor
In [9]:
import lecture.MyMultiConstructor;

MyMultiConstructor a = new MyMultiConstructor(10,20);
return a.y;
Out[9]:
20

Operators for objects

Equality comparison

Two reference variables can be compared to equality using ==. This returns true if both are referencing the same object and false otherwise. Note that the value of fields are irrelevant here. Two obejcts are only considered to be the same if they are physcially the same object.

In [10]:
import lecture.MyMultiConstructor;

MyMultiConstructor x = new MyMultiConstructor(10,10);
MyMultiConstructor y = x; // y points to the same object as x
MyMultiConstructor z = new MyMultiConstructor(10,10); // even though the object pointed to by z has the same value as the one pointed to x and y they are not the same object

System.out.printf("x and y are the same object? %b\n", x == y);
System.out.printf("x and z are the same object? %b\n", x == z);
x and y are the same object? true
x and z are the same object? false
Out[10]:
null

The instanceof operator

The <object> instanceof <class> operator is used to check whether an object <object> is an instance of class <class> or of one of the subclasses of <class>.

In [15]:
import lecture.Bike;
import lecture.Vehicle;

Vehicle x = new Bike(530, true);
Bike b = (Bike) x;
Vehicle y = new Vehicle(4500);

System.out.printf("x instance of Vehicle: %b\n", x instanceof Vehicle);
System.out.printf("x instance of Bike: %b\n", x instanceof Bike);
System.out.printf("b instance of Vehicle: %b\n", b instanceof Vehicle);
System.out.printf("y instance of Vehicle: %b\n", y instanceof Vehicle);
System.out.printf("y instance of Bike: %b\n", y instanceof Bike);
x instance of Vehicle: true
x instance of Bike: true
b instance of Vehicle: true
y instance of Vehicle: true
y instance of Bike: false
Out[15]:
null

Casting References

As we have seen above, reference types can be casted. Java enforces type saefty for this:

  • a cast (<class>) <variable> is valid if the object pointed to be <variable> is an instance of <class> or one of the subtypes of <class>.

Modifiers

We already have seen the use of access modifiers such as public and private in Java. The Java language supports additional modifiers that are not related to access control.

Modifier Description
static We already discussed the meaning of static fields and methods
final Used for finalizing implementations of classes, variables, and methods
abstract Used for creating abstract methods and classes
synchronized Used in threads and locks the method or variable so it can only be used by one thread at a time
volatile Used in threads and keeps the variable in main memory rather than caching it locally in each thread

Final modifier

  • Variables and Fields - a final variable or field is one whose value cannot be changed once it has been assign a value. Note that for reference variables (any non-primitive type) this only means that the variable/field will only point to a fixed object, but not that the object's fields cannot be modified.
  • Class - a final class is one that cannot be inherited from, i.e., you cannot create a subclass of a final class
  • Method - a final method cannot be overriden in a subclass of the class defining the final method
In [1]:
final int a;
a = 5;
return a;
Out[1]:
5
In [2]:
final int a = 5;
a = 6; // error cannot reassign a value to a final variable
cannot assign a value to final variable a
 a = 6
 ^^     
In [5]:
public class X {
    int a;
}
Out[5]:
com.twosigma.beaker.javash.bkr6f35bbc3.X
In [7]:
final X x = new X();
x.a = 5;
x.a = 6; // that is allowed because we are modifying a field of the X object pointed to be x
return x.a;
Out[7]:
6
In [8]:
final X x = new X();
x = new X(); // error we are trying to assign a different object to a final variable that we did assign a value to before
cannot assign a value to final variable x
 x = new X()
 ^^           
In [3]:
public final class Uninheritable {
    
}
Out[3]:
com.twosigma.beaker.javash.bkr6f35bbc3.Uninheritable
In [4]:
public class Inherit extends Uninheritable { // not allowed
    
}
ERROR: java.lang.IllegalStateException: cannot inherit from final com.twosigma.beaker.javash.bkr6f35bbc3.Uninheritable
 public class Inherit extends Uninheritable { 
                              ^            ^    
In [11]:
public class FinalMethod {
    
    public final void mymethod() {
        System.out.println("this is a final method");
    }
}
Out[11]:
com.twosigma.beaker.javash.bkr6f35bbc3.FinalMethod
In [13]:
public class InFinalMethod extends FinalMethod {
    
    public final void mymethod() { // error cannot override final method
        
    }
}
ERROR: java.lang.IllegalStateException: mymethod() in com.twosigma.beaker.javash.bkr6f35bbc3.InFinalMethod cannot override mymethod() in com.twosigma.beaker.javash.bkr6f35bbc3.FinalMethod
  overridden method is final
 public final void mymethod() {  }
 ^                                ^ 

Abstract

The abstract modifier for method and classes is used in abstract classes we will discussed later when talking about interfaces.

Synchronized

This keyword is used to control concurrent access from different threads. We will discuss this at the end of the course when discussing currency.

Volatile

Volatile forces Java to read the value of a variable from memory for every access instead of caching it in a register. This is useful for certain types of concurrent programming. We will not discuss this modifier further in this course.