Sunday, January 30, 2022

Java Tutorial: Connecting to Database Using JDBC

Chapters

Overview

The Java Database Connectivity (JDBC) API provides universal data access from the Java programming language. Using the JDBC API, you can access virtually any data source, from relational databases to spreadsheets and flat files. In this tutorial, we're going to use some classes in java.sql package in order to connect to a database.

Java can connect to many types of databases. In this tutorial, I'm gonna demonstrate on how to connect to MySQL and SQLite.

Connecting Java to MySQL Database

To connect our java application to a database, we need to create a connection between our application and the database. To do that, we're going to use Connection inferface. To establish a connection, we're going to use DriverManager class.

Let's demonstrate database connection between. Before running the example below, you must install MySQL first then create a database with the following table, columns and inserts:
Table Name: account_info
Columns
idtable1 INT (PRIMARY_KEY)
             (AUTO_INCREMENT)
             (UNSIGNED)
first_name: VARCHAR(25)
last_name: VARCHAR(25)

Inserts
1st row
firstname: Martin
lastname: Mayers

2nd row
firstname: Oliver
lastname: Crawford
Also, download JDBC Driver for MySQL (Connector/J). Connector/J can be included during installation of MySQL. Make sure your MySQL server is running. This example demonstrates java connecting to mysql.
import java.sql.*;

public class SampleClass{

  public static void main(String[] args){
  
    //in JDBC 3.9 and below, it's required
    //to explicity register a driver.
    //since JDBC 4.0 and later versions, 
    //it's not required to explicity register
    //a driver. Once the vendor jar is loaded
    //in our app, java will automatically
    //register the driver inside that jar file
    //Class.forName("com.mysql.jc.jdbc.Driver");
    
    try(
    Connection con = 
    DriverManager.getConnection
    ("jdbc:mysql://localhost:3306/mydb","root","root")){
      Statement statement = con.createStatement();
      ResultSet rs = 
      statement.executeQuery("SELECT * from account_info");
      
      System.out.println("Elements");
      while(rs.next())
        System.out.println
        (rs.getInt(1) + " | " +
         rs.getString(2) + " | " +
         rs.getString(3));
    }
    catch(Exception e){
      e.printStackTrace();
    }
    
  }
}

Result
Elements
1 | Martin | Mayers
2 | Oliver | Crawford
Note that the information in getConnection method shouldn't be written in the source code in production scenario for security purposes. It's only acceptable to write those information in source code during development phase.

Note: If you're using build tool like maven, you don't need to add a new classpath in order to use Connector/J JAR file.
Before running the example above, we will temporarily add a new classpath where Connector/J is located. Command syntax:
set classpath=[root]:\[path];
e.g.
set classpath=C:\MySQL\ConnectorJ\mysql-connector-java-8.0.28.jar;
Once the new classpath is added, we can execute the example above. Once we close cmd/terminal, number of classpaths in our system will return to normal. Now, let's discuss the example above. com.mysql.jc.jdbc.Driver denotes the location of Driver.class in Connector/J jar file. This location com.mysql.jdbc.Driver is deprecated.

If you're gonna bundle your program in a JAR file and you need to link another JAR file for example, mysql-connector.jar, you should read this article.

getConnection(String url, String user, String password) attempts to establish a connection to the given database URL. Returns a Connection object. This method has multiple forms which can be found in the documentation. user and password are the username and password that you put in your MySQL software. Next, let's examine the url in the getConnection() in the example above.

Let's examine this URL: jdbc:mysql://localhost:3306/mydb
jdbc is the connection type between our java app and mysql.
: is a separator.
mysql is the database software we're connected to.
//localhost is "//+hostname".
3306 is a port number. You can learn more about ports in this article.
/mydb is "/+database-name".

createStatement() Creates a Statement object for sending SQL statements to the database. createStatement() has more forms which can be found in the documentation. Once a Statement object is created, we need to write SQL statement. executeQuery() Executes the given SQL statement, which returns a single ResultSet object.

ResultSet is a table of data representing a database result set, which is usually generated by executing a statement that queries the database. next() method returns true if the cursor(selector or pointer) of the result set doesn't past beyond the max row count of the result set. Otherwise, it returns false.

getInt(1) returns an int whereas getString(2) returns a String. The arguments are the column index based on the database. In some database like MySQL, column index starts at 1.

There are different tyes of getters in the ResultSet interface that get SQL data based on their types. For example getInt() gets integer-based data of MySQL like INT data type of MySQL. getString() gets character sequence data type of MySQL like VARCHAR. More getters can be found in the ResultSet interface. Don't forget to close any database connection.

Connecting Java to SQLite

SQLite is a C-language library that implements a small, fast, self-contained, high-reliability, full-featured, SQL database engine. Unlike MySQL, SQLite doesn't need a server. Also, the licenses of SQLite and some of its connectors are less restrictive the MySQL.

You can go to the official website and download the latest version of SQLite. Alternatively, you can use SQLiteStudio if you don't have time setting up SQLite yourself.

To connect java to SQLite database, we need to download a connector. Go to this download page and select SQLite JDBC connection version that you want. At the time of this writing, the latest version is 3.36.0.3

Before executing the example below, create a database first. Name it dbtest and create the following tables, columns and data.
Table Name: account_info
Columns
idtable1 INTEGER (PRIMARY_KEY)
                 (AUTO_INCREMENT)
                 (UNIQUE)
first_name: VARCHAR
last_name: VARCHAR

Inserts
1st row
firstname: Martin
lastname: Mayers

2nd row
firstname: Oliver
lastname: Crawford
This example demonstrates java connecting to SQLite.
import java.sql.*;

public class SampleClass{

  public static void main(String[] args){
  
    try(
    Connection con = 
    DriverManager.getConnection
    ("jdbc:sqlite:C:/test/sqlitetest/dbtest.db")){
      Statement statement = con.createStatement();
      ResultSet rs = 
      statement.executeQuery("SELECT * from account_info");
      
      System.out.println("Elements");
      while(rs.next())
        System.out.println
        (rs.getInt(1) + " | " +
         rs.getString(2) + " | " +
         rs.getString(3));
    }
    catch(Exception e){
      e.printStackTrace();
    }
    
  }
}

Result
Elements
1 | Martin | Mayers
2 | Oliver | Crawford
Note: If you're using build tool like maven, you don't need to add a new classpath in order to use sqlite-jdbc JAR file.
Before running the example above, we will temporarily add a new classpath where sqlite-jdbc is located. Command syntax:
set classpath=[root]:\[path];
e.g.
set classpath=C:\test\sqlitetest\sqlite-jdbc-3.36.0.3.jar;
Once the new classpath is added, we can execute the example above. Once we close cmd/terminal, number of classpaths in our system will return to normal.

If you're gonna bundle your program in a JAR file and you need to link another JAR file for example, sqlite-jdbc.jar, you should read this article.

Next, let's examine this URL: jdbc:sqlite:C:/test/sqlitetest/dbtest.db
jdbc is the connection type between our java app and mysql.
: is a separator.
sqlite is the database software we're connected to.
C:/test/sqlitetest/dbtest.db is the path where dbtest.db is located.

In sqlite, if the database in the getConnection doesn't exist, it will create a new database with the specified name, Assuming you have permission to create files in the specified directory.

This example is very similar to the example in this topic. You should read my explanation there before reading this topic.

Thursday, January 27, 2022

Java Tutorial: Compressing and Decompressing ZIP File

Chapters

Overview

In this tutorial, We're gonna discuss how to compress files/directories in a ZIP file and how to decompress ZIP file. java.util.zip provides classes for reading and writing the standard ZIP and GZIP file formats. Also includes classes for compressing and decompressing data using the DEFLATE compression algorithm, which is used by the ZIP and GZIP file formats.

Additionally, there are utility classes for computing the CRC-32, CRC-32C and Adler-32 checksums of arbitrary input streams. More information can be found in the documentation.

Compressing Files

To compress files, we need to set up output stream for the compressed Zip file and input stream for the files. This example demonstrates compressing files using ZipOutputStream.
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.util.zip.ZipOutputStream;
import java.util.zip.ZipEntry;
import java.io.File;
import java.io.IOException;

public class SampleClass{
  
  void putToZip(ZipOutputStream zos,
                File source, String entryName)
                throws IOException{
                
    for(File f : source.listFiles()){
        
        if(f.isDirectory()){
          
          if(f.getName().endsWith("/"))
            zos.putNextEntry(
            new ZipEntry(
            entryName+f.getName()));
          else
            zos.putNextEntry(
            new ZipEntry(
            entryName+f.getName()+"/"));
          
          zos.closeEntry();
          
          putToZip(zos, f, 
          entryName+f.getName()+"/");
          continue;
        }
        
        FileInputStream fis = null;
        try{
          fis = new FileInputStream(f);
          ZipEntry ze = new ZipEntry(
          entryName+f.getName());
          zos.putNextEntry(ze);
        
          byte[] buffer = new byte[1024];
          int length;
          while((length = fis.read(buffer)) >= 0)
            zos.write(buffer);
       }finally{
         if(fis != null)
           fis.close();
         zos.closeEntry();
       }
        
    }
  }
  
  public static void main(String[] args)
                      throws IOException{
    File source = new File("C:\\test\\Files");
    File output = 
    new File("C:\\test\\output\\"+
             source.getName()+".zip");
    
    if(!source.exists()){
       System.out.println("Source doesn't exist!");
       return;
    }
    if(output.exists()){
      System.out.println
      (source.getName()+".zip "+" already exists!");
      return;
    }
    
    try(
    FileOutputStream fos =
    new FileOutputStream(output);
    ZipOutputStream zos = new ZipOutputStream(fos)){
      new SampleClass()
      .putToZip(zos, source, "");
    }
    System.out.println("Operation Complete!");
  }
}
First off, we need source and destination of the zip file. In the example above, the source is C:\\test\\Files which is a directory. Next, create a FileOutputStream and attach it to ZipOutputStream. Before writing files to ZipOutputStream, we need to make an entry per file using ZipEntry. Directories doesn't need to be written by ZipOutputStream.

When putting directories in a ZipEntry, their names need to end with "/". Some operating systems put "/" after directory name. It better to check if "/" is already part of directory name so we don't accidentally add another "/".

putNextEntry() method begins writing a new Zip file entry and positions ZipOutputStream to the start of the entry data. Closes the current entry if still active. Once we create a ZipEntry, we need to close it. Once this method is invoked, it automatically closes previous entry if it's currently active.

closeEntry() closes the current Zip entry and positions the stream for writing the next entry. In the example above, putToZip is a recursive method.

Decompressing Zip File

To decompress ZIP file, we need to input stream for extracting data bytes in the ZIP file and output stream for converting the bits to file. This example demonstrates decompressing files using ZipInputStream.
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipEntry;
import java.io.File;
import java.io.IOException;

public class SampleClass{
  
  public static void main(String[] args)
                      throws IOException{
    File output = new File("C:\\test\\output");
    File source = 
    new File("C:\\test\\output\\Files.zip");
    
    if(!source.exists()){
       System.out.println("Source doesn't exist!");
       return;
    }
    
    if(!output.exists()){
      System.out.println
      ("output folder doesn't exist!");
      return;
    }
    else if(!output.isDirectory()){
      System.out.println
      ("Destination must be a directory!");
      return;
    }
    
    try(
    FileInputStream fis = 
    new FileInputStream(source);
    ZipInputStream zis = new ZipInputStream(fis)){
      ZipEntry ze = zis.getNextEntry();
      
      byte[] buffer = new byte[1024];
      while(ze != null){
        /*zip slip guard*/
        File entryFile = 
        new File(output, ze.getName());
        
        String entryPath = 
        entryFile.getCanonicalPath();
        String outputPath = 
        output.getCanonicalPath();
        
        if(!entryPath
        .startsWith(
         outputPath + File.separator)){
          System.err.println
          ("File destination is invalid!");
          return;
        }
        /**/
        
        if(ze.isDirectory())
          if(!entryFile.mkdirs()){
            System.out.println
            ("Failed to create directories or "+
             "directories already exist!");
            System.out.println
            ("Operation Aborted!");
            return;
          }
          else{
            ze = zis.getNextEntry();
            continue;
          }
        
        try(
        FileOutputStream fos =
        new FileOutputStream(entryFile)){
         int len = 0;
         while((len = zis.read(buffer)) >= 0) 
             fos.write(buffer, 0, len);
        }
        ze = zis.getNextEntry();
      }
    System.out.println("Operation Complete!");
    }
  }
}
First off, we need a ZIP file and a destination folder. Then, we need FileInputStream and attach it to ZipInputStream to read zip entries and content of ZIP file. Zip slip guard code snippet is used for protecting our program from Zip Slip vulnerability.

Next, we need to check if there are entries that are directories. If there are, we need to make directories in our storage disk before writing their content to it. isDirectory() method in ZipEntry checks if an entry is a directory entry. If the entry name ends with "/" then, that entry is a directory.

getNextEntry() gets the next entry in a ZIP file. This method returns null if there are no more entries left in the ZIP file. write(byte[] b, int off, int len) Writes len bytes from the specified byte array starting at offset. In the example above, This expression fos.write(buffer, 0, len) is equivalent to fos.write(buffer)

CheckedInputStream and CheckedOutputStream

CheckedInputStream is an input stream that also maintains a checksum of the data being read whereas CheckedOutputStream is an output stream that also maintains a checksum of the data being written. The checksum can then be used to verify the integrity of the processed data.

This example demonstrates CheckedInputStream and CheckedOutputStream.
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
import java.util.zip.CRC32;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.File;

public class SampleClass{

  public static void main(String[] args)
                      throws IOException{
    File source = 
    new File("C:\\test\\output\\Files.zip");
    File copy = 
    new File("C:\\test\\output\\FilesCopy.zip");
    
    try(CheckedInputStream cis = 
    new CheckedInputStream(
    new FileInputStream(source),new CRC32());
    CheckedOutputStream cos = 
    new CheckedOutputStream(
    new FileOutputStream(copy),new CRC32())){
      byte[] buffer = new byte[1024];
      while(cis.read(buffer) > 0)
        cos.write(buffer);
        
      long sourceCheckSum = 
      cis.getChecksum().getValue();
      
      long copyCheckSum =
      cos.getChecksum().getValue();
      
      System.out.println
      ("source: "+sourceCheckSum);
      System.out.println
      ("copy: "+copyCheckSum);
    }
    
  }
}

Result(may vary)
source: 3855373666
copy: 2474678416
Let's assume that Files.zip is created by ZipOutputStream. In the example above, the source and copy have different checksum. In my opinion, this happens because the source is created differently from the copy. In the copy creation, we just copy the source bytes. In the source creation, we used ZipOutputStream and laid out entries in the zip file. Now, run the example above but this time let FilesCopy.zip be the source and make a copy of it using the example above. This time the source and copy will have the same checksum.

getChecksum() returns Checksum object. Note that CheckedInputStream and CheckedOutputStream are not limited to zip files. They can be used for other file types. Also, java supports three types of checksums: CRC-32, CRC-32C and Adler-32.

When we want create an uncompressed zip file, we need change the ZipOutputStream compression level. To do that, we use seLevel() method and put Deflater.NO_COMPRESSION as argument. However, this compression level requires a maintained checksum. We can use CheckedOutputStream to do this. This snippet demonstrates setting compression level to Deflater.NO_COMPRESSION.
FileOutputStream fos = new FileOutputStream(sourceZip);
CheckedOutputStream checksum = 
new CheckedOutputStream(fos,new CRC32());
ZipOutputStream zos = new ZipOutputStream(checksum);
zos.setLevel(Deflater.NO_COMPRESSION);

Splitting Zip File

Java doesn't provide any tools for splitting zip file. However, there are ways to split zip file. If you have lots of time in your hands, you may read this zip specification. This article has a nice explanation about zip headers. The constants(e.g. CENATX, CENEXT, etc.) that you see in some classes in java.util.zip package like ZipEntry are zip headers.

This article has solutions on how to split zip files. One of them is using zip4j library. It's better to use library like zip4j if you don't have time reading the zip specification.

Monday, January 24, 2022

Java Tutorial: Flow API

Chapters

Flow API

Interrelated interfaces and static methods for establishing flow-controlled components in which Publishers produce items consumed by one or more Subscribers, each managed by a Subscription.

These interfaces correspond to the reactive-streams specification. They apply in both concurrent and distributed asynchronous settings: All (seven) methods are defined in void "one-way" message style. More information can be found in the documentation

To create a simple reactive-stream using java Flow API, we need three components: Publisher, Subscriber and Subscription. First off, let's create a Publisher. SubmissionPublisher implements Flow.Publisher. Thus we can use it as a Publisher.

Next, we need to create subscribers. Java doesn't have class implementation of Flow.Subscriber. We need to create a class that will implement Flow.Subscriber.

This example demonstrates creation of reactive-stream using Flow API in java.
import java.util.concurrent.Flow.*;
import java.util.concurrent.SubmissionPublisher;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.Random;

public class SampleClass{

  public static void main(String[] args)
                throws InterruptedException{
    Random rand = new Random();
    int subCount = 2;
    ExecutorService es = 
    Executors.newFixedThreadPool(subCount);
    
    SubmissionPublisher<Integer> sp =
    new SubmissionPublisher<>
    (es, java.util.concurrent.Flow
         .defaultBufferSize());
    
    for(int i = 0; i < subCount; i++)
      sp.subscribe(new Receiver("Receiver"+i));
      
    for(int i = 0; i < 3; i++)
      sp.submit(rand.nextInt(50));
    
    //pause main thread for a bit
    //before closing executor and
    //publisher
    //to assure all tasks are
    //complete
    Thread.sleep(200);
    
    sp.close();
    es.shutdown();
  }
}

class Receiver implements Subscriber<Integer>{
  
  private Subscription subscription;
  private String receiverName;
  
  Receiver(String receiverName){
    this.receiverName = receiverName;
  }
  
  @Override
  public void onSubscribe(Subscription subscription){
    System.out.println(receiverName + " has subscribed!");
    this.subscription = subscription;
    this.subscription.request(1);
  }
  
  @Override
  public void onNext(Integer item){
    System.out.println(receiverName + " got: " + 
    item);
    this.subscription.request(1);
  }
  
  @Override
  public void onError(Throwable e){
    e.printStackTrace();
  }
  
  @Override
  public void onComplete(){
    System.out.println(receiverName + 
    " is done!");
  }
  
}

Result(may vary)
Receiver0 has subscribed!
Receiver1 has subscribed!
Receiver0 got: 47
Receiver1 got: 47
Receiver0 got: 20
Receiver1 got: 20
Receiver0 got: 21
Receiver1 got: 21
Receiver0 is done!
Receiver1 is done!
First off, let's examine Receiver class that implements Subscriber interface. Subscriber interface has four methods that are needed to be overriden. onSubscribe() method is the first method to be invoked once Publisher makes Subscriber subscribe to it by invoking subscribe() method.

onNext() method invoked everytime Subscriber fulfills a request from the Subscription. onError() method is invoked upon an unrecoverable error encountered by a Publisher or Subscription, after which no other Subscriber methods are invoked by the Subscription. onComplete() method is invoked if Publisher is not producing any data or Publisher is closed. If there are leftover submitted data and then Publisher is closed, onComplete() may not be invoked.

request() method adds the given number n of items to the current unfulfilled demand for this subscription. If n is less than or equal to zero, the Subscriber will receive an onError signal with an IllegalArgumentException argument. Otherwise, the Subscriber will receive up to n additional onNext invocations (or fewer if terminated). Everytime a request is fulfilled, n will be reduced.

submit() method submits a value to Publisher that will be distributed to Subscribers. close() method issues onComplete signals to current subscribers, and disallows subsequent attempts to publish, unless already closed. Upon return, this method does NOT guarantee that all subscribers have yet completed.

We need to use some kind of flag like boolean or counter flag or some kind of timing if we want all subscribers to be done. I think the purpose of Flow API is to stream indefinite amount of data to subscribers. Thus, Publisher is not required to guarantee that all subscribers have completed before it closes.

defaultBufferSize() Returns a default value for Publisher or Subscriber buffering, that may be used in the absence of other constraints. The current value returned is 256.

SubmissionPublisher constructor has three forms. In the example above, I used this form:
SubmissionPublisher(Executor executor, int maxBufferCapacity)
This form creates a new SubmissionPublisher using the given Executor for async delivery to subscribers, with the given maximum buffer size for each subscriber. More information about SubmissionPublisher's constructors can be found in the documentation.

Note that if any Subscriber method throws an exception, its subscription is cancelled. To explicitly cancel a subscription, use the cancel() method in the Flow.Subscription interface.

Flow.Processor Interface

Flow.Processor is a Flow API component that acts as both a Subscriber and Publisher. This component is mainly used for transforming messages that we sent to subscribers.

This example demonstrates Flow.Processor.
import java.util.concurrent.Flow.*;
import java.util.concurrent.SubmissionPublisher;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.Random;
import java.util.function.Function;

public class SampleClass{

  public static void main(String[] args) 
                throws InterruptedException{
    Random rand = new Random();
    ExecutorService es = 
    Executors.newFixedThreadPool(2);
    
    SubmissionPublisher<Number> sp =
    new SubmissionPublisher<>
    (es, java.util.concurrent.Flow
         .defaultBufferSize());
    
    Transformer t1 = 
    new Transformer("t1", String::valueOf);
    sp.subscribe(t1);
    t1.subscribe(new Receiver("r1"));
    t1.subscribe(new Receiver("r2"));
    
    //submit three values
    sp.submit(rand.nextInt(50));
    sp.submit(rand.nextDouble());
    sp.submit(rand.nextInt(10));
    
    //pause main thread for a bit
    //before closing executor and
    //publishers
    //to assure all tasks are
    //complete
    Thread.sleep(200);
    
    sp.close();
    t1.close();
    es.shutdown();
  }
}

class Transformer extends SubmissionPublisher<String>
implements Processor<Number, String>{

  private Subscription subscription;
  private String processorName;
  private Function<Number, String> transform;
  
  Transformer(String processorName, 
  Function<Number, String> transform){
    this.processorName = processorName;
    this.transform = transform;
  }
  
  @Override
  public void onSubscribe(Subscription subscription){
    System.out.println(processorName + " has subscribed!");
    this.subscription = subscription;
    this.subscription.request(3);
  }
  
  @Override
  public void onNext(Number item){
    submit(transform.apply(item));
  }
  
  @Override
  public void onError(Throwable e){
    e.printStackTrace();
  }
  
  @Override
  public void onComplete(){
    System.out.println(processorName + 
    " is done!");
  }
}

class Receiver implements Subscriber<String>{
  
  private Subscription subscription;
  private String receiverName;
  
  Receiver(String receiverName){
    this.receiverName = receiverName;
  }
  
  @Override
  public void onSubscribe(Subscription subscription){
    System.out.println(receiverName + " has subscribed!");
    this.subscription = subscription;
    this.subscription.request(3);
  }
  
  @Override
  public void onNext(String item){
    System.out.println(receiverName + " got: " + 
    item);
  }
  
  @Override
  public void onError(Throwable e){
    e.printStackTrace();
  }
  
  @Override
  public void onComplete(){
    System.out.println(receiverName + 
    " is done!");
  }
  
}

Result(may vary)
t1 has subscribed!
r1 has subscribed!
r2 has subscribed!
r1 got: 15
r1 got: 0.5124233779675397
r1 got: 4
r2 got: 15
r2 got: 0.5124233779675397
r2 got: 4
r1 is done!
t1 is done!
r2 is done!
First off, let's examine Transformer class that extends SubmissionPublisher and implements Processor. When a class implements Processor, it needs to override 5 methods: subscribe in Publisher and onSubscribe, onNext, onError and onComplete methods in Subscriber.

I extended SubmissionPublisher so that I didn't need to override subscribe method because SubmissionPublisher already overrides subscribe. Moreover, we can use some SubmissionPublisher methods in Transformer class if we needed. In the example above, I used the submit() method in onNext() method.

Now, let's discuss the Flow mechanism in the example above. Transformer class is a Publisher/Subscriber component. Thus, this class can subscribe to a Publisher. In the example above, I created a SubmissionPublisher sp with a Number generic type. The generic type of sp is based on the Processor<T, R>.

In the example above, the parameter types of Processor are <Number, String>. Parameter type T is the subscribed item type. Therefore, Number is the subscribed item type in Transformer class. Parameter type R is the published item type. Therefore, String is the published item type.

The reason why sp has Number generic-type argument is because the subscriber of sp is Transformer class. Number type is the subscribed item type of Transformer class. Therefore, Publisher sp needs to publish data with Number type.

Next, Receiver is Subscriber of Transformer class. The reason why Receiver has String generic-type argument is because String is the published item type of Transformer. As you can see in the example above, Processor transform Number to String by using a Function functional interface. submit() method in onNext() method of Transformer class submits the transformed data to the subscriber of Transformer which is the Receiver.

Sunday, January 23, 2022

Java Tutorial: AsynchronousFileChannel

Chapters

Opening and Reading AsynchronousFileChannel

Note: You should be knowledgeable about FileChannel before reading this article. AsynchronousFileChannel is just a FileChannel but it's built for asynchronous file access.

AsynchronousFileChannel is associated with a thread pool to which tasks are submitted to handle I/O events and dispatch to completion handlers that consume the results of I/O operations on the channel. asynchronous file channel does not have a current position within the file. Instead, the file position is specified to each read and write method that initiates asynchronous operations.

A CompletionHandler is specified as a parameter and is invoked to consume the result of the I/O operation. This class also defines read and write methods that initiate asynchronous operations, returning a Future to represent the pending result of the operation. The Future may be used to check if the operation has completed, wait for its completion, and retrieve the result. More information can be found in the documentation.

This example demonstrates reading a file using AsynchronousFileChannel.
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class SampleClass{

  public static void main(String[] args)
                throws IOException,
                       ExecutionException,
                       InterruptedException{
  
    Path source = Paths.get("C:\\test\\fileTest.txt");
    
    if(!Files.exists(source)){
      System.err.println("source doesn't exist!");
      return;
    }
    String content = null;
    
    try(AsynchronousFileChannel afc = 
     AsynchronousFileChannel
     .open(source,StandardOpenOption.READ)){
    
      int buffer = 
      (1024L > afc.size()) ? 
      (int)afc.size() : 1024;
      
      ByteBuffer bb = 
      ByteBuffer.allocate(buffer);
      
      Future<Integer> result = afc.read(bb, 0);
      
      //wait for the Future to be completed
      result.get();
      
      content = 
      new String(bb.array(),
                 StandardCharsets.UTF_8);
    }
    if(content != null)
      System.out.println("Content: " + content);
    else System.err.println("content is null!");
  }
}
In the example above, I use this form of open() method of AsynchronousFileChannel:
open(Path file, OpenOption... options)
This method associates AsynchronousFileChannel with default thread pool that is defined in the AsynchronousChannelGroup class.

Then, Future is used to determine if all threads in the pool are done reading the file in the example ebove. The return value of Future of read() method is the number of bytes read. Note that ByteBuffer is not thread safe. Make sure that you're not using ByteBuffer in multiple threads except for AsynchronousFileChannel operations like read() and write() methods.

Writing AsynchronousFileChannel

We can use AsynchronousFileChannel for writing data to a file. This example demonstrates writing data to a file using AsynchronousFileChannel.
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
                     
    Path source = Paths.get("C:\\test\\test.txt");
    
    ExecutorService es = Executors.newFixedThreadPool(2);
    
    try(AsynchronousFileChannel afc = 
     AsynchronousFileChannel
     .open(source, 
     java.util.EnumSet.of
     (StandardOpenOption.WRITE,
      StandardOpenOption.CREATE),es)){
           
       ByteBuffer bb = ByteBuffer.allocate(1024);
       bb.put("ABCDEFGHIJK".getBytes());
       bb.flip();
       
       afc.write(bb, 0, bb, new 
       FinalizeTask<Integer, ByteBuffer>());
     }
     es.shutdown();
  }
}

class FinalizeTask<T,A> implements 
CompletionHandler<T,A>{

  @Override
  public void completed(T result, A attachment) {
    
    System.out.println("Writing Complete!");
    if(attachment instanceof ByteBuffer){
      ByteBuffer bb = (ByteBuffer)attachment;
      System.out.println
      ("Buffer has remaining content? " + bb.hasRemaining());
      System.out.println("Number of bytes written: " + result);
    }
  }
  
  @Override
  public void failed(Throwable exc, A attachment) {
    exc.printStackTrace();
  }
}
In the example above, I use this form of open() method:
open(Path file, Set<? extends OpenOption> options, ExecutorService executor, FileAttribute<?>... attrs)
This method requires an ExecutorService. FileAttribute is optional. StandardOpenOption.CREATE create a file if the file in the source doesn't exist.

flip() method flips the buffer. The limit is set to the current position and then the position is set to zero. If the mark is defined then it is discarded. Just like file channel, byte buffer has pointer that is repositioned if byte buffer executes an operation like put(byte[] src) method.

Next, let's examine this form of write() method:
write(ByteBuffer src, long position, A attachment, CompletionHandler<Integer,? super A> handler)
src is a ByteBuffer source. position is the position of the file pointer of file channel. attachment is an attachment that we wanna attach in the CompletionHandler. handler is a CompletionHandler.

CompletionHandler consumes the result of an asynchronous I/O operation. The asynchronous channels defined in this package allow a completion handler to be specified to consume the result of an asynchronous operation.

The completed method is invoked when the I/O operation completes successfully. The failed method is invoked if the I/O operations fails. The implementations of these methods should complete in a timely manner so as to avoid keeping the invoking thread from dispatching to other completion handlers.

This interface has two overridable methods: completed(V result, A attachment) and failed(Throwable exc, A attachment). result is the total number of read/written bytes. attachment is the attached object. In this example, ByteBuffer bb is the attached object. exc is a Throwable exception that occurs if I/O operation fails.

read() method has this version of this form of write():
read(ByteBuffer dst, long position, A attachment, CompletionHandler<Integer,? super A> handler)

Saturday, January 22, 2022

Java Tutorial: FileChannel

Chapters

FileChannel

FileChannel is a SeekableByteChannel that is connected to a file. File channel can reposition its file pointer to any position in a file just like RandomAccessFile; can force file updates to go directly to the storage disk, can map region of files into memory, which is often efficient for large files; can lock a region of a file; and can access by multiple threads.

Even though file channel can be accessed by multiple threads, FileChannel operations are blocking operations. Although, according to the documentation:

"Other operations, in particular those that take an explicit position, may proceed concurrently; whether they in fact do so is dependent upon the underlying implementation and is therefore unspecified."

My explanation here is simplified. More information can be found in the documentation.

Opening a FileChannel

There are two ways of opening a FileChannel. We can use the open() methods in the FileChannel class or use the getChannel() from file-stream-based classes like RandomAccessFile, FileInputStream and others.

This example demonstrates opening a FileChannel using open() and reading file content using FileChannel.
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.io.ByteArrayOutputStream;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    Path source = Paths.get("C:\\test\\fileTest.txt");
    
    if(!Files.exists(source)){
      System.err.println("source doesn't exist!");
      return;
    }
    
    String content = null;
    try(FileChannel fc = 
    FileChannel.open(source, StandardOpenOption.READ);
    ByteArrayOutputStream out =
    new ByteArrayOutputStream()){
      
      int buffer = 
      (1024L > fc.size()) ? 
      (int)fc.size() :
      1024;
      
      ByteBuffer bb = 
      ByteBuffer.allocate(buffer);
      
      while(fc.read(bb) > 0){
        out.write(bb.array());
        bb.clear();
      }
      
      content = 
      new String(out.toByteArray(),
                 StandardCharsets.UTF_8);
      
    }
    
    if(content != null)
      System.out.println("Content: " + content);
    else System.err.println("content is null!");
    
  }
}
In the example above, I use this form of open():
public static FileChannel open(Path path, OpenOption... options)throws IOException
StandardOpenOption.READ grants read access to file channel. Read OpenOption and StandardOpenOption for more options that can be put in this method.

In order to read and write from FileChannel, we need to use ByteBuffer. FileChannel reads and writes data from ByteBuffer. Then, in the example above, I write the read data to ByteArrayOutputStream.

allocate() method sets the buffer size of ByteBuffer. array() method in ByteBuffer wraps bytes in a byte[] array. StandardCharsets.UTF_8 encodes bytes into UTF_8 characters. size() method returns the current size of the file that a FileChannel is connected to.

Another way of opening a FileChannel is to open a file-stream-based class like FileOutputStream and use its getChannel() method. This example demonstrates getChannel() and writing to a FileChannel.
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.io.IOException;
import java.io.FileOutputStream;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    Path source = Paths.get("C:\\test\\test.txt");
    
    try(FileOutputStream out =
    new FileOutputStream(source.toFile())){
    
      out.write(
      new String("ABCDEFGHIJK").
          getBytes(StandardCharsets.UTF_8));
          
      try(FileChannel fc = out.getChannel()){
      
        ByteBuffer bb = 
        ByteBuffer.wrap(new String("LMNOPQRSTUVWXYZ").
        getBytes(StandardCharsets.UTF_8));
        
        while(bb.hasRemaining()){
          fc.write(bb);
        }
      }
    }
    System.out.println("Writing Complete!");
  }
}

Result(C:\test\test.txt content)
ABCDEFGHIJKLMNOPQRSTUVWXYZ
FileChannel that is opened using getChannel() follows the file pointer and mode of the source file-stream. That's why the written data in FileChannel didn't overwrite the written data in FileOutputStream because FileChannel followed the file pointer of FileOutputStream.

wrap(byte[] array) method sets the new buffer's capacity and limit to the length of the array in the argument. More information can be found in the documentation. Remember that we can't open a file channel if the source stream is closed.

In the example above, file channels are automatically closed by try-with-resources. If you want to manually close a file channel, use the close() method.

force() Method

Method form: public abstract void force(boolean metaData) throws IOException
Sometimes, Operating systems may cache file changes instead of sending file changes directly to the file in the storage device. This method Forces any updates to this channel's file to be written to the storage device that contains it.

If this channel's file resides on a local storage device then when this method returns it is guaranteed that all changes made to the file since this channel was created, or since this method was last invoked, will have been written to that device.

This is useful for ensuring that critical information is not lost in the event of a system crash. If the file does not reside on a local device then no such guarantee is made. To enable the feature discussed above, just enable it by setting force() argument to true. More information can be found in the documentation
try(FileChannel fc = out.getChannel()){
  
  //enable force update
  fc.force(true);
  
  ByteBuffer bb = 
  ByteBuffer.wrap(new String("LMNOPQRSTUVWXYZ").
  getBytes(StandardCharsets.UTF_8));
        
  while(bb.hasRemaining()){
    fc.write(bb);
  }
}
map() Method

Method Form: public abstract MappedByteBuffer map(FileChannel.MapMode mode, long position, long size) throws IOException
Maps a region of this channel's file directly into memory. This method accepts three type of modes:
READ_ONLY
READ_WRITE
PRIVATE
More information can be found in the documentation. More information about modes can be found in this documentation.

This example demonstrates map().
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.charset.StandardCharsets;
import java.io.IOException;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    
    Path source = Paths.get("C:\\test\\test.txt");
    try(FileChannel fc = 
    FileChannel.open(
    source, 
    StandardOpenOption.WRITE,
    StandardOpenOption.READ)){
    
      MappedByteBuffer mBuff =
      fc.map(FileChannel.MapMode.PRIVATE, 5, 5);
      
      ByteBuffer bb = 
      ByteBuffer.wrap(new String("ZZZZZ").
      getBytes(StandardCharsets.UTF_8));
      
      //Note: if we put a ByteBuffer that has
      //size greater than the capacity of
      //mBuff, this method will throw
      //BufferOverflowException
      mBuff.put(bb);
    }
    System.out.println("Writing Complete!");
  }
}
Assume that C:\test\test.txt exists in your machine and has this content:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
MapMode.PRIVATE ensures that any changes to mapped data of a file in memory won't affect the file. Thus, the example above won't do any changes to test.txt

Now, in the example above, try changing the map mode to MapMode.READ_WRITE and run the example again. This time the content of test.txt is going to be like this:
ABCDEZZZZZKLMNOPQRSTUVWXYZ

If you want the mapped data to be read-only, use MapMode.READ. map() method returns MappedByteBuffer which is a subclass of ByteBuffer.

position() Method

This method has two forms. position() returns current position of FileChannel's file pointer. position(long newPosition) sets FileChannel's new position.

This example demonstrates both forms.
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.io.IOException;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
  
    Path source = Paths.get("C:\\test\\test.txt");
    
    if(!Files.exists(source)){
      System.err.println("source doesn't exist!");
      return;
    }
    
    try(FileChannel fc = 
    FileChannel.open(source, StandardOpenOption.READ)){
      
      ByteBuffer bb = 
      ByteBuffer.allocate(1);
      
      System.out.println
      ("pos: " + fc.position());
      fc.read(bb);
      System.out.println("content: " +
      new String(bb.array(),
                 StandardCharsets.UTF_8));
      bb.clear();
      
      System.out.println("set pos...");
      fc.position(7);
      
       System.out.println
      ("pos: " + fc.position());
      fc.read(bb);
      System.out.println("content: " +
      new String(bb.array(),
                 StandardCharsets.UTF_8));
      bb.clear();
    }
    
  }
}
Assume that C:\test\test.txt exists in your machine and has this content:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
The result is going to be:
pos: 0
content: A
set pos...
pos: 7
content: H
truncate() Method

Method Form: public abstract FileChannel truncate(long size)throws IOException
Truncates this channel's file to the given size. If the given size is less than the file's current size then the file is truncated, discarding any bytes beyond the new end of the file.

If the given size is greater than or equal to the file's current size then the file is not modified. In either case, if this channel's file position is greater than the given size then it is set to that size.

This example demonstrates truncate().
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.io.IOException;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    Path source = Paths.get("C:\\test\\test.txt");
    
    if(!Files.exists(source)){
      System.err.println("source doesn't exist!");
      return;
    }
    
    try(FileChannel fc = 
    FileChannel.open(source, StandardOpenOption.WRITE)){
      //value in the argument is in byte
      //not bit
      fc.truncate(16L);
    }
    System.out.println("file truncated!");
  }
}
Assume that C:\test\test.txt exists in your machine and has this content:
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Then, test.txt has a file size of 26 bytes. In the example above, the file size will be reduced to 16 bytes.

transferFrom() Method

Method Form: public abstract long transferFrom(ReadableByteChannel src, long position, long count) throws IOException
Transfers bytes into this channel's file from the given readable byte channel. More information can be read in the documentation. This method can be used for transferring FileChannel data to another FileChannel. We can also use this method to transfer data from channels that implements ReadableByteChannel to this FileChannel.

This example demonstrates transferFrom().
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    Path source1 = Paths.get("C:\\test\\test1.txt");
    Path source2 = Paths.get("C:\\test\\test2.txt");
    
    try(FileChannel fc1 = 
    FileChannel.open(source1,StandardOpenOption.READ);
    FileChannel fc2 = 
    FileChannel.open(source2, StandardOpenOption.WRITE)){
      fc2.transferFrom(fc1, 0, fc1.size());
    }
    System.out.println("Operation Complete!");
  }
}
Note that freshly written data into the source(fc1) before calling this method won't be transferred. This method is potentially much more efficient than a simple loop that reads from the source channel and writes to this channel. Many operating systems can transfer bytes directly from the source channel into the filesystem cache without actually copying them.

transferTo() Method

Method Form: public abstract long transferTo(long position, long count, WritableByteChannel target)throws IOException
Transfers bytes from this channel's file to the given writable byte channel. More information can be found in the documentation. This method is the inverted version of transferFrom().

This method demonstrates transferTo().
import java.nio.channels.FileChannel;
import java.nio.file.*;
import java.nio.charset.StandardCharsets;
import java.io.IOException;

public class SampleClass{

  public static void main(String[] args)
                     throws IOException{
    Path source1 = Paths.get("C:\\test\\test1.txt");
    Path source2 = Paths.get("C:\\test\\test2.txt");
    
    try(FileChannel fc1 = 
    FileChannel.open(source1,StandardOpenOption.READ);
    FileChannel fc2 = 
    FileChannel.open(source2, StandardOpenOption.WRITE)){
      fc1.transferTo(0, fc1.size(), fc2);
    }
    System.out.println("Operation Complete!");
  }
}
Note that freshly written data into the source(fc1) before calling this method won't be transferred. This method is potentially much more efficient than a simple loop that reads from the source channel and writes to this channel. Many operating systems can transfer bytes directly from the source channel into the filesystem cache without actually copying them.

Locking FileChannel

We can use two methods to lock file access of file channels. lock() and tryLock() are methods that returns FileLock. lock() and tryLock() have second forms:
tryLock(long position, long size, boolean shared)
lock(long position, long size, boolean shared)
These forms lock a region of a file. Their first forms lock the entire region of file. the shared is a boolean flag than enables/disables shared lock. FileLock has two types of lock: Exclusive and Shared lock.

Shared lock enables thread to acquire overlapping shared locks. FileLock locks may overlap one other if the regions they're locking overlap. For example, lock1 locks position 5-10 then lock2 locks position 8-13. lock1 and lock2 are overlapping. If FileLock has shared lock, FileChannel that has lock2 can acquire lock1.

Note that shared lock can't acquire overlapping exclusive lock. Exclusive lock doesn't allow acquiring of either overlapping lock types. Also, remember that File locks are held on behalf of the entire Java virtual machine. They are not suitable for controlling access to a file by multiple threads within the same virtual machine. More information can be found in the documentation.

If you want locks that are suitable for a single virtual machine, you may consider using locks in java.util.concurrent.locks. The difference between lock() and tryLock() are explained there. This article that I created explains locks in java.util.concurrent.locks.

This snippet demonstrates lock(long position, long size, boolean shared).
try(FileChannel fc = 
    FileChannel.open(path, StandardOpenOption.READ)){
      FileLock lock = fc.lock(5,10,false);
      System.out.println(Thread.currentThread().getName() + 
      " acquired the lock.");
      //other tasks...
      lock.release();
    }

Thursday, January 20, 2022

Java Tutorial: StringBuilder

Chapters

StringBuilder and StringBuffer

StringBuilder is like the mutable version of String. StringBuilder is not synchronized and therefore it's not thread safe. However, StringBuilder has a synchronized version which is called StringBuffer. If you need a mutable String that is accessed by multiple threads, consider using StringBuffer. If a mutable String is not accessed by multiple threads, it's recommended to use StringBuilder as the mutable String.

By using StringBuilder or StringBuffer, we can efficiently save memory resources regarding Strings. As you may have known now, String objects are immutable. Everytime we create or "modify" a String, a new String object may be created. I explained how Strings do this in this article.

StringBuilder or StringBuffer doesn't create new object everytime we modify the String value in it. Thus, these two classes are useful if we are modifying a String value lots of times. Once we're done modifying the String value in StringBuilder, we can use the toString() method to create a String object of the String value in the StringBuilder.

StringBuilder or StringBuffer stores elements in a dynamic container similar to ArrayList. By default, this container has a capacity of 16 characters. This capacity will be doubled once the number of characters in the StringBuilder exceeds the maximum capacity of the StringBuilder.

In this tutorial, I'm going to demonstrate some methods in StringBuilder that I think are essential for understanding this class. More infornation and methods can be found in the documentation. The example that I'm gonna demonstrate here is applicable(with few modifications) to StringBuffer since StringBuilder and StringBuffer have identical methods. Also, some methods of StringBuilder are identical to some methods in String class.

append() Method

This method appends a String representation of this method's parameter. This method has multiple forms. Check the documentation to see all method forms.

This example demonstrates append(char c).
public class SampleClass{

  public static void main(String[] args){
  
    StringBuilder builder = 
    new StringBuilder("A");
    
    builder.append('B');
    builder.append('C');
    builder.append('D');
    
    System.out.println(builder.toString());
  }
}

Result
ABCD
capacity() and length() Methods

capactiy() returns the current capacity. The capacity is the number of characters that can be stored (including already written characters), beyond which an allocation will occur.
length() returns the length (character count).

This example demonstrates capacity() and length().
public class SampleClass{

  public static void main(String[] args){
  
    StringBuilder sb = 
    new StringBuilder();
    
    sb.append('A');
    sb.append('B');
    sb.append('C');
    sb.append('D');
    
    System.out.println("Capacity: " + sb.capacity());
    System.out.println("length: " + sb.length());
    System.out.println("Value: " + sb.toString());
  }
}

Result
Capacity: 16
length: 4
Value: ABCD
ensureCapacity() Method

Ensures that the capacity is at least equal to the specified minimum. If the current capacity is less than the argument, then a new internal array is allocated with greater capacity.

This example demonstrates ensureCapacity().
public class SampleClass{

  public static void main(String[] args){
  
    StringBuilder sb1 = 
    new StringBuilder();
    
    System.out.println("sb1 Capacity: " + sb1.capacity());
    System.out.println("ensure capacity...");
    sb1.ensureCapacity(18);
    System.out.println("sb1 Capacity: " + sb1.capacity());
    
    StringBuilder sb2 = 
    new StringBuilder();
    
    System.out.println();
    System.out.println("sb2 Capacity: " + sb2.capacity());
    System.out.println("ensure capacity...");
    sb2.ensureCapacity(35);
    System.out.println("sb2 Capacity: " + sb2.capacity());
    
  }
}

Result
sb1 Capacity: 16
ensure capacity...
sb1 Capacity: 34

sb2 Capacity: 16
ensure capacity...
sb2 Capacity: 35
In the example above, the current capacity is doubled plus 2 additional space. Making the new capacity larger than the capacity that we put in the argument of ensureCapacity(). If the capacity in the argument is greater than the doubled capacity + 2, the current capacity of the StringBuilder is going to be the replaced by the capacity in the argument.

compareTo() Method

Compares two StringBuilder instances lexicographically. This method follows the same rules for lexicographical comparison as defined in the CharSequence.compare(this, another) method.

This example demonstrates ensureCapacity().
public class SampleClass{

  public static void main(String[] args){
  
    StringBuilder sb1 = 
    new StringBuilder();
    
    StringBuilder sb2 = 
    new StringBuilder();
    
    StringBuilder sb3 = 
    new StringBuilder();
    
    sb1.append('A');
    sb1.append('B');
    
    sb2.append('A');
    sb2.append('B');
    
    sb3.append('B');
    sb3.append('C');
    
    System.out.println(sb1.compareTo(sb2));
    System.out.println(sb1.compareTo(sb3));
    
  }
}

Result
0
-1
If the return value of compareTo() is 0, it means that the characters in both StringBuilder objects are equal.

delete() Method

Method Form: public StringBuilder delete(int start, int end)
Removes the characters in a substring of this sequence. The substring begins at the specified start and extends to the character at index end - 1 or to the end of the sequence if no such character exists. If start is equal to end, no changes are made.

This example demonstrates delete().
public class SampleClass{

  public static void main(String[] args){
  
    StringBuilder sb = 
    new StringBuilder();
    
    sb.append(10);
    sb.append(200);
    sb.append("String");
    
    System.out.println("Value: " + sb.toString());
    System.out.println("delete...");
    sb.delete(2,5);
    System.out.println("Value: " + sb.toString());
    
  }
}

Value: 10200String
delete...
Value: 10String
reverse() Method

Method Form: public StringBuilder reverse()
Causes this character sequence to be replaced by the reverse of the sequence. If there are any surrogate pairs included in the sequence, these are treated as single characters for the reverse operation. Thus, the order of the high-low surrogates is never reversed.

Let n be the character length of this character sequence (not the length in char values) just prior to execution of the reverse method. Then the character at index k in the new character sequence is equal to the character at index n-k-1 in the old character sequence.

Note that the reverse operation may result in producing surrogate pairs that were unpaired low-surrogates and high-surrogates before the operation. For example, reversing "\uDC00\uD800" produces "\uD800\uDC00" which is a valid surrogate pair.

Surrogate pair is a pair of two unicode code points that is implemented to UTF-16. You may read more about UTF-16 and surrogate pair in this article.

This example demonstrates reverse().
public class SampleClass{

  public static void main(String[] args){
  
    StringBuilder sb = 
    new StringBuilder();
    
    sb.append('A');
    sb.append('B');
    sb.append('C');
    sb.append('D');
    
    System.out.println("Value: " + sb.toString());
    System.out.println("reverse...");
    sb.reverse();
    System.out.println("Value: " + sb.toString());
  }
}

Value: ABCD
reverse...
Value: DCBA
insert() Method

The characters of the String argument are inserted, in order, into this sequence at the indicated offset, moving up any characters originally above that position and increasing the length of this sequence by the length of the argument. If str is null, then the four characters "null" are inserted into this sequence. This method has multiple forms. More information can be found in the documentation.

This example demonstrates insert().
public class SampleClass{

  public static void main(String[] args){
  
    StringBuilder sb = 
    new StringBuilder();
    
    sb.append('A');
    sb.append('B');
    sb.append('G');
    sb.append('H');
    
    System.out.println("Value: " + sb.toString());
    System.out.println("insert...");
    sb.insert(2,"CDEF");
    System.out.println("Value: " + sb.toString());
  }
}

Result
Value: ABGH
reverse...
Value: ABCDEFGH
setLength() Method

Method Form: public void setLength(int newLength)
Sets the length of the character sequence. The sequence is changed to a new character sequence whose length is specified by the argument. For every nonnegative index k less than newLength, the character at index k in the new character sequence is the same as the character at index k in the old sequence if k is less than the length of the old character sequence.

Otherwise, it is the null character '\u0000'. In other words, if the newLength argument is less than the current length, the length is changed to the specified length.

This example demonstrates setLength().
public class SampleClass{

  public static void main(String[] args){
  
    StringBuilder sb = 
    new StringBuilder();
    
    sb.append('A');
    sb.append('B');
    sb.append('C');
    sb.append('D');
    
    System.out.println("sb length: " + sb.length());
    System.out.println("sb capacity: " + sb.capacity());
    System.out.println("sb value: " + sb.toString());
    System.out.println("setLength...");
    sb.setLength(0);
    System.out.println("sb length: " + sb.length());
    System.out.println("sb capacity: " + sb.capacity());
    System.out.println("sb value: " + sb.toString());
    
    StringBuilder sb2 = 
    new StringBuilder();
    
    sb2.append('A');
    sb2.append('B');
    sb2.append('C');
    sb2.append('D');
    
    System.out.println();
    System.out.println("sb2 length: " + sb2.length());
    System.out.println("sb2 capacity: " + sb2.capacity());
    System.out.println("sb2 value: " + sb2.toString());
    System.out.println("setLength...");
    sb2.setLength(6);
    System.out.println("sb2 length: " + sb2.length());
    System.out.println("sb2 capacity: " + sb2.capacity());
    System.out.println("sb2 value: " + sb2.toString());
    
  }
}

Result
sb length: 4
sb capacity: 16
sb value: ABCD
setLength...
sb length: 0
sb capacity: 16
sb value: 

sb2 length: 4
sb2 capacity: 16
sb2 value: ABCD
setLength...
sb2 length: 0
sb2 capacity: 16
sb2 value: ABCD
In the result above, sb2 value has 4 characters but the total length of sb2 is 6. Those two invisible characters are null characters or '\u0000'. Typically, setLength() is used to clear characters in a StringBuilder.

trimToSize() Method

Method Form: public void trimToSize()
Attempts to reduce storage used for the character sequence. If the buffer is larger than necessary to hold its current sequence of characters, then it may be resized to become more space efficient. Calling this method may, but is not required to, affect the value returned by a subsequent call to the capacity() method.

This example demonstrates trimToSize().
public class SampleClass{

  public static void main(String[] args){
  
    StringBuilder sb = 
    new StringBuilder();
    
    sb.append('A');
    sb.append('B');
    sb.append('C');
    sb.append('D');
    
    System.out.println("sb length: " + sb.length());
    System.out.println("sb capacity: " + sb.capacity());
    System.out.println("sb value: " + sb.toString());
    System.out.println("setLength...");
    sb.setLength(2);
    System.out.println("sb length: " + sb.length());
    System.out.println("sb capacity: " + sb.capacity());
    System.out.println("sb value: " + sb.toString());
    System.out.println("trimToSize...");
    sb.trimToSize();
    System.out.println("sb length: " + sb.length());
    System.out.println("sb capacity: " + sb.capacity());
    System.out.println("sb value: " + sb.toString());
    
  }
}

Result
sb length: 4
sb capacity: 16
sb value: ABCD
setLength...
sb length: 2
sb capacity: 16
sb value: AB
trimToSize...
sb length: 2
sb capacity: 2
sb value: AB

Java Tutorial: assert Keyword

Chapters

assert Keyword

This keyword is used for making assumptions during debugging. This keyword can be helpful when we're trying to fix or detect bugs in our program. By default, assert keyword is disabled at runtime. To enable it at runtime, we need to add additional command when we run our program.

On command prompt, we typically type java program-name to run a program. If we want to enable assert keyword, we type this command
java -enableassertions program-name
Shorter version
java -ea program-name

assert keyword has two forms. These are:
assert expression1
assert expression1 : expression2
The first form will throw java.lang.AssertionError if the expression1(boolean-expression) is false. The second form will throw java.lang.AssertionError with additional message(expression2).

This example demonstrates the first form of assert keyword.
import java.util.Random;

public class SampleClass{

  public static void main(String[] args){
    Random rand = new Random();
    
    int num = rand.nextInt(10);
    
    assert num > 5;
    
    System.out.println("num: " + num);
  }
}
We can run the example above like this:
java -ea SampleClass
Then, if num is less than 5, the assert keyword will throw java.lang.AssertionError once the execution executes the line where the assert keyword resides. Otherwise, the println() will be executed.

Next, This example demonstrates the second form of assert keyword.
import java.util.Random;

public class SampleClass{

  public static void main(String[] args){
    Random rand = new Random();
    
    int num = rand.nextInt(10);
    
    assert (num % 2) == 0 : num + " is odd!";
    
    System.out.println("num: " + num);
  }
}
If num is odd, the result would be like this:
Exception in thread "main" java.lang.AssertionError: 1 is odd!
at SampleClass.main(SampleClass.java:10)



Don't forget to omit -ea command and its long version once your program is ready for distribution. Only enable assertion during development.

Wednesday, January 19, 2022

Java Tutorial: Object and Objects Classes

Chapters

Overview

In this tutorial, we're gonna explore the Object and Objects classes. These two seem equivalent. However, their purpose is different.


Object Class

Class Object is the root of the class hierarchy. Every class has Object as a superclass. All objects, including arrays, implement the methods of this class. I will demonstrate this class method except for wait(), notify() and notifyAll(). I explained this method in this article. I won't also demonstrate finalize() because it's deprecated since Java 9.

clone() Method

Method Form: protected Object clone()throws CloneNotSupportedException
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object. More information can be read in the documentation. A class must implement Cloneable interface and override clone() method in order to make cloning work. There are two ways of copying objects: Shallow Copy and Deep Copy.


Shallow Copy

This copy mechanism copies primitive values and reference of objects without using the new keyword. This copy mechanism combined with clone() method improves performance compare to copy constructor.

This example demonstrates shallow copy mechanism.
public class SampleClass{

  public static void main(String[] args)
                throws CloneNotSupportedException{
    ClassA c1 = new ClassA(1, new StringBuilder("A"));
    ClassA c2 = c1.clone();
    
    System.out.println("c1 | num1 | " + c1.getNum());
    System.out.println("c2 | num1 | " + c2.getNum());
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
    
    System.out.println();
    System.out.println("modify c2 builder");
    c2.builder.append("B");
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
  }
}

class ClassA implements Cloneable{
  private int num1;
  StringBuilder builder;
  
  ClassA(int num1, StringBuilder builder){
    this.num1 = num1;
    this.builder = builder;
  }
  
  public int getNum(){
    return num1;
  }
  
  //clone() method in Cloneable interface has protected
  //access modifier. It means that we can only access
  //that method in a class that implements Cloneable
  //
  //That's why we create this method to contain the 
  //clone() method. Take note that we didn't override
  //clone()  method of Cloneable interface
  public ClassA clone() throws CloneNotSupportedException{
    return (ClassA)super.clone();
  }
}

Result
c1 | num1 | 1
c2 | num1 | 1

c1 | builder | A
c1 | builder | A

modify c2 builder

c1 | builder | AB
c1 | builder | AB
In the example above, we see that num1 value in c1 is copied to num1 in c2. We also see that builder reference in c1 is copied to builder reference in c2. Since we copied the reference of builder to c2, any changes made to builder of c2 will be reflected to c1.

Deep Copy

This copy mechanism is similar to Shallow Copy. However, in deep copy, new instances of objects in the object that we wanna copy are created. Making the copy independent to the original. We use deep copy if we want to create an independent copy of an object contained in the object that we wanna copy.

This example demonstrates Deep Copy.
public class SampleClass{

  public static void main(String[] args)
                throws CloneNotSupportedException{
    ClassA c1 = new ClassA(1, new StringBuilder("A"));
    ClassA c2 = c1.clone();
    
    System.out.println("c1 | num1 | " + c1.num1);
    System.out.println("c2 | num1 | " + c2.num1);
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
    
    System.out.println();
    System.out.println("modify c2 builder");
    c2.builder.append("B");
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
  }
}

class ClassA implements Cloneable{
  int num1;
  StringBuilder builder;
  
  ClassA(int num1, StringBuilder builder){
    this.num1 = num1;
    this.builder = builder;
  }
  
  public ClassA clone() throws CloneNotSupportedException{
    
    //create a shallow copy of ClassA
    ClassA ca = (ClassA)super.clone();
    
    //create a deep copy of builder
    ca.builder = new StringBuilder(builder.toString());
    
    //return the shallow copy
    return ca;
  }
}

Result
c1 | num1 | 1
c2 | num2 | 1

c1 | builder | A
c1 | builder | A

modify c2 builder

c1 | builder | A
c1 | builder | AB
Copy Constructor

We can set up a constructor to copy fields of another object with the same type. This type of constructor is called Copy Constructor.

This example demonstrates Copy Constructor.
public class SampleClass{

  public static void main(String[] args)
                throws CloneNotSupportedException{
    ClassA c1 = new ClassA(1, new StringBuilder("A"));
    ClassA c2 = new ClassA(c1);
    
    System.out.println("c1 | num1 | " + c1.getNum());
    System.out.println("c2 | num1 | " + c2.getNum());
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
    
    System.out.println();
    System.out.println("modify c2 builder");
    c2.builder.append("B");
    System.out.println();
    
    System.out.println("c1 | builder | " 
    + c1.builder.toString());
    System.out.println("c2 | builder | " 
    + c2.builder.toString());
  }
}

class ClassA{
  private int num1;
  StringBuilder builder;
  
  ClassA(int num1, StringBuilder builder){
    this.num1 = num1;
    this.builder = builder;
  }
  
  //Copy Constructor
  ClassA(ClassA a){
    num1 = a.num1;
    builder = a.builder;
  }
  
  int getNum(){
    return num1;
  }
  
}

Result
c1 | num1 | 1
c2 | num2 | 1

c1 | builder | A
c1 | builder | A

modify c2 builder

c1 | builder | AB
c1 | builder | AB
In the example above, the copy constructor copies primitives and produces a shallow copy of builder object. However, copy constructor doesn't utilize the mechanism of clone() method. In my personnal preference, I use clone() if an object is going to be copied a lot. Otherwise, copy constructor will suffice.

equals() Method

Method Form: public boolean equals(Object obj)
Indicates whether some other object is "equal to" this one. More information can be read in the documentation. This method is overriden by various classes like String class. In String class, equals() compares the characters of Strings. In Object class, equals() compares the hash code of objects.

This example demonstrates equals().
public class SampleClass{

  public static void main(String[] args){
    ClassA c1 = new ClassA();
    ClassA c2 = c1;
    ClassA c3 = new ClassA();
    
    System.out.println("c1 == c2: "+ c1.equals(c2));
    System.out.println("c1 == c3: "+ c1.equals(c3));
    
    //equivalent to
    //System.out.println("c1 == c2: "
    //+(c1.hashCode() == c2.hashCode()));
    //System.out.println("c1 == c3: "
    //+(c1.hashCode() == c3.hashCode()));
    
  }
}

class ClassA{
}

Result
c1 == c2: true
c1 == c3: false

getClass() Method

Method Form: public final Class<?> getClass()
Returns the runtime class of this Object. The returned Class object is the object that is locked by static synchronized methods of the represented class. You should be knowledgeable about Class<T> to understand the return type of this method. More information can be found in the documentation.

This example demonstrates getClass().
import java.lang.reflect.Field;

public class SampleClass{

  public static void main(String[] args){
    ClassA c1 = new ClassA();
    Class<?> c = c1.getClass();
    
    for(Field f : c.getFields())
      System.out.println(f.getType());
  }
}

class ClassA{
  public int num = 1;
  public double decimal = 10.5;
}

Result
int
double

hashCode() Method

Method Form: public int hashCode()
Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by HashMap. More information can be found in the documentation.

This example demonstrates hashCode().
public class SampleClass{

  public static void main(String[] args){
    ClassA c1 = new ClassA();
    ClassA c2 = new ClassA();
    
    System.out.println("c1 hashCode(): "
    + c1.hashCode());
    System.out.println("c2 hashCode(): "
    + c2.hashCode());
    
  }
}

class ClassA{
}

Result(may vary)
c1 hashCode: 1175962212
c2 hashCode: 1706377736

toString() method

Method Form: public String toString()
Returns a string representation of the object. This method is overriden by various classes like String. In String, toString() return the string value of the String. In Object class, toString() returns this type of String:
getClass().getName() + '@' + Integer.toHexString(hashCode())
More information can be found in the documentation.

This example demonstrates toString().
public class SampleClass{

  public static void main(String[] args){
    
    ClassA c1 = new ClassA();
    System.out.println(c1.toString());
  }
}

class ClassA{
}

Result(may vary)
ClassA@4617c264
Tip: We don't need to call toString() if we wanna display value returned by toString() on the console using println(). If we put an object in println(), toString() is automatically called. Take a look at this example.
public class SampleClass{

  public static void main(String[] args){
    
    ClassA c1 = new ClassA();
    System.out.println(c1);
    
    String str = "Hello";
    System.out.println(str);
  }
}

Result(may vary)
ClassA@4617c264
Hello
Objects Class

Objects class consists of static utility methods for operating on objects, or checking certain conditions before operation. These utilities include null-safe or null-tolerant methods for computing the hash code of an object, returning a string for an object, comparing two objects, and checking if indexes or sub-range values are out of bounds.

In this tutorial, I'm gonna demonstrate some methods. More methods can be found in the documentation.
checkFromIndexSize() Method

Method Form: public static int checkFromIndexSize(int fromIndex, int size, int length)
Method Form: public static long checkFromIndexSize(long fromIndex, long size, long length)
Checks if the sub-range from fromIndex (inclusive) to fromIndex + size (exclusive) is within the bounds of range from 0 (inclusive) to length (exclusive). More information can be found in the documentation.

This example demonstrates checkFromIndexSize().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    byte[] pseudoBuffer = new byte[]{100, 50, 25, 39};
    
    try{
      int start = 0;
      int end = 4;
      start = Objects.checkFromIndexSize
      (1, end, pseudoBuffer.length);
      
      System.out.print("[ ");
      for(int i = start; i < start+end; i++)
        System.out.print(pseudoBuffer[i] + " ");
      System.out.print("]");
    }
    catch(IndexOutOfBoundsException e){
      System.err.println(e.getMessage());
    }
    
  }
}

Result
Range [1, 1 + 4) out of bounds for length 4

Result(if end == 3)
[ 50 25 39 ]
checkFromToIndex() Method

Checks if the sub-range from fromIndex (inclusive) to toIndex (exclusive) is within the bounds of range from 0 (inclusive) to length (exclusive). This method has two forms. More information can be found in the documentation.

This example demonstrates int checkFromToIndex() method.
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    byte[] pseudoBuffer = new byte[]{100, 50, 25, 39};
    
    try{
      int start = 0;
      int end = 5;
      start = Objects.checkFromToIndex
      (1, end, pseudoBuffer.length);
      
      System.out.print("[ ");
      for(int i = start; i < end; i++)
        System.out.print(pseudoBuffer[i] + " ");
      System.out.print("]");
    }
    catch(IndexOutOfBoundsException e){
      System.err.println(e.getMessage());
    }
    
  }
}

Result
Range [1, 5) out of bounds for length 4

Result(if end == 4)
[ 50 25 39 ]
checkIndex() Method

Checks if the index is within the bounds of the range from 0 (inclusive) to length (exclusive). This method has two forms. More information can be found in the documentation.

This example demonstrates long checkIndex() method.
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    byte[] pseudoBuffer = new byte[]{100, 50, 25, 39};
    
    try{
      int index = 0;
      index = Objects.checkIndex
      (4, pseudoBuffer.length);
      
      System.out.println("Content: " 
      + pseudoBuffer[index]);
    }
    catch(IndexOutOfBoundsException e){
      System.err.println(e.getMessage());
    }
    
  }
}

Result
Index 4 out of bounds for length 4

Result(if index == 3)
Content: 39
compare() Method

Method Form: public static <T> int compare(T a, T b, Comparator<? super T> c)
Returns 0 if the arguments are identical and c.compare(a, b) otherwise. Consequently, if both arguments are null 0 is returned. Note that if one of the arguments is null, a NullPointerException may or may not be thrown depending on what ordering policy, if any, the Comparator chooses to have for null values.

This example demonstrates compare().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String s1 = "String";
    String s2 = "string";
    String s3 = "myString";
    String s4 = null;
    
    int result = 
    Objects.compare
    (s1, s2, String.CASE_INSENSITIVE_ORDER);
    System.out.println("1st result: " + result);
    
    result = Objects.compare
    (s1, s3, String.CASE_INSENSITIVE_ORDER);
    System.out.println("2nd result: " + result);
    
    result = Objects.compare
    (s4, s4, String.CASE_INSENSITIVE_ORDER);
    System.out.println("3rd result: " + result);
    
    result = Objects.compare
    (s1, s4, String.CASE_INSENSITIVE_ORDER);
    System.out.println("4th result: " + result);
  }
}

Result
1st result: 0
2nd result: 6
3rd result: 0
java.lang.NullPointerException...
deepEquals() method

Method Form: public static boolean deepEquals(Object a, Object b)
Returns true if the arguments are deeply equal to each other and false otherwise. Two null values are deeply equal. If both arguments are arrays, the algorithm in Arrays.deepEquals is used to determine equality. Otherwise, equality is determined by using the equals method of the first argument.

This example demonstrates deepEquals().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    int[][] ints1 = {{1,2,3},{4,5,6}};
    int[][] ints2 = {{1,2,3},{4,5,6}};
    int[][] ints3 = {{1,3},{4,5,6}};
    String s1 = "String";
    String s2 = "string";
    
    System.out.println("1st result: " +
    Objects.deepEquals(ints1, ints2));
    
    System.out.println("2nd result: " +
    Objects.deepEquals(ints1, ints3));
    
    System.out.println("3rd result: " +
    Objects.deepEquals(s1, s2));
  }
}

Result
1st result: true
2nd result: false
3rd result: false
equals() method

Method Form: public static boolean equals(Object a, Object b)
Returns true if the arguments are equal to each other and false otherwise. Consequently, if both arguments are null, true is returned. Otherwise, if the first argument is not null, equality is determined by calling the equals method of the first argument with the second argument of this method. Otherwise, false is returned.

This example demonstrates equals().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String s1 = "String";
    String s2 = "String";
    String s3 = "sTrInG";
    String s4 = null;
    
    System.out.println("1st result: " +
    Objects.equals(s1, s2));
    
    System.out.println("2nd result: " +
    Objects.equals(s1, s3));
    
    System.out.println("3rd result: " +
    Objects.equals(s1, s4));
  }
}

1st result: true
2nd result: false
3rd result: false
hash() method

Method Form: public static int hash(Object... values)
Generates a hash code for a sequence of input values. The hash code is generated as if all the input values were placed into an array, and that array were hashed by calling Arrays.hashCode(Object[]). More information can be found in the documentation.

This example demonstrates hash().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String s1 = "String";
    StringBuilder sb = 
    new StringBuilder("Builder");
    int[] ints = {2,4,6};
    
    int hash = Objects.hash(s1, sb, ints);
    System.out.println("1st result: " + hash);
    
    hash = Objects.hash(s1);
    System.out.println("2nd result: " +
    (hash == s1.hashCode()));
  }
}

Result(may vary)
1st result: -1273781580
2nd result: false
hashCode() method

Method Form: public static int hashCode(Object o)
Returns the hash code of a non-null argument and 0 for a null argument. This example demonstrates hashCode().
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String s1 = "String";
    String s2 = null;
    
    System.out.println("1st result: " + Objects.hashCode(s1));
    System.out.println("2nd result: " + Objects.hashCode(s2));
    //exception will occur here
    //System.out.println("2nd result: " + s2.hashCode());
  }
}
isNull() method

Method Form: public static boolean isNull(Object obj)
Returns true if the provided reference is null otherwise returns false. This method exists to be used as a Predicate, filter(Objects::isNull)

This example demonstrates isNull().
import java.util.Objects;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
  
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("str1");
    ar.add(null);
    ar.add("str2");
    ar.add(null);
    
    long nullCount = ar.stream()
      .filter(Objects::isNull)
      .count();
      
    System.out.println("null count: "+nullCount);
  }
}

Result
null count: 2
nonNull() method

Method Form: public static boolean nonNull(Object obj)
Returns true if the provided reference is non-null otherwise returns false. This method exists to be used as a Predicate, filter(Objects::nonNull).

This example demonstrates nonNull().
import java.util.Objects;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
  
    ArrayList<String> ar = 
    new ArrayList<>();
    
    ar.add("str1");
    ar.add(null);
    ar.add("str2");
    ar.add(null);
    
    ar.stream()
      .filter(Objects::nonNull)
      .forEach(System.out::println);
      
  }
}

Result
str1
str2
requireNonNull() method

Checks that the specified object reference is not null. This method has three forms. More information can be found in the documentation. This example demonstrates requireNonNull(T obj, String message).
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String str = null;
    
    try{
      Object s = 
      Objects.requireNonNull(str, "str is null!");
      if(s instanceof String)
        System.out.println(s);
    }
    catch(NullPointerException e){
      System.err.println(e.getMessage());
    }
  }
}

Result
str is null!
requireNonNullElse() method

Method Form: public static <T> T requireNonNullElse(T obj, T defaultObj)
Returns the first argument if it is non-null and otherwise returns the non-null second argument. This example demonstrates requireNonNullElse().
import java.util.Objects;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Integer> ar = 
    new ArrayList<>();
    
    ar.add(10);
    ar.add(null);
    ar.add(100);
    ar.add(null);
    
    ar.stream()
      .map((e) -> 
       Objects
       .requireNonNullElse(e,Integer.valueOf(0)))
      .forEach(System.out::println);
  }
}

Result
10
0
100
0
requireNonNullElseGet() method

Method Form: public static <T> T requireNonNullElseGet(T obj, Supplier<? extends T> supplier)
Returns the first argument if it is non-null and otherwise returns the non-null value of supplier.get(). Thie example demonstrates requireNonNullElseGet().
import java.util.Objects;
import java.util.ArrayList;

public class SampleClass{

  public static void main(String[] args){
    ArrayList<Double> ar = 
    new ArrayList<>();
    
    ar.add(3.5);
    ar.add(null);
    ar.add(5.5);
    ar.add(null);
    
    ar.stream()
      .map((e) -> 
       Objects
       .requireNonNullElseGet(e,Math::random))
      .forEach(System.out::println);
  }
}

Result(may vary)
3.5
0.6468480442983721
5.5
0.762667953719096
toString() method

Returns the result of calling toString for a non-null argument or a particular message for a null argument. This method has two forms. This example demonstrates toString() and its forms.
import java.util.Objects;

public class SampleClass{

  public static void main(String[] args){
    String str = "String";
    String s = null;
    
    //public static String toString(Object o)
    System.out.println(Objects.toString(str));
    System.out.println(Objects.toString(s));
    
    //public static String toString(Object o,
    //String nullDefault)
    System.out.println
    (Objects.toString(str, "none"));
    System.out.println
    (Objects.toString(s, "none"));
  }
}

Result
String
null
String
none