There are many ways to do more or less what you're looking for .... I'm not familiar with the "entity-component-system" or Unity, but I built an example that may fit your needs.
It starts with an Component type, I made an empty interface for that, but you can make it an abstract class or whatever fits your needs:
public interface Component {
}
The next class I added is a ComponentFactory. An ComponentFactory creates Component-Objects of a fixed Type, given some parameter.
public interface ComponentFactory<IN, T extends Component> {
Class<T> getComponentClass();
T createComponent(IN input);
}
The next class is Lazy, which simply adds support for Lazy but only-once initialization of anything. I will later use it.
public static class Lazy<T> {
private volatile T value;
private final Supplier<? extends T> supplier;
public Lazy(Supplier<? extends T> supplier) {
this.supplier = supplier;
}
public T getValue() {
if (this.value == null) {
synchronized (this) {
if (this.value == null) {
this.value = this.supplier.get();
}
}
}
return this.value;
}
}
The next class is Entity. Entity is one of the classes you seem to already have, but you struggled because an Entity would need it's components passed to the constructor, but the Components would also need the Entity passed in the constructor. The Entity now won't receive the Components passed in its constructor, but factories which it may use to construct actual Components at a later point in time.
public static class Entity {
private final Map<Class<? extends Component>, Lazy<Component>> components;
public Entity(List<ComponentFactory<? super Entity, ?>> componentFactories) {
// do all of the basic construction
final Map<Class<? extends Component>, Lazy<Component>> components = new HashMap<>();
for (ComponentFactory<? super Entity, ?> factory : componentFactories) {
components.put(factory.getComponentClass(), new Lazy<>(() -> factory.createComponent(this)));
}
this.components = Collections.unmodifiableMap(components);
}
public <T extends Component> T getComponent(Class<T> componentClass) {
final Lazy<Component> lazy = this.components.get(componentClass);
final T component;
if (lazy != null) {
component = componentClass.cast(lazy.getValue());
} else {
component = null;
}
return component;
}
}
Example
As an example I created the 2 classes "TransformComponent" which takes an Entity and "PhysicsComponent" which takes an entity and uses that entity to receive the TransformComponent of the given entity.
public static class PhysicsComponent implements Component {
public static final ComponentFactory<Entity, PhysicsComponent> FACTORY = new ComponentFactory<>() {
@Override
public Class<PhysicsComponent> getComponentClass() {
return PhysicsComponent.class;
}
@Override
public PhysicsComponent createComponent(Entity input) {
return new PhysicsComponent(input);
}
};
private final TransformComponent tf;
public PhysicsComponent(Entity player) {
this.tf = player.getComponent(TransformComponent.class);
}
}
public static class TransformComponent implements Component {
public static final ComponentFactory<Entity, TransformComponent> FACTORY = new ComponentFactory<>() {
@Override
public Class<TransformComponent> getComponentClass() {
return TransformComponent.class;
}
@Override
public TransformComponent createComponent(Entity input) {
return new TransformComponent(input);
}
};
public TransformComponent(Entity player) {
}
}
Note that both Component-Implementation have an public static final Variable for their own Factory.
And finally, we can create our player Entity:
public static void example() {
final Entity player = new Entity(List.of(TransformComponent.FACTORY, PhysicsComponent.FACTORY));
final PhysicsComponent physicsComponent = player.getComponent(PhysicsComponent.class);
}