Java Puzzles
Videos |
---|
Type inference
- helps remove the noise from the code
- IDE’s provides the hover and know feature to know the type of the object.
- allows the compiler to know the type about
var test = "test";
test.foo();
Notice the compile time log : location: variable test of type String
error: cannot find symbol
test.foo();
^
symbol: method foo()
location: variable test of type String
notice in
the byte code
LOCALVARIABLE test Ljava/lang/String; L1 L2 1
public static main([Ljava/lang/String;)V
L0
LINENUMBER 5 L0
LDC "test"
ASTORE 1
L1
LINENUMBER 7 L1
RETURN
L2
LOCALVARIABLE[] args
Ljava/lang/String; L0 L2 0
LOCALVARIABLE test Ljava/lang/String; L1 L2 1
MAXSTACK = 1
MAXLOCALS = 2
Use of type inference
When the response is received from a call of a method or a service, use type inference because its type is determined by the return type of the method or the service being called.
Arrays asList add() vs set()
List<Integer> numbers = Arrays.asList(1,2,3);// does not support add method
System.out.println(numbers.getClass());//class java.util.Arrays$ArrayList
try{
numbers.add(4);//this fails
} catch (Exception e){
System.out.println("add unsupported ");//This runs, as add is not supported in Arrays.asList()
}
try {
numbers.set(2, 2);//This works
} catch (Exception ex) {
System.out.println("set() unsupported");
}
Static of()
Quit using Arrays.asList
and start using List.of()
, **the immutable variant
**. Similarly, use Static Factories Set.of()
and Map.of()
.
- The Set’s
of()
does not permit duplicate - the
of
methods does not permit nulls.- IDE Warns :
Passing 'null' argument to parameter annotated as @NotNull
- throws
NullPointerException
if null is tried to be inserted
- IDE Warns :
Immutability makes it safe to make a copy and safe to share. the Of() factory methods are smart. If the collection is truly immutable, it shares a reference instead of duplicating.
remove
Be careful while using the var
Collection<Integer> numbers = new ArrayList<Integer>(getIntegers());
numbers.remove(1);//Removes the Object => //[2, 3]
var numbers = new ArrayList<Integer>(getIntegers());
numbers.remove(1);//overloaded Remove method that takes Integer instead of Object
//[1, 3]
Function Purity - Shared Mutability
List<String> words = getData(url);//Add more data
List<String> result = new ArrayList<>();//Shared Mutable Variable
words
.parallelStream() //Gives problem
//.stream
.map(String::toUpperCase)
.forEach(name -> result.add(name));//Shared Mutability is BAD
- The execution is always lazy in Java or C#.
- For Kotlin and Scala you can choose between eager and lazy
- In Kotlin, by default, its eager evaluation. if using
asSequence()
, then its lazy
- In Kotlin, by default, its eager evaluation. if using
- For Kotlin and Scala you can choose between eager and lazy
- Functional programming relies on lazy evaluation for efficiency
- Lazy evaluation relies on purity of functions for correctness.
Programmers need to make sure that Lambdas are pure
Rule for purity
Rule 1 is necessary but not sufficient
- No shared mutability : The function does not make any change that is visible outside
- The function does not depend on anything that may change from outside
Parallel Stream
//Which Thread will transform run
List.of(1,2,3).stream()
.parallel()
.map(number -> transform(number))
.sequential()
.forEach(number -> print(number));
Java 8 streams do not segment the pipeline for different threading model. The * *last setting** overrides the entire pipeline.
Reactive streams segment the pipeline for different threading model.
Inheritance
Do not do anything serious in the constructor, especially do not call * *virtual method**
Lesson from effective Java: Make the constructor simple and private and make the Factory method create it. By the time you get to the Factory method, the constructor would have been completed.
class Base{
public Base(){//Constructor
System.out.println("In base");
check();// The check of the Derived is called
}
public void check(){}
}
class Derived extends Base {
private final String value;
public Derived(String value) {
System.out.println("In Derived");
this.value = value;
}
@Override
public void check() {
if (value.length() == 0) {
throw new RuntimeException("Null Value");
}
}
}
public static void main(String[] args) {
try {
new Derived("");//Null Pointer exception
} catch (Exception e){
System.out.println(e);
}
}
toList or .collect(Collectors.toList())
It is better to use toList directly in the stream rather than
.collect(Collectors.toList())
.toList();//Immutable List
.collect(Collectors.toList()); //Mutable
.collect(Collectors.toUnmodifiableList())//Immutable
Records
record Year(int year){
//Avoid Canonical constructors as much as possible.
// Use the compact constructor instead
//Compact constructor is a filter or a pre-processor before the constructor is called.
//code --> Compact constructor --> constructor.
Year {
if(year < 0){
throw new RuntimeException("Negative Year");
}
if(year < 100){
//this.year = 2000 + year;//This is not available yet
year = 2000 + year;
}
}
}
CopyOf
asList creates a mutable List ofList creates immutable
What is immutable is safe to share - returns the same reference to it What is immutable is safe to copy - it never changes ever
Stock price at an instant of time is immutable
In Domain Driven Design, the value objects are expected to be immutable
Teeing
Exceptions
catch expressions (introduced in Java 16) and
Use Case : well-suited for simple exception handling cases without complex logic or control flow change
- Catch expressions are limited to a single expression, so cannot use control flow statements directly within the catch expression.
// Using Catch Expressions (Java 16+)
try {
int result = 10 / 0; // ArithmeticException
} catch (ArithmeticException e) -> System.err.println("Arithmetic Exception occurred: " + e.getMessage());
- Encapsulate any control flow logic within a separate method or code block and call it from catch expression
try {
int result = performComplexOperation();
} catch (CustomException e) -> logAndThrow(e);
private void logAndThrow(CustomException e) {
System.err.println("Custom Exception occurred: " + e.getMessage());
logError(e);
throw new AnotherException();
}
- Catch Expressions Encapsulating Control Flow
try {
int result = performComplexOperation();
} catch (CustomException e) -> {
System.err.println("Custom Exception occurred: " + e.getMessage());
return; // Encapsulating control flow
}
catch statements (traditional approach)
- The catch block can contain multiple statements, including control flow statements like return or break.
- Provides flexibility to control the program’s flow after catching an exception
- Use Cases: Suitable for handling complex exception scenarios.