The
File class
The
File
class
has a deceiving name – you might think it refers to a file, but it
doesn’t. It can represent either the
name
of a particular file or the
names
of a set of files in a directory. If it’s a set of files, you can ask for
the set with the list( )
method, and this returns an array of
String.
It makes sense to return an array rather than one of the flexible collection
classes because the number of elements is fixed, and if you want a different
directory listing you just create a different
File
object. In fact, “FilePath” would have been a better name. This
section shows a complete example of the use of this class, including the
associated FilenameFilter
interface.
A
directory lister
Suppose
you’d like to see a directory listing. The
File
object can be listed in two ways. If you call
list( )
with no arguments, you’ll get the full list that the
File
object contains. However, if you want a restricted list, for example, all of
the files with an extension of
.java,
then you use a “directory filter,” which is a class that tells how
to select the
File
objects for display.
Here’s
the code for the example: (See page
97
if you have trouble executing this program.)
//: DirList.java
// Displays directory listing
package c10;
import java.io.*;
public class DirList {
public static void main(String[] args) {
try {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(new DirFilter(args[0]));
for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
} catch(Exception e) {
e.printStackTrace();
}
}
}
class DirFilter implements FilenameFilter {
String afn;
DirFilter(String afn) { this.afn = afn; }
public boolean accept(File dir, String name) {
// Strip path information:
String f = new File(name).getName();
return f.indexOf(afn) != -1;
}
} ///:~
The
DirFilter
class “implements” the
interface
FilenameFilter.
(Interfaces were covered in Chapter 7.) It’s useful to see how simple the
FilenameFilter
interface
is:
public interface FilenameFilter {
boolean accept(File dir, String name);
}
It
says that all that this type of object does is provide a method called
accept( ).
The whole reason behind the creation of this class is to provide the
accept( )
method to the
list( )
method so that
list( )
can
call
back
accept( )
to determine which file names should be included in the list. Thus, this
technique is often referred to as a callback
or sometimes a functor
(that is,
DirFilter
is a functor because its only job is to hold a method). Because
list( )
takes a
FilenameFilter
object as its argument, it means that you can pass an object of any class that
implements
FilenameFilter
to choose (even at run-time) how the
list( )
method will behave. The purpose of a callback is to provide flexibility in the
behavior of code.
DirFilter
shows that just because an
interface
contains only a set of methods, you’re not restricted to writing only
those methods. (You must at least provide definitions for all the methods in an
interface, however.) In this case, the
DirFilter
constructor is also created.
The
accept( )
method must accept a
File
object representing the directory that a particular file is found in, and a
String
containing the name of that file. You might choose to use or ignore either of
these arguments, but you will probably at least use the file name. Remember
that the
list( )
method is calling
accept( )
for each of the file names in the directory object to see which one should be
included – this is indicated by the
boolean
result returned by
accept( ). To
make sure that what you’re working with is only the name and contains no
path information, all you have to do is take the
String
object and create a
File
object out of it, then call
getName( )
which strips away all the path information (in a platform-independent way). Then
accept( )
uses the String
class
indexOf( )
method to see if the search string
afn
appears anywhere in the name of the file. If
afn
is found within the string, the return value is the starting index of
afn,
but if it’s not found the return value is -1. Keep in mind that this is a
simple string search and does not have regular expression
“wildcard” matching such as “fo?.b?r*” which is much
more difficult to implement.
The
list( )
method returns an array. You can query this array for its length and then move
through it selecting the array elements. This ability to easily pass an array
in and out of a method is a tremendous improvement over the behavior of C and
C++.
Anonymous
inner classes
This
example is ideal for rewriting using an anonymous
inner class (described in Chapter 7). As a first cut, a method
filter( )
is
created that returns a handle to a
FilenameFilter:
//: DirList2.java
// Uses Java 1.1 anonymous inner classes
import java.io.*;
public class DirList2 {
public static FilenameFilter
filter(final String afn) {
// Creation of anonymous inner class:
return new FilenameFilter() {
String fn = afn;
public boolean accept(File dir, String n) {
// Strip path information:
String f = new File(n).getName();
return f.indexOf(fn) != -1;
}
}; // End of anonymous inner class
}
public static void main(String[] args) {
try {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(filter(args[0]));
for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
} catch(Exception e) {
e.printStackTrace();
}
}
} ///:~
Note
that the argument to
filter( )
must be final.
This is required by the anonymous inner class so that it can use an object from
outside its scope.
This
design is an improvement because the
FilenameFilter
class is now tightly bound to
DirList2.
However, you can take this approach one step further and define the anonymous
inner class as an argument to
list( ),
in which case it’s even smaller:
//: DirList3.java
// Building the anonymous inner class "in-place"
import java.io.*;
public class DirList3 {
public static void main(final String[] args) {
try {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(
new FilenameFilter() {
public boolean
accept(File dir, String n) {
String f = new File(n).getName();
return f.indexOf(args[0]) != -1;
}
});
for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
} catch(Exception e) {
e.printStackTrace();
}
}
} ///:~
The
argument to
main( )
is now
final,
since the anonymous inner class uses
args[0]
directly.
This
shows you how anonymous inner classes allow the creation of quick-and-dirty
classes to solve problems. Since everything in Java revolves around classes,
this can be a useful coding technique. One benefit is that it keeps the code
that solves a particular problem isolated together in one spot. On the other
hand, it is not always as easy to read, so you must use it judiciously.
A
sorted directory listing
Ah,
you say that you want the file names
sorted?
Since there’s no support for sorting in Java 1.0 or Java 1.1
(although sorting
is
included in Java 1.2),
it will have to be added into the program directly using the
SortVector
created
in Chapter 8:
//: SortedDirList.java
// Displays sorted directory listing
import java.io.*;
import c08.*;
public class SortedDirList {
private File path;
private String[] list;
public SortedDirList(final String afn) {
path = new File(".");
if(afn == null)
list = path.list();
else
list = path.list(
new FilenameFilter() {
public boolean
accept(File dir, String n) {
String f = new File(n).getName();
return f.indexOf(afn) != -1;
}
});
sort();
}
void print() {
for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
}
private void sort() {
StrSortVector sv = new StrSortVector();
for(int i = 0; i < list.length; i++)
sv.addElement(list[i]);
// The first time an element is pulled from
// the StrSortVector the list is sorted:
for(int i = 0; i < list.length; i++)
list[i] = sv.elementAt(i);
}
// Test it:
public static void main(String[] args) {
SortedDirList sd;
if(args.length == 0)
sd = new SortedDirList(null);
else
sd = new SortedDirList(args[0]);
sd.print();
}
} ///:~
A
few other improvements have been made. Instead of creating
path
and
list
as local variables to
main( ),
they are members of the class so their values can be accessible for the
lifetime of the object. In fact,
main( )
is now just a way to test the class. You can see that the constructor of the
class automatically sorts the list once that list has been created.
The
sort is case-insensitive so you don’t end up with a list of all the words
starting with capital letters, followed by the rest of the words starting with
all the lowercase letters. However, you’ll notice that within a group of
file names that begin with the same letter the capitalized words are listed
first, which is still not quite the desired behavior for the sort. This problem
will be fixed in Java 1.2.
Checking
for and creating directories
The
File
class is more than just a representation for an existing directory path, file,
or group of files. You can also use a
File
object to create a new directory
or an entire directory path if it doesn’t exist. You can also look at the characteristics
of files (size, last modification date, read/write), see whether a
File
object represents a file or a directory, and delete a file. This program shows
the remaining methods available with the
File
class:
//: MakeDirectories.java
// Demonstrates the use of the File class to
// create directories and manipulate files.
import java.io.*;
public class MakeDirectories {
private final static String usage =
"Usage:MakeDirectories path1 ...\n" +
"Creates each path\n" +
"Usage:MakeDirectories -d path1 ...\n" +
"Deletes each path\n" +
"Usage:MakeDirectories -r path1 path2\n" +
"Renames from path1 to path2\n";
private static void usage() {
System.err.println(usage);
System.exit(1);
}
private static void fileData(File f) {
System.out.println(
"Absolute path: " + f.getAbsolutePath() +
"\n Can read: " + f.canRead() +
"\n Can write: " + f.canWrite() +
"\n getName: " + f.getName() +
"\n getParent: " + f.getParent() +
"\n getPath: " + f.getPath() +
"\n length: " + f.length() +
"\n lastModified: " + f.lastModified());
if(f.isFile())
System.out.println("it's a file");
else if(f.isDirectory())
System.out.println("it's a directory");
}
public static void main(String[] args) {
if(args.length < 1) usage();
if(args[0].equals("-r")) {
if(args.length != 3) usage();
File
old = new File(args[1]),
rname = new File(args[2]);
old.renameTo(rname);
fileData(old);
fileData(rname);
return; // Exit main
}
int count = 0;
boolean del = false;
if(args[0].equals("-d")) {
count++;
del = true;
}
for( ; count < args.length; count++) {
File f = new File(args[count]);
if(f.exists()) {
System.out.println(f + " exists");
if(del) {
System.out.println("deleting..." + f);
f.delete();
}
}
else { // Doesn't exist
if(!del) {
f.mkdirs();
System.out.println("created " + f);
}
}
fileData(f);
}
}
} ///:~
In
fileData( )
you can see the various file investigation methods put to use to display
information about the file or directory path.
The
first method that’s exercised by
main( )
is renameTo( ),
which allows you to rename (or move) a file to an entirely new path represented
by the argument, which is another
File
object. This also works with directories of any length.
If
you experiment with the above program, you’ll find that you can make a
directory path of any complexity because mkdirs( )
will do all the work for you. In Java 1.0,
the
-d
flag reports that the directory is deleted but it’s still there; in Java
1.1
the directory is actually deleted.