Notes for certification exam 1Z0-808 Java SE 8 Programmer I


Topics interesting only for Java programmers

Link to this posting

Postby Ursego » 05 Mar 2019, 07:37

Notes for self-preparation to the cerification exam 1Z0-808 Java SE 8 Programmer I. They contain excerpts from the following books:
• Mala Gupta. OCA Java SE 8 Programmer I Certification Guide
• Jeanne Boyarsky, Scott Selikoff. OCA Oracle Certified Associate Java SE 8 Programmer Study Guide Exam 1Z0-808
It's a good idea to read these books, but, for me, it was not realistic to read them a few times, so I wrote out their main thoughts for further re-reading. I have passed the exam successfully, but, maybe, my notes will help more lazy people. :lol:

I fact, these materials are very useful for all students, learning Java - even if they do not intend to receive the certificate. But read the notes only after you have read a beginner's book since they don't explain the basics. I wrote out only some "more interesting" stuff. The notes cover in depth the following topics:
• Wrapper classes, boxing/unboxing
• String
• StringBuilder
• Array (incl. multidimensional)
• ArrayList
• Working with Dates and Times
• Object-oriented programming
• Method Overloading
• Constructor
• Method Overriding
• Interface (incl. static and default methods)
• Polymorphism
• Exceptions

To see the keywords colored, save two the following code boxes in a text file and open it in a Java compiler (or in Notepad++ and select in the menu: Language > J > Java).

Code: Select all
char myLittleChar = 'b';
char myBigChar = Character.toUpperCase(myLittleChar);

int b = 100;
if (!b++ > 100) // compilation error - "!" cannot be applied to int, only to boolean
if (!(b++ > 100)) // OK

byte a = 40, b = 50;
byte sum = (byte) a + b; // compilation error - the cast operator has the highest precedence, so (byte) is applied to a only; the whole expr is int
byte sum = (byte) (a + b); // OK - the whole expr is byte

// Operator == can compare only vars of a same type (or comparable types):
int x = 0;
String s = null;
if (x == s)... // compilation error - the data types are incomparable because neither variable can be converted to the other variable's type
// Another example: Java does not allow you to compare String and StringBuilder using == .

public class Application {
   public static void main(String... args) {
      double price = 10;
      String model;
      if (price > 10)
         model = "Smartphone";
      else if (price <= 10)
         model = "Landline";
      System.out.println(model); // Compilation error! There is a chance that model is not initialized (because of an "if" without "else")
   }
}

// In an internal block, a method cannot define a var with the same name as an existing local var in that method:
void riverRafting() {
   String name = "m2";
   if (8 > 2) {
      String name = "m3"; // compilation error - a var with that name already exists in the method
      System.out.println(name);
   }
}

short i = 6; // compiles because short is an integral type - even though the literal 6 is int

// Boolean fields initialize to false and references initialize to null, so the output is "Empty = false, Brand = null".
public class WaterBottle {
   private String brand;
   private boolean empty;
   public static void main(String[] args) {
      WaterBottle wb = new WaterBottle();
      System.out.print("Empty = " + wb.empty);
      System.out.print(", Brand = " + wb.brand);
   }
}
   
// javac compiles a .java (text) file into a .class (bytecode) file.

// main():
public static void main(String[] args) // must be public static void!
public static void main(String args[]) // legal - the same
public static void main(String[] whatever) // legal - the argument's name is not required to be the traditional "args"
public static void main(String ... args) // works as well (vararg is converted to an array internally)
static public void main(String[] args) // legal - the placements of the keywords public and static are interchangeable. Less common and should be avoided

// From command line, expressions are not evaluated and are converted to String as are:
java MainMethod 1+2 2*3 4-3 5+1 // pass 4 String arguments "1+2", "2*3", "4-3", "5+1" - NOT "3", "6", "1", "6"!!!

// Packages:
// For classes and interfaces defined in a package, the package statement is the first statement in a Java source file (a .java file) (except comments).
// There can be a maximum of one package statement per Java source code file (.java file).
// All the classes and interfaces defined in a Java source code file are defined in the same package. They can't be defined in separate packages.
// The hierarchy of classes and interfaces defined in packages must match the hierarchy of the directories in which these classes and interfaces exist.
// To enable the Java Runtime Environment (JRE) to find your classes, add the base directory that contains your packaged Java code to the classpath.
// If you don't include an explicit package statement in a class or an interface, it's part of a default, no-name package. It is automatically imported in the Java classes and interfaces defined within the same directory on your system. Classes, which aren't defined in an explicit package, can use each other if they're defined in the same directory.
// The package name represents any folders UNDERNEATH THE CURRENT PATH!!!
// The package names are case sensitive, just like variable names and other identifiers!!!

// Can a local variable be accessed in a method, before its declaration? No. A forward reference to local variables isn't allowed.

// A byte can hold a value from -128 to 127. You can be asked that in the exam (bot only about byte - you don't need to remember ranges of other numeric types).

// The default type of integer (that is, nondecimal) number literals is int. To designate an integer literal value as a long value, add the suffix L or l to the literal value.
// The default type of decimal number literals is double. To designate a decimal literal value as a float value, add the suffix F or f to the literal value.

long max = 3123456789; // DOES NOT COMPILE - the literal 3123456789 is treated as int and, so, is out of range.
long max = 3123456789L; // compiles - now Java knows the literal is a long

// Smaller data types (byte, short, and char) are first promoted to int any time they're used with a Java binary arithmetic operator, even if neither of the operands is int. Unary operators are excluded from this rule. For example, applying ++ or += to a short value results in a short value.

// What is the data type of x + y ?
double x = 39.21;
float y = 2.1;
// This is actually a trick question, as this code will not compile! FLOATING-POINT LITERALS ARE ASSUMED TO BE DOUBLE, unless postfixed with an f, as in 2.1f. If the value was set properly to 2.1f, the code would compile.

// Keep an eye out for questions on the exam that use the logical complement operator or numeric values with boolean expressions or variables. Unlike some other programming languages, in Java 1 and true are not related in any way, just as 0 and false are not related.

// The result of the assignment is an expression by itself, equal to the assigned value. For example, the following snippet of code is perfectly valid, if not a little odd looking:
long x = 5;
long y = (x = 3);
System.out.println(x); // Outputs 3
System.out.println(y); // Also, outputs 3
// The key here is that the expression (x = 3) does two things. First, it sets the value of the variable x to be 3. Second, it returns a value of the assignment, which is also 3.

// Unary operators have a higher precedence than multiplication operators and addition operators.

// Underscores can be inserted to numeric literals only between digits - not as the first or last symbol, and not just before/after decimal dot.

// Java is an interpreted language because it gets compiled to bytecode. A key benefit is that Java code gets compiled once rather than needing to be recompiled for different operating systems. This is known as "write once, run everywhere". On the OCP exam, you'll learn that it is possible to write code that does not run everywhere. For example, you might refer to a file in a specific directory. If you get asked on the OCA exam, the answer is that the same class files run everywhere.

// If two the parts of a ternary operator have the same type, then that type is returned by the whole ternary operator:
<bool> ? <int> : <int> // int
<bool> ? <Dog> : <Dog> //Dog
// If they have different numeric primitive types, then the larger of them defines the type, returned by the whole ternary operator:
<bool> ? <int> : <long> // long
<bool> ? <float> : <double> // double
// If one part is a boxed number, and second - a primitive type, then the object is automatically unboxed:
<bool> ? <Byte> : <int> // int (not Integer!) - it's larger than byte, into which Byte was unboxed
// Two the parts must be assignment-compatible:
<bool> ? <boolean> : <int> // ERROR
// If two the parts of a ternary operator have different reference types, then the whole ternary operator returns the most specific super-type to which both the classes are assignable (i.e. their closest ancestor):
<bool> ? <Dog> : <Cat> // Mammal
<bool> ? <Dog> : <Crocodile> // Animal
<bool> ? <Dog> : <Book> // Object

// Modulo: <number> % <a larger number> returns <number>. For example: 5%6 returns 5.

// ####### LOOPS:

while (<condition>) { // curly braces must be used always - even if there is only one statement in the loop
   ...
}

do { // curly braces must be used only if there are 2 or more statements in the loop
   ...
} while (<condition>);

// Regular "for" loop:
for (int i = 0; i < the_array.length; i++) {
   int value = the_array[i];
   System.out.println(value);
}

// The variables in the initialization block must all be of the same type:
for(long y = 0, int x = 4; x < 5 && y < 10; x++, y++) { // DOES NOT COMPILE
   System.out.print(x + " ");
}

// Firstly, the condition is evaluated (a <= 20). Just after that, the loop body is executed.
// Only after that the counter in incremented (++a)!!!!!!! This loop iterates only 1 time:
for (a = 10; a <= 20; ++a) {
   a = a * 2; // a is 20 now; that will stop the loop (++a will increase it to 21)
   System.out.println(a);
}

// Enhanced "for" loop (you can read it as, "for each value in the array"):
for (int value : theArray) {
   System.out.println(value);
}

// Enhanced "for" loop does not work with strings. But you can convert any string to a character array and iterate that:
for (char letter : lastName.toCharArray()) {
   System.out.println(letter);
}

// When you create an array of ints, the elements are initialized to zero.

// You can use any expression as an array's index, as long as it has type int.

// The Java library provides a utility class java.util.Arrays that has methods for working with arrays. Copy an array:
double[] toArray = Arrays.copyOf(fromArray, fromArray.length);
// Another way to copy an array:
System.arraycopy(fromArray, 0, toArray, 0, fromArray.length);
// Scenario:
int[] myArray = {1, 2, 3};
// Ooops... We discover that now we need 10 elements, not 3... Our steps:
int[] temp = new int[10];
System.arraycopy(myArray, 0, temp, 0, myArray.length); // now, temp is 1 2 3 0 0 0 0 0 0 0
myArray = temp // now, myArray is 1 2 3 0 0 0 0 0 0 0

public void feed(Cat[] cats) {...}
feed({new Cat("Kitty"), new Cat("Murka")}); // DOES NOT COMPILE - we pass an array elements separately while the signature requires an array
feed(new Cat[] {new Cat("Kitty"), new Cat("Murka")}); // OK - we pass a pointer to an array

// ####### Wrapper classes:

// Classes Boolean and Character directly extend Object. All the numeric wrapper classes extend the class java.lang.Number (which directly extend Object). All the wrapper classes implement the interfaces java.io.Serializable and java.lang.Comparable. All these classes can be serialized to a stream, and their objects define a natural sort order.

// You can create objects of all the wrapper classes in multiple ways:
// @ Assignment         By assigning a primitive to a wrapper class variable (autoboxing): Double d = 10.98; Character c = 'a';
// @ Constructor      By using wrapper class constructors: Double d = new Double(10.98); Character c = new Character('a');
// @ Static methods      By calling static method of wrapper classes, like, valueOf(): Double d = Double.valueOf(10.98);

// All wrapper classes (except Character) define a constructor that accepts a String argument representing the primitive value that needs to be wrapped. Watch out for exam questions that include a call to a no-argument constructor of a wrapper class. None of these classes define a no-argument constructor:
Double d = new Double(); // compilation error

// There are methods for converting a String to a primitive or wrapper class. IMPORTANT! The parse methods, such as parseInt(), return a primitive, while the valueOf() method returns a wrapper class. For example:
int primitive = Integer.parseInt("123");
Integer wrapper = Integer.valueOf("123");
int bad = Integer.valueOf("123.45"); // throws NumberFormatException - 123.45 isn't a valid int

// To get a primitive data type value corresponding to a string value, you can use the static utility method parse<DataType>

/*-------------------------------------------------------------------------------------
Wrapper class   Converting String to PRIMITIVE   Converting String to WRAPPER CLASS
----------------------------------------------------------------------------------------
Boolean       Boolean.parseBoolean("true");    Boolean.valueOf("TRUE");
Byte          Byte.parseByte("1");          Byte.valueOf("2");
Short          Short.parseShort("1");         Short.valueOf("2");
Integer       Integer.parseInt("1");          Integer.valueOf("2");
Long          Long.parseLong("1");          Long.valueOf("2");
Float          Float.parseFloat("1.1");       Float.valueOf("2.2");
Double          Double.parseDouble("1.1");       Double.valueOf("2.2");
Character       None                      None
-------------------------------------------------------------------------------------*/

// The Character class doesn't participate in the parse/valueOf methods. Since a String is made up of characters, you can just call charAt() normally.

// All parse<DataType> methods throw NumberFormatExceptions for invalid values:
Integer.parseInt("a"); // throws NumberFormatException - "a" isn't a valid int
Long.parseLong("12.34"); // throws NumberFormatException - 12.34 isn't a valid long
Byte.parseByte("1234"); // throws NumberFormatException - 1234 is out of range for byte
Byte.parseByte("123_4"); // throws NumberFormatException - use of underscores is allowed only in numeric literals, but not in strings
// All parse methods throw NumberFormatException except Boolean.parseBoolean(). This method returns false whenever the string it parses is not equal to "true" (case-insensitive comparison).
Boolean.parseBoolean("TrUe"); // returns true, no exception - the String argument isn't case-sensitive
Boolean.parseBoolean("Yes"); // returns false

// All numeric wrapper classes define methods of the format <primitive>Value(), where <primitive> refers to the exact primitive data type name: booleanValue(), charValue(), byteValue(), shortValue(), intValue(), longValue(), floatValue(), doubleValue(). These methods retrieve the value of the primitive value wrapper class stores, as a byte, short, int, long, float, or double. All six numeric wrapper classes inherit all six <primitive>Value() methods from their common superclass, Number.

// The <primitive>Value() methods are automatically called on unboxing. For example:
Double a = 0.0;
Double b = 0.0;
a += b;
// "a += b;" is internally converted by Java to this:
a = a.doubleValue() + b.doubleValue();

// Since Java 5, you can just type the primitive value and Java will convert it to the relevant wrapper class for you. This is called autoboxing:
List<Double> weights = new ArrayList<>();
weights.add(50.5); // [50.5] - autobox the double primitive into a Double object and add that to the List
weights.add(new Double(60)); // [50.5, 60.0] - you can still write code the long way and pass in a wrapper object ("manual" boxing)
weights.remove(50.5); // [60.0] - again autobox into the wrapper object and passes it to remove()
double first = weights.get(0); // 60.0

// Unboxing a wrapper reference variable, which refers to null, will throw a NullPointerException.

// Be careful when autoboxing into Integer:
List<Integer> numbers = new ArrayList<>();
numbers.add(1); // [1]
numbers.add(2); // [1, 2]
numbers.remove(1); // 1 is the index of the element to be removed, not the value!!!
System.out.println(numbers); // 1
// Because there's already a remove() method that takes an int parameter, Java calls that method rather than autoboxing.
// If you want to remove the element with value 1, you can write this to force wrapper class use:
numbers.remove(new Integer(1))

// Wrapper classes provide toString, which returns a string representation of a value:
int num = 12345;
String str = Integer.toString(num); // returns "12345"

// Wrapper classes are immutable. Adding a primitive value to a wrapper class variable doesn't modify the value of the object it refers to. Instead, the wrapper class variable is assigned a new object.

// Method equals() compares the primitive value stored by a wrapper instance. The operator == compares reference variables - checking whether they point to the same instance.

// You can't compare wrapper instances for equality using equals() or ==, if they aren't of the same class. The code won't compile for instances that are compared using ==. When compared using equals(), the output will be always false:
Integer obj1 = 100;
Short obj2 = 100;
System.out.println(obj1.equals(obj2)); // outputs false
System.out.println(obj1 == obj2); // doesn't compile

// You can explicitly cast a wrapper object to only the type that it wraps:
Byte b1 = (byte)100;
Long l1 = (long)300;
Float f1 = (float)b1 + (int)l1; // compilation error. The code (int)l1 isn't casting primitive long to int. It's trying to cast object Long to primitive int.

// ####### Short-circuit evaluation:

// Logical operators (not short-circuit!): & , | , ^
// Short-circuit logical operators: && , ||

 // Short-circuit operators are used is checking for null objects before performing an operation, such as this:
if(x != null && x.getValue() < 5) {...}

// Be wary of short-circuit behavior on the exam, as questions are known to alter a variable on the right-hand side of the expression that may never be reached. For example, what is the output of the following code?
int x = 6;
boolean y = (x >= 6) || (++x <= 7);
System.out.println(x);
// Because x >= 6 is true, the increment operator on the right-hand side of the expression is never evaluated, so the output is 6.

// As of Java 7, only one of the right-hand expressions of the ternary operator will be evaluated at runtime.

// ####### "switch":

// Data types supported by "switch" statements include the following:
// int and Integer
// byte and Byte
// short and Short
// char and Character
// int and Integer
// String
// enum values
// Note that boolean, long, float and double (and their associated wrapper classes) are not supported by switch statements.

// The values in each case statement must be compile-time constant values of the same data type as the switch value. This means you can use only literals, enum constants, or final constant variables of the same data type.

// The exam creators are fond of switch examples that are missing break statements! When evaluating switch statements on the exam, always consider that multiple branches may be visited in a single execution.

// ####### String:

// If a String object is created using the keyword new, it always results in the creation of a new String object. String objects created this way are never pooled. When a variable is assigned a String literal using the assignment operator, a new String object is created only if a String object with the same value isn't found in the String constant pool.

String dogName = "Fluffy"; // if the string "Fluffy" exists in the pool, reuse ut (make dogName to point it); otherwise, add it to the pool
String dogName = new String("Fluffy"); // don't use the string pool - create a new object; don't place the created string in the pool

// The default value for String is null.

// Strings are immutable. Once initialized, a String value can't be modified. All the String methods that return a modified String value return a new String object with the modified value. The original String value always remains the same.

// charAt:
String fruit = "banana";
char letter = fruit.charAt(0); // 'b'
int length =  fruit.length() // in String, length() is a method (not an instanse variable as in an array!)
char lastChar = fruit.charAt(length - 1); // 'a'
// charAt method throws IndexOutOfBoundsException, if the index value passed is less than zero or greater than or equal to the length of the string.
char fragment = fruit.substring(/* start from */ 2, /* and stop just before */ 5) // "nan"; notice that the character indicated by the end index is not included
int index = fruit.indexOf('a'); // returns 1
index = fruit.indexOf('z'); // returns -1 - important to remember, can be on the exam!

// Overloads of substring():
int substring(int beginIndex) // ...until the end of the string
int substring(int beginIndex, int endIndex) // it stops right before endIndex, i.e. doesn't include it

System.out.println("abc".equals("ABC")); // false - it's case sensitive!
System.out.println("ABC".equals("ABC")); // true
System.out.println("abc".equalsIgnoreCase("ABC")); // true

System.out.println("abc".startsWith("a")); // true
System.out.println("abc".startsWith("A")); // false
System.out.println("abc".endsWith("c")); // true
System.out.println("abc".endsWith("a")); // false

System.out.println("abc".contains("b")); // true
System.out.println("abc".contains("B")); // false

// Overloads of replace():
String replace(char oldChar, char newChar)
String replace(CharSequence oldChar, CharSequence newChar) // CharSequence is an interface, implemeted by String and StringBuilder
// The following code shows how to use these methods:
System.out.println("abcabc".replace('a', 'A')); // AbcAbc; the char overload called
System.out.println("abcabc".replace("a", "A")); // AbcAbc; the CharSequence overload called because of double quotes

System.out.println(" abc ".trim()); // "abc"
System.out.println("\t a b c\n".trim()); // "a b c"

// ####### StringBuilder:

// StringBuilder changes its own state and returns a reference to itself.
// myStringBuilder.substring(...) returns a String rather than a reference to the StringBuilder - to keep it not changed.

// You cannot assign a String to a StringBuilder
StringBuilder b = "rumble" // compilation error
StringBuilder b = new StringBuilder("rumble") // OK

// The append() method is the most frequently used method in StringBuilder:
public StringBuilder append(String str) // it has many overloads, so you can just call append() without having to convert your parameter to a String first.

// The insert() method adds characters to the StringBuilder at the requested index and returns a reference to the current StringBuilder. Just like append(), there are lots of method signatures for different types. Here's one:
public StringBuilder insert(int offset, String str)
// The arg offset is the index of the element, BEFORE WHICH you want to insert str (the characters beginning from offset are shifted right):
StringBuilder sb = new StringBuilder("animals");
sb.insert(7, "-"); // sb = animals-
sb.insert(0, "-"); // sb = -animals-
sb.insert(4, "-"); // sb = -ani-mals
// The append and insert methods are overloaded so as to accept data of any type. Each effectively converts a given datum to a string and then appends or inserts.

// The StringBuilder.delete() method removes the characters in a substring of this sequence:
public StringBuilder delete(int start, int end)
// start - the beginning index, inclusive; end - the ending index, EXCLUSIVE.
// Throws StringIndexOutOfBoundsException if start is negative, greater than length(), or greater than end.

// Calling == on String variables will check whether they point to the same object in the pool. Calling == on StringBuilder variables will check whether they point to the same StringBuilder object.

// The default implementation of equals() method in the super-class Object compares pointers, i.e. acts same like ==. Descendants of Object can override equals() and provide custom check. Class String overrides equals() so it compares the contained string. So, calling equals() on String objects will check whether the sequence of characters is the same. Unfortunately, StringBuilder doesn't override equals() - calling equals() on StringBuilder objects will check whether they are pointing to the same object rather than looking at the values inside!!!

String x = "Hello World";
String z = " Hello World".trim();
System.out.println(x.equals(z)); // true
// This works because the authors of the String class implemented the method equals() to check the values inside the String rather than check reference equality. If a class doesn't implemented the equals() method, Java determines whether the references point to the same object - which is exactly what == does. The authors of StringBuilder did not implement equals(). If you call equals() on two StringBuilder instances, it will check reference equality.

// There is no concat method in StringBuilder. It defines a whole army of overloaded append methods to add data at the end of a StringBuilder object.

// ####### StringBuffer:

public final class StringBuffer extends AbstractStringBuilder implements Serializable, CharSequence

// StringBuffer provides Thread safety but on a performance cost. If you are in a single threaded environment or don't care about thread safety, you should use StringBuilder else use StringBuffer.

// https://docs.oracle.com/javase/8/docs/api/java/lang/StringBuffer.html

// ####### Array declaration:

int[] numbers1 = new int[3]; // each element is defaulted to 0
int[] numbers2 = {42, 55, 99}; // creates array of 3 elements - same as int[] numbers2 = new int[] {42, 55, 99};

// You can type the [] before or after the name, and adding a space is optional. This means that all four of these statements do the exact same thing:
int[] numAnimals;
int [] numAnimals2;
int numAnimals3[];
int numAnimals4 [];

int[] ids, types; // declare two arrays of int
int ids[], types; // declare one array of int and one scalar int variable

String [] bugs = {"cricket", "beetle", "ladybug"};
String [] alias = bugs;
System.out.println(bugs.equals(alias)); // true; we can call equals() because an array is an object. It returns true because of reference equality
System.out.println(bugs.toString()); // [Ljava.lang.String;@160bc7c0
// Since Java 5, Java has provided a method that prints an array nicely: java.util.Arrays.toString(bugs) would print [cricket, beetle, ladybug]. The exam tends not to use it because most of the questions on arrays were written a long time ago. Regardless, this is a useful method when testing your own code.

// ####### "Arrays" class:

// The java.util.Arrays class is a part of the Java Collection Framework. This class provides static methods to dynamically create and access Java arrays. It consists of only static methods and the methods of Object class. The methods of this class can be used by the class name itself:
import java.util.Arrays;   
int[] numbers = {6, 9, 1};
Arrays.sort(numbers);
// Arrays.binarySearch() searches for the specified element in the array with the help of Binary Search algorithm. Arrays.binarySearch() should be applied only to a sorted array. In this case, it returns the index of match if the element found. If the element is not found, Arrays.binarySearch() returns a negative value showing one smaller than the negative of index, where a match needs to be inserted to preserve sorted order:
int[] numbers = {2,4,6,8};
System.out.println(Arrays.binarySearch(numbers, 2)); // 0
System.out.println(Arrays.binarySearch(numbers, 4)); // 1
System.out.println(Arrays.binarySearch(numbers, 1)); // -1
System.out.println(Arrays.binarySearch(numbers, 3)); // -2
System.out.println(Arrays.binarySearch(numbers, 9)); // -5
// If the array is unsorted, the result of Arrays.binarySearch() isn't predictable. As soon as you see the array isn't sorted, look for an answer choice about unpredictable output.

// ####### Multidimentional arrays:

int[][] rectangle1; // 2D array
int rectangle2 [][]; // 2D array
int[] rectangle3[]; // 2D array
int[] rectangle4 [], space [][]; // a 2D AND a 3D array

// You can specify the size of your multidimensional array in the declaration if you like:
String [][] rectangle5 = new String[3][2];

// Although it is legal to leave out the size for later dimensions of a multidimensional array, the first one is required (pay attention - we are talking about creating an instance, i.e. about what appears after the "new" keyword - not about declaration, which is before the "="!):
int arr[][] = new int[3][]; // OK - array of 3 elements, each one of them - a pointer to an int array, initialized with null
int arr[][] = new int[][3]; // COMPILATION ERROR!

// This example prints out a 2D array:
int[][] twoD = new int[3][2];
for (int i = 0; i < twoD.length; i++) {
   for (int j = 0; j < twoD[i].length; j++)
      System.out.print(twoD[i][j] + " "); // print element
   System.out.println(); // time for a new row
}
// The same:
for (int[] innerArray : twoD) {
   for (int num : innerArray)
      System.out.print(num + " ");
   System.out.println();
}

// ####### ArrayList:

// For the OCA exam, the only members of the Collections framework that you need to be aware of are List and ArrayList. List Interface is the subinterface of Collection. It contains index-based methods to insert and delete elements: https://www.javatpoint.com/java-list . ArrayList class is a dynamic array. It implements List interface: https://www.javatpoint.com/java-arraylist .

// What will this code output?
final String[] names = new String[3];
names[0] = "Lisa";
names[1] = "Kevin";
names[2] = "Roger";
for(String name : names) {
   System.out.print(name + ", ");
}
// This code will compile and print:
// Lisa, Kevin, Roger,

// What will this code output?
java.util.List<String> values = new java.util.ArrayList<String>();
values.add("Lisa");
values.add("Kevin");
values.add("Roger");
for(String value : values) {
   System.out.print(value + ", ");
}
// This code will compile and print the same values:
// Lisa, Kevin, Roger,

// An ArrayList object has a capacity and a size. The capacity is the total number of allocated cells (used and not used). The size is the number of cells that have data in them (i.e. used). Cells 0 up through size-1 have data in them. Data are added in order. Before cell N gets data, cells 0, 1, 2, ... N-1 must hold data. The size increases by one each time an element is added. However, the capacity remains unchanged until the ArrayList is full. When an element is added to a full list, the Java runtime system will greatly increase the capacity of the list so that many more elements can be added. To find out the current size of an ArrayList use its size() method (not a property!!!).

// The elements of an ArrayList can't be added to a higher position if lower positions are available:
ArrayList<String> seasons = new ArrayList<>();
seasons.add(1, "Spring"); // throws IndexOutOfBoundsException - cannot add by index 1 when index 0 is available

// An array has one glaring shortcoming: you have to know how many elements will be in the array when you create it and then you are stuck with that choice. ArrayList can change size at runtime as needed. Like an array, an ArrayList is an ordered sequence that allows duplicates. "Ordered" means "can be accessed by an index"; it doesn't mean "sorted"!!!
import java.util.ArrayList;
ArrayList list1 = new ArrayList(); // number of slots is unknown in the time of declaration
ArrayList list2 = new ArrayList(10); // create an ArrayList containing a specific number of slots, not assigned yet
ArrayList list3 = new ArrayList(list2); // make a copy of another ArrayList (i.e. clone it). We copy both the size and contents of that ArrayList

// Generics:
// In the previous examples, the type of each slot is Object, so each slot can be everything except primitives, and not necesseraly same type. Generics allow to restrict all slots to point only to objects of one, specific type:
ArrayList<String> list4 = new ArrayList<String>(); // each slot can point only to String
ArrayList<String> list5 = new ArrayList<>(); // same as previous - stating from Java 7

// ArrayList implements an interface called List. In other words, an ArrayList is a List. You can store an ArrayList in a List reference variable:
List<String> list6 = new ArrayList<>();

// E is used by convention in generics to mean "any class that this array can hold". If you didn't specify a type when creating the ArrayList, E means Object. Otherwise, it means the class you put between < and >.

// add() methods insert a new value in the ArrayList. The method signatures are as follows:
boolean add(E element) // ignore boolean return type
void add(int index, E element) // add just before the index
// Examples:
List<String> birds = new ArrayList<>();
birds.add("hawk"); // [hawk]
birds.add(1, "robin"); // [hawk, robin] - index 1 doesn't exist, so add to the end
birds.add(0, "blue jay"); // [blue jay, hawk, robin]
birds.add(1, "cardinal"); // [blue jay, cardinal, hawk, robin]
System.out.println(birds); // [blue jay, cardinal, hawk, robin]; ArrayList implements toString() - in contrast to Array

// remove() methods remove the first matching value in the ArrayList or remove the element at a specified index:
boolean remove(Object object) // boolean return value tells us whether a match was removed
E remove(int index) // the E return type if the element that actually got removed
// Examples:
List<String> birds = new ArrayList<>();
birds.add("hawk"); // [hawk]
birds.add("hawk"); // [hawk, hawk]
System.out.println(birds.remove("hawk")); // prints true
System.out.println(birds.remove("cardinal")); // prints false because no such element is found
System.out.println(birds.remove(4)); // throws an IndexOutOfBoundsException because no such index is found
System.out.println(birds.remove(0)); // prints hawk
System.out.println(birds); // []

// isEmpty() and size() methods look at how many of the slots are in use:
boolean isEmpty()
int size()
// Examples:
List<String> birds = new ArrayList<>();
System.out.println(birds.isEmpty()); // true
System.out.println(birds.size()); // 0
birds.add("hawk"); // [hawk]
birds.add("hawk"); // [hawk, hawk]
System.out.println(birds.isEmpty()); // false
System.out.println(birds.size()); // 2

// set() method changes one of the elements of the ArrayList without changing the size:
E set(int index, E newElement) // the E returns type of the element that got replaced.
// Examples:
List<String> birds = new ArrayList<>();
birds.add("hawk"); // [hawk]
System.out.println(birds.size()); // 1
birds.set(0, "robin"); // [robin]
System.out.println(birds.size()); // 1
birds.set(1, "robin"); // IndexOutOfBoundsException (since the size is 1, the only valid index is 0)

// clear() method discards all elements of the ArrayList. After clear() has been called, isEmpty() returns true, and size() returns 0.

// contains() method checks whether a certain value is in the ArrayList:
boolean contains(Object object)
// Examples:
List<String> birds = new ArrayList<>();
birds.add("hawk"); // [hawk]
System.out.println(birds.contains("hawk")); // true
System.out.println(birds.contains("robin")); // false
// contains() calls equals() on each element of the ArrayList to see whether there are any matches. Since String implements equals(), this works out well.

// equals(): ArrayList has a custom implementation of equals() to compare two lists to see if they contain the same elements in the same order:
boolean equals(Object object)
// Examples:
List<String> one = new ArrayList<>();
List<String> two = new ArrayList<>();
System.out.println(one.equals(two)); // true
one.add("a"); // [a]
System.out.println(one.equals(two)); // false
two.add("a"); // [a]
System.out.println(one.equals(two)); // true
one.add("b"); // [a,b]
two.add(0, "b"); // [b,a]
System.out.println(one.equals(two)); // false

// Collections.sort():
List<Integer> numbers = new ArrayList<>();
numbers.add(99);
numbers.add(5);
numbers.add(81);
Collections.sort(numbers);
System.out.println(numbers); // [5, 81, 99]

// Converting Between array and List:

// List.toArray() method to convert List to array:
List<String> list = new ArrayList<>();
list.add("hawk");
list.add("robin");
Object[] objectArray = list.toArray(); // ArrayList knows how to convert itself to an array; it defaults to an array of class Object
System.out.println(objectArray.length); // 2
String[] stringArray = list.toArray(new String[0]); // specify the type of the array and do what we actually want
System.out.println(stringArray.length); // 2

// Arrays.asList() method to convert array to List:
// Converting from an array to a List is more interesting. The original array and created array backed List are linked. When a change is made to one, it is available in the other. It is a fixed-size list and is also known a backed List because the array changes with it:
String[] myArray = { "hawk", "robin" }; // [hawk, robin]
List<String> list = Arrays.asList(myArray); // it isn't the java.util.ArrayList we've grown used to. It is a fixed-size, backed version of a List
System.out.println(list.size()); // 2
list.set(1, "test"); // [hawk, test] - this line updates both array and list because they point to the same data store
myArray[0] = "new"; // [new, test] - also changes both array and list
for (String b : myArray) System.out.print(b + " "); // new test
list.remove(1); // throws UnsupportedOperation Exception because we are not allowed to change the size of the list (in fact, of the pointed array)
// Attention! asList is a static method of Arrays class, accepting one parameter, so the next code will fail:
List<String> list = myArray.asList();

// ####### Working with Dates and Times:

// Objects are created (returned) by static methods:

import java.time.*;

LocalDate date1 = LocalDate.of(2015, Month.JANUARY, 31); // Month enum starts from 1, not from 0
LocalDate date2 = LocalDate.of(2015, 1, 31); // same as prev
LocalDate date2 = LocalDate.of(2015, Month.JANUARY, 32); // throws DateTimeException

LocalTime time1 = LocalTime.of(6, 15); // hour and minute
LocalTime time2 = LocalTime.of(6, 15, 30); // + seconds
LocalTime time3 = LocalTime.of(6, 15, 30, 200); // + nanoseconds

LocalDateTime dateTime1 = LocalDateTime.of(2015, Month.JANUARY, 31, 6, 15, 30);
LocalDateTime dateTime2 = LocalDateTime.of(date1, time1);

// You are not allowed to construct a date or time object directly:
LocalDate d = new LocalDate(); // DOES NOT COMPILE (the constructor is private to force you to use the static methods aka "factory")

// Adding to a date is easy. The date and time classes are immutable, just like String was. This means that we need to remember to assign the results of these methods to a reference variable so they are not lost.
LocalDate date = LocalDate.of(2014, Month.JANUARY, 20);
System.out.println(date); // 2014-01-20
date = date.plusDays(2);
System.out.println(date); // 2014-01-22
date = date.plusWeeks(1);
System.out.println(date); // 2014-01-29
date = date.plusMonths(1);
System.out.println(date); // 2014-02-28
date = date.plusYears(5);
System.out.println(date); // 2019-02-28

LocalDate date = LocalDate.of(2020, Month.JANUARY, 20);
LocalTime time = LocalTime.of(5, 15);
LocalDateTime dateTime = LocalDateTime.of(date, time);
System.out.println(dateTime); // 2020-01-20T05:15
dateTime = dateTime.minusDays(1);
System.out.println(dateTime); // 2020-01-19T05:15
dateTime = dateTime.minusHours(10);
System.out.println(dateTime); // 2020-01-18T19:15
dateTime = dateTime.minusSeconds(30);
System.out.println(dateTime); // 2020-01-18T19:14:30

// Whenever you see immutable types, pay attention to make sure the return value of a method call isn't ignored:
LocalDate date = LocalDate.of(2020, Month.JANUARY, 20);
date.plusDays(10);
System.out.println(date); // prints January 20, 2020. Adding 10 days was useless because we ignored the result.

LocalDate date = LocalDate.of(2020, Month.JANUARY, 20);
date = date.plusMinutes(1); // DOES NOT COMPILE since LocalDate does not contain time. This means you cannot add minutes to it

// LocalDate has only DATE-related methods:
// plusYears / minusYears
// plusMonths / minusMonths
// plusWeeks / minusWeeks
// plusDays / minusDays

// LocalTime has only TIME-related methods:
// plusHours / minusHours
// plusMinutes / minusMinutes
// plusSeconds / minusSeconds
// plusNanos / minusNanos

// LocalDateTime has both.

// All the three (LocalDate, LocalTime and LocalDateTime) have the methods isBefore and isAfter. THE ARGUMENT MUST BE OF THE SAME TYPE AS THE OBJECT! For example, you cannot ask if a LocalDate isBefore (or isAfter) a LocalDateTime.

// Period is a class to store a period of time:
Period annually = Period.ofYears(1); // every 1 year
Period quarterly = Period.ofMonths(3); // every 3 months
Period everyThreeWeeks = Period.ofWeeks(3); // every 3 weeks
Period everyOtherDay = Period.ofDays(2); // every 2 days
Period everyYearAndAWeek = Period.of(1, 0, 7); // every 1 year and 7 days

// Period can be passed to plus() method of LocalDate and LocalDateTime (but not LocalTime):
LocalDate date = LocalDate.of(2015, 1, 20);
LocalTime time = LocalTime.of(6, 15);
LocalDateTime dateTime = LocalDateTime.of(date, time);
Period period = Period.ofMonths(1);
System.out.println(date.plus(period)); // 2015-02-20
System.out.println(dateTime.plus(period)); // 2015-02-20T06:15
System.out.println(time.plus(period)); // UnsupportedTemporalTypeException

// In date/time formats, we have h for hour and m for minute. Remember M (uppercase) is month and m (lowercase) is minute. We can only use this formatter with objects containing times.

// String to date / time. DateTimeFormatter is a class which stores date / time format (i.e. instructs how to parse strings, containing date and time):
DateTimeFormatter f = DateTimeFormatter.ofPattern("MM dd yyyy");
LocalDate date = LocalDate.parse("01 02 2015", f); // the 2nd arg says how to parse the 1st
LocalTime time = LocalTime.parse("11:22"); // If you don't specify any formatter, parse() uses the default for that type.
System.out.println(date); // 2015-01-02
System.out.println(time); // 11:22

// ####### OOP #######

// Multiple classes and interfaces can be defined in the same .java file, but only one of them is allowed to be public. The public class must match the name of the file.

// Classes that do not specify a superclass with extends automatically inherit from java.lang.Object.

// A null value can always be passed as an object value, regardless of type. For example, the method wash() accepts a parameter of class Car:
Car aCar = new Car();
this.wash(aCar);
this.wash(null); // compiles successfully as well

// Instance Initializer Block - it's a block {...} which appears outside a method:

public static void main(String[] args) {
    { System.out.println("Feathers"); } // regular block (inside a method)
 }
 { System.out.println("Snowy"); } // instance initializer block
 
// Fields and instance initializer blocks are run in the order in which they appear in the file. The constructor runs last - after all fields has been declared and instance initializer blocks have run.

// What do you think this code prints out?
public class Egg {
   public Egg() {
      number = 5;
   }
   public static void main(String[] args) {
      Egg egg = new Egg();
      System.out.println(egg.number);
   }
   private int number = 3;
   { number = 4; } // instance initializer block
}
// If you answered 5, you got it right. Fields and blocks are run first in order, setting number to 3 and then 4. Then the constructor runs, setting number to 5.
 
// Java allows objects to implement a method called finalize() that might get called. This method gets called if the garbage collector tries to collect the object. If the garbage collector doesn't run, the method doesn't get called. If the garbage collector fails to collect the object and tries to run it again later, the method doesn't get called a second time. I.e. finalize() call could run zero or one time. In practice, this means you are highly unlikely to use it in real projects. Luckily, there isn't much to remember about finalize() for the exam. Just keep in mind that it might not get called and that it definitely won't be called twice.

// Member access:
// When there is no access modifier, Java uses the default, which is package private access. This means that the member is "private" to classes in the same package. In other words, only classes in the package may access it. Protected access allows everything that default (package private) access allows + access from descendants.

// Static members can be accessed / called even if the pointer is null:
public class Koala {
   public static int count = 0; // static variable
   public static void main(String[] args) { // static method
      System.out.println(count);
   }
}

Koala k = new Koala();
System.out.println(k.count);
k = null;
System.out.println(k.count); // OK - Java doesn't care that k happens to be null. Since we are looking for a static, it doesn't matter.
// The exam creators will try to trick you into thinking a NullPointerException is thrown because the variable happens to be null. Don't be fooled!

// When a static method is recreated in a subclass, it is referred to as method hiding.

// IMPORTANT TO REMEMBER: In contrast to regular (non-static) methods, invocation of a static method is tied to the type of the REFERENCE VARIABLE and doesn't depend on the type of the OBJECT that's assigned to that variable:
class Phone {
   static void call() {System.out.println("Phone");}
}
class SmartPhone extends Phone {
   static void call() {System.out.println("SmartPhone");}
}
class TestPhones {
   public static void main(String... args) {
      Phone phone = new Phone();
      Phone smartPhone = new SmartPhone();
      phone.call();
      smartPhone.call(); // prints "Phone" - as the class of the VARIABLE (Phone) defines, not the type of the OBJECT (SmartPhone).
                           // If the method would not be static, "SmartPhone" would be printed.
   }
}

// Static Initialization:
// Previously, we covered instance initializers that looked like unnamed methods. Just code inside braces. Static initializers look similar. They only add the static keyword to specify they should be run only one time - when the class is first used:
private static final int NUM_SECONDS_PER_HOUR;
static {
   int numSecondsPerMinute = 60;
   int numMinutesPerHour = 60;
   NUM_SECONDS_PER_HOUR = numSecondsPerMinute * numMinutesPerHour;
}
// The static initializer runs when the class is first used. The statements in it run and assign any static variables as needed. There is something interesting about this example. We just got through saying that final variables aren't allowed to be reassigned. The key here is that the static initializer is the first assignment. And since it occurs up front, it is okay:
private static int one;
private static final int two; // OK - even thow we see a final var which is not initialized
                        // (the compiler knows that it will be initialized in the static initializer)
private static final int three = 3;
private static final int four; // DOES NOT COMPILE - a final var must be initialized - on the declaration or in a static initializer
static {
   one = 1;
   two = 2;
   three = 3; // DOES NOT COMPILE - second attempt - it has already been initialized when declared, and final vars cannot be changed
   two = 4; // DOES NOT COMPILE - second attempt - it has already been initialized in this static initializer
}
// Remember: static final variables must be set exactly once, and it must be in the declaration line or in a static initialization block, but not a constructor!

// Static Imports:
// Regular imports are for importing classes. Static imports are for importing static members of classes.
import java.util.List;
import static java.util.Arrays.asList; // import of a static method; pay attention - only the method's name is mentioned, but not parameters
public class StaticImports {
   public static void main(String[] args) {
      List<String> list1 = asList("one", "two"); // not Arrays.asList - only the name of the imported static method!
      List<String> list2 = Arrays.asList("one", "two"); // compilation error - the class Arrays was not imported and, therefore, is unknown
   }
}
// In this example, we are specifically importing the asList method. This means that any time we refer to asList in the class, it will call Arrays.asList(). An interesting case is what would happen if we created an asList method in our StaticImports class. Java would give it preference over the imported one and the method we created would be used. On real projects, avoid overusing static imports; otherwise, the code might become a bit confusing about which imported component comes from which class.

import static java.util.Arrays; // DOES NOT COMPILE - attempt to statically import a class
import static java.util.Arrays.asList; // OK
static import java.util.Arrays.*; // DOES NOT COMPILE - the syntax is "import static" and not vice versa
                                       // (the feature is called "static import", but the syntax is "import static")

// The compiler will complain if you try to explicitly do a static import of two methods with the same name or two static variables with the same name. For example:
import static statics.A.TYPE;
import static statics.B.TYPE; // DOES NOT COMPILE
// Luckily when this happens, we can just refer to the static members via their classname in the code instead of trying to use a static import.

// ####### Overloading:

// Overloading and Varargs:
public void fly(int[] lengths) { }
public void fly(int ... lengths) { } // DOES NOT COMPILE
// Java treats varargs as if they were an array. This means that the method signature is the same for both methods. Since we are not allowed to overload methods with the same parameter list, this code doesn't compile. Even though the code doesn't look the same, it compiles to the same parameter list.

// Overloading and Autoboxing:
public void fly(int numMiles) { }
public void fly(Integer numMiles) { }
fly(3);
// Java will call the int numMiles version. Java tries to use the most specific parameter list it can find. When the primitive int version isn't present, it will autobox. However, when the primitive int version is provided, there is no reason for Java to do the extra work of autoboxing.

// Overloading and Primitives:
// Primitives work in a way similar to reference variables. Java tries to find the most specific matching overloaded method:
public class Plane {
   public void fly(int i) { System.out.print("int"); }
   public void fly(long l) { System.out.print("long"); }
   public static void main(String[] args) {
      Plane p = new Plane();
      p.fly(123); // prints "int"
      p.fly(123L); // "long"
   }
}
// If we comment out the declaration of the overloaded method with the int parameter, the output becomes "long long ". Java has no problem calling a larger primitive. However, it will not do so unless a better match is not found. Note that Java can only accept wider types. An int can be passed to a method taking a long parameter. Java will not automatically convert to a narrower type. If you want to pass a long to a method taking an int parameter, you have to add a cast to explicitly say narrowing is okay.

// The order Java uses to choose the right overloaded method - what will be chosen for glide(1, 2):
public String glide(int i, int j) {} // 1st attempt - Exact match by type
public String glide(long i, long j) {} // 2nd attempt - Larger primitive type
public String glide(Integer i, Integer j) {} // 3rd attempt - Autoboxed type
public String glide(int... nums) {} // 4th attempt - Varargs
User avatar
Ursego
Site Admin
 
Posts: 143
Joined: 19 Feb 2013, 20:33

Link to this posting

Postby Ursego » 05 Mar 2019, 07:38

Code: Select all
// ####### Constructor:

// You can define a constructor using all four access levels: public, protected, default, and private.

// A constructor can't be defined using non-access modifiers static, abstract, or final.

// A constructor must not define any return type - not even void. Instead, it creates and returns an object of the class in which it's defined. If you write "return" in a constructor, it means "return this". If you define a return type for a constructor, it'll no longer be treated as a constructor. Instead, it'll be treated as a regular method, even though it shares the same name as its class.

// Default Constructor:
// Every class in Java has a constructor whether you code one or not. If you don't include any constructors in the class, Java will create one for you - without any parameters, and only calling the default constructor of the super-class:
public Thing() {
   super();
}

// A default constructor is only automatically created if there are no constructors present. But if you modify the class later by adding a constructor to it, the Java compiler will remove the default, noargument constructor that it initially added to the class. Having a private constructor in a class tells the compiler not to provide a default noargument constructor. It also prevents other classes from instantiating the class. This is useful when a class only has static methods or the class wants to control all calls to create new instances of itself ("factory").

// The accessibility of a default constructor matches the accessibility of its class. Java creates a public default constructor for a public class. It creates a default constructor with package access for a class with package-level access.

// IMPORTANT!!! If the parent class doesn't have a no-argument constructor, an explicit call to a parent constructor must be provided in the child's constructors (otherwise - compilation error). Pay close attention on the exam to any class that defines a constructor with arguments and doesn't define a no-argument constructor.

// Subclasses don't inherit constructors.
// When you create a subclass and don't put any explicit constructor declarations in your code, Java creates a default noargument constructor for you. If you create a subclass and define any constructors yourself, Java doesn't add a default constructor for the subclass (and the subclass doesn't inherit any constructors, either).

// When "this" is used in a constructor as if it were a method name, Java calls another constructor on the same instance of the class:
public Hamster(int weight, String color) { // the two-args overload of the constructor
   this.weight = weight;
   this.color = color;
}
public Hamster(int weight) { // the one-arg overload of the constructor
   this(weight, "brown"); // call the two-args overload of the constructor
}

// If you call another constructor with this(), that call must be the first noncommented statement in the constructor:
public Hamster(int weight) {
   System.out.println("in constructor");
   this(weight, "brown"); // DOES NOT COMPILE
}
// Even though the println statement doesn't change any variables, it is still a Java statement and is not allowed to be inserted before the call to this(). The comment just fine. Comments don't run Java statements and are allowed anywhere.

// You can't call multiple constructors from a constructor - only one this() can present.

// The constructor is part of the initialization process, so it is allowed to assign final instance variables in it (but not final static!). By the time the constructor completes, all final instance variables must have been set.

// Order of Initialization:
// Unfortunately, you do have to memorize this list:
// 1. If there is a superclass, initialize it first.
// 2. Static variable declarations and static initializers in the order they appear in the file (on the first use only).
// 3. Instance variable declarations and instance initializers in the order they appear in the file.
// 4. The constructor.
// The four rules apply only if an object is instantiated. If the class is referred to without a new call, only rules 1 and 2 apply. The other two rules relate to instances and constructors. They have to wait until there is code to instantiate the object.

// super():
// Java compiler automatically inserts a call to the no-argument constructor super() if the first statement is not a call to the parent constructor. The following three classes are equivalent, because the compiler will automatically convert the first two to the last example:
public class Donkey {
}

public class Donkey {
   public Donkey() {}
}

public class Donkey {
   public Donkey() {
      super();
   }
}

// What happens if the parent class doesn't have a no-argument constructor? Recall that the no-argument constructor is not required and only inserted automatically if there is no constructor defined in the class by a programmer. In this case, the Java compiler will not help and you must create at least one constructor in your child class that explicitly calls a parent constructor via the super() command. You should be wary of any exam question in which the parent class defines a constructor that takes arguments and doesn't define a no-argument constructor.

// Constructor Definition Rules:
// 1. The first statement of every constructor is a call to another constructor within the class using this(),
//                  or a call to a constructor in the direct parent class using super().
//                  As you see, only one of them can be used, but never both.
// 2. The super() call is not allowed to be used after the first statement of the constructor.
// 3. If no super() call is declared in a constructor, Java will insert a no-argument super() as the first statement of the constructor.
// 4. If the parent doesn't have a no-argument constructor and the child doesn't define any constructors, the compiler will throw an error
//                   and try to insert a default no-argument constructor into the child class.
// 5. If the parent doesn't have a no-argument constructor, the compiler requires an explicit call to a parent constructor in each child constructor.

// The parent constructor is always executed before the child constructor. For example, try to determine what the following code outputs:
class Primate {
   public Primate() {
      System.out.println("Primate");
   }
}

class Ape extends Primate {
   public Ape() {
      System.out.println("Ape");
   }
}

public class Chimpanzee extends Ape {
   public static void main(String[] args) {
      new Chimpanzee();
   }
}
// The compiler first inserts the super() command as the first statement of both the Primate and Ape constructors. Next, the compiler inserts a default no-argument constructor in the Chimpanzee class with super() as the first statement of the constructor. So, here is what is finally created by Java:
class Primate {
   public Primate() {
      super();
      System.out.println("Primate");
   }
}

class Ape extends Primate {
   public Ape() {
      super();
      System.out.println("Ape");
   }
}

public class Chimpanzee extends Ape {
   public Chimpanzee() {
      super();
   }
   public static void main(String[] args) {
      new Chimpanzee();
   }
}
// The code will execute with the parent constructors called first and yields the following output:
// Primate
// Ape

// "super()" vs "super":
// "super()" is a statement that explicitly calls a parent constructor and may only be used in the first line of a constructor of a child class. Can be used only in a constructor.
// "super" is a keyword used to reference a member defined in a parent class (in the same manner as "this" keyword is used to reference a member defined in the current class). Can be used in any method of the child class.

// ####### Method Overriding:

// "super" calls the version of the method which would be in the current class if the current class would not override it. That means, that if the method was overridden a few times through the hierarchy, then only the closest implementation (i.e. the last one in the ancestors chain) is accessible with "super". There is no way to access other, earlier overrides.

// When you override a method, you may reference the parent version of the method using the super keyword. In this manner, the keywords "this" and "super" allow you to select between the current and parent version of a method, respectively. We illustrate this with the following example:
public class Canine {
   public double getAverageWeight() {
      return 50;
   }
}
public class Wolf extends Canine {
   public double getAverageWeight() {
      return super.getAverageWeight() + 20;
   }
}
public static void main(String[] args) {
   System.out.println(new Canine().getAverageWeight()); // 50.00
   System.out.println(new Wolf().getAverageWeight()); // 70.00
   }
}

// The compiler performs the following checks when you override a nonprivate method:
// 1. The method in the child class must have the same signature as the method in the parent class.
//      If two methods have the same name but different signatures, the methods are overloaded, not overridden.
// 2. The method in the child class must be at least as accessible or more accessible than the method in the parent class.
//      For example, if parent's method is protected, the override in an descendant can be protected or public, but not private or default.
// 3. The method in the child class may not throw a checked exception that is new or broader than the class of any exception thrown
//       in the parent class method.
// 4. If the method returns a value, it must be the same or a subclass of the method in the parent class, known as covariant return types.

// Illustration of rule 3:
public class InsufficientDataException extends Exception {}

public class Reptile {
   protected boolean hasLegs() throws InsufficientDataException {
      throw new InsufficientDataException();
   }
   protected double getWeight() throws Exception {
      return 2;
   }
}

public class Snake extends Reptile {
   protected boolean hasLegs() { // OK - no new exception is introduced
      return false;
   }
   protected double getWeight() throws InsufficientDataException{ // OK - InsufficientDataException is not broader than Exception
                                                   // (oppositely - it is narrower)
      return 2;
   }
}
// Both parent and child classes define two methods, hasLegs() and getWeight(). The first method, hasLegs(), throws an exception InsufficientDataException in the parent class but doesn't throw an exception in the child class. This does not violate the third rule, though, as no new exception is defined. In other words, a child method may hide or eliminate a parent method's exception without issue. The second method, getWeight(), throws Exception in the parent class and InsufficientDataException in the child class. This is also permitted, as InsufficientDataException is a subclass of Exception.

// Let's review some examples that do violate the third rule of overriding methods:
public class InsufficientDataException extends Exception {}

public class Reptile {
   protected double getHeight() throws InsufficientDataException {
      return 2;
   }
   protected int getLength() {
      return 10;
   }
}

public class Snake extends Reptile {
   protected double getHeight() throws Exception { // DOES NOT COMPILE - Exception is not a subclass of InsufficientDataException
      return 2;
   }
   protected int getLength() throws InsufficientDataException { // DOES NOT COMPILE - a new exception is introduced (the parent class didn't have it)
      return 10;
   }
}

// Redeclaring private Methods:
// The previous section defined the behavior if you override a public or protected method in the class. Now let's expand our discussion to private methods. It is not possible to override a private method in a parent class since the parent method is not accessible from the child class. Just because a child class doesn't have access to the parent method, doesn't mean the child class can't define its own version of the method. It just means, that the new method is not an overridden version of the parent class's method. Java permits you to redeclare a new method in the child class with the same or modified signature as the method in the parent class. This method in the child class is a separate and independent method, unrelated to the parent version's method, so none of the rules for overriding methods are invoked. THE TYPE OF THE VARIABLE (NOT OF THE ACTUAL OBJECT) DEFINES WHICH VERSION IS INVOKED. Example:
public abstract class Bird {
   private void fly() { System.out.println("Bird is flying"); } // private!
   public static void main(String[] args) {
      Bird bird = new Pelican();
      bird.fly(); // prints "Bird is flying" since the var is of type Bird; it would print "Pelican is flying" if the var would be of type Pelican,
                  // or if fly() in Bird would be protected (in the last case it would be an overriding, not a redeclaring).
   }
}
class Pelican extends Bird {
   protected void fly() { System.out.println("Pelican is flying"); } // absolutely not related to the fly() method of the Bird class
}

// Hiding Static Methods:
// A hidden method occurs when a child class defines a static method with the same name and signature as a static method defined in a parent class. Unlike overriding a method, in which a child method replaces the parent method in calls defined in both the parent and child, hidden methods only replace parent methods in the calls defined in the child class. Method hiding is similar but not exactly the same as method overriding. First, the four previous rules for overriding a method must be followed when a method is hidden. In addition, a new rule is added for hiding a method, namely that the usage of the static keyword must be the same between parent and child classes:
// 5. The method defined in the child class must be marked as static if it is marked as static in the parent class (method hiding).
//      Likewise, the method must not be marked as static in the child class if it is not marked as static in the parent class (method overriding).
//      NON-STATIC IN ANCESTOR AND STATIC IN DESCENDANT (OR VICE VERSA) WILL GIVE A COMPILATION ERROR. THEY MUST BE SAME IN BOTH!

// Hiding static methods is fraught with pitfalls and potential problems and as a practice should be avoided. Though you might see questions on the exam that contain hidden static methods that are syntactically correct, avoid hiding static methods in your own work, since it tends to lead to confusing and difficult-to-read code. You should not reuse the name of a static method in your class if it is already used in the parent class.

// Overriding vs. Hiding Methods:
// Overriding a method: a child method replaces the parent method in calls defined in both the parent and child.
// Hidden methods only replace parent methods in the calls defined in the child class.
public class Marsupial {
   public static boolean isBiped() {return false;} // static!
public void getMarsupialDescription() {
      System.out.println("Marsupial walks on two legs: " + isBiped()); // call isBiped defined in the type of the would-be run-time pointer
   }
}

public class Kangaroo extends Marsupial {
   public static boolean isBiped() {return true;} // must be static as well!
   public void getKangarooDescription() {
      System.out.println("Kangaroo hops on two legs: " + isBiped()); // call isBiped defined in the type of the would-be run-time pointer
   }
   public static void main(String[] args) {
      Kangaroo joey = new Kangaroo();
      joey.getMarsupialDescription(); // Marsupial walks on two legs: true (isBiped of Kangaroo was called since that is the type of the pointer joey)
      joey.getKangarooDescription(); // Kangaroo hops on two legs: true
   }
}
// In this example, the isBiped() method is overridden, not hidden, in the child class. Therefore, it is replaced at runtime in the parent class with the call to the child class's method. This example used polymorphism.

// Creating final methods:
// You can create a method with the final keyword. By doing so you forbid a child class from overriding this method. THIS RULE IS IN PLACE BOTH WHEN YOU OVERRIDE A METHOD AND WHEN YOU HIDE A METHOD. IN OTHER WORDS, YOU CANNOT HIDE A STATIC METHOD IN A PARENT CLASS IF IT IS MARKED AS final. You'd mark a method as final when you're defining a parent class and want to guarantee certain very precise behavior of a method in the parent class, regardless of which child is invoking the method.

// Hiding Variables:
// When you hide an instance or static variable, you define a variable with the same name as a variable in a parent class. This creates two copies of the variable within an instance of the child class: one instance defined for the parent reference and another defined for the child reference. As when hiding a static method, you can't override a variable; you can only hide it. Also similar to hiding a static method, the rules for accessing the parent and child variables are quite similar. If you're referencing the variable from within the parent class, the variable defined in the parent class is used. Alternatively, if you're referencing the variable from within a child class, the variable defined in the child class is used. Likewise, you can reference the parent value of the variable with an explicit use of the super keyword. The descendant object contains two copies of the variable with the same name: one defined in the parent class and one defined in the child class. These variables are kept separate from each other, allowing the descendant object to reference both independently (with this and with super).

// Although Java allows you to hide a variable defined in a parent class with one defined in a child class, it is considered an extremely poor coding practice. Hiding variables makes the code very confusing and difficult to read. When defining a new variable in a child class, it is considered good coding practice to select a name for the variable that is not already a public, protected, or default variable in use in a parent class. Hiding private variables is considered less problematic because the child class did not have access to the variable in the parent class to begin with.

// Abstract classes & methods:
// An abstract class is a class that is marked with the abstract keyword and cannot be instantiated. An abstract method is a method marked with the abstract keyword defined in an abstract class, for which no implementation is provided in the class in which it is declared. An abstract class may include nonabstract methods and variables. In fact, an abstract class is not required to include any abstract methods. An abstract method may only be defined in an abstract class. An abstract method cannot have implementation (i.e. the semicolon appears immediately after the signature - no curly braces at all!). For example, the following code won't compile because an abstract method is not defined within an abstract class:
public class Chicken {
   public abstract void peck(); // DOES NOT COMPILE
}
// The exam creators are fond of questions like this one, which mixes nonabstract classes with abstract methods. They are also fond of questions with methods marked as abstract for which an implementation is also defined.
public abstract class Turtle {
   public abstract void swim() {}; // DOES NOT COMPILE - even empty body is considered an implementation; must be: public abstract void swim();
   public abstract int getAge() { // DOES NOT COMPILE
      return 10;
   }
}

// A concrete class is the first nonabstract subclass that extends an abstract class. It is required to implement all inherited abstract methods. When you see a concrete class extending an abstract class on the exam, check that it implements all of the required abstract methods. Abstract classes can extend other abstract classes and are not required to provide implementations for any of the abstract methods. A concrete subclass is not required to provide an implementation for an abstract method if an intermediate abstract class provides the implementation. Here's one way to think about this: if an intermediate class provides an implementation for an abstract method, that method is inherited by subclasses as a concrete method, not as an abstract one. In other words, the subclasses do not consider it an inherited abstract method because it is no longer abstract by the time it reaches the subclasses.

// Abstract Class Definition Rules:
// 1. Abstract classes cannot be instantiated directly.
// 2. Abstract classes may be defined with any number, including zero, of abstract and non-abstract methods.
// 3. Abstract classes may not be marked as private or final.
// 4. An abstract class that extends another abstract class inherits all of its abstract methods as its own abstract methods.
// 5. The first concrete class that extends an abstract class must provide an implementation for all of the inherited abstract methods.

// Abstract Method Definition Rules:
// 1. Abstract methods may only be defined in abstract classes.
// 2. Abstract methods may not be declared private or final (that would prevent them from being implemented!).
// 3. Abstract methods must not provide a method body/implementation in the abstract class for which is it declared.
// 4. Implementing an abstract method in a subclass follows the same rules for overriding a method. For example, the name and signature must be the same, and the visibility of the method in the subclass must be at least as accessible as the method in the parent class.

// ####### Interface:

// The modifiers abstract and public are assumed for default and static methods. In other words, whether or not you provide them, the compiler will automatically insert them as part of the method definition. Prior to Java 8 ALL interface methods would be assumed to be abstract. Since Java 8 now includes default and static methods and they are never abstract, you cannot assume the abstract modifier will be implicitly applied to ALL methods by the compiler.

// The first concrete class that implements an interface (or extends an abstract class that implements an interface but doesn't implemet all its methods), must provide an implementation for all of the inherited abstract methods.

// You must implement an abstract method of an interface using the explicit access modifier public!!!!!!!!!!!!!!!

// You can explicitly cast any object to an interface, even if it doesn't implement it to make the code compile. But if the object's class doesn't implement the interface, the code will throw a ClassCastException at runtime:
Jumpable var = (Jumpable)(new Animal()); // compiles anyway; will throw ClassCastException if Animal class doesn't implement Jumpable interface

// Interfaces allow multiple inheritance:
public interface HasTail {public int getTailLength();}
public interface HasWhiskers {public int getNumberOfWhiskers();}
public interface Seal extends HasTail, HasWhiskers {}
// Any class that implements the Seal interface must provide an implementation for all methods in the parent interfaces - in this case, getTailLength() and getNumberOfWhiskers().

// If you define a class that implements two interfaces that contain the same abstract method, one implementation of that abstract method in the class will satisfy both the interfaces.

// What about an abstract class that implements an interface? In this scenario, the abstract class is treated in the same way as an interface extending another interface. In other words, the abstract class inherits the abstract methods of the interface but is not required to implement them. That said, like an abstract class, the first concrete class to extend the abstract class must implement all the inherited abstract methods of the interface:
public interface HasTail {public int getTailLength();}
public interface HasWhiskers {public int getNumberOfWhiskers();}
public abstract class HarborSeal implements HasTail, HasWhiskers {} // compiles successfully since it's abstract - implementing is optional
public class LeopardSeal implements HasTail, HasWhiskers {} // DOES NOT COMPILE - not abstract, so must implement all the methods of all the interfaces!

// Although a class can implement an interface, a class cannot extend an interface -  the only connection between a class and an interface is "class implements interface". Likewise, whereas an interface can extend another interface, an interface cannot implement another interface:
public interface CanRun {}
public class Cheetah extends CanRun {} // DOES NOT COMPILE - classes can only implement interfaces, not extend
public class Hyena {}
public interface HasFur extends Hyena {} // DOES NOT COMPILE - interfaces can extend only other interfaces, but not classes

// It is prohibited to a class define two methods with the same name and input parameters but different return types. So, a class cannot implement (and interface cannot extend) 2 interfaces which have methods with same name and signature, but differ by return type:
public interface Herbivore {public int eatPlants();}
public interface Omnivore {public void eatPlants();}
public interface Supervore extends Herbivore, Omnivore {} // DOES NOT COMPILE
public abstract class AbstractBear implements Herbivore, Omnivore {} // DOES NOT COMPILE

// Interface Variables (in fact, constants):
// Like interface methods, interface variables are assumed to be public. Unlike interface methods, though, interface variables are also assumed to be static and final. Here are two interface variables rules:
// 1. Interface variables are assumed to be public, static, and final. Therefore, marking a variable as private or protected will trigger a compiler error, as will marking any variable as abstract.
// 2. The value of an interface variable must be set when it is declared since it is marked as final.
// In this manner, interface variables are essentially constants defined on the interface level. Because they are assumed to be static, they are accessible even without an instance of a class which implements the interface. The following two interface definitions are equivalent, because the compiler will automatically convert them both to the second example:
public interface CanSwim {
   int MAXIMUM_DEPTH = 100;
   final static boolean UNDERWATER = true;
   public static final String TYPE = "Submersible";
}

public interface CanSwim {
   public static final int MAXIMUM_DEPTH = 100;
   public static final boolean UNDERWATER = true;
   public static final String TYPE = "Submersible";
}
// As we see in this example, the compiler will automatically insert public static final to any constant interface variables it finds missing those modifiers.

public interface CanDig {
   private int MAXIMUM_DEPTH = 100; // DOES NOT COMPILE - private conflicts with assumed public
   protected abstract boolean UNDERWATER = false; // DOES NOT COMPILE - protected conflicts with assumed public, abstract conflicts with assumed final
   public static String TYPE; // DOES NOT COMPILE - constants must be initialized
}

// Default Interface Methods:
// A default method is a method defined within an interface with the default keyword in which a method body is provided. Contrast default methods with "regular" methods in an interface, which are assumed to be abstract and may not have a method body. Classes have the option to override the default method if they need to, but they are not required to do so. If the class doesn't override the method, the default implementation will be used. By providing a default implementation of the method, though, the interface becomes backward compatible with the existing codebase, while still providing those individuals who do want to use the new method with the option to override it:
public interface IsWarmBlooded {
   boolean hasScales(); // normal abstract method
   public default double getTemperature() {return 10.0;} // default method
}
// The following are the default interface method rules:
// 1. A default method may only be declared within an interface and not within a class (even an abstract class).
// 2. A default method must be marked with the default keyword. If a method is marked as default, it must provide a method body.
// 3. A default method is not assumed to be static, final, or abstract, as it may be used or overridden by a class that implements the interface.
// 4. Like all methods in an interface, a default method is assumed to be public and will not compile if marked as private or protected.
// 5. While overriding a default method in a class, which implements the interface, you must not use the keyword default.
public interface Carnivore {
   public default void eatMeat(); // DOES NOT COMPILE - marked as default but doesn't provide a method body
   public int getRequiredFoodAmount() {return 13;} // DOES NOT COMPILE - provides a method body but is not marked with the default keyword
}
// Unlike interface variables, which are assumed static class members, default methods cannot be marked as static and require an instance of the class implementing the interface to be invoked. They cannot be marked as final (because they are allowed to be overridden in subclasses) or abstract (because they have body).

// When an interface extends another interface that contains a default method, it may choose to ignore the default method, in which case the default implementation for the method will be used. Alternatively, the interface may override the definition of the default method using the standard rules for method overriding, such as not limiting the accessibility of the method and using covariant returns. Finally, the interface may redeclare the method as abstract, requiring classes that implement the new interface to explicitly provide a method body. Analogous options apply for an abstract class that implements an interface. For example, the following class overrides one default interface method and redeclares a second interface method as abstract:
public interface HasFins {
   public default int getNumberOfFins() {return 4;}
   public default double getLongestFinLength() {return 20.0;}
   public default boolean doFinsHaveScales() {return true;}
}

public interface SharkFamily extends HasFins {
   public default int getNumberOfFins() {return 8;} // override it
   public double getLongestFinLength(); // replace the default method with a new abstract method, forcing any class,
                                    // implementing the interface, to provide an implementation of the method
   public boolean doFinsHaveScales() {return false;} // DOES NOT COMPILE - has a body, but is not marked as default
}

// Default Methods and Multiple Inheritance:
// If a class implements two interfaces that have default methods with the same name and signature, the compiler will throw an error:
public interface Walk {
   public default int getSpeed() {return 5;}
}
public interface Run {
   public default int getSpeed() {return 10;}
}
public class Cat implements Walk, Run { // DOES NOT COMPILE
   public static void main(String[] args) {
      System.out.println(new Cat().getSpeed()); // it would be not clear which version to call
   }
}
// There is an exception to this rule, though: if the subclass overrides the duplicate default methods, the code will compile without issue - the ambiguity about which version of the method to call has been removed. For example, the following modified implementation of Cat will compile and output 1:
public class Cat implements Walk, Run {
   public int getSpeed() {return 1;}

   public static void main(String[] args) {
      System.out.println(new Cat().getSpeed());
   }
}
// You can see that having a class that implements or inherits two duplicate default methods forces the class to implement a new version of the method, or the code will not compile. This rule holds true even for abstract classes that implement multiple interfaces, because the default method could be called in a concrete method within the abstract class.

// Static Interface Methods:
// These methods are defined explicitly with the static keyword and function nearly identically to static methods defined in classes. In fact, there is really only one distinction between a static method in a class and an interface. A static method defined in an interface is not inherited in any classes that implement the interface. Here are the static interface method rules you need to be familiar with:
// 1. Like all methods in an interface, a static method is assumed to be public and will not compile if marked as private or protected.
// 2. To reference the static method, a reference to the name of the interface must be used. A STATIC METHOD IN AN INTERFACE CAN'T BE CALLED USING A REFERENCE VARIABLE - IT MUST BE CALLED USING THE INTERFACE NAME. Unlike an interface, if you define a static method in a base class, it can be accessed using either a reference variable or the class name.
// The following is an example of a static method defined in an interface:
public interface Hop {
   static int getJumpHeight() {return 8;}
}
public class Bunny implements Hop {
   public void printDetails() {
      System.out.println(this.getJumpHeight()); // DOES NOT COMPILE without a reference to the name of the interface,
                                                   // even though Bunny implements Hop - Bunny doesn't inherit getJumpHeight()!
      System.out.println(Hop.getJumpHeight()); // compiles successfully with a reference to the interface name
   }
}
// A class that implements two interfaces containing static methods with the same signature will still compile at runtime, because the static methods are not inherited by the subclass and must be accessed with a reference to the interface name. Contrast this with the behavior you saw for default interface methods (the code would compile if the subclass overrode the default methods and would fail to compile otherwise). Static interface methods have none of the same multiple inheritance issues and rules as default interface methods do.

// If an interface defines a static method, the class that implements it can define a static method with the same name, but the method in the interface isn't related to the method defined in the class. Static methods in a class and the interface that it implements are not related to each other. A static method in a class doesn't hide or override the static method in the interface that it implements.

// A class can implement multiple interfaces with the same constant names, only if a reference to the constants isn't ambiguous (i.e. interfaceName.CONSTANT rather than CONSTANT).

// ####### Polymorphism:

// An object may be accessed using a reference with the same type as the object, a reference with the type of a superclass of the object, or a reference with the type of an interface the object implements, either directly or through a superclass. Furthermore, a cast is not required if the object is being reassigned to a super type or interface of the object. Regardless of the type of the reference you have for the object in memory, the object itself doesn't change (it only becomes pointed by other variables).

// Once the object has been assigned a new reference type, only the methods and variables available to that reference type are callable on the object without an explicit cast:
public class Primate {
   public boolean hasHair() {return true;}
}

public interface HasTail {
   public boolean isTailStriped();
}

public class Lemur extends Primate implements HasTail {
   public int age = 10;

   public boolean isTailStriped() {return false;}

   public static void main(String[] args) {
      Lemur lemur = new Lemur(); // a reference with the same type as the object
      System.out.println(lemur.age); // 10

      HasTail hasTail = lemur; // a reference with the type of an interface the object implements
      System.out.println(hasTail.isTailStriped()); // false
      System.out.println(hasTail.age); // DOES NOT COMPILE - the reference hasTail has direct access only to methods defined with the HasTail interface;
                                             // it doesn't know which members exist in objects which implement it;
                                             // therefore, it doesn't know the variable age is part of the object

      Primate primate = lemur; // a reference with the type of a superclass of the object
      System.out.println(primate.hasHair()); // true
      System.out.println(primate.isTailStriped()); // DOES NOT COMPILE - the reference primate has access only to methods defined
                                       // in the ancestor (Primate) class; it doesn't know which interfaces are implemented in descenants;
                                       // therefore, it doesn't know about existence of the isTailStriped() method in that descendant (Lemur)
      }
}

// Casting Objects:
// An instance can be automatically cast to a superclass or interface reference without an explicit cast. Alternatively, an explicit cast is required if the reference is being narrowed to a subclass of the object. In the previous example, we created a single instance of a Lemur object and accessed it via superclass and interface references. Once we changed the reference type, though, we lost access to more specific methods defined in the subclass that still exist within the object. We can reclaim those references by casting the object back to the specific subclass it came from:
Primate primate = lemur;
Lemur lemur2 = primate; // DOES NOT COMPILE - we try to convert an ancestor type reference (Primate) back to
                              // a descendant type reference (Lemur) without an explicit cast
Lemur lemur3 = (Lemur)primate; // success when we explicitly cast...
System.out.println(lemur3.age); // ...and we gain back access to all the methods available to the descendant class (Lemur)
// Here are some basic rules to keep in mind when casting variables:
// 1. Casting an object from a subclass to a superclass doesn't require an explicit cast (any Lemur is a Primate for sure).
// 2. Casting an object from a superclass to a subclass requires an explicit cast (it's not guaranteed, that the Primate is a Lemur - for example,
//      it can be a monkey; an explicit cast says: "you can assign safely - I know 100%, that the animal, pointed by primate variable, is a lemur!").
// 3. The compiler will not allow casts to unrelated types:
public class Bird {}
public class Book {
   public static void main(String[] args) {
      Book book = new Book();
      Bird bird = (Bird)book; // DOES NOT COMPILE - the classes Book and Bird don't belong to a same hierarchy branch
   }
}
// 4. Even when the code compiles without issue, an exception may be thrown at runtime if the object being cast is not actually an instance of that class. Even though two classes share a related hierarchy, that doesn't mean an instance of one can automatically be cast to another!
public class Primate {}
public class Lemur extends Primate {
   public static void main(String[] args) {
      Primate primate = new Primate();
      Lemur lemur = (Lemur)primate; // compiles successfully, but throws ClassCastException at runtime; the object,
                                             // referenced by primate var, is not an instance of the Lemur class!
   }
}
// Although this topic is out of scope for the OCA exam, keep in mind that the "instanceof" operator can be used to check whether an object belongs to a particular class and to prevent ClassCastExceptions at runtime. Unlike the previous example, the following code snippet doesn't throw an exception at runtime:
if (primate instanceof Lemur) {
   Lemur lemur = (Lemur)primate;
}
// When reviewing a question on the exam that involves casting and polymorphism, be sure to remember what the instance of the object actually is. Then, focus on whether the compiler will allow the object to be referenced with or without explicit casts.

// Virtual Methods:
// A virtual method is a method in which the specific implementation is not determined until runtime. In fact, all non-final, non-static, and non-private Java methods are considered virtual methods, since any of them can be overridden at runtime. If you call a method on an object that overrides a method, you get the overridden method, even if the call to the method is on a parent reference or within the parent class. In other words, THE VERSION OF THE OBJECT'S ACTUAL TYPE WILL BE CALLED (NOT OF THE POINTER'S TYPE):
public class Bird {
   public String getName() {return "Unknown";}
   public void displayInformation() {
      System.out.println("The bird name is: " + getName()); // it can be the version of this class, or a descendant's class (if getName()
                                                // is overridden in a descendant); that is unknown in compile time
   }
}

public class Peacock extends Bird {
   public String getName() {return "Peacock";} // overridden in this descendant!
   public static void main(String[] args) {
      Bird bird = new Peacock();
      bird.displayInformation(); // "The bird name is: Peacock" - the version of the object's actual type (Peacock)
                                    // is called, not of the pointer's type (Bird)!!!!!!!
   }
}

// Polymorphism and Method Overriding:
// The first rule of overriding is that an overriding method must be at least as accessible as the method, overridden by it. Let's assume this rule is not necessary and consider the following example:
public class Animal {
   public String getName() {return "Animal";}
}

public class Gorilla extends Animal {
   protected String getName() {return "Gorilla";} // DOES NOT COMPILE since protected is less accessible than public;
                                                            // but let's suppose it compiles successfully
}

public class ZooKeeper {
   public static void main(String[] args) {
      Animal animal = new Gorilla();
      System.out.println(animal.getName()); // the reference animal.getName() is allowed because the method is public in the Animal class.
                                 // The version of Gorilla will be called (since the object, pointed by animal, is Gorilla); but the version
                                 // of Gorilla is protected and, therefore, invisible for ZooKeeper! Java eliminates this contradiction,
                                 // disallowing a method from being overridden by a less accessible version.
   }
}

// Likewise, a subclass cannot declare an overridden method with a new or broader exception than in the superclass, since the method may be accessed using a reference to the superclass. For example, if an instance of the subclass is passed to a method using a superclass reference, then the enclosing method would not know about any new checked exceptions that exist on methods for this object, potentially leading to compiled code with checked (by the classification) exceptions which are in the reality not checked, i.e. that are never handled (with "catch") or thrown outwards (with "throws"). Therefore, the Java compiler disallows overriding methods with new or broader exceptions.

// Finally, overridden methods must use covariant return types for the same kinds of reasons as just discussed. If an object is cast to a superclass reference and the overridden method is called, the return type must be compatible with the return type of the parent method. If the return type in the child is too broad, it will result an inherent cast exception when accessed through the superclass reference. For example, if the return type of a method is Double in the parent class and is overridden in a subclass with a method that returns Number (a superclass of Double), then the subclass method would be allowed to return any valid Number, including Integer, another subclass of Number. If we are using the object with a reference to the superclass, that means an Integer could be returned when a Double was expected. Since Integer is not a subclass of Double, this would lead to an implicit cast exception as soon as the value was referenced. Java solves this problem by only allowing covariant return types for overridden methods.

// ####### Exceptions:

//                     java.lang.Object
//                           |
//                     java.lang.Throwable
//                        /      \
//                     /            \
//      java.lang.Exception               java.lang.Error
//               |
//      java.lang.RuntimeException

// <<< Checked exception - inherited directly from java.lang.Exception: >>>
// * "Checked" means "checked at compile time, i.e. processed by a programmer in the code". Since checked exceptions are anticipated, Java enforces that the programmer do something to show the exception was thought about. In other words: if a method throws (or calls a method which throws) a checked exception, then it should handle that exception (using "try-catch" block) or pass it up the stack (using "throws" keyword in the signature), otherwise the program will give a compilation error.
// * Intended for issues a programmer might reasonably be expected to recover from - for example, to display an explanatory message about a business situation.
// * These are technical or business exceptions that are INTERNAL TO THE APPLICATION, and that the application usually can anticipate or recover from.
// * Thrown by Java (built in technical exceptions) or by coders (custom created business rule exceptions - like CandidateTooYong or SalaryTooBigException).
// * In fact, a business rule exception is not necesseraly something bad - it can be a way of program logic branching (an instruction what to do in different circumstances). For example, if OutOfHoursOfOperationException thrown, the program can update other tables (compared to the normal hours). In such a situation, no error message is displayed.
// * Examples of Checked exception:
//      FileNotFoundException   Thrown programmatically when code tries to reference a file that does not exist
//      IOException            Thrown programmatically when there's a problem reading or writing a file
// * More examples: PrinterException, PrintException, SQLException, ServerNotActiveException, SOAPException, URISyntaxException.

// <<< Unchecked (aka runtime) exception - inherited from java.lang.RuntimeException: >>>
// * "Unchecked" means "unchecked at compile time, i.e. NOT required to be processed by a programmer in the code" (but Java will automatically check at runtime).
// * A method is not obliged to handle (i.e. "catch" or pass outwards with "throws") unchecked exceptions. In fact, a huge amount of problems can arise at runtime - it would be impractical to handle all possible exception. For example, a NullPointerException can happen when you try to call a member on a null reference. This can occur in any method. If you had to handle runtime exceptions everywhere, every single method would have that clutter!
// * These are technical exceptions that are INTERNAL TO THE APPLICATION, and that the application usually cannot anticipate or recover from.
// * Usually thrown by JVM, but can be thrown by the programmer as well (for example, IllegalArgumentException if the method discovered, that an argument contains an illegal value, or NullPointerException if the method discovered, that an argument contains null - they are unchecked and, therefore, are not needed to be added tho the "throws" section of the signature).
// * It doesn't mean that if compiler doesn't check exceptions, inherited from java.lang.RuntimeException, we cannot handle them:
class Example { 
   public static void main(String args[]) {
      try {
         int arr[] = {1,2,3,4,5};
         System.out.println(arr[7]);
      }
         catch(ArrayIndexOutOfBoundsException e) {
            System.out.println("The specified index does not exist in array.");
      }
   }
}
// * Examples:
//      ArithmeticException - Thrown by the JVM when code attempts to divide by zero, and both the numbers are integers.
//         If one of the numbers is decimal (the divided number, or divide by 0.0, not by 0), the expression will NOT throw ArithmeticException -
//         instead, it will return Infinity (if the divided number is positive) or -Infinity (if the divided number is negative).
//      ArrayIndexOutOfBoundsException - Thrown by the JVM when code uses an illegal index to access an array
//           IndexOutOfBoundsException - Thrown by the JVM when code uses an illegal index to access an ArrayList
//      ClassCastException - Thrown by the JVM when an attempt is made to cast an exception to a subclass of which it is not an instance
//      IllegalArgumentException - Even though it's a runtime exception, programmers usually use it to indicate that a method has been passed
//                                 an inappropriate argument (when throw, pass an explanatory message as the exception's argument)
//      NullPointerException - Thrown by the JVM when there is a null reference where an object is required
//      NumberFormatException - Thrown by the programmer when an attempt is made to convert a string to a numeric type
//                                                      but the string doesn't have an appropriate format

// <<< Unchecked (severe) exception - inherited from java.lang.Error: >>>
// * A serious problem that a reasonable application should not try to catch. Most such errors are abnormal conditions - something went so horribly wrong that your program should not attempt to recover from it. For example, the disk drive "disappeared". These are conditions that you aren't likely to encounter.
// * These are technical exceptions that are EXTERNAL TO THE APPLICATION, and that the application usually cannot anticipate or recover from. For example, suppose that an application successfully opens a file for input, but is unable to read the file because of a hardware or system malfunction. The unsuccessful read will throw java.io.IOError. An application might choose to catch this exception, in order to notify the user of the problem - but it also might make sense for the program to print a stack trace and exit.
// * Thrown by JVM only (not by the programmer!).
// * Examples of Severe unchecked exception:
//         ExceptionInInitializerError   - when a static initializer throws an exception and doesn't handle it
//         StackOverflowError   - when a method calls itself too many times (this is called infinite recursion because the method typically
//                                                   calls itself without end)
//         NoClassDefFoundError - when a class that the code uses is available at compile time but not runtime
// * More examples: ThreadDeath, VirtualMachineError, IOError (thrown when a serious I/O error has occurred).

// The try-catch block must have curly braces even if there is only one statement in each section:
try // DOES NOT COMPILE
   fall();
catch (Exception e)
   System.out.println("get up");

// The exam will try to trick you with missing clauses or clauses in the wrong order:
try { // DOES NOT COMPILE because the catch and finally blocks are in the wrong order
   fall();
} finally {
   System.out.pr
} catch (Exception e) {
   System.out.println("get up");
}

try { // DOES NOT COMPILE because there must be a catch or finally block
   fall();
}

try { // just fine - catch is not required if finally is present
   fall();
} finally {
   System.out.println("all better");
}

// There is one exception to "the finally block always runs after the catch block" rule: Java defines a method that you call as System.exit(0);. The integer parameter is the error code that gets returned. System.exit tells Java, "Stop. End the program right now". When System.exit is called in the try or catch block, finally does not run.

// Multiple catch blocks are also allowed, provided no superclass exception type appears in an earlier catch block than its subclass. Java looks at the catch blocks in the order they appear. If it is impossible for one of the catch blocks to be executed, a compiler error about unreachable code occurs. This happens when a superclass is caught before a subclass:
class ExhibitClosed extends RuntimeException { }
class ExhibitClosedForLunch extends ExhibitClosed { }
public void visitMonkeys() {
   try {
      seeAnimal();
   } catch (ExhibitClosed e) { // the more specific ExhibitClosedForLunch exception will be caught here since it is a subclass of ExhibitClosed -
                        // which means there is no way for the second catch block to ever run!
      System.out.print("not today");
   } catch (ExhibitClosedForLunch e) {// DOES NOT COMPILE - Java correctly tells us there is an unreachable catch block
      System.out.print("try back later");
   }
}

// If both catch and finally throw an exception, the one from finally gets thrown (the exception from the catch block gets forgotten about):
try {
   throw new RuntimeException(); // it causes to execute the catch
} catch (RuntimeException e) {
   throw new RuntimeException(); // another exception thrown (if there were no finally block, it will be ultimately thrown outwards)
} finally {
   throw new Exception(); // The finally block runs after the try block. Since the finally block throws an exception of its own, this one gets ultimately thrown outwards.
                     // The RuntimeException from the catch block gets forgotten about.
}

// Next we are going to show you the hardest example you can be asked related to exceptions:
public String exceptions() {
   String result = "";
   String v = null;
   try {
      try {
         result += "before ";
         v.length();
         result += "after ";
      } catch (NullPointerException e) {
         result += "catch ";
         throw new RuntimeException();
      } finally {
         result += "finally ";
         throw new Exception();
      }
   } catch (Exception e) {
      result += "done";
   }
   return result; // "before catch finally done"
}

// When a class overrides a method from a superclass or implements a method from an interface, it's not allowed to add new CHECKED exceptions to the method signature:
class CanNotHopException extends Exception { }
class Hopper {
   public void hop() { }
}
class Bunny extends Hopper {
   public void hop() throws CanNotHopException { } // DOES NOT COMPILE
}
// Imagine what would happen if subclasses could add checked exceptions - you could write code that calls Hopper's hop() method and not handle any exceptions. Then if Bunny was used in its place, the code wouldn't know to handle or declare CanNotHopException.
// This rule applies only to checked exceptions. The following code is legal because it has an unchecked exception in the subclass's version:
class Hopper {
   public void hop() { }
}
class Bunny extends Hopper {
   public void hop() throws IllegalStateException { } // OK - IllegalStateException is unchecked
}
// The reason that it's okay to declare new runtime exceptions in a subclass method is that the declaration is redundant. Methods are free to throw any unchecked exceptions they want without mentioning them in the method declaration.

// When overriding a method, the method in the subclass is allowed to throw (declare) FEWER exceptions than the original version (in the superclass or interface). This is legal because callers are already handling them:
class Hopper {
   public void hop() throws CanNotHopException { }
}
class Bunny extends Hopper {
   public void hop() { } // OK; not-declaring an exception means that the method will never throw it - no problem!
}
// A subclass not declaring an exception is similar to a method declaring it throws an exception that it never actually throws. This is perfectly legal.

// Similarly, a class is allowed to declare a subclass of an exception type. The idea is the same. The superclass or interface has already taken care of a broader type:
class Hopper {
   public void hop() throws Exception { }
}
class Bunny extends Hopper {
   public void hop() throws CanNotHopException { } // OK; since a caller can handle any Exception, it can handle CanNotHopException as well!
}
// Bunny could declare that it throws Exception directly, or it could declare that it throws a more specific type of Exception. It could even declare that it throws nothing at all.

// Printing an Exception:
// There are three ways to print an exception. You can let Java print it out, print just the message, or print where the stack trace comes from:
 private static void hop() {
   throw new RuntimeException("cannot hop");
 }

public static void main(String[] args) {
   try {
      hop();
   }
   catch (Exception e) {
      System.out.println(e); // "java.lang.RuntimeException: cannot hop" - toString(), implemeted in Exception, returns exception's name and error message
      System.out.println(e.getMessage()); // "cannot hop"
      e.printStackTrace(); // "java.lang.RuntimeException: cannot hop
                      //  at trycatch.Handling.hop(Handling.java:15)
                      //  at trycatch.Handling.main(Handling.java:7)"
   }
 }

// Will a finally block execute even if the try or the catch block defines a return statement? Yes. The return statement does not return the control to the calling method before execution of the finally block completes. If both the catch and finally blocks define return statements, the calling method will receive a value from the finally block.

// Watch out for code that returns a value from the catch block and modifies it in the finally block. If a catch block returns a primitive data type, the finally block can't modify the value being returned by it. If a catch block returns an object, the finally block can modify the state of the object being returned by it.

// Predicate interface and its test() method.
// This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference. Represents a predicate (boolean-valued function) of one argument. This is a functional interface whose functional method is test(Object) which returns true if the input argument matches the predicate, otherwise false:
test(T t) // evaluates this predicate on the given argument

// The return type of the functional method test in the functional interface Predicate is boolean. The following Lambda expression is trying to return a String value and so the code fails compilation:
Predicate<String> aSeason = (s) -> s == "Summer" ? season1.name : season2.name;
User avatar
Ursego
Site Admin
 
Posts: 143
Joined: 19 Feb 2013, 20:33




Ketones are a more high-octane fuel for your brain than glucose. Become a biohacker and upgrade yourself to version 2.0!



cron
Traffic Counter

eXTReMe Tracker