Home > Software design >  How to promise type safety to the compiler in Java
How to promise type safety to the compiler in Java

Time:12-15

How can I give a promise to the compiler that what I am doing is type safe?

I have a class "Container" that can have some info. I extend said class into what I will refer to as EContainer (E for Extended) and define that it can have some specific info, like say an int and a string. Now, I want to be able to make a list of EContainers and sort it by either int or string, and my solution was to have a ContainerList<T> class where T is what you sort by, and you pass in a lambda to act as an adapter for EContainer to extract T.

So far so good, however now say I want to delimit the list in some way into groups, so if the hypothetical EContainer has ints ranging from 0 to 100, I would want to say "group EContainers like this: 0-20, 21-50, 51-100".

My initial guess was that I would add a function I'll refer to as getDelimiter inside EContainer that would be able to return the "group" it is in, utilizing a protected predefined enum that would define delimiters, however, this is where I ran into problems - the way I would return a group is by having a shared value of T that multiple EContainers would return after running getDelimiter, and so I would group based on that, but unfortunately that would mean that getDelimiter would be a multi-type return function and I do not know how exactly I would tell the compiler that "yes, I know what I am doing, and that I will definitely get back T from getDelimiter"

I could probably get around this by adding another lambda in there that would define the function to call on the EContainer to get the delimiter function, and that should be type safe and everything, but I thought that would end up in an unnecessary boilerplate in EContainer, so maybe there is a better option I don't know of? I have to say, it is very difficult to find the keywords I could use for this problem, so I couldn't do much initial research.

Edit: Just had an idea of having getDelimiter return everything as Object and then casting to T right after, sounds like that should work and would minimize boilerplate.

CodePudding user response:

How can I give a promise to the compiler that what I am doing is type safe?

Generally speaking, that is what the @SuppressWarnings("unchecked") does.

It is not clear if it will work for your use-case. (Show us your actual code ... if you want a more specific answer.)

However, there will still be a hidden runtime check to detect that you are not violating runtime type safety. There is no way you can turn off those checks.


Just had an idea of having getDelimiter return everything as Object and then casting to T right after, sounds like that should work and would minimize boilerplate.

Hmm ... you can't cast something statically typed as Object to T. The fundamental problem is that the actual type of T is erased, so the runtime doesn't know what type to cast to.

One possible workaround is to represent the actual type of T as a Class object, and use reflection to do the type check / cast. Indeed, this is the general workaround when you need to do type checks in spite of type erasure. Unfortunately this is cumbersome, because at some point you need to pass an extra explicit Class object as a parameter to make it work.

CodePudding user response:

    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Comparator;
    
    public class StackOverflowNonsense {
        public static abstract class Container{
            public String sharedFunc(){return "this is a shared function";}
        }
        public static class EContainer{
            public int _int; //id=0
            public float _float; //id=!0
            public EContainer(int _int,float _float){
                this._int=_int;
                this._float=_float;
            }
            protected enum Delimiters{
                INT(new ArrayList<Object>(Arrays.asList(0,21,51))),
                FLOAT(new ArrayList<Object>(Arrays.asList((float)0,(float)0.5,(float)1)));
                final ArrayList<Object> delim;
                Delimiters(ArrayList<Object> delim){
                    this.delim=delim;
                }
            }
            public Object getDelimiter(int id){
                if (id==0){
                    int pos=Collections.binarySearch(Delimiters.INT.delim, (Object)_int,Comparator.comparingInt(key->(int)key));
                    return (Object)Delimiters.INT.delim.get(pos<0?-2-pos:pos);
                }else{
                    int pos=Collections.binarySearch(Delimiters.FLOAT.delim, (Object)_float,Comparator.comparingDouble(key->(Float)key));
                    return (Object)Delimiters.FLOAT.delim.get(pos<0?-2-pos:pos);
                }
            }
            @Override
            public String toString(){
                return String.format("EContainer with (%d, %f)", _int,_float);
            }
        }
            public static void main(String[] args){
                ArrayList<EContainer> ContainerList=new ArrayList<>(
                Arrays.asList(new EContainer(0,(float)2.0),new EContainer(69,(float)0.69))
                );
                Collections.sort(ContainerList,Comparator.comparingInt(key->(int)key.getDelimiter(0)));
                System.out.println(ContainerList);
                Collections.sort(ContainerList,Comparator.comparingDouble(key->(Float)key.getDelimiter(1)));
                System.out.println(ContainerList);
            }
    }

This appears to provide the functionality I want, however I'm not sure if this code breaks some holy scripts, so maybe there is a better solution?

Output

  • Related