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.
package lecture;
public class MyFirstClass {
public static void main(String[] args) {
System.out.println("hello world I'm here!");
}
}
import lecture.MyFirstClass;
MyFirstClass.main(null);
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;
}
package lecture;
public class A {
public int a;
public int b;
}
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);
Now let us consider a class with a field that is of a reference type.
package lecture;
public class B {
public A a = new A(); // create a new A object
public int c = -1; // initialize to -1
}
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;
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.
package lecture;
public class SecretA {
private int a;
private int b;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
import lecture.SecretA;
SecretA x = new SecretA();
x.setA(1);
return x.getA();
In Jave fields can exist once for the class independent of any instance of the class or once per instance
package lecture;
public class StaticA {
public static int a;
public int b;
}
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);
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;
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);
}
}
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");
package lecture;
public class MyFirstReturnMethod {
public static String concat (String a, String b) {
return a + b;
}
}
import lecture.MyFirstReturnMethod;
return MyFirstReturnMethod.concat("Hello", " World");
Consider an example illustrating how the call stack works
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;
}
}
import lecture.CallStack;
return CallStack.compute(0, 5);
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 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);
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);
}
}
import lecture.MyOverloading;
MyOverloading.printIt("hello");
MyOverloading.printIt(13);
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:
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);
}
}
import lecture.Vehicle;
Vehicle x = new Vehicle(4505);
return x.toString();
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
.
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());
}
}
import lecture.Bike;
Bike x = new Bike (530, true);
return x.toString();
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.
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:
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());
Using a variable of the superclass we cannot access methods or fields defined in the subclass.
import lecture.Vehicle;
import lecture.Bike;
Vehicle x = new Bike(530, true);
System.out.println("x.toString: " + (String) x.getIsSports());
import lecture.Vehicle;
import lecture.Bike;
Bike b = new Bike(530, true);
System.out.println("b.isSports: " + b.getIsSports());
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.
In Java the following scopes exist:
{
and }
) they are defined in starting from their definitionThe lifetime of elements is as follows:
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!
{
int x;
x = 3;
}
return x; // the scope of x is the block of code between {} -> this leads to an error
{
int x;
x = 3;
return x; // this works
}
for(int i = 0; i < 3; i++) { // i has loop scope and is valid within the loop
System.out.println(i);
}
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
}
}
import lecture.Scopes;
Scopes a = new Scopes();
a.m(20); // returns 20
{
int x = 1;
{
int x = 2; // not allowed since the outer x is still in scope
}
}
package lecture;
public class BrokenScopes {
public static void m (int x) {
{
int x = 40; // parameter is already called x
}
}
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.
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
}
...
}
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
}
}
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;
}
}
package lecture;
public class SubClass extends SuperClass {
public int calculate() {
int y = super.calculate();
return y * y;
}
}
import lecture.SuperClass;
import lecture.SubClass;
SubClass v = new SubClass();
v.setX(5);
return v.calculate(); // return (x + x) * (x + x)
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 opjectpublic 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.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.
import lecture.*;
MyFirstClass x = new MyFirstClass();
return x;
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;
}
}
import lecture.MyMultiConstructor;
MyMultiConstructor a = new MyMultiConstructor(10,20);
return a.y;
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.
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);
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>
.
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);
As we have seen above, reference types can be casted. Java enforces type saefty for this:
(<class>) <variable>
is valid if the object pointed to be <variable>
is an instance of <class>
or one of the subtypes of <class>
.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 int a;
a = 5;
return a;
final int a = 5;
a = 6; // error cannot reassign a value to a final variable
public class X {
int a;
}
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;
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
public final class Uninheritable {
}
public class Inherit extends Uninheritable { // not allowed
}
public class FinalMethod {
public final void mymethod() {
System.out.println("this is a final method");
}
}
public class InFinalMethod extends FinalMethod {
public final void mymethod() { // error cannot override final method
}
}
The abstract modifier for method and classes is used in abstract classes we will discussed later when talking about interfaces.
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 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.