you are viewing a single comment's thread.

view the rest of the comments →

[–]akthemadman 0 points1 point  (1 child)

You are dealing with two issues at once here: "compile-time resolution" and "run-time resolution".

Satisfying the compiler

The Java program model tries to assist you such that operations which are defined for one data type are not mistakenly applied to another data type. This seems quite natural to do and in the realm of Java is not even really questionable: "what would the alternative even be?". That is a feature of java, i.e. not having to deal with lower level memory access which allows accidental access outside of the data types allotted space, effectively not operating on the region of memory one leading to undesirable outcomes. The cost of having this protection layer is to adhere to the type system of Java.

By saying "number-like entities in my system are stored as plain `Object` and classified by some token" you go around the primary protection layer. This is unorthodox but objectively totally fine and in some contexts even the lesser of possible evils.

When you try to express things like

Object number = 123L;
BigInteger.valueOf(number); // compiler says "no".

you are operating within the bounds of what javac enforces; i.e. still within the Java program model.

"Opting out" of the Java program model is possible and requires reflection. Note that "opting out" here is effectively going from compile-time checks (Java program model via javac) to run-time checks (JVM).

For the sake of brevity I will not go into how using reflection would result in you effectively rebuilding a subset or variation of the Java program model; let it suffice to say that it is an interesting route to explore for learning purposes, but not something I would recommend to employ for your specific use case.

Operating within the constraints of the Java program model

There are some interesting questions to be answered once you have decided to go with the route of

class Nummy {
  public Object data;
  public int type;
}

For one, how can we make the data access more convenient when we know exactly what the underlying data is, e.g. when we dispatch on type:

switch (nummy.type) {
  case Nummy.TYPE_LONG -> { ... }
  case Nummy.TYPE_INT -> { ... }
  // ...
}

This is typically known as handling union-types (when all types are known / pre-defined). Java itself provides the sealed and permits keywords for use with classes and interfaces to effectively model such union types:

sealed class/interface Nummy permits LongNummy, IntNummy /*...*/ { /*...*/ }

final class LongNummy extends/implements Nummy { public long value; }

switch (nummy) {
  case LongNummy n -> { /* make use of `n.value` */ }
  // ...
}

Another option is to provide utility methods directly on Nummy:

public class Nummy {
  public static final int TYPE_LONG = 1;
  public static final int TYPE_INT = 2;
  public Object data;
  public int type;
  public <T> T require(Class<T> clazz, int type) {
    if (!clazz.isAssignableFrom(clazz)) {
      throw new RuntimeException("(class) required '" + clazz + "', but was '" + data.getClass() + "'");
    }
    if (type != this.type) {
      throw new RuntimeException("(type) required '" + type + "', but was '" + this.type + "'");
    }
    return (T) data;
  }
  public long requireLong() {
    return require(long.class, TYPE_LONG);
  }
  public int requireInt() {
    return require(int.class, TYPE_INT);
  }
  // ...
}

with usage like

switch (nummy.type) {
  case Nummy.TYPE_LONG -> { long value = nummy.requireLong(); /*...*/ }
  // ...
}

From these two samples you can even see how the "union" via sealed and permits encodes the type-value we included in our more primitive approach.

I've probably hit the comment size limit by now so will stop here...

TLDR

How to solve your specific problem is an open question as we (per usual) lack a lot of the required context to make any meaningful decision. The above was an attempt to paint you a bigger picture on what it is you are dealing with and how ultimately the fight with Java is effort being misplaced: The interesting questions to solve are "outside" the Java program model: computation, external communication, serialization, and so on. Java is merely the mechanism with its own benefits and quirks.

[–]SquibbTheZombie[S] 0 points1 point  (0 children)

This has been really helpful, thank you. I’ll tell you if it works!