What is Default Methods in Java?

Default methods are a feature introduced in Java 8, allowing the declaration of methods in interfaces, apart from abstract methods. They are also known as defender methods or virtual extension methods.

With the use of default keyword, these methods are defined within the interface and provide a default implementation. This means they can be directly used by any class implementing this interface without needing to provide an implementation for these methods.

The main advantage of default methods is that they allow the interfaces to be evolved over time without breaking the existing code.

Here’s an example of a default method in an interface:

interface MyInterface {
    void abstractMethod();

    default void defaultMethod() {
        System.out.println("This is a default method in the interface");
    }
}

In the above example, any class implementing MyInterface needs to provide an implementation for abstractMethod(), but not for defaultMethod() unless it needs to override the default implementation.

Before Java 8, we could declare only abstract methods in interfaces. It means that classes which implement the interface were obliged to provide an implementation of all methods declared in an interface. However, this was not flexible for developers, especially when they wanted to add new methods to the interfaces.

For instance, here is an interface used by multiple classes:

interface Animal {
    void eat();
}

Now, if we wanted to add a new method called run(), all classes that implement Animal would need to define this method, which could potentially introduce bugs and is quite cumbersome if we have many classes that implement the interface.

To mitigate such issues, Java 8 introduced default methods in interfaces. With default methods, we can now add new methods in the interface with a default implementation, thereby having the least impact on the classes that implement the interface.

interface Animal {
    void eat();

    default void run() {
        System.out.println("Running");
    }
}

So in the updated Animal interface, the run() method is a default method. Classes implementing Animal can choose to override this method, but they are not obliged to do so. If a class does not provide an implementation for this method, the default implementation from the interface will be used.

Here’s an example implementation of the Animal interface:

class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();  // Output: Dog is eating
        dog.run();  // Output: Running
    }
}

As you can see, the Dog class didn’t implement the run method, but we’re still able to call dog.run() because of the default implementation in the Animal interface.

Note: In case a class implements multiple interfaces and these interfaces have default methods with identical signatures, the compiler will throw an error. The class must override the method to resolve the conflict.

What is a Functional Interface in Java?

A Functional Interface in Java is an interface that has exactly one abstract method. Apart from this abstract method, it can include default and static methods. Java 8 introduced the @FunctionalInterface annotation to ensure an interface follows the rules of Functional Interface. It’s optional but good practice to use this annotation.

Functional interfaces are extensively used in Java’s lambda expressions. The main purpose of a functional interface is to be used as Lambda Expressions or Method References.

Here’s a basic example of defining a functional interface:

@FunctionalInterface
interface GreetingService {
    void sayMessage(String message);
}

You could use it in conjunction with a lambda like this:

GreetingService greetService = message -> System.out.println("Hello " + message);
greetService.sayMessage("world");

In the code above, message -> System.out.println("Hello " + message) is a lambda expression that provides the implementation of the abstract method sayMessage(String message).

Java 8 has also defined several built-in functional interfaces. These built-in interfaces are packed in the java.util.function package. Some common ones include Predicate<T>, Function<T, R>, Supplier<T>, and Consumer<T>. Furthermore, BinaryOperator<T>, UnaryOperator<T>, BiFunction<T, U, R> are some other standard functional interfaces available.

Here are some examples of Java 8 built-in functional interfaces:

1. Predicate

Predicate<T> is a functional interface that takes a single input and returns a boolean value. It is located in java.util.function package.

Predicate<String> lengthCheck = s -> s.length() > 5;
System.out.println(lengthCheck.test("Hello"));  // Output: false

Predicate is often used when you need to pass some sort of condition or filter as a parameter. For example, you might be checking if the User inputs are valid:

Predicate<String> isValidEmail = email -> email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
System.out.println(isValidEmail.test("[email protected]"));  // Output: true
System.out.println(isValidEmail.test("testgmail.com"));   // Output: false

2. Function

Function<T, R> is an interface that accepts one argument and produces a result.

Function<String, Integer> parse = Integer::parseInt;
System.out.println(parse.apply("123"));  // Output: 123

A Function<T, R> can be useful when you need to convert from one type to another, such as transforming a list of String into a list of Integer.

Function<String, Integer> stringToInteger = Integer::parseInt;
List<String> strings = Arrays.asList("1", "2", "3");
List<Integer> integers = strings.stream()
                                .map(stringToInteger)
                                .collect(Collectors.toList());

3. Consumer

Consumer<T> is an interface that takes one argument and returns no results. It is meant for implementing side effects.

Consumer<String> printer = System.out::println;
printer.accept("Hello");  // Output: Hello

The Consumer<T> interface is often used in conjunction with Java streams or Optional, where you have a collection of objects, and you want to perform a certain action on each of the objects.

Consumer<String> printUpperCase = str -> System.out.println(str.toUpperCase());
List<String> names = Arrays.asList("Jon", "Sansa", "Arya", "Bran");
names.forEach(printUpperCase);

4. Supplier

Supplier<T> is an interface that does not take any argument, but it produces a result.

Supplier<LocalDate> current = LocalDate::now;
System.out.println(current.get());  // Output: [current date]

Suppose you have a class RandomService that produces random numbers and is supposed to be used by other classes in your system.

class RandomService {
    Supplier<Double> getRandomNumber = Math::random;
}

// Usage in another class
RandomService rs = new RandomService();
System.out.println(rs.getRandomNumber.get());

5. BinaryOperator and UnaryOperator

UnaryOperator<T> takes one argument and returns a result of the same type. BinaryOperator<T> takes two arguments and returns a result of the same type.

UnaryOperator<String> upperifier = String::toUpperCase;
System.out.println(upperifier.apply("hello"));  // Output: HELLO

BinaryOperator<String> concatenator = String::concat;
System.out.println(concatenator.apply("Hello ", "World"));  // Output: Hello World

You have already learned about a few key functional interfaces in Java and how to use them with lambda expressions. Now, I’ll introduce you to a few more advanced topics about functional interfaces:

1. Custom Functional Interface

If the built-in functional interfaces in Java do not satisfy your requirements, you can define your own functional interfaces. Here is an example of a custom functional interface:

@FunctionalInterface
interface CustomInterface {
    String concatenateStrings(String s1, String s2);
}

You can now use it like this:

CustomInterface ci = (s1, s2) -> s1 + s2;
System.out.println(ci.concatenateStrings("Hello", " World")); // Output: Hello World

2. Method References

In some cases, lambdas just call an existing method. In those cases, we can use method references to make the code clearer. Here are some examples

Consumer<String> printer = System.out::println; // same as s -> System.out.println(s)

Predicate<String> lengthCheck = String::isEmpty; // same as s -> s.isEmpty()

Supplier<LocalDate> current = LocalDate::now; // same as () -> LocalDate.now()

3. Chaining Functional Interface Calls (Compose and AndThen)

You can chain multiple calls of Function, Consumer, and Predicate using default methods they provide, such as compose, andThen.

Function<Integer, Integer> multiplyBy2 = x -> x * 2;
Function<Integer, Integer> add1 = x -> x + 1;
Function<Integer, Integer> add1AndThenMultiplyBy2 = add1.andThen(multiplyBy2);

System.out.println(add1AndThenMultiplyBy2.apply(2)); // Output: 6

Remember that with compose, functions execute in reverse order.

Function<Integer, Integer> multiplyBy2ThenAdd1 = add1.compose(multiplyBy2);

System.out.println(multiplyBy2ThenAdd1.apply(2)); // Output: 5

Chaining calls this way leads to functional-style programming that can make your code more readable and maintainable by creating pipelines of transformations.

Real-world use of the functional interface is prevalent in Java library features such as Stream API, where they come together with lambda expressions to offer functional programming capabilities. They help contribute to writing clean, robust and concurrent code structures.

Overall, functional interfaces bring the power of functional programming to Java and are extensively used for implementing simple callback-style interfaces, or for defining “thin” data structures used in control statements, among other uses.

What are Method References in Java?

Method references in Java are a feature that was introduced in Java 8. They provide a way to refer to a method without actually executing it. They are often used in conjunction with Java’s functional programming features, such as Streams and Lambdas, where a method to be executed is often expected as a parameter.

The syntax for a method reference is the name of the class (or the name of an object), followed by :: and the method’s name. Here’s an example:

List<String> words = Arrays.asList("Hello", "Method", "References", "In", "Java");

// Let's use a method reference to print each word in the list
words.forEach(System.out::println);

In the above code, System.out::println is a method reference. The forEach method expects a lambda that takes a parameter and does something with it. Here, the println method of the System.out class is being referenced, and it will be used to print each word in the list.

There are four types of method references in Java:

  1. Static method reference: They refer to the static methods of a class. For example, ClassName::staticMethodName.
  2. Instance method reference of a particular object: They refer to the instance methods of a particular object. For example, in above code System.out::println.
  3. Instance method reference of an arbitrary object: They refer to the instance methods where the first parameter is the target of the method. For example, String::length.
  4. Constructor reference: They refer to the constructor of a class. For example, ClassName::new.

Let’s take a deeper look at the four kinds of method references with more elaborated examples.

1. Static method references:

Static method references can be used when the method to be invoked is a static method. For example:

package org.kodejava.basic;

import java.util.stream.Stream;

public class StaticMethodRef {
    public static void main(String[] args) {
        String[] array = {"Java", "Python", "Ruby", "JavaScript"};
        Stream.of(array).forEach(StaticMethodRef::printStr);
    }

    static void printStr(String str) {
        System.out.println("printStr method called with value: " + str);
    }
}

In this example, the printStr method is a static method, and we reference this method using StaticMethodRef::printStr.

2. Instance method reference of a particular object:

Instance method references can be used when the method to be invoked is an instance method. For example:

package org.kodejava.basic;

import java.util.stream.Stream;

public class InstanceMethodRef {
    public static void main(String[] args) {
        InstanceMethodRef instance = new InstanceMethodRef();
        String[] array = {"Java", "Python", "Ruby", "JavaScript"};
        Stream.of(array).forEach(instance::printInstanceStr);
    }

    void printInstanceStr(String str) {
        System.out.println("printInstanceStr method called with value: " + str);
    }
}

In this example, printInstanceStr is an instance method, and we create an instance of InstanceMethodRef and refer to an instance method instance::printInstanceStr.

3. Instance method reference of an arbitrary object:

We can do this when we have a collection of instances and want to invoke a method on them. For example:

package org.kodejava.basic;

import java.util.stream.Stream;

public class InstanceMethodReferenceArbitrary {
    public static void main(String[] args) {
        String[] array = {"Java", "Python", "Ruby", "JavaScript"};
        Stream.of(array).map(String::toUpperCase).forEach(System.out::println);
    }
}

In this example, String::toUpperCase invokes the toUpperCase method for every instance of the String in the Stream.

4. Constructor reference:

Constructor references are used for a constructor call. For example:

package org.kodejava.basic;

import java.util.stream.Stream;

class Student {
    String name;

    Student(String name) {
        this.name = name;
    }
}

public class ConstructorReference {
    public static void main(String[] args) {
        Stream.of("John", "Martin", "Don")
                .map(Student::new)
                .forEach(student -> System.out.println("Student name is: " + student.name));
    }
}

In the above example, Student::new creates a new instance of Student.

How do I create a string of repeated characters?

The following code demonstrates how to create a string of repeated characters. We use the String.repeat(int count) method introduced in Java 11. This method takes one parameter of type int which is the number of times to repeat the string. The count must be a positive number, a negative number will cause this method to throw java.lang.IllegalArgumentException.

In the snippet below, we use the method to repeat characters and draw some triangles. We combine the repeat() method with a for loop to draw the triangles.

package org.kodejava.basic;

public class StringRepeatDemo {
    public static void main(String[] args) {
        String star = "*";
        String fiveStars = star.repeat(5);
        System.out.println("fiveStars = " + fiveStars);

        String arrow = "-->";
        String arrows = arrow.repeat(10);
        System.out.println("arrows    = " + arrows);
    }
}

The outputs of the code snippet above are:

fiveStars = *****
arrows    = -->-->-->-->-->-->-->-->-->-->
package org.kodejava.basic;

public class StringRepeatDemo {
    public static void main(String[] args) {
        String asterisk = "#";
        for (int i = 1; i <= 10; i++) {
            System.out.println(asterisk.repeat(i));
        }
}

The outputs of the code snippet above are:

#
##
###
####
#####
######
#######
########
#########
##########
package org.kodejava.basic;

public class StringRepeatDemo {
    public static void main(String[] args) {
        int height = 10;
        for (int i = 1, j = 1; i <= height; i++, j += 2) {
            System.out.println(" ".repeat(height - i) + "*".repeat(j));
        }
    }
}

The outputs of the code snippet above are:

         *
        ***
       *****
      *******
     *********
    ***********
   *************
  ***************
 *****************
*******************

Basic Operators in Java

This article covers basic operators of Java syntax, and how they function. By thorough discussion and coding examples, you’ll be able to use basic operators in your programs like a pro.

What are basic operators?

Java provides different sets of operators to perform simple computations like addition/ subtraction and other advanced operators for decision-making or individual bitwise calculations.

Here are some major categories of operators

  • Arithmetic Operators (+, -, *, /)
  • Relational Operators (==, !=)
  • Logical Operators (&&, ||)
  • Assignment Operators (=, +=, -=)
  • Unary Operators (pre/post-fix)
  • Shift Operators (>>, << )
  • Bitwise Operators (&, |, ^)
  • Ternary/Conditional Operator (?:)
  • Misc Operators

The scope of this article encompass arithmetic, relational and logical operators only.

Arithmetic Operators

You can use basic arithmetic operators to perform a mathematical calculation and impact the value of any variable. Let’s see how it works in Java.

package com.basicoperators.core;

public class ArithmeticOperators {
    public static void main(String[] args) {
        // Addition 
        int apples = 5;
        int oranges = 7;
        int totalFruits = apples + oranges;

        System.out.println("\n-------------Addition---------------- " );
        System.out.println("Apples: " + apples);
        System.out.println("Oranges: " + oranges);
        System.out.println("Total Fruits: " + totalFruits);

        // Subtraction      
        int totalBananas = 24;
        int bananasSold = 12;
        int bananasLeft = totalBananas - bananasSold;

        System.out.println("\n----------------Subtraction--------------- " );
        System.out.println("Total Bananas: " + totalBananas);
        System.out.println("Bananas Sold: " + bananasSold);
        System.out.println("Bananas Left: " + bananasLeft);

        // Multiplication   
        int weeks = 3;
        int daysInAWeek = 7;
        int totalNumberOfDays = weeks * daysInAWeek;

        System.out.println("\n--------------Multiplication-------------- " );
        System.out.println("Days In A Week: " + daysInAWeek);
        System.out.println("Days In A Week: " + daysInAWeek);
        System.out.println("Total Number Of Days: " + totalNumberOfDays);

        // Division
        int totalMinutesConsumed = 420;
        int minutesInOneHour = 60;
        int numOfHours = totalMinutesConsumed / minutesInOneHour;

        System.out.println("\n----------------Division---------------- " );

        System.out.println("Total Minutes: " + totalMinutesConsumed);
        System.out.println("Minutes In One Hour: " + minutesInOneHour);
        System.out.println("Num Of Hours: " + numOfHours);    
    }
}

Output

----------------------Addition---------------------- 
Apples: 5
Oranges: 7
Total Fruits: 12

---------------------Subtraction--------------------- 
Total Bananas: 24
Bananas Sold: 12
Bananas Left: 12

--------------------Multiplication------------------- 
Days In A Week: 7
Days In A Week: 7
Total Number Of Days: 21

----------------------Division---------------------- 
Total Minutes Consumed: 420
Minutes In One Hour: 60
Num Of Hours: 7

Relational Operators

As the name implies, relational operators define the relationship of one instance with another. This means you can compare two numbers and see what relationship do they share. If they are equal to each other, one is greater than or smaller than the other number. Like 2 is less than 3. According to Java syntax, both instances should be of the same data type. For example, you can not compare if an integer is less than a string. Here is a small snippet explaining how you can use basic relational operators in Java.

package com.basicoperators.core;

public class RelationalOperators {
    public static void main(String[] args) {
        int even = 2;
        int odd = 3;

        System.out.println("Even = " + even);
        System.out.println("Odd = " + odd);

        // prints if even is equal to odd
        boolean check = even == odd;
        System.out.println("Is Even equal to Odd? " + check);

        // prints if even is not equal to odd
        check = even != odd;
        System.out.println("Is Even not equal to Odd? " + check);

        // prints if even is greater than odd
        check = even > odd;
        System.out.println("Is Even greater than Odd? " + check);

        // prints if even is less than odd
        check = even < odd;
        System.out.println("Is Even less than Odd? " + check);

        // prints if even is greater than equal to odd
        check = even >= odd;
        System.out.println("Is Even greater than equal to Odd? " + check);

        // prints if even is less than equal to odd
        check = even <= odd;
        System.out.println("Is Even less than equal to Odd? " + check);
    }
}

Output

Even = 2
Odd = 3
Is Even equal to Odd? false
Is Even not equal to Odd? true
Is Even greater than Odd? false
Is Even less than Odd? true
Is Even greater than equal to Odd? false
Is Even less than equal to Odd? true

Logical Operators

Logical Operators in Java are used for decision-making. They allow the programmer to test if the combination of given expressions are true or false. Based on the result of your expression, you can make a decision.

  • AND – returns “true” only if both expressions are true
  • OR – returns “true” if any of the given expressions is true
  • NOT – returns the “inverse” of any given boolean expression

For your better understanding, let’s look at the following snippet.

package com.basicoperators.core;

public class LogicalOperators {
    public static void main(String[] args) {
        String myPet1 = "doggo";
        String myPet2 = "kitty";

        System.out.println("Pet1: " + myPet1);
        System.out.println("Pet2: " + myPet2);

        // implements AND
        boolean check = myPet1.equals("doggo") && myPet2.equals("kitty");
        // returns true only when both conditions are true
        System.out.println("Does my first pet name \"doggo\", and second one \"kitty\"? " + check);

        check = myPet1.equals("dog") && myPet2.equals("kitty");
        // returns "false" even if single condition is false 
        // remember these conditions are case sensitive
        System.out.println("Does my first pet name \"dog\", and second one \"kitty\"? " + check);

        // implements OR
        check = myPet1.equals("doggo") || myPet2.equals("lion");
        // returns "true" even when single condition is true
        System.out.println("Does any of my pet name \"doggo\"? " + check);

        check = myPet1.equals("cat") || myPet2.equals("tiger");
        // returns "false" because both conditions are false
        System.out.println("Does any of my pet name \"tiger\"? " + check);

        // implements NOT
        check = !(myPet1.equals("bingo") && myPet2.equals("kate"));
        // returns "true" when both conditions are true (inverse of statement)
        System.out.println("Does my first pet name \"bingo\", and second one \"kate\"? " + check);

        check = !(myPet1.equals("doggo") && myPet2.equals("kitty"));
        // returns "false" because both conditions are true
        System.out.println("Does my first pet name \"doggo\", and second one \"kitty\"? " + check);
    }
}

Output

Pet1: doggo
Pet2: kitty
Does my first pet name "doggo", and second one "kitty"? true
Does my first pet name "dog", and second one "kitty"? false
Does any of my pet name "doggo"? true
Does any of my pet name "tiger"? false
Does my first pet name "bingo", and second one "kate"? true
Does my first pet name "doggo", and second one "kitty"? false

Conclusion

The basic operators in Java are pretty simple to learn and easy to use. You might get overwhelmed by studying the different operators all at once. However, we recommend you practicing one set at a time. This way, you’ll master all of them soon. As always, you’re welcome to plug-in in case of any confusion. Happy learning!