History
Java was created in the early 90's by James Gosling at Sun Microsystems. It is a general purpose object oriented programming language used in a variety of different application. Let us study how a computer program is executed on a computer. The diagram is somewhat simplistic and offers an overview of how a computer will work in general.
The instructions for the program are stored in the RAM and the CPU grabs an instruction from the RAM and executes it. It will then grab the next instruction and execute that and so on. What type of instructions are these ? Well these are the instructions that the CPU understands. In case of an Intel CPU the machine language is based on x86 . That is the only thing the CPU understands. It does not know about C++ , Java or any other language. It only knows about the x86 machine language. What do the instructions of this machine language look like ?
000000 00001 00010 00110 00000 100000
100011 00011 01000 00000 00001 000100
As you can imagine writing machine language instructions is a tedious and impractical task. Programmers
used Assembly language instead to do their programming. So how did the CPU run Assembly language .
Well it didn't. The Assembly language program was converted to machine language by a compiler.
What did the Assembly language instructions look like ?
MOV AL, 1h ; Load AL with immediate value 1
MOV CL, 2h ; Load CL with immediate value 2
MOV DL, 3h ; Load DL with immediate value 3
The Assembly language instruction had one to one correlation with machine language and was
slightly better in terms of using opcodes but still a tedious task. So now the high level languages
came such as Fortran, Pascal, C . For a long time programming was done in these high level
languages . These language had features like functions, procedures , structures. A programmer could
use variables and did not have to worry about mapping variables to specific addresses in memory.
However these languages involved the programmer converting the application problem in
terms of the language which still bore a close relationship to the way the CPU worked. The
concept of object oriented languages was developed and thus came languages such as Java,
C# and C++ . Now a programmer could translate the application problem in terms of objects and
classes. The way we think about problems and concepts can be correlated and organized better with
object oriented programming. As an example think of an application for the bank . We can think of
a customer with properties such as name, age, gender, address, phone. This customer could have an
account and the account could be checking or saving. An account can have properties like amount and
account number. It could also have methods like add a deposit and so on. This sort of technique can
be used in many problems where we can model the solution in terms of classes to more closely resemble
the problem domain.
Installation
Go to the following Oracle link:
http://www.oracle.com/technetwork/java/javase/archive-139210.html
It is recommended to install JDK 1.8 as it has most of the necessary features
and also we can run JavaFX programs without downloading other packages.
Write Once Run Anywhere
Usually a traditional program gets compiled to a machine language exe. Let's say the C++ program gets compiled on a Windows machine to an exe. The exe will be machine language instructions and it will have some operating system specific code ( in machine language also ) .
We can take this "exe" and run it on another Windows machine. However we cannot take it to a Mac or Linux and run it there or even on a machine that say has a different type of CPU . So how does Java state that one can write Java code once and run it anywhere. Let us go through the process of running a Java program.
To compile Java code we first need a JDK installed on our machine. Let's assume we are on a Windows machine. We can download JDK from the Oracle site . Once we have it installed we note down the installed directory. Let us assume it was installed at:
C:\jdk18
Now we can create a folder say "Java" on the "C:" drive.
C:\Java
We will now create a file in this folder called "Hello.java" .
File: "Hello.java"
public class Hello
{
public static void main(String args[] )
{
System.out.println( "Hello World" ) ;
}
}
Now we compile the file from the dos prompt. To compile the file we do :
javac Hello.java
Now we might run into an error of the form:
'javac' is not recognized as an internal or external command,
operable program or batch file.
We need to tell the operating system where the path to the java compiler is. Create another file called "set1.bat" in the folder .
File: "set1.bat"
set path=c:\jdk8\bin
We run the batch file :
c:\java\set1.bat
Now we can run the command :
javac Hello.java
This will create a class file ( if there were no compilation errors ) called "Hello1.class" . Note that it is not an executable . We cannot run the ".class" file by itself. So what's in the "class" file. It is a bunch of something called "bytecodes" . These are like assembly language instructions that the Java people made up . They are not associated with any particular CPU or hardware but rather with a CPU/virtual machine that again Java people have made up( hence the word virtual).
To run the class file we give the command:
java Hello
This will invoke the Java runtime engine and run the class file. We need either the JDK or the JRE installed on the user machine to run the "class" file. If we need to run our program on another machine then we give the ".class" file. Since it is not machine language the compatible issue is resolved. However we do need a JVM/JRE on that machine in order to run the program. Some terms:
1) JVM Java Virtual Machine . A specification about the virtual machine and the byte codes.
2) JRE Java Runtime Engine. An implementation of the JVM specification. We need the JRE in order to run the program.
3) JDK Java Devleopment Kit. Contains the tools to compile the program to ".class" bytecode files. Also includes the JRE.
We give the ".class" file to another machine ( that may have a different CPU language type and a different operating system ) the other machine will be able to run the program . However it must have the JRE installed on it. The ".class" is not an executable by itself so we cannot run it by itself.
Contrast this with a "C++" program.
File: "hello.cpp"
#include <iostream>
using namespace std ;
int main()
{
cout << "Hello World" << endl ;
return( 0 ) ;
}
We compile it:
g++ -o hello.exe hello.cpp
Now we have a "hello.exe" . We can run this on our machine. It contains machine language code. If we give it to someone with the same operating system and the same CPU as ours then the person can run the file. The other person does not need anything else ( software installed to run it ) . However if we give it to a person who is on a different operating system or a different CPU then the program will not run. The executable contains operating system specific code as well as our CPU machine code.
Link to different machine languages:
https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Machine_languages
We can see the difference in Java compilation process and the traditional language like C++ .
C++
Program --> Compiler --> Executable ( machine language code , operating system specific instructions )
Java
Program --> Compiler --> ".class" file ( Java byte codes) --> JRE runs the .class files.
Some books and articles will talk about the the JVM converting the bytecodes to machine language code. That can happen in certain cases where JVM may decide to speed up the program by converting certain sections of the bytcode to machine language code. However we should not assume that the whole of ".class" will be converted to "exe" and then run . There are tools that can do this. For more reading check the following links:
Object oriented language
An object oriented language will support features such as encapsulation, abstraction, inheritance and polymorphism. The language should of course support the concept of a class and to create objects of that class but that is not sufficient to classify the language as being object oriented. It should have features such as encapsulation, abstraction, inheritance and polymorphism. Languages will have differences in what sort of functionality they provide for these features. As an example "C++" provides for multiple inheritance while Java does not.
Encapsulation is the ability to hide data in a class and provide access through methods only. This allows the programmer control as no other user ( user in terms of who is using the class ) can change the data directly and the programmer can implement checks and error handling reducing the need to check for errors related to the data in the class . It also allows the programmer to change the data and use different form of data without affecting the rest of the code by the users who have used the class.
Abstraction is very similar to encapsulation but is more high level. It deals with the kind of interface we want to provide to the users ( again programmers of our class ) . Java provides 2 constructs ( Interface and Abstract class ) that provide for this feature.
Inheritance allows a class to be reused so that a similar class does not have to be rewritten from scratch. Polymorphism is related to inheritance and allows a base class variable holding an object of derived type to call the method on the right class. We will examine each of the concepts in little bit more detail below.
Structure of a Java Program
Let us take at a closer look at the "Hello.java"
File: "Hello.java"
public class Hello
{
public static void main(String args[] )
{
System.out.println( "Hello World" ) ;
}
}
We declare a class called "Hello". We cannot create a method by itself in Java. All our methods need to be in a class. If we declare a class as "public" then we are signalling our intent that the class can be used by other classes and we need to place it in a file with the exact name as the class. If we don't declare the class as public then we can have a different file name:
File: "Hello1.java"
class Hello
{
public static void main(String args[] )
{
System.out.println( "Hello World" ) ;
}
}
We can compile this with the command:
javac Hello1.java
However the class that is created is still "Hello.class" and we have to run it with the same command:
java Hello
The reason that the rule for public classes is there is to find our public classes ore easily in a large program. Remember there might be hundreds of files. With the file name the same as the public class we know what file contains the class before opening the file. This also means that we cannot have 2 public classes in the same file.
File: Hello2.java
public class student
{
int id ;
}
public class Hello2
{
public static void main(String args[] )
{
System.out.println( "Hello World" ) ;
}
}
After running the command:
javac Hello2.java
Output:
C:\Java>javac Hello2.java
Hello2.java:2: error: class student is public, should be declared in a file named student.java
public class student
^
1 error
The entry point for the execution is the function "main" in the class. We do not need to have the function "main" in the class. We will be able to compile the class but we won't be able to run it.
File: "Hello3.java"
public class Hello3
{
}
C:\Java>javac Hello3.java
C:\Java>java Hello3
Error: Main method not found in class Hello3, please define the main method as:
public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application
It is possible to have a class with no methods or data members as in "Hello3" . Do we need a "main" method in each class. Not really. If we want to run the class then yes we need a "main" method" . We can have some other class that has a main method that uses this class . Notice the main method is declared with the "static" key word. Normally a method that is not static cannot be called just like that ; we need to create an instance of the class before calling the method.
File: "Hello4.java"
public class Hello4
{
void method1()
{
System.out.println( "Inside Method1" ) ;
}
public static void main( String args[] )
{
method1() ; //Compiler Error
}
}
Output:
C:\Java>javac Hello4.java
Hello4.java:11: error: non-static method method1() cannot be referenced from a static context
method1() ; //Compiler Error
^
1 error
The method belongs to the class and we must create an object of the class in order to call the method.
File: "Hello5.java"
public class Hello5
{
void method1()
{
System.out.println( "Inside Method1" ) ;
}
static void method2()
{
System.out.println( "Inside Method2" ) ;
}
public static void main( String args[] )
{
Hello5 object1 = new Hello5() ;
object1.method1() ;
method2() ;
}
}
Output:
C:\Java>java Hello5
Inside Method1
Inside Method2
In the file above the method2 is static. That means it is a global method. It doesn't really belong to the class even though it is inside the class. To call "method1" we need to create an object of the class and then call the method "method1" . Now we can understand why the "main" method is static. The JVM should not have to create objects and call our method. Making the "main" method global allows the JVM to call it. However it must have a particular signature and that signature is:
public static void main( String args[] )
Encapsulation
Encapsulation is a fundamental feature of object oriented programming. It allows data and methods to be bundled together into a construct called class.Let's see how this is
done in the below example:
File: Person1.java
class Person1
{
public int id ;
public String name ;
//Interface of the class .
//How we can communicate with the class.
public void setId( int idP )
{
id = idP ;
}
public void setName( String nameP )
{
name = nameP ;
}
public int getId( )
{
return( id ) ;
}
public String getName( )
{
return name ;
}
public void print()
{
System.out.println( "Id:" + id + " Name:" +
name ) ;
}
public static void main( String args[] )
{
Person1 Person1Object = new Person1() ;
Person1Object.id = 1 ;
Person1Object.name = "Jack Johnson" ;
Person1Object.print() ;
}
}
Output:
[amittal@hills oop]$ java Person1
Id:1 Name:Jack Johnson
In this class "Person1" we have 2 data variables "id" and "name" and a couple of methods such as "getName" . A class is similar to a type such as "int" and we can create instances of the type ( in this case Person1Object ) . We have created an object that contains the variables and we have assigned values to the variables, However the problem is that we can access the variables directly. Let's assume that the "id" variable cannot have negative values. However because we can access it directly we can do :
Person1 Person1Object = new Person1() ;
Person1Object.id = -1 ;
Person1Object.name = "Jack Johnson" ;
Person1Object.print() ;
And now we have an invalid value for "id" . We specified the access to the variables as "public" . Java provides 4 access specifiers:
public
protected
private
default
We will study the meaning of each of these in detail in later sections. The access "public" states that the variable/method can be accessed from outside the class such as the "main" method in the above example. The access "private" means that only the class functions can access the variables. Let us improve on the class.We have created a new class called "Person2Driver" because having a "main" method inside the "Person" class allows us access to the private variables of the "Person" class and we want to illustrate the use of public methods to access the private class.
class Person2
{
private int id ;
private String name ;
public void setId( int idP )
{
if ( id < 0 )
{
System.out.println( " The id cannot be less than 0" ) ;
return ;
}
id = idP ;
}
public void setName( String nameP )
{
name = nameP ;
}
public int getId( )
{
return( id ) ;
}
public String getName( )
{
return name ;
}
public void print()
{
System.out.println( "Id:" + id + " Name:" +
name ) ;
}
}
public class Person2Driver
{
public static void main( String args[] )
{
Person2 Person2Object = new Person2() ;
Person2Object.id = -1 ;
Person2Object.name = "Jack Johnson" ;
Person2Object.print() ;
}
}
Output:
Person2Driver.java:50: error: id has private access in Person2
Person2Object.id = -1 ;
^
Person2Driver.java:51: error: name has private access in Person2
Person2Object.name = "Jack Johnson" ;
^
There are compiler errors because the variables have private access. So now we shall use the public methods to access them.
File: Person3Driver.java
class Person3
{
private int id ;
private String name ;
public void setId( int idP )
{
if ( idP < 0 )
{
System.out.println( " The id cannot be less than 0" ) ;
return ;
}
id = idP ;
}
public void setName( String nameP )
{
name = nameP ;
}
public int getId( )
{
return( id ) ;
}
public String getName( )
{
return name ;
}
public void print()
{
System.out.println( "Id:" + id + " Name:" +
name ) ;
}
}
public class Person3Driver
{
public static void main( String args[] )
{
Person3 Person3Object = new Person3() ;
Person3Object.setId( -1 ) ;
Person3Object.setName( "Jack Johnson" ) ;
Person3Object.print() ;
}
}
Output:
ajay.mittal$ java Person3Driver
The id cannot be less than 0
Id:0 Name:Jack Johnson
In the above we use the setter method because we don't have access to the variables directly. Since we have error checking we get an error printed out. We have control over what we want to do with the error. We may choose to throw an Exception. In the above example for simplicity we are just printing the error out. The above example is a good illustration of how encapsulation can work,
Interfaces & abstraction
File: "inter1.java"
class Person
{
private int id ;
private String name ;
//Interface of the class .
//How we can communicate with the class.
public void setId( int idP )
{
id = idP ;
}
public void setName( String nameP )
{
name = nameP ;
}
public int getId( )
{
return( id ) ;
}
public String getName( )
{
return name ;
}
}
public class inter1
{
public static void main(String args[] )
{
Person p1Object = new Person() ;
int someId = p1Object.id ;
System.out.println( "Interface" ) ;
}
}
Once we define a class we can keep the data members private and the methods public. The collection of methods is what the user of the class will use and may not care or know about the inner private data.How do we access the private data. We can use methods and make them public. By convention these are called getter and setter methods and by methods their signature is as follows.
getPropertyname
setPropertyname
If the property is Id then the methods are:
In addition to being able to create a class OOP usually requires to provide more features such as composition, inheritance and polymorphism.
Composition
File: "Composition.java"
class Engine
{
int noOfCC ;
String manfacturer ;
}
class Car
{
String make ;
Engine engineObject ;
}
public class Composition
{
public static void main(String args[] )
{
Engine someEngine = new Engine() ;
someEngine.noOfCC = 1600 ;
someEngine.manfacturer = "Mercedes" ;
Car carObject = new Car() ;
carObject.make = "Dodge" ;
carObject.engineObject = someEngine ;
}
}
Inheritance and Polymorphism
Inheritance allows for the reuse of a class. Let's assume we have a Person class with a few more attributes.
Ex:
class Person
{
int id ;
String name ;
int age ;
String address ;
String gender ; // M or F
public void setId( int idP )
{
id = idP ;
}
public int getId()
{
return ( id ) ;
}
}
public class Person4Driver
{
public static void main( String args[] )
{
Person PersonObject = new Person() ;
}
}
For simplicity we have left out the "getter" and "setter" methods for some of the data variables. Now let's say we are creating some software for a company and we want to create a class called "Employee" that will have some attributes such as "Title", "Salary", "Starting Date" and so on. However the employee will have things like "name", age", "address" and "gender". Wouldn't it be nice to "reuse" the Person class instead of redefining all these attributes inside the Employee class. That's where inheritance comes in. We are able to "inherit" and access the variables and methods of a class.
class Person
{
int id ;
String name ;
int age ;
String address ;
String gender ; // M or F
public void setId( int idP )
{
id = idP ;
}
public int getId()
{
return ( id ) ;
}
}
class Employee extends Person
{
String title ;
int salary ;
String startingDate ;
}
public class Person5Driver
{
public static void main( String args[] )
{
Employee employeeObject = new Employee() ;
employeeObject.age = 53 ;
employeeObject.setId( 1) ;
}
}
Polymorphism
The word "polymorphism" means having many forms. What exactly does that mean ? Let's look at a class hierarchy .
Vehicle
Car Train Motorcycle
The class "Vehicle" is the base class and has 3 derived classes "Car", "Train" and "Motorcycle" .We know that the class "Car" can access the properties and
methods of the base class "Vehicle" . However, we can also conceptually say that a "Car" is also a "Vehicle" . A "Motorcycle" is also a type of vehicle. What this
means specifically for objects of the derived types is that they can be assigned to the base type.
Vehicle v1Obj ;
v1Obj = new Car() ;
We can also do:
v1Obj = new Vehicle() ;
v1Obj = new Train() ;
The variable "v1Obj" is declared as of type "Vehicle" but can actually contain objects of other types namely objects of the derived types. We can only call the methods and
access the properties of the base object but that is ok because we could do that while we had objects of the derived class also. The question is what happens when
we call a method on "v1Obj" that is also defined in the derived class. We need an example to show the behavior
Ex: "poly1.java"
class Shape
{
void draw()
{
System.out.println("Shape: draw" ) ;
}
void erase( )
{
System.out.println("Shape: erase" ) ;
}
void move( )
{
System.out.println("Shape: move" ) ;
}
}
class Circle extends Shape
{
double radius ;
//Overrides the base class method
void draw()
{
System.out.println("Circle: draw" ) ;
}
double getArea()
{
final double pi = 3.14 ;
return( pi* radius * radius ) ;
}
void erase( )
{
System.out.println("Circle: erase" ) ;
}
}
class Triangle extends Shape
{
void draw()
{
System.out.println("Triangle: draw" ) ;
}
void FlipVertical()
{
}
void FlipHorizontal()
{
}
}
class Square extends Shape
{
void draw()
{
System.out.println("Square: draw" ) ;
}
}
public class poly1
{
public static void drawShape ( Shape shapeObject )
{
shapeObject.draw() ;
}
public static void main(String args[] )
{
Circle circleObject = new Circle() ;
Square squareObject = new Square() ;
drawShape( circleObject ) ;
drawShape( squareObject ) ;
}
}
Output:
ajay.mittal$ java poly1
Circle: draw
Square: draw
A derived class can reuse the base class . We do not have to redefine the methods or properties in the base class. Also we can always convert a derived object to a base class object. In our example we have the base class as "Shape" and the derived class as "Circle" . We can say that a "Circle" is kind of a "Shape" .
The above example shows polymorphism in action. We have 2 derived objects and they are used in the function "drawShape" which takes an argument of type "Shape" so the derived objects are converted ( upcast ) to the base class "Shape" object. Now the "draw" method of the "Shape" object gets called. If we study the output then we can see that even though the Shape's "draw" method got called the actual method that got called was the derived class object methods. Another thing to notice is that the "Shape"'s draw method has the same exact signature as the derived class "Circle"'s draw method. This sort of situation is what can allow Polymorphism to work.
Polymorphism is a very powerful concept. It allows us to write generic code in one part of the program and not only works with existing derived classes calling their methods when
need be but it can also work with future classes that are derived from the base class. The code reuse comes into play with the code of the section where we have written the generic code and its ability to work with new derived classes producing different behavior without any changes to the generic code.
What happens if the derived class does not "override" the base class method . It does not have an implementation for it. In that case the base class method would get called. This is the same behavior if we created a derived object and called a method that was defined in the base class.
File: "poly2.java"
class Shape
{
void draw()
{
System.out.println("Shape: draw" ) ;
}
void erase( )
{
System.out.println("Shape: erase" ) ;
}
void move( )
{
System.out.println("Shape: move" ) ;
}
}
class Circle extends Shape
{
double radius ;
//Overrides the base class method
void draw()
{
System.out.println("Circle: draw" ) ;
}
double getArea()
{
final double pi = 3.14 ;
return( pi* radius * radius ) ;
}
void erase( )
{
System.out.println("Circle: erase" ) ;
}
}
class Triangle extends Shape
{
void draw()
{
System.out.println("Triangle: draw" ) ;
}
void FlipVertical()
{
}
void FlipHorizontal()
{
}
}
class Square extends Shape
{
void draw()
{
System.out.println("Square: draw" ) ;
}
}
public class poly2
{
public static void drawShape ( Shape shapeObject )
{
shapeObject.draw() ;
}
public static void main(String args[] )
{
Circle circleObject = new Circle() ;
Square squareObject = new Square() ;
drawShape( circleObject ) ;
drawShape( squareObject ) ;
}
}
Output:
$ ./run1.sh poly2
Shape: draw
Square: draw
Single Rooted Hierarchy
In Java all objects are derived from the class "Object" .
File: "Object1.java"
class Person
{
int id ;
String name ;
void setId( int idP )
{
id = idP ;
}
void setName( String nameP )
{
name = nameP ;
}
int getId( )
{
return( id ) ;
}
String getName( )
{
return name ;
}
public String toString()
{
return( "Employee. Name: " + name + " Id:" + id ) ;
}
}
public class Object1
{
public static void main(String args[] )
{
Person personObject = new Person() ;
personObject.setId ( 1 ) ;
personObject.setName ( "Larry Holmes" ) ;
System.out.println( personObject.hashCode() ) ;
System.out.println( personObject ) ;
}
}
Output:
[amittal@hills Introduction]$ java Object1
1608446010
Employee. Name: Larry Holmes Id:1
Even though we have not explicitly stated so the "Person" class is derived from the base class "Object" . We can use some of the base class methods such as "hashCode()" and override the "toString" method. We can then give the "personObject" to "println" and the "toString" method will be called giving an output that is more readable than the default printout which just prints the address.
Binary Arithmetic
We work with numbers using base 10 or the decimal system. When we have a number such as
245
This is interpreted as :
2 * 10 to power 2 + 4 * 10 to power 1 + 5
If we have a base such as 10 then the digits range from 0 to 9 ( 0 to base -1 ) . When we go from right to left we
multiply the digit by the base to the power increasing the power by 1 each time.
Computer work with base 2 also called the binary system. The digits are 0 and 1 . The number
101
is 5 in decimal system( 1*2 power2 + 0 + 1 ) .
If we have 3 digits then the unsigned number will look like below:
000 -- 0
001 -- 1
010 -- 2
011 -- 3
100 -- 4
101 -- 5
110 -- 6
111 -- 7
Two's complement.
A negative binary number is represented in two's complement. In this scheme the leftmost bit is "1" and represents the negative number. The right most bits are inverted and then a "1" is added to get the negative number amount.
Ex:
101
The bit "01" are inverted to form "10" and then a "1" is added to form "11" so the number "101" is actually "-3" . If we have 3 digits then the signed numbers look as below:
000 0
001 1
010 2
011 3
100 -4
101 -3
110 -2
111 -1
Base 16 also called Hexadecimal system . The digits are 0-9, A-F with A representing 10 and F representing 15.
Examples:
F0 - 240
AF - 175
Object Lifetime
Let us study how a program gets run in general in the computer.
The operating system will load the program into a block of RAM. This block is reserved for the program only. A section of this block is for the code instructions . We also need to store the data in the RAM. There are 2 sections for that: heap and the stack. Let's say we have a function calling another function.
Pseudo Code:
int sum( int x1, int x2 ) //1
{
return ( x1 + x2 ) ; //2
}
main()
{
int x3 ;
x3 = sum( 3 , 4 ) ; //3
x3 = 5 //4
}
The CPU will take one instruction from the RAM and execute it and then fetch the next instruction and execute it. Let's assume we are are at line 3 ( comment //3 ) . Now we need to make a jump to another section in our code and after the function "sum" completes we need to come back to line 3 . Also we need to store the values 3 and 4 so that we can pass the to the sum function . The CPU is not going to take care of these details by itself . We store this info in the stack.
Stack
4
3
Line 3
Now we switch control to line 1 ( comment //1 ) . The function pops off the values 3 and 4 and assigns them to x1 and x2 . Now it adds them together but needs to pass this result back to x3. Let's use the stack again for this.
Stack
7
We had the location of the line where we wanted to go back in the stack as "Line 3" and go to that line 3 and then pop the 7 from the stack and assign it to "x3" . It's possible that we would need to go to another function from the "sum" function . We can keep creating new stack frames to store the return address and pass the parameters going in and the return value coming back. We can see that the number of parameters could be large and the return object could be large also and there could be many levels of function calling so the stack is used for that. Another use of the stack is to store local variable that are created. In Java all the primitive variables get stored in the Stack.
Ex:
public static void main(String args[] )
{
int x1 ;
int x2 ;
}
The variables "x1"' and "x2" will get stored on the stack. There is a requirement for the stack storage and that is the size of the objects must be known at compile time. Once the variables go out of scope then their memory is released from the stack.
The heap is used for storing variables that have been allocated dynamically. In Java we can do that using the "new" keyword.
void function1()
{
String str1 = new String( "Test" ) ;
}
Now the memory for the "String" is allocated on the heap instead of on the stack. When the function ends then the memory is not released automatically. So how is the memory released ? This is where the Garbage Collector comes in. This is another program ( thread ) than will run and release the memory for unused objects on the heap. We do not know when it will run or if it will run. That is up to the JVM. The programmer does not have to release the memory manually.
Let us see a working example showing the garbage collector in action.
File: "heap1.java"
class Person
{
int[] arr1 = new int[1000000] ;
protected void finalize()
{
try
{
System.out.println( "Finalize method getting called." ) ;
Thread.sleep( 1000 ) ;
}
catch( Exception ex1 )
{
}
}
}
public class heap1
{
public static void function1()
{
while( true )
{
Person personObject = new Person() ;
}
}
public static void main(String args[] )
{
function1() ;
//System.gc() ;
}
}
Output:
[amittal@hills Introduction]$ java heap1
Finalize method getting called.
Finalize method getting called.
Finalize method getting called.
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at Person.<init>(heap1.java:5)
at heap1.function1(heap1.java:31)
at heap1.main(heap1.java:40)
[amittal@hills Introduction]$
The class Object( all classes in Java are derived from this class ) has a method called "finalize" . This method is called by the garbage collector when the it decides to release the memory for the object. Now let us look at the loop:
public static void function1()
{
while( true )
{
Person personObject = new Person() ;
}
}
We are running an infinite loop and creating a Person object in the statement. Basically we are creating a lot of objects very fast. In the Person class we have:
int[] arr1 = new int[1000000] ;
An array "arr1" is getting created very fast that is of size 1 MB and since each integer is 4 bytes the size is 4 Mb. That's a large object. Now the garbage collector will kick in at some point and try to delete the Person objects. Remember just before deleting the Person object it will call the "finalize"method.
protected void finalize()
{
try
{
System.out.println( "Finalize method getting called." ) ;
Thread.sleep( 1000 ) ;
}
catch( Exception ex1 )
{
}
}
We have put a delay of 1 second in the "finalize" method. The garbage collector will call the "finalize" method and will pause for 1 second. In that second more Person objects will get created. The Garbage Collector will not be able to free up the objects fast enough and the heap will run out of memory as shown in the output.
Exception Handling
An OOP language will usually provide error handling features that are "try" , "catch" based. Let us look at the procedural style .
int result ;
result = method1() ;
//check error code
result = method2() ;
//check for error code
We call functions and every time we call a function it returns a return code that we check for. We check if an error occurred or not. If we call a function again then we check again. This involves a lot of lines to check for errors . The OOP approach is to separate the error handling from the code by using the following approach:
try
{
method1() ;
method2()
}
catch( Exception ex )
{
//Write code to handle the error
}
File: "except1.java"
import java.util.* ;
public class except1
{
public static void method1() throws Exception
{
int x1 = 4 ;
if ( x1 == 4 )
throw new Exception("From method1.") ;
}
public static void function1()
{
/*
int result ;
result = method1() ;
//check error code
result = method2() ;
//check for error code
*/
try
{
method1() ;
//code ...
//...
System.out.println("Inside the try block." ) ;
}
catch( Exception except )
{
System.out.println("Error caught." + except.toString() ) ;
}
}
public static void main(String args[] )
{
function1() ;
}
}
Output:
[amittal@hills Introduction]$ java except1
Error caught.java.lang.Exception: From method1.
The above code calls "method1" . The method1 throws an exception with the statement:
if ( x1 == 4 )
throw new Exception("From method1.") ;
If a method want to indicate that it has encountered an error then it throws an exception . The rest of the method is not executed . Now the control goes to "function1" . Since "method1 was called inside the "try" block the control now goes to the "catch" part.The line
System.out.println("Inside the try block." ) ;
is never executed. The catch part prints:
System.out.println("Error caught." + except.toString() ) ;
If we don't catch the exception then the exception will bubble up and the program will exit.
File: "except2.java"
import java.util.* ;
public class except2
{
public static void method1() throws Exception
{
int x1 = 4 ;
if ( x1 == 4 )
throw new Exception("From method1.") ;
}
public static void function1() throws Exception
{
/*
int result ;
result = method1() ;
//check error code
result = method2() ;
//check for error code
*/
method1() ;
//code ...
//...
System.out.println("Inside the try block." ) ;
}
public static void main(String args[] ) throws Exception
{
function1() ;
}
}
Output:
[amittal@hills Introduction]$ java except2
Exception in thread "main" java.lang.Exception: From method1.
at except2.method1(except2.java:11)
at except2.function1(except2.java:26)
at except2.main(except2.java:35)
[amittal@hills Introduction]$
Each function threw the exception and the exception came to the "main" function that exited out with an error.
Generics
import java.util.* ;
class myContainer<T>
{
T obj1 ;
myContainer(T param1)
{
obj1 = param1 ;
}
}
public class generic1
{
public static void main(String args[] )
{
String str1 = "Testing" ;
myContainer<String> cont1 = new myContainer<String>(str1) ;
//cont1.obj1 = 5 ;
System.out.println(cont1.obj1 ) ;
}
}
Generics is the ability to specify a type to a class or a method. We can give values to the arguments when calling a function and similarly we can specify a value for the class. However this value is not a value like 10 or a string. It is the name of a class.
class myContainer<T>
{
T obj1 ;
myContainer(T param1)
{
obj1 = param1 ;
}
}
In the above section of the code we are stating that the class "myContainer" is going to take a parameter that is a class type. When creating an object of type "myContinaer" we can state:
String str1 = "Testing" ;
myContainer<String> cont1 = new myContainer<String>(str1) ;
In the above section we are giving the type "String" as the type parameter to our class "myContainer". Generics allow for compile time checking and are used extensively in the Java collections.
Scope
import java.util.* ;
public class scope1
{
int c1 ;
boolean b1 ;
String str1 ;
public static void main(String args[] )
{
/* int x1 = 4 ;
{
int x1 = 5 ;
} */
int c2 ;
//System.out.println( c2 ) ;
scope1 scope1Object = new scope1() ;
System.out.println( scope1Object.c1 + " : " + scope1Object.b1 +
" : " + scope1Object.str1 ) ;
} //public static void main(String args[] )
}
If we have a variable "x1" in the outer scope then we cannot have a variable by the same name in an inner scope.
Wrappers
There are many instances in Java where we want to use the primitive type but we can't because we need a class instead of a primitive type. As an example the container "ArrayList" only holds class types. We cannot do:
ArrayList<int> list1 = new ArrayList<int>() ;
We could create a class that can hold integers but that has already been done for us by Java. The "Integer" class is the wrapper class for int. And now we can do:
ArrayList<Integer> list1 = new ArrayList<Integer>() ;
list1.add( new Integer(4 ) ) ;
Java goes one step further and lets us simply do:
list1.add( 4 ) ;
Java will automatically take our number ad do "new Integer(4)" . This is called "auto boxing" and the converse( taking an Integer object and converting it to a primitive) is called "unboxing" .
File: "wrapper1.java"
import java.util.* ;
public class wrapper1
{
public static void main(String args[] )
{
int x1 = 4 ;
Integer obj1 = 5 ; // new Integer( x1 ) ;
ArrayList< Integer > list1 = new ArrayList<Integer> () ;
list1.add( 3 );
list1.add( 4 );
int j1 = list1.get(0 ) ;
System.out.println( "j1:" + j1 ) ;
}
}
Output:
[amittal@hills Introduction]$ java wrapper1
j1:3
1)
import java.util.* ;
public class cont1
{
public static void processList( List<Integer> list1 )
{
//Run a single loop to find and print the max , min and the sum of the integer
//elements in the list
int sum , min , max ;
sum = list1.get(0) ;
min = list1.get(0) ; max = list1.get(0) ;
//TO DO
System.out.println("min:" + min + " max:" + max +
" sum:" + sum) ;
}
public static void findDuplicateList( List<Integer> list1 )
{
//Run 2 nested for loops to find and print the duplicate
for( int i1=0 ; i1<list1.size() ; i1++ )
for( int j1=0 ; j1<list1.size() ; j1++ )
/ /TO DO
}
public static void main(String args[] )
{
//Sample for loop
for( int i1=0 ; i1<3 ; i1++ )
System.out.println("i1:" + i1 ) ;
System.out.println( "------------------------------" ) ;
//Sample nested loop
for( int i1=0 ; i1<3 ; i1++ )
for( int j1=0 ; j1<2 ; j1++ )
System.out.println("i1:" + i1 + " j1:" + j1 ) ;
System.out.println( "------------------------------" ) ;
ArrayList<Integer> list1 = new ArrayList<Integer>() ;
list1.add( 1 ) ; list1.add( 11 ) ;list1.add( 2 ) ;
list1.add( 9 ) ; list1.add( 2 ) ;list1.add( 4 ) ;
processList( list1 ) ;
findDuplicateList( list1 ) ;
}
}
2)
File: "Inherit2.java"
class bird
{
String name ;
public String toString()
{
return( "" ) ;
}
}
class landBird extends bird
{
int maxWeight ;
//return the string
public String toString()
{
//TO DO
//Return a string with the name and the maxWeight
}
}
class canFlyBird extends bird
{
int altitudeCanFlyAt ;
//return the string
public String toString()
{
//TO DO
//return a string with the name and altitudeCanFlyAt
}
}
public class Inherit2
{
//TO DO Write code to print information about bird
public static void printInfo( bird birdObj )
{
}
public static void main(String args[] )
{
//TO DO
//Create a land bird object named "bird1" . Assign it's name to be
//"Ostrich" and it's maxWeight to be 156
//Create a can fly bird object named "bird2" . Assign it's name to be
// "Eagle" and it's altitudeCanFlyAt to be 37000
//Call printInfo for both bird1 and bird2
}
}
3)
Fill in the function below to sort an array that can only hold 0's and 1's . The zeroes need to come before ones.
import java.util.* ;
public class sort1
{
public static void simpleSort( int arr1[] )
{
}
public static void main(String args[] )
{
//int arr1[] = { 1, 1 ,0, 0, 1, 0, 1, 1 } ;
//Should be { 0, 0, 0, 1, 1 , 1, 1, 1 {
int arr1[] = { 1, 1 ,0, 0 } ;
//Should be {0,0, 1,1}
simpleSort( arr1 ) ;
System.out.println( Arrays.toString( arr1 ) ) ;
}
}
Output:
[amittal@hills Introduction]$ java sort1
[0, 0, 1, 1]
1)
import java.util.* ;
public class cont1
{
public static void processList( List<Integer> list1 )
{
//Run a single loop to find and print the max , min and the sum of the integer
//elements in the list
int sum , min , max ;
sum = list1.get(0) ;
min = list1.get(0) ; max = list1.get(0) ;
for( int i1=1 ; i1 < list1.size() ; i1++ )
{
if( list1.get(i1) > max )
max = list1.get(i1) ;
if( list1.get(i1) < min )
min = list1.get(i1) ;
sum += list1.get(i1) ;
}
System.out.println("min:" + min + " max:" + max +
" sum:" + sum) ;
}
public static void findDuplicateList( List<Integer> list1 )
{
//Run 2 nested for loops to find and print the duplicate
for( int i1=0 ; i1<list1.size() ; i1++ )
for( int j1=0 ; j1<list1.size() ; j1++ )
if( i1 != j1 )
{
if( list1.get( i1 ) == list1.get(j1 ) )
System.out.println("Duplicate element:" + list1.get(i1) ) ;
}
}
public static void main(String args[] )
{
//Sample for loop
for( int i1=0 ; i1<3 ; i1++ )
System.out.println("i1:" + i1 ) ;
System.out.println( "------------------------------" ) ;
//Sample nested loop
for( int i1=0 ; i1<3 ; i1++ )
for( int j1=0 ; j1<2 ; j1++ )
System.out.println("i1:" + i1 + " j1:" + j1 ) ;
System.out.println( "------------------------------" ) ;
ArrayList<Integer> list1 = new ArrayList<Integer>() ;
list1.add( 1 ) ; list1.add( 11 ) ;list1.add( 2 ) ;
list1.add( 9 ) ; list1.add( 2 ) ;list1.add( 4 ) ;
processList( list1 ) ;
findDuplicateList( list1 ) ;
}
}
2)
File: "Inherit2.java"
class bird
{
String name ;
public String toString()
{
return( "" ) ;
}
}
class landBird extends bird
{
int maxWeight ;
//return the string
public String toString()
{
return ( "Name:" + name + " maxWeight:" + maxWeight ) ;
}
}
class canFlyBird extends bird
{
int altitudeCanFlyAt ;
//return the string
public String toString()
{
return ( "Name:" + name + " max Altitude:" + altitudeCanFlyAt ) ;
}
}
public class Inherit2
{
public static void printInfo( bird birdObj )
{
System.out.println( birdObj ) ;
}
public static void main(String args[] )
{
landBird bird1 = new landBird() ;
bird1.name = "Ostrich" ; bird1.maxWeight = 156 ;
canFlyBird bird2 = new canFlyBird() ;
bird1.name = "Eagle" ; bird2.altitudeCanFlyAt = 37000 ;
printInfo( bird1 ) ;
printInfo( bird2 ) ;
}
}
3)
import java.util.* ;
public class sort1
{
public static void simpleSort( int arr1[] )
{
for( int i1=0 ; i1< arr1.length ; i1++ )
{
if ( arr1[i1] == 0 )
continue ;
boolean foundZero = false ;
for( int j1=i1+1 ; j1< arr1.length ; j1++ )
{
if ( arr1[j1] == 0 )
{
foundZero = true ;
arr1[i1] = 0 ;
arr1[j1] = 1 ;
break ;
}
} //for
if ( !foundZero )
break ;
}//for
}
public static void main(String args[] )
{
//int arr1[] = { 1, 1 ,0, 0, 1, 0, 1, 1 } ;
int arr1[] = { 1, 1 ,0, 0 } ;
//Should be { 0, 0, 0, 1, 1 , 1, 1, 1 {
simpleSort( arr1 ) ;
System.out.println( Arrays.toString( arr1 ) ) ;
}
}
References