EXAMPLE FOR CLASS DECRARATION:
Fields: variable declarations.
Methods: functions with or without passed parameters, with or without return value.
In a class the execution pointer moves from top to bottom.
Naming convention is that all words within a class name must be started with upper-cased letter, and no special characters nor underscores (_) should be used.
An application needs a main (root) class which carries the main method (which invokes first when the application starts) and imports, calls, uses all other classes, fields and methods of the application.
Name of the root class equals with the .java (or .class) file name and equals with the name of the application.
The contents of the root class must be static.
With the "this" keyword it is possible to:
access a constructor within the class
access a field within the class
access a method within the class
In static or interface content the "this" keyword must not be used.
EXAMPLE FOR THE "THIS" KEYWORD:
In order to create an instance from a class, a constructor method of that class is invoked. This is an initialization process where inner fields are set.
In cases there is no constructor, the Java invokes a default constructor instead which has no arguments nor method code block.
There can be more constructors implemented in a class.
Invoking a constructor along with declaring a variable:
ClassName variableName = new ClassName(...);EXAMPLE FOR INITIALIZING AN OBJECT:
The constructor method has to have the same name then its owner class has, and there is no return value (nor void).
There can be overloaded versions of the constructor as well (with different method signatures).
EXAMPLE FOR A CONSTRUCTOR:
Because the execution flow begins at the top, the constructors have to be the first methods in the class.
The "this" keyword refers to the class, the "this()" method name refers to the constructors where the argument list will tell which constructor should be called internally.
In case the "this()" is called, the caller statement must be the first one in the caller constructor.
EXAMPLE FOR CALLING THIS():
In case the constructor is private in a class, the solution is called singleton class.
It is a common practice for a class, that fields are not accessible from outside the class, instead it is better to introduce getter/setter methods, and instead of setter methods, introduce proper constructors. This technique will protect the field as the constructor/setter methods will let value giving through data check.
EXAMPLE FOR GETTER/SETTER METHODS:
Best practice is to protect the data in OOP world. In case there is an instance of a class (an object), that instance should not be modified for their fields but instead create a new instance with new data and replace the old object with it behind the same reference.
Of course, in time the memory could be overloaded since the old objects are still in memory. Java uses the Garbage Collector to delete all objects without reference pointing to them from memory. Also, Java uses memory pools to keep the objects there and if a new variable gets its value, Java will look for existing object in the pool to reference that to the variable, with this spares memory.
In immutable world there is no setter method in a class. The initial settings are made via the constructor. Also, all fields are private and final.
Immutability ensures that the data contained in an object stays constant in time. Data can be transferred between threads or scopes without being worried about changes, and no synchronization is needed. Therefore, using immutable objects has no side effects.
For short, an immutable object is thread-safe and free for side effects.
EXAMPLE FOR A BASIC IMMUTABLE CLASS:
In order to make an immutable object universal for examining equality, accessing and converting to String, additional methods should be added to its class:
equals()
hashCode()
toString()
These methods are inherited from Object class therefore overriding is necessary.
EXAMPLE FOR AN UNIVERSAL IMMUTABLE CLASS:
Naturally, immutable classes can have other methods as well but only if those methods will not modify the data stored.
In case there are no other methods except what are needed in an universal immutable class, in order to prevent boilerplate coding, from higher versions of Java records can be used.
EXAMPLE FOR A NON-STATIC (INNER) NESTED CLASS:
EXAMPLE FOR A STATIC NESTED CLASS:
Only the nested class can be static.
The nested class can not reach the outer class.
Access modifiers can be: -/private/protected.
Anonymous classes are non-static (inner) nested classes without a named class declaration.
It is a kind of variable declaration but with class code block.
EXAMPLE FOR ANONYMOUS CLASSES:
Type can be a superclass (the anonymous class will inherit from it) or an interface (will implement it).
Usage: creating a temporary class for executing a definite task in place, dynamically.
Perfect solution for polymorphism as an entity is created for only one task from a superclass/interface.
EXAMPLE FOR EXTENDING:
EXAMPLE FOR IMPLEMENTING:
It is a formula which ensures that from a class there can be only one object.
Can be useful for handling a database channel or central logging where more than one object must be avoided.
EXAMPLE FOR A SINGLETON CLASS:
An enumeration class is a special class type which contains a series of constants and automatically made methods, and can contain user-defined fields and methods as well.
Safe in compile-time: an enum variable can get only the pre-defined values or throws compile-time error.
Enum classes are final by default. They can not be inherited.
Enum classes can not inherit (extend) from other classes. Reason: the declaration of an enum class consists of an inheritance from java.lang.Enum by default, hidden, and in Java, there is no multiple inheritance.
Enum classes can implement interfaces since there is no restriction in Java for number of implements.
EXAMPLE FOR AN ENUMERATION TYPE:
Every constant has an inner integer identifier, starting with 0, following the defined constant names.
These values can be accessed by using the constant names. This makes the code more readable, verbal.
The compiler creates an object for every constant under the EnumName class so the access is EnumName.CONTANTNAME1 etc.
For the enum class, the compiler creates methods automatically for all sub-objects.
EXAMPLE FOR ENUMERATION TYPE METHODS:
Additionally to the enumeration "type", user-defined fields and methods can be added.
The particular object will have the extended capabilities.
EXAMPLE FOR ENUMERATION CLASS:
For creating constant-value pairs (value can be String but in special cases any orderable type like int or char).
By using it, the enumeration will be more verbal.
In case pairs are needed, although it is a matter of taste, this version is preferred to enumeration class since all pairs can be seen together.
EXAMPLE FOR ENUMERATION CONSTRUCTOR:
EXAMPLE FOR DECLARING ENUMERATION (CONSTRUCTOR VERSION):
Of course, the automatically created methods will be created for this usage of enum as well.
These methods (except the .name() method) can be overridden.
Because for all constants will be an object created, must take it into consideration.
EXAMPLE FOR ENUMERATION STRING:
Classes which can be used with any kind of data. For this, a virtual type called T or "type parameter" is used as its type.
No primitive types can be used as generics class.
By using generics class, the type structure can be designed once, in its instances it will be virtually inherited via a wrapper or class type.
Collection subclasses use generics at declaration.
EXAMPLE FOR DECLARING GENERICS CLASS:
EXAMPLE FOR CREATING INSTANCES OF GENERICS CLASS (DESIGNED IN PREVIOUS EXAMPLE):
EXAMPLE FOR DECLARING A COLLECTION SUBCLASS:
By using <T>, any types except primitive types can be accepted. Narrowing is possible by using <T extends typeClass> where typeClass can be e.g. Number for numbers or any custom types so T can be only a subtype of typeClass.
Such T is called bounded type.
EXAMPLE FOR DECLARING GENERICS CLASS OF NUMBERS:
Advantages of using generics:
code re-usability
compile-time type checking
used with Collections (e.g. ArrayList<String> creates a String-based instance of ArrayList<T>)
In subclasses of Collection and in arrays the element type means all elements must have the same type.
In case the need is to have a variable which can contain more than one type, use a multi-type class.
As an element, primitive types, wrapper and other classes can be used together.
The number of contained types depends on needs.
With using this solution, primitive types can be stored in collections and arrays.
EXAMPLE FOR DECLARING AN IMMUTABLE MULTI-TYPE CLASS FOR WRAPPERS:
EXAMPLE FOR DECLARING COMPARATOR FOR AN IMMUTABLE MULTI-TYPE CLASS FOR WRAPPERS:
EXAMPLE FOR USING AN IMMUTABLE MULTI-TYPE CLASS FOR WRAPPERS:
EXAMPLE FOR DECLARING AN IMMUTABLE MULTI-TYPE CLASS FOR PRIMITIVES:
EXAMPLE FOR DECLARING COMPARATOR FOR AN IMMUTABLE MULTI-TYPE CLASS FOR PRIMITIVES:
EXAMPLE FOR USING AN IMMUTABLE MULTI-TYPE CLASS FOR PRIMITIVES:
Java lets the developers declare classes:
in different files
in the same file, above each other
nested, a class declared inside a class
Although Java lets it, it is advised to organize all classes into different files. Best practice is to keep everything simple so if needed, the classes can be organized into a multi-level package system under the root class.
Following best practices for inheritance is also important.
Classes can not be transmitted over the network. Exceptions are classes implementing the Serializable interface. With this, fields of an instance can be converted into bits and bytes which enables transmitting. Methods cannot be transmitted.
A class can contain fields which have custom class types with their own constructors.
EXAMPLE FOR NESTED CLASS AS FIELD (CONSTRUCTOR IN CONSTRUCTOR):
EXAMPLE FOR NESTED CLASS AS FIELD (CONSTRUCTOR IN FIELD DECLARATION):
In the above examples with every instance of Class2 a new instance of Class1 will be nested.
These two solutions above are not comfortable:
it is not possible to create independent unit test for Class1 and Class2 since the instantiating of Class1 happens along with instantiating Class2
in case Class1 has been changed, class tests of Class2 must be revisited
in case instantiating of Class1 is not possible due to any problems, tests of Class2 will break
the caller class (e.g. Main) will instantiate only Class2
The best practice in such cases is the class dependency injection:
writing independent unit tests for every injected classes becomes possible
testing the container class becomes possible by mocking the injected classes
the caller class (e.g. Main) will instantiate all classes
EXAMPLE FOR CLASS DEPENDENCY INJECTION:
In case more container classes would use dependency injection for the same class, there is a possibility that in time the heap will be full.
If the object created from the injected class serves a common function for all (e.g. an email sender), best practice is to use singleton.
EXAMPLE FOR USING SINGLETON FOR CLASS DEPENDENCY INJECTION:
EXAMPLE FOR USING SINGLETON FOR CLASS DEPENDENCY INJECTION: