1
\$\begingroup\$

I've just learned about custom wrapper classes and I'm a bit confused about a couple of things. First, what's the utility of such tool? From what I understand you can mimic the copied class behaviour but with slight modifications. Is this tool used often?

Also, could you please have a look at my code? I know I'm suppose to close streams with "try with resources", but I'm not sure where it fits inside a constructor. Any other suggestions about the quality of my code are appreciated, please tear it apart!

/* 
UnsupportedFileName
Change the TxtInputStream class so that it only works with txt files (* .txt).
For example, first.txt or name.1.part3.txt.
If a non-txt file is passed (e.g. file.txt.exe), then the constructor should throw
an UnsupportedFileNameException.
Think about what else you need to do if an exception is thrown.


Requirements:
x 1. The TxtInputStream class must inherit the FileInputStream class.
2. If a txt file is passed to the constructor, TxtInputStream should behave like a regular FileInputStream.
3. If a non-txt file is passed to the constructor, an UnsupportedFileNameException should be thrown.
4. If an exception is thrown, then you must also call super.close().

*/

public class TxtInputStream extends FileInputStream {
    private FileInputStream original;
    public static String fileName;

    public TxtInputStream(String fileName) throws IOException, UnsupportedFileNameException {
        super(fileName);
        if(fileName.substring(fileName.length()-4).equals(".txt")) {
            this.fileName = fileName;
        } else {
            super.close();
            throw new UnsupportedFileNameException();

        }
    }

    public static void main(String[] args) {
    }
}

\$\endgroup\$
6
  • 3
    \$\begingroup\$ Welcome to code review. " I know I'm suppose to close streams with "try with resources", but I'm not sure where it fits inside a constructor." --- In a constructor you should only assign its parameters to member variables. Especially you should not do anything that could result in an Exception to be thrown. \$\endgroup\$ – Timothy Truckle May 10 '20 at 9:10
  • \$\begingroup\$ @TimothyTruckle so this is a poorly designed exercise I suppose? \$\endgroup\$ – Pablo Aguirre de Souza May 10 '20 at 9:13
  • 2
    \$\begingroup\$ "so this is a poorly designed exercise?" --- I strongly agree to that statement. ;o) \$\endgroup\$ – Timothy Truckle May 10 '20 at 9:13
  • \$\begingroup\$ "Especially you should not do anything that could result in an Exception to be thrown." @TimothyTruckle there is nothing wrong with performing argument validation in a constructor and throwing an exception. \$\endgroup\$ – Marcono1234 Sep 27 '20 at 21:52
  • \$\begingroup\$ @Marcono1234 "there is nothing wrong with performing argument validation in a constructor" -- It depends. As long as no additional dependency is needed to do some simple validation (e.g. check for null) might be acceptable but usually the constructor arguments should be checked before calling the constructor. Especially when talking about DTOs this can dramatically simplify unit test configurations where you often don't deed these DTOs having consistent configuration. Business classes should not have constructor parameters that need to be consistent beyond not being null anyway. ;o) \$\endgroup\$ – Timothy Truckle Sep 28 '20 at 6:43
2
\$\begingroup\$

The cleanest way to solve this would probably be to check the argument before calling the super constructor. This allows the code to fail fast and prevents it from opening the file, rendering the close() call unnecessary:

public class TxtInputStream extends FileInputStream {
    public TextInputStream(String fileName) {
        // super call must be first statement so have to call validation method
        // as argument to super call
        super(checkFileName(fileName));
    }

    private static String checkFileName(String fileName) {
        if (!fileName.endsWith(".txt")) {
            throw new UnsupportedFileNameException();
        }
        return fileName;
    }
}

However, this is a pretty bad example for wrappers since the TxtInputStream does not add any functionality at all.

If you want to see some good examples have at look at which JDK classes implement InputStream:

  • BufferedInputStream: Buffers data, this can be useful when you are only consuming small amounts of data at a time, but reading such small amounts of data would be inefficient, e.g. for a network connection. Instead it reads larger chunks from the wrapped InputStream and when you then consume data it first serves it from the buffer.
  • CipherInputStream: Decrypts the data
  • InflaterInputStream: Uncompresses data

So assuming you read encrypted, compressed data over the network you could have:

InputStream networkInputStream = ...
BufferedInputStream bufferedInputStream = new BufferedInputStream(networkInputStream);
CipherInputStream cipherInputStream = new CipherInputStream(bufferedInputStream, cipher);
InflaterInputStream inflaterInputStream = new InflaterInputStream(cipherInputStream);

Then you can simply use inflaterInputStream like any other InputStream and do not even have to worry about all the previous processing steps which are taking place.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.