Can I pass a complex type structure in Java generics?
Asked Answered
F

1

12

I am currently trying to implement an API for a conceptual model using Java interfaces and generics. The model (Transmodel V5.0) is described as an entity-relationship model in great detail, but it does not specify some of the base types used. For example the types of identifiers for the various entities or the types used to establish ordering in sequences are not defined.

Since I want to keep the API as generic as possible I started using generics to configure those details. I do not want to make any assumptions about the types, including not assuming that anything is consistent. Each entity can have a different identifier type, each sequence can have a different type used for ordering purposes.

The problem I am facing is that the complexity grows fast once one entity references another -- not only do I need to pass the types for its identifier, but also everything needed to configure the referenced entity.

For example I have:

/**
 * @param <ID> The type for the identifier of this entity.
 * @param <ID_JP> The type identifying journey patterns.
 * @param <OP_JP> The ordering used for points in journey patterns.
 * @param <JP> The type of journey pattern referenced by this entity.
 */
public interface VehicleJourney<
        ID,
        ID_JP, OP_JP extends Comparable<OP_JP>, JP extends JourneyPattern<ID_JP, OP_JP>
    > extends IdentifiableObject<ID>
{
    JP getJourneyPattern();
}

I can still read that and make sense of it, but it is getting more than a bit verbose. And then entities like the VehicleJourney can be referenced in other entities, which makes the type parameter list explode. This is pretty much the smallest non-trivial example I can think of.

Is there a way to create a single Java entity modelling the whole configuration of the type system? I am thinking of something that would have all the identifier types and ordering types attached and can then be passed around as one, turning the example above into something like this:

public interface VehicleJourney<CONF, JP extends JourneyPattern<CONF>> 
       extends IdentifiableObject<???>
{
    JP getJourneyPattern();
}

In the spot with the question marks the type of the VehicleJourney identifier would have to be extracted from CONF somehow. If that would be feasible, then the complexity should stay at a manageable level.

Foreordain answered 31/1, 2014 at 1:32 Comment(9)
I'm sure C++ has a way to accomplish this, and that whatever it is, it would make my head explode.Ebenezer
Without inheritance, I don't think so.Lyckman
You can decalre new types without parameters extending the complex one with a meaningful names. Unfortunately this has nasty side-effects.Dorsiventral
@ajb, C++ has typedefs but they don't work in compiler output :(Dorsiventral
Scala has type variables which are almost (but not exactly) what's needed.Dorsiventral
@Basilevs: I am using plain types in the concrete models, providing subtypes/implementations that bind all type parameters. But that doesn't solve the pain of having to write the generic model, nor does it make writing a concrete version easy ;-)Foreordain
Moreover that forces you to use concrete type everywhere loosing a part of generics usefulness.Dorsiventral
Yeah, if you can use a JVM language that's not java, I think Scala can do this with abstract type members, but I'm not 100% sure.Vulpecula
@Basilevs, Rob N: Scala might actually be an option -- those abstract type members sure look tasty. Let's see if I get a definite answer on my question first while I read up on Scala's type system.Foreordain
S
1

Not going to be pretty.

You can use unanchored types, and simplify by having a type that groups the id and comparator types:

public interface Meta<ID, COMP extends Comparable<COMP>> {

}

public interface IdentifiableObject<M extends Meta<?, ?>> {

}

public interface JourneyPattern<M extends Meta<?, ?>>
        extends IdentifiableObject<M> {

}

public interface VehicleJourney<M extends Meta<?, ?>, JP extends JourneyPattern<?>>
        extends IdentifiableObject<M> {

}

You really don't need to give names to anchor the nested types of the types. When you have the concrete class (or even sub-interfaces), all the types will be anchored anyway. For example:

public class VehicleJourneyImplMeta implements Meta<String, String> {

}

public class VehicleJourneyImpl extends IdentifiableObjectBase<VehicleJourneyImplMeta> 
        implements VehicleJourney<
                VehicleJourneyImplMeta, 
                JourneyPattern<Meta<Integer, String>>> {

}

You will need to delegate accessing of the types in Meta by using intermediate classes, most likely anonymous ones:

VehicleJourney<Meta<String, String>, ?> v = something();
Meta<String, String> m = v.getObjectMeta();
String idOfV = m.getId();

With help of various type parameters at method level, you can probably make this work.

Seems to me what you (and me, and most people) really want is something like this:

public interface VehicleJourney<M extends Meta<?, ?>, JP extends JourneyPattern<?>>
        extends IdentifiableObject<M> {

        public JP.M.ID getIdOfReferencedX();
}

Unfortunately Java does not support typing JP.M.ID as return type. Maybe someone will raise a JSR for this. Bytecode contain names of generic type parameters, if I remember correctly.

Stepdaughter answered 31/1, 2014 at 5:34 Comment(1)
I believe the type parameters are 'erased', so might not be easy to backtrackHardner

© 2022 - 2024 — McMap. All rights reserved.