Skip to content

abhisheks-gh/Java-SE-8

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Java SE 8 - Long Term Support (LTS)

The current LTS versions of Java are Java 7, Java 8, Java 11, and soon, Java 17. Java 11 and Java 17

A Java LTS (long-term support) release is a version of Java that will remain the industry standard for several years. To give you an example of this, Java 8 was released in 2014, it will continue to receive updates until 2020, and extended support will end by 2025.

Why Java is "Robust" ?

When we write code, we are going to go through many processes. We are going to write what's called source code & then we can run that source code through compiler. A compiler is another program that takes our source code and turns it into something that the Java Platform can understand & then eventually it will run that code. So, with it being robust, there are actually different checks that happen to make sure that our code run well.

There is COMPILE TIME CHECKING, so we can find out if we did anything syntactically wrong at that time & then there is RUNTIME CHECKING, things that can be resolved until the program is actually running.

For example, if a database is down, we don't really know that when we are compiling the program, we can only discover that when the program is running.

Why Java is "Secure" ?

  • Everything in Java is Sandboxed. A sandbox is an isolated testing environment that enables users to run programs or open files without affecting the application, system or platform on which they run. Software developers use sandboxes to test new programming code. Cyber-security professionals use sandboxes to test potentially malicious software.

  • There are lots of efforts in protecting the memory that is being used by Java & so on.

Why Java is "Architecturally neutral" & "Portable" ?

This is also known as platform independence. Java popularise the term "WRITE ONCE RUN ANYWHERE". So, the idea is that we can write a program on Windows & we can run it on UNIX or on MAC PC & we can use the same code that we wrote for both. The way that they are able to achieve platform independence is that we never compile our programs into a machine code that's made up for a specific architecture. Let's say we want to create an .exe file for windows in other programming languages, we'll write the code then we'll compile it for that underline architecture or the underline operating system.

But, with Java we don't have to do that, we write our code once, it gets compiled into something that java platform understands (Java bytecode) & that way it achieves platform independence.

Why Java is "interpreted", "threaded" & "dynamic" ?

Java platforms includes an interpreter & so it's going to interpret the bytecode that we write.

Byte Code: The code that is going to run through the Java compiler.

So, that byte code is going to be interpreted.

It's threaded that helps it to achieve high performance & it is dynamic means that it can resolve at the runtime how things should be linked together.

In other words, lets say we are writing some code that's going to use a database service. At runtime, we can actually choose what that database service is going to be & so that our code is more dynamic.

Resources (Features of Java):
https://www.oracle.com/java/technologies/introduction-to-java.html

The Java Platform

Java platform has two different components:

  • The Java Virtual Machine (JVM) - What's actually going to run our program. JVM is coded in such a way that it's going to interpret our source code for the underlying operating system. The JVM for different architectures are different. For example, JVM in a MAC operating system and a Windows 64-bit system are going to be different. In this way it helps us to achieve platform independence.

  • The Java Application Programming Interface (API) - We can think of API as code written by someone else that we get to use. It's just bunch of libraries of code.

*NOTE: Another thing that is interesting about Java platform is that it can be used to run languages that are Java. So, if you have ever heard of programming languages like Groovy, Scala, Clojure, etc. These are programming languages that are Java but have been return for the Java platform.

So at higher level, this is how we are going to interact with the Java platform. First, we are going to write a Java application, which is in part going to use the Java API. The great thing about the Java API is that it not only uses the code that we did right, but the Java API is already bundled with the Java platform. If we are using a ton of code from the Java API, we don't have to bundle it with our own application. It's already on the client's computer which has the Java platform.

        Our JAVA APPLICATION
        JAVA API
        JAVA VIRTUAL MACHINE
        HARDWARE / OS

Java Runtime Environment (JRE)

Java API and Java Virtual Machine (JVM) together makes JRE.

The Java Platform Cont.

In Java, applications are both compiled and interpreted.

  • The Java compiler compiles our application source code to an intermediate language that is interpreted by the JVM.
  • The intermediate language is called bytecode.
  • Bytecode is not machine code that gets executed directly.
  • Instead, the JVM translates (i.e., interprets) the bytecode into machine code, which gets run by the actual platform.
  • In other words, bytecode is like the "machine code" for the JVM.
    • The bytecode is platform independent and can therefore be interpreted by any JVM.
    • JVMs are built specific to particular platforms (hardware and OS).

Rules for main method are

A) It must be public and static (in any order, as long as it is to the left of the return type).
B) It must return "void."
C) The name "main" must be written in all lowercase.
D) It must declare a String array parameter (where the square brackets may be written to the right of String or to the right of the identifier).
E) The identifier for the String array may be named anything we'd like (though, by convention, it is typically "args").

Objects vs. Primitives

  • Primitives behave differently in that they don't have procedures (behaviour).
  • Primitives only have state (no methods), and they are limited to one piece of state (data).

Primitive types in Java

Java has 8 primitive data types; char, boolean, byte, short, int, long, float, and double. For this exercise, we'll work with the primitives used to hold integer values (byte, short, int, and long):

  • A byte is an 8-bit signed integer.
  • A short is a 16-bit signed integer.
  • An int is a 32-bit signed integer.
  • A long is a 64-bit signed integer.

Default Values

It's not always necessary to assign a value when a field is declared. Fields that are declared but not initialized will be set to a reasonable default by the compiler. Generally speaking, this default will be zero or null, depending on the data type. Relying on such default values, however, is generally considered bad programming style.

The following chart summarizes the default values for the above data types.

image

Creating (Instantiating) object

  • The following code creates three different types of Java objects:

    String s = new String();
    Customer c = new Customer();
    Circle circle = new Circle();
    

Lets consider

    Customer c = new Customer(); 

// Customer = Reference Type
// c = Reference Variable
// new = "new" Keyword
// Customer() = Object Type

  • The technical term for creating an object is instantiation.
  • Another important note about creating objects is to understand when the object is actually created.
    • When a reference is defined, this does not create the object.

    • Defining a reference only means that it is ready to point to an object.

    • The object is created when "new" is used.

      Circle c;           // No object exists yet
      c = new Circle();   // Object created and assigned to 'c'
      
    • Single reference can be reused over & over again.

        Customer c = new Customer("CID10394");   // Same reference - different objects
        Customer c = new Customer("CID23511");
        Customer c = new Customer("CID39203");
        Customer c = new Customer("CID88374");
      
      • Depending on other code in our application, the old object that our reference was pointing to may be destroyed.
    • When you create an object with the "new" keyword, you are actually calling a method called a constructor.

    • The constructor is responsible for the initialization of an object when it is created.

    • Constructor methods can only be called during instantiation.

    • All object have at least one constructor but can have as many as they want or need.

    • The benefit of multiple constructor is flexibility.

      • Depending on what information you have when you create an object, you may have the ability to pass in all, some, or no data.

Default Constructors

  • For every class, Java provides a default constructor. But, if we create our own constructor for that class then that default constructor is no longer there. So, if someone calls that default constructor anymore in the program (after the creation of our new constructor), the whole code for that particular class will break at the point where the default constructor is called & the program will not even compile.

Default vs. "No-Arg" Constructor

  • A "no-arg" constructor is one that lists no parameters (it takes no arguments). A "default" constructor is a "no-arg" constructor that is created by the compiler, in the event that you don't explicitly create one yourself.

Creating Object Types

There are plenty of object types (like String and Point) created in Java and ready for you to use. These object types are part of Java API.

  • An object type is called a class.

Static methods have two primary purposes:

  1. They are used to access (update or fetch) class variable data.

    • Although this can be done with any instance of the class, it is considered more appropriate to use class method for this purpose.

    • In some cases, we may not have an instance of an object created before the data is needed.

    • We don't have to create an object just to be able to access class variables.

  2. Static methods provide functionality without the need for an object / instance.

    • For example, mathematical formulas are great reasons to have static methods.
    • Should you have to create an instance of some object to compute sine, cosine, or tangent?
    • Examine the Math class (java.lang.Math) to see some excellent use of static methods.

Static Reference Variables and null

If the reference variable you are working with has a null value (meaning it is not referring to an object), it's in a dangerous state. Specifically, if you access an instance member with it, you will see the dreaded "NullPointerException." This is an exception created by the JVM and unless special exception handling code is written, will result in your program quitting prematurely.

For example, the following code will result in a NullPointerException :

public static void main(String[] args) { 
String s = null;
s = s.toUpperCase(); // Oops! s is null! 
}

Static members are different. While you shouldn't write sloppy/confusing code like this, you should know (especially for the exam) that accessing static members with a null Class reference will not throw a NullPointerException. For example, the java.lang.Math class has a static method called floor:
public static void main(String[] args) {   
Math m = null;  
double result = m.floor(28.15); // Confusing code, but it works.
}

I want to stress, while the preceding code won't throw a NullPointerException, you shouldn't write code like that. It is much clearer to use the class name:
public static void main(String[] args) {   
double result = Math.floor(28.15);   
}

See the "Exception Handling" section for more details on exceptions.

Static vs Regular Initialization Block

  • Static initialization block is executed only once (when the class is loaded by JVM) whereas the regular initialization blocks are executed every time a object is initialized.
  • A static block just like an static method can only load static variables & methods.

'this' keyword

  • 'this' keyword is used to bypass the local variable (searching of value in local frame on stack) and directly go to the object associated with it.

Rules for Constructor chaining

  • this() is used for constructor chaining.
  • Parameters passed inside 'this()' decides to which constructor call should be generated.
  • Need to be done in first line of constructor.
  • Can't place it inside a method.

Automatic Garbage Collection

The java runtime environment manages memory for us!

  • We never have to destroy an object explicitly or manage the memory it uses.

  • The java runtime environment deletes objects when it determines that they are no longer being used. This process is known as garbage collection.

  • The java runtime environment's garbage collector periodically searches memory for "free" (unreferenced) objects that are no longer needed.

  • When it finds one, it removes it from the heap.

  • Garbage Collection Command
    Used to give hint to Garbage Collector that it should run.
    But, it's just a hint or suggestion and garbage collector choose to ignore it if it decides that now it's not a optimal time to run.

    System.gc();
    

Memory Leaks

  • In general, a Java memory leak happens when an application unintentionally (due to logical errors in code) holds on to object references that are no longer required. These unintentional object references prevent the built-in Java garbage collection mechanism from freeing up the memory consumed by these objects.

Default Packages in a Java Application

  1. The package that you are currently in
  2. The java.lang package
    The java.lang has classes that are fundamental to nearly every program you write.
    For example, System, String, and Integer, etc. are all part of java.lang package.

Classpath

  • A classpath is a system variable that allows you to tell the compiler and class loader where you store your files (the bytecode and other application files).

  • A classpath can contain multiple directories, JAR files, or zip files.

  • Separate files in the classpath with the correct operating system path separator(e.g., semicolons for Windows, for *nix).

  • When you compile, you can also explicitly designate a classpath for the compiler by including the classpath option.

          javac -classpath c:/temp/myclasses;. SomeClass.java
    
  • Although, it is possible to set the "classpath" environment variable, it is not recommended

    • Most projects will have unique classpath locations and if you don't change that, you might be looking for the code in an outdated directory

More on classpath

A classpath says: "here is a list of root/top level folders where code may be found." You don't have to list every single directory that has code... just the root folder(s) the compiler & JVM should start their search from. All of the subfolders are determined by package & import statements in the class.

For example, imagine that we have code in two different directories

c:\src\com\intertech\transport\Truck.class

c:\libraries\com\intertech\util\VINFormatter.class

The root code folders are:

c:\src and c:\libraries

Each class would list the subdirectories they live in, via their package statements (note that the package statement is relative from the root code folder... in other words, you don't write "src" or "libraries" as part of the package statement):

package com.intertech.transport;   
public class Truck {        
}   

and

package com.intertech.util; 
public class VINFormatter { 
}   

Now, let's say we want to compile a class in another root folder:

c:\myapp\com\intertech\inventory\InventoryManager.java

package com.intertech.inventory;    
import com.intertech.transport.Truck;   
import com.intertech.util.VINFormatter; 
public class InventoryManager { 
private Truck truck;    
private VINFormatter formatter; 
// ... more code    
public static void main(String[] args) { //... }    
}   

If we navigated to the

c:\myapp directory and just wrote

javac com\intertech\inventory\InventoryManager.java

or

java com.intertech.inventory.InventoryManager

...the compiler and JVM, respectively, would try to find Truck in c:\myapp\com\intertech\transport and VINFormatter in c:\myapp\com\intertech\util... which are the wrong directories.

So instead, we use the following compile command w/ classpath to let the compiler know it should search for the classes in different root folders: javac -classpath c:\src;\c:\libraries;. com\intertech\inventory\InventoryManager.java (the "." means the current directory is also a root folder).

... and we'd run the class with:

java -classpath c:\src;\c:\libraries;. com.intertech.inventory.InventoryManager

This classpath, combined with the package/import statements, are used by the compiler AND the JVM at runtime to find a given class.

Rules for BigInteger and BigDecimal (java.math)

  • BigInteger and BigDecimal can't use operators (like +, -, *, /) as they are objects not primitives.
  • So, instead of these operators we use methods that are present to perform the calculations.

StringBuilder and StringBuffer Classes

  • Strings in Java are MUTABLE i.e. every time we modify the value of string, the modified value is stored in a new string.
  • Java developers are encouraged to use StringBuilder and StringBuffer classes when an application needs to manipulate a lot of strings.
  • StringBuilder and StringBuffer (both in java.lang) provide a mutable sequence of characters but without the fancy string literals and operators.
  • StringBuffer is thread-safe whereas StringBuilder is not thread-safe.

"Protected" Keyword

Protected access means that the state or behavior is available to code in the same class, same package, and to subtypes in any other package. Let's look at some examples that use the following class:

package com.intertech.examples;
public class Person {
    protected void doSomething() {
        System.out.println("Doing something");
    }
}

In this first example, calling doSomething() is legal, because it is being invoked from a class in the same package.

package com.intertech.examples; 
public class PersonTester {
    public static void main(String[] args) {
        Person p = new Person();
        p.doSomething();
   }
}

Although this next example defines a class in a different package than the one which contains Person, calling doSomething() is legal because Employee extends Person

package com.something.else;
import com.intertech.examples.Person;
public class Employee extends Person {
    public void testingProtectedMethod() {
        doSomething();
    }
}

When dealing with code that is in a different package from the one that contains the protected state or behavior, it's not enough for the call to be made with a subtype object. The object must be called within a subtype class. For example, the following is illegal. Even though the object being used is an Employee (a subtype of Person), the code has been defined in a class that is not a subtype of, nor in the same package, as Person.

package com.something.else;
public class EmployeeTester {
    public static void main(String[] args) {
        Employee e = new Employee();
        e.doSomething() // ILLEGAL
    }
}

The most surprising rule, however, is that when accessing protected state or behavior from a different package, it can only be accessed with an object reference that is the same type as the class it is defined in. For example, here we have an Employee class. The class is defined in a different package from Person, but it is a subtype of Person. Therefore, the doSomething() method is available to Employee. It is NOT, however, available for a Person object (where the method is defined!) or a Musician object (another subtype of Person) when they are used in this Employee class:

package com.something.else;
import com.intertech.examples.Person;
import com.intertech.examples.Musician; /* Musician extends Person */
public class Employee extends Person {
    public void testingProtectedMethod() {
        doSomething();  // LEGAL
    }

    public static void main(String[] args) {
        Employee e = new Employee();
        e.doSomething(); // LEGAL
        Person p = new Person();
        p.doSomething(); // ILLEGAL
        Musician m = new Musician();
        m.doSomething(); // ILLEGAL
  }
}

"super" keyword and First Statement Rule

I mentioned that when chaining constructors, the call to the super constructor must be the first statement in the constructor. And that's true. Assuming the Person class contains a no-arg constructor, the following constructor is valid:

public Employee() {
    super();
    deptId = 281;
}

...whereas this one is not:

public Employee() {
    deptId = 281;
    super();
}

I want to clarify that using super with the dot operator, to invoke a super type's method (other than a constructor) or access its state, is NOT bound by the first statement rule.

Both of these examples are valid:

public void driveLikeDad() {
    super.drive();
    doSomethingElse();
}

and...

public void driveLikeDad() {
    doSomethingElse();
    super.drive();
}

Class/Object Invocation Order

When an object in an inheritance chain is instantiated for the first time, this is the order that the code is executed:

  • All of the static variables are defined in the class (with default values). For example, the code public static String firstName = "Jason"; would result in the creation of the variable firstName but in this step, it is assigned the default value of null (rather than the explicit value of "Jason"). This happens for all of the classes in the hierarchy (moving up the inheritance chain).
  • All of the static initialization blocks and explicit values assigned to static variables (such as "Jason" above), are executed in the base class, in the order they are written (from top to bottom).
  • All of the static initialization blocks and explicit values assigned to static variables are executed for the immediate child of the base class, in the order they are written (from top to bottom).
  • Step 3 is repeated all the way down the object hierarchy until the instantiated object type is reached.
  • All of the instance variables are defined with default values, for all of the classes in the hierarchy (moving up the inheritance chain).
  • All of the instance initialization blocks and explicit values assigned to instance variables are executed for the base class, in the order they are written (from top to bottom).
  • The constructor for the base class is executed.
  • All of the instance initialization blocks and explicit values assigned to instance variables are executed for the immediate child of the base class, in the order they are written (from top to bottom),
  • The constructor for the immediate child of the base class is executed.
  • Steps 8 - 9 are repeated all the way down the object hierarchy until the instantiated object type is reached.

Note that the first steps that execute "static" code will only happen once during the entire life of the class, and may have been executed before an object has been instantiated (such as when a reference variable is created or a static member is accessed).

Here's an example to illustrate the order class and object instantiation is executed. The following classes are defined in three separate files (and note that their members are organized in a haphazard manner just to make sure you're paying attention!):

Person

public class Person {
  {
    System.out.println("Person: First Instance Initialization Block");
  }
  static {
    System.out.println("Person: First Static Block");
  }
  {
    System.out.println("Person: Second Instance Initialization Block");
  }
  static {
    System.out.println("Person: Second Static Block");
  }
  public Person() {
    System.out.println("Person()");
  }
  public void sayHello() {
    System.out.println("Person: Hello!");
  }
}

Employee

public class Employee extends Person {
{
System.out.println("Employee: First Instance Initialization Block");
}
static {
System.out.println("Employee: First Static Block");
}
{
System.out.println("Employee: Second Instance Initialization Block");
}
static {
System.out.println("Employee: Second Static Block");
}
public Employee() {
System.out.println("Employee()");
}
}

Instructor

    public class Instructor extends Employee {
    {
    System.out.println("Instructor: First Instance Initialization Block");
    str1 = "First Instance Initialization String";
    }
    static {
    System.out.println("Instructor: First Static Block");
    str1 = "First Static Initialization String";
    }
    public static String str1 = "Explicit Initialization String";
    {
    System.out.println("Instructor: Second Instance Initialization Block");
    str1 = "Second Instance Initialization String";
    }
    static {
    System.out.println("Instructor: Second Static Block");
    str1 = "Second Static Initialization String";
    }
    public Instructor() {
    System.out.println("Instructor()");
    }
    }

The following code...

Instructor i =  new Instructor();
i.sayHello();
System.out.println(Instructor.str1);

...would generate this output :
(assuming the Person, Employee, and Instructor classes have not been previously referenced, otherwise the static blocks wouldn't be executed again)

Person: First Static Block

Person: Second Static Block

Employee: First Static Block

Employee: Second Static Block

Instructor: First Static Block

Instructor: Second Static Block

Person: First Instance Initialization Block

Person: Second Instance Initialization Block

Person()

Employee: First Instance Initialization Block

Employee: Second Instance Initialization Block

Employee()

Instructor: First Instance Initialization Block

Instructor: Second Instance Initialization Block

Instructor()

Person: Hello!

Second Instance Initialization String

NOTE: If another Instructor object is instantiated, the static blocks would be skipped, since that only happens once during the life of the class.

Being Available vs. Being Inside

Throughout this section, I've mentioned that a reference variable may only send messages that are available to its type. In other words imagine that a Person class has a setFirstName method, and an Employee class has a setSalary method:

Person p = new Employee();
p.setFirstName("Jane"); // Legal since setFirstName is available to Person
p.setSalary(80_000);    // Illegal since salary is available to an Employee, not a Person.

I want to be clear, however, that "being available to an object" is not the same as "being declared inside an object." Let's say we have a subtype of Employee called Instructor. If we create an Employee reference variable, we can call any member available to Employee - including those members it inherits. For example:

Employee e = new Instructor();
e.setFirstName("Jane"); 

... is legal because Employee inherits the setFirstName method from Person. It wasn't declared inside Employee, but it was available to Employee. So that's why I specifically say that a reference variable is limited to the members that are available to, rather than defined inside, the class itself.

Remember: All Interface Methods are "public."

While an IDE or a compiler will complain if you make this mistake, you won't have these tools when taking the exam. So remember: all interface methods are public, even if they don't use the public keyword.

Therefore given the following legal interface:

public interface Payable {
    double pay();
}

Unlike interface methods, concrete implementations MUST include the public modifier. Therefore, the following code will not compile because pay() is missing the "public" modifier.

public class Consultant implements Payable {
    double pay() {
        return 80_000.0;
    }
}

The correct code is:

public class Consultant implements Payable {
    public double pay() {
        return 80_000.0;
    }
}

Multi-dimensional arrays

  • Balance the Brackets

    • Make sure both sides are balanced with the same number of square brackets:

      int[][] credentials = new int[3][2]; // legal int[][] credentials2 = new int[3]; /* Illegal. Missing the 2nd dimension */

  • Square Bracket Placement
    Just like regular arrays, the square brackets can go to the right of the type, or the right of the identifier:

int[][] credentials = new int[3][2]; or

int credentials[][] = new int[3][2];

They can be split up as well (though this is uncommon). For example a 3 dimensional array could legally be declared as:

int[] moreStuff[][] = new int[3][3][2]; or

int[][] moreStuff[] = new int[3][3][2];

Initializing the Final Dimension The first dimension must be given a size during its definition. It is legal to initialize the other dimensions after the array has been defined. For example the code below is legal.

int[][][] moreStuff = new int[3][3][];
moreStuff[0][2] = new int[2]; // Line A
moreStuff[0][2][0] = 0;
moreStuff[0][2][1] = 1;

While this is legal, note that Line A is adding a 3rd dimension to only one member of 2nd dimension. In other words, if you tried to access another 2nd dimension value and add an int to its 3rd dimension, it would throw a NullPointerException at runtime:

moreStuff[0][1][0] = 0; /* ILLEGAL! Only [0][2] has a third dimension. */
There can't be any gaps of size definition between the first dimension and the subsequent dimensions: The following three lines of code are illegal and would not compile:

int[][][] moreStuff = new int[3][][3];
int[][][] moreStuff2 = new int[][3][3];
int[][][] moreStuff3 = new int[][][3];