Concepts / Using the File Class

Using the File Class

A Java File object represents a file or directory, and creating one with new File(name) asks the program to refer to a path rather than read it.

  • Programming

Predicting the Result for an Ordinary File

This article follows one complete Java program, DirectoryList. The program asks the user to type in a directory name and then lists the files inside it. If the name entered is not a directory, the program prints a message and ends. The goal is to learn how the program decides what to do, not only what it prints. As you read, keep the program's purpose in mind and watch which checks run for each input.

What do you think happens?

Suppose the user types the name of an ordinary file, not a folder. Which result does the program produce?

  • There is no such directory

  • The file is not a directory

  • A listing of file names

Reveal answer
Answer:

The file is not a directory

The file exists, so the program does not print the no-such-directory message. Because isDirectory() returns false, the outer if block runs, and the nested check finds that the path exists but is not a directory. The program never calls list() on it.

A File Object Holds a Name

Definition

A java.io.File object represents a file or directory by holding a path name. The named file may or may not exist, and creating the object creates nothing on disk.

The word File in the class name invites a wrong reading: that creating a File object opens or creates a file. The source tests that assumption directly. The line that creates the object does not read anything from the disk by itself. It builds an object that refers to the path the user typed, and the program then asks that object questions.

java
Notes on the code
  • The constructor only stores the path string notes.txt inside the object
  • exists() asks the file system whether that name is present
Key point

Creating a File object is a request to refer to a path. Asking exists() or isDirectory() is what checks the disk. Until you ask, the object tells you nothing about contents.

Two Ways to Build a File Object

Two constructors build File objects. new File(String) takes a single path string. new File(File, String) takes a directory and a name or relative path, and combines the two parts into one path. Which one you choose depends on how much of the path you already have as separate pieces. Both produce the same kind of object, so the later checks apply to either.

java
Notes on the code
  • single is built from one complete path string
  • combined is built from a directory object and a relative name, and the constructor joins them
ConstructorInputsBest used when
new File(String)One path stringThe whole path arrives as a single piece of text, such as typed user input
new File(File, String)A File for the directory and a name or relative pathThe directory and the entry name are already separate pieces

The two File constructors and when each fits

Tracing the Validity Check

The program first tests directory.isDirectory() == false. This is a negated test: the outer if block runs when the path is not a directory. Inside that block, the program separates two cases. If directory.exists() == false, nothing by that name is present, so the message is that there is no such directory. Otherwise, something exists but it is not a directory, so the message is that the file is not a directory. Only when isDirectory() is true does the else branch run, and only then does the program call list(). The nested if is what separates a missing path from an existing file, so a non-directory input always gets a specific message.

User types a nameisDirectory() ==false?exists() == false?No such directoryFile is not adirectoryCall list()Print each file namecheck firsttrue: not a directorytrue: nothing presentfalse: something existsfalse: is a directoryarray of names
Given a user input, which check runs first, which branch is taken, and which message or listing does the program print?

Read the diagram from the top. The first decision is always isDirectory(). A missing path and an ordinary file both take the left-hand branch, and only the second decision separates them. Only the path on the right, where isDirectory() is true, reaches list(). Everything that prints a file name comes after the validity check has passed.

Walking Three Inputs Through the Branches

Worked example

Tracing an ordinary file name

The user types notes.txt, where notes.txt is an ordinary file in the current folder. Which branch runs and what does the program print?

  1. 1
    Create the object

    new File("notes.txt") stores the path. Nothing is read from the disk yet.

  2. 2
    Test isDirectory()

    The method returns false, because the entry is a regular file. The negated test directory.isDirectory() == false is therefore true, and the outer if block runs.

  3. 3
    Test exists()

    The method returns true, because the file is present. The check directory.exists() == false is therefore false, so the program takes the otherwise path.

  4. 4
    Print the message

    Since the entry exists but is not a directory, the program prints that the file is not a directory and ends. list() is never called.

Result

The program prints that the file is not a directory. No file listing appears.

Now change the input to a name that is not present anywhere. The first test is still isDirectory(), which returns false. The second test, exists(), now returns false, so the program prints that there is no such directory. The two non-directory inputs differ only in the nested check, which is why the program can give a specific message for each. A valid directory is the only input that skips both messages and reaches the listing.

Why list() Runs Last

The list() method returns an array of file names in the directory. The program stores that array in the variable files, uses files.length as the loop limit, and prints each entry on an indented line. The ordering matters. Because list() is called only after isDirectory() has returned true, the program never asks a regular file for its contents. The message branches and the listing branch are therefore separate, safe paths.

java
Notes on the code
  • The isDirectory() check guards the call to list()
  • list() returns names, which are plain strings
  • files.length bounds the loop, so each name is printed once

Object Versus File on Disk

The clearest way to hold the two ideas apart is to set them side by side. The File object lives in your program and carries a name. The file lives in the file system and carries contents. The name and the file are linked only when the operating system finds a match for that name. The diagram shows that link as a question you ask across the arrow, not as something the object already knows.

File objectholds a namePath namesuch as notes.txtEntry on diskholds contents, if presentrefers tomatched only by a check
How is the File object related to the actual file or directory on disk, and what changes, or does not change, when the File object is created?

Asking Questions About an Entry

Because a File object holds a name, its useful operations are questions and requests about that name. Does the named entry exist? Can the program read or write it? Is it a directory? Should it be deleted? If it is a directory, what does it contain? The table groups the main instance methods by the question each one answers.

MethodQuestion it answers about the path
exists()Is anything present at this path?
canRead()Can the program read this entry?
canWrite()Can the program write to this entry?
isDirectory()Does the path refer to a directory?
delete()Remove the named entry
list()Return the names inside a directory, as an array of strings
listFiles()Return the contents of a directory as File objects

Main File methods grouped by the question each answers

Key point

list() and listFiles() return directory contents only when the object refers to a directory. Calling them on a regular file is the mistake the DirectoryList program avoids by checking isDirectory() first.

Checking State Before Handing a File to FileReader

Stream classes such as FileReader accept a File object in their constructors, which is how a named file becomes a source of data. The usual order is to create the File, ask questions about its state, and only then pass it to the stream. The source also notes that exists() helps you avoid overwriting an existing file when you create a new output stream, so the same check serves both reading and writing.

java
Notes on the code
  • Step 1 creates the File object, which only holds the name
  • Step 2 asks exists() and canRead() about that name
  • Step 3 hands the File to FileReader only when the checks pass

Mistakes Beginners Make with File Objects

  • Common mistake

    Assuming new File(name) creates the file on disk

    What it looks like

    File f = new File("notes.txt"); then expecting notes.txt to now be present

    Why it fails

    A File object holds a name. Creating it creates nothing on disk, and the named file may or may not exist.

    Do this instead

    Use exists() to ask whether the file is present before you rely on it.

  • Common mistake

    Calling list() before checking isDirectory()

    What it looks like

    Calling list() on the path an ordinary file name refers to

    Why it fails

    list() returns directory contents only when the object refers to a directory, so the call is not meaningful for a regular file.

    Do this instead

    Place list() inside the branch that runs only after isDirectory() returns true.

  • Common mistake

    Treating an ordinary file and a missing path as the same case

    What it looks like

    Printing one generic error for every non-directory input

    Why it fails

    A missing path and an existing non-directory need different messages, and only a nested exists() check separates them.

    Do this instead

    Keep the nested exists() test inside the negated isDirectory() branch.

  • Common mistake

    Forgetting that isDirectory() is a question about the path, so its answer depends on what is on disk when you ask

Practice: Predict the Branch

EASY

A user types the name of a folder that exists. Without running the program, trace the branches. Which checks run, which branch is taken, and what does the program print? Then explain why the program never calls list() on a missing path.

Hints
  • Start with directory.isDirectory() == false. What does it return for a folder?
  • If the negated test is false, which block is skipped entirely?
  • list() sits only in the else branch, so ask what condition has to be true to reach it.

Key Takeaways

  • A File object holds a path name, not file contents, and creating it does nothing on disk until you ask questions such as exists() or isDirectory().
  • In DirectoryList, isDirectory() is checked first, and a nested exists() test separates a missing path from an existing non-directory, so each invalid input gets a specific message.
  • list() returns the names inside a directory and is only called after the validity check passes, which keeps the listing branch safe.
  • The constructors new File(String) and new File(File, String) build the same kind of object, so the checks described here apply to either one.
  • Check a file's state with exists() and canRead() or canWrite() before passing the File object to a stream such as FileReader.