Advanced Java Programming - Part 2

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:
    1. Obtain a Stream pipeline with data
    2. 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:

  1. Reference must be within a functional interface
  2. Referenced method must exist
  3. Parameter and return types of referenced method must match abstract method
  4. 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

  1. Other classes: other object::method name
  2. This class: this::method name
  3. 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:

  1. Is there already a method that fits current needs?
  2. If such method exists, does it satisfy reference rules?
    1. Static class name::method name
    2. Instance method
    3. 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

  1. Outputs exception name, cause and location info to console
  2. 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

  1. Define exception class
  2. Write inheritance relationship
  3. Empty constructor
  4. 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:

  1. 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");
    }
});

  1. 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;
    }
}

Tags: java immutable-collections stream-api exception-handling file-io

Posted on Sat, 16 May 2026 06:29:28 +0000 by Strings