Table of Contents
Advanced class design:
Anonymous classes are implicitly final and it can never be static.
static method > default method
default method > abstract method
default method > default method (re-declared with different implementation)
default method > static method
static method > non-static method
non-static method > static method
Static methods can never be abstract.
Static interface methods must be invoked using the interface name instead of reference variable – otherwise an error will be raised.
When overriding you set of exceptions may be smaller (none is ok) or the same but no bigger; although you can add as many unchecked exceptions as you need.
Differences between static and default methods in Java 8
(https://stackoverflow.com/questions/27833168/difference-between-static-and-default-methods-in-interface):
- Default methods can be overriden in implementing class, while static cannot.
- Static method belongs only to Interface class, so you can only invoke static method on Interface class, not on class implementing this Interface
- Both class and interface can have static methods with same names, and neither overrides other!
Lambda Expressions and Functional Interfaces:
A local variable has to be final or effectively final to be accessed from an inner class or lambda expression.
All of the java.lang package wrapper classes are immutable: Boolean, Byte, Character, Double, Float, Integer, Long, Short, String.
The parameter names in the lambda expression exist in the same scope as the method or the block in which the lambda expression is created.
Enums:
- Enum constructor is always private.
- Enums are implicitly final.
- Enums cannot extend anything (they already extend java.lang.Enum) but can implement some interface.
- You cannot clone enums.
- Implement Comparable (can be added to sorted collections)
- important note here: elements are compared by the order they were defined! not alphabetically or some other way!
- these methods are important:
- values() – an array of all constants
- valueOf() – return a constant from Enum
- ordinal() – index of constant starting with 0
- name() – name of the constant
Access modifier of constructors match that of the class it belongs to.
Outer classes have access to private inner members of inner classes.
Generic methods:
Generic methods are methods that take generic parameter types – that is they may be used on different types (classes, interfaces) not just one (as in case of simple methods).
Difference between simple and generic method:
- Simple method:
public static void methodName(){}
- Generic method:
public static <T> void methodName(){}
Subtyping does not work for generic parameters.
List<Number> list = new ArrayList<Integer>(); // this is wrong
Wildcard parameter types allow for subtyping.
List<Number> list = new ArrayList<Number>();
or
List<Number> list = new ArrayList<>();
Wildcard parameters
Wildcard parameters can be used when you want just to use methods that access objects.
List<?> list = new ArrayList<Integer>(); System.out.println(list.size());
Wildcard parameters cannot be used when you plan to use methods that modify objects.
List<?> list = new ArrayList<Integer>(); list.add(10);
Collections
Collection name | May add null keys | May add null values |
HashSet | Not applicable | Yes |
HashMap | Yes | Yes |
ConcurrentHashMap | NullPointerException | NullPointerException |
TreeMap methods to be learned:
- pollFirstEntry() – removes first map entry and returns removed Map.Entry, null if map empty
- pollLastEntry() – removes last map entry and returns removed Map.Entry, null if map empty
- tailMap(K fromKey, boolean inclusive) – returns a view of the map starting with fromKey and may include that element or not depending on inclusive. View here is an important term – if you modify the map that is a view of another map the source map will get modified.
Dequeue:
- can act as Queue and as Stack – so it has methods that reproduce behavior of:
- Queue (FirstInFirstOut), has methods:
- add(e) / offer(e)
- add(e) – returns true if element added or throws IllegalStateException if queue already has maximum capacity
- offer(e) – returns true if ok or false if not – no exceptions
- remove() / poll()
- remove() – removes and returns head of queue or throws NoSuchElementException if queue is empty
- poll() – removes and returns head or null if queue is empty
- element() / peek()
- element() – returns head of queue or throws NoSuchElementException if queue is empty
- peek() – returns head or null
- add(e) / offer(e)
- Stack (LastInFirstOut), has methods:
- push(e) – equal to addElement(e); pushes an item to top of stack. Returns the item pushed.
- pop() – removes top element of stack and returns it (just like remove() in queue) or throws EmptyStackException if stack was empty.
- Queue (FirstInFirstOut), has methods:
Nota bene: In Stack peek() may throw an EmptyStackException whilst in Queue peek() would just return null.
⇐ Out (Queue) | Dequeue queue behaviour | ⇐ In | ||
|
| |||
⇒ In (Stack) ⇐ Out (Stack) | Dequeue stack behaviour | |||
|
Collections.sort(someCollection, null) – will just use the default sorting for someCollection – no exception will be thrown.
Sorting strings rule:
spaces < numbers < uppercase < lowercase
From java.util only 2 collections are thread-safe:
- Vector
- Hashtable
You can turn a simple collection in a thread-safe one using java.util.Collections.synchronizedCollectionName() like:
Set s = Collections.synchronizedSet(new HashSet());
Since Java 5 there is a special package with thread-safe collections – java.util.concurrent.
Keys of a TreeMap have to be comparable between each other.
Integer class hashCode() returns the int value.
Exceptions and Assertions
Any exception that is thrown while closing a resource is added to the list of suppressed exceptions of the exception thrown while opening a resource (or thrown from the try block.)
The exception parameter in a multi-catch clause is implicitly final. Thus, it cannot be reassigned. If there is only one exception in the catch clause exception parameter is not final.
Resources are closed automatically at the end of the try block in reverse order of their creation. The close method is called on all the resources one by one even if any resource throws an exception in its close method.
An overriding method must not throw any new or broader checked exceptions than the ones declared in the overridden method. This means, the overriding method can only throw the exceptions or the subclasses of the exceptions declared in the overridden method. It can throw any subclass of Error or RuntimeException as well because it is not mandatory to declare Errors and RuntimeExceptions in the throws clause. An overriding method may also choose not to throw any exception at all.
When an assertion fails, a programmer should always throw the exception.
AutoCloseable‘s close method throws Exception while Closeable‘s close method throws IOException.
In try-with-resources resources are closed at the end of the try block and before any catch or finally block.
Using the Java SE 8 Date/Time API
Important classes in java.time
java.time contains following subpackages:
- java.time.temporal – access date/time fields and units
- java.time.format – format input/output of date/time objects
- java.time.zone – handle timezones
- java.time.chrono – support foreign calendar systems
LocalDate
java.time.LocalDate – date without time or timezone. Represented in ISO-8601 – YYYY-MM-DD – 2018-10-26.
- LocalDate.now() – current date in YYYY-MM-DD formate ex. 2018-05-07. System time, default timezone.
- LocalDate.of(2018, 1, 5) – not 01
To avoid ambiguity with months – java.time.Month enum may be used.
ex.: LocalDate.of(2018, Month.MAY, 05)
LocalTime
Represents time without dates or time zones. Time is in ISO-8601 calendar system format: HH:MM:SS.nanosecond.
LocalDateTime
Represents both date and time – without time zones.
You can extract LocalDate and LocalTime from LocalDateTime using .toLocalDate() and .toLocalTime().
When printing LocalDateTime you get something like 2018-10-03T16:14:11.344. T is a separator between date and time.
Instant
Instant is used for timestamps. It stores seconds since the beginning of Unix epoch (January 1, 1970 at 00:00:00). Negative values are stored for any time before that moment.
Instant stores seconds in long types variables and nanoseconds in int type.
Nota bene: Instant.now()’s time zone is Greenwich not system default time zone.
Period
Used for measuring an amount of time in years, months, days.
Important methods:
- Period.between(LocalDate a, LocalDate b)
- Period.of(int years, int months, int days)
- Period.ofWeeks(int a)
- Period.ofDays(int a)
- Period.ofMonths(int a)
- Period.ofYears(int a)
- Period.parse(CharSequence string) – ex. Period.parse(“P2Y1M3D”)
Printing returns something like: P3Y2M4D.
P – period
Duration
Duration is similar to Period but it works with hours, minutes, seconds, milliseconds (millis) and nanoseconds (nanos).
When printed you obtain: PT4H3M46S.
Note the smallest unit being printed is S. Millis and nanos are printed as decimal formatted units of seconds.
If a section has zero value it is omitted.
Duration duration = Duration.ofMillis(2); System.out.println(duration);
Prints: PT0.002S
Duration duration = Duration.ofNanos(3); System.out.println(duration);
Prints: PT0.000000003S
Enum ChronoUnit (implementation of TemporalUnit – the only one as of version 8) is very handy when you need to work with time-related constructos and so on.
Duration duration = Duration.of(2, ChronoUnit.NANOS); System.out.println(duration);
Working with time zones and daylight savings
Time zones are identified by the offset from GMT(Greenwich Mean Time – GMT aka UTC/Greenwich).
ZoneId
Zone ids are held in a Set<String>. Like: “Asia/Kolkata”
ZoneId.getAvailableZoneIds().forEach(System.out::println);
Asia/Aden
America/Cuiaba
Etc/GMT+9
Etc/GMT+8
Africa/Nairobi
America/Marigot
Asia/Aqtau
Pacific/Kwajalein
America/El_Salvador
…
ZoneId zoneId = ZoneId.of("America/Marigot"); System.out.println(zoneId.getId()); System.out.println(zoneId.normalized()); System.out.println(zoneId.getDisplayName(TextStyle.FULL, Locale.US)); System.out.println(zoneId.getDisplayName(TextStyle.SHORT, Locale.US));
America/Marigot
America/Marigot
Atlantic Time
AT
ZoneOffset
ZoneOffset represents zone offset from GMT (UTC/Greenwich).
System.out.println(ZonedDateTime.now().getOffset());
+03:00
ZonedDateTime
LocalDate + LocalTime + ZoneId = ZonedDateTime
LocalDate localDate = LocalDate.now(); System.out.println(localDate); LocalTime localTime = LocalTime.now(); System.out.println(localTime); ZoneId zoneId = ZoneId.systemDefault(); System.out.println(zoneId); ZonedDateTime zonedDateTime = ZonedDateTime.of(localDate, localTime, zoneId); System.out.println(zonedDateTime);
2018-05-14
16:42:46.939
Europe/Athens
2018-05-14T16:42:46.939+03:00[Europe/Athens]
LocalDateTime + ZoneId = ZonedDateTime
LocalDateTime localDateTime = LocalDateTime.now(); System.out.println(localDateTime); ZoneId zoneId = ZoneId.systemDefault(); System.out.println(zoneId); ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, zoneId); // or localDateTime.atZone(zoneId) System.out.println(zonedDateTime);
2018-05-14T16:55:16.011
Europe/Athens
2018-05-14T16:55:16.011+03:00[Europe/Athens]
I/O Fundamentals
Console is used for interaction with user – usually through command-line and keyboard.
Console is in System.io package.
User typed info (character format) is read using:
- readLine()
- readPassword()
Console has a Reader and a PrintWriter associated with it which can be received using:
- reader()
- writer()
For Console to be available – JVM must be started from command-line and standard input & output streams should not be redirected. As opposite to JVM being started by some background job scheduler. If Console is there it is acquired by System.console(); if not – this method returns null.
Return type of readPassword() is char[] and not a String.
Serializable is a “marker” interface – meaning it does not declare any methods.
During deserialization no-args constructor of the first non-serializable super class is invoked.
http://www.oracle.com/technetwork/articles/java/javaserial-1536170.html
“Because readObject() can read any serializable object, a cast to the correct type is required. With that in mind, the class file must be accessible from the system in which the restoration occurs. In other words, the object’s class file and methods are not saved; only the object’s state is saved.”
Non-serializable fields of a serializable object must be marked as transient. Transient and static fields are never serialized.
Although Serializable interface has no methods defined it IS possible to change the default behavior of the JVM for serialization deserialization. For that we have to define 2 methods in our class:
- private void writeObject(ObjectOutputStream out) throws IOException;
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException;
JVM looks for these methods and in case they are there uses them for serializing/deserializing purposes. Both ObjectOutputStream & ObjectInputStream have methods that guarantee the default serialization/deserialization functionality:
- out.defaultWriteObject()
- in.defaultReadObject()
These methods are added to the body of writeObject/readObject and are surrounded with custom logic. All of these manipulations allow for customization of serialization/deserialization.
The order of values to be read explicitly in readObject must be exactly the same as the order they were written in writeObject.
File class:
- mkdir() – will create last directory in path
- mkdirs() – will create directories recursively – all missing ones in the supplied path
- getParentFile() – returns a File object
- getParent() – returns a String
FileOutputStream
has a constructor with append as 2nd argument – setting it to true will make FileOutputStream append characters to the end of the file instead of beginning.
OutputStream
doesn’t provide methods for writing primitives – writes bytes only.
DataInput
/DataOutputStream
have specialized methods for writing primitives – ex. writeInt(), writeChar() etc.
Data streams support binary I/O of primitive data type values (boolean
, char
, byte
, short
, int
, long
, float
, and double
) as well as String values.
DataOutputStream
can only be created as a wrapper for an existing byte stream object.
ObjectInputStream
and ObjectOutputStream
are used with serializable objects + primitives (covers DataInput/DataOutputStream functionality).
A stream can only contain one copy of an object, though it can contain any number of references to it. However, if a single object is written to two different streams, it is effectively duplicated — a single program reading both streams back will see two distinct objects.
PrintWriter
has overloaded print methods for writing various primitives. None of the PrintWriter methods throw any I/O exceptions because they suppress the errors in writing and set an internal flag for error status instead.
The checkError
method returns true
if there has been a problem in writing.
There is no way you can chain a Reader and a Writer together.
Buffered streams are used to wrap unbuffered ones.
There are 4 different buffered stream types:
BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
For wrapping you just pass the unbuffered stream to the buffered stream’s constructor:
BufferedReader br = new BufferedReader(new FileReader("filename.txt"));
BufferedReader
– java.nio.file.NoSuchFileException
FileInputStream, FileOutputStream, RandomAccessFile – java.io.FileNotFoundException