Most performant way to achieve type safety on primitive types in Java?
Asked Answered
T

2

10

Let's say I'd like to ensure type safety in Java on primitive types. For the sake of an example, let us want to distinguish a Ratio from an AbsoluteValue, both are represented by a double.

Java to my best knowledge does not support type synonyms.

So I'm quite confident some overhead will be involved.

From the top of my head I could think of either defining new calsses which wrap double (a lot of boilerplate), or subclassing Double... or maybe there are even other options?

Which approach gives me the least performance overhead?

EDIT: I'm confined to Java 8, so that's the implicit basis of the question; however, if there are alternative solutions in newer versions of the language I'd still be keen to hear about them.

Termless answered 30/10, 2019 at 9:26 Comment(3)
Well, you can't sub-class Double, it's final. That leaves you with creating a custom wrapper class, if you must.Roccoroch
There is a units of measure API.Bulganin
The least performance impact has a solution based on type annotations, as it wouldn’t affect the runtime behavior at all. In fact, even the Java compiler would ignore them, so you need to integrate an annotation processor enforcing the rule that @Ratio double is not assignable from @AbsoluteValue double. I don’t know whether there are ready-to-use solutions, like configuring existing checker tools to add that rule. Treating int like enum is a related example of using a primitive type with semantics.Ursal
S
2

The most performant way I can think of would be to write your own wrapper classes which have some marker annotation:

@PrimitiveTypeAlias(double.class)
public class Milliseconds
{
    double value() { ... } 
}

Then hook into the annotation processor at compile-time and physically replace the wrapper classes with their primitive counterparts (using something like Lombok).

... But I suspect you may have been implying "most performant and also low-effort" :)

Selfassured answered 30/10, 2019 at 9:57 Comment(13)
Simpler is the other way round, create type annotations and use them together with the primitive type, so the compiler does already generate the intended byte code. So the duty of the annotation processors is to check the correctness of the type use, for which, at least to some degree, tools do already exist.Ursal
As said, these tools do already exist. The already offered features like @NonNull aren’t different. They don’t need to analyze the stack, as they simply check every assignment. When every assignment is correct, the correctness applies transitively to the entire code. I linked already to an example in my previous comment, though it is tailored to int values used as enumeration. But the principle is the same.Ursal
@Ursal The fundamental problem with that strategy that you linked to is that it will only result in compile-time failures if the annotations are present. There is nothing to enforce that you add them. You need to physically remember to annotate every method return, parameter, field... If you miss one half way down a complex call hierarchy, your NavigationMode returns from a rigidly enforced type, back to a plain old int. If you have a proper type system, this can't happen. You can't forget a type like you can with an annotation, since a type is required while an annotation is not.Selfassured
@Selfassured your proposed solution sounds quite interesting, can you point out something in the Lombok (or similar) documentation about how this could be implemented?Bronwen
@Selfassured It starts as simple as a library developer annotating the method parameters which expect it. Since that’s the one who typically creates the annotation, it’s the one who best understands the need for it. Starting with that, all callers without the annotation get flagged and fixes the issues will cause the transitive fixing of all place. It’s not typical to have lots of sinks for a particular custom type.Ursal
@Bronwen It would almost certainly involve forking Lombok and adding your own handlers here - Lombok is not designed to add plugins or anything. See also projectlombok.org/contributing/lombok-execution-pathSelfassured
But before starting to implement anything, you should consider, how many tools for eliminating the boxing overhead of generic code have been developed in the last fifteen years, as eliminating the overhead of the double to Milliseconds boxing is not much different to eliminating the overhead of the double to java.lang.Double boxing. And ask yourself, why it should work better when you try it…Ursal
The point is, the reason why no such thing exists, is, because it is very hard. When you use a reference type like Double or your Milliseconds class, variables of that type can become null or get assigned to reference variables of a wider type like Object or stored into arrays, not only of type Milliseconds[], but also of type Object[] (and the latter may even contain other objects in addition to Milliseconds. So you can’t replace every occurrence of your boxed type with a primitive type equivalent.Ursal
@Selfassured exactly, “no one is actually going to write it”, because it’s insanely complex. But you are suggesting it. Not even that, you were saying that it was preferable over my suggestion to use type annotation based tools which actually exist. What’s the point of suggesting something while assuming that no-one is actually doing it anyway?Ursal
@Ursal He asked what the "most performant way to achieve type safety" was. Your suggestion does not provide type safety, as I already explained. What I have written here seems to me to be the "most performant way to achieve type safety". However, are the benefits of type safety worth the time it would take to write such a thing? I suspect most people's conclusion will be 'no'. Does that invalidate the answer? I don't think so.Selfassured
@Ursal Your suggestion may be a reasonable compromise solution if you are being pragmatic, but it is certainly not "type safety". Anyway, if that is your solution then post it as an answer so it can be properly voted upon.Selfassured
re "low-effort" -- it's not so much about being low effort, but about organisational constraints. I have no control over the build pipeline and getting something like Project Lombok added *cough* corporate *cough* has no good outlook.Termless
@Ursal I encourage you too to "post [your solution] as an answer".Termless
A
2

Take a look a Manifold framework (like Lombok but more concerned with Types than boilerplate reduction). @Extension methods or @Structural interfaces provide various ways of implementing "duck typing" which covers some of the requirement. It won't work directly with primitive fields though. A very basic wrapper class may be optimised by the JIT in various cases (and will be able to use Project Valhalla's inline modifier when that lands).

(I suspect it is a reasonable feature-request to implement first-class aliases to complement Manifold's existing feature set.)

Abseil answered 30/10, 2019 at 11:9 Comment(1)
Raised this to see what the author has to say: github.com/manifold-systems/manifold/issues/133Abseil

© 2022 - 2024 — McMap. All rights reserved.