Viewing Dependencies – Java Module System

Viewing Dependencies

The Java Dependency Analysis tool, jdeps, is a versatile command-line tool for static analysis of dependencies between Java artifacts like class files and JARs. It can analyze dependencies at all levels: module, package, and class. It allows the results to be filtered and aggregated in various ways, even generating various graphs to illustrate its findings. It is an indispensable tool when migrating non-modular code to make use of the module system.

In this section, we confine our discussion to modular code—that is, either an exploded module directory with the compiled module code (e.g., mods/main) or a modular JAR (e.g., mlib/main.jar).

In this section, the results from some of the jdeps commands have been edited to fit the width of the page without any loss of information, or elided to shorten repetitious outputs.

Viewing Package-Level Dependencies

The jdeps command with no options, shown below, illustrates the default behavior of the tool. Line numbers have been added for illustrative purposes.

When presented with the root module directory mods/model (1), the default behavior of jdeps is to print the name of the module (2), the path to its location (3), its module descriptor (4), followed by its module dependencies (5), and lastly the package-level dependencies (6). The package-level dependency at (6) shows that the package com.passion.model in the model module depends on the java.lang package in the proverbial java.base module.

Click here to view code image

(1) 
>jdeps mods/model

(2)  model
(3)  [file:…/adviceApp/mods/model/]
(4)     requires mandated java.base (@17.0.2)
(5)  model -> java.base
(6)     com.passion.model           -> java.lang           java.base

The following jdeps command if let loose on the model.jar archive will print the same information for the model module, barring the difference in the file location:

>
jdeps mlib/model.jar

However, the following jdeps command with the main.jar archive gives an error:

Click here to view code image

>jdeps mlib/main.jar

Exception in thread “main” java.lang.module.FindException: Module controller
not found, required by main
       at java.base/…

Dependency analysis cannot be performed on the main module by the jdeps tool because the modules the main module depends on cannot be found. In the case of the model module, which does not depend on any other user-defined module, the dependency analysis can readily be performed.

The two options –module-path (no short form) and –module (short form: -m) in the jdeps command below unambiguously specify the location of other modular JARs and the module to analyze, respectively. The format of the output is the same as before, and can be verified easily.

Click here to view code image

>jdeps –module-path mlib –module main
main
 [file:…/adviceApp/mlib/main.jar]
   requires controller
   requires mandated java.base (@17.0.2)
main -> controller
main -> java.base
   com.passion.main     -> com.passion.controller     controller
   com.passion.main     -> java.lang                  java.base

However, if one wishes to analyze all modules that the specified module depends on recursively, the –recursive option (short form: -R) can be specified. The output will be in the same format as before, showing the package-level dependencies for each module. The output from the jdeps command for each module is elided below, but has the same format we have seen previously.

Click here to view code image

>
jdeps –module-path mlib –module main –recursive

controller

main

model

view


Absolute and Relative Paths – Java I/O: Part II

Absolute and Relative Paths

Directory entries can be referenced using both absolute and relative paths, but the path naming must follow the conventions of the host platform.

An absolute path starts with the platform-dependent root component of the file system, as in the examples above. All information is contained in an absolute path to reference the directory entry—that is, an absolute path uniquely identifies a directory entry in the file system.

A relative path is without designation of the root component and therefore requires additional path information to locate its directory entry in the file system. Typically, a relative path is interpreted in relation to the current directory (see the next subsection). Some examples of relative paths are given below, but they alone are not enough to uniquely identify a directory entry.

Click here to view code image

c/d                on Unix-based platforms
c\d                on Windows-based platforms

Note that in a path, the name elements are all parent directory names, except for the last name element, which can be either a file name or a directory name. The name of a directory entry does not distinguish whether it is a file or a directory in the file system. Although file extensions can be used in a file name for readability, it is immaterial for the file system. Care must also be exercised when choosing name elements, as characters allowed are usually platform dependent.

Java programs should not rely on system-specific path conventions. In the next section we will construct paths in a platform-independent way.

Current and Parent Directory Designators

A file system has a notion of a current directory which changes while traversing in the file system. The current directory is designated by the period character (.) and its parent directory by two periods (..). These designators can be used in constructing paths. Given that the current directory is /a/b in the directory tree shown earlier, Table 21.1 illustrates relative paths constructed using the current and the parent directory designators, and their corresponding absolute paths.

Table 21.1 Using Current and Parent Directory Designators

Relative path (Current directory: /a/b)Absolute path
./c/d/a/b/c/d
./a/b
./../a
./../../ (i.e., the root component)

Summary of Selected Operations with the JDK Tools – Java Module System

19.14 Summary of Selected Operations with the JDK Tools

Table 19.6 provides a ready reference for commands to perform miscellaneous operations using the JDK tools introduced in this chapter. Short forms for the options are shown where appropriate. Use the indicated reference for each operation to learn more about it. The only way to become familiar with these tools is to use them.

Table 19.6 Selected Operations with the JDK Tools

OperationCommand
Compiling modular code (p. 1186)Click here to view code image javac –module-path
modulepath
 -d
directory sourceAndModuleInfoFiles

javac  -p          
modulepath
 -d
directory sourceAndModuleInfoFiles

javac –module-source-path
modulepath
 -d
directory \

      –module
moduleName1
,…
javac –module-source-path
modulepath
 -d
directory
 \
       -m
moduleName1
,…

Launching modular application (p. 1189)Click here to view code image java –module-path
modulepath
 –module
moduleName/qualifiedClassName

java  -p          
modulepath
  -m     
moduleName/qualifiedClassName

java –module-path
modulepath
  -module
moduleName

java  -p          
modulepath
  -m     
moduleName

Creating and listing a modular JAR archive (p. 1189)Click here to view code image jar –verbose –create –file
jarfile -C directory files

jar -vcf
jarfile
 -C
directory
 .
jar -vcf
jarfile
 –main-class
qualifiedMainClassName
 -C
directory
 .
jar -vcfe
jarfile qualifiedMainClassName
 -C
directory
 .

jar –list –file
jarfile

jar -tf
jarfile

Listing available modules (p. 1212)Click here to view code image java –list-modules
java –module-path
modulepath
 –list-modules
java  -p          
modulepath
 –list-modules

Describing a module—that is, printing the module descriptor (p. 1213)Click here to view code image java –module-path
modulepath
 –describe-module
moduleName

java  -p          
modulepath
  -d              
moduleName


jar –file
jarFile
 –describe-module
jar  -f   
jarFile
  -d

Viewing package-level dependencies (p. 1214)Click here to view code image jdeps –module-path
modulepath
 -m
moduleName

jdeps –module-path
modulepath
 -m
moduleName
 –recursive
jdeps –module-path
modulepath
 -m
moduleName
 -R

Viewing module dependencies (p. 1216)Click here to view code image jdeps –module-path
modulepath
 -m
moduleName
 -summary
jdeps –module-path
modulepath
 -m
moduleName
 -summary –recursive
jdeps –module-path
modulepath
 -m
moduleName
 -s -R

Viewing class-level dependencies (p. 1216)Click here to view code image jdeps –module-path
modulepath
 -m
moduleName
 -verbose
jdeps –module-path
modulepath
 -m
moduleName
 -verbose –recursive
jdeps –module-path
modulepath
 -m
moduleName
 -v -R

Selected Options for the javac Tool – Java Module System

Selected Options for the javac Tool

The Java language compiler, javac, compiles Java source code into Java bytecode. The general form of the javac command is:

javac
[options] [sourcefiles]

Table 19.7 shows some selected options that can be used when compiling modules. The optional sourcefiles is an itemized list of source files, often omitted in favor of using module-related options.

Table 19.7 Selected Options for the javac Tool

OptionDescription
–module-source-path
   
moduleSourcePath

The moduleSourcePath specifies the source directory where the exploded modules with the source code files can be found.
–module-path
modulepath

 -p          
modulepath

The modulepath specifies where the modules needed by the application can be found. This can be a root directory of the exploded modules with the class files or a root directory where the modular JARs can be found. Multiple directories of modules can be specified, separated by a colon (:) on a Unix-based platform and semicolon (;) on the Windows platform.
–module
moduleName

 -m     
moduleName

Specifies the module(s) to be compiled. Can be a single module name or a comma-separated (,) list of module names. For each module specified, all modules it depends on are also compiled, according to the module dependencies.
-d
classesDirectory

Specifies the destination directory for the class files. Mandatory when compiling modules. For classes in a package, their class files are put in a directory hierarchy that reflects the package name, with directories being created as needed. Without the -d option, the class files are put in the same directory as their respective source files. Specifying the directory path is platform dependent: slash (/) on Unix-based platforms and backslash (\) on Windows platforms being used when specifying the directory path.
–add-modules
module,…

Specifies root modules to resolve in addition to the initial modules. These modules can be modular JAR files, JMOD files, or even exploded modules.

Selected Options for the java Tool

The java tool launches an application—that is, it creates an instance of the JVM in which to run the application. A typical command to launch a modular application is by specifying the location of its modules (path) and the entry point of the application (as module or module/mainclass):

Click here to view code image

java –module-path
path
 –module
module[/mainclass]

Table 19.8 shows some selected options that can be used for launching and exploring modular applications.

Table 19.8 Selected Options for the java Tool

OptionDescription
–module-path
modulepath…

 -p          
modulepath

The modulepath specifies the location where the modules needed to run the application can be found. This can be a root directory for the exploded modules with the class files or a directory where the modular JARs can be found. Multiple directories of modules can be specified, separated by a colon (:) on a Unix-based platform and semicolon (;) on Windows platforms.
–module
module
[/
mainclass
]
 -m     
module
[/
mainclass
]

When module/mainclass is specified, it explicitly states the module name and the fully qualified name of the class with the main() method, thereby overriding any other entry point in the application. Without the mainclass, the entry point of the application is given by the module which must necessarily contain the main-class.
–add-modules
module,…

Specifies root modules to resolve in addition to the initial modules.
–list-modules

Only lists the observable modules, and does not launch the application. That is, it lists the modules that the JVM can use when the application is run.
–describe-module
moduleName

 -d              
moduleName

Describes a specified module, in particular its module descriptor, but does not launch the application.
–validate-modules

Validates all modules on the module path to find conflicts and errors within modules.

Selected Options for the jar Tool – Java Module System

Selected Options for the jar Tool

The jar tool is an archiving and compression tool that can be used to bundle Java artifacts and any other resources that comprise the application. The archive file names have the .jar extension. A typical command to create a modular JAR (jarfile) with an application entry point (qualifiedMainClassName), based on the contents of a specific directory (DIR), is shown below. Note the obligatory dot (.) at the end of the command.

Click here to view code image

jar –create –file
jarfile
 –main-class
qualifiedMainClassName
 -C
DIR
 .

Table 19.9 gives an overview of some selected options that can be used for working with JARs.

Table 19.9 Selected Options for the jar Tool

OptionDescription
–create or -cCreates a new archive.
–extract or -xExtracts specified or all files in the archive.
–list or -tLists the contents of the archive.
–update or -uUpdates an existing archive with specified files.
–describe-module or -dPrints the module descriptor of the archive and the main-class, if one is specified in the manifest.
–verbose or -vPrints extra information about the operation.
–file
jarfile

–file=
jarfile

 -f
jarfile

 -f=
jarfile

Specifies the name of the archive.
-C DIR filesChanges to the specified directory and includes the contents of the specified files from this directory. If files is a dot (.), the contents under the specified directory DIR are included.
Click here to view code image –main-class
qualifiedMainClassName

–main-class=
qualifiedMainClassName

 -e
qualifiedMainClassName

 -e=
qualifiedMainClassName

Specifies the entry point of the application.
–manifest
TXTFILE

–manifest=
TXTFILE

 -m
TXTFILE

 -m=
TXTFILE

Reads the manifest information for the archive from the specified TXTFILE and incorporates it in the archive—for example, the value of the Main-Class attribute that specifies the entry point of the application.
–module-path
modulepath

 -p          
modulepath

Specifies the location of the modules for recording hashes.

Selected Options for the jdeps Tool

The Java Class Dependency Analyzer, jdeps, is the tool of choice when working with modules, as it is module savvy and highly versatile. Among its extensive module analyzing capabilities, it can be used to explore dependencies at different levels: module level, package level, and class level.

Table 19.10 gives an overview of some selected options for the jdeps tool that can be used for analyzing modules.

Table 19.10 Selected Options for the jdeps Tool

OptionDescription
–module-path modulepathSpecifies where to find the module JARs needed by the application. No short form, as -p is already reserved for –package.
–module-name
moduleName

 -m          
moduleName

Specifies the root module for module dependency analysis.
-summary or -sPresents only a summary of the module dependencies.
–recursive or -RForces jdeps to recursively iterate over the module dependencies. When used alone, also prints the package-level dependencies.
-verbose or -vAlso includes all class-level dependencies in the printout.
-verbose:packageIncludes package-level dependencies in the printout, excluding, by default, dependencies within the same package.
-verbose:classIncludes class-level dependencies in the printout, excluding, by default, dependencies within the same JAR.

Selected Options for the jlink Tool – Java Module System

Selected Options for the jlink Tool

The jlink tool creates a runtime image of an application. A typical command to create a runtime image of an application requires the location of its modules (path), names of modules to include (module_names), and output directory to store the runtime image (output_dir):

Click here to view code image

jlink –module-path
path
 –add-modules
module_names
 –output
output_dir

The runtime image can be executed by the output_dir/java command.

Selected options for the jlink tool are summarized in Table 19.11.

Table 19.11 Selected Options for the jlink Tool

OptionDescription
–module-path
modulepath…

 -p          
modulepath

Specifies the location where the modules for the application can be found. This can be a root directory for the exploded modules with the class files or a directory where the modular JARs can be found. Multiple directories of modules can be specified, separated by a colon (:) on Unix-based platforms and semicolon (;) on Windows platforms.
–add-modules module,…Specifies the modules to include in the generated runtime image. All application modules must be listed. Any standard or JDK modules needed will be automatically included.
–output pathSpecifies the location of the generated runtime image.

Final Remarks on Options for the JDK Tools

It is worth taking a note of how the command options having the short form -p, -m, and -d are specified in different JDK tools. Table 19.12 gives an overview of which long form they represent in which tool and how they are specified.

Table 19.12 Selected Common Shorthand Options for JDK Tools

javacjavajarjdeps
–module-path
path

 -p           
path

–module-path
path

 -p          
path

–module-path
path

 -p          
path

–module-path path (no short form as -p is reserved for –package)
–module
module

 -m     
module

–module
module
[/
mainclass
]
 -m     
module
[/
mainclass
]

–manifest
TXTFILE

 -m       
TXTFILE

–module
module

 -m
module

(no long form)
-d classesDirectory
–describe-module
module

 -d              
module

–describe-module
 -d


(no -d option)

Reading Text Files – Java I/O: Part I

Reading Text Files

When reading characters from a file using the default character encoding, the following two procedures for setting up an InputStreamReader can be used.

Setting up an InputStreamReader which is chained to a FileInputStream (Figure 20.5(a)):

Figure 20.5 Setting Up Readers to Read Characters

Create a FileInputStream:

Click here to view code image

FileInputStream inputFile = new FileInputStream(“info.txt”);

Create an InputStreamReader which is chained to the FileInputStream:

Click here to view code image

InputStreamReader reader = new InputStreamReader(inputFile);

The InputStreamReader uses the default character encoding for reading the characters from the file.

Setting up a FileReader which is a subclass of InputStreamReader (Figure 20.5(b)):

Create a FileReader:

Click here to view code image

FileReader fileReader = new FileReader(“info.txt”);

This is equivalent to having an InputStreamReader chained to a FileInputStream for reading the characters from the file, using the default character encoding.

If a specific character encoding is desired for the reader, the first procedure can be used (Figure 20.5(a)), with the encoding being specified for the InputStreamReader:

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileInputStream inputFile = new FileInputStream(“info.txt”);
InputStreamReader reader = new InputStreamReader(inputFile, utf8);

This reader will use the UTF-8 character encoding to read the characters from the file. Alternatively, we can use one of the FileReader constructors that accept a character encoding:

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileReader reader = new FileReader(“info.txt”, utf8);

A BufferedReader can also be used to improve the efficiency of reading characters from the underlying stream, as explained later in this section (p. 1251).

Using Buffered Writers

A BufferedWriter can be chained to the underlying writer by using one of the following constructors:

Click here to view code image

BufferedWriter(Writer out)
BufferedWriter(Writer out, int size)

The default buffer size is used, unless the buffer size is explicitly specified.

Characters, strings, and arrays of characters can be written using the methods for a Writer, but these now use buffering to provide efficient writing of characters. In addition, the BufferedWriter class provides the method newLine() for writing the platform-dependent line separator.

The following code creates a PrintWriter whose output is buffered, and the characters are written using the UTF-8 character encoding (Figure 20.6(a)):

Figure 20.6 Buffered Writers

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileOutputStream   outputFile      = new FileOutputStream(“info.txt”);
OutputStreamWriter outputStream    = new OutputStreamWriter(outputFile, utf8);
BufferedWriter     bufferedWriter1 = new BufferedWriter(outputStream);
PrintWriter        printWriter1    = new PrintWriter(bufferedWriter1, true);

The following code creates a PrintWriter whose output is buffered, and the characters are written using the default character encoding (Figure 20.6(b)):

Click here to view code image

FileWriter     fileWriter      = new FileWriter(“info.txt”);
BufferedWriter bufferedWriter2 = new BufferedWriter(fileWriter);
PrintWriter    printWriter2    = new PrintWriter(bufferedWriter2, true);

Note that in both cases, the PrintWriter is used to write the characters. The Buffered-Writer is sandwiched between the PrintWriter and the underlying OutputStreamWriter (which is the superclass of the FileWriter class).

Using Buffered Readers – Java I/O: Part I

Using Buffered Readers

A BufferedReader can be chained to the underlying reader by using one of the following constructors:

Click here to view code image

BufferedReader(Reader in)
BufferedReader(Reader in, int size)

The default buffer size is used, unless the buffer size is explicitly specified.

In addition to the methods of the Reader class, the BufferedReader class provides the method readLine() to read a line of text from the underlying reader.

Click here to view code image

String readLine() throws IOException

The null value is returned when the end of the stream is reached. The returned string must explicitly be converted to other values.

The BufferedReader class also provides the lines() method to create a stream of text lines with a buffered reader as the data source (§16.4, p. 902).

Stream<String> lines()

Returns a finite sequential ordered Stream of element type String, where the elements are text lines read by this BufferedReader.

The following code creates a BufferedReader that can be used to read text lines from a file (Figure 20.7(b)):

Figure 20.7 Buffered Readers

Click here to view code image

// Using the UTF-8 character encoding:
Charset utf8 = Charset.forName(“UTF-8”);
FileReader     fileReader      = new FileReader(“lines.txt”, utf8);
BufferedReader bufferedReader1 = new BufferedReader(reader)
// Use the default encoding:
FileReader     fileReader      = new FileReader(“lines.txt”);
BufferedReader bufferedReader2 = new BufferedReader(fileReader);

Note that in both cases the BufferedReader object is used to read the text lines.

Java primitive values and objects cannot be read directly from their text representation in a file. Characters must be read and converted to the relevant values explicitly. If the text representation of the values is written as lines of text, each line can be read and tokenized first—that is, grouping characters into tokens that meaningfully represent a value. For example, the line “Potatoes 2.50” contains two tokens: the item token “Potatoes” and the price token “2.50”. Once a line is tokenized, the tokens can be parsed to obtain the appropriate type of value. The item token is already of type String, but the price token “2.50” needs to be parsed to a double value using the Double.parseDouble() method. A scanner provided by the java.io.Scanner class or the String.split() method called on each line can be used to tokenize the character input—both of which are beyond the scope of this book.

In contrast to Example 20.2, which demonstrated the reading and writing of binary representations of primitive data values, Example 20.4 illustrates the reading and writing of text representations of values using I/O streams that are readers and writers.

The CharEncodingDemo class in Example 20.4 writes text representations of values using the UTF-8 character encoding specified at (1). It uses a try-with-resources statement for handling the closing of I/O streams, as shown at (2) and (4). The PrintWriter is buffered (Figure 20.6(b)). Its underlying writer uses the specified encoding, as shown at (2). Values are written out with the text representation of one value on each line, as shown at (3). The example uses the same character encoding to read the text file. A BufferedReader is created (Figure 20.7(b)). Its underlying reader uses the specified encoding, as shown at (4). The text representation of the values is read as one value per line, and parsed accordingly. Each text line in the file is read by calling the readLine() method. The characters in the line are explicitly converted to an appropriate type of value, as shown at (5).

The values are printed on the standard output stream, as shown at (6). We check for the end of the stream at (7), which is signaled by the null value returned by the readLine() method of the BufferedReader class. Note the exceptions that are specified in the throws clause of the main() method.

Although buffering might seem like overkill in this simple example, for efficiency reasons, it should be considered when reading and writing characters from external storage.

It is a useful exercise to modify Example 20.4 to use the various setups for chaining streams for reading and writing characters, as outlined in this section.

Example 20.4 Demonstrating Readers and Writers, and Character Encoding

Click here to view code image

import java.io.*;
import java.nio.charset.Charset;
import java.time.LocalDate;
public class CharEncodingDemo {
  public static void main(String[] args)
         throws FileNotFoundException, IOException, NumberFormatException {
    // UTF-8 character encoding.
    Charset utf8 = Charset.forName(“UTF-8”);                           // (1)
    try(// Create a BufferedWriter that uses UTF-8 character encoding     (2)
        FileWriter writer = new FileWriter(“info.txt”, utf8);
        BufferedWriter bufferedWriter1 = new BufferedWriter(writer);
        PrintWriter printWriter = new PrintWriter(bufferedWriter1, true);) {
      System.out.println(“Writing using encoding: ” + writer.getEncoding());
      // Print some values, one on each line.                             (3)
      printWriter.println(LocalDate.now());
      printWriter.println(Integer.MAX_VALUE);
      printWriter.println(Long.MIN_VALUE);
      printWriter.println(Math.PI);
    }
    try(// Create a BufferedReader that uses UTF-8 character encoding     (4)
        FileReader reader = new FileReader(“info.txt”, utf8);
        BufferedReader bufferedReader = new BufferedReader(reader);) {
      System.out.println(“Reading using encoding: ” + reader.getEncoding());
      // Read the character input and parse accordingly.                  (5)
      LocalDate ld = LocalDate.parse(bufferedReader.readLine());
      int iMax = Integer.parseInt(bufferedReader.readLine());
      long lMin = Long.parseLong(bufferedReader.readLine());
      double pi = Double.parseDouble(bufferedReader.readLine());
      // Write the values read on the terminal                            (6)
      System.out.println(“Values read:”);
      System.out.println(ld);
      System.out.println(iMax);
      System.out.println(lMin);
      System.out.println(pi);
      // Check for end of stream:                                         (7)
      String line = bufferedReader.readLine();
      if (line != null ) {
        System.out.println(“More input: ” + line);
      } else {
        System.out.println(“End of input stream”);
      }
    }
  }
}

Output from the program:

Writing using encoding: UTF8
Reading using encoding: UTF8
Values read:
2021-06-22
2147483647
-9223372036854775808
3.141592653589793
End of input stream

Selective Serialization – Java I/O: Part I

Selective Serialization

As noted earlier, static fields are not serialized, as these are not part of the state of an object. An instance field of an object can be omitted from being serialized by specifying the transient modifier in the declaration of the field—typically used for sensitive data in a field. Selective serialization discussed here is not applicable to record classes.

Example 20.7 illustrates some salient aspects of serialization. The setup comprises the classes Wheel and Unicycle, and their client class SerialClient. The class Unicycle has a field of type Wheel, and the class Wheel has a field of type int. The class Unicycle is a compound object with a Wheel object as a constituent object. The class Serial-Client serializes and deserializes a unicycle in the try-with-resources statements at (4) and (5), respectively. The state of the objects is printed to the standard output stream before serialization, and so is the state of the object created by deserialization.

Both the Compound Object and Its Constituents Are Serializable

If we run the program with the following declarations for the Wheel and the Unicycle classes, where a compound object of the serializable class Unicycle uses an object of the serializable class Wheel as a constituent object:

Click here to view code image

class Wheel implements Serializable {                               // (1a)
  private int wheelSize;
  …
}
class Unicycle implements Serializable {                            // (2)
  private Wheel wheel;                                              // (3a)
  …
}

we get the following output, showing that both serialization and deserialization were successful:

Click here to view code image

Before writing: Unicycle with wheel size: 65
After reading: Unicycle with wheel size: 65

A compound object with its constituent objects is often referred to as an object graph. Serializing a compound object serializes its complete object graph—that is, the compound object and its constituent objects are recursively serialized.

Example 20.7 Non-Serializable Objects

Click here to view code image

import java.io.Serializable;
// public class Wheel implements Serializable {                   // (1a)
public class Wheel {                                              // (1b)
  private int wheelSize;
  public Wheel(int ws) { wheelSize = ws; }
  @Override
  public String toString() { return “wheel size: ” + wheelSize; }
}

Click here to view code image

import java.io.Serializable;
public class Unicycle implements Serializable {                     // (2)
  private Wheel wheel;                                              // (3a)
//transient private Wheel wheel;                                    // (3b)
  public Unicycle (Wheel wheel) { this.wheel = wheel; }
  @Override
  public String toString() { return “Unicycle with ” + wheel; }
}

Click here to view code image

import java.io.*;
public class SerialClient {
  public static void main(String args[])
      throws IOException, ClassNotFoundException {
    try (// Set up the output stream:                              // (4)
        FileOutputStream outputFile = new FileOutputStream(“storage.dat”);
        ObjectOutputStream outputStream = new ObjectOutputStream(outputFile)) {
      // Write the data:
      Wheel wheel = new Wheel(65);
      Unicycle uc = new Unicycle(wheel);
      System.out.println(“Before writing: ” + uc);
      outputStream.writeObject(uc);
    }
    try (// Set up the input streams:                              // (5)
        FileInputStream inputFile = new FileInputStream(“storage.dat”);
        ObjectInputStream inputStream = new ObjectInputStream(inputFile)) {
      // Read data.
      Unicycle uc = (Unicycle) inputStream.readObject();
      // Write data on standard output stream.
      System.out.println(“After reading: ” + uc);
    }
  }
}

Writing Formatted Values – Java I/O: Part I

Writing Formatted Values

Although formatting of values is covered extensively in Chapter 18, p. 1095, here we mention the support for formatting values provided by I/O streams. The PrintWriter class provides the format() methods and the printf() convenient methods to write formatted values. The printf() methods are functionally equivalent to the format() methods. As the methods return a PrintWriter, calls to these methods can be chained.

The printf() and the format() methods for printing formatted values are also provided by the PrintStream and the Console classes (p. 1256). The format() method is also provided by the String class (§8.4, p. 457). We assume familiarity with printing formatted values on the standard output stream by calling the printf() method on the System.out field which is an object of the PrintStream class (§1.9, p. 24).

Click here to view code image

PrintWriter format(String format, Object… args)
PrintWriter format(Locale loc, String format, Object… args)
PrintWriter printf(String format, Object… args)
PrintWriter printf(Locale loc, String format, Object… args)

The String parameter format specifies how formatting will be done. It contains format specifiers that determine how each subsequent value in the variable arity parameter args will be formatted and printed. The resulting string from the formatting will be written to the current writer.

If the locale is specified, it is taken into consideration to format the args.

Any error in the format string will result in a runtime exception.

Writing Text Files

When writing text representation of values to a file using the default character encoding, any one of the following four procedures for setting up a PrintWriter can be used.

Setting up a PrintWriter based on an OutputStreamWriter which is chained to a FileOutputStream (Figure 20.4(a)):

Figure 20.4 Setting Up a PrintWriter to Write to a File

Create a FileOutputStream:

Click here to view code image

FileOutputStream outputFile = new FileOutputStream(“info.txt”);

Create an OutputStreamWriter which is chained to the FileOutputStream:

Click here to view code image

OutputStreamWriter outputStream = new OutputStreamWriter(outputFile);

The OutputStreamWriter uses the default character encoding for writing the characters to the file.

Create a PrintWriter which is chained to the OutputStreamWriter:

Click here to view code image

PrintWriter printWriter1 = new PrintWriter(outputStream, true);

The value true for the second parameter in the constructor will result in the output buffer being flushed by the println() and printf() methods.

Setting up a PrintWriter based on a FileOutputStream (Figure 20.4(b)):

Create a FileOutputStream:

Click here to view code image

FileOutputStream outputFile = new FileOutputStream(“info.txt”);

Create a PrintWriter which is chained to the FileOutputStream:

Click here to view code image

PrintWriter printWriter2 = new PrintWriter(outputFile, true);

The intermediate OutputStreamWriter to convert the characters using the default encoding is automatically supplied. The output buffer will also perform automatic line flushing.

Setting up a PrintWriter based on a FileWriter (Figure 20.4(c)):

Create a FileWriter which is a subclass of OutputStreamWriter:

Click here to view code image

FileWriter fileWriter = new FileWriter(“info.txt”);

This is equivalent to having an OutputStreamWriter chained to a FileOutputStream for writing the characters to the file, as shown in Figure 20.4(a).

Create a PrintWriter which is chained to the FileWriter:

Click here to view code image

PrintWriter printWriter3 = new PrintWriter(fileWriter, true);

The output buffer will be flushed by the println() and printf() methods.

Setting up a PrintWriter, given the file name (Figure 20.4(d)):

Create a PrintWriter, supplying the file name:

Click here to view code image

PrintWriter printWriter4 = new PrintWriter(“info.txt”);

The underlying OutputStreamWriter is created to write the characters to the file in the default encoding, as shown in Figure 20.4(d). In this case, there is no automatic flushing by the println() and printf() methods.

If a specific character encoding is desired for the writer, the third procedure (Figure 20.4(c)) can be used, with the encoding being specified for the FileWriter:

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
FileWriter fileWriter = new FileWriter(“info.txt”, utf8);
PrintWriter printWriter5 = new PrintWriter(fileWriter, true);

This writer will use the UTF-8 character encoding to write the characters to the file. Alternatively, we can use a PrintWriter constructor that accepts a character encoding:

Click here to view code image

Charset utf8 = Charset.forName(“UTF-8”);
PrintWriter printWriter6 = new PrintWriter(“info.txt”, utf8);

A BufferedWriter can also be used to improve the efficiency of writing characters to the underlying stream, as explained later in this section.