Useful methods, the Standard Library, and streams
A library is a collection of related modules that provides reusable functionality to simplify development. The Java Standard Library is a collection of pre-written classes and APIs that provide essential functionality for Java development, such as data structures, networking, file I/O, and utilities and is bundled with the Java Development Kit (JDK). In this lesson, we will use the built-in classes from this library. We import these classes using the import statement, e.g., import java.util.Random;.
Table of contents
- The
Randomclass - Streams
- The
Mathclass - The
CalendarandDateclasses - The
Arraysclass - The
Systemclass - The
Stringclass - The
Characterclass - The
StringBuilderclass - Converting between strings and numbers
The Random class
The Random class is used to generate random numbers. The numbers generated using this class are pseudorandom, which means they are drawn according to a certain pattern. The initial value used by the generator is called a seed. If we know the value of the seed, we can predict the rest of the numbers produced by the generator because, for a given seed, the same numbers will always be generated (in the same order). The seed can be set with the randomObject.seed(value) command.
import java.util.Random;
public class Main {
public static void main(String[] args) {
Random rand1 = new Random();
long seed = (long) 3.142;
Random rand2 = new Random(seed);
long newseed = (long) 2.7182;
rand2.setSeed(newseed); // changing the seed
int i = rand1.nextInt(); // drawing integers without a range
double d = rand1.nextDouble(); // drawing double numbers without a range
System.out.println("Random integer: " + i);
System.out.println("Random double: " + d);
// Drawing integers (we determine the range in the arguments - start, stop)
i = rand1.nextInt(5, 25); // (first value - inclusive, last value - exclusive)
System.out.println("Random integer in range [5, 25]: " + i);
i = rand1.nextInt(25); // 0 is the default first value
System.out.println("Random integer in range [0, 25]: " + i);
}
}
Streams
Streams provide a way to process data sequences, including collections and arrays, in a functional style. The Stream API enables us to perform operations such as filtering, mapping, and reducing datasets concisely. By using streams, we can efficiently handle large volumes of data. Additionally, streams support lazy evaluation, meaning values are generated on the fly as they are consumed, leading to performance optimizations when not all values are needed simultaneously (we do not need to store the whole sequence).
Creating streams
Streams can be created from many sources. The most common way is from a collection using .stream(), but we can also create them from arrays, individual values, or generate them programmatically.
// Creating from a List
List<String> names = List.of("Alice", "Bob", "Charlie");
Stream<String> nameStream = names.stream();
// Creating from individual values
Stream<Integer> numStream = Stream.of(1, 2, 3, 4, 5);
// Creating from an array
String[] arr = {"x", "y", "z"};
Stream<String> arrStream = Arrays.stream(arr);
// Infinite stream generated programmatically
Stream<Integer> generated = Stream.iterate(0, n -> n + 2).limit(5); // 0, 2, 4, 6, 8
// Flattening a list of lists
List<List<String>> allSources = List.of(List.of("Alice", "Bob"), List.of("Charlie", "Dave"), List.of("Eve", "Frank"));
List<String> allListsInOne = new ArrayList<>();
allSources.forEach(allListsInOne::addAll);
Random streams
The Random class offers methods like doubles(), ints(), and longs() that return streams of random values of the specified type. They can be customized with parameters to define a range, enabling the generation of random numbers within specific bounds. Because streams use lazy evaluation, random numbers are generated on the fly as they are consumed, so we do not need to store the whole sequence in memory. Here's how we can create a stream of random doubles:
Random rand1 = new Random();
DoubleStream stream = rand1.doubles(0, 10).limit(10); // 10 double numbers from the range 0-10
stream.forEach(System.out::println); // displaying all stream values
The map() method
The map() method takes a function as a parameter. It applies the given function to each element in the stream and returns a new stream with the transformed results.
List<String> names = List.of("alice", "bob", "charlie");
names.stream()
.map(String::toUpperCase) // transforming each name to uppercase
.forEach(System.out::println);
The filter() method
The filter() method takes a predicate (a function returning a boolean) as a parameter. It applies the given predicate to each element in the stream and returns a new stream containing only those elements for which the function returned true.
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
numbers.stream()
.filter(n -> n % 2 == 0) // keeping only even numbers
.forEach(System.out::println);
The reduce() method
The reduce() method takes an identity value and an accumulator function as parameters. It applies a rolling computation to the elements of the stream, combining them one by one into a single result.
The identity value is the initial value for the reduction - it's the starting point for the accumulation. For example, if we're summing numbers, the identity would be 0, and if we're multiplying, it would be 1. It's also the value returned if the stream is empty.
List<Integer> numbers = List.of(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce(0, (a, b) -> a + b);
// Using a method reference
int product = numbers.stream().reduce(1, Integer::multiply);
The collect() method
The collect() method is a terminal operation that gathers the stream elements into a collection or other data structure. It is used with the Collectors utility class, which provides ready-made collectors for lists, sets, maps, and more.
List<String> names = List.of("Alice", "Bob", "Charlie", "Anna");
// Collecting to a List with an additional condition
List<String> filtered = names.stream().filter(n -> n.startsWith("A")).collect(Collectors.toList()); // [Alice, Anna]
// Collecting to a Set
Set<String> nameSet = names.stream().collect(Collectors.toSet());
// Joining into a single String
String joined = names.stream().collect(Collectors.joining(", ")); // "Alice, Bob, Charlie, Anna"
The sorted() and distinct() methods
The sorted() method returns a stream with elements in natural order (or by a custom comparator), while the distinct() method removes duplicate elements. Both are intermediate operations.
List<Integer> nums = List.of(5, 3, 1, 4, 1, 2, 3);
nums.stream().distinct().sorted().forEach(System.out::println);
// Custom comparator - sorting strings by length
names.stream().sorted(Comparator.comparingInt(String::length)).forEach(System.out::println);
Chaining operations
The real power of streams comes from chaining multiple intermediate operations together before a terminal operation. Each intermediate operation returns a new stream, so they can be composed into a clean, readable pipeline.
List<String> names = List.of("Alice", "Bob", "Charlie", "Anna", "Brian");
List<String> result = names.stream()
.filter(n -> n.length() > 3) // keep names longer than 3 chars
.map(String::toUpperCase) // convert to uppercase
.sorted() // sort alphabetically
.collect(Collectors.toList()); // [ALICE, BRIAN, CHARLIE]
The Math class
The Math class provides more complex mathematical operations.
import java.lang.Math;
public class Main {
public static void main(String[] args) {
int x = 16;
double y = 3.14;
System.out.println(Math.ceil(y)); // rounding up a number
System.out.println(Math.floor(y)); // rounding down a number
System.out.println(Math.sqrt(x)); // calculating the square root of a number
System.out.println(Math.pow(x, 2)); // raising a number to a given power
System.out.println(Math.log(y)); // calculating the natural logarithm of y (base e)
System.out.println(Math.PI); // displaying the PI number
System.out.println(Math.E); // displaying the E number
}
}
The Calendar and Date classes
These classes are used to handle time. Creating and instance of the Date class will assign the current date and time to a variable: final Date date = new Date();.
import java.util.Calendar;
import java.util.Date;
public class Main {
public static void main(String[] args) {
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.YEAR, 2023);
calendar.set(Calendar.MONTH, 0); // January
calendar.set(Calendar.DATE, 8);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
Date date = calendar.getTime(); // converting the calendar instance to a Date object
System.out.println(date);
}
}
The Arrays class
The Arrays class allows us to, e.g., sort arrays.
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] numbers = new int[] {5, 2, 8, 1, 3};
Arrays.sort(numbers); // sorting an array
for (int num : numbers)
System.out.println(num);
}
}
The System class
The System class is used to handle system operations.
import java.lang.System;
public class Main {
public static void main(String[] args) {
long currentTime = System.currentTimeMillis(); // getting the current time in milliseconds
System.out.println("Current time in milliseconds: " + currentTime);
String userHome = System.getenv("HOME"); // getting the environment variables
System.out.println("User's home directory: " + userHome);
System.exit(0); // ending the program
}
}
The String class
The String class is used to manipulate strings.
import java.lang.String;
public class Main {
public static void main(String[] args) {
String str = "Hello World!";
// Checking the number of characters in a string (its length)
System.out.println("String length: " + str.length());
// Returning a part of a string (a substring)
System.out.println("Substring: " + str.substring(0, 5)); // "Hello" (starting index - inclusive, stopping index - exclusive)
// Lowercase to uppercase
System.out.println("Uppercase: " + str.toUpperCase());
// Uppercase to lowercase
System.out.println("Lowercase: " + str.toLowerCase());
// Comparing strings
String str2 = "Hello World!";
System.out.println("Strings are equal: " + str.equals(str2));
// Breaking a string into an array by dividing it every time a given delimiter is encountered
String[] words = str.split(" "); // the space is the delimiter (we can choose a different one)
for (String word: words)
System.out.println(word);
// We can limit the number of splits by passing a second argument, e.g., split(" ", 2). This ensures that only the first space splits the string while the rest remains intact.
// Joining the elements of an array into a single string by inserting a delimiter between each element
String joined = String.join(" ", words);
System.out.println(joined);
// Checking if a string contains a specified substring
boolean result = str.contains("World");
System.out.println(result);
// Finding the position of the first occurrence of a substring in a string (the index of the substring's first letter). If there are more than one - the index of the first one encountered.
int pos = str.indexOf("World");
if (pos != -1) // checking if the substring was found
System.out.println("Found at position: " + pos);
else
System.out.println("Not found");
}
}
The Character class
The Character class provides static methods to work with individual characters.
public class Main {
public static void main(String[] args) {
char c1 = '5';
char c2 = 'A';
char c3 = ' ';
System.out.println(c1 + " is digit? " + Character.isDigit(c1)); // checking if a character is a digit
System.out.println(c2 + " is letter? " + Character.isLetter(c2)); // checking if a character is a letter
System.out.println("'" + c3 + "' is whitespace? " + Character.isWhitespace(c3)); // checking if a character is whitespace
}
}
The StringBuilder class
The StringBuilder class is used for creating and modifying mutable strings. Unlike String, which creates a new object on every modification, it allows for more efficient string manipulation.
import java.lang.StringBuilder;
public class Main {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hello");
// Appending to the string
sb.append(" World!");
System.out.println(sb); // "Hello World!"
// Inserting into the string
sb.insert(5, ",");
System.out.println(sb); // "Hello, World!"
// Reversing the string
sb.reverse();
System.out.println(sb); // "!dlrow ,olleH"
}
}
Converting between strings and numbers
String str = "15";
// String to integer
int i = Integer.parseInt(str);
System.out.println(i);
// String to float
float f = Float.parseFloat(str);
System.out.println(f);
// Integer to string
Integer i2 = 15; // the type has to be an object (even our class if it overrides the toString() method)
String str1 = i2.toString(); // if i2 is null, it would throw a NullPointerException (an error)
System.out.println(str1);
// Integer to string
int i3 = 15; // the type has to be primitive
String str2 = String.valueOf(i3); // a primitive type can't be null
System.out.println(str2);