1 2 3 Previous Next 32 Replies Latest reply: Jun 12, 2010 10:09 PM by 843793 Go to original post RSS
      • 30. Re: What does the return type " <T> T" mean?
        843793
        BenSchulz wrote:
        Only calls to methods whose signature

        - has a type variable bound to a type parameter of the same method as the return type and
        - the variable's type can not be inferred from any of the arguments

        would need to be flagged.
        Then how about this method?
        public <T> T readObjectWithDefault(T default) {
           T next = readObject();
           return next == null ? default : (T) next;
        }
        It has practical use, and is still completely unsafe. Yet static analysis cools couldn't diagnose that one either without having to read very far into semantics.

        One other thing that has been demonstrated is how devastating an effect this can have if you put the result of your readObject method into a generic container. With a cast, you know for sure the ClassCastException, if any, will happen in your code. Consider this method:
        public List<String> readStrings() {
            List<String> myList = new ArrayList<String>();
            myList.add(readObject());
            myList.add(readObject());
            myList.add(readObject());
        }
        
        //somewhere later
        private List<String> data; //assigned the result of readStrings()
        
        //and 25 hours after that:
        public String toString() {
           StringBuilder builder = new StringBuilder();
           for ( String item : data ) {
              builder.append(data).append('\n');
           }
        }
        You can get a ClassCastException hours after readObject was called. That's non-transparent.

        Nevermind cases like this:
        List<String> myList = readObject(); //no warning and no runtime failure, even if it is a List of Integers.
        
        //vs:
        
        List<String> myList = (List<String>)readObject(); //warning given for unchecked cast
        So now I've identified two more cases where use of your method is unsafe, indicated by the compiler to be safe, and if assumed to be safe, would not throw ClassCastExceptions where your method is called but rather down the line (possibly hours, days later) when they are used. That could come with hours or days of debugging to figure out why it's unsafe.

        So now you're faced with further documenting these cases, or again counting on consumers of your library to be perfect and not make any mistakes.
        • 31. Re: What does the return type " <T> T" mean?
          843793
          endasil wrote:
          Then how about this method?
          public <T> T readObjectWithDefault(T default) {
          T next = readObject();
          return next == null ? default : (T) next;
          }
          Absolutely correct, without an implementation to type check it would be impossible to flag readObject(T)T.

          >
          It has practical use, and is still completely unsafe. Yet static analysis cools couldn't diagnose that one either without having to read very far into semantics.

          One other thing that has been demonstrated is how devastating an effect this can have if you put the result of your readObject method into a generic container. With a cast, you know for sure the ClassCastException, if any, will happen in your code. Consider this method:
          public List<String> readStrings() {
          List<String> myList = new ArrayList<String>();
          myList.add(readObject());
          myList.add(readObject());
          myList.add(readObject());
          }
          Those lines actually would not compile as Object would need to be inferred for T. Assuming you make it explicit (myList.add(ois.<String> readObject())) it would still immediately fail with a ClassCastException. You need erasure to help you out here:
          List<String> strings = new ArrayList<String>();
          readAndAdd(strings); // does not fail, admittedly very bad
          strings.add(Test.<String> readObject()); // fails with CCE
          
          private static <T> void readAndAdd(List<T> list) {
            list.add(Test.<T> readObject());
          }
          
          private static <T> T readObject() {
            return (T) (Object) 5;
          }
          Nevermind cases like this:
          List<String> myList = readObject(); //no warning and no runtime failure, even if it is a List of Integers.
          
          //vs:
          
          List<String> myList = (List<String>)readObject(); //warning given for unchecked cast
          At runtime they behave exactly the same. I don't understand how the unchecked-warning makes a difference. A user will ignore it anyway because their sure the next object in the stream will be a List<String>, otherwise they would not have written the line in the first place.
          So now I've identified two more cases where use of your method is unsafe, indicated by the compiler to be safe, and if assumed to be safe, would not throw ClassCastExceptions where your method is called but rather down the line (possibly hours, days later) when they are used. That could come with hours or days of debugging to figure out why it's unsafe.
          Yeah, I agree, one needs to know the use cases and one needs to know the limitations of generics before one should write such a method.
          So now you're faced with further documenting these cases
          Yes and it's getting tiring; this will be my last attempt.
          or again counting on consumers of your library to be perfect and not make any mistakes.
          Well, there are numerous dynamically typed languages which count on the same and even higher levels of perfection. I don't see all those falling apart. python, ruby, php, bash scripts.. all dynamically typed and the world is still turning.

          With kind regards
          Ben
          • 32. Re: What does the return type " <T> T" mean?
            843793
            BenSchulz wrote:
            Those lines actually would not compile as Object would need to be inferred for T. Assuming you make it explicit (myList.add(ois.<String> readObject())) it would still immediately fail with a ClassCastException. You need erasure to help you out here
            Thanks for the education. I wrote that a bit hastily, and as is often the case with generics, my intuition failed me when I guessed at the behaviour. Sorry for failing to verify my point before making it!
            List<String> myList = readObject(); //no warning and no runtime failure, even if it is a List of Integers.
            
            //vs:
            
            List<String> myList = (List<String>)readObject(); //warning given for unchecked cast
            At runtime they behave exactly the same. I don't understand how the unchecked-warning makes a difference. A user will ignore it anyway because their sure the next object in the stream will be a List<String>, otherwise they would not have written the line in the first place.
            At runtime, all generic code runs the same as non-generic code (with the very small exception of some reflective utilities). Warnings and errors are the only possible difference. The unchecked warning makes a difference because it tells the user what they're doing is unsafe. Isn't that the point of generics? Due to type erasure, there is literally no other purpose to using generics (I guess you could use the boilerplate code argument again, but I don't think that actually has business value in this age of auto-complete so I ignore that aspect). Now, obviously at this point it depends on the requirements of the code, but I have actually written a method like this:
               public static void main(String[] args) {
                  Collection raw = Arrays.asList("hello", "world");
                  Collection<String> typed = fromRaw(raw, String.class);
                  //...
               }
               
               @SuppressWarnings("unchecked")
               public static <E> Collection<E> fromRaw(Collection<?> coll, Class<E> clazz) {
                  for ( Object o : coll ) {
                     clazz.cast(o);
                  }
                  return (Collection<E>) coll;
               }
            Because while I knew I expected a Collection of Strings, working on a 10-year old codebase that only recently started migrating to using generics and having 50 developers on it, I knew somewhere down the line somebody would not possibility, but probably give me something different. I would rather pay the runtime penalty for a fail-fast behaviour that fails in my code than risk type unsafety somewhere down the line that's hard to diagnose.
            Yeah, I agree, one needs to know the use cases and one needs to know the limitations of generics before one should write such a method.
            You're right, and I've said what I wanted to say. I've at least proven to myself that there is no way I could ever justify to myself that such a method would ever be warranted to prevent a few characters of code. You know more about this than I do though, so if afterward you have different conclusions, we just have to agree to disagree.
            So now you're faced with further documenting these cases
            Yes and it's getting tiring; this will be my last attempt.
            Sorry! I'm done talking about it too. It's been good, even if I can't convince you in the end. Thanks for sticking with it.
            or again counting on consumers of your library to be perfect and not make any mistakes.
            Well, there are numerous dynamically typed languages which count on the same and even higher levels of perfection. I don't see all those falling apart. python, ruby, php, bash scripts.. all dynamically typed and the world is still turning.
            Java was deliberately designed to prevent programmer error where those types of languages do not. But I'm not trying to say you can prevent mistakes from happening; I'm just saying it's nice to design it in a way that limits the possibility.
            1 2 3 Previous Next