Jon Rumsey

An online markdown blog and knowledge repository.


Project maintained by nojronatron Hosted on GitHub Pages — Theme by mattgraham

Java IO Data and Object Streams

This page is a collection of notes taken while working through Oracle Java Tutorials and supplemental information made public by Baeldung.com.

Table of Contents

Oracle Java Tutorials Basic IO

The Generic Interface Stream

Library: java.util.stream

Extends: BaseStream<T,Stream<T>>

"A sequence of elements supporting sequenctial and parallel aggregate operations."

Example aggregate operation using Stream and IntStream [docs.oracle.com]:

int sum = widgets.stream()
  .filter(w -> w.getColor() == RED)
  .mapToInt(w -> w.getWeight())
  .sum();

IntStream is one of several 'primitive specializations' (Streams).

A Stream represents a pipeline.

Stream Pipeline sources could be Collections, Arrays, a Generator Function, I-O Channel, etc.

Stream Pipelines include a processing section for 'intermediate operations'.

The end of a Stream Pipeline is denoted by a 'terminal operation', producing a result or side effect.

Streams are lazy, meaning they do not operate until the 'terminal operation' is initiated.

Source Elements are consumed on an as-needed basis.

Goals:

Think of Stream Operations "as a query on the stream source." Could be similar to DotNET LINQ Operators.

Operations that alter the Source could cause unexpected Stream Pipeline behavior.

Use lambda expressions w -> w.getWeight() to specify behavior.

Necessary behavioral parameters:

Streams Rules:

Streams can be made to be:

Static Interface Stream.Builder<T> can be used as a mutable builder for a Stream.

Byte Streams

8-bit data streams.

Byte Stream classes are decendants of InputStream and OutputStream.

Seems like its a good idea to import java.io.IOException for these streams.

Instantiate FileInputStream with the filename of the data source.

Instantiate FileOutputStream with a write-to filename.

// always initialize Streams as null
FileInputStream in = null;
FileOutputStream out = null;

// set up the input and output files
in = new FileInputStream("input_file.txt");
out = new FileOutputStream("output_file.txt");

Use a loop to read-in data until FileInputStream sees byte -1.

try {
  int character;
  while((character = in.read()) != -1) {
    out.write(character);
  }
}

Always close streams - use a Finally block to safely close them:

finally {
  // if a file was not opened, in will be null and never opened
  if (in != null) {
    in.close();
  }
  // same here, no file, out will be null and should not be closed
  if (out != null) {
    out.close();
  }
}

Summary of Byte Streams

Character Streams

Java Basic IO Character Streams.

CopyCharacters class example [Docs.Oracle.com]:

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyCharacters {
  public static void main(String[] args) throws IOExecption {
    FileReader inputStream = null; // Character Stream uses FileReader for input
    FileWriter outputStream = null; // Character Stream uses FileWriter for output

    try {
      inputStream = new FileReader("input_file.txt");
      outputStream = new FileWriter("character_output_file.txt");
      int character; // holds Character VALUE
      while ((character = inputStream.read()) != -1) {
        outputStream.write(character);
      }
    } finally {
      if (inputStream != null) {
        inputStream.close();
      }
      if (outputStream != null) {
        outputStream.close();
      }
    }
  }
}

Bridge Streams

This sub-section is largely a copy from [Docs.Oracle.com]

Line-Oriented IO

Note: Line terminators might be altered by PrintWriter from whatever the source terminator(s), to terminator(s) native to the current environment or Operating System.

Scanning and Formatting have more capability in regards to longer data read and write operations, and is covered in a later section.

Buffered Streams

Unbuffered Streams:

Buffered IO Streams:

var inputStream = new BufferedReader(new FileReader("input_file.txt"));
var outputSTream = new BufferedWriter(new FileWriter("output_file.txt"));

Four buffered stream classes:

Flushing Buffers

Scanning and Formatting

Usually requires translating between human-friendly and computer-ready data strams.

Scanning

Scanner:

Basic Implementation of Scanner:

  1. Initialize as null: Scanner scanner = null
  2. Use try-finally block and ensure Scanner instance is closed before exiting the program.
  3. Use while (scanner.hasNext())) to step through the delimited data tokens.

Formatting

PrintWriter (Character Stream class) and PrintStream (Byte Stream class) support formatting.

PrintWriter supports formatting an output stream for human use.

PrintStream is better for System.out and System.err data stream formatting.

Formatting Levels:

Use System.out.format with %s value placeholders, eol tokens, etc.

Note: %n will always produce the correct New Line code for the local OS locale but \n will not!

Format Specifier Elements:

IO From Command Line

Java handles this via 2 means:

Standard Streams:

Standard IOs are defined automatically in Java:

To use Standard Input as a character stream wrap System.in in InputStreamReader.

The Console

There is a basic example of how to implement a password changing program using the Conole by Oracle Docs.

Data Streams

Class DataOutputStream can only be instantiated as a wrapper to an existing ByteStream object, and provides buffers. Same for DataInputSTream.

var out = new DataOutputStream(
  new BufferedOutputStream(
    new FileOutputStream(input_file)
  )
);

DataOutputStream methods include:

For both Input and Output the instantiation pattern is: File Stream(file_name), wrapped by Buffered Stream, wrapped by Data Stream.

Note: DataInputStream throws EndOfFileException when it finds the end of a file, rather than setting a specific state.

Note: Data Streams use Floating-point values to represent monetary Numbers, so certain decimals will not convert properly using Data Streams!

Object Streams

Object Stream Classes are:

Object Stream Classes implement subinterfaces ObjectInput and ObjectOutput of DataInput and DataOutput, respectively.

Benefit: Object Streams support BigDecimal objects for fractional value representations (better than floating-point as in Data Streams).

Object Management Logic

Reconstituting an object from a stream can be tricky.

File IO with NIO.2

Oracle Java Docs refers to JDK 8 release and java.nio.file package and its sub-packages in this sub-section.

The documentation reviews paths, both relative and absolute, and Symbolic Links.

Unix-based paths are not comparable to Windows-based paths due to their native naming convention. However, both can be handled and processed by Java.

Symbolic Links are:

Java handles circularly-referenced Symlinks.

Paths

Create a path using the Paths helper class:

Path path1 = Paths.get("/home");
Path path2 = Paths.get(args[0]);
Path path3 = Paths.get(URI.create("file:///Users/user/file.java"));

Paths helper is shorthand for FileSystems.getDefault().getPath(String);

Notes (source java nio File Paths helper class):

Create a file and get a reference to it in either Unix or Windows:

Path myFile = Paths.get(System.getProperty("user.home"), "logs", "logfile.txt");

Path syntax can be bound to the underlying OS sematics but it might be better to use Environment Variables and Path instance methods to get info:

Path path = Paths.get("/home/username/log-file.log");
path.toString(); // /home/username/log-file.log
path.getFileName(); // log-file
path.getName(0); // /home
path.getNameCount(); // count of elements in path: 3
path.subpath(0,2); // /home/username
path.getParent(); // /home/username
path.getRoot(); // The root path: /
path.normalize(); // removes redundant indicators like . and .. from the path

Key takeaway: Path class contains methods for manipulating a path.

Converting a Path

Three methods to do so:

Path path1 = Paths.get("/home/users");
path.toAbsolutePath(path1); // /home/users
Path userInputPath = Paths.get(args[0]);
path.toAbsolutPath(userINputPath); // /root/pathOfExecutable/userInputPath

toRealPath(): Returns the real path of an existing file:

Note: Leverage NoSuchFileException to catch errors related to Path and Paths operations.

Join Paths using Paths.get("path_one").resolve("child_path").

Use Relativize method to return the path to a sibling path from a starting path:

Path p1 = Paths.get("foo");
Path p2 = Paths.get("bar");
Path p1_to_p2 = p1.relativize(p2); // ../bar
Path p2_to_p1 = p2.relativize(p1); // ../joe
// use this to avoid typing stuff like Paths.get("../../grandParentPath/baz")

Note: Relative paths might be a problem for Relativize depending on the root path and the OS.

Compare Two Paths using equals:

path.equals(otherPath); // returns boolean if path equals otherPath
path.startsWith(beginning); // returns boolean of Path root is equal to "beginning"
path.endsWith(ending); // returns boolean of Path last-child is equal to "ending"

Path implements:

File Operations

Files class is a java.nio.file package entrypoint.

Terminology:

Key Takeaways on Files class methods:

Globs Rules

Glob syntax rules:

Note: Command-line escape characters might differ between systems.

Consider Globs to be like RegEx syntax that java.nio.file understands.

Checking a File or Directory

Verify existence!

Three possible results:

File Accessibility:

Note: TOCTTOU (Tock-too) errors can occur when testing for Read, Write, and Executability and then accessing the file. Read about how to avoid this situation.

Two Paths Can Locate The Same File!

Files and Directories:

Symlinks:

Deletion Methods:

Copying Files and Directories

copy(Path, Path, CopyOption...)

CopyOption Enums/Varargs:

Example:

import static java.nio.file.StandardCopyOption.*;
Files.copy(source, target, REPLACE_EXISTING);

Note: Files.walkFileTree() method supports recursive copying.

Moving Files and Directories

move(Path, Path, CopyOption...): Same failure mode as Copying.

CopyOption Enums/Varargs:

Example:

import static java.nio.file.StandardCopyOption.*;
Files.move(source, target, REPLACE_EXISTING);

Managing Metadata

Filesystem metadata is stored IN files and directories.

AKA File Attributes.

See Oracle Java Docs File Attributes for methods that act of Filesystem Metadata.

Metadata is collected into Views so that effectively similar information can be displayed, whether POSIX or DOS style Filesystem:

Note: Not all views are supported by all systems, and some file systems support views NOT supported in java.nio.file libraries.

FileAttributeView interfaces can be accessed via getFileAttributeView(Path, Class<V>, LinkOption...) but in most cases is not necessary.

Some FileAttributes can be programmatically set. See Oracle Java Docs File Attributes for methods and usage.

Reading, Writing, Creating, and Opening Files

Utility Methods for simpler, common cases:

Iteration over a stream or lines of text and interop with java.io package (which includes Buffered Streaming capability):

More complex, less common methods:

Locking and memory-mapped IO required:

OpenOptions Parameter:

Methods for Unbuffered Streams and Interop with java.io APIs

Reading File with Stream IO:

Creating, Writing File with Stream IO:

Methods for Channels and ByteBuffers

Two methods for reading and writing channel IO:

These return an instance of a SeekableByteChannel that can be cast to a FileChannel which can allow mapping file contents to memory for faster access.

Key Takeaway: To seek to a specific location in a file and read it, use Files.newByteChannel and its returned instance of SeekableByteChannel to read/write from/to any position within a file.

See Oracle Docs File for more (there is a lot).

Methods for Creating Regular and Temporary Files

createFile is an atomic operation method.

There example code of how to create a file with default attributes using createFile().

For temporary files, use:

See Oracle Docs File for an example.

Random Access Files

Non-sequential access to file contents.

  1. Open
  2. Seek to location
  3. Read from (or write to) the file

SeekableByteChannel interface provides a Channel I-O.

SeekableByteChannel API Methods:

Path.newByteChannel methods return an instance of SeekableByteChannel.

SeekableByteChannel can be cast to FileChannel providing advanced features: Mapping a region to memory, locking a region of a file, and reading/writing bytes from an absolute position.

There is a code snippet that demonstrates the use of ByteBuffer, FileChannel, position, rewind, nread, and write.

Creating and Reading Directories

FileSystem Class contains a variety of methods for obtaining information about the file system.

List Directories:

Create a Directory:

Create a Temporary Directory:

Listing Directory Contents:

Be sure to use Try-with-resources because the returned DirectoryStream is a Stream.

Everything including files, links, subdirectories, and hidden files will all be returned unless Globbing is used:

DirectoryStream<Path> stream = Files.newDirectoryStream(dir, "*.{java,class,jar}")) etc.

Writing Directory Filters

Use DirectoryStream.Filter<T> interface, method accept():

Create Filter partial example: DirecotryStream.Filter<Path> filter = newDirectoryStream.Filter<Path>() { public boolean accept(Path file) throws IOException { ... }}

Invoke custom Filter partial example: try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir, filter)) { ... }

See Oracle's Java Documentation on Writing Your Own Directory Filter for the full example.

java.nio.file package supports links and behavior can be configured when a Symlink is discovered.

Notes about Hard Links:

Create a Symbolic Link:

Create a Hard Link:

Detecting a Symlink:

Finding Link Target:

Key Takeaway: Detect Symlinks by using java.nio.file package File instanace .isSymbolicLink(Path) for a boolean result.

Walking The File Tree

FileVisitor Interface:

FileVisitor Traversal Behavior Methods:

Review FileVisitor Interface documentation on Oracle JavaSE Docs for examples and more info.

Initiating a Traversal with these Methods and options enum:

FileVisitor Considerations:

Controlling the Flow of FileVisitor:

FileVisitor methods return a FileVisitResult value where you control the flow of the traversal:

Check out Controlling the Flow Code Snippets and Examples at page bottom at Oracle JavaSE Docs.

Finding Files

Use pattern matching to help, i.e. * for any, ? for single placeholder, etc.

File System implementations in java.nio.file provides a PathMatcher functionality.

PathMatcher accepts Glob or Regular Expressions as the the params to .getPathMatcher().

Steps to use:

  1. Create a PathMatcher instance.
  2. Match files against it.
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.{java, class});`
Path filename = ...;
if (matcher.matches(filename)) {
  System.out.println(filename);
}

Example java taken directly from the Finding Files section of [javase tutorials on essential io]

Recursive pattern matching is used to find a file somewhere within a file system.

Use Find with a glob pattern to search the file system for a file.

The Find class:

Watch a Directory for Changes

Utilize functionality called 'file change notification'.

Detection through file system changes is inefficient, so java.nio.file has an API for that:

There is also an OVERFLOW event, but registration is not required in order to receive it (probably an unchecked exception escape hatch).

Oracle recommends caution when deciding to implement their 'Watch Service API' referenced in the documentation:

Determining MIME Types

Use probeContentType(Path) to get a file's MIME Type:

try {
  String type = Files.probeContentType(filename);
  if(type==null) {
    System.err.format("%s has an unknown filetype.%n", filename);
  } else if (!type.equals("text/plain")) {
    System.err.format("%s is not a plain text file.%n", filename);
    continue;
  }
  catch (IOException ioex) {
    System.err.println(ioex);
  }
}

Return is either String or null (if cannot be determined).

Content Type is generally managed by the platform's type detector, so presumptive content-types like: Extension .class is a Java file, might not be correct.

Take a look at FileTypeDetector in the Oracle Java Docs. Note that it still 'guesses'.

Default File System

Use FileSystems.getDefault() method and chain it with FileSystem type methods. Example:

PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:*.*");

Path String Separator

Path separators are not always the same:

Retreive the path separator character using:

File System Stores

Files and Directories are actually stored within a File System's Stores.

File Store represents the underlying storage device:

List all file stores the system has:

for (FileStore store: FileSystems.getDefault().getFileStores()) {
  ...
}

Get a specific File Store:

Path file = ...;
FileStore store = Files.getFileStore(file);

Legacy File IO Code

Prior to Java SE 7:

Migrating to NIO

java.io.File.toPath() converts File instance to java.nio.file.Path instance: Path input = file.toPath();

There is a large matrix of functionality that relates java.io.File functionality to java.nio.file Functionality, along with links to tutorials on usage at Oracles Java Docs on File IO.

Oracle Tutorial Q and A

What Class and method would you use to read a few pieces of data that are at known positions near the end of a large file?

Use Files.newByteChannel to get instance of SeekableByteChannel which allows reading from/writing to any position in a file.

When invoking format, what is the best way to indicate a new line?

Use %nwhich is platform independant.

How would you determine the MIME type of a file?

Two options: Use Files.probeContentTypes(Path: file_name) (which uses the file systems underlying file type detector). Note: Oracle suggested an optional FileTypeDetector code file to try.

What method(s) would you use to determine whether a file is s symbolic link?

Use Files.isSymbolicLink(Path: file_name) method.

Note:

Notes from Baeldung Readings

Creating Streams

Examples:

// Stream of String (of Integer representation of Characters)
IntStream streamOfChars = "word".chars();
// use RegEx
Stream<String> streamOfString = Pattern.compile(", ").splitAsStream("X", "Y", "Z");
// Stream of File (of Strings)
Path path = Paths.get('C:\\example.txt');
Stream<String> streamOfStrings = Files.lines(path);
Stream<String> streamWithCharset = Files.lines(path, Charset.forName('UTF-8'));

[Java 8 Streams at Baeldung.com]

Referencing Streams

List<String> elements = Stream.of("X", "Y", "Z")
  .filter(element -> element
  .contains("Y"))
  .collect(Collectors.toList());
Optional<String> anyElement = elements.stream().findAny();
Option<String> firstElement = elements.stream().findFirst();

Stream Pipelines

Sequence of operations for using Streams:

  1. Source: Where the data will be streamed from, e.g. an Array.
  2. Intermediate Operations: Return a new, modified Stream. Can be chained. skip(), map(), and others.
  3. Terminal Operation: Only one is allowed per Stream. Returns a result of operations done on the Stream.
List<String> list = Arrays.asList("alpha", "bravo", "charlie");
long size = list.stream().skip(1).map(item -> item.substring(0, 3)).sorted().count()

TODO:

Lazy Invokation

Order of Execution

Stream Reduction

Reduce Method

Variations:

OptionalInt reduced = IntStream.range(1, 4).reduce((a, b) -> a + b);
// 1+2, 3+3 returns 6
int reducedTwoparams = intStream.range(1, 4).reduce(10, (a, b) -> a + b);
// 10 + 1 + 2, 13 + 3 returns 16
int reducedParams = Stream.of(1, 2, 3)
  .reduce(10, (a, b) -> a + b, (a, b) -> {
    log.info("combiner was called");
    return a + b;
  });
  // combiner will not be called in non-parallel reducers
  // however result is still 16
int reducedParallel = Arrays.asList(1, 2, 3)
  .parallelStream()
  .reduce(10, (a, b) -> a + b, (a, b) -> {
    log.info("combiner was called");
    return a + b;
  });
// combiner called twice and result is 36
// each combiner processing happens in parallel
// 10 + 1 and 10 + 2 and 10 + 3
// so when accumulated is 11, 12, and 13
// and when combined is 11 + 12 + 13 result 36

[Java 8 Streams at Baeldung.com]

The Collect Method

Steps:

  1. Start with a List<T> and init as Arrays.asList(new {type(value)}, new {type(vlaue)}) to init or add elements of 'type' into a List.
  2. Convert to a Collection, List, or Set using .stream().map(MyType::getName).collect(Collectors.toList());.
  3. Operate on the Collection using .collect(Collectors.command(args)).

Tools:

Custom Collectors can be created using of() method of type Collector:

Collector<MyType , ?, LinkedList<MyType>> toLinkedList = Collector.of(LinkedList::new, LinkedList::add, (first, second) -> {
  first.addAll(second);
  return first;
});
LinkedList<MyType> linkedListOfMythings = listOfMyThings.stream().collect(toLinkedList);
// Collector instance gets reduced to the LinkedList, first element?

Parallel Streams

Java 8 makes things simpler with:

Create parallel Streams when source is a Collection or an Array type, using parallelStream() method:

Stream<MyType> streamOfCollection = myTypes.parallelStream();
boolean isParallel = streamOfCollection.isParallel();
boolean bigInstance = streamOfCollection
  .map(myTypeItem -> myTypeItem.getValue() * someValue)
  .anyMatch(thisValue -> thisValue > 200);

Create parallel Streams using parallel() when source is not a Collection or an Array type:

IntStream intStreamParallel = IntStream.range(1, 100).parallel();
boolean isParallel = intStreamParallel.isParallel();

Notes:

Convert Parallel Mode Stream back to sequential mode using sequential():

IntStream intStreamSequential = intStreamParallel.sequential();
boolean isParallel = intStreamSequential.isParallel();

[Java 8 Streams at Baeldung.com]

Memory Leaks

Always apply close() to terminal operations to avoid memory leaks.

Unconsumed Streams will create memory leaks.

Stream FindAny and FindFirst

See Baeldung Streams FindAny and FindFirst

Stream FindAny

Find any element within a Stream.

Stream FindFirst

Find the first element in a Stream.

Streams Functional Interfaces in Java 8

See Baeldung Java8 Streams Functional Interfaces

Baeldung Lambda Expressions and Functional Interfaces Tips and Best Practices.

Lambda Expressions

Functional Interfaces

Note: Default Methods do not count as Abstract.

Functions

Simple description: Interface with single method takes single value and returns single value public interface Function<T, R> { ... }.

Example of Standard Library Function Type: Map.computeIfAbsent()

// for a HashMap<String, Integer> myHashMap...
myHashMap.computeIfAbsent(string, (item) -> item.length);
// computeIfAbsent looks for key String and if not found inserts String into map with value item.length

// another way to look at it:
myHashMap.computeIfAbsent(string key, function( (string item) -> return new int(item.length)) );

// breaking it down to a simple implementation:
string foundValueOrNull = computeIfAbsent(string, {calculateValue(arg)});

Converting to a functional interface:

myHashMap.computeIfAbsent(string, String::length);
// consider the pair of colons to imply this is a lambda function

The Built-in Compose Method

Function Interface has default compose() method.

How to:

  1. Define a Function that uses Functional Interface ::.
  2. Define a Function that is a lambda that uses the addition operator to concatenate quoted strings and primitives that have default toString() method.
  3. Define a Function that is the result of calling Function2 with the compose function, passing in Function1: Function2.compose(Function1).

Very abstract. Here's [The example from Baeldung.com] with comments added by me:

// function uses Functional Interface and stores an Integer, returns a String
Function<Integer, String> intToString = Object::toString;
// function uses lambda and stores String input, returns a String output
Function<String, String> quote = s -> "'" + s + "'";
// function calls intToString, passing in quote and executes its built-in compose() method
Function<Integer, String> quoteIntToString = quote.compose(intToString);
// using the compose in-line to print to the console without creating quoteIntToString
System.out.println(quote.compose(intToString).apply(5));
// prints '5' to the console

Primitive Function Specializations

Primitive Types cannot be a generic type argument.

Function Interface has ability to use double, int, long, and combinations thereof.

Returning another Primitive Type from a Primitive Function can be achieved using a Functional Interface decorator/attribute, and implementing a custom function.

Example from [Baeldung.com] with my comments added:

@FunctionalInterface
public interface ShortToByteFunction {
  // a single method in the functional interface takes a single value and returns a single value
  byte applyAsByte(short s);
}

// allow passing in a Lambda Expression using ShortToByteFunction interface
public byte[] transformArray(short[] array, ShortToByteFunction function) {
  byte[] transformedArray = new byte[array.length];
  // step through each element in input array and apply the custom function interface method
  for (int i = 0; i < array.length; i++) {
    transformedArray[i] = function.applyAsByte(array[i]);
  }
  return transformedArray;
}
// transform an array of shorts to an array of bytes multiplied by 2
short[] array = {(short) 1, (short) 2, (short) 3};
byte[] transformedArray = transformedArray(array, s -> (byte) (s * 2));
byte[] expectedArray = {(byte) 2, (byte) 4, (byte) 6};
assertArrayEquals(expectedArray, transformedArray);

This is an excellent example of why an Interface is important in object oriented programming. If any function was passed in but did not have the .applyAsByte(short s) method applied, the code would not compile.

Two-Arity Function Specializations

Two-Arity Function Specializations at Baeldung.com

Define lambdas with 2 input args using library functions with "Bi" in the name.

When using a Map function (or Map-like for e.g. HashMap.ReplaceAll()), a Two-Arity function can allow processing 2 values and returning a single result that the ReplaceAll() function proceses to elements in the HashMap.

Suppliers

A Function Specialization that takes zero arguments.

Commonly used for lazy-generation or values.

Returns a Supplier type that can then be treated as a Stream object, and the 'state' values are treated as final.

Available Suppliers:

Consumers

A 'void' type Function that accepts Generic Inputs.

Useful for generating side effects e.g. Console output.

Built-in Consumer functions:

Predicates

Function that accepts one (Generic Type) value, and returns a Boolean.

Using a lambda like .filter(n -> f(x)) within chained Stream functions provides a predicate (true/false) result that following chained functions that would operate when the predicate returns true.

Variations that accept primitive arguments:

Operators

Functions that receive and return the same value type.

Unary Operators ++ and -- are examples.

From W3Schools, with comments added by me:

public class unaryop {
 public static void main(String[] args) {
  int r = 6;
  // prints 6, stores 7 into variable r
  System.out.println("r=: " + r++);

  // print 7
  System.out.println("r=: " + r);

  int x = 6;
  // prints 6, stores 5 into variable x
  System.out.println("x=: " + x--);

  // prints 5
  System.out.println("x=: " + x);

  int y = 6;
  // stores 7 into variable y then prints 7
  System.out.println("y=: " + ++y);

  int p = 6;
  // stores 5 into variable p then prints 5
  System.out.println("p=: " + --p);
 }
}

Legacy Functional Interfaces

Generally speaking:

Stream Collectors

Used as a final step in processing a Stream.

Useful in parallel processing.

Stream.collect()

Stream API terminal method.

Used to perform operations on mutable elements.

Processing could be many types of logic including concatenation, grouping, etc.

Collectors

Import static collectors from java.util.stream.Collectors.*.

Singly-import specific Collectors e.g. java.util.stream.Collectors.toList.

Collectors toList and toUnmodifiableList functions:

List<Integer> listResult = temperatures.stream().collect(toList());
List<Integer> unmodifiableListResult = temperatures.stream().collect(toUnmodifiableList());

See Oracle Java Docs: List Interface, unmodifiableList.

Collectors toSet and toUnmodifiableSet functions:

Collectors toCollection function:

Collectors toMap and toUnmodifiableMap functions:

Map<String, Integer> temperatures = temps.stream().collect(toMap(Function.identity(). String::length));
Map<String, Integer> tempsWithDuplicates = temps
  .stream()
  .collect(toMap(Function.identity(), String::length,
    (item, identicalItem) -> item // binary operator
));

Collectors collectingAndThen() function:

Collectors joining function:

Collectors counting function:

Collectors summarizingDouble summarizingLong summarizingInt functions:

Collectors averagingDouble averagingLong averagingInt functions:

Collectors summingDouble summingLong summingInt functions:

Collectors maxBy and minBy functions:

Collectors groupingBy function:

Collectors partitioningBy function:

Collectors teeing function:

Custom Collectors

Implement the Collector interface: public interface Collector<T, A, R> { ... }.

  1. Implement a class that will be used as the Collector.
  2. Use implements Collector<T, A, R> where A and R are defined like ImmutableSet.Builder<T> and ImmutableSet<T> respectively (as an example).
  3. Implement the supplier(), accumulator(), combiner(), finisher() and characteristics() functions, using @Override annotation.
  4. Instantiate the custom Collector class and use it to capture the stream collect toYourCustomCollector output.

characteristics() function:

Key Takeaways

Important: Operate on a Collection not the Stream itself:

List<String> elements = Stream
  .of("X", "Y", "Z")
  .filter(element -> element.contains("Y"))
  .collect(Collectors.toList());
Optional<String> anyElement = elements.stream().findAny();
Optional<String> firstElement = elements.stream().findFirst();

Resources

Oracle Java Tutorials.

Baeldung Java Streams Links and Information.

Baeldung Java IO Tutorials (mostly about file system manipulation).

Baeldung GitHub Repo of Lambda and FunctionInterface examples

Return to ContEd Index.

Return to Root README.