Before you begin

This unit is part of the “Intro to Java programming” learning path. Although the concepts discussed in the individual units are standalone in nature, the hands-on component builds as you progress through the units, and I recommend that you review the prerequisites, setup, and unit details before proceeding.

Unit objectives

  • Know the main uses of the java.io.File class
  • Understand how to use byte streams and character streams
  • Know how to read data from and write data to a File

Working with external data

More often than not, the data you use in your Java programs comes from an external data source, such as a database, direct byte transfer over a socket, or file storage. Most of the Java tools for collecting and manipulating external data are in the java.io package.

Files

Of all the data sources available to your Java applications, files are the most common and often the most convenient. If you want to read a file in your application, you must use streams that parse its incoming bytes into Java language types.

java.io.File is a class that defines a resource on your file system and represents that resource in an abstract way. Creating a File object is easy:


File f = new File("temp.txt");

File f2 = new File("/home/steve/testFile.txt");

The File constructor takes the name of the file it represents. The first call represents a file called temp.txt in the current directory. The second call represents a file in a specific location on my Linux system. You can pass any String to the constructor of File, provided that it’s a valid file name for your operating system, whether or not the file that it references even exists.

This code asks the newly created File object if the file exists:


File f2 = new File("/home/steve/testFile.txt");
if (f2.exists()) {
  // File exists. Process it...
} else {
  // File doesn't exist. Create it...
  f2.createNewFile();
}

java.io.File has some other handy methods that you can use to:

  • Delete files
  • Create directories (by passing a directory name as the argument to File‘s constructor)
  • Determine if a resource is a file, directory, or symbolic link
  • More

The main action of Java I/O is in writing to and reading from data sources, which is where streams come in.

Using streams in Java I/O

You can access files on the file system by using streams. At the lowest level, streams enable a program to receive bytes from a source or to send output to a destination. Some streams handle all kinds of 16-bit characters (Reader and Writer types). Others handle only 8-bit bytes (InputStream and OutputStream types). Within these hierarchies are several flavors of streams, all found in the java.io package.

Byte streams read (InputStream and subclasses) and write (OutputStream and subclasses) 8-bit bytes. In other words, a byte stream can be considered a more raw type of stream. Here’s a summary of two common byte streams and their usage:

  • FileInputStream / FileOutputStream: Reads bytes from a file, writes bytes to a file
  • ByteArrayInputStream / ByteArrayOutputStream: Reads bytes from an in-memory array, writes bytes to an in-memory array

Character streams

Character streams read (Reader and its subclasses) and write (Writer and its subclasses) 16-bit characters. Here’s a selected listing of character streams and their usage:

  • StringReader / StringWriter: Read and write characters to and from Strings in memory.
  • InputStreamReader / InputStreamWriter (and subclasses FileReader / FileWriter): Act as a bridge between byte streams and character streams. The Reader flavors read bytes from a byte stream and convert them to characters. The Writer flavors convert characters to bytes to put them on byte streams.
  • BufferedReader / BufferedWriter: Buffer data while reading or writing another stream, making read and write operations more efficient.

Rather than try to cover streams in their entirety, I’ll focus here on the recommended streams for reading and writing files. In most cases, these are character streams.

Reading from a File

You can read from a File in several ways. Arguably the simplest approach is to:

  1. Create an InputStreamReader on the File you want to read from.
  2. Call read() to read one character at a time until you reach the end of the file.

Listing 1 is an example in reading from a File:

Listing 1. Reading from a File

public List<Employee> readFromDisk(String filename) {
  final String METHOD_NAME = "readFromDisk(String filename)";
  List<Employee> ret = new ArrayList<>();
  File file = new File(filename);
  try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file))) {
    StringBuilder sb = new StringBuilder();
    int numberOfEmployees = 0;
    int character = reader.read();
    while (character != ‑1) {
        sb.append((char)character);
        character = reader.read();
    }
    log.info("Read file: \n" + sb.toString());
    int index = 0;
    while (index < sb.length()‑1) {
      StringBuilder line = new StringBuilder();
      while ((char)sb.charAt(index) != '\n') {
        line.append(sb.charAt(index++));
      }
      StringTokenizer strtok = new StringTokenizer(line.toString(), Person.STATE_DELIMITER);
      Employee employee = new Employee();
      employee.setState(strtok);
      log.info("Read Employee: " + employee.toString());
      ret.add(employee);
      numberOfEmployees++;
      index++;
    }
    log.info("Read " + numberOfEmployees + " employees from disk.");
  } catch (FileNotFoundException e) {
    log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "Cannot find file " + 
       file.getName() + ", message = " + e.getLocalizedMessage(), e);
  } catch (IOException e) {
    log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "IOException occurred, 
       message = " + e.getLocalizedMessage(), e);
  }
  return ret;
}

Writing to a File

As with reading from a File, you have several ways to write to a File. Once again, I go with the simplest approach:

  1. Create a FileOutputStream on the File you want to write to.
  2. Call write() to write the character sequence.

Listing 2 is an example of writing to a File:

Listing 2. Writing to a File

public boolean saveToDisk(String filename, List<Employee> employees) {
  final String METHOD_NAME = "saveToDisk(String filename, List<Employee> employees)";
  
  boolean ret = false;
  File file = new File(filename);
  try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file))) {
    log.info("Writing " + employees.size() + " employees to disk (as String)...");
    for (Employee employee : employees) {
      writer.write(employee.getState()+"\n");
    }
    ret = true;
    log.info("Done.");
  } catch (FileNotFoundException e) {
    log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "Cannot find file " + 
       file.getName() + ", message = " + e.getLocalizedMessage(), e);
  } catch (IOException e) {
    log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "IOException occurred, 
       message = " + e.getLocalizedMessage(), e);
  }
  return ret;
}

Buffering streams

Reading and writing character streams one character at a time isn’t efficient, so in most cases you probably want to use buffered I/O instead. To read from a file using buffered I/O, the code looks just like Listing 1, except that you wrap the InputStreamReader in a BufferedReader, as shown in Listing 3.

Listing 3. Reading from a File with buffered I/O

public List<Employee> readFromDiskBuffered(String filename) {
  final String METHOD_NAME = "readFromDisk(String filename)";
  List<Employee> ret = new ArrayList<>();
  File file = new File(filename);
  try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
    String line = reader.readLine();
    int numberOfEmployees = 0;
    while (line != null) {
      StringTokenizer strtok = new StringTokenizer(line, Person.STATE_DELIMITER);
      Employee employee = new Employee();
      employee.setState(strtok);
      log.info("Read Employee: " + employee.toString());
      ret.add(employee);
      numberOfEmployees++;
      // Read next line
      line = reader.readLine();
    }
    log.info("Read " + numberOfEmployees + " employees from disk.");
  } catch (FileNotFoundException e) {
    log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "Cannot find file " + 
       file.getName() + ", message = " + e.getLocalizedMessage(), e);
  } catch (IOException e) {
    log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "IOException occurred, 
       message = " + e.getLocalizedMessage(), e);
  }
  return ret;
}

Writing to a file using buffered I/O is the same: You wrap the OutputStreamWriter in a BufferedWriter, as shown in Listing 4.

Listing 4. Writing to a File with buffered I/O

public boolean saveToDiskBuffered(String filename, List<Employee> employees) {
  final String METHOD_NAME = "saveToDisk(String filename, List<Employee> employees)";
  
  boolean ret = false;
  File file = new File(filename);
  try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)))) {
    log.info("Writing " + employees.size() + " employees to disk (as String)...");
    for (Employee employee : employees) {
      writer.write(employee.getState()+"\n");
    }
    ret = true;
    log.info("Done.");
  } catch (FileNotFoundException e) {
    log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "Cannot find file " + 
       file.getName() + ", message = " + e.getLocalizedMessage(), e);
  } catch (IOException e) {
    log.logp(Level.SEVERE, SOURCE_CLASS, METHOD_NAME, "IOException occurred, 
       message = " + e.getLocalizedMessage(), e);
  }
  return ret;
}

Test your understanding

  1. True or false: A file must exist on disk before you can create a File object to represent it.

  2. Which statement best describes character streams?

    1. Character streams are 8-bit streams that are used to read and write from files into memory through ByteArrayProcessor interfaces.
    2. Character streams should not be used to read binary data.
    3. Character streams are mainly used to read and write text files.
    4. Character streams are 16-bit streams that are used to read and write data from files through Reader and Writer subclasses.
    5. None of the above
  3. Which statement best describes why you might use a BufferedReader?

    1. Using a BufferedReader to wrap an OutputStreamWriter helps it process 16-bit character streams more efficiently by buffering the input and output.
    2. When using a StreamReader and StreamWriter together, you must be careful not to cross the streams.
    3. A BufferedReader, when acting as a wrapper for InputStreamReader, helps it process 16-bit character streams more efficiently by buffering the input.
    4. A BufferedReader should never be used to read from an input stream.
    5. None of the above
  4. Create a file called lorem.txt that contains the first 250 words of lorem ipsem (you can use generator.lorem-ipsum.info or similar websites to generate this text). Save the file to the root directory of your Java project. Now write a class called Unit21 with a method called readFile() to read the file, print out its contents using a JDK Logger instance, and return the String (containing the file’s contents) to the caller. Write a JUnit test class called Unit21Test as a test harness. Issues to consider:

    • How will you specify the file to be read by readFile()?
    • How will you handle exceptions?
    • How will you make sure to close any file resources when you’re finished?
  5. Add a method to your Unit21 class from Question 4 called writeFile(), which writes the file you read in to a new file called lorem2.txt. Add a new test method to Unit21Test as a test harness. Note: The same types of issues that you addressed in your solution to Question 4 also apply to output streams.

  6. Augment your solution to Question 4 so that each line is no longer than maxCharactersPerLine characters long. (Don’t worry about trying to preserve words when you reach the maxCharactersPerLineth character.) Write your JUnit test to specify 80 as the value for maxCharactersPerLine.

  7. Augment your solution to Question 6 so that you do not truncate a word that occurs when you hit the maxCharactersPerLineth character. Instead, output that word (and any words that follow) on the next line. This is similar to what a word processor does. Write your JUnit test to specify 80 as the value for maxCharactersPerLine. Hint: it might be easier to process the current line one word at a time, and check to see if the current word causes the line to exceed maxCharactersPerLine, and if so put that word on the next line.

Check your answers

  1. True or false: A file must exist on disk before you can create a File object to represent it. False

  2. Which statement best describes character streams?

    1. Character streams are 8-bit streams that are used to read and write from files into memory through ByteArrayProcessor interfaces.
    2. Character streams should not be used to read binary data.
    3. Character streams are mainly used to read and write text files.
    4. Character streams are 16-bit streams that are used to read and write data from files through Reader and Writer subclasses.
    5. None of the above
  3. Which statement best describes why you might use a BufferedReader?

    1. Using a BufferedReader to wrap an OutputStreamWriter helps it process 16-bit character streams more efficiently by buffering the input and output.
    2. When using a StreamReader and StreamWriter together, you must be careful not to cross the streams.
    3. A BufferedReader, when acting as a wrapper for InputStreamReader, helps it process 16-bit character streams more efficiently by buffering the input.
    4. A BufferedReader should never be used to read from an input stream.
    5. None of the above
  4. Create a file called lorem.txt that contains the first 250 words of lorem ipsem (you can use generator.lorem-ipsum.info or similar websites to generate this text). Save the file to the root directory of your Java project. Now write a class called Unit21 with a method called readFile() to read the file, print out its contents using a JDK Logger instance, and return the String (containing the file’s contents) to the caller. Write a JUnit test class called Unit21Test as a test harness. Issues to consider:

    • How will you specify the file to be read by readFile()?
    • How will you handle exceptions?
    • How will you make sure to close any file resources when you’re finished?

      SOLUTION lorem.txt

      Lorem ipsum dolor sit amet, fugit consetetur efficiantur nec eu, mea agam ridens vocibus an, falli errem eam ut. Minim voluptatibus         in qui, no nec tibique perpetua imperdiet. Sententiae dissentias no vim, est cu mundi dicam eloquentiam, iudico aliquando cu cum. Ea eum     inimicus recteque philosophia, mei mnesarchum adversarium eu. Velit possim definitiones mea ex, ad quem expetenda scripserit pro. Saepe     utinam omittam est id, te eos ubique tritani.
      
      Id quas accumsan reprehendunt cum, in agam luptatum adversarium usu. Vix no errem voluptua lucilius. Eum hinc singulis no. Ea doctus     nostrum aliquando mei.
      
      Augue vocent cum eu. Usu modo eius vulputate et, ea ius fabulas pericula voluptatum. Ne dicta laoreet sed, id amet forensibus nec. Ne mentitum persequeris complectitur sit. Est vocent virtute necessitatibus in, harum deleniti maluisset ex est, vix ea intellegam interpretaris.
      
      Omnis dicant facete sed eu. Ei equidem minimum concludaturque pri, dolor iudico eum ea, at porro scripta adolescens eum. Wisi illud ludus nam an. Ad vis possit intellegam adversarium. Ea solet soluta usu, ut usu zril audiam recteque. In repudiare quaerendum consequuntur mea, mea an odio placerat.
      
      Sea id sale aliquando, duo sint consectetuer definitionem no, et cum velit vocibus. Ex sale aperiri conceptam cum, ut ullum solet liberavisse sea. Quaeque platonem te sed, sanctus fastidii qui te, at doctus molestiae voluptaria usu. At quo lucilius disputando. Te qui munere officiis, cum te graeci bonorum. Per ne nostro sententiae, odio debitis eam cu.
      
      At nec iisque tamquam eligendi, cum an malis debet tractatos. Mea noster corpora.
      

      readFile()

      public String readFile(String fileName) {
      
      String ret;
      StringBuilder sb = new StringBuilder();
      
      File file = new File(fileName);
      int lineNumber = 0;
      // try with-resources will close resources when done
      try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
      String line = reader.readLine();
      while (line != null) {
         sb.append(line);
         sb.append('\n');
         // Read the next line
         line = reader.readLine();
      }
      } catch (IOException e) {
      log.severe("IOException occurred: " + e.getLocalizedMessage());
      }
      
      ret = sb.toString();
      log.info("File contents:\n" + ret);
      
      return ret;
      }
      

      JUnit test

      private static final String INPUT_FILE_NAME = "./lorem.txt";
      
      @Test
      public void testReadFile() {
       Unit21 classUnderTest = new Unit21();
      
       classUnderTest.readFile(INPUT_FILE_NAME);
      
      }
      
  5. Add a method to your Unit21 class from Question 4 called writeFile(), which writes the file you read in to a new file called lorem2.txt. Add a new test method to Unit21Test as a test harness. Note: The same types of issues that you addressed in your solution to Question 4 also apply to output streams.

    SOLUTION

    writeFile

     public void writeFile(String fileName, String fileContents) {
    
     File file = new File(fileName);
    
     try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)))) {
        writer.write(fileContents);
     } catch (IOException e) {
        log.severe("IOException occurred: " + e.getLocalizedMessage());
     }
    
     }
    

    JUnit test

     @Test
     public void testWriteFile() {
        String fileName = "./lorem2.txt";
    
        Unit21 classUnderTest = new Unit21();
    
        String fileContents = classUnderTest.readFile(INPUT_FILE_NAME);
        classUnderTest.writeFile(fileName, fileContents);
     }
    

    lorem2.txt

     Lorem ipsum dolor sit amet, fugit consetetur efficiantur nec eu, mea agam ridens vocibus an, falli errem eam ut. Minim voluptatibus in qui, no nec tibique perpetua imperdiet. Sententiae dissentias no vim, est cu mundi dicam eloquentiam, iudico aliquando cu cum. Ea eum inimicus recteque philosophia, mei mnesarchum adversarium eu. Velit possim definitiones mea ex, ad quem expetenda scripserit pro. Saepe utinam omittam est id, te eos ubique tritani.
    
     Id quas accumsan reprehendunt cum, in agam luptatum adversarium usu. Vix no errem voluptua lucilius. Eum hinc singulis no. Ea doctus nostrum aliquando mei.
    
     Augue vocent cum eu. Usu modo eius vulputate et, ea ius fabulas pericula voluptatum. Ne dicta laoreet sed, id amet forensibus nec. Ne mentitum persequeris complectitur sit. Est vocent virtute necessitatibus in, harum deleniti maluisset ex est, vix ea intellegam interpretaris.
    
     Omnis dicant facete sed eu. Ei equidem minimum concludaturque pri, dolor iudico eum ea, at porro scripta adolescens eum. Wisi illud ludus nam an. Ad vis possit intellegam adversarium. Ea solet soluta usu, ut usu zril audiam recteque. In repudiare quaerendum consequuntur mea, mea an odio placerat.
    
     Sea id sale aliquando, duo sint consectetuer definitionem no, et cum velit vocibus. Ex sale aperiri conceptam cum, ut ullum solet liberavisse sea. Quaeque platonem te sed, sanctus fastidii qui te, at doctus molestiae voluptaria usu. At quo lucilius disputando. Te qui munere officiis, cum te graeci bonorum. Per ne nostro sententiae, odio debitis eam cu.
    
      At nec iisque tamquam eligendi, cum an malis debet tractatos. Mea noster corpora.
    
  6. Augment your solution to Question 4 so that each line is no longer than maxCharactersPerLine characters long. (Don’t worry about trying to preserve words when you reach the maxCharactersPerLineth character.) Write your JUnit test to specify 80 as the value for maxCharactersPerLine.

    SOLUTION

         public String readFileFormatWithMaxCharactersPerLine(String fileName, int maxCharactersPerLine) {
        String ret;
        StringBuilder sb = new StringBuilder();
    
        File file = new File(fileName);
        int lineNumber = 0;
        // try with-resources will close resources when done
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
           String line = reader.readLine();
           while (line != null) {
              line = formatLine(line, maxCharactersPerLine);
              sb.append(line);
              sb.append('\n');
              // Read the next line
              line = reader.readLine();
           }
        } catch (IOException e) {
           log.severe("IOException occurred: " + e.getLocalizedMessage());
        }
    
        ret = sb.toString();
        log.info("File contents:\n" + ret);
    
        return ret;
     }
    
     private String formatLine(String inputString, int maxCharactersPerLine) {
        String ret;
    
        StringBuilder sb = new StringBuilder();
    
        int currentCharacterIndex = 0;
        while (currentCharacterIndex < inputString.length()) {
           for (int aa = 0; aa < maxCharactersPerLine; aa++, currentCharacterIndex++) {
              if (currentCharacterIndex < inputString.length()) {
                 sb.append(inputString.charAt(currentCharacterIndex));
              } else {
                 break;
              }
           }
           sb.append('\n');
        }
        ret = sb.toString();
        return ret;
     }
    

    JUnit test

     @Test
     public void testReadFileFormatWithMaxCharactersPerLine() {
       Unit21 classUnderTest = new Unit21();
    
       classUnderTest.readFileFormatWithMaxCharactersPerLine(INPUT_FILE_NAME, 80);
     }
    

    Lorem ipsum text (no-preserve words)

     Lorem ipsum dolor sit amet, fugit consetetur efficiantur nec eu, mea agam ridens
     vocibus an, falli errem eam ut. Minim voluptatibus in qui, no nec tibique perpe
     tua imperdiet. Sententiae dissentias no vim, est cu mundi dicam eloquentiam, iud
     ico aliquando cu cum. Ea eum inimicus recteque philosophia, mei mnesarchum adver
     sarium eu. Velit possim definitiones mea ex, ad quem expetenda scripserit pro. S
     aepe utinam omittam est id, te eos ubique tritani.
    
     Id quas accumsan reprehendunt cum, in agam luptatum adversarium usu. Vix no err
     em voluptua lucilius. Eum hinc singulis no. Ea doctus nostrum aliquando mei.
    
     Augue vocent cum eu. Usu modo eius vulputate et, ea ius fabulas pericula volupt
     atum. Ne dicta laoreet sed, id amet forensibus nec. Ne mentitum persequeris comp
     lectitur sit. Est vocent virtute necessitatibus in, harum deleniti maluisset ex
     est, vix ea intellegam interpretaris.
    
     Omnis dicant facete sed eu. Ei equidem minimum concludaturque pri, dolor iudico
      eum ea, at porro scripta adolescens eum. Wisi illud ludus nam an. Ad vis possit
      intellegam adversarium. Ea solet soluta usu, ut usu zril audiam recteque. In re
     pudiare quaerendum consequuntur mea, mea an odio placerat.
    
     Sea id sale aliquando, duo sint consectetuer definitionem no, et cum velit voci
     bus. Ex sale aperiri conceptam cum, ut ullum solet liberavisse sea. Quaeque plat
     onem te sed, sanctus fastidii qui te, at doctus molestiae voluptaria usu. At quo
     lucilius disputando. Te qui munere officiis, cum te graeci bonorum. Per ne nost
     ro sententiae, odio debitis eam cu.
    
     At nec iisque tamquam eligendi, cum an malis debet tractatos. Mea noster corpor
     a.
    
  7. Augment your solution to Question 6 so that you do not truncate a word that occurs when you hit the maxCharactersPerLineth character. Instead, output that word (and any words that follow) on the next line. This is similar to what a word processor does. Write your JUnit test to specify 80 as the value for maxCharactersPerLine. Hint: it might be easier to process the current line one word at a time, and check to see if the current word causes the line to exceed maxCharactersPerLine, and if so put that word on the next line.

    SOLUTION

      public String readFileFormatWithMaxCharacterPerLinePerserveWords(String fileName, int maxCharactersPerLine) {
        String ret;
        StringBuilder sb = new StringBuilder();
    
        File file = new File(fileName);
        // try with-resources will close resources when done
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) {
              String line = reader.readLine();
              while (line != null) {
                 line = formatLinePreserveWords(line, maxCharactersPerLine);
                 sb.append(line);
                 // Read the next line
                 line = reader.readLine();
              }
           } catch (IOException e) {
       log.severe("IOException occurred: " + e.getLocalizedMessage());
           }
    
           ret = sb.toString();
           log.info("File contents: " + ret);
    
           return ret;
        }
        private String formatLinePreserveWords(String inputString, int maxCharactersPerLine) {
           String ret;
    
           StringBuilder sb = new StringBuilder();
    
           int currentCharacterIndex = 0;
           while (currentCharacterIndex < inputString.length()) {
              int currentLineIndex = 0;
              while (currentLineIndex < maxCharactersPerLine) {
                 String word = fetchNextWord(inputString, currentCharacterIndex, currentLineIndex, maxCharactersPerLine);
                 if (word != null) {
                    currentCharacterIndex += word.length();
                    currentLineIndex += word.length();
                    sb.append(word);
                 } else {
                    break;
                 }
              }
              sb.append('\n');
           }
    
           ret = sb.toString();
           return ret;
        }
    
        private String fetchNextWord(String inputString, int currentCharacterIndex, int currentLineIndex, int maxCharactersPerLine) {
           String ret = null;
    
           StringBuilder sb = new StringBuilder();
           while (currentCharacterIndex < inputString.length() && currentLineIndex < maxCharactersPerLine) {
              char currentCharacter = inputString.charAt(currentCharacterIndex);
              currentCharacterIndex++;
              currentLineIndex++;
              sb.append(currentCharacter);
              if (Character.isWhitespace(currentCharacter) || currentCharacterIndex == inputString.length()) {
                 ret = sb.toString();
                 break;
              }
           }
           return ret;
        }
    

    JUnit test

     @Test
     public void testReadFileFormatWithMaxCharactersPerLinePreserveWords() {
       Unit21 classUnderTest = new Unit21();
    
       classUnderTest.readFileFormatWithMaxCharacterPerLinePerserveWords(INPUT_FILE_NAME, 80);
     }
    

    Lorem ipsum text (preserve words)

     Lorem ipsum dolor sit amet, fugit consetetur efficiantur nec eu, mea agam
    ridens vocibus an, falli errem eam ut. Minim voluptatibus in qui, no nec
    tibique perpetua imperdiet. Sententiae dissentias no vim, est cu mundi dicam
    eloquentiam, iudico aliquando cu cum. Ea eum inimicus recteque philosophia, mei
    mnesarchum adversarium eu. Velit possim definitiones mea ex, ad quem expetenda
    scripserit pro. Saepe utinam omittam est id, te eos ubique tritani.
    
      Id quas accumsan reprehendunt cum, in agam luptatum adversarium usu. Vix no
    errem voluptua lucilius. Eum hinc singulis no. Ea doctus nostrum aliquando mei.
    
      Augue vocent cum eu. Usu modo eius vulputate et, ea ius fabulas pericula
    voluptatum. Ne dicta laoreet sed, id amet forensibus nec. Ne mentitum
    persequeris complectitur sit. Est vocent virtute necessitatibus in, harum
    deleniti maluisset ex est, vix ea intellegam interpretaris.
    
      Omnis dicant facete sed eu. Ei equidem minimum concludaturque pri, dolor
    iudico eum ea, at porro scripta adolescens eum. Wisi illud ludus nam an. Ad vis
    possit intellegam adversarium. Ea solet soluta usu, ut usu zril audiam
    recteque. In repudiare quaerendum consequuntur mea, mea an odio placerat.
    
      Sea id sale aliquando, duo sint consectetuer definitionem no, et cum velit
    vocibus. Ex sale aperiri conceptam cum, ut ullum solet liberavisse sea. Quaeque
    platonem te sed, sanctus fastidii qui te, at doctus molestiae voluptaria usu.
    At quo lucilius disputando. Te qui munere officiis, cum te graeci bonorum. Per
    ne nostro sententiae, odio debitis eam cu.
    
      At nec iisque tamquam eligendi, cum an malis debet tractatos. Mea noster
    corpora.
    

    Previous: GenericsNext: Java serialization