Chapter 1
IN THIS CHAPTER
Examining the File class
Understanding command-line parameters
Introducing the JFileChooser class
In this chapter, you discover the ins and outs of working with files and directories. I don’t show you how to read or write files (for that info, you should flip to Book 8, Chapter 2), but you do find out how to find files on the hard drive; create, delete, and rename files; and work with directories. You find out how to use the Swing file-chooser dialog box that lets you add filing capabilities to Swing applications. Finally, you find out how to retrieve parameters from the command line — a useful technique, because command-line parameters are often used to pass file information to console programs.
Beginning with Java 1.7, you have two choices for working with files and directories: You can use the original File
class or you can use the new Path
class, which is part of a new file-processing package called NIO.2. (NIO.2 stands for New I/O version 2.) This chapter covers both of these classes.
The File
class is your key to processing files and directories. A File
object represents a single file or directory. Note that the file or directory doesn’t actually have to exist on your hard drive. Instead, the File
object represents a file that may or may not actually exist.
The File
class is in the java.io
package, so any program that uses it should import java.io.File
or java.io.*
.
Table 1-1 lists the main constructors and methods of the File
class.
TABLE 1-1 The File Class
Constructor |
Description |
|
Creates a file with the specified pathname. |
Field |
Description |
|
Separates components of a pathname on this system; usually is |
Method |
Description |
|
Determines whether the file can be read. |
|
Determines whether the file can be written. |
|
Creates the file on the hard drive if it doesn’t exist. The method returns |
|
Deletes the file or directory. The method returns |
|
Returns |
|
Returns the complete path to the file, including the drive letter if run on a Windows system; throws |
|
Gets the name of this file. |
|
Gets the name of the parent directory of this file or directory. |
|
Gets a |
|
Returns |
|
Returns |
|
Returns |
|
Returns the time when the file was last modified, expressed in milliseconds since 0:00:00 a.m., January 1, 1970. |
|
Returns the size of the file in bytes. |
|
Returns an array of |
|
Returns an array of |
|
Returns an array containing a |
|
Creates a directory on the hard drive from this |
|
Creates a directory on the hard drive from this |
|
Renames the |
|
Sets the last modified time for the |
|
Marks the file as read-only. The method returns |
|
Returns the pathname for this file or directory as a string. |
To create a File
object, you call the File
constructor, passing a string representing the filename as a parameter. Here’s an example:
File f = new File("hits.log");
Here the file’s name is hits.log,
and it lives in the current directory, which usually is the directory from which the Java Virtual Machine (JVM) was started.
If you don’t want the file to live in the current directory, you can supply a complete pathname in the parameter string. You’re now entering one of the few areas of Java that becomes system-dependent, however, because the way you write pathnames depends on the operating system you’re using. The pathname c:logshits.log
is valid for Windows systems, for example, but not on Unix or Macintosh systems, which don’t use drive letters and use forward slashes instead of backslashes to separate directories.
String path = "c:\logs\hits.log";
Creating a File
object doesn’t create a file on the hard drive. Instead, it creates an in-memory object that represents a file or directory that may or may not actually exist on the hard drive. To find out whether the file or directory exists, you can use the exists
method, as in this example:
File f = new File(path);
if (!f.exists())
System.out.println
("The input file does not exist!");
Here an error message is displayed on the console if the file doesn’t exist.
To create a new file on the hard drive, create a File
object with the filename you want to use and then use the createNewFile
method, like this:
File f = new File(path);
if (f.createNewFile())
System.out.println("File created.");
else
System.out.println("File could not be created.");
Note that the createNewFile
method returns a boolean that indicates whether the file was created successfully. If the file already exists, createNewFile
returns false
, so you don’t have to use the exists
method before you call createNewFile
.
Several of the methods of the File
class simply return information about a file or directory. You can find out whether the File
object represents a file or directory, for example, by calling its isDirectory
or isFile
method. Other methods let you find out whether a file is read-only or hidden, or retrieve the file’s age and the time when it was last modified.
You can get the name of the file represented by a File
object in several popular ways:
getName
method. This method returns a string that includes just the filename, not the complete path.File
object (such as logshit.log
), use the toString
method instead.getCannonicalPath
method. This method removes any system-dependent oddities such as relative paths, dots (which represent the current directory), and double dots (which represent the parent directory) to get the file’s actual path.A directory is a file that contains a list of other files or directories. Because a directory is just a special type of file, it’s represented by an object of the File
class. You can tell whether a particular File
object is a directory by calling its isDirectory
method. If this method returns true
, the File
is a directory. Then you can get an array of all the files contained in the directory by calling the listFiles
method.
The following code snippet lists the name of every file in a directory whose pathname is stored in the String
variable path:
File dir = new File(path);
if (dir.isDirectory())
{
File[] files = dir.listFiles();
for (File f : files)
System.out.println(f.getName());
}
The following snippet is a little more selective because it lists only files, not subdirectories, and doesn’t list hidden files:
File dir = new File(path);
if (dir.isDirectory())
{
File[] files = dir.listFiles();
for (File f : files)
{
if (f.isFile() && !f.isHidden())
System.out.println(f.getName());
}
}
You can rename a file by using the renameTo
method. This method uses another File
object as a parameter that specifies the file you want to rename the current file to. It returns a boolean
value that indicates whether the file was renamed successfully.
The following statements change the name of a file named hits.log
to savedhits.log
:
File f = new File("hits.log");
if (f.renameTo(new File("savedhits.log")))
System.out.println("File renamed.");
else
System.out.println("File not renamed.");
Depending on the capabilities of the operating system, the renameTo
method can also move a file from one directory to another. This code moves the file hits.log
from the folder logs
to the folder savedlogs
:
File f = new File("logs\hits.log");
if (f.renameTo(new File("savedlogs\hits.log")))
System.out.println("File moved.");
else
System.out.println("File not moved.");
To delete a file, create a File
object for the file and then call the delete
method, as in this example:
File f = new File("hits.log");
if (f.delete())
System.out.println("File deleted.");
else
System.out.println("File not deleted.");
If the file is a directory, the directory must be empty to be deleted.
private static void deleteFile(File dir)
{
File[] files = dir.listFiles();
for (File f : files)
{
if (f.isDirectory())
deleteFile(f);
else
f.delete();
}
dir.delete();
}
Then, to delete a folder named folder1
along with all its files and subdirectories, call the deleteFile
method:
deleteFile(new File("folder1");
Ever since Book 1, Chapter 1, I’ve used this construction in every Java program presented so far:
public static void main(String[] args)
It’s high time that you find out what the args
parameter of the main
method is used for. The args
parameter is an array of strings that lets you access any command-line parameters that are specified by the user when he or she runs your program.
Suppose that you run a Java program named Test
from a command program like this:
C:>java Test the quick brown fox
In this case, the Java program is passed four parameters: the
, quick
, brown
, and fox
. You can access these parameters via the args
array.
Suppose that the main
method of the Test
class is written like this:
public static void main(String[] args)
{
for (String s : args)
System.out.println(s);
}
Then the program displays the following output on the console when run with the command shown a few paragraphs back:
the
quick
brown
fox
Command-line parameters are useful in Java programs that work with files as a way to pass pathnames to the program. Here’s a program that lists all the files in a directory passed to the program as a parameter:
import java.io.*;
public class ListDirectory
{
public static void main(String[] args)
{
if (args.length > 0)
{
String path = args[0];
File dir = new File(path);
if (dir.isDirectory())
{
File[] files = dir.listFiles();
for (File f : files)
{
System.out.println(f.getName());
}
}
else
System.out.println("Not a directory.");
}
}
}
For the most part, you don’t want to mess around with command-line parameters in Swing applications. Instead, you want to use the JFileChooser
class to let users pick the files they want to work with. This class lets you display Open and Save dialog boxes similar to the ones you’ve seen in other graphic user interface (GUI) applications with just a few lines of code.
Figure 1-1 shows an Open dialog box created with just these two lines of code:
JFileChooser fc = new JFileChooser();
int result = fc.showOpenDialog(this);
This code appears in a frame class that extends the JFrame
class, so the this
keyword in the showOpenDialog
call refers to the parent frame.
The result returned by the showOpenDialog
method indicates whether the user chose to open a file or click Cancel, and the JFileChooser
class provides a handy getSelectedFile
method that you can use to get a File
object for the file selected by the user.
The JFileChooser
class has many additional methods that you can use to tailor its appearance and behavior in just about any way imaginable. Table 1-2 lists the commonly used constructors and methods of this powerful class.
TABLE 1-2 The JFileChooser Class
Constructor |
Description |
|
Creates a file chooser that begins at the user’s default directory. On Windows systems, this directory is usually |
|
Creates a file chooser that begins at the location indicated by the file parameter. |
|
Creates a file chooser that begins at the location indicated by the path string. |
Method |
Description |
|
Adds a file filter to the chooser. |
|
Returns a |
|
Returns an array of |
|
If |
|
Sets the text for the Approve button. |
|
Sets the title displayed by the file-chooser dialog box. |
|
Doesn’t show hidden files if |
|
Allows the user to select more than one file if |
|
Displays a custom dialog box with the specified text for the Accept button. The return values are |
|
Determines whether the user can select files, directories, or both. The parameter can be specified as |
|
Displays an Open dialog box. The return values are the same as for the |
|
Displays a Save dialog box. The return values are the same as for the |
As you’ve just seen, you can create an Open dialog box with just a few lines of code. First, you call the JFileChooser
constructor to create a JFileChooser
instance; then you call the showOpenDialog
method to display an Open dialog box.
If you don’t pass a parameter to the constructor, the file chooser starts in the user’s default directory, which on most systems is the operating system’s current directory. If you want to start in some other directory, you have two options:
File
object for the directory and then pass the File
object to the constructor.The JFileChooser
class also includes methods that let you control the appearance of the chooser dialog box. You can use the setDialogTitle
method to set the title (the default is Open
), for example, and you can use the setFileHidingEnabled
method to control whether hidden files are shown. If you want to allow the user to select more than one file, use the setMultiSelectionEnabled
method.
A setFileSelectionMode
method lets you specify whether users can select files, directories, or both. The options for this method need a little explanation:
JFileChooser.FILES_ONLY
: With this option (which is the default), the user can choose files only with the file-chooser dialog box. The user can navigate directories in the file-chooser dialog box but can’t actually select a directory.JFileChooser.DIRECTORIES_ONLY
: With this option, the user can select only directories, not files. One common use for this option is to let the user choose a default location for files used by your application without actually opening a file.JFileChooser.FILES_AND_DIRECTORIES
: This option lets the user select either a file or a directory. For most applications, you want the user to pick either a file or a directory, but not both, so you probably won’t use this option much.The file-chooser dialog box is a modal dialog box, which means that after you call the showOpenDialog
method, your application is tied up until the user closes the file-chooser dialog box by clicking the Open or Cancel button.
You can find out which button the user clicked by inspecting the value returned by the showOpenDialog
method:
JFileChooser.APPROVE_OPTION
.JFileChooser.CANCEL_OPTION
.JFileChooser.ERROR_OPTION
.Assuming that the showOpenDialog
method returns APPROVE_OPTION
, you can use the getSelectedFile
method to get a File
object for the file selected by the user. Then you can use this File
object elsewhere in the program to read or write data.
Putting it all together, then, here’s a method that displays a file-chooser dialog box and returns a File
object for the file selected by the user. If the user cancels or an error occurs, the method returns null
.
private File getFile()
{
JFileChooser fc = new JFileChooser();
int result = fc.showOpenDialog(null);
File file = null;
if (result == JFileChooser.APPROVE_OPTION)
file = fc.getSelectedFile();
return file;
}
You can call this method from an action event handler when the user clicks a button, selects a menu command, or otherwise indicates that he or she wants to open a file.
The file-chooser dialog box includes a Files of Type drop-down list filter that the user can use to control what types of files are displayed by the chooser. By default, the only item available in this drop-down list is All Files, which doesn’t filter the files at all. If you want to add another filter to this list, you must create a class that extends the FileFilter
abstract class and then pass an instance of this class to the addChoosableFileFilter
method.
Table 1-3 lists the methods of the FileFilter
class. Fortunately, it has only two methods that you need to implement. This class is in the javax.swing.filechooser
package.
TABLE 1-3 The FileFilter Class
Method |
Description |
|
You must implement this method to return |
|
You must implement this method to return the description string that is displayed in the Files of Type drop-down list in the chooser dialog box. |
class JavaFilter
extends javax.swing.filechooser.FileFilter
The getDescription
method simply returns the text displayed in the Files of Type drop-down list. You usually implement it with a single return statement that returns the description. Here’s an example:
public String getDescription()
{
return "Java files (*.java)";
}
Here the string Java files (*.java)
is displayed in the Files of Type drop-down list.
The accept
method does the work of a file filter. The file chooser calls this method for every file it displays. The file is passed as a parameter. The accept
method returns a boolean that indicates whether the file is displayed.
The accept
method can use any criteria it wants to decide which files to accept and which files to reject. Most filters do this based on the file-extension part of the filename. Unfortunately, the File
class doesn’t have a method that returns the file extension, but you can get the name with the getName
method and then use the matches
method with a regular expression to determine whether the file is of the type you’re looking for. Here’s an if
statement that determines whether the filename in the name
variable is a .java
file:
if (name.matches(".*\.java"))
Here the regular expression matches strings that begin with any sequence of characters and end with .java
. (For more information about regular expressions, refer to Book 5, Chapter 3.)
Here’s a FileFilter
class that displays files with the extension .java
:
private class javaFilter
extends javax.swing.filechooser.FileFilter
{
public boolean accept(File f)
{
if (f.isDirectory())
return true;
String name = f.getName();
if (name.matches(".*\.java"))
return true;
else
return false;
}
public String getDescription()
{
return "Java files (*.java)";
}
}
After you create a class that implements a file filter, you can add the file filter to the file chooser by calling the addChoosableFileFilter
method, passing a new instance of the FileFilter
class, like this:
fc.setChoosableFileFilter(new JavaFilter());
If you want, you can remove the All Files filter by calling the method setAcceptAllFileFilterUsed
, like this:
fc.setAcceptAllFileFilterUsed(false);
Then only the file filters that you add to the file chooser appear in the Files of Type drop-down list.
Java 1.7 introduces a new type of object called a Path
to address several weaknesses of the File
class, which has been part of Java since version 1.0. The most egregious of the File
class flaws addressed by Path
objects is that many of the operations of the File
class don’t provide detailed error information when problems occur. The delete
method of the File
class, for example, returns a boolean value to indicate whether the file was deleted, but if the file could not be deleted, there’s no way to find out why. Path
objects correct this deficiency by throwing exceptions that contain detailed error information if an operation fails.
Note that Path
is not actually a class; instead, it’s an interface. That means you can’t directly create a Path
object by calling a constructor. Instead, you use a method of new class called Paths
to create Path
objects. The naming is confusing — Path
versus Paths
, interfaces versus classes — but after working with the Paths
class to create Path
objects, you get used to the distinction.
Both the Path
interface and the Paths
class are in the java.nio.File
package, so any program that uses them should import java.nio.file.*
.
Table 1-4 lists the constructor and methods of the Path
class.
TABLE 1-4 Path Methods
Method |
Description |
|
Copies the file to the specified target file. |
|
Moves the file to the target path and deletes the original file. |
|
Creates the directory on the hard drive if it doesn’t already exist. |
|
Creates the file on the hard drive if it doesn’t already exist. |
|
Deletes the file or directory. The method throws an exception if the file or directory doesn’t exist or couldn’t be deleted. |
|
Deletes the file or directory if it exists. The method doesn’t throw an exception if the file or directory doesn’t exist. |
|
Returns |
|
Returns |
|
Returns the full absolute path to the file, including the drive letter if run on a Windows system. |
|
Gets a |
|
Gets a |
|
Returns |
|
Returns the pathname for this file or directory as a string. |
One thing you may find confusing right off the bat is that the Path
objects are created by calling a static method of the Paths
class. Path
and Paths
are two distinct things: Path
is an interface, and Paths
is a class. To create a Path
object, use the get
method of the static Paths
class, like this:
Path p = Paths.get("c:\test.txt");
Here the file’s name is test.txt,
and it resides in the root directory of the C:
drive.
Like a File
object, a Path
object represents a file that may or may not actually exist on the hard drive. You can test to see whether a file exists like this:
Path p = Paths.get(path);
if (!p.exists())
System.out.println
("The input file does not exist!");
To create a new file, use the createFile
method, like this:
Path p = Paths.get("c:\test.txt");
try
{
p.createFile();
System.out.println ("File created!");
}
catch (Exception e)
{
System.out.println ("Error: " + e.getMessage());
}
Note that the createFile
method throws an exception if the file couldn’t be created. The getMessage
method of this exception returns a message that explains why the file couldn’t be created.
One of the weaknesses of the File
class is that it doesn’t deal well with large directories. In particular, methods such as listFiles
that allow you to access the contents of a directory return an array of File
objects. That’s fine if the directory contains a few dozen or even a few hundred files, but what if the directory contains thousands or tens of thousands of files? In short, the File
class is not scalable.
The Path
class remedies this deficiency by letting you access the contents of a directory via a stream object defined by DirectoryStream
. I say more about working with streams in Book 8, Chapter 2, but for now, suffice it to say that a stream provides a simple way to access a large number of data items one at a time. You can retrieve the items in a directory stream easily by using an enhanced for
statement, as in this example:
Path c = Paths.get("C:\");
try
{
DirectoryStream<Path> stream
= c.newDirectoryStream();
for (Path entry: stream)
System.out.println(entry.toString());
}
catch (Exception e)
{
System.out.println("Error: " + e.getMessage());
}
This example displays a listing of the contents of the C:
directory, much as typing DIR C:
at a command prompt would.
You could change the preceding example to list just the text files (files with the extension .txt
) by changing the first statement after the try
statement to this:
DirectoryStream<Path> stream
= c.newDirectoryStream("*.txt");
In the section “Getting the contents of a directory,” earlier in this chapter, I mention that processing subdirectories of a main directory by using the File
class requires fancy recursive programming. With Java 1.7 (and later versions), you can use a new feature that does the difficult recursive part of the programming for you. It does this by walking the file tree — visiting every file in the tree and calling one or more methods defined by a special class you create, called a file visitor. Here are the basic details of how this magic works:
Create a file-visitor class, which is extended from FileVisitor
or, more often, SimpleFileVisitor
.
The SimpleFileVisitor
class defines several methods that are called for every file in the file tree, as shown in Table 1-5.
In the file visitor, override one or more methods that are defined by the SimpleFileVisitor
class.
These methods are where you write the code that you want to execute for every file visited when the directory tree is walked. You always want to override at least three of the methods listed in Table 1-5:
visitFile
, which is called for every file in the file treevisitFileFailed
, which is called for every file that can’t be accessed (usually due to permissions issues)preVisitDirectoryFailed
, which is called for every directory that couldn’t be accessedCreate a Path
object that indicates the starting point (that is, the root) of the file tree you want to walk.
If you want to visit all the files on your C:
drive, for example, this path should point to C:.
Call the walkFileTree
method of the static Files
class to process the files.
This method takes just two arguments: the Path
object (which identifies the root of your file tree) and an instance of your file-visitor class.
TABLE 1-5 The SimpleFileVisitor Class
Method |
Description |
|
Called once for every file in the file tree. |
|
Called if the file couldn’t be accessed. |
|
Called once for every directory in the file tree. This method is called before any of the files in the directory are visited. |
|
Called once for every directory in the file tree that couldn’t be accessed. |
|
Called once for every directory in the file tree. This method is called after all the files in the directory are visited. |
Confusing enough? An example should clear things up. Listing 1-1 shows just about the simplest example of a file visitor I can come up with. This program lists all the files in C:WindowsSystem32
, including all its subfolders.
LISTING 1-1 Simple File Visitor
import java.nio.file.*;
import java.io.*;
import java.nio.file.attribute.*;
public class FileVisitorDemo
{
public static void main(String[] args)
{
Path start = Paths.get("c:\Windows\System32"); →9
MyFileVisitor visitor = new MyFileVisitor(); →10
try
{
Files.walkFileTree(start, visitor); →13
}
catch (Exception e)
{
System.out.println(e.getMessage());
}
}
private static class MyFileVisitor extends SimpleFileVisitor <Path> →21
{
public FileVisitResult visitFile(Path file, →23
BasicFileAttributes attr)
{
System.out.println(file.toString());
return FileVisitResult.CONTINUE; →27
}
public FileVisitResult visitFileFailed(Path file, →30
BasicFileAttributes attr)
{
System.out.println(file.toString() + " COULD NOT ACCESS!");
return FileVisitResult.CONTINUE;
}
public FileVisitResult preVisitDirectoryFailed →36
(Path dir, IOException e)
{
System.out.println(dir.toString() + " COULD NOT ACCESS!");
return FileVisitResult.CONTINUE;
}
}
}
Here are the highlights of how this program works:
Path
class that starts the file tree at C:WindowsSystem32
. You could substitute any directory you want for this path.MyFileVisitor
class, which is defined later in the program (at line 21).start
, using the MyFileVisitor
object created in line 10.MyFileVisitor
class, which extends the SimpleFileVisitor
class. SimpleFileVisitor
is a generic class, so you must specify a type. Usually you specify the Path
type so that SimpleFileVisitor
processes Path
objects.visitFile
method, which is called once for each file that is accessed as the file tree is walked. This method simply prints the name of the file to the console. In a more realistic program, you would perform some more significant action on the file, such as copying it or opening the file and reading its contents.visitFile
method, which is of type FileVisitResult
. The most commonly returned value is CONTINUE
, which indicates that the file-tree walker should continue processing files. Other options include TERMINATE
, SKIP_SIBLINGS
, and SKIP_SUBTREE
.visitFileFailed
method, which is called whenever a file can’t be accessed. In this program, the visitFileFailed
method simply prints an error message.preVisitDirectoryFailed
method, which is called whenever a directory can’t be accessed. Like the visitFileFailed
method, the preVisitDirectoryFailed
method simply prints an error message.