I/O and NIO - OCP - OCA/OCP Java SE 7 Programmer I & II Study Guide (Exams 1Z0-803 & 1Z0-804) (2015)

OCA/OCP Java SE 7 Programmer I & II Study Guide (Exams 1Z0-803 & 1Z0-804) (2015)

Part 2. OCP

Chapter 9. I/O and NIO

CERTIFICATION OBJECTIVES

• Read and Write Data from the Console

• Use Streams to Read From and Write To Files by Using Classes in the java .io Package, Including BufferedReader, BufferedWriter, File, FileReader, FileWriter, DataInputStream, DataOutputStream, ObjectOutputStream, ObjectInputStream, and PrintWriter

• Operate on File and Directory Paths with the Path Class (sic)

• Check, Delete, Copy, or Move a File or Directory with the Files Class

• Read and Change File and Directory Attributes, Focusing on the BasicFileAttributes, DosFileAttributes, and PosixFileAttributes Interfaces

• Recursively Access a Directory Tree Using the DirectoryStream and FileVisitor Interfaces

• Find a File with the PathMatcher Interface

• Watch a Directory for Changes with the WatchService Interface

image Two-Minute Drill

Q&A Self Test

I/O (input/output) has been around since the beginning of Java. You could read and write files along with some other common operations. Then with Java 1.4, Java added more I/O functionality and cleverly named it NIO. That stands for “new I/O.” Don’t worry—you won’t be asked about those Java 1.4 additions on the exam.

The APIs prior to Java 7 still had a few limitations when you had to write applications that focused heavily on files and file manipulation. Trying to write a little routine listing all the files created in the past day within a directory tree would give you some headaches. There was no support for navigating directory trees, and just reading attributes of a file was also quite hard. In Java 7, this whole routine is less than 15 lines of code!

Now what to name yet another I/O API? The name “new I/O” was taken, and “new new I/O” would just sound silly. Since the Java 7 functionality was added to package names that begin with java.nio, the new name was NIO.2. For the purposes of this chapter and the exam, NIO is shorthand for NIO.2.

Since NIO (or NIO.2 if you like) builds upon the original I/O, some of those concepts are still tested on the exam in addition to the new parts. Fortunately, you won’t have to become a total I/O or NIO guru to do well on the exam. The intention of the exam team was to include just the basic aspects of these technologies, and in this chapter, we cover more than you’ll need to get through these objectives on the exam.

CERTIFICATION OBJECTIVES

File Navigation and I/O (OCP Objectives 7.1 and 7.2)

7.1 Read and write data from the console.

7.2 Use streams to read from and write to files by using classes in the java.io package, including BufferedReader, BufferedWriter, File, FileReader, FileWriter, DataInputStream, DataOutputStream, ObjectOutputStream, ObjectInputStream, and PrintWriter.

I/O has had a strange history with the OCP certification. It was included in all the versions of the exam, up to and including 1.2, then removed from the 1.4 exam, reintroduced for Java 5, extended for Java 6, and extended still more for Java 7.

I/O is a huge topic in general, and the Java APIs that deal with I/O in one fashion or another are correspondingly huge. A general discussion of I/O could include topics such as file I/O, console I/O, thread I/O, high-performance I/O, byte-oriented I/O, character-oriented I/O, I/O filtering and wrapping, serialization, and more. Luckily for us, the I/O topics included in the Java 7 exam are fairly well restricted to file I/O for characters and Serialization. Due to a late change in the Oracle objectives, you WILL NOT find Serialization discussed in this chapter. Instead, we created a complete “Serialization mini-chapter” (along with a Self Test) as Appendix A.

Here’s a summary of the I/O classes you’ll need to understand for the exam:

image File The API says that the File class is “an abstract representation of file and directory pathnames.” The File class isn’t used to actually read or write data; it’s used to work at a higher level, making new empty files, searching for files, deleting files, making directories, and working with paths.

image FileReader This class is used to read character files. Its read() methods are fairly low-level, allowing you to read single characters, the whole stream of characters, or a fixed number of characters. FileReaders are usually wrapped by higher-level objects such as BufferedReaders, which improve performance and provide more convenient ways to work with the data.

image BufferedReader This class is used to make lower-level Reader classes like FileReader more efficient and easier to use. Compared to FileReaders, BufferedReaders read relatively large chunks of data from a file at once and keep this data in a buffer. When you ask for the next character or line of data, it is retrieved from the buffer, which minimizes the number of times that time-intensive, file-read operations are performed. In addition, BufferedReader provides more convenient methods, such as readLine(), that allow you to get the next line of characters from a file.

image FileWriter This class is used to write to character files. Its write() methods allow you to write character(s) or strings to a file. FileWriters are usually wrapped by higher-level Writer objects, such as BufferedWriters or PrintWriters, which provide better performance and higher-level, more flexible methods to write data.

image BufferedWriter This class is used to make lower-level classes like FileWriters more efficient and easier to use. Compared to FileWriters, BufferedWriters write relatively large chunks of data to a file at once, minimizing the number of times that slow, file-writing operations are performed. The BufferedWriter class also provides a newLine() method to create platform-specific line separators automatically.

image PrintWriter This class has been enhanced significantly in Java 5. Because of newly created methods and constructors (like building a PrintWriter with a File or a String), you might find that you can use PrintWriter in places where you previously needed a Writer to be wrapped with a FileWriter and/or a BufferedWriter. New methods like format(), printf(), and append() make PrintWriters very flexible and powerful.

image Console This new Java 6 convenience class provides methods to read input from the console and write formatted output to the console.

image

Stream classes are used to read and write bytes, and Readers and Writers are used to read and write characters. Since all of the file I/O on the exam is related to characters, if you see API class names containing the word “Stream”—for instance,DataOutputStream—then the question is probably about serialization or something unrelated to the actual I/O objective.

Creating Files Using the File Class

Objects of type File are used to represent the actual files (but not the data in the files) or directories that exist on a computer’s physical disk. Just to make sure we’re clear, when we talk about an object of type File, we’ll say File, with a capital F. When we’re talking about what exists on a hard drive, we’ll call it a file with a lowercase f (unless it’s a variable name in some code). Let’s start with a few basic examples of creating files, writing to them, and reading from them. First, let’s create a new file and write a few lines of data to it:

image

If you compile and run this program, when you look at the contents of your current directory, you’ll discover absolutely no indication of a file called fileWrite1.txt. When you make a new instance of the class File, you’re not yet making an actual file; you’re just creating a filename. Once you have a File object, there are several ways to make an actual file. Let’s see what we can do with the File object we just made:

image

This produces the output

image

And also produces an empty file in your current directory. If you run the code a second time, you get the output

image

Let’s examine these sets of output:

image First execution The first call to exists() returned false, which we expected… remember, new File() doesn’t create a file on the disk! The createNewFile() method created an actual file and returned true, indicating that a new file was created and that one didn’t already exist. Finally, we called exists() again, and this time it returned true, indicating that the file existed on the disk.

image Second execution The first call to exists() returns true because we built the file during the first run. Then the call to createNewFile() returns false since the method didn’t create a file this time through. Of course, the last call to exists() returns true.

A couple of other new things happened in this code. First, notice that we had to put our file creation code in a try/catch. This is true for almost all of the file I/O code you’ll ever write. I/O is one of those inherently risky things. We’re keeping it simple for now and ignoring the exceptions, but we still need to follow the handle-or-declare rule, since most I/O methods declare checked exceptions. We’ll talk more about I/O exceptions later. We used a couple of File’s methods in this code:

image boolean exists() This method returns true if it can find the actual file.

image boolean createNewFile()This method creates a new file if it doesn’t already exist.

image

Remember, the exam creators are trying to jam as much code as they can into a small space, so in the previous example, instead of these three lines of code:

image

you might see something like the following single line of code, which is a bit harder to read, but accomplishes the same thing:

image

Using FileWriter and FileReader

In practice, you probably won’t use the FileWriter and FileReader classes without wrapping them (more about “wrapping” very soon). That said, let’s go ahead and do a little “naked” file I/O:

image

which produces the output:

image

Here’s what just happened:

1. FileWriter fw = new FileWriter(file) did three things:

a. It created a FileWriter reference variable, fw.

b. It created a FileWriter object and assigned it to fw.

c. It created an actual empty file out on the disk (and you can prove it).

2. We wrote 12 characters to the file with the write() method, and we did a flush() and a close().

3. We made a new FileReader object, which also opened the file on disk for reading.

4. The read() method read the whole file, a character at a time, and put it into the char[] in.

5. We printed out the number of characters we read in size, and we looped through the in array, printing out each character we read, and then we closed the file.

Before we go any further, let’s talk about flush() and close(). When you write data out to a stream, some amount of buffering will occur, and you never know for sure exactly when the last of the data will actually be sent. You might perform many write operations on a stream before closing it, and invoking the flush() method guarantees that the last of the data you thought you had already written actually gets out to the file. Whenever you’re done using a file, either reading it or writing to it, you should invoke the close() method. When you are doing file I/O, you’re using expensive and limited operating system resources, and so when you’re done, invoking close() will free up those resources.

Now, back to our last example. This program certainly works, but it’s painful in a couple of different ways:

1. When we were writing data to the file, we manually inserted line separators (in this case \n) into our data.

2. When we were reading data back in, we put it into a character array. It being an array and all, we had to declare its size beforehand, so we’d have been in trouble if we hadn’t made it big enough! We could have read the data in one character at a time, looking for the end of the file after each read(), but that’s pretty painful too.

Because of these limitations, we’ll typically want to use higher-level I/O classes like BufferedWriter or BufferedReader in combination with FileWriter or FileReader.

Combining I/O Classes

Java’s entire I/O system was designed around the idea of using several classes in combination. Combining I/O classes is sometimes called wrapping and sometimes called chaining.The java.io package contains about 50 classes, 10 interfaces, and 15 exceptions. Each class in the package has a specific purpose (creating high cohesion), and the classes are designed to be combined with each other in countless ways to handle a wide variety of situations.

When it’s time to do some I/O in real life, you’ll undoubtedly find yourself poring over the java.io API, trying to figure out which classes you’ll need and how to hook them together. For the exam, you’ll need to do the same thing, but Oracle artificially reduced the API (phew!). In terms of studying for Exam Objective 7.2, we can imagine that the entire java.io package—consisting of the classes listed in Exam Objective 7.2 and summarized in Table 9-1—is our mini I/O API.

TABLE 9-1 java.io Mini API

image

Now let’s say that we want to find a less painful way to write data to a file and read the file’s contents back into memory. Starting with the task of writing data to a file, here’s a process for determining what classes we’ll need and how we’ll hook them together:

1. We know that ultimately we want to hook to a File object. So whatever other class or classes we use, one of them must have a constructor that takes an object of type File.

2. Find a method that sounds like the most powerful, easiest way to accomplish the task. When we look at Table 9-1 we can see that BufferedWriter has a newLine() method. That sounds a little better than having to manually embed a separator after each line, but if we look further, we see that PrintWriter has a method called println(). That sounds like the easiest approach of all, so we’ll go with it.

3. When we look at PrintWriter’s constructors, we see that we can build a PrintWriter object if we have an object of type File, so all we need to do to create a PrintWriter object is the following:

image

Okay, time for a pop quiz. Prior to Java 5, PrintWriter did not have constructors that took either a String or a File. If you were writing some I/O code in Java 1.4, how would you get a PrintWriter to write data to a file? Hint: You can figure this out by studying the mini I/O API inTable 9-1.

Here’s one way to go about solving this puzzle: First, we know that we’ll create a File object on one end of the chain, and that we want a PrintWriter object on the other end. We can see in Table 9-1 that a PrintWriter can also be built using a Writer object. Although Writer isn’t aclass we see in the table, we can see that several other classes extend Writer, which for our purposes is just as good; any class that extends Writer is a candidate. Looking further, we can see that FileWriter has the two attributes we’re looking for:

image It can be constructed using a File.

image It extends Writer.

Given all of this information, we can put together the following code (remember, this is a Java 1.4 example):

image

At this point, it should be fairly easy to put together the code to more easily read data from the file back into memory. Again, looking through the table, we see a method called readLine() that sounds like a much better way to read data. Going through a similar process, we get the following code:

image

image

You’re almost certain to encounter exam questions that test your knowledge of how I/O classes can be chained. If you’re not totally clear on this last section, we recommend that you use Table 9-1 as a reference and write code to experiment with which chaining combinations are legal and which are illegal.

Working with Files and Directories

Earlier, we touched on the fact that the File class is used to create files and directories. In addition, File’s methods can be used to delete files, rename files, determine whether files exist, create temporary files, change a file’s attributes, and differentiate between files and directories. A point that is often confusing is that an object of type File is used to represent either a file or a directory. We’ll talk about both cases next.

We saw earlier that the statement

File file = new File("foo");

always creates a File object and then does one of two things:

1. If “foo” does NOT exist, no actual file is created.

2. If “foo” does exist, the new File object refers to the existing file.

Notice that File file = new File(“foo”); NEVER creates an actual file. There are two ways to create a file:

1. Invoke the createNewFile() method on a File object. For example:

image

2. Create a Writer or a Stream. Specifically, create a FileWriter, a PrintWriter, or a FileOutputStream. Whenever you create an instance of one of these classes, you automatically create a file, unless one already exists; for instance

image

Creating a directory is similar to creating a file. Again, we’ll use the convention of referring to an object of type File that represents an actual directory as a Directory object, with a capital D (even though it’s of type File). We’ll call an actual directory on a computer a directory, with a small d. Phew! As with creating a file, creating a directory is a two-step process; first we create a Directory (File) object; then we create an actual directory using the following mkdir() method:

image

Once you’ve got a directory, you put files into it and work with those files:

image

This code is making a new file in a subdirectory. Since you provide the subdirectory to the constructor, from then on, you just refer to the file by its reference variable. In this case, here’s a way that you could write some data to the file myFile:

image

Be careful when you’re creating new directories! As we’ve seen, constructing a Writer or a Stream will often create a file for you automatically if one doesn’t exist, but that’s not true for a directory.

image

This will generate an exception that looks something like

java.io.IOException: No such file or directory

You can refer a File object to an existing file or directory. For example, assume that we already have a subdirectory called existingDir in which resides an existing file existingDirFile.txt, which contains several lines of text. When you run the following code:

image

the following output will be generated:

image

Take special note of what the readLine() method returns. When there is no more data to read, readLine() returns a null—this is our signal to stop reading the file. Also, notice that we didn’t invoke a flush() method. When reading a file, no flushing is required, so you won’t even find a flush() method in a Reader kind of class.

In addition to creating files, the File class lets you do things like renaming and deleting files. The following code demonstrates a few of the most common ins and outs of deleting files and directories (via delete()) and renaming files and directories (via renameTo()):

image

image

This outputs

delDir is false

and leaves us with a directory called newDir that contains a file called newName.txt. Here are some rules that we can deduce from this result:

image delete() You can’t delete a directory if it’s not empty, which is why the invocation delDir.delete() failed.

image renameTo() You must give the existing File object a valid new File object with the new name that you want. (If newName had been null, we would have gotten a NullPointerException.)

image renameTo() It’s okay to rename a directory, even if it isn’t empty.

There’s a lot more to learn about using the java.io package, but as far as the exam goes, we only have one more thing to discuss, and that is how to search for a file. Assuming that we have a directory named searchThis that we want to search through, the following code uses theFile.list() method to create a String array of files and directories. We then use the enhanced for loop to iterate through and print.

image

On our system, we got the following output:

image

Your results will almost certainly vary :)

In this section, we’ve scratched the surface of what’s available in the java.io package. Entire books have been written about this package, so we’re obviously covering only a very small (but frequently used) portion of the API. On the other hand, if you understand everything we’ve covered in this section, you will be in great shape to handle any java.io questions you encounter on the exam, except for the Console class, which we’ll cover next. (Note: Serialization is covered in Appendix A)

The java.io.Console Class

New to Java 6 is the java.io.Console class. In this context, the console is the physical device with a keyboard and a display (like your Mac or PC). If you’re running Java SE 6 from the command line, you’ll typically have access to a console object, to which you can get a reference by invoking System.console(). Keep in mind that it’s possible for your Java program to be running in an environment that doesn’t have access to a console object, so be sure that your invocation of System .console() actually returns a valid console reference and not null.

The Console class makes it easy to accept input from the command line, both echoed and nonechoed (such as a password), and makes it easy to write formatted output to the command line. It’s a handy way to write test engines for unit testing or if you want to support a simple but secure user interaction and you don’t need a GUI.

On the input side, the methods you’ll have to understand are readLine and readPassword. The readLine method returns a string containing whatever the user keyed in—that’s pretty intuitive. However, the readPassword method doesn’t return a string; it returns a character array. Here’s the reason for this: Once you’ve got the password, you can verify it and then absolutely remove it from memory. If a string was returned, it could exist in a pool somewhere in memory, and perhaps some nefarious hacker could find it.

Let’s take a look at a small program that uses a console to support testing another class:

image

Let’s review this code:

image At line 1, we get a new Console object. Remember that we can’t say this:

Console c = new Console();

image At line 2, we invoke readPassword, which returns a char[], not a string. You’ll notice when you test this code that the password you enter isn’t echoed on the screen.

image At line 3, we’re just manually displaying the password you keyed in, separating each character with a space. Later on in this chapter, you’ll read about the format() method, so stay tuned.

image At line 4, we invoke readLine, which returns a string.

image At line 5 is the class that we want to test. Later in this chapter, when you’re studying regex and formatting, we recommend that you use something like NewConsole to test the concepts that you’re learning.

The Console class has more capabilities than are covered here, but if you understand everything discussed so far, you’ll be in good shape for the exam.

CERTIFICATION OBJECTIVES

Files, Path, and Paths (OCP Objectives 8.1 and 8.2)

8.1 Operate on file and directory paths with the Path class.

8.2 Check, delete, copy, or move a file or directory with the Files class.

Note: For coverage of Serialization, see Appendix A.

The OCP 7 exam has two sections devoted to I/O. The previous section Oracle refers to as “Java I/O Fundamentals” (which we’ve referred to as the 7.x objectives), and it was focused on the java.io package. Now we’re going to look at the set of objectives Oracle calls “Java File I/O (NIO.2),” whose specific objectives we’ll refer to as 8.x. The term NIO.2 is a bit loosely defined, but most people (and the exam creators) define NIO.2 as being the key new features introduced in Java 7 that reside in two packages:

image java.nio.file

image java.nio.file.attribute

We’ll start by looking at the important classes and interfaces in the java.nio.file package, and then we’ll move to the java.nio.file.attribute package later in the chapter.

As you read earlier in the chapter, the File class represents a file or directory at a high level. NIO.2 adds three new central classes that you’ll need to understand well for the exam:

image Path This interface replaces File as the representation of a file or a directory when working in NIO.2. It is a lot more powerful than a File though.

image PathsThis class contains static methods that create Path objects. (In the next chapter, you’ll learn this is called a factory.)

image Files This class contains static methods that work with Path objects. You’ll find basic operations in here like copying or deleting files.

The interface java.nio.file.Path is one of the key classes of file-based I/O under NIO.2. Just like the good old java.io.File, a Path represents only a location in the file system, like C:\java\workspace\ocpjp7 (a Windows directory) or /home/nblack/docs (the docs directory of user nblack on UNIX). When you create a Path to a new file, that file does not exist until you actually create the file using Files.createFile(Path target). The Files utility class will be covered in depth in the next section.

Let’s take a look at these relationships another way. The Paths class is used to create a class implementing the Path interface. The Files class uses Path objects as parameters. All three of these are new to Java 7. Then there is the File class. It’s been around for a long time. File andPath objects know how to convert to the other. This lets any older code interact with the new APIs in Files. But notice what is missing. In the figure, there is no line between File and Files. Despite the similarity in name, these two classes do not know about each other.

image

image

The difference between File, Files, Path, and Paths is really important. Read carefully on the exam. A one-letter difference can mean a big difference in what the class does.

To make sure you know the difference between these key classes backward and forward, make sure you can fill in the four rightmost columns in Table 9-2.

TABLE 9-2 Comparing the Core Classes

image

Creating a Path

A Path object can be easily created by using the get methods from the Paths helper class. Remember you are calling Paths.get() and not Path.get(). If you don’t remember why, study the last section some more. It’s important to have this down cold.

Taking a look at two simple examples, we have:

image

The actual method we just called is Paths.get(String first, String… more). This means we can write it out by separating the parts of the path.

image

As you can see, you can separate out folder and filenames as much or as little as you want when calling Paths.get(). For Windows, that is particularly cool because you can make the code easier to read by getting rid of the backslash and escape character.

Be careful when creating paths. The previous examples are absolute paths since they begin with the root (/ on UNIX or c: on Windows). When you don’t begin with the root, the Path is considered a relative path, which means Java looks from the current directory. Which file1.txtdo you think p6 has in mind?

image

It depends. If the program is run from the root, it is the one in /tmp/file1.txt. If the program is run from /tmp, it is the one in /tmp/tmp/file1.txt. If the program is run from anywhere else, p6 refers to a file that does not exist.

One more thing to watch for. If you are on Windows, you might deal with a URL that looks like file:///c:/temp. The file:// is a protocol just like http:// is. This syntax allows you to browse to a folder in Internet Explorer. Your program might have to deal with such a Stringthat a user copied/pasted from the browser. No problem, right? We learned to code:

image

Unfortunately, this doesn’t work and you get an Exception about the colon being invalid that looks something like this:

image

Paths provides another method that solves this problem. Paths.get(URI uri) lets you (indirectly), convert the String to a URI (Uniform Resource Identifier) before trying to create a Path.

image

The last thing you should know is that the Paths.get() method we’ve been discussing is really a shortcut. You won’t need to code the longer version, but it is good to understand what is going on under the hood. First, Java finds out what the default file system is. For example, it might be WindowsFileSystemProvider. Then Java gets the path using custom logic for that file system. Luckily, this all goes on without us having to write any special code or even think about it.

image

Now that you know how to create a Path instance, you can manipulate it in various ways. We’ll get back to that in a bit.

image

As far as the exam is concerned, Paths.get() is how to create a Path initially. There is another way that is useful when working with code that was written before Java 7:

image

If you are updating older code that uses File, you can convert it to a Path and start calling the new classes. And if your newer code needs to call older code, it can convert back to a File.

Creating Files and Directories

With I/O, we saw that a File doesn’t exist just because you have a File object. You have to call createNewFile()to bring the file into existence and exists()to check if it exists. Rewriting the example from earlier in the chapter to use NIO.2 methods, we now have:

image

NIO.2 has equivalent methods with two differences:

image You call static methods on Files rather than instance methods on File.

image Method names are slightly different.

See Table 9-3 for the mapping between old class/method names and new ones. You can still continue to use the older I/O approach if you happen to be dealing with File objects.

TABLE 9-3 I/O vs. NIO.2

image

image

There is a new method Files.notExists() to supplement Files.exists(). In some incredibly rare situations, Java won’t have enough permissions to know whether the file exists. When this happens, both methods return false.

You can also create directories in Java. Suppose we have a directory named /java and we want to create the file /java/source/directory/Program.java. We could do this one at a time:

image

Or we could create all the directories in one go:

image

While both work, the second is clearly better if you have a lot of directories to create. And remember that the directory needs to exist by the time the file is created.

Copying, Moving, and Deleting Files

We often copy, move, or delete files when working with the file system. Up until Java 7, this was hard to do. In Java 7, however, each is one line. Let’s look at some examples:

image

This is all pretty self-explanatory. We copy a file, delete the copy, and then move the file. Now, let’s try another example:

image

Java sees it is about to overwrite a file that already exists. Java doesn’t want us to lose the file, so it “asks” if we are sure by throwing an Exception. copy()and move() actually take an optional third parameter—zero or more CopyOptions. The most useful option you can pass isStandardCopyOption.REPLACE_EXISTING.

image

We have to think about whether a file exists when deleting the file too. Let’s say we wrote this test code:

image

We don’t know whether methodUnderTest works properly yet. If it does, the code works fine. If it throws an Exception, we never create the file and Files.delete() throws a NoSuchFileException. This is a problem, as we only want to delete the file if it was created so we aren’t leaving stray files around. There is an alternative. Files.deleteIfExists(path) returns true and deletes the file only if it exists. If not, it just quietly returns false. Most of the time, you can ignore this return value. You just want the file to not be there. If it never existed, mission accomplished.

image

If you have to work on pre-Java 7 code, you can use the FileUtils class in Apache Commons IO (http://commons.apache.org/io.) It has methods similar to many of the copy, move, and delete methods that are now built into Java 7.

To review, Table 9-4 lists the methods on Files that you are likely to come across on the exam. Luckily, the exam doesn’t expect you to know all 30 methods in the API. The important thing to remember is to check the Files JavaDoc when you find yourself dealing with files.

TABLE 9-4 Files Methods

image

Retrieving Information about a Path

The Path interface defines a bunch of methods that return useful information about the path that you’re dealing with. In the following code listing, a Path is created referring to a directory and then we output information about the Path instance:

image

When you execute this code snippet on Windows, the following output is printed:

image

Based on this output, it is fairly simple to describe what each method does. Table 9-5 does just that.

TABLE 9-5 Path Methods

image

Here is yet another interesting fact about the Path interface: It extends from Iterable<Path>. At first sight, this seems anything but interesting. But every class that (correctly) implements the Iterable<?> interface can be used as an expression in the enhanced for loop. So you know you can iterate through an array or a List, but you can iterate through a Path as well. That’s pretty cool!

Using this functionality, it’s easy to print the hierarchical tree structure of a file (or directory), as the following example shows:

image

When you run this example, a (simplistic) tree is printed. Thanks to the variable spaces (which is increased with each iteration by 2), the different subpaths are printed like a directory tree.

image

Normalizing a Path

Normally (no pun intended), when you create a Path, you create it in a direct way. However, all three of these return the same logical Path:

image

p1 is probably what you would type if you were coding. p2 is just plain redundant. p3 is more interesting. The two directories—anotherDirectory and myDirectory—are on the same level, but we have to go up one level to get there:

image

You might be wondering why on earth we wouldn’t just type myDirectory in the first place. And you would if you could. Sometimes, that doesn’t work out. Let’s look at a real example of why this might be.

image

If you wanted to compile MyClass, you would cd to /My_Project/source and run javac MyClass.java. Once your program gets bigger, it could be thousands of classes and have hundreds of jar files. You don’t want to type in all of those just to compile, so someone writes a script to build your program. buildScript.sh now finds everything that is needed to compile and runs the javac command for you. The problem is that the current directory is now /Build_Project/scripts and not /My_Project/source. The build script helpfully builds a path for you by doing something like this:

image

which outputs:

image

Whew. The second one is much easier to read. The normalize() method knows that a single dot can be ignored. It also knows that any directory followed by two dots can be removed from a path.

Be careful when using this normalize()! It just looks at the String equivalent of the path and doesn’t check the file system to see whether the directories or files actually exist.

Let’s practice and see what normalize returns for these paths. This time, we aren’t providing a directory structure to show that the directories and files don’t need to be present on the computer. What do you think the following prints out?

image

The output is:

image

The first one removes all the single dots since they just point to the current directory. The second doesn’t change anything since the dot is part of a filename and not a directory. The third sees one set of double dots, so it only goes up one directory. The last one is a little tricky. The two dots do say to go up one directory. But since there isn’t a directory before it, Path can’t simplify it.

To review, normalize() removes unneeded parts of the Path, making it more like you’d normally type it. (That’s not where the word “normalize” comes from, but it is a nice way to remember it.)

Resolving a Path

So far, you have an overview of all methods that can be invoked on a single Path object, but what if you need to combine two paths? You might want to do this if you have one Path representing your home directory and another containing the Path within that directory.

image

This produces the absolute path by merging the two paths:

image

path1.resolve(path2) should be read as “resolve path2 within path1’s directory.” In this example, we resolved the path of the file within the directory provided by dir.

Keeping this definition in mind, let’s look at some more complex examples:

image

The output is:

image

The first three do what you’d expect. They add the parameter to resolve to the provided path object. The fourth and fifth ones try to resolve an absolute path within the context of something else. The problem is that an absolute path doesn’t depend on other directories. It is absolute. Therefore, resolve just returns that absolute path. The sixth one tries to resolve a directory within the context of a file. Since that doesn’t make any sense, Java just tries its best and gives you nonsense.

Just like normalize(), keep in mind that resolve() will not check that the directory or file actually exists. To review, resolve() tells you how to resolve one path within another.

image

Be careful with methods that come in two flavors: one with a Path parameter and the other with a String parameter such as resolve(). The tricky part here is that null is a valid value for both a Path and a String. What will happen if you pass just null as a parameter? Which method will be invoked?

image

The compiler can’t decide which method to invoke: the one with the Path parameter or the other one with the String parameter. That’s why this code won’t compile, and if you see such code in an exam question, you’ll know what to do.

The following examples will compile without any problem, because the compiler knows which method to invoke thanks to the type of the variable other and the explicit cast to String.

image

Relativizing a Path

Now suppose we want to do the opposite of resolve. We have the absolute path of our home directory and the absolute path of the music file in our home directory. We want to know just the music file directory and name.

image

The output is: country/Swift.mp3. Java recognized that the /home/java part is the same and returned a path of just the remainder.

path1.relativize(path2) should be read as “give me a path that shows how to get from path1 to path2.” In this example, we determined that music is a file in a directory named country within dir.

Keeping this definition in mind, let’s look at some more complex examples:

image

The output is

image

Before you scratch your head, let’s look at the logical directory structure here. Keep in mind the directory doesn’t actually need to exist; this is just to visualize it.

image

Now we can trace it through. The first example is straightforward. It tells us how to get to absolute3 from absolute1 by going down two directories. The second is similar. We get to absolute1 from absolute3 by doing the opposite—going up two directories. Remember fromnormalize() that a double dot means to go up a directory.

The third output statement says that we have to go up two directories and then down two directories to get from absolute1 to absolute2. Java knows this since we provided absolute paths. The worst possible case is to have to go all the way up to the root like we did here.

The fourth output statement is okay. Even though they are both relative paths, there is enough in common for Java to tell what the difference in path is.

The fifth example throws an exception. Java can’t figure out how to make a relative path out of one absolute path and one relative path.

Remember, relativize() and resolve() are opposites. And just like resolve(), relativize() does not check that the path actually exists. To review, relativize() tells you how to get a relative path between two paths.

CERTIFICATION OBJECTIVES

File and Directory Attributes (OCP Objective 8.3)

8.3 Read and change file and directory attributes, focusing on the BasicFileAttributes, DosFileAttributes, and PosixFileAttributes interfaces.

Reading and Writing Attributes the Easy Way

In this section, we’ll add classes and interfaces from the java.nio.file.attribute package to the discussion. Prior to NIO.2, you could read and write just a handful of attributes. Just like we saw when creating files, there is a new way to do this using Files instead of File. Oracle also took the opportunity to clean up the method signatures a bit. The following example creates a file, changes the last modified date, prints it out, and deletes the file using both the old and new method names. We might do this if we want to make a file look as if it were created in the past. (As you can see, there is a lesson about not relying on file timestamps here!)

image

As you can see from the output, the only change in functionality is that the new Files.getLastModifiedTime() uses a human-friendly date format.

image

The other common type of attribute you can set are file permissions. Both Windows and UNIX have the concept of three types of permissions. Here’s what they mean:

image Read You can open the file or list what is in that directory.

image Write You can make a change to the file or add a file to that directory.

image Execute You can run the file if it is a runnable program or go into that directory.

Printing out the file permissions is easy. Note that these permissions are just for the user who is running the program—you! There are other types of permissions as well, but these can’t be set in one line.

image

Table 9-6 shows how to get and set these attributes that can be set in one line, both using the older I/O way and the new Files class. You may have noticed that setting file permissions isn’t in the table. That’s more code, so we will talk about it later.

TABLE 9-6 I/O vs. NIO.2 Permissions

image

Types of Attribute Interfaces

The attributes you set by calling methods on Files are the most straightforward ones. Beyond that, Java NIO.2 added attribute interfaces so that you could read attributes that might not be on every operating system.

image BasicFileAttributes In the JavaDoc, Oracle says these are “attributes common to many file systems.” What they mean is that you can rely on these attributes being available to you unless you are writing Java code for some funky new operating system. Basic attributes include things like creation date.

image PosixFileAttributes POSIX stands for Portable Operating System Interface. This interface is implemented by both UNIX-and Linux-based operating systems. You can remember this because POSIX ends in “x,” as do UNIX and Linux.

image DosFileAttributes DOS stands for Disk Operating System. It is part of all Windows operating systems. Even Windows 8 has a DOS prompt available.

There are also separate interfaces for setting or updating attributes. While the details aren’t in scope for the exam, you should be familiar with the purpose of each one.

image BasicFileAttributeView Used to set the last updated, last accessed, and creation dates.

image PosixFileAttributeView Used to set the groups or permissions on UNIX/Linux systems. There is an easier way to set these permissions though, so you won’t be using the attribute view.

image DosFileAttributeView Used to set file permissions on DOS/Windows systems. Again, there is an easier way to set these, so you won’t be using the attribute view.

image FileOwnerAttributeView Used to set the primary owner of a file or directory.

image AclFileAttributeView Sets more advanced permissions on a file or directory.

Working with BasicFileAttributes

The BasicFileAttributes interface provides methods to get information about a file or directory.

image

The sample output shows that all three date/time values can be different. A file is created once. It can be modified many times. And it can be last accessed for reading after that. The isDirectory method is the same as Files.isDirectory(path). It is just an alternative way of getting the same information.

image

There are some more attributes on BasicFileAttributes, but they aren’t on the exam and you aren’t likely to need them when coding. Just remember to check the JavaDoc if you need more information about a file.

So far, you’ve noticed that all the attributes are read only. That is because Java provides a different interface for updating attributes. Let’s write code to update the last accessed time:

image

In this example, we demonstrated getting all three times. In practice, when calling setTimes(), you should pass null values for any of the times you don’t want to change, and only pass Filetimes for the times you want to change.

The key takeaways here are that the “XxxFileAttributes” classes are read only and the “XxxFileAttributeView” classes allow updates.

image

The BasicFileAttributes and BasicFileAttributeView interfaces are a bit confusing. They have similar names but different functionality, and you get them in different ways. Try to remember these three things:

image BasicFileAttributeView is singular, but BasicFileAttributes is not.

image You get BasicFileAttributeView using Files .getFileAttributeView, and you get BasicFileAttributes using Files.readAttributes.

image You can ONLY update attributes in BasicFileAttributeView, not in BasicFileAttributes. Remember that the view is for updating.

PosixFileAttributes and DosFileAttributes inherit from BasicFileAttributes. This means that you can call Basic methods on a POSIX or DOS subinterface.

image

Try to use the more general type if you can. For example, if you are only going to use basic attributes, just get BasicFileAttributes. This lets your code remain operating system independent. If you are using a mix of basic and POSIX attributes, you can use PosixFileAttributesdirectly rather than calling readAttributes() twice to get two different ones.

Working with DosFileAttributes

DosFileAttributes adds four more attributes to the basics. We’ll look at the most common ones here—hidden files and read-only files. Hidden files typically begin with a dot and don’t show up when you type dir to list the contents of a directory. Read-only files are what they sound like—files that can’t be updated. (The other two attributes are “archive” and “system,” which you are quite unlikely to ever use.)

image

The output is:

image

The first tricky thing in this code is that the String “readonly” is lowercase even though the method name is mixed case. If you forget and use the String “readOnly,” Java will silently ignore the statement and the file will still allow anyone to write to it.

The other tricky thing is that you cannot delete a read-only file. That’s why the code calls setAttribute a second time with false as a parameter, to make it no longer “read only” so the code can clean up after itself. And you can see that we had to call readAttributes again to see those updated values.

image

There is an alternative way to set these attributes where you don’t have to worry about the String values. However, the exam wants you to know how to use Files. It is good to know both ways, though.

image

Working with PosixFileAttributes

PosixFileAttributes adds two more attributes to the basics—groups and permissions. On UNIX, every file or directory has both an owner and group name.

UNIX permissions are also more elaborate than the basic ones. Each file or directory has nine permissions set in a String. A sample is “rwxrw-r--.” Breaking this into groups of three, we have “rwx”, “rw-,” and “r--.” These sets of permissions correspond to who gets them. In this example, the “user” (owner) of the file has read, write, and execute permissions. The “group” only has read and write permissions. UNIX calls everyone who is not the owner or in the group “other.” “Other” only has read access in this example.

Now let’s look at some code to set the permissions and output them in human-readable form:

image

The output looks like this:

image

It’s not symmetric. We gave Java the permissions in cryptic UNIX format and got them back in plain English. You can also output the group name:

image

which outputs something like this:

image

Reviewing Attributes

Let’s review the most common attributes information in Table 9-7.

TABLE 9-7 Common Attributes

image

CERTIFICATION OBJECTIVES

DirectoryStream (OCP Objective 8.4)

8.4 Recursively access a directory tree using the DirectoryStream and FileVisitor interfaces.

Now let’s return to more NIO.2 capabilities that you’ll find in the java.nio.file package… You might need to loop through a directory. Let’s say you were asked to list out all the users with a home directory on this computer.

image

As expected, this outputs

image

The DirectoryStream interface lets you iterate through a directory. But this is just the tip of the iceberg. Let’s say we have hundreds of users and each day we want to only report on a few of them. The first day, we only want the home directories of users whose names begin with either the letter v or the letter w.

image

This time, the output is

image

Let’s examine the expression [vw]*. [vw] means either of the characters v or w. The * is a wildcard that means zero or more of any character. Notice this is not a regular expression. (If it were, the syntax would be [vw].*—see the dot in there.) DirectoryStream uses something new called a glob. We will see more on globs later in the chapter.

There is one limitation with DirectoryStream. It can only look at one directory. One way to remember this is that it works like the dir command in DOS or the ls command in UNIX. Or you can remember that DirectoryStream streams one directory.

CERTIFICATION OBJECTIVES

FileVisitor (OCP Objective 8.4)

8.4 Recursively access a directory tree using the DirectoryStream and FileVisitor interfaces.

Luckily, there is another class that does, in fact, look at subdirectories. Let’s say you want to get rid of all the .class files before zipping up and submitting your assignment. You could go through each directory manually, but that would get tedious really fast. You could write a complicated command in Windows and another in UNIX, but then you’d have two programs that do the same thing. Luckily, you can use Java and only write the code once.

Java provides a SimpleFileVisitor. You extend it and override one or more methods. Then you can call Files.walkFileTree, which knows how to recursively look through a directory structure and call methods on a visitor subclass. Let’s try our example:

image

This is a simple file visitor. It only implements one method: visitFile. This method is called for every file in the directory structure. It checks the extension of the file and deletes it if appropriate. In our case, two .class files are deleted.

There are two parameters to visitFile(). The first one is the Path object representing the current file. The other is a BasicFileAttributes interface. Do you remember what this does? That’s right—it lets you find out if the current file is a directory, when it was created, and many other similar pieces of data.

Finally, visitFile()returns FileVisitResult.CONTINUE. This tells walkFileTree() that it should keep looking through the directory structure for more files.

Now that we have a feel for the power of this class, let’s take a look at all the methods available to us with another example:

image

You might get the following output:

image

Note that Java goes down as deep as it can before returning back up the tree. This is called a depth-first search. We said “might” because files and directories at the same level can get visited in either order.

You can override as few or many of the four methods as you’d like. Note that the second half of the methods have IOException as a parameter. This allows those methods to handle problems that came earlier when walking through the tree. Table 9-8 summarizes the methods.

TABLE 9-8 FileVisitor Methods

image

You actually do have some control, though, through those FileVisitResult constants. Suppose we changed the preVisitDirectory method to the following:

image

Now the output is:

image

Since we instructed the program to skip the entire child subtree—i.e., we don’t see the file: b.txt or the sub-directory: grandchild—we also don’t see the post visit call.

Now what do you think would happen if we changed FileVisitResult. SKIP_SIBLINGS to FileVisitResult . TERMINATE? The output might be:

image

We see that as soon as the “child” directory came up, the program stopped walking the tree. And again, we are using “might” in terms of the output. It’s also possible for emptyChild to come up first, in which case, the last line of the output would be /home/emptyChild.

There’s one more result type. What do you think would happen if we changed FileVisitResult. TERMINATE to FileVisitResult.SKIP_SIBLINGS? The output happens to be the same as the previous example:

image

SKIP_SIBLINGS is a combination of SKIP_SUBTREE and “don’t look in any folders at the same level.” This means we skip everything under child and also skip emptyChild.

One more example to make sure you really understand what is going on. What do you think gets output if we use this method?

image

Assuming child is encountered before emptyChild, the output is:

image

We don’t see file: c.txt or post:/home/child/grandchild because we skip grandchild the subtree. We don’t see “post:/home/emptyChild” because we skip siblings of emptyChild. But wait. Isn’t /home/child a sibling? It is. But the visitor goes in order. Since child was seen beforeemptyChild, it is too late to skip it. Just like when you print a document, it is too late to prevent pages from printing that have already printed. File visitor can only skip subtrees that it has not encountered yet.

CERTIFICATION OBJECTIVES

PathMatcher (OCP Objective 8.5)

8.5 Find a file with PathMatcher interface.

DirectoryStream and FileVisitor allowed us to go through the files that exist. Things can get complicated fast, though. Imagine you had a requirement to print out the names of all text files in any subdirectory of “password.” You might be wondering why anyone would want to do this. Maybe a teammate foolishly stored passwords for everyone to see and you want to make sure nobody else did that. You could write logic to keep track of the directory structure, but that makes the code harder to read and understand. By the end of this section, you’ll know a better way.

Let’s start out with a simpler example to see what a PathMatcher can do:

image

which outputs:

image

We can see that the code checks if a Path consists of any characters followed by “.txt.” To get a PathMatcher, you have to call FileSystems.getDefault() .getPathMatcher because matching works differently on different operating systems. PathMatchers use a new type that you probably haven’t seen before called a glob. Globs are not regular expressions, although they might look similar at first. Let’s look at some more examples of globs using a common method so we don’t have to keep reading the same “boilerplate” code. (Boilerplate code is the part of the code that is always the same.)

image

In the world of globs, one asterisk means “match any character except for a directory boundary.” Two asterisks means “match any character, including a directory boundary.”

image

image

Remember that we are using a file system–specific PathMatcher. This means slashes and backslashes can be treated differently, depending on what operating system you happen to be running. The previous example does print the same output on both Windows and UNIX because it uses forward slashes.

However, if you change just one line of code, the output changes:

image

Now Windows still prints:

image

However, UNIX prints:

image

Why? Because UNIX doesn’t see the backslash as a directory boundary. The lesson here is to use / instead of \\ so your code behaves more predictably across operating systems.

Now let’s match files with a four-character extension. A question mark matches any character. A character could be a letter or a number or anything else.

image

Globs also provide a nice way to match multiple patterns. Suppose we want to match anything that begins with the names Kathy or Bert:

image

The first glob shows we can put wildcards inside braces to have multiple glob expressions. The second glob shows that we can put common wildcards outside the braces to share them. The third glob shows that without the wildcard, we will only match the literal strings “Bert” and “Kathy.”

You can also use sets of characters like [a-z] or [#$%] in globs just like in regular expressions. You can also escape special characters with a backslash. Let’s put this all together with a tricky example:

image

Spelling out what the glob does, we have the following:

image [0-9] One single digit. Can also be read as any one character from 0 to 9.

image \\* The literal character asterisk rather than the asterisk that means to match anything. A single backslash before * escapes it. However, Java won’t let you type a single backslash, so you have to escape the backslash itself with another backslash.

image {A*, b}Either a capital A followed by anything or the single character b.

image /**/ One or more directories with any name.

image 1 The single character 1.

The second path doesn’t match because it has the literal backslash followed by the literal asterisk. The glob was looking for the literal asterisk by itself. The third path also doesn’t match because there is no literal asterisk. The fourth path doesn’t match because there is no directory between “b” and “1” for the ** to match. Luckily, nobody would write such a crazy, meaningless glob. But if you can understand this one, you are all set. Globs tend to be simple expressions like {*.txt,*.html} when used for real.

Since globs are just similar enough to regular expressions to be tricky, Table 9-10 reviews the similarities and differences in common expressions. Regular expressions are more powerful, but globs focus on what you are likely to need when matching filenames.

TABLE 9-10 Glob vs. Regular Expression

image

By now, you’ve probably noticed that we are dealing with Path objects, which means they don’t actually need to exist on the file system. But we wanted to print out all the text files that actually exist in a subdirectory of password. Luckily, we can combine the power ofPathMatchers with what we already know about walking the file tree to accomplish this.

image

The code looks similar, regardless of what you want to do. You just change the glob pattern to what you actually want to match.

CERTIFICATION OBJECTIVES

WatchService (OCP Objective 8.6)

8.6 Watch a directory for changes with the WatchServiceinterface.

The last thing you need to know about in NIO.2 is WatchService. Suppose you are writing an installer program. You check that the directory you are about to install into is empty. If not, you want to wait until the user manually deletes that directory before continuing. Luckily, you won’t have to write this code from scratch, but you should be familiar with the concepts. Here’s the directory tree:

image

Here’s the code snippet:

image

Supposing we delete directory “other” followed by directory directoryToDelete, this outputs:

image

Notice that we had to watch the directory that contains the files or directories we are interested in. This is why we watched /dir instead of /dir/directoryToDelete. This is also why we had to check the context to make sure the directory we were actually interested in is that one that was deleted.

The basic flow of WatchService stays the same, regardless of what you want to do:

1. Create a new WatchService

2. Register it on a Path listening to one or more event types

3. Loop until you are no longer interested in these events

4. Get a WatchKey from the WatchService

5. Call key.pollEvents and do something with the events

6. Call key.reset to look for more events

Let’s look at some of these in more detail. You register the WatchService on a Path using statements like the following:

image

(Note: These ENTRY_XXX constants can be found in the StandardWatchEventsKinds class. Here and in later code, you’ll probably want to create static imports for these constants.) You can register one, two, or three of the event types. ENTRY_DELETE means you want your program to be informed when a file or directory has been deleted. Similarly, ENTRY_CREATE means a new file or directory has been created. ENTRY_MODIFY means a file has been edited in the directory. These changes can be made manually by a human or by another program on the computer.

Renaming a file or directory is interesting, as it does not show up as ENTRY_MODIFY. From Java’s point of view, a rename is equivalent to creating a new file and deleting the original. This means that two events will trigger for a rename—both ENTRY_CREATE and ENTRY_DELETE. Actually editing a file will show up as ENTRY_MODIFY.

To loop through the events, we use while(true). It might seem a little odd to write a loop that never ends. Normally, there is a break or return statement in the loop so you stop looping once whatever event you were waiting for has occurred. It’s also possible you want the program to run until you kill or terminate it at the command line.

Within the loop, you need to get a WatchKey. There are two ways to do this. The most common is to call take(), which waits until an event is available. It throws an InterruptedException if it gets interrupted without finding a key. This allows you to end the program. The other way is to call poll(), which returns null if an event is not available. You can provide optional timeout parameters to wait up to a specific period of time for an event to show up.

image

Next, you loop through any events on that key. In the case of rename, you’ll get one key with two events—the EVENT_CREATE and EVENT_DELETE. Remember that you get all the events that happened since the last time you called poll()or take(). This means you can get multiple seemingly unrelated events out of the same key. They can be from different files but are for the same WatchService.

image

Finally, you call key.reset(). This is very important. If you forget to call reset, the program will work for the first event, but then you will not be notified of any other events.

image

There are a few limitations you should be aware of with WatchService. To begin with, it is slow. You could easily wait five seconds for the event to register. It also isn’t 100 percent reliable. You can add code to check if kind == OVERFLOW, but that just tells you something went wrong. You don’t know what events you lost. In practice, you are unlikely to use WatchService.

WatchService only watches the files and directories immediately beneath it. What if we want to watch to see if either p.txt or c.txt is modified?

image

One way is to register both directories:

image

This works. You can type in all the directories you want to watch. If we had a lot of child directories, this would quickly get to be too much work. Instead, we can have Java do it for us:

image

This code goes through the file tree recursively registering each directory with the watcher. The NIO.2 classes are designed to work together. For example, we could add PathMatcher to the previous example to only watch directories that have a specific pattern in their path.

CERTIFICATION OBJECTIVES

Serialization (Objective 7.2)

7.2 Use streams to read from and write to files by using classes in the java.io package, including BufferedReader, BufferedWriter, File, FileReader, FileWriter, DataInputStream, DataOutputStream, ObjectOutputStream, ObjectInputStream, and PrintWriter.

Over time, Oracle has fine-tuned the objectives of the OCP 7 exam. Serialization was a topic on the old SCJP 5 and SCJP 6 exams, and recently (as of the summer of 2014), Oracle reintroduced serialization for the OCP 7 exam. Please see Appendix A for in-depth, complete chapter coverage of serialization, right down to a self-test.

CERTIFICATION SUMMARY


File I/O Remember that objects of type File can represent either files or directories but that until you call createNewFile() or mkdir() you haven’t actually created anything on your hard drive. Classes in the java.io package are designed to be chained together. It will be rare that you’ll use a FileReader or a FileWriter without “wrapping” them with a BufferedReader or BufferedWriter object, which gives you access to more powerful, higher-level methods. As of Java 5, the PrintWriter class has been enhanced with advanced append(), format(), and printf()methods, and when you couple that with new constructors that allow you to create PrintWriters directly from a String name or a File object, you may use BufferedWriters a lot less. The Console class allows you to read nonechoed input (returned in a char[?]), and is instantiated using System.console().

NIO.2 Objects of type Path can be files or directories and are a replacement of type File. Paths are created with Paths.get(). Utility methods in Files allow you to create, delete, move, copy, or check information about a Path. In addition, BasicFileAttributes, DosFileAttributes(Windows), and PosixFileAttributes (UNIX/Linux/Mac) allow you to check more advanced information about a Path. BasicFileAttributeView, DosFileAttributeView, and PosixFileAttributeView allow you to update advanced Path attributes.

Using a DirectoryStream allows you to iterate through a directory. Extending SimpleFileVisitor lets you walk a directory tree recursively looking at files and/or directories. With a PathMatcher, you can search directories for files using regex-esqu expressions called globs.

Finally, registering a WatchService provides notifications for new/changed/removed files or directories.

image TWO-MINUTE DRILL

Here are some of the key points from the certification objectives in this chapter.

File I/O (OCP Objectives 7.1 and 7.2)

image The classes you need to understand in java.io are File, FileReader, BufferedReader, FileWriter, BufferedWriter, PrintWriter, and Console.

image A new File object doesn’t mean there’s a new file on your hard drive.

image File objects can represent either a file or a directory.

image The File class lets you manage (add, rename, and delete) files and directories.

image The methods createNewFile() and mkdir() add entries to your file system.

image FileWriter and FileReader are low-level I/O classes. You can use them to write and read files, but they should usually be wrapped.

image Classes in java.io are designed to be “chained” or “wrapped.” (This is a common use of the decorator design pattern.)

image It’s very common to “wrap” a BufferedReader around a FileReader or a BufferedWriter around a FileWriter to get access to higher-level (more convenient) methods.

image PrintWriters can be used to wrap other Writers, but as of Java 5, they can be built directly from Files or Strings.

image As of Java 5, PrintWriters have new append(), format(), and printf() methods.

image Console objects can read nonechoed input and are instantiated using System.console().

Path, Paths, and File (OCP Objectives 8.1 and 8.2)

image NIO.2 was introduced in Java 7.

image Path replaces File for a representation of a file or directory.

image Paths.get() lets you create a Path object.

image Static methods in Files let you work with Path objects.

image A Path object doesn’t mean the file or directory exists on your hard drive.

image The methods Files.createFile() and Files.createDirectory() add entries to your file system.

image The Files class provides methods to move, copy, and delete Path objects.

image Files.delete() throws an Exception if the file does not exist and Files.deleteIfExists() returns false.

image On Path, normalize() simplifies the path representation.

image On Path, resolve() and relativize()work with the relationship between two path objects.

File Attributes (OCP Objective 8.3)

image The Files class provides methods for common attributes such as whether the file is executable and when it was last modified.

image For less common attributes the classes: BasicFileAttributes, DosFileAttributes, and PosixFileAttributes read the attributes.

image DosFileAttributes works on Windows operating systems.

image PosixFileAttributes works on UNIX, Linux, and Mac operating systems.

image Attributes that can’t be updated via the Files class are set using the classes: BasicFileAttributeView, DosFileAttributeView, PosixFileAttributeView, FileOwnerAttributeView, and AclFileAttributeView.

Directory Trees, Matching, and Watching for Changes (OCP Objectives 8.4, 8.5, and 8.6)

image DirectoryStream iterates through immediate children of a directory using glob patterns.

image FileVisitor walks recursively through a directory tree.

image You can override one or all of the methods of SimpleFileVisitor—preVisitDirectory, visitFile, visitFileFailed, and postVisitDirectory.

image You can change the flow of a file visitor by returning one of the FileVisitResult constants: CONTINUE, SKIP_SUBTREE, SKIP_SIBLINGS, or TERMINATE.

image PathMatcher checks if a path matches a glob pattern.

image Know what the following expressions mean for globs: *, **, ?, and {a, b}.

image Directories register with WatchService to be notified about creation, deletion, and modification of files or immediate subdirectories.

image PathMatcher and WatchService use FileSystem-specific implementations.

SELF TEST

The following questions will help you measure your understanding of the material presented in this chapter. Read all of the choices carefully, as there may be more than one correct answer. Choose all correct answers for each question. Stay focused.

1. Note: The use of “drag-and-drop” questions has come and gone over the years. In case Oracle brings them back into fashion, we threw a couple of them in the book.

Using the fewest fragments possible (and filling the fewest slots possible), complete the following code so that the class builds a directory named “dir3” and creates a file named “file3” inside “dir3.” Note you can use each fragment either zero or one times.

Code:

image

Fragments:

image

2. Given:

image

and that the invocation

image

is issued from a directory that has two subdirectories, “dir1” and “dir2,” and that “dir1” has a file “file1.txt” and “dir2” has a file “file2.txt,” and the output is “false true,” which set(s) of code fragments must be inserted? (Choose all that apply.)

A. String path = d;
System.out.print(file.exists() + ““);

B. String path = d;
System.out.print(file.isFile() + ““);

C. String path = File.separator + d;
System.out.print(file.exists() + ““);

D. String path = File.separator + d;
System.out.print(file.isFile() + ““);

3. Given:

image

And given that myfile.txt contains the following two lines of data:

ab
cd

What is the result?

A. ab

B. abcd

C. ab cd

D. a

b

c

d

E. Compilation fails

4. Given:

image

If line 6 creates a valid Console object and if the user enters fred as a username and 1234 as a password, what is the result? (Choose all that apply.)

A. username:

password:

B. username: fred

password:

C. username: fred

password: 1234

D. Compilation fails

E. An Exception is thrown at runtime

5. This question is about serialization, which Oracle reintroduced to the OCP 7 exam and is covered in Appendix A.

Given:

image

Instances of which class(es) can be serialized? (Choose all that apply.)

A. Car

B. Ford

C. Dodge

D. Wheels

E. Vehicle

6. Which of the following creates a Path object pointing to c:/temp/exam? (Choose all that apply.)

A. new Path(“c:/temp/exam”)

B. new Path(“c:/temp”, “exam”)

C. Files.get(“c:/temp/exam”)

D. Files.get(“c:/temp”, “exam”)

E. Paths.get(“c:/temp/exam”)

F. Paths.get(“c:/temp”, “exam”)

7. Given a directory tree at the root of the C: drive and the fact that no other files exist:

image

and these two paths:

image

Which of the following statements prints out: y/a?

A. System.out.println(one.relativize(two));

B. System.out.println(two.relativize(one));

C. System.out.println(one.resolve(two));

D. System.out.println(two.resolve(one));

E. System.out.println (two.resolve(two));

F. None of the above

8. Given the following statements:

I. A nonempty directory can usually be deleted using Files.delete

II. A nonempty directory can usually be moved using Files.move

III. A nonempty directory can usually be copied using Files.copy

Which of the following is true?

A. I only

B. II only

C. III only

D. I and II only

E. II and III only

F. I and III only

G. I, II, and III

9. Given:

new File("c:/temp/test.txt").delete();

How would you write this line of code using Java 7 APIs?

A. Files.delete(Paths.get(“c:/temp/test.txt”));

B. Files.deleteIfExists(Paths.get(“c:/temp/test.txt”));

C. Files.deleteOnExit(Paths.get(“c:/temp/test.txt”));

D. Paths.get(“c:/temp/test.txt”).delete();

E. Paths.get(“c:/temp/test.txt”).deleteIfExists();

F. Paths.get(“c:/temp/test.txt”).deleteOnExit();

10. Given:

image

Which code inserted at // CODE HERE will compile and run without error on Windows? (Choose all that apply.)

A. BasicFileAttributes attr = Files.readAttributes(dir, BasicFileAttributes.class);

B. BasicFileAttributes attr = Files.readAttributes(dir, DosFileAttributes.class);

C. DosFileAttributes attr = Files.readAttributes(dir, BasicFileAttributes.class);

D. DosFileAttributes attr = Files.readAttributes(dir, DosFileAttributes.class);

E. PosixFileAttributes attr = Files.readAttributes(dir, PosixFileAttributes.class);

F. BasicFileAttributes attr = new BasicFileAttributes(dir);

G. BasicFileAttributes attr =dir.getBasicFileAttributes();

11. Which of the following are true? (Choose all that apply.)

A. The class AbstractFileAttributes applies to all operating systems

B. The class BasicFileAttributes applies to all operating systems

C. The class DosFileAttributes applies to Windows-based operating systems

D. The class WindowsFileAttributes applies to Windows-based operating systems

E. The class PosixFileAttributes applies to all Linux/UNIX-based operating systems

F. The class UnixFileAttributes applies to all Linux/UNIX-based operating systems

12. Given a partial directory tree:

image

In what order can the following methods be called if walking the directory tree from x? (Choose all that apply.)

I: preVisitDirectory x

II: preVisitDirectory x/y

III: postVisitDirectory x/y

IV: postVisitDirectory x

V: visitFile x/a

A. I, II, III, IV, V

B. I, II, III, V, IV

C. I, V, II, III, IV

D. I, V, II, IV, III

E. V, I, II, III, IV

F. V, I, II, VI, III

13. Given:

image

Which code inserted at // CODE HERE would cause the FileVisitor to stop visiting files after it sees the file Test.java?

A.return FileVisitResult.CONTINUE;

B.return FileVisitResult.END;

C.returnFileVisitResult.SKIP_SIBLINGS;

D.return FileVisitResult.SKIP_SUBTREE;

E.return FileVisitResult.TERMINATE;

F.return null;

14. Assume all the files referenced by these paths exist:

image

What is the correct string to pass to PathMatcher to match both these files?

A. “glob:*/*.txt”

B. “glob:**.txt”

C. “glob:*.txt”

D. “glob:/*/*.txt”

E. “glob:/**.txt”

F. “glob:/*.txt”

G. None of the above

15. Given a partial directory tree at the root of the drive:

image

And the following snippet:

image

What is the result?

A. c:/x/a.txt

B. c:/x/a.txt c:/x/y/b.txt c:/x/y/z/c.txt

C. Code compiles but does not output anything

D. Does not compile because DirectoryStream comes from FileSystems, not Files

E. Does not compile for another reason

16. Given a partial directory tree:

image

and given that a valid Path object, dir, points to x, and given this snippet:

image

If a WatchService is set using the given WatchKey, what would be the result if a file is added to dir y?

A. No notice is given

B. A notice related to dir x is issued

C. A notice related to dir y is issued

D. Notices for both dir x and dir y are given

E. An Exception is thrown

F. The behavior depends on the underlying operating system

SELF TEST ANSWERS

1. image Answer:

image

Notes: The new File statements don’t make actual files or directories, just objects. You need the mkdir()and createNewFile()methods to actually create the directory and the file. While drag-and-drop questions are no longer on the exam, it is still good to be able to complete them. (OCP Objective 7.2)

2. image A and B are correct. Because you are invoking the program from the directory whose direct subdirectories are to be searched, you don’t start your path with a File.separator character. The exists()method tests for either files or directories; the isFile() method tests only for files. Since we’re looking for a file, both methods work.

image C and D are incorrect based on the above. (OCP Objective 7.2)

3. image E is correct. You need to call flush()only when you’re writing data. Readers don’t have flush() methods. If not for the call to flush(), answer C would be correct.

image A, B, C, and D are incorrect based on the above. (OCP Objective 7.2)

4. image D is correct. The readPassword() method returns a char[]. If a char[] were used, answer B would be correct.

image A, B, C, and E are incorrect based on the above. (OCP Objective 7.1)

5. image A and B are correct. Dodge instances cannot be serialized because they “have” an instance of Wheels, which is not serializable. Vehicle instances cannot be serialized even though the subclass Car can be.

image C, D, and E are incorrect based on the above. (Pre-OCPJP 7 only)

6. image E an d F are correct since Paths must be created using the Paths.get() method. This method takes a varargs String parameter, so you can pass as many path segments to it as you like.

image A and B are incorrect because you cannot construct a Path directly. C and D are incorrect because the Files class works with Path objects but does not create them from Strings. (Objective 8.1)

7. image A is correct because it prints the path to get to two from one.

image B is incorrect because it prints out ../.. which is the path to navigate to one from two. This is the reverse of what we want. C, D, and E are incorrect because it does not make sense to call resolve with absolute paths. They might print out c:/x/c:/x/y/a, c:/x/y/a/c:/x, andc:/x/y/a/c:/x/y/a, respectively. F is incorrect because of the above. Note that the directory structure provided is redundant. Neither relativize() nor resolve() requires either path to actually exist. (OCP Objective 8.1)

8. image E is correct because a directory containing files or subdirectories is copied or moved in its entirety. Directories can only be deleted if they are empty. Trying to delete a nonempty directory will throw a DirectoryNotEmptyException. The question says “usually” because copy and move success depends on file permissions. Think about the most common cases when encountering words such as “usually” on the exam.

image A, B, C, D, F, and G are incorrect because of the above. (OCP Objective 8.2)

9. image B is correct because, like the Java 7 code, it returns false if the file does not exist.

image A is incorrect because this code throws an Exception if the file does not exist. C,D,E, and F are incorrect because they do not compile. There is no deleteOnExit() method, and file operations such as delete occur using the Files class rather than the path object directly. (OCP Objective 8.2)

10. image A,B, and D are correct. Creation time is a basic attribute, which means you can read BasicFileAttributes or any of its subclasses to read it. DosFileAttributes is one such subclass.

image C is incorrect because you cannot cast a more general type to a more specific type. E is incorrect because this example specifies it is being run on Windows. While it would work on UNIX, it throws an UnsupportedOperationException on Windows due to requesting theWindowsFileSystemProvider to get a POSIX class. F and G are incorrect because those methods do not exist. You must use the Files class to get the attributes. (OCP Objective 8.3)

11. image B, C, and E are correct. BasicFileAttributes is the general superclass. DosFileAttributes subclasses BasicFileAttributes for Windows operating systems. PosixFileAttributes subclasses BasicFileAttributes for UNIX/Linux/Mac operating systems.

image A,D, and F are incorrect because no such classes exist. (Objective 8.3)

12. image B and C are correct because file visitor does a depth-first search. When files and directories are at the same level of the file tree, they can be visited in either order. Therefore, “y” and “a” could be reversed. All of the subdirectories and files are visited before postVisit is called on the directory.

image A,D, and E are incorrect because of the above. (Objective 8.4)

13. image E is correct because it is the correct constant to end the FileVisitor.

image B is incorrect because END is not defined as a result constant. A,C, and D are incorrect. While they are valid constants, they do not end file visiting. CONTINUE proceeds as if nothing special has happened. SKIP_SUBTREE skips the subdirectory, which doesn’t even make sense for a Java file. SKIP_SIBLINGS would skip any files in the same directory. Since we weren’t told what the file structure is, we can’t assume there aren’t other directories or subdirectories. Therefore, we have to choose the most general answer of TERMINATE. F is incorrect because file visitor throws a NullPointerException if null is returned as the result. (OCP Objective 8.4)

14. image B is correct. ** matches zero or more characters, including multiple directories.

image A is incorrect because */ only matches one directory. It will match “temp” but not “c:/temp,” let alone “c:/temp/dir.” C is incorrect because *.txt only matches filenames and not directory paths. D,E, and F are incorrect because the paths we want to match do not begin with a slash. G is incorrect because of the above. (Objective 8.5)

15. image C is correct because DirectoryStream only looks at files in the immediate directory. **/*.txt means zero or more directories followed by a slash, followed by zero or more characters followed by .txt. Since the slash is in there, it is required to match, which makes it mean one or more directories. However, this is impossible because DirectoryStream only looks at one directory. If the expression were simply *.txt, answer A would be correct.

image A, B,D, and E are incorrect because of the above. (OCP Objective 8.5)

16. image A is correct because watch service only looks at a single directory. If you want to look at subdirectories, you need to set recursive watch keys. This is usually done using a FileVisitor.

image B,C,D,E, and F are incorrect because of the above. (OCP Objective 8.6)