Java and Kotlin: A Practical comparison (part II)
Jos茅 Ram贸n (JR)
Posted on January 4, 2024
Design Patterns
We will analyze how some design patterns are implemented in both languages.
1.- Optional Pattern
In Java, Optional doesn't solve the Null Pointer Exception or NPE problem. It just wraps it and "protects" our return values.
Optional<String> getCity(String user) {
var city = getOptionalCity(user);
if (city != null)
return Optional.of(city);
else
return Optional.empty();
}
Optional.ofNullable(null)
.ifPresentOrElse(
email -> System.out.println("Sending email to " + email),
() -> System.out.println("Cannot send email"));
Optional is useful for returning types, but it should not be used on parameters or properties.
getPermissions(user, null);
getPermissions(user, Optional.empty()); // Not recommended
KOTLIN
Solution: Nullability is built into the type system. Kotlin embraces null.
String? and String are different types. T is a subtype of T?.
val myString: String = "hello"
val nullableString: String? = null // correct!!
In Kotlin, all regular types are non-nullable by default unless you explicitly mark them as nullable. If you don't expect a function argument to be null, declare the function as follows:
fun stringLength(a: String) = a.length
The parameter a has the String type, which in Kotlin means it must always contain a String instance and it cannot contain null.
An attempt to pass a null value to the stringLength(a: String) function will result in a compile-time error.
This works for parameters, return types, properties and generics.
val list: List<String>
list.add(null) // Compiler error
2.- Overloading Methods
void log(String msg) { ......... };
void log(String msg, String level) { ......... };
void log(String msg, String level, String ctx) { ......... };
KOTLIN
In kotlin we declare only one function, because we have default arguments and named arguments.
fun log(
msg: String,
level: String = "INFO",
ctx: String = "main"
) {
.........
}
log(level="DEBUG", msg="trace B")
3.- Utility static methods
final class NumberUtils {
public static boolean isEven(final int i) {
return i % 2 == 0;
}
}
In some projects we may end up declaring the same utility function more than once.
KOTLIN
fun Int.isEven() = this % 2 == 0 // Extension function
2.isEven()
4.- Factory
public class NotificationFactory {
public static Notification createNotification(
final NotificationType type
) {
return switch(type) {
case Email -> new EmailNotification();
case SMS -> new SmsNotification();
};
}
}
KOTLIN
In Kotlin a function is used instead of an interface.
// This would be a code smell in Java
fun Notification(type: NotificationType) = when(type) {
NotificationType.Email -> EmailNotification()
NotificationType.SMS -> SmsNotification()
}
}
val notification = Notification(NotificationType.Email)
5.- Singleton
// Much code, it's not even thread-safe
public final class MySingleton {
private static final MySingleton INSTANCE;
private MySingleton() {}
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
}
KOTLIN
This pattern is built into the Kotlin language. It's lazy and thread-safe.
object Singleton {
val myProperty......
fun myInstanceMethod() {
...............
}
}
6.- Iterator
This can be applied only to collections, not to user defined classes.
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
var iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element); // A, B, C
}
KOTLIN
val list = listOf("A", "B", "C")
for (elem in list) {
println(elem)
}
This can be applied to any class that has the iterator operator function defined.
class School(
val students: List<Student> = listOf(),
val teachers: List<Teacher> = listOf()
)
operator fun School.iterator() = iterator<Person> { // Extension function
yieldAll(teachers)
yieldAll(students)
}
val mySchool = School()
for (person in mySchool) {
println(person)
}
Likewise, the operator function compareTo must be used to compare objects.
7.- Comparable
class School(val students: List<Student>, val teachers: List<Teacher>)
override fun School.compareTo(other: School) =
students.size.compareTo(other.students.size)
fun main() {
val school1 = School(listOf(Student("John"), Student("Alice")), listOf(Teacher("Mr. Smith")))
val school2 = School(listOf(Student("Bob"), Student("Eve"), Student("Carol")), listOf(Teacher("Mrs. Johnson")))
if (school1 > school2) {
println("$school1 has more students than $school1")
}
}
8.- Strategy pattern
Implementation with interfaces
This is the classical approach, shown in Kotlin.
fun interface PaymentStrategy {
fun charge(amount: BigDecimal) : PaymentState
}
Next, we implement the interface for all the different payment methods we want to support:
class CreditCardPaymentStrategy : PaymentStrategy {
override fun charge(amount: BigDecimal) : PaymentState = PaymentState.PAID
}
class PayPalPaymentStrategy : PaymentStrategy {
override fun charge(amount: BigDecimal) = PaymentState.PAID
}
This is the resulting class:
class ShoppingCart2(private val paymentStrategy: PaymentStrategy) {
fun process(totalPrice: BigDecimal) = paymentStrategy.charge(totalPrice)
}
Implementation with Function Types
This implementation is easier to read than the previous one, but it's less reusable and less maintainable.
class ShoppingCart(private val paymentProcessor: (BigDecimal) -> PaymentState) {
fun process(totalPrice: BigDecimal) = paymentProcessor(totalPrice)
}
typealias PaymentStrategy = (BigDecimal) -> PaymentState
class ShoppingCart(private val paymentProcessor: PaymentStrategy) {
fun process(totalPrice: BigDecimal) = paymentProcessor(totalPrice)
}
This is how it's used:
val creditCardPaymentProcessor = { amount: BigDecimal -> ... }
val payPalPaymentProcessor = { amount: BigDecimal -> ... }
**JAVA
In Java, function types have a strange syntax.
interface PaymentProcessor {
public Function<BigDecimal, PaymentState> process;
};
This is how it's used:
class creditCardPaymentProcessor implements PaymentProcessor {
@Override
public Function<BigDecimal, PaymentState> process = .....;
};
It's quite annoying having to create a class per strategy.
Posted on January 4, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.