Bi-directional Map in Java?
You can use the Google Collections API for that, recently renamed to Guava, specifically a BiMap
A bimap (or "bidirectional map") is a map that preserves the
uniqueness of its values as well as that of its keys. This constraint
enables bimaps to support an "inverse view", which is another bimap
containing the same entries as this bimap but with reversed keys and
values.
How to create a 2 way map in java
It seems like you may be looking for a bimap.
The Google Collections (now a part of Guava) contains an BiMap
interface with a few implementations.
From the BiMap
documentation:
A bimap (or "bidirectional map") is a
map that preserves the uniqueness of
its values as well as that of its
keys. This constraint enables bimaps
to support an "inverse view", which is
another bimap containing the same
entries as this bimap but with
reversed keys and values.
The BiMap.inverse
method appears to return a Map
with the values as the keys, and the keys as the values, so that Map
can be used to call get
on the value and retrieve a key.
In addition the Map
returned by inverse
is a view of the underlying data, so it does not have to make extra copies of the original data.
From the BiMap.inverse
method documentation:
Returns the inverse view of this
bimap, which maps each of this bimap's
values to its associated key. The two
bimaps are backed by the same data;
any changes to one will appear in the
other.
Bidirectional multi-valued map in Java
So you need support for many-to-many relationships? Closest you can get is Guava's Multimap
like @Mechkov wrote - but more specifically Multimap
combination with Multimaps.invertFrom
. "BiMultimap" isn't implemented yet, but there is an issue requesting this feature in Google Guava library.
At this point you have few options:
If your "BiMultimap" is going to immutable constant - use
Multimaps.invertFrom
andImmutableMultimap
/ImmutableListMultimap
/ImmutableSetMultimap
(each of theese three has different collection storing values). Some code (example taken from app I develop, usesEnum
s andSets.immutableEnumSet
):public class RolesAndServicesMapping {
private static final ImmutableMultimap<Service, Authority> SERVICES_TO_ROLES_MAPPING =
ImmutableMultimap.<Service, Authority>builder()
.put(Service.SFP1, Authority.ROLE_PREMIUM)
.put(Service.SFP, Authority.ROLE_PREMIUM)
.put(Service.SFE, Authority.ROLE_EXTRA)
.put(Service.SF, Authority.ROLE_STANDARD)
.put(Service.SK, Authority.ROLE_STANDARD)
.put(Service.SFP1, Authority.ROLE_ADMIN)
.put(Service.ADMIN, Authority.ROLE_ADMIN)
.put(Service.NONE, Authority.ROLE_DENY)
.build();
// Whole magic is here:
private static final ImmutableMultimap<Authority, Service> ROLES_TO_SERVICES_MAPPING =
SERVICES_TO_ROLES_MAPPING.inverse();
// before guava-11.0 it was: ImmutableMultimap.copyOf(Multimaps.invertFrom(SERVICES_TO_ROLES_MAPPING, HashMultimap.<Authority, Service>create()));
public static ImmutableSet<Authority> getRoles(final Service service) {
return Sets.immutableEnumSet(SERVICES_TO_ROLES_MAPPING.get(service));
}
public static ImmutableSet<Service> getServices(final Authority role) {
return Sets.immutableEnumSet(ROLES_TO_SERVICES_MAPPING.get(role));
}
}If you really want your Multimap to be modifiable, it will be hard to maintain both K->V and V->K variants unless you will be modifying only
kToVMultimap
and callinvertFrom
each time you want to have its inverted copy (and making that copy unmodifiable to make sure that you accidentally don't modifyvToKMultimap
what wouldn't updatekToVMultimap
). This is not optimal but should do in this case.(Not your case probably, mentioned as bonus):
BiMap
interface and implementing classes has.inverse()
method which givesBiMap<V, K>
view fromBiMap<K, V>
and itself afterbiMap.inverse().inverse()
. If this issue I mentioned before is done, it will probably have something similar.(EDIT October 2016) You can also use new graph API which will be present in Guava 20:
As a whole, common.graph supports graphs of the following varieties:
- directed graphs
- undirected graphs
- nodes and/or edges with associated values (weights, labels, etc.)
- graphs that do/don't allow self-loops
- graphs that do/don't allow parallel edges (graphs with parallel edges are sometimes called multigraphs)
- graphs whose nodes/edges are insertion-ordered, sorted, or unordered
Use bidirectional entity methods with Mapstruct
MapStruct has the concept of Collection Mapping Strategies. It allows you to use adders when mapping them.
e.g.
@Mapper(componentModel = "spring", collectionMappingStrategy = CollectionMappingStrategy.ADDER_PREFERRED)
public interface TestMapper {
Parent toEntity(ParentDto parentDto);
}
Mapstruct bidirectional mapping
Check out the Mapstruct mapping with cycles example.
A solution to your problem is also demonstrated in the documentation for Context annotation.
Example
A complete example: https://github.com/jannis-baratheon/stackoverflow--mapstruct-mapping-graph-with-cycles.
Reference
Mapper:
@Mapper
public interface UserMapper {
@Mapping(target = "userProfileEntity", source = "userProfile")
UserEntity mapToEntity(User user,
@Context CycleAvoidingMappingContext cycleAvoidingMappingContext);
@InheritInverseConfiguration
User mapToDomain(UserEntity userEntity,
@Context CycleAvoidingMappingContext cycleAvoidingMappingContext);
@Mapping(target = "userEntity", source = "user")
UserProfileEntity mapToEntity(UserProfile userProfile,
@Context CycleAvoidingMappingContext cycleAvoidingMappingContext);
@InheritInverseConfiguration
UserProfile mapToDomain(UserProfileEntity userProfileEntity,
@Context CycleAvoidingMappingContext cycleAvoidingMappingContext);
}
where CycleAvoidingMappingContext
keeps track of the already mapped objects and reuses them avoiding the stack overflow:
public class CycleAvoidingMappingContext {
private final Map<Object, Object> knownInstances = new IdentityHashMap<>();
@BeforeMapping
public <T> T getMappedInstance(Object source,
@TargetType Class<T> targetType) {
return targetType.cast(knownInstances.get(source));
}
@BeforeMapping
public void storeMappedInstance(Object source,
@MappingTarget Object target) {
knownInstances.put(source, target);
}
}
Mapper usage (mapping single object):
UserEntity mappedUserEntity = mapper.mapToEntity(user, new CycleAvoidingMappingContext());
You can also add a default method on your mapper:
@Mapper
public interface UserMapper {
// (...)
default UserEntity mapToEntity(User user) {
return mapToEntity(user, new CycleAvoidingMappingContext());
}
// (...)
}
Does Java have a HashMap with reverse lookup?
There is no such class in the Java API. The Apache Commons class you want is going to be one of the implementations of BidiMap.
As a mathematician, I would call this kind of structure a bijection.
Related Topics
Jackson JSON Custom Serialization for Certain Fields
How to Convert "Mon Jun 18 00:00:00 Ist 2012" to 18/06/2012
Why Can't I Use \U000D and \U000A as Cr and Lf in Java
Differencebetween Synchronized on Lockobject and Using This as the Lock
Generating 8-Character Only Uuids
Concurrent Threads Adding to Arraylist at Same Time - What Happens
Tomcat 7 "Severe: a Child Container Failed During Start"
Apache Httpclient 4.0.3 - How to Set Cookie with Sessionid for Post Request
Spring Boot Actuator Without Spring Boot
Making Spring-Data-Mongodb Multi-Tenant
How to Unzip Files Recursively in Java
Hibernate/JPA - Annotating Bean Methods VS Fields