JDK 1.5: Discussing the auto-unboxing feature

Discussions

News: JDK 1.5: Discussing the auto-unboxing feature

  1. JDK 1.5: Discussing the auto-unboxing feature (29 messages)

    There seem to be two very opposite point of views on the subject of JDK 1.5 auto-unboxing, with opponents and supporters for both and some gray area of things that are bound to taste or are dependent on the use-case. Maybe it's better to pull auto-unboxing back out as was originally planned and only perform auto-boxing.

    I think that the JDK 1.5 auto-unboxing issue is very important and would like to collect public opinions about it. That's why I take the liberty to paste the JSR-201 group's reply and my answer on my blog.

    Here's the summary:

    * the NPE behaviour has been adopted since the JSR-201 group came to the concensus that when converting to natural default values, masked errors can potentially be introduced,

    * I think that by throwing NPE exceptions, masked errors are always introduced since there's no clear indication of where to perform null checks.

    Read The dangers of auto-unboxing (part 2)

    Threaded Messages (29)

  2. auto-unboxing issues[ Go to top ]

    Interesting.

    When I first came to java back in 1996, I thougth having two different type for scalar types (Integer/int, Character/char, Boolean/boolean and so on) was a big mistake. It was a choice : Performance against OO principle. In my mind, an Integer should be an object and int is useless, as performance is a compiler matter.

    Whatever. I feel that auto-(un)boxing is a try to correct this flaw. Does it mean we should spend hours digging the code till we found that "...m.get(key) + 1..." is faulty, because we have associated a null value to this key, and m.get(key) return a 0? I don't think so.

    A NPE is a good way to undestand that something went wrong, and where in the code. Of course, as it can occur, you'll have to check it. And it WILL occur...

    Your question is a good one, but it's a kind of chicken/egg one. I can live with int v = ((Integer)m.get(key)).intValue(), but I prefer int v = m.get(key), even if I have to check against NULL values. BTW, both pieces of code are similar, because you still have to check if ((Integer)m.get(key)) return a null value or not, before getting its int value, no?

    so :
    - auto-unboxing : +1
    - NPE : +1

    Of course, it's just my personnal opinion, and I may miss something. It's just that I saw to many lines of code obfuscated with those ((Integer)m.get(key).intValue() or m.put(key, new Integer(v)) constructions ...

    Emmanuel Lecharny
  3. auto-unboxing issues[ Go to top ]

    Of course, it's just my personnal opinion, and I may miss something. It's just
    that I saw to many lines of code obfuscated with those ((Integer)m.get
    (key).intValue() or m.put(key, new Integer(v)) constructions ...

    Yes, auto-boxing will certainly make the "short example" code snippets more readable. For a real application it has always been, and will always be, possible to wrap the Collection with a type safe interface.
  4. auto-unboxing issues[ Go to top ]

    Throwing NPEs is safe. Programmers should be instead very careful when considering the possibility of storing nulls in Maps.
    Maps let you test if a key is actually contained in it, then arguing what happens when a null is unboxed is pretty pointless.
  5. auto-unboxing issues[ Go to top ]

    Throwing NPEs is safe. Programmers should be instead very careful when considering the possibility of storing nulls in Maps.
    Please read the original article. The author brings up a very interesting example without any Maps but with the obvious risk of NPE using generics and autounboxing.
    Throwing NPE is not safe when you are handling promitives. And it will very hard to know all the positions where NPE could happen or not. Autoboxing is for convenience? Not at all. I will be the pain in the ass. I hope there will be a switch for switching off this feature.
    Maz
  6. auto-unboxing issues[ Go to top ]

    I must be missing something.
    Why would int i=map.get(3) throw a NPE??
    Wouldn't a NaN be more appropriate?
  7. auto-unboxing issues[ Go to top ]

    Because int i=map.get(3) is mapped to int i=((Integer)map.get(3)).intValue() and this will cause a NPE, if get() returnes null when there is no associated object with Integer(3). null.intValue() will always throw an exception.
    There is no NaN for int, only for double/float. And a NaN can be a legal value of a map: map.put( 3, Double.NaN );
  8. When I first came to java back in 1996, I thougth having two different type for scalar types (Integer/int, Character/char, Boolean/boolean and so on) was a big mistake. It was a choice : Performance against OO principle. In my mind, an Integer should be an object and int is useless, as performance is a compiler matter.
    ...
    I think that we are getting to where we only have the objects, and the primitives are gone. The performance gain we get with explicit primitives could just as well be gained by the system using them behind our back as an optimization.

    What's needed in addition to autoboxing/unboxing is only that the same object is always generated for the same primitive value. That would make comparision (with ==) of two equal primitives and their boxed versions give the same result.
    The JVM could decide if it want to keep our Integer as an int for performance reason. We would never see any difference in the behaviour.

    We would then, for example, get the same performance whether we wrote:
    Float value = 1.0;
    or:
    float vaule = 1.0;

    I have assumed that this is made so on purpose. For example, the immutability of the number classes is the main reason why it would work.

    I have also assumed that the cost of looking upp and returning the same object for the same value is rather expensive. I think that's the reason we don't have this already in Tiger.

    I'm quite satified with having autoboxing/unboxing, because it allows us to write programs like primitives and number classes were the same, until some time in the future they actually are the same. I regard the difference as manual optimization that the compiler or JVM will do later on.
  9. I think that we are getting to where we only have the objects, and the primitives are gone. The performance gain we get with explicit primitives could just as well be gained by the system using them behind our back as an optimization.
    If you have ever tried to use Java for heavy numerical processing algorithms (such as multiple linear regression with large data sets), you would not want to deprecate primitive types. I have worked with Java extensively in a number of large applications, including one or two involving extensive numerical processing. I have tried to use Double and collection types, or even arrays of Double, but the performance was horrendous when compared to using arrays of the primitive type double. Deprecate primitive types and I will be forced to switch to C++ for similar applications.
  10. I'm sorry, but I really think that it's a compiler issue. You should not choise an int against an Integer just because, years ago, primitive types had been used because they were faster to compute than objects. Assume that a compiler will generate an int if necessary.

    An Integer type could still exist if we need to exploit its imutability property (which is pretty unfrequent, IMHO).
  11. I think that we are getting to where we only have the objects, and the primitives are gone. The performance gain we get with explicit primitives could just as well be gained by the system using them behind our back as an optimization.
    If you have ever tried to use Java for heavy numerical processing algorithms (such as multiple linear regression with large data sets), you would not want to deprecate primitive types. I have worked with Java extensively in a number of large applications, including one or two involving extensive numerical processing. I have tried to use Double and collection types, or even arrays of Double, but the performance was horrendous when compared to using arrays of the primitive type double. Deprecate primitive types and I will be forced to switch to C++ for similar applications.
    You haven't correctly understood what I ment. With the optimization I talked about you get the performance of primitives, but doesn't have to have them in the language. You declare them as instances of the number class, but the system can handle them as it does handle primitives today. Except when you do something that forces it to be an object, such as putting it into a collection.

    As you say, we need the performance of the primitives, and can't throw them out from the language until we have that performcance some other way.
  12. If we assume that the compiler (or JVM) optimizes the performance of the number wrapper classes by internally handling them like the corresponding primitive. Which semantic differences remains, now when we have autoboxing/unboxing? How hard would it be to merge these two concepts completely?

    Conceptually, objects are stored in the heap (and contain a reference to its class). But the number wrapper classes are immutable. It's therefore possible to replace the references to the objects with the data it encapsulates. The object therefore doesn’t have to be stored in the heap. They can in principle be handled like the primitives and be stored in the stack (or registers).

    (A reference to an Integer(12) would, for example, be replaced with the int value 12.)

    Autoboxing/unboxing helps merging the primitives and their wrappers, but there are still some differences. I can think of these:

    1) Comparing two int:s using == says that they are equal when the values are the same. But two number objects based on the same value could still be regarded as unequal by this comparison. I think the solution to this is to always return the same instance for the same value. (This may perhaps be rather expensive.)

    2) The instanceof operator doesn't work for primitives. It should accept a primitive to be an instance of its wrapper class.

    3) You can't call the wrapper class methods on the primitive. Most of the methods aren't really needed if the concepts are merged but in principle this should be handled as if a primitive is an instance of the wrapper class.

    Are there any more semantic differences than this?

    I don't think we should make these changes just for the current set of wrapper classes. The more general concept that we should aim for is to allow the programmer to define his own value classes. By this I mean classes that are final and can be stored in the stack. They also shouldn't have any explicit class reference in their instances, just the naked data.
    An instance of such a class can be allocated on the stack like the primitives can.

    The most important new value class would perhaps be for complex numbers.
  13. There is a third way, i think its possible to give to
    the developer a way to choose between
    auto-unboxing with NPE or with default values.

    Because NPE behavior is not whished primarily when
    auto-unboxing is used with parameterized type,
    I propose a new syntax :

    Map<Integer> map=new HashMap<Integer>();
    int i=map.get(3); // raise a NPE

    Map<int> map=new HashMap<int>();
    int i=map.get(3): // i=0

    What corresponds to permit to use primitive type as parameter type.
    HashMap<int> signifies HashMap<Integer> + boxing + unboxing with
    default values.

    I know that the use of primitive type as parameter type
    has been rejected by JSR14 group but i think they could
    reconsider the question because this syntax clearly simplify
    the auto-unboxing issue.

    Ps: i apologize but i'not a native english speaker.
  14. That's a real nice one![ Go to top ]

    I like that solution. It gives you choise, and is clear on what to expect. default 0 for primitives, and nullables for Objects


    I have a question. Why are primitives implemented as their Object types, and not as arrays:

    int[1] vs Integer


    What are the performance differences between these two?
  15. I have a question. Why are primitives implemented as their Object types, and not as arrays:

    int[1] vs Integer

    What are the performance differences between these two?

    Because int[1] isn't immutable as Integer is. It would be more like an int pointer, not an int object (I used it for this once =P)
  16. Map<Integer> map=new HashMap<Integer>();
    int i=map.get(3); // raise a NPE

    Map<int> map=new HashMap<int>();
    int i=map.get(3): // i=0

    I actually like this a lot. It seems to solve the issues by clearly indicating the use of the parameterized type. The downside I see to this however is that it starts to blur the clear distinction of primitives and classes. Ie. you can't use the type identifier to see have the data is stored behind the scenes.
  17. Map<Integer> map=new HashMap<Integer>();
    int i=map.get(3); // raise a NPE

    Map<int> map=new HashMap<int>();
    int i=map.get(3): // i=0


    I think new operator is a good idea. We can do it this way:
    Map<int> map=new HashMap<int>();
    int i = (map.get(3) == null) ? 0 : map.get(3);

    This way must be more clear and "short" :

    Map<int> map=new HashMap<int>();
    int i = map.get(3) : 0;

    it can be translated to something like this :

    Map<int> map = new HashMap<int>();
    Integer obj = map.get(3);
    int i = ( obj == null) ? 0 : obj;
  18. Map<int,int> map=new HashMap<int,int>();

    I like this syntax, but I don't like how you intended it to work. What this syntax could do is prevent you from supplying a null value in the first place. For example,

    List<int> intList = new ArrayList<int>();
    intList.add( null ); // compiler error - null is not a valid value for a primitive type
    Integer i = null;
    intList.add( i ); // runtime error - java.lang.IllegalArgumentException
    int i = intList.get( 0 ); // guaranteed to never be null

    If Java ever unified primitives and objects, we could introduce option types (like Nice) and int would be an alias for a non-nullable Integer. This syntax would fit in perfectly.

    So basically, my suggestion is that we ammend the generics specification to support primitive types with autoboxing/unboxing, with the semantics that a "primitive type parameter" can never hold the value, null. That would seem to solve everyone's complaints.

    God bless,
    -Toby Reyelts
  19. > Map<int,int> map=new HashMap<int,int>();

    I like this syntax, but I don't like how you intended it to work. What this syntax could do is prevent you from supplying a null value in the first place. For example,

    List<int> intList = new ArrayList<int>();
    intList.add( null ); // compiler error - null is not a valid value for a primitive type
    Integer i = null;
    intList.add( i ); // runtime error - java.lang.IllegalArgumentException
    int i = intList.get( 0 ); // guaranteed to never be null

    If Java ever unified primitives and objects, we could introduce option types (like Nice) and int would be an alias for a non-nullable Integer. This syntax would fit in perfectly.

    So basically, my suggestion is that we ammend the generics specification to support primitive types with autoboxing/unboxing, with the semantics that a "primitive type parameter" can never hold the value, null. That would seem to solve everyone's complaints.

    Your suggestion seems interresting and elegant.
    I my mind, its a complementary approach indeed
    the compiler can add runtime check to avoid the insertion
    of null but cannot avoid a code to return null.

    In the case of the method, Map.get(), even if the compiler doesn't
    permit to add null as argument of the method put(),
    the method get() could return null if the key isn't present in the map.

    So it is not sufficient to check null insertion,
    the compiler must check return value and
    replace null by the type default value.

    God bless,
    -Toby Reyelts

    Rémi Forax
  20. I agree -- remove auto-unboxing[ Go to top ]

    I agree with you completely -- I think auto-unboxing should be revisited. Your example on your blog -- http://www.uwyn.com/blog/archives/000060.html -- really opened my eyes to how these NPEs could lurk invisibly. As I understand it primitives cannot be used as a parameter type because of bytecode backwards-compatibility issues (that would have been my first idea too -- to only unbox when the parameter type is a primitive), so in lieu of that kind of solution I would actually prefer auto-unboxing be removed completely so as to prevent unexpected behavior (from either an NPE or a technically incorrect uninitialized value). I realize it's difficult to remove features once they're in, but 1.5 hasn't been finalized quite yet -- I'm glad you brought this issue to my attention.

    If they do leave auto-unboxing in, I will definitely be very wary when depending on it. Since this feature is mainly for convenience, it doesn't seem right for it to need such careful consideration when employing it. I don't think people will like the sound of removing auto-unboxing, but I think leaving it in will probably raise a lot of problems from unexpected behavior like you have described.
  21. There seem to be two very opposite point of views on the subject of JDK 1.5 auto-unboxing, with opponents and supporters for both and some gray area of things that are bound to taste or are dependent on the use-case. Maybe it's better to pull auto-unboxing back out as was originally planned and only perform auto-boxing.

    ...

    I think that by throwing NPE exceptions, masked errors are always introduced since there's no clear indication of where to perform null checks.

    For me NPE solution is quit obvious. It’s a fail-fast and most restrictive which is what it should be. Unless you are catching runtime NPE (which is a bug in most of the cases anyways) the calling thread will be terminated and error condition will become clear one way or another. Returning default value (as oppose to NPE) will delay the error detection if not completely hide it – which is the worse practice in any case.

    So I fail to see the point in the original post.

    Regards,
    Nikita.
    xTier - Service Oriented Middleware
  22. For me NPE solution is quit obvious.

    Sinnce when do you find NPE obvious on primitives? Auto-unboxing results in a primitive and most often raises its head since you're forced to use classes together with parametrized types or collections. Having a NPE appear behind the scenes is totally un-obvious. When is the last time you had NPE when you conceptually work/think without references?
  23. Sinnce when do you find NPE obvious on primitives? Auto-unboxing results in a primitive and most often raises its head since you're forced to use classes together with parametrized types or collections. Having a NPE appear behind the scenes is totally un-obvious. When is the last time you had NPE when you conceptually work/think without references?

    Well, I think it’s pretty obvious indeed. Unboxing (manual or automatic for that matter) converts reference type to primitive that by definition can result in exceptional situation (<code>null</code> reference doesn’t have default primitive representation) and such situation is commonly handled by NPE in Java. Most of NPE in Java happen "behind the scenes" anyways so no surprise here as well. That’s all.

    Regards,
    Nikita.
    xTier - Service Oriented Middleware
  24. my two cents[ Go to top ]

    So when we introduce autoconverting between objects and primitives some previous not present behaviour is added?... Yup. Sounds logical. Hence throwing NPE's in an appearantly primitive expression when using auto-unboxing is (as Nikita said) perfectly legit. So starting with Java1.5 that is something we need to watch for. Furthermore autoboxing is an addon, because you still can do old style manual (un)boxing, so if you don't like it, don't use it. I know I will.

    So my opinion: the NPE is the only sensible solution since it does not obfuscate a possible error, and I do not see the problem there. And the <Integer> vs <int> solution would be a nice add. As would the "quick null check" operator. Let's do all three! ;-)

    Tom
  25. my two cents[ Go to top ]

    BTW "quick null check" instruction exists in JVM. It must be very trivial to add this kind of operator too.
  26. my two cents[ Go to top ]

    Juozas: BTW "quick null check" instruction exists in JVM.

    The "quick" instructions do not exist in the JVM. They exist as an optimization in an early Sun interpreter (an implementation of the JVM.)

    Peace,

    Cameron Purdy
    Tangosol, Inc.
    Coherence: Clustered JCache for Grid Computing!
  27. Sinnce when do you find NPE obvious on primitives? Auto-unboxing results in a primitive and most often raises its head since you're forced to use classes together with parametrized types or collections. Having a NPE appear behind the scenes is totally un-obvious. When is the last time you had NPE when you conceptually work/think without references?
    Auto unboxing is a useles feature,
    it must be better to use custom type safe collections for primitive types,
    but it makes generistics useless for primitive types too (NullPointerException is not better than ClassCastExeption at runtime).
  28. What we really need[ Go to top ]

    I think what we really need is an operator to substitute a null value with a default value in Java. How many times have you done the following?

    int i = (map.get(3) == null ? 999 : map.get(3));

    I'd propose a shorthand for it:

    int i = map.get(3) ? 999;

    This provides a nice syntax to avoid NPE in many situations, not just for the unboxing problem.
  29. What we really need[ Go to top ]

    Oh, by the way, I want to propose that the compiler mandates this default value operator in an unboxing context.
  30. Psudo-casting to allow nulls[ Go to top ]

    I would suggest using a psudo-cast to allow autoboxing/unboxing of null.

    When autounboxing, it could be like this:

      int v1 = (nullIsZero)m.get(key);
      int v2 = (nullIsOne)m.get(key);

    The psuedo-casts are tags for allowing null and telling the system how to handle them. Instead of an option to allow autoboxing of null in the entire program, you can select the behavior exactly where you need it.

    In the same manner, I would psudo-cast when autoboxing.

     int value = 0;
     m.put(key, (zeroIsNull)value);

     int v2 = 1;
     m.put(key, (oneIsNull)value);

    The value actually put into the list for zero (resp. one) is null.

    we need this because we want the value in the list to remain null if we retrive it with autounboxint, and later store the value back with autoboxing. It would be a shame if we filled the list with Integer(0), when it originally had null:s, which we wanted.

    Witout a psudo-cast, a null value would cause an exception - as the current suggestion says.