8

I have been studying Decorator pattern and developed simple class ToUpperCaseInputStream. I overrode read() method so it could convert all chars from InputStream to uppercase. Code of the method is shown below (throws OutOfMemoryError):

@Override
public int read() throws IOException {
    return Character.toUpperCase((char)super.read());
}

As I figured out later, casting to char is redundant, but it's not the point. I'm having "java.lang.OutOfMemoryError: Java heap space" when the code:

((char) super.read())  

evaluates. To make this simpler I wrote the same method (this one throws OutOfMemoryError):

@Override
public int read() throws IOException {
    int c =(char) super.read();
    return (c == -1 ? c : Character.toUpperCase(c));
} 

And this one does not:

@Override
public int read() throws IOException {
    int c = super.read();
    return (c == -1 ? c : Character.toUpperCase(c));
} 

When I remove casting from the assignment the code runs with no errors and results in all text uppercased. As it's said at Oracle tutorials:

An assignment to an array component of reference type (§15.26.1), a method invocation expression (§15.12), or a prefix or postfix increment (§15.14.2, §15.15.1) or decrement operator (§15.14.3, §15.15.2) may all throw an OutOfMemoryError as a result of boxing conversion (§5.1.7).

It seems that autoboxing is used, but as for me it's not the case. Both variants of the same method result in OutOfMemoryError. If I am wrong, please explain this to me, because it will blow up my head.

To provide more info there is the client code:

public class App {
public static void main(String[] args) throws IOException {

    try (InputStream inet = new ToUpperCaseInputStream(new FileInputStream("d:/TEMP/src.txt"));
        FileOutputStream buff = new FileOutputStream("d:/TEMP/dst.txt")) {
        copy(inet, buff);
    }
}

public static void copy(InputStream src, OutputStream dst) throws IOException {
    int elem;
    while ((elem = src.read()) != -1) {
        dst.write(elem);
    }
}

}

What it does is just prints simple message from one file to another.

Although the case is solved I want to share a really good explanation of how casting is done. https://stackoverflow.com/a/24637624/1923644

Zarial
  • 283
  • 3
  • 11
  • That's got to be a coincidence. The two versions are identical. – chrylis -cautiouslyoptimistic- Jan 17 '15 at 19:14
  • *"when I remove casting from the assignment and changing variable primitive type from char to int, the code runs with no errors...both methods result in OutOfMemoryException"* Please edit the question so it is clear to us what throws and what does not. – Radiodef Jan 17 '15 at 19:24
  • *"changing variable primitive type from char to int"* I see in both cases the primitive is an `int`, what did you want to say ? – Daniel Jan 17 '15 at 20:03
  • @Mondkin I'm sorry, I had to edit this a couple of times due to previous comments. Now it is clear and correct. – Zarial Jan 17 '15 at 20:06
  • 1
    Now that you added more code, @yurgis answer makes sense. Put a `println` inside the `while` to see how many times it is being executed in each case. I guess in the second case the while condition always satisfies. – Daniel Jan 17 '15 at 20:07
  • @Mondkin somehow I figured out that after aquiring -1 end-point **while** loops on "65535". And it doesn't matter whether I checked for -1 or no. The problem is in "int c = (char) super.read();". Somehow casting to char in this line makes code unable to catch "-1 end-point" – Zarial Jan 17 '15 at 20:19
  • 1
    Although you corrected your code it is still wrong. After the cast c is never -1 – yurgis Jan 17 '15 at 20:27
  • @Mondkin Got this!) The reason "while loop" never ends because when I cast -1 of type **int** it results in smth my IDE doesn't know how to render - , and so does not know char. The line "int c = (char) super.read();" produces "65535" in an endless loop when reaches the end of file (-1). – Zarial Jan 17 '15 at 20:29
  • It's not that your IDE does not know how to render, it's as yurgis pointed in their answer below. That 65535 is something normal in that cast you did. – Daniel Jan 17 '15 at 20:30
  • @Mondkin I mean, try to System.out.println((char) -1); and you'll see what I ment. I made this casting on purpose and I still can't get it why the line of code "int i = (char) -1;" results in 65535 – Zarial Jan 17 '15 at 20:34
  • 1
    Check this: http://docs.oracle.com/javase/specs/jls/se7/html/jls-5.html – Daniel Jan 17 '15 at 20:36

1 Answers1

4

Before you cast to char you need to check for -1 (signaling end of input data).

char in Java is an unsigned short, meaning that when -1 is returned, your cast will make it 65535. Even if you did not have OutOfMemory, your code is still broken.

Regarding why you are getting OOM error, it is hard to say without full code, maybe, later in the code there are some memory allocations based on value of the character.

So try this and see if it helps:

@Override
public int read() throws IOException {
    int c = super.read();
    if (c == -1) return c;

    char ch = (char) c;
    return Character.toUpperCase(ch);
} 
yurgis
  • 4,017
  • 1
  • 13
  • 22
  • You misunderstood me. Your code works fine as intended, but when I add casting like that: int c = (char) super.read(); everything crashes... – Zarial Jan 17 '15 at 20:11
  • 1
    You can't cast before checking for -1. After the cast you will never get -1 because char is always positive. – yurgis Jan 17 '15 at 20:33
  • So the line "int i = (char) -1;" always results in max char? – Zarial Jan 17 '15 at 20:35
  • 2
    yes, try this code { char c = 0; c--; System.out.println(c); } More about unsigned values: http://en.wikipedia.org/wiki/Signedness – yurgis Jan 17 '15 at 20:39