Java Basic Archives - Huong Dan Java https://huongdanjava.com/category/java-basic Java development tutorials Wed, 19 Nov 2025 08:47:24 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://huongdanjava.com/wp-content/uploads/2018/01/favicon.png Java Basic Archives - Huong Dan Java https://huongdanjava.com/category/java-basic 32 32 Video: Mastering the Switch statement in Java – Part 1 https://huongdanjava.com/video-mastering-the-switch-statement-in-java-part-1-2.html https://huongdanjava.com/video-mastering-the-switch-statement-in-java-part-1-2.html#respond Wed, 19 Nov 2025 08:40:30 +0000 https://huongdanjava.com/?p=24812 The post Video: Mastering the Switch statement in Java – Part 1 appeared first on Huong Dan Java.

]]>

The post Video: Mastering the Switch statement in Java – Part 1 appeared first on Huong Dan Java.

]]>
https://huongdanjava.com/video-mastering-the-switch-statement-in-java-part-1-2.html/feed 0
Deque in Java https://huongdanjava.com/deque-in-java.html https://huongdanjava.com/deque-in-java.html#respond Mon, 20 Oct 2025 10:12:40 +0000 https://huongdanjava.com/?p=24711 Interface Deque (short for Double-Ended Queue) in Java is an interface that defines a data structure that allows us to add or remove elements at the front or rear: You can watch the videos below: and: This Deque interface extends from the Queue interface and… Read More

The post Deque in Java appeared first on Huong Dan Java.

]]>
Interface Deque (short for Double-Ended Queue) in Java is an interface that defines a data structure that allows us to add or remove elements at the front or rear:

You can watch the videos below:

and:

This Deque interface extends from the Queue interface and it has 2 main implementations: ArrayQueue and LinkedList, as follows:

We can use Deque to implement:

  • Stack with LIFO (Last In First Out): add elements and remove elements from the front
  • or Queue with FIFO (First In First Out): add elements from the rear, remove elements from the front

To work with Deque, you can use some of its methods as follows:

addFirst() and addLast() methods

  • addFirst(E e): add element at the front.
  • addLast(E e): add element at the rear.

For example:

package com.huongdanjava.java;

import java.util.ArrayDeque;
import java.util.Deque;

public class Application {

  public static void main(String[] args) {
    Deque<Integer> deque = new ArrayDeque<>();
    deque.add(3);

    deque.addFirst(1);
    deque.addLast(2);

    deque.forEach(System.out::print);
  }

}

The add() method is similar to the addLast() method! The push() method is similar to the addFirst() method!

In the above example, initially, my deque only has 3 elements. Using the addFirst() and addLast() methods, I can add more elements to the beginning and end. The result is as follows:

offerFirst() and offerLast() methods

The two methods offerFirst() and offerLast() are similar to the two methods addFirst() and addLast(), but the difference is that if your collection has capacity. Adding new elements may not be possible when your collection is full capacity.

  • offerFirst(E e): add elements to the front, if it cannot be added, it will return false, otherwise it will return true.
  • offerLast(E e): add elements to the rear, similar to offerFirst(), it will return false if it cannot be added, otherwise it will return true.

With the two implementations, ArrayQueue or LinkedList, the size of the collection will automatically increase, so when you use the two methods offerFirst() and offerLast(), the result will always return true!

For example:

package com.huongdanjava.java;

import java.util.ArrayDeque;
import java.util.Deque;

public class Application {

  public static void main(String[] args) {
    Deque<Integer> deque = new ArrayDeque<>(1);
    deque.add(3);

    System.out.println(deque.offerFirst(1));
    System.out.println(deque.offerLast(2));

    deque.forEach(System.out::print);
  }

}

Result:

As you can see, although I only initialized the ArrayDeque object with a capacity of 1, this object can automatically increase its capacity and save all the elements that I added using the offer methods, without any errors.

removeFirst(), pollFirst(), removeLast() and pollLast() methods

The two methods removeFirst() and pollFirst() remove and return the element at the beginning of the collection. The difference is that if there is no element to remove, removeFirst() will throw java.util.NoSuchElementException, while pollFirst() will return null.

Similarly, the methods removeLast() and pollLast() remove and return the element at the end of the collection. The difference is that if there is no element to remove, removeLast() will throw java.util.NoSuchElementException, while pollLast() will return null.

For example:

package com.huongdanjava.java;

import java.util.ArrayDeque;
import java.util.Deque;

public class Application {

  public static void main(String[] args) {
    Deque<Integer> deque = new ArrayDeque<>();
    deque.add(3);
    deque.add(2);

    System.out.println(deque.removeFirst());
    System.out.println(deque.pollFirst());
    System.out.println(deque.pollLast());
    System.out.println(deque.removeLast());
    
    deque.forEach(System.out::print);
  }

}

Result:

The poll() method is similar to the pollFirst() method!

getFirst(), peekFirst(), getLast() and peekLast() methods

The getFirst() and peekFirst() methods will only return the element at the front without removing it. The difference is that if there is no element to return, getFirst() will throw java.util.NoSuchElementException, while peekFirst() will return null.

The same goes for the getLast() and peekLast() methods!

For example:

package com.huongdanjava.java;

import java.util.ArrayDeque;
import java.util.Deque;

public class Application {

  public static void main(String[] args) {
    Deque<Integer> deque = new ArrayDeque<>();
    deque.add(3);

    System.out.println("getFirst: " + deque.getFirst());
    System.out.println("getLast: " + deque.getLast());
    System.out.println("peekFirst: " + deque.peekFirst());
    System.out.println("peekLast: " + deque.peekLast());

    deque.forEach(System.out::println);

    System.out.println("removeFirst: " + deque.removeFirst());

    System.out.println("peekFirst: " + deque.peekFirst());
    System.out.println("getLast:" + deque.getLast());

    deque.forEach(System.out::print);
  }

}

Result:

The post Deque in Java appeared first on Huong Dan Java.

]]>
https://huongdanjava.com/deque-in-java.html/feed 0
Switch statement in Java – Part 2 https://huongdanjava.com/switch-statement-in-java-part-2.html https://huongdanjava.com/switch-statement-in-java-part-2.html#respond Thu, 25 Sep 2025 05:54:46 +0000 https://huongdanjava.com/?p=24497 In the previous tutorial, I introduced you to the basic knowledge of the switch statement in Java. In this tutorial, I will talk more about the new features that Java supports from version 14 onwards! Switch from Java 14 When working with a switch statement,… Read More

The post Switch statement in Java – Part 2 appeared first on Huong Dan Java.

]]>
In the previous tutorial, I introduced you to the basic knowledge of the switch statement in Java. In this tutorial, I will talk more about the new features that Java supports from version 14 onwards!

Switch from Java 14

When working with a switch statement, you can encounter the following problems:

  • The first problem is that we forget to declare the break statement: this means that our code will not exit the switch statement after matching the first case but will continue the next case, for example:

package com.huongdanjava;

public class SwitchExample {

	public static void main(String[] args) {
		int a = 1;
		int b = 2;
		switch (a + b) {
			case 2:
				System.out.println("Khanh");
				break;
			case 3:
				System.out.println("Huong Dan Java");
			case 4:
				System.out.println("Lap Trinh Java");
			default:
				System.out.println("Hello");
				break;
		}
	}

}

Result:

Java enhancements for Switch statement since Java 12

  • The second problem is if we want in case the value of the variable with different values a and b, we will handle the same code. We will write the following code:

package com.huongdanjava;

public class SwitchExample {

	public static void main(String[] args) {
		int a = 1;
		int b = 2;
		switch (a + b) {
			case 2:
				System.out.println("Khanh");
				break;
			case 3:
				System.out.println("Huong Dan Java");
				break;
			case 4:
				System.out.println("Huong Dan Java");
				break;
			default:
				System.out.println("Hello");
				break;
		}
	}

}

Or:

package com.huongdanjava;

public class SwitchExample {

	public static void main(String[] args) {
		int a = 1;
		int b = 2;
		switch (a + b) {
			case 2:
				System.out.println("Khanh");
				break;
			case 3:
			case 4:
				System.out.println("Huong Dan Java");
				break;
			default:
				System.out.println("Hello");
				break;
		}
	}

}

These ways of writing are inconvenient because of duplicating the code, or we sometimes can’t control it, where should we put a break statement?

  • The third problem is that if you want to use a switch statement to determine the value of a variable, you must write the following:

package com.huongdanjava;

public class SwitchExample {

	public static void main(String[] args) {
		String message = "";
		int n = 1;
		switch (n) {
			case 1:
				message = "Khanh";
				break;
			case 2:
				message = "Huong Dan Java";
				break;
			case 3:
				message = "Lap Trinh Java";
				break;
			default:
				break;
		}
		System.err.println(message);
	}

}

To solve these problems, from Java 14, you can write a switch statement with the following improvements:

  • For the first problem, Java 14 now supports us in writing a switch statement like a lambda expression, for example. as:

package com.huongdanjava;

public class SwitchExample {

	public static void main(String[] args) {
		int a = 1;
		int b = 2;
		switch (a + b) {
			case 2 -> System.out.println("Khanh");
			case 3 -> System.out.println("Huong Dan Java");
			case 4 -> System.out.println("Lap Trinh Java");
			default -> System.out.println("Hello");
		}
	}

}

Result:

Java enhancements for Switch statement since Java 12

  • For the second problem, you can rewrite the switch statement as follows:

package com.huongdanjava;

public class SwitchExample {

	public static void main(String[] args) {
		int a = 1;
		int b = 2;
		switch (a + b) {
			case 2 -> System.out.println("Khanh");
			case 3, 4 -> System.out.println("Huong Dan Java");
			default -> System.out.println("Hello");
		}
	}

}

Result:

Java enhancements for Switch statement since Java 12

  • As for the third problem, now Java 12 has supported us to return a value with a switch expression as follows:

package com.huongdanjava;

public class SwitchExample {

	public static void main(String[] args) {
		int n = 1;
		var message = switch (n) {
			case 1 -> "Khanh";
			case 2 -> "Huong Dan Java";
			case 3 -> "Lap Trinh Java";
			default -> "Hello";
		};
		System.out.println(message);
	}

}

Result:

Java enhancements for Switch statement since Java 12

If you want to add code to handle business logic in each case of the switch expression, then you can add the code to use the yield keyword to return the value you want. The example is as follows:

package com.huongdanjava;

public class SwitchExample {

	public static void main(String[] args) {
		int n = 1;
		var message = switch (n) {
			case 1 -> {
				String firstName = "Khanh";
				String lastName = "Nguyen";

				String fullName = firstName + " " + lastName;

				yield fullName;
			}
			case 2 -> "Huong Dan Java";
			case 3 -> "Lap Trinh Java";
			default -> "Hello";
		};
		System.out.println(message);
	}

}

Result:

Java enhancements for Switch statement since Java 12

Switch from Java 16

Since Java 16, the switch supports pattern matching similar to the instanceof operator from Java 14.

For example, I have an interface with two implementations as follows:

package com.huongdanjava.javaexample;

public interface Shape {

}

package com.huongdanjava.javaexample;

public class Triangle implements Shape {

}

package com.huongdanjava.javaexample;

public class Rectangle {

}

To check and print the implementation of a Shape object, we would normally write code like this:

package com.huongdanjava.javaexample;

public class SwitchExample {
    public static void main(String[] args) {
        Shape shape = new Triangle();
        if (shape instanceof Triangle triangle) {
            System.out.println("This is triangle!");
        } else if (shape instanceof Rectangle rectangle) {
            System.out.println("This is rectangle!");
        } else {
            throw new IllegalArgumentException("Unrecognized shape!");
        }
    }
}

From Java 16, you can write briefly with the switch as follows:

package com.huongdanjava.javaexample;

public class SwitchExample {
    public static void main(String[] args) {
        Shape shape = new Triangle();
        switch (shape) {
            case Triangle triangle -> System.out.println("This is triangle!");
            case Rectangle rectangle -> System.out.println("This is rectangle!");
            default -> throw new IllegalArgumentException("Unrecognized shape!");
        }
    }
}

In this case, you must declare the default case! Because the shape object may not be in the implementations that we declared in the cases.

Switch từ Java 23 Preview

Since Java 23 Preview, switch statement supports pattern matching with primitive types! That means, now you can write:

package com.huongdanjava.java;

public class SwitchExample {

  public static void main() {
    test(1);
  }

  static void test(Object obj) {
    if (obj instanceof int i) {
      System.out.println("It's an int: " + i);
    }
  }
}

Result:

The post Switch statement in Java – Part 2 appeared first on Huong Dan Java.

]]>
https://huongdanjava.com/switch-statement-in-java-part-2.html/feed 0
Install JDK using SDKMAN https://huongdanjava.com/install-jdk-using-sdkman.html https://huongdanjava.com/install-jdk-using-sdkman.html#respond Wed, 24 Jul 2024 19:34:08 +0000 https://huongdanjava.com/?p=23017 I have guided you how to install Oracle JDK on macOS by downloading the Oracle JDK installation files and installing it manually. There is another way that is much more convenient and easier which is to use the SDKMAN (Software Development Kit Manager) tool. How… Read More

The post Install JDK using SDKMAN appeared first on Huong Dan Java.

]]>
I have guided you how to install Oracle JDK on macOS by downloading the Oracle JDK installation files and installing it manually. There is another way that is much more convenient and easier which is to use the SDKMAN (Software Development Kit Manager) tool. How is it in detail? Let’s find out together in this tutorial!

A note for you is that SDKMAN can run on Unix, macOS, and Windows WSL and we can also use SDKMAN to install some other tools such as Apache Maven, Ant, Spring Boot CLI, …

In this tutorial, I will work with SDKMAN on macOS only!

Install SDKMAN

SDKMAN provides us a script to install it at https://get.sdkman.io/. To install it, you just need to run the curl command with the following syntax:

curl -s "https://get.sdkman.io" | bash

By default, SDKMAN will be installed in the “<user_home>/.sdkman” directory. You can change this default directory by declaring the SDKMAN_DIR environment variable pointing to the directory where you want it to be installed!

My example is as follows:

export SDKMAN_DIR=/Volumes/Passport/softwares/sdkman

Now, run the curl command above, you will see the same result as me, as follows:

As you can see, we need to run another command, mine is:

source "/Volumes/Passport/softwares/sdkman/bin/sdkman-init.sh"

to be able to use SDKMAN immediately or you can just close the current Terminal window and open a new Terminal!

Now, check the SDKMAN version using the command:

sdk version

You will see the result like me, as follows:

So we have successfully installed the SDKMAN tool!

Install JDK

To install JDK using the SDKMAN tool, first, you can check the Java versions that we can install using the command:

sdk list java

My results are as follows:

As you can see, there are many distributions supported by SDKMAN such as Corretto, Gluon, GraalVM Oracle, OpenJDK, Eclipse Temurin, … Each distribution will have a different version. You can choose to install the version you want and use the command:

sdk install java <identifier>

to install!

The identifier is the value of the rightmost column in the list above! For example, in the image above, the identifier of version 22.0.1, Corretto distribution is 22.0.1-amzn!

For example, I will install Temurin distribution:

with version 21.0.3, I will use the following command:

sdk install java 21.0.3-tem

My results are as follows:

SDKMAN will automatically download the JDK version you want to install to the $SDKMAN_DIR/candidates/java directory and install it for us.

Now you can check the results using the command:

java -version

My results are as follows:

If now, you want to install Java version 8, you can run the following command:

sdk install java 8.0.422-tem

Result:

During the installation of this version 8, because I had previously installed Java version 21 with SDKMAN, I had already set the default Java version to version 21, so as you can see, SDKMAN will ask you if you want to set the Java version you are installing as the default version of the system. If you want, just type “Y” and enter! Here, I did not choose.

After the installation is complete, if you want to choose the Java version you just set as the default Java version, you can use the following command:

sdk default java <identifier>

My example is as follows:

sdk default java 8.0.422-tem

Result:

So we have successfully installed JDK using the SDKMAN tool!

The post Install JDK using SDKMAN appeared first on Huong Dan Java.

]]>
https://huongdanjava.com/install-jdk-using-sdkman.html/feed 0
Virtual thread in Java https://huongdanjava.com/virtual-thread-in-java.html https://huongdanjava.com/virtual-thread-in-java.html#respond Sun, 04 Feb 2024 03:01:35 +0000 https://huongdanjava.com/?p=22761 In the tutorial Initializing and running a thread in Java, I introduced you to Java’s Thread class to create a new thread. Threads created by this Thread class are called platform threads and the number of platform threads that can be created is limited, depending… Read More

The post Virtual thread in Java appeared first on Huong Dan Java.

]]>
In the tutorial Initializing and running a thread in Java, I introduced you to Java’s Thread class to create a new thread. Threads created by this Thread class are called platform threads and the number of platform threads that can be created is limited, depending on the specification of the machine you are using to run the Java application. From version 19, Java introduces a new feature called virtual thread that allows us to create a large number of threads, used similarly to platform threads. Virtual threads will be managed by the JRE, useful in cases of dealing with blocking IO processing. How is it in detail? We will find out together in this tutorial!

First, to distinguish between the initialization of platform threads and virtual threads, Java introduces two static() methods in the Thread class, ofVirtual() and ofPlatform() to initialize these two types of threads.

For example, I have a task implemented using the Runnable interface with the following content:

package com.huongdanjava.java;

public class Calculator implements Runnable {

    public void run() {
        System.out.println("Calculator : " + Thread.currentThread().getName());

        int a = 2;
        int b = 7;

        int sum = a + b;

        System.out.println(sum);
    }

}

I can create a virtual thread and platform thread to submit this task as follows:

package com.huongdanjava.java;

public class Application {

  public static void main(String[] args) throws InterruptedException {
    Thread.ofVirtual().start(new Calculator());

    Thread.ofPlatform().start(new Calculator());

    Thread.sleep(3000);
  }

}

Result:

As you can see, both types of threads can execute tasks and return the same results.

In the Calculator class above, I have a line of code to print the name of the Thread that is processing the task, but as you can see, for virtual threads, we cannot get the name of this Thread. That’s because, by default, virtual threads do not have a defined name. You can define a name for the virtual thread as follows:

package com.huongdanjava.java;

public class Application {

  public static void main(String[] args) throws InterruptedException {
    Thread.ofVirtual().name("Huong Dan Java Virtual Thread").start(new Calculator());

    Thread.ofPlatform().start(new Calculator());

    Thread.sleep(3000);
  }

}

Now you will see the name of the virtual thread displayed as follows:

You can also create a new virtual thread using the static startVirtualThread() method of the Thread class or newVirtualThreadPerTaskExecutor() of the Executors class.

The static startVirtualThread() method of the Thread class will initialize a new virtual thread and call the start() method to execute the task. I can create a new virtual thread and submit tasks to this virtual thread with the startVirtualThread() method as follows:

package com.huongdanjava.java;

public class Application {

  public static void main(String[] args) throws InterruptedException {
    Thread.startVirtualThread(new Calculator());

    Thread.sleep(3000);
  }

}

Run the application, you will see the following results:

Using the static newVirtualThreadPerTaskExecutor() method of the ExecutorService class, you can do the same thing:

package com.huongdanjava.java;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Application {

  public static void main(String[] args) throws InterruptedException {
    ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
    executorService.submit(new Calculator());

    Thread.sleep(3000);
  }

}

Result:

As I said above, we can create a large number of virtual threads instead of being limited like platform threads.

With the same code, if using platform threads, you can only create a certain number of new threads, for example as follows:

package com.huongdanjava.java;

public class Application {

  public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i< 1_000_000; i++) {
      System.out.println("Creating thread " + i);
      new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Thread.sleep(60000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
      }).start();
    }

    Thread.sleep(60000);
  }

}

In the code above, I loop 1 million times and each time I loop I will create a new platform thread and start it. With my machine, I can only create 2025 platform threads, and get an out-of-memory error afterward, as follows:


If I now use a virtual thread instead:

package com.huongdanjava.java;

public class Application {

  public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 1_000_000; i++) {
      System.out.println("Creating thread " + i);
      Thread.startVirtualThread(new Runnable() {
        @Override
        public void run() {
          try {
            Thread.sleep(60000);
          } catch (InterruptedException e) {
            throw new RuntimeException(e);
          }
        }
      });
    }

    Thread.sleep(60000);
  }

}

You will see that you can create 1 million new virtual threads without any problem:

Internally, a pool of platform threads is used to run virtual threads.

Our task is executed using the platform thread, via a virtual thread.

When performing a blocking IO task, to avoid blocking platform threads, virtual threads should be used.

The virtual thread is executed using the Continuation class. This Continuation class uses the run() method to execute our task in a virtual thread. While waiting for a blocking IO task to complete, the virtual thread will put our task in heap memory, releasing the platform thread it is using for other tasks.

When the IO task completes, a signal will be triggered to call Continuation.run(). This Continuation.run() will retrieve the virtual thread’s stack from the heap memory and put it on the platform thread’s waiting list to get the thread and resume the task. The new thread can be different from the original thread the task used to run.

Virtual threads should only be used for blocking IO tasks, not for computational tasks!

The post Virtual thread in Java appeared first on Huong Dan Java.

]]>
https://huongdanjava.com/virtual-thread-in-java.html/feed 0
Scoped Value in Java https://huongdanjava.com/scoped-value-in-java.html https://huongdanjava.com/scoped-value-in-java.html#respond Sat, 28 Oct 2023 04:29:52 +0000 https://huongdanjava.com/?p=22583 Scoped Value in Java is a Java feature that allows us to get immutable data anywhere in related code we want, without having to pass this data from this method to another method so that it can be used later in a method that needs… Read More

The post Scoped Value in Java appeared first on Huong Dan Java.

]]>
Scoped Value in Java is a Java feature that allows us to get immutable data anywhere in related code we want, without having to pass this data from this method to another method so that it can be used later in a method that needs it.

You can imagine an example in a RESTful API to get a user’s information. When the user request comes, this request handler method will have the user information. Next, our code will need to handle some business logic without user information. Only when manipulating the database, the code actually need the user information to retrieve data from the database. Normally, we will need to pass user information in methods that only handle business logic so that when we operate with the database, we will retrieve this user information for use. With the Scoped Value feature from Java 20, you don’t need to do that anymore.

As an example, I have the following 2 classes:

package com.huongdanjava.java;

public class UserService {

  public boolean validateUser(String username) {
    return true;
  }

}

and:

package com.huongdanjava.java;

public class StudentManagement {

  public String getLoggedUserInfo(String username) {
    UserService us = new UserService();
    if (us.validateUser(username)) {
      return "Valid user";
    }

    return "Invalid user";
  }
}

And a class with main() method to run the example:

package com.huongdanjava.java;

public class Application {

  public static void main(String[] args) {
    String user = "Khanh";

    StudentManagement sm = new StudentManagement();
    System.out.println(sm.getLoggedUserInfo(user));
  }
}

The UserService class has a method validateUser() with the username parameter to validate user information. For simplicity, I will always return true for this method. The StudentManagement class with the getLoggedUserinfo() method is used to get user information based on the username, after using the UserService class to validate the username information. As you can see, username information will need to be passed back and forth during the process of handling the request, although it is possible that in a certain method, this username information is not necessary. We can eliminate this by using Java’s Scoped Value to get the username information when we want it!

To use Scoped Value, first declare a static variable for the object of the ScopedValue class in the Application class so that this variable can be accessed anywhere in the code:

public static final ScopedValue<String> LOGGED_USER = ScopedValue.newInstance();

After defining the username information, we will use the static where() method of the ScopedValue class to define this username information so that it can be used throughout the methods we want. My example is as follows:

package com.huongdanjava.java;

public class Application {

  public static final ScopedValue<String> LOGGED_USER = ScopedValue.newInstance();

  public static void main(String[] args) {
    String user = "Khanh";

    StudentManagement sm = new StudentManagement();
    ScopedValue.where(LOGGED_USER, user).run(
        sm::getLoggedUserInfo
    );
  }
}

As you can see, I have defined Scoped Value for user information using the where() method. The where() method’s return object is a Carrier object. You need to call the methods we want to use Scoped Value in the run() method of this Carrier object. The getLoggedUserInfo() method and the methods called inside the getLoggedUserInfo() method can get and use the user information we have defined. The getLoggedUserInfo() method no longer needs to pass user information:

package com.huongdanjava.java;

public class StudentManagement {

  public String getLoggedUserInfo() {
    UserService us = new UserService();
    if (us.validateUser()) {
      return "Valid user";
    }

    return "Invalid user";
  }
}

The validateUser() method in the UserService class also no longer needs to pass user information. It can retrieve user information as follows:

package com.huongdanjava.java;

public class UserService {

  public boolean validateUser() {
    String loggedUser = Application.LOGGED_USER.get();
    System.out.println("Logged user: " + loggedUser);
    return true;
  }

}

Rerun the above example, you can see the user information printed in the validateUser() method of the UserService class as follows:

If I define another method in the UserService class:

package com.huongdanjava.java;

public class UserService {

  public boolean validateUser() {
    String loggedUser = Application.LOGGED_USER.get();
    System.out.println("Logged user: " + loggedUser);
    return true;
  }
  
  public void doSomeThing() {
    String loggedUser = Application.LOGGED_USER.get();
    System.out.println("Logged user1: " + loggedUser);
  }
}

and call it inside the run() method above:

package com.huongdanjava.java;

public class Application {

  public static final ScopedValue<String> LOGGED_USER = ScopedValue.newInstance();

  public static void main(String[] args) {
    String user = "Khanh";

    StudentManagement sm = new StudentManagement();
    UserService us = new UserService();

    ScopedValue.where(LOGGED_USER, user).run(()-> {
          sm.getLoggedUserInfo();
          us.doSomeThing();
        }
    );
  }
}

The doSomething() method also retrieves user information as follows:

You may see that Scoped Value is similar to Thread Local which I introduced in the previous tutorial. That’s right, but there are also differences between Scoped Value and Thread Local:

  • The value that Scoped Value contains is immutable data, meaning its value cannot be changed once initialized. The ScopedValue class only has the get() method to get the value, while the ThreadLocal class has both get() and set() methods to get and assign the value whenever we want.
  • The value in the ThreadLocal object can exist forever during the application’s runtime if you do not remove that value. The value in the ScopedValue object will be destroyed after the run() method of the Carrier object finishes running. You will see an error in our program when trying to get the value in ScopedValue object after the run() method has finished running:

package com.huongdanjava.java;

public class Application {

  public static final ScopedValue<String> LOGGED_USER = ScopedValue.newInstance();

  public static void main(String[] args) {
    String user = "Khanh";

    StudentManagement sm = new StudentManagement();
    UserService us = new UserService();

    ScopedValue.where(LOGGED_USER, user).run(()-> {
          sm.getLoggedUserInfo();
          us.doSomeThing();
        }
    );
    us.doSomeThing();
  }
}

Result:

The post Scoped Value in Java appeared first on Huong Dan Java.

]]>
https://huongdanjava.com/scoped-value-in-java.html/feed 0
ThreadLocal in Java https://huongdanjava.com/threadlocal-in-java.html https://huongdanjava.com/threadlocal-in-java.html#respond Thu, 12 Oct 2023 00:39:27 +0000 https://huongdanjava.com/?p=22555 The ThreadLocal class in Java is a class that allows us to store and retrieve the values of variables in the same thread. As long as it’s the same thread, you can store a value and retrieve it whenever we want. You can initialize objects… Read More

The post ThreadLocal in Java appeared first on Huong Dan Java.

]]>
The ThreadLocal class in Java is a class that allows us to store and retrieve the values of variables in the same thread. As long as it’s the same thread, you can store a value and retrieve it whenever we want.

You can initialize objects of the ThreadLocal class normally like other classes as follows:

ThreadLocal<String> threadLocal = new ThreadLocal<>();

You should clearly specify the data type that the ThreadLocal object will store. In the example above, I declared the data type that the ThreadLocal object will store as String!

Now you can use the set() method of this ThreadLocal object to store the value you want, for example:

threadLocal.set("Huong Dan Java");

To get this value, you can use the get() method as follows:

threadLocal.get()

Results when I run the example:

package com.huongdanjava.java;

public class Example {

  public static void main(String[] args) {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();

    threadLocal.set("Huong Dan Java");

    System.out.println(threadLocal.get());
  }
}

will be as follows:
If you retrieve the value stored in ThreadLocal in another thread, you will see that the value will be null. Because two different threads will not see the value in each other’s ThreadLocal. For example, if I write more code to read the value of ThreadLocal that I created above:

package com.huongdanjava.java;

public class Example {

  public static void main(String[] args) {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();

    threadLocal.set("Huong Dan Java");

    System.out.println(threadLocal.get());

    Thread thread = new Thread(new Runnable() {

      @Override
      public void run() {
        System.out.println("Getting value from another thread: " + threadLocal.get());
      }
    });
    thread.start();
  }
}

then when you run it, you will see the following results:

My example has 2 threads: one is the main thread running the application and a new thread that I create. As you can see, the newly created thread does not see the value stored in the main thread’s ThreadLocal.

You can remove the value stored in ThreadLocal using the remove() method:

package com.huongdanjava.java;

public class Example {

  public static void main(String[] args) {
    ThreadLocal<String> threadLocal = new ThreadLocal<>();

    threadLocal.set("Huong Dan Java");

    System.out.println(threadLocal.get());

    threadLocal.remove();

    System.out.println(threadLocal.get());
  }
}

The result when I run the example will be as follows:

After removing, there are no more values stored in ThreadLocal!

The post ThreadLocal in Java appeared first on Huong Dan Java.

]]>
https://huongdanjava.com/threadlocal-in-java.html/feed 0
Structured Concurrency in Java https://huongdanjava.com/structured-concurrency-in-java.html https://huongdanjava.com/structured-concurrency-in-java.html#respond Thu, 28 Sep 2023 19:17:01 +0000 https://huongdanjava.com/?p=22534 Structured Concurrency in Java is an improvement of Java from Java 19, on implementing and maintaining code related to the execution of tasks consisting of many sub-tasks handled by multi-threading. This improvement makes our code easier to read, and controls how the execution of subtasks… Read More

The post Structured Concurrency in Java appeared first on Huong Dan Java.

]]>
Structured Concurrency in Java is an improvement of Java from Java 19, on implementing and maintaining code related to the execution of tasks consisting of many sub-tasks handled by multi-threading. This improvement makes our code easier to read, and controls how the execution of subtasks will take place, and if an error occurs during the execution of subtasks, how we will handle it. How is it in detail? We will find out together in this tutorial!

As an example, I will create a new Maven project as follows:

Suppose my example application has the task of retrieving student information from many different sources, maybe from a database or from a web service. Because these sources are independent, we can use Java’s multi-threading to submit corresponding subtasks to each source!

I have a Student class containing simple student information as follows:

package com.huongdanjava.java;

public record Student(String name) {

}

Class StudentDatabaseService implements the Callable interface to retrieve student information from the database with simple content as follows:

package com.huongdanjava.java;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;

public class StudentDatabaseService implements Callable<List<Student>> {

  @Override
  public List<Student> call() throws Exception {
    return Arrays.asList(new Student("Khanh"), new Student("Quan"));
  }
}

The StudentWebService class also implements the Callable interface, and has simple content as follows:

package com.huongdanjava.java;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;

public class StudentWebService implements Callable<List<Student>> {

  @Override
  public List<Student> call() throws Exception {
    return Arrays.asList(new Student("Thanh"), new Student("Binh"));
  }
}

To execute these subtasks using concurrency, I will create a new thread pool using the Executor Service framework and submit these subtasks to get the following results:

package com.huongdanjava.java;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Application {

  public static void main(String[] args) throws Exception {
    try (ExecutorService executorService = Executors.newFixedThreadPool(10)) {
      Future<List<Student>> studentsFromDatabaseFuture = executorService.submit(
          new StudentDatabaseService());

      Future<List<Student>> studentsFromWebServiceFuture = executorService.submit(
          new StudentWebService());

      List<Student> studentsFromDatabase = studentsFromDatabaseFuture.get();
      List<Student> studentsFromWebService = studentsFromWebServiceFuture.get();

      List<Student> students = new ArrayList<>();
      students.addAll(studentsFromDatabase);
      students.addAll(studentsFromWebService);

      students.forEach(s -> System.out.println(s.name()));
    }
  }
}

The result when running the application will be as follows:

This is a happy case, subtasks proceed normally without any errors.

In case a sub-task fails due to some reason, for example:

package com.huongdanjava.java;

import java.util.List;
import java.util.concurrent.Callable;

public class StudentDatabaseService implements Callable<List<Student>> {

  @Override
  public List<Student> call() throws Exception {
    throw new Exception("Something went wrong");
  }
}

the remaining tasks take time to complete:

package com.huongdanjava.java;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;

public class StudentWebService implements Callable<List<Student>> {

  @Override
  public List<Student> call() throws Exception {
    Thread.sleep(10000);
    return Arrays.asList(new Student("Thanh"), new Student("Binh"));
  }
}

When you run the example again, you will see that even though one sub-task has an error, our program will wait until the remaining task finishes running for 10 seconds before throwing the error:

How can we cancel the remaining subtasks to run all these tasks again instead of waiting for the remaining subtasks to complete with results that are not what we expected?

For example, you only need the results of one of the subtasks. There is no need to run all subtasks, how can we cancel other subtasks when one of the subtasks has been completed?

To solve the above questions, you can use Java’s Structured Concurrency!

You can use Java’s StructuredTaskScope class to implement this Structured Concurrency. This StructuredTaskScope class will help us clearly define the scope of each subtask, easily cancel other subtasks when an exception occurs in a subtask. We can also easily cancel other subtasks if their results are no longer needed.

With the example above, I can rewrite the code using the StructuredTaskScope class as follows:

package com.huongdanjava.java;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;

public class Application {

  public static void main(String[] args) throws Exception {
    try (var scope = new StructuredTaskScope<>()) {
      Subtask<List<Student>> studentsFromDatabaseSubtask = scope.fork(
          new StudentDatabaseService());

      Subtask<List<Student>> studentsFromWebServiceSubtask = scope.fork(
          new StudentWebService());
      
      scope.join();

      List<Student> studentsFromDatabase = studentsFromDatabaseSubtask.get();
      List<Student> studentsFromWebService = studentsFromWebServiceSubtask.get();

      List<Student> students = new ArrayList<>();
      students.addAll(studentsFromDatabase);
      students.addAll(studentsFromWebService);

      students.forEach(s -> System.out.println(s.name()));
    }
  }
}

We will replace the ExecutorService class with the StructuredTaskScope class. Each sub-task will be a Subtask, we use the fork() method of the StructuredTaskScope class to submit the task.

In addition, you also need to call the join() method of the StructuredTaskScope class so that the program waits for the tasks to be completed. The results of each task can be retrieved using the get() method just like when we use the Future interface!

In the happy case, with:

package com.huongdanjava.java;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;

public class StudentDatabaseService implements Callable<List<Student>> {

  @Override
  public List<Student> call() throws Exception {
    return Arrays.asList(new Student("Khanh"), new Student("Quan"));
  }
}

and:

package com.huongdanjava.java;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;

public class StudentWebService implements Callable<List<Student>> {

  @Override
  public List<Student> call() throws Exception {
    return Arrays.asList(new Student("Thanh"), new Student("Binh"));
  }
}

you will see the same results as when we use the ExecutorService class with the Future interface:

The StructuredTaskScope class defines default Policies that allow us to define the behavior we want when an error occurs in a subtask, or if a subtask is completed and we don’t need to run other subtasks.

There are 2 default Policies that the StructuredTaskScope class has defined:

  • ShutdownOnFailure
  • ShutdownOnSuccess

The ShutdownOnSuccess policy will cancel other subtasks if one of the subtasks has been completed. You can initialize object of the StructuredTaskScope class with the ShutdownOnSuccess policy as follows:

package com.huongdanjava.java;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.StructuredTaskScope;

public class Application {

  public static void main(String[] args) throws Exception {
    try (var scope = new StructuredTaskScope.ShutdownOnSuccess<List<Student>>()) {
      scope.fork(new StudentDatabaseService());
      scope.fork(new StudentWebService());

      scope.join();

      List<Student> students = new ArrayList<>();
      students.addAll(scope.result());

      students.forEach(s -> System.out.println(s.name()));
    }
  }
}

In this case, we no longer need to call the get() method to get the results for each subtask. We just need to use the result() method of the StructuredTaskScope class!

The result you will now see is as follows:

After completing the StudentWebService subtask, the results have been returned!

The ShutdownOnFailure policy will cancel other subtasks if an exception occurs in a subtask. In this case, you will write code as follows:

package com.huongdanjava.java;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.StructuredTaskScope;
import java.util.concurrent.StructuredTaskScope.Subtask;

public class Application {

  public static void main(String[] args) throws Exception {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
      Subtask<List<Student>> studentsFromDatabaseSubtask = scope.fork(
          new StudentDatabaseService());

      Subtask<List<Student>> studentsFromWebServiceSubtask = scope.fork(
          new StudentWebService());

      scope.join();
      scope.throwIfFailed();

      List<Student> studentsFromDatabase = studentsFromDatabaseSubtask.get();
      List<Student> studentsFromWebService = studentsFromWebServiceSubtask.get();

      List<Student> students = new ArrayList<>();
      students.addAll(studentsFromDatabase);
      students.addAll(studentsFromWebService);

      students.forEach(s -> System.out.println(s.name()));
    }
  }
}

We will initialize the object of the StructuredTaskScope class with the ShutdownOnFailure policy. The throwIfFailed() method of the ShutdownOnFailure class will take the exception that occurs in the sub-task to throw!

Rerun the example with:

package com.huongdanjava.java;

import java.util.List;
import java.util.concurrent.Callable;

public class StudentDatabaseService implements Callable<List<Student>> {

  @Override
  public List<Student> call() throws Exception {
    throw new Exception("Something went wrong");
  }
}

and:

package com.huongdanjava.java;

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.Callable;

public class StudentWebService implements Callable<List<Student>> {

  @Override
  public List<Student> call() throws Exception {
    Thread.sleep(10000);
    return Arrays.asList(new Student("Thanh"), new Student("Binh"));
  }
}

you will see our program throw an error right after running, instead of having to wait 10 seconds for the StudentWebService task to complete.

So in this article, I have introduced you to Structured Concurrency in Java, and the problems that Structured Concurrency has solved.

The post Structured Concurrency in Java appeared first on Huong Dan Java.

]]>
https://huongdanjava.com/structured-concurrency-in-java.html/feed 0
Working with .properties files using the Properties class in Java https://huongdanjava.com/working-with-properties-files-using-the-properties-class-in-java.html https://huongdanjava.com/working-with-properties-files-using-the-properties-class-in-java.html#respond Sun, 17 Sep 2023 15:13:57 +0000 https://huongdanjava.com/?p=22502 .properties files are files used to configure information for the application. This is information that can change during the process of developing and deploying the application to the production environment. This information will be defined with key and value pairs in the .properties file, for… Read More

The post Working with .properties files using the Properties class in Java appeared first on Huong Dan Java.

]]>
.properties files are files used to configure information for the application. This is information that can change during the process of developing and deploying the application to the production environment. This information will be defined with key and value pairs in the .properties file, for example as follows:

env=DEV
huongdanjava.base.url=https://huongdanjava.com

To work with .properties files in Java, we can use objects of the Properties class:

Properties properties = new Properties();

You can use the load() methods of this Properties object with a parameter that can be a Reader or an InputStream to read the content of the .properties file.

For example, my config.properties file with the above content is in the project’s src/main/resources folder:

To read the content of this config.properties file, I will define an InputStream as follows:

Path path = Paths.get("src", "main", "resources", "config.properties");
InputStream inputStream = new FileInputStream(path.toFile());

Now I can read the content of the config.properties file using the load() method as follows:

properties.load(inputStream);

After reading, you can get information about the properties in this file using the getProperty() method of the Properties object, for example as follows:

System.out.println(properties.getProperty("env"));

Result:

You can assign a default value in case the property we are looking for is not available, using the getProperty() method with 2 parameters: property name and default value, for example as follows:

System.out.println(properties.getProperty("test", "Testing"));

In the config.properties file, there is no “test” property, so if I run this example again, you will see the default value returned as follows:

You can also add and modify the value of properties after reading the .properties file using the setProperty() method, for example as follows:

properties.setProperty("test", "Testing");
properties.setProperty("env", "PROD");

System.out.println(properties.getProperty("test"));
System.out.println(properties.getProperty("env"));

In the example above, I added a new property “test” and updated the value of the property “env”. Now run the example, you can see the following results:

Although the value of the “env” property in the config.properties file is “DEV”, the value of this property is now “PROD”!

You can also remove the property if you want, for example as follows:

properties.remove("env");

System.out.println(properties.getProperty("env"));

The result will be as follows:

The post Working with .properties files using the Properties class in Java appeared first on Huong Dan Java.

]]>
https://huongdanjava.com/working-with-properties-files-using-the-properties-class-in-java.html/feed 0
Format string in Java https://huongdanjava.com/format-string-in-java.html https://huongdanjava.com/format-string-in-java.html#respond Sun, 14 May 2023 20:48:59 +0000 https://huongdanjava.com/?p=21821 In Java, to format a string to include what values, where these values are taken, you can use the static format() method of the String object along with the format you want. To format and print a string to the console, you can use the… Read More

The post Format string in Java appeared first on Huong Dan Java.

]]>
In Java, to format a string to include what values, where these values are taken, you can use the static format() method of the String object along with the format you want. To format and print a string to the console, you can use the System.out.printf() method along with the format you want. How is it in detail? We will find out together in this tutorial!

First, I will take the following string as an example “Welcome to Huong Dan Java”. If you want to print this string to the console, you can use the following line of code:

System.out.println("Welcome to Huong Dan Java");

Result:

Very simple, right?

If the value of the string “Huong Dan Java” from the above string is taken from a variable, you can rewrite it as follows:

String name = "Huong Dan Java";
System.out.println("Welcome to " + name);

But if your string is as long as “Welcome to Huong Dan Java. My name is Khanh” with the string “Khanh” also taken from a variable, then adding the string like this will be very confusing:

String site = "Huong Dan Java";
String name = "Khanh";
System.out.println("Welcome to " + site + ". My name is " + name);

Right guys?

You can format this string for clarity as follows:

String site = "Huong Dan Java";
String name = "Khanh";
System.out.printf("Welcome to %s. My name is %s", site, name);

The first parameter of the printf() method is “Welcome to %s. My name is %s”, which is a formatted string of characters.

The “%s” character in this first parameter will be replaced by String data type variables declared after this parameter. We have 2 characters “%s”, so we will need to declare 2 variables of data type String, “site” and “name”.

You can see that our code will be easier to see than the string concatenation above.

The “%s” character also allows us to right padding and left padding the value of the substitution variable. In short, the right padding will display the value of the replacement variable with the correct length that we declare. If the replacement string is larger than the length we want, the result will show that string. If the string is less than the length we want, our string will shift to the left, and the missing part of the length will be replaced with the characters we want. For example, if I want to right padding with the replacement character as space for the value of the “site” variable above, I will write the following code:

package com.huongdanjava.javaexample;

public class JavaExample {
  public static void main(String[] args) {
    String site = "Huong Dan Java";
    String name = "Khanh";
    System.out.printf("Welcome to %-20s. My name is %s", site, name);
  }
}

Result:

20 is the length I want the value of the site variable to be displayed, for right padding, we will add a “-” sign. As you can see, my string “Huong Dan Java” will be displayed with spaces after it. Right padding is the padding on the right.

Left padding is the opposite of right padding. Our string will shift to the right, the remainder will be displayed using the replacement character. With the space character, you can write the following:

package com.huongdanjava.javaexample;

public class JavaExample {
  public static void main(String[] args) {
    String site = "Huong Dan Java";
    String name = "Khanh";
    System.out.printf("Welcome to %20s. My name is %s", site, name);
  }
}

Result:

For a variable whose value type is number, we will use the character “%d” instead of “%s”.

For example, I have the following string:

package com.huongdanjava.javaexample;

public class JavaExample {
  public static void main(String[] args) {
    int age = 36;

    System.out.printf("I'm %d years old", age);
  }
}

%d will be replaced with the value of the “age” variable.

Result:

If you want to display the age in 3-digit format, if the value of the variable is not enough 3 digits, add leading zeros, you can write the following code:

package com.huongdanjava.javaexample;

public class JavaExample {
  public static void main(String[] args) {
    int age = 36;

    System.out.printf("I'm %03d years old", age);
  }
}

Result:

How many leading zeros you want to add, just increase or decrease the number 3 in “%03d”.

The post Format string in Java appeared first on Huong Dan Java.

]]>
https://huongdanjava.com/format-string-in-java.html/feed 0