Use cases for immutable collections:
- If data should not be modified, defensively copy it into an immutable collection
- When collections are passed to untrusted libraries, immutable forms provide safety
List<String> list = List.of("Zhang San", "Li Si", "Wang Wu", "Zhao Liu");
Map<String, String> map = Map.of("Zhang San", "Li Si", "Wang Wu", "Zhao Liu");
// Notes:
// Keys cannot be duplicated
// Map's of method has parameter limits, maximum 20 parameters (10 key-value pairs)
// For more than 10, use ofEntries method
Map<Object, Object> map = Map.ofEntries(hm.entrySet().toArray(new Map.Entry[0]));
Map<String, String> map = Map.copyOf(hm); // JDK 10+
Stream API
list1.stream().filter(name->name.startsWith("Zhang")).filter(name -> name.length() == 3).forEach(name-> System.out.println(name));
Stream API benefits:
- Combines with Lambda expressions to simplify operations on collections and arrays
- Usage steps:
- Obtain a Stream pipeline with data
- Apply various operations through Stream API methods
Intermediate methods: filtering, transformation
Terminal methods: counting, printing
// 1. Single-column collection to obtain Stream
ArrayList<String> list = new ArrayList<>();
Collections.addAll(list,"a","b","c","d","e");
Stream<String> stream1 = list.stream();
list.stream().forEach(s -> System.out.println(s));
// 2. Two-column collection
HashMap<String,Integer> hm = new HashMap<>();
// Option 1:
hm.keySet().stream().forEach(s -> System.out.println(s));
// Option 2:
hm.entrySet().stream().forEach(s-> System.out.println(s));
// Array
int[] arr = {1,2,3,4,5,6,7,8,9,10};
Arrays.stream(arr).forEach(s-> System.out.println(s));
// Discrete data
Stream.of(1,2,3,4,5).forEach(s-> System.out.println(s));
Stream.of("a","b","c","d","e").forEach(s-> System.out.println(s));
// Note:
// Static method of Stream interface 'of' details
// Method parameter is varargs, can pass multiple discrete data or array
// But array must be reference type, if basic types are passed, entire array becomes one element in Stream
int[] arr1 = {1,2,3,4,5,6,7,8,9,10};
Stream.of(arr1).forEach(s-> System.out.println(s));
Intermediate Methods
Note 1: Intermediate methods return new Stream objects, original Stream can only be used once, recommend chain programming
Note 2: Modifying data in Stream does not affect original collection or array
list.stream()
.filter(s -> s.startsWith("Zhang"))
.filter(s -> s.length() == 3)
.forEach(s -> System.out.println(s));
list.stream()
.limit(3)
.forEach(s -> System.out.println(s)); // First three elements
list.stream()
.skip(4)
.forEach(s -> System.out.println(s)); // Skip first four
list.stream()
.limit(6)
.skip(4)
.forEach(s -> System.out.println(s)); // Output elements 4, 5, 6
list.stream()
.distinct()
.forEach(s -> System.out.println(s)); // Deduplication
Stream.concat(list1.stream(),list2.stream())
.forEach(s -> System.out.println(s)); // Merge
// First type: Original data type in stream
// Second type: Type after conversion
list.stream()
.map(s-> Integer.parseInt(s.split("-")[1]))
.forEach(s-> System.out.println(s));
Terminal Methods
list.stream().forEach(s -> System.out.println(s));
long count = list.stream().count(); // Count
String[] arr2 = list.stream().toArray(value -> new String[value]);
System.out.println(Arrays.toString(arr2)); // Collect to array
// Collect to List
// Collect all males
List<String> newList = list.stream()
.filter(s -> "male".equals(s.split("-")[1]))
.collect(Collectors.toList());
// Collect to Set
// Remove duplicates
Set<String> newList2 = list.stream()
.filter(s -> "male".equals(s.split(regex: "-")[1]))
.collect(Collectors.toSet());
// Collect to Map
Map<String, Integer> map2 = list.stream()
.filter(s ->"male".equals(s.split(regex: "-")[1]))
.collect(Collectors.toMap(
s -> s.split("-")[0],
s -> Integer.parseInt(s.split("-")[2])));
Practice Exercises
Collections.addAll(list,1, 2, 3, 4, 5, 6,7, 8, 9, 10);
// Filter odd numbers, keep even numbers
// If even number, return true to retain
List<Integer> evenList = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
Method References
Reusing existing methods as functional interface abstract method bodies
:: Method reference operator
Requirements:
- Reference must be within a functional interface
- Referenced method must exist
- Parameter and return types of referenced method must match abstract method
- Referenced method functionality must meet current needs
// Can be built-in Java methods or third-party utility classes
// Reference subtraction method in FunctionDemo1 class
// Use this method as abstract method body
Arrays.sort(arr, FunctionDemo1::subtraction);
Static Method References
Format: Class::static method
list.stream().map(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
int i = Integer.parseInt(s);
return i;
}
}).forEach(s -> System.out.println(s));
list.stream().map(Integer::parseInt).forEach(s-> System.out.println(s));
Instance Method References
Format: object::instance method
- Other classes: other object::method name
- This class: this::method name
- Parent class: super::method name
// Other class
public class StringOperation{
public boolean stringJudge(String s){
return s.startsWith("Zhang") && s.length() == 3;
}
}
StringOperation so = new StringOperation();
list.stream().filter(so::stringJudge)
.forEach(s-> System.out.println(s));
// This class, requires usage within non-static method using this, otherwise
list.stream().filter(new FunctionDemo3()::stringJudge)
.forEach(s -> System.out.println(s));
list.stream().filter(this::stringJudge);
// Parent class
list.stream().filter(super::stringJudge);
When referencing instance methods of parent or this class, reference location cannot be static method
Constructor References
Format: Class::new
// Wrap in Student object and collect to List
list.stream().map(Student::new).collect(Collectors.toList());
Class Instance Method References
Format: Class::instance method
/* Abstract method parameter explanation:
First parameter: indicates the caller of referenced method, determines which class methods can be referenced
In stream flow, first parameter generally represents each data item in stream. If stream data is String, method reference can only reference methods in String class
Second parameter to last parameter: matches referenced method parameters. If no second parameter, referenced method should be parameterless instance method
Limitation:
Cannot reference instance methods from all classes.
Depends on first parameter of abstract method. What type the first parameter is, only methods from that class can be referenced.
*/
// map(String::toUpperCase)
// Take each data item from stream and call String class's toUpperCase method, return value is converted result
list.stream().map(String::toUpperCase).forEach(s -> System.out.println(s));
Array Constructor References
Format: data type[]::new
// 1. Create collection and add elements
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list, 1, 2, 3, 4, 5);
// 2. Collect to array
Integer[] arr = list.stream().toArray(new IntFunction<Integer[]>() {
@Override
public Integer[] apply(int value) {
return new Integer[value];
}
});
// Rewrite as below
// Detail:
// Array type must match data type in stream
Integer[] arr2 = list.stream().toArray(Integer[]::new);
Techniques:
- Is there already a method that fits current needs?
- If such method exists, does it satisfy reference rules?
- Static class name::method name
- Instance method
- Constructor Class::new
Exceptions
An exception represents a problem in the program
Error:
Represents system-level errors (serious problems). When system issues occur, Sun will encapsulate these errors into Error objects
Exception:
Called exceptions, represent potential program issues. We typically use Exception and its subclasses to encapsulate program issues
Runtime exceptions: RuntimeException and its subclasses, compile-time won't show exceptions, warnings appear at runtime (like: array index out of bounds)
Compile-time exceptions: Show exceptions during compilation phase (like: date parsing exception)
Compile-time vs Runtime Exceptions
// Compile-time exception
String time ="2023年1月1日";
SimpleDateFormat sdf = new SimpleDateFormat(pattern: "yyyy年MM月dd日");
Date date = sdf.parse(time); // parse error, no throws ParseException
System.out.println(date);
// Runtime exception, usually due to parameter passing errors
int[] arr = {1,2,3,4, 5};
System.out.println(arr[1e]);
Exception Purpose
/*
Exception purpose one: Exception serves as key reference information for bug investigation
Exception purpose two: Exception can act as special return value inside method, notify caller about execution status
*/
// Throw exception
throw new RuntimeException();
Handling Exceptions
JVM Default Handling
- Outputs exception name, cause and location info to console
- Stops program execution, code below exception won't run
Catching Exceptions
try {
Code that may cause exception;
} catch(ExceptionType variableName) {
Exception handling code;
}
// Benefit: allows program to continue running
try{
System.out.println(arr[10]);
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("Index out of bounds");
}
If no problem occurs in try block, how is it executed?
- All code in try executes completely, catch code won't execute
- Note: Only executes catch code when exception occurs
If try may encounter multiple issues, how to handle?
- Write multiple catch blocks accordingly
- If multiple exceptions have parent-child relationship, parent class must be placed below
- Since JDK 7, multiple exceptions can be caught in one catch using | separator for same handling
catch(ArrayIndexOutOfBoundsException ArithmeticException e)
try{
System.out.println(arr[10]);// ArrayIndexOutOfBoundsException
System.out.println(2/0);// ArithmeticException
String s = null;
System.out.println(s.equals("abc"));
}catch(ArrayIndexOutOfBoundsException e){
System.out.println("Index out of bounds");
}catch(ArithmeticException e){
System.out.println("Divide by zero");
}catch(NullPointerException e){
System.out.println("Null pointer exception");
}
System.out.println("Check if I executed?");
If exception in try isn't caught, how to proceed?
try{
System.out.println(arr[10]);// ArrayIndexOutofBoundsException
}catch(NullPointerException e){
System.out.println("Null pointer exception");
}
// Equivalent to writing try...catch code in vain, ultimately handled by virtual machine
If exception occurs in try, will code below still execute?
- Code below won't execute, jumps directly to coresponding catch to execute catch statement
- If no matching catch, still handed over to VM
Common Exception Methods
public void printStackTrace()
Outputs exception error info to console
// Returns detailed message string of throwable
String message = e.getMessage();
System.out.println(message);
// Index 1 out of bounds for length 6
// Returns brief description of throwable
String str = e.toString();
System.out.println(str);
// java.lang.ArrayIndexOutOfBoundsException: Index 19 out of bounds for length
e.printStackTrace();
// Only prints info, doesn't stop program
Throwing and Handling
In method, if exception occurs,
Method loses meaning of continuing execution, take throwing approach.
Let method stop and inform caller about issue.
throws
Note: Written at method definition, declares an exception to tell caller what exceptions might occur when using this method
- Compile-time exception: Must write
- Runtime exception: Optional
public void method()throws ExceptionType1,ExceptionType2...
throw
Note: Written inside method, ends method manually throws exception object, passes to calling method, code below won't execute
public void method(){
throw new NullPointerException();
}
Practice
while (true){
try{
System.out.println("Enter your favorite girl friend's name");
String name = sc.nextLine();
gf.setName(name);
System.out.println("Enter your favorite girl friend's age");
String ageStr = sc.nextLine();
int age = Integer.parseInt(ageStr);
gf.setAge(age);
// If all data correct, break loop
break;
}catch (NumberFormatException e){
System.out.println("Age format incorrect, please enter number");
}catch (RuntimeException e){
System.out.println("Name length or age range incorrect");
}
}
System.out.println(gf);
JavaBean class:
public void setName(String name){
int len = name.length();
if(len < 3 || len > 10){
throw new RuntimeException();
}
this.name = name;
}
getAge also rewritten
Custom Exception
- Define exception class
- Write inheritance relationship
- Empty constructor
- Constructor with parameters
Purpose: Make console error messages clearer
// NameFormatException.java
public class NameFormatException extends RuntimeException{
public NameFormatException() {
}
public NameFormatException(String message){
super(message);
}
}
File
File object represents a path, can be file path or directory path
Path can exist or not exist
Create File object based on file path
public File(String pathname)
Create File object based on parent path and child path
public File(String parent, String child)
Create File object based on parent File and child path
public File(File parent, String child)
// 1. Convert string path to File object
String str ="C:\\Users\\alienware\\Desktop\\a.txt";
File f1 = new File(str);
System.out.println(f1);//C:\Users\ alienware\Desktop\a.txt
// 2. Parent path: C:\Users\alienware\Desktop, Child path: a.txt
String parent ="C:\\Users\\alienware\\Desktop";
String child = "a.txt";
File f2 = new File(parent,child);
System.out.println(f2);//C:\Users \alienware\Desktop\a.txt
// Or concatenate manually, less used, linux: /
File f3 = new File(pathname: parent + "\\"+ child);
// 3. Concatenate File path with string path
File parent2 = new File(pathname: "C:\\Users\\alienware\\Desktop");
String child2 = "a.txt";
File f4 = new File(parent2,child2);
System.out.println(f4);
File Methods
public boolean isDirectory()
public boolean isFile()
public boolean exists()
public long length()
public String getAbsolutePath()
public string getPath()
public String getName()
public long lastModified()
/*
Check if File path represents a directory
Check if File path represents a file
Check if File path exists
Return file size (byte count)
Return absolute path of file
Return path used when defining file
Return file name with extension
Return last modification time (milliseconds)
*/
FileDemo.java
// 1. Check file path
File f1 = new File(pathname: "D:\\aaa\\a.txt");
System.out.println(f1.isDirectory());//false
System.out.println(f1.isFile());//true
System.out.println(f1.exists());//true
Retrieval
// 1. length returns file size (byte count)
// Detail 1: This method only gets file size, unit is bytes
// If we want M, G, continuously divide by 1024
// Detail 2: Cannot get folder size
// To get folder size, sum all file sizes in folder
File f1 = new File(pathname:"D:\\aaa\\a.txt");
long len = f1.length();
System.out.println(len);//12
File f2 = new File(pathname: "D:\\aaa\\bbb");
long len2 = f2.length();
System.out.println(len2);//0
// 2. getAbsolutePath returns absolute path of file
File f4 = new File("myFile\\a.txt");
// Current project
// No myFile means direct project
String path2 = f4.getAbsolutePath();
System.out.println(path2);//Absolute path
// 3. getPath returns defined path of file
File f5 = new File(pathname: "D:\\aaa\\a.txt");
String path3 = f5.getPath();
System.out.println(path3);//D:\aaa\a.txt
File f6 = new File("myFile\\a.txt");
String path4 = f6.getPath();
System.out.println(path4);//myFile\.txt
// 4. getName gets name
File f7 = new File(pathname:"D:\\aaa\\a.txt");
String name1 = f7.getName();
System.out.println(name1);
File f8 = new File("D:\\aaa\\bbb");
String name2 = f8.getName();
System.out.println(name2);//bbb
// 5. lastModified returns last modification time (milliseconds)
File f9 = new File("D:\\aaa\\a.txt");
long time = f9.lastModified();
System.out.println(time);//1667380952425
Creation and Deletion
// delete method only deletes files and empty folders, no recycle bin
// 1. createNewFile creates a new empty file
File f1 = new File("D:\\aaa\\c.txt");
boolean b = f1.createNewFile();
System.out.println(b);
// Detail 1: c.txt exists, won't error, shows creation failed
// Detail 2: If parent path doesn't exist, throws IOException
// Detail 3: createNewFile always creates file, if no suffix, creates file without extension
// 2. mkdir makes directory (folder)
// Detail 1: Windows paths are unique, if path exists, creation fails, returns false
// Detail 2: mkdir can only create single-level folders, not multi-level folders
File f2 = new File("D:\\aaa\\bbb");
boolean b = f2.mkdir();
System.out.println(b);
// 3. mkdirs creates multi-level folders
// Detail: Creates both single and multi-level folders
File f3 = new File("D:\\aaa\\aaa\\bbb\\ccc");
boolean b = f3.mkdirs();
System.out.println(b);//true
// 4. Delete file
boolean b = f1.delete();
/* Detail 1:
If deleting file, deletes directly, no recycle bin. If deleting empty folder, deletes directly, no recycle bin.
If deleting folder with content, deletion fails
*/
Retrieving and Traversing
File f = new File("D:\\aaa");
// listFiles method
// Purpose: Get all contents in aaa folder, put all contents in array and return
File[] files = f.listFiles();
for (File file : files) {
// file represents each file or folder in aaa folder
System.out.println(file);
}
/* Details:
When called File path doesn't exist, returns null
When called File path is file, returns null
When called File path is empty folder, returns empty array
When called File path has content, returns File array with all files and folders
When called File path has hidden files, returns all files and folders including hidden ones
When called File path requires permission to access, returns null
*/
/*
public static File[] listRoots() Lists available filesystem drives
public string[] list() Gets all contents in current path (only names)
public string[] list(FilenameFilter filter) Uses filename filter to get all contents in current path
(Important) public File[] listFiles() Gets all contents in current path
public File[] listFiles(FileFilter filter) Uses file filter to get all contents in current path
public File[] listFiles(FilenameFilter filter) Uses filename filter to get all contents in current path
*/
File[] arr = File.listRoots();
String[] arr2 = f.list();
listFiles()
File[] arr = f.listFiles();
for (File file : arr){
// file represents each file or folder path in aaa folder
if(file.isFile() && file.getName().endsWith(".txt")){
System.out.println(file);
}
}
listFiles() overload:
- listFiles(FileFilter filter)
// accept parameter is full path
File[] arr = f.listFiles(new FileFilter(){
@Override
public boolean accept(File pathname){
return pathname.isFile() && pathname.getName().endsWith(".txt");
}
});
- File[] listFiles(FilenameFilter filter)
// Concatenation
File[] arr2 = f.listFiles(new FilenameFilter(){
@Override
public boolean accept(File dir,String name){
File src = new File(dir, name);
return src.isFile() && name.endsWith(".txt");
}
});
Exercises:
1. Find all AVI movies on computer (consider subfolders)
Sample code:
public class Test3 {
public static void main(String[] args) {
/* Requirement:
Find all AVI movies on computer (consider subfolders)
Pattern:
1. Enter folder
2. Traverse array
3. Judge
4. Judge
*/
findAVI();
}
public static void findAVI(){
// Get all local drive letters
File[] arr = File.listRoots();
for (File f : arr) {
findAVI(f);
}
}
public static void findAVI(File src){//"C:\\
// 1. Enter folder src
File[] files = src.listFiles();
// 2. Traverse array, get each file or folder in src
if(files != null){
for (File file : files) {
if(file.isFile()){
// 3. Judge, if file, execute business logic
String name = file.getName();
if(name.endsWith(".avi")){
System.out.println(file);
}
}else{
// 4. Judge, if folder, recursive
// Detail: When calling method again, parameter must be next level path
findAVI(file);
}
}
}
}
}
2. Delete multi-level folder
Requirement: To delete folder with content
1. First delete all content inside folder
2. Then delete itself
Sample code:
public class Test4 {
public static void main(String[] args) {
/*
Delete multi-level folder
To delete folder with content
1. First delete all content inside folder
2. Then delete itself
*/
File file = new File("D:\\aaa\\src");
delete(file);
}
/*
* Purpose: Delete src folder
* Parameter: Folder to delete
* */
public static void delete(File src){
// 1. First delete all content inside folder
// Enter src
File[] files = src.listFiles();
// Traverse
for (File file : files) {
// Judge, if file, delete
if(file.isFile()){
file.delete();
}else {
// Judge, if folder, recursive
delete(file);
}
}
// 2. Then delete itself
src.delete();
}
}
3. Calculate total folder size
Sample code:
public class Test5 {
public static void main(String[] args) {
/* Requirement:
Calculate total size of folder
*/
File file = new File("D:\\aaa\\src");
long len = getLen(file);
System.out.println(len);//4919189
}
/*
* Purpose:
* Calculate total size of folder
* Parameter:
* Folder to calculate
* Return value:
* Calculated result
*
* Total folder size:
* Simply, sum of all file sizes in folder
* */
public static long getLen(File src){
// 1. Define variable for accumulation
long len = 0;
// 2. Enter src folder
File[] files = src.listFiles();
// 3. Traverse array
for (File file : files) {
// 4. Judge
if(file.isFile()){
// Add current file size to len
len = len + file.length();
}else{
// Judge, if folder, recursive
len = len + getLen(file);
}
}
return len;
}
}
4. Count file types
Requirement: Count number of each file type in folder and print (consider subfolders)
Print format:
txt:3
doc:4
jpg:6
Sample code:
public class Test6 {
public static void main(String[] args) throws IOException {
/*
Requirement: Count number of each file type in folder and print (consider subfolders)
Print format:
txt:3
doc:4
jpg:6
*/
File file = new File("D:\\aaa\\src");
HashMap<String, Integer> hm = getCount(file);
System.out.println(hm);
}
/*
* Purpose:
* Count number of each file type in folder
* Parameter:
* Folder to count
* Return value:
* Map collection for statistics
* Key: Extension name, Value: Count
*
* a.txt
* a.a.txt
* aaa (not counted)
*
*
* */
public static HashMap<String,Integer> getCount(File src){
// 1. Define collection for counting
HashMap<String,Integer> hm = new HashMap<>();
// 2. Enter src folder
File[] files = src.listFiles();
// 3. Traverse array
for (File file : files) {
// 4. Judge, if file, count
if(file.isFile()){
// a.txt
String name = file.getName();
String[] arr = name.split("\\.");
if(arr.length >= 2){
String endName = arr[arr.length - 1];
if(hm.containsKey(endName)){
// Exists
int count = hm.get(endName);
count++;
hm.put(endName,count);
}else{
// Not exists
hm.put(endName,1);
}
}
}else{
// 5. Judge, if folder, recursive
// sonMap contains counts of each file type in subfolder
HashMap<String, Integer> sonMap = getCount(file);
// hm: txt=1 jpg=2 doc=3
// sonMap: txt=3 jpg=1
// Traverse sonMap, accumulate values to hm
Set<Map.Entry<String, Integer>> entries = sonMap.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
int value = entry.getValue();
if(hm.containsKey(key)){
// Exists
int count = hm.get(key);
count = count + value;
hm.put(key,count);
}else{
// Not exists
hm.put(key,value);
}
}
}
}
return hm;
}
}