Alternative to using Object and casting in a map - java

I have the following class which represents a set of properties.
public class Properties
{
/** String type properties. */
private final List<String> m_stringProperties = Arrays.asList("str1", "str2", "str3");
/** Float type properties. */
private final List<String> m_floatProperties = Arrays.asList("float1", "float2", "float3");
/** Integer type properties. */
private final List<String> m_intProperties = Arrays.asList("int1", "int2");
public class PropertyType
{
private final String m_name;
private final Object m_value;
public PropertyType(String name, Object value)
{
m_name = name;
m_value = value;
}
public String getName()
{
return m_name;
}
public Object getValue()
{
return m_value;
}
}
/** The container for the properties. */
private final Map<String, PropertyType> m_properties = new HashMap<>();
public PropertyType getProperty(String name)
{
return m_properties.get(name);
}
public void setProperty(String name, Object value)
{
if ((m_stringProperties.contains(name) && value instanceof String)
|| (m_floatProperties.contains(name) && value instanceof Float)
|| (m_intProperties.contains(name) && value instanceof Integer))
{
m_properties.put(name, new PropertyType(name, value));
}
else
{
assert false : "Invalid property name";
}
}
}
Notes
Each property has a name and a value.
Property values can be of type String, Float or Integer.
The names of properties is restricted to the values defined in the list at the top of the class.
A given property can only be added to the map if it is the correct type for that property name.
The class could be used as follows:
Properties properties = new Properties();
// set properties
properties.setProperty("str1", "testId");
properties.setProperty("float1", 1.0f);
// get properties
Properties.PropertyType str1 = properties.getProperty("str1");
Properties.PropertyType float1 = properties.getProperty("float1");
Properties.PropertyType invalid = properties.getProperty("unknown"); // return null
System.out.println("str1: " + str1.getValue());
System.out.println("float1: " + float1.getValue());
float f1 = (float) properties.getProperty("float1").getValue();
Object o1 = properties.getProperty("float1").getValue();
System.out.println("f1: " + f1);
System.out.println("o1: " + o1);
properties.setProperty("str1", 1.0f); // assertion - str1 property should be String, not Float
I'd like to know if there is a better way to implement this. Specifically, I'd like to avoid the use of Object and the casting that goes with it. I've experimented with a parametrised class and generic types and even a typesafe heterogenous container as described in Item 20 of Effective Java.
I would like to make it as typesafe as possible - i.e. enforce type checking by the compiler - so that if getProperty is called the return value is automatically the correct type.
I realise that I could overload setProperty for each type and that getProperty could just return an Object instead of the nested type PropertyType but that would still leave me with a container of <String, Object>.
I'm a Java newbie coming from C++. In C++ the map value would be a boost::variant.

To make sure that the class will receive one of the 3 types, and the Compiler will acuse that, you can use some Polymorphism.
Example:
public PropertyType(String name, String value)
{
m_name = name;
m_value = value;
}
public PropertyType(String name, Integer value)
{
m_name = name;
m_value = value;
}
public PropertyType(String name, Float value)
{
m_name = name;
m_value = value;
}
The String,Integer and Float extends Object, so you don't need to cast they to the variable private final Object m_value;
But if you need to check the type of the variable in execution type (For example, create and variable and don't know if it's some of the three types) this may not work.

Related

Java iterate over class fields to create other class

I have a class like this:
public class Example {
private String a;
private Integer b;
private Boolean c;
private List<AnotherClass> d;
}
and I want to convert it to something like this:
[
{
name: "a",
value: "a value"
},
{
name: "b",
value: "1",
},
{
name: "c",
value: "true",
}
]
So, I create a class like this:
public class Test {
private String name;
private String value;
}
I want to have a method to iterate through the Example class so it will produce the Test class without including d attribute. How to achieve that?
This is something you can do easily with reflection. In the example below, I renamed class Test to Property because it represents a key-value pair. If you are happy with using whatever toString() returns as the value for a field, then the solution is pretty simple:
public class Property {
private final String name;
private final String value;
public Property(String name, String value) {
this.name = name;
this.value = value;
}
public static List<Property> toProperties(Object object, String... fieldNames)
throws ReflectiveOperationException
{
ArrayList<Property> properties = new ArrayList<>();
for( String fieldName : fieldNames ) {
Field field = object.getClass().getDeclaredField(fieldName);
properties.add(new Property(fieldName, field.get(object).toString()));
}
return properties;
}
public String toString() {
return String.format("%s: \"%s\"", name, value);
}
}
This sample requires you to specify the names of the desired fields explicitly, for example:
List<Property> properties = Property.toProperties(myExample, "a", "b", "c");
If you'd rather have the fields be auto-detected based on some criterion (for example all primitive and String-typed fields, then you could add this logic within toProperties and get rid of the varargs.
you would need to have some appropriate getters in class Example, and a proper constructor in class Test to initialize the object instance variables like
public Test (String name, int value) {
this.name = name;
this.value = value'
}
Then for each instance of class Example - lets say you have multiple of those in an array or list - you could iterate over them, retrieve the values you want via the getter methods, and initialize one Test object for each one, eg
List<Test> yourListOfTestInstances = new ArrayList<>();
for (Example exampleObject : yourExampleObjectsListOrArray) {
yourListOfTestInstances.add(new Test(exampleObject.getA() , exampleObject.getB()));
}
Then for each created Test instance inside your ArrayList, you could easily build your JSON as needed (even though I do not fully understand why you even need at all this intermediate Test class to do that)

Generic method of mapping variables from 1 class to another

Coming from C++ and currently employed in a Java environment, I was wondering how I would be able to create a mapping of void* and void* in Java in order to create a generic mapping from A to B and from B to A. I am aware that Java doesn't have pointers and references the way C++ does, but am failing to find a method that would still allow this.
An example of what I am trying to achieve:
public class A{
#GenericMapping(1)
private Integer temp1;
}
public class B{
#GenericMapping(1)
private Integer temp2;
}
public class Mapper{
private List<Pair<Integer, Integer>> mapping;
public void map(Object ObjectOfAnyClassButLetsAssumeA, Object ObjectOfAnyClassButLetsAssumeB){
// Get all parameters with GenericMapping above it, get its value
// and match the corresponding value with the value of B
// Resulting in A.temp1 = B.temp2;
}
}
However, if possible I'd rather create a map (like map[A.temp1] = B.temp2) in order to avoid using the #GenericMapping, seeing as that would allow me to not modify the class in any way and still facilitate its mapping.
I think I understand what you want to do here and you can accomplish it with some metadata and Java 8's Lambdas.
What we do is set up a helper class that contains all mappings identified by class and IDs (analogous to your #GenericMapping but without actually annotating the classes) and containing methods for setting and getting the value. It's important that all mappings for the same ID have the same value type or a ClassCastException may be thrown when transferring values.
My example uses three classes where not all mappings apply to all classes.
Here's the code:
public class GenericMappingDemo {
static class A {
private Integer integerA;
private String stringA;
private Float floatA;
public A(final Integer integerA, final String stringA, final Float floatA) {
this.integerA = integerA;
this.stringA = stringA;
this.floatA = floatA;
}
public Integer getIntegerA() {
return integerA;
}
public void setIntegerA(final Integer integerA) {
this.integerA = integerA;
}
public String getStringA() {
return stringA;
}
public void setStringA(final String stringA) {
this.stringA = stringA;
}
public Float getFloatA() {
return floatA;
}
public void setFloatA(final Float floatA) {
this.floatA = floatA;
}
#Override
public String toString() {
return "A{integerA=" + integerA + ", stringA='" + stringA + "', floatA=" + floatA + '}';
}
}
static class B {
private Integer integerB;
private String stringB;
public Integer getIntegerB() {
return integerB;
}
public void setIntegerB(final Integer integerB) {
this.integerB = integerB;
}
public String getStringB() {
return stringB;
}
public void setStringB(final String stringB) {
this.stringB = stringB;
}
#Override
public String toString() {
return "B{integerB=" + integerB + ", stringB='" + stringB + '\'' + '}';
}
}
static class C {
private Float floatC;
private String stringC;
public Float getFloatC() {
return floatC;
}
public void setFloatC(final Float floatC) {
this.floatC = floatC;
}
public String getStringC() {
return stringC;
}
public void setStringC(final String stringC) {
this.stringC = stringC;
}
#Override
public String toString() {
return "C{floatC=" + floatC + ", stringC='" + stringC + "'}";
}
}
static class GenericMapping<C, T> {
final int id;
final Class<C> type;
final Function<C, T> getter;
final BiConsumer<C, T> setter;
public GenericMapping(final int id,
final Class<C> type,
final Function<C, T> getter,
final BiConsumer<C, T> setter) {
this.id = id;
this.type = type;
this.getter = getter;
this.setter = setter;
}
}
static class Mapper {
// All mappings by class and id
private final Map<Class<?>, Map<Integer, GenericMapping<?, ?>>> mappings
= new HashMap<>();
public void addMapping(GenericMapping<?, ?> mapping) {
mappings.computeIfAbsent(mapping.type,
c -> new TreeMap<>()).put(mapping.id, mapping);
}
/**
* Map values from one object to another,
* using any mapping ids that apply to both classes
* #param from The object to transfer values from
* #param to The object to transfer values to
*/
public <From, To> void map(From from, To to) {
Map<Integer, GenericMapping<?, ?>> getters
= mappings.get(from.getClass());
Map<Integer, GenericMapping<?, ?>> setters
= mappings.get(to.getClass());
if (getters == null || setters == null) {
// Nothing to do
return;
}
// Create a set with the ids in both getters and
// setters, i.e. the mappings that apply
Set<Integer> ids = new HashSet<>(getters.keySet());
ids.retainAll(setters.keySet());
// Transfer all mappings
for (Integer id : ids) {
GenericMapping<From, ?> getter
= (GenericMapping<From, ?>) getters.get(id);
GenericMapping<To, ?> setter
= (GenericMapping<To, ?>) setters.get(id);
transfer(from, to, getter, setter);
}
}
private <From, To, V> void transfer(final From from,
final To to, final GenericMapping<From, ?> getter,
final GenericMapping<To, V> setter) {
// This will throw an exception if the mappings are invalid
final V value = (V) getter.getter.apply(from);
setter.setter.accept(to, value);
}
}
public static void main(String[] args) {
final Mapper mapper = new Mapper();
// Mapping definition for class A
mapper.addMapping(new GenericMapping<>(1, A.class,
A::getIntegerA, A::setIntegerA));
mapper.addMapping(new GenericMapping<>(2, A.class,
A::getStringA, A::setStringA));
mapper.addMapping(new GenericMapping<>(3, A.class,
A::getFloatA, A::setFloatA));
// Mapping definition for class B
mapper.addMapping(new GenericMapping<>(1, B.class,
B::getIntegerB, B::setIntegerB));
mapper.addMapping(new GenericMapping<>(2, B.class,
B::getStringB, B::setStringB));
// Mapping definition for class C
mapper.addMapping(new GenericMapping<>(2, C.class,
C::getStringC, C::setStringC));
mapper.addMapping(new GenericMapping<>(3, C.class,
C::getFloatC, C::setFloatC));
// Use the mappings
A a = new A(7, "foo", 3.7f);
B b = new B();
C c = new C();
System.out.printf("A before map: %s%n", a);
System.out.printf("B before map: %s%n", b);
System.out.printf("C before map: %s%n", c);
// This will transfer a.integerA to b.integerB and a.stringA to b.stringB
mapper.map(a, b);
// This will transfer a.stringA to c.stringC and a.floatA to c.floatC
mapper.map(a, c);
System.out.println();
System.out.printf("A after map: %s%n", a);
System.out.printf("B after map: %s%n", b);
System.out.printf("C after map: %s%n", c);
}
}
And the result after running it:
A before map: A{integerA=7, stringA='foo', floatA=3.7}
B before map: B{integerB=null, stringB='null'}
C before map: C{floatC=null, stringC='null'}
A after map: A{integerA=7, stringA='foo', floatA=3.7}
B after map: B{integerB=7, stringB='foo'}
C after map: C{floatC=3.7, stringC='foo'}
Java 7
The same general solution can be used for Java 7, but it will be a lot more verbose. Since Java 7 doesn't have the functional interfaces Function<U, V> and BiConsumer<U, V> you'll need to define these yourself, which isn't that much trouble. It could be argued that they should be defined in Java 8 too so interface and method names makes more sense (e.g. Getter.get and Setter.set).
The big thing is the mapping definitions which will have to use anonymous classes instead of lambdas - lambdas is mostly syntactic sugar for anonymous classes with only one method anyways, but they make the code a lot more readable.
The mapping for a.integerA will look like this in Java 7:
mapper.addMapping(new GenericMapping<>(1, A.class,
new Function<A, Integer>() {
#Override
public Integer apply(final A a1) {
return a1.getIntegerA();
}
},
new BiConsumer<A, Integer>() {
#Override
public void accept(final A a1, final Integer integerA) {
a1.setIntegerA(integerA);
}
}));
You could also have a look at Apache Commons BeanUtils, which also have a quite sophisticated, although explicit (not Annotation-based), Converter API:
http://commons.apache.org/proper/commons-beanutils/javadocs/v1.9.3/apidocs/org/apache/commons/beanutils/Converter.html
http://commons.apache.org/proper/commons-beanutils/javadocs/v1.9.3/apidocs/org/apache/commons/beanutils/ConvertUtilsBean.html

Assign an enum element to an annotation

I would like to use an enum element as a value of an annotation attribute (which requires a string value). Hence, I have created an interface holding the String constants:
public interface MyStringConstants {
public static final String COMPANY_LOGIN = "Company Login";
public static final String COMPANY_LOGOUT = "Company Logout";
...
}
Furthermore I created the enum:
public enum MyEnumType implements MyStringConstants {
COMPANY_CONFIGURATION_READ(MyStringConstants.COMPANY_CONFIGURATION_READ),
COMPANY_CONFIGURATION_WRITE(MyStringConstants.COMPANY_CONFIGURATION_WRITE),
...;
private final String value;
private MyEnumType(final String myStringConstant) {
this.value = myStringConstant;
}
public String getValue() {
return this.value.toString();
}
public static MyEnumType getByValue(final String value){
for(final MyEnumType type : values()){
if( type.getValue().equals(value)){
return type;
}
}
return null;
}
}
There exists an annotation:
#DeviceValidatorOperation(operationType=MyStringConstants.COMPANY_CONFIGURATION_READ)
I would like to define the enum as mentioned above to put as a value for the annotation's operationType attribute. Using my enum from above results in this way:
#DeviceValidatorOperation(operationType=MyEnumType.COMPANY_CONFIGURATION_READ.getValue())
results in Eclipse complaining:
The value for annotation attribute DeviceValidatorOperation.operationType must be a constant expression
How can I achieve to use an enum element as a value for an annotation's attribute?

Is it possible to have a map that only allows value types based on the key?

I know this sounds a little crazy but here it is. I have an enum type that represents represents a bunch of different properties. Each could be just a string but it would be nice to enforce some kind of type safety. So basically check the type associated with each enum value and throw an exception if there is a mismatch. I guess it could be done with instance of but I am curious if there is another way to do this without instanceof. I know that may not be possible but I am curious.
Edit, I created a new example that I think illustrates what I am asking better:
public class CmisProperties {
public enum CmisPropEnum{
Name (PropertyIds.NAME, new String() ),
CreatedBy (PropertyIds.CREATED_BY, new String() ),
CreationDate (PropertyIds.CREATION_DATE, new Date() ),
LastModifiedBy (PropertyIds.LAST_MODIFIED_BY, new String() ),
LastModificationDate (PropertyIds.LAST_MODIFICATION_DATE, new Date() ),
ChangeToken (PropertyIds.CHANGE_TOKEN, new String() );
private String propId;
CmisPropEnum ( String propId , Object templateObject ){
this.propId = propId;
}
public <T> String getPropId(){
return propId;
}
}
private Map<CmisPropEnum, Object> propertyMap = new HashMap<CmisPropEnum, Object>();
public Object getProperty(CmisPropEnum propEnum){
return propertyMap.get(propEnum.getPropId());
}
public void setProperty( CmisPropEnum propEnum, Object value){
propertyMap.put(propEnum, value);
}
}
Later on I want this to happen:
CmisProperties props = new CmisProperties();
/* This causes a compile time exception */
props.setProperty(CmisPropEnum.CreationDate, "foobar" );
/* This I want to be ok, because the type matches that in the enum */
props.setProperty(CmisPropEnum.CreationDate, new Date() );
Check out Josh Bloch's Effective Java, Item 29, where he describes a "typesafe heterogeneous container" that he calls Favorites. The API is
public class Favorites {
public <T> void putFavorite(Class<T> type, T instance);
public <T> T getFavorite(Class<T> type);
}
I think it would fit your needs (probably change the name???). You could call
favorite.putFavorite(Name.getClass(), "Fred");
favorite.putFavorite(ADate.getClass(), new Date(1234));
and later
Date date = favorite.getFavorite(ADate.getClass());
As already mentioned by irreputable, you need classes to have variability based on types (i.e. generics). This is a corresponding generic version of your example:
public class Properties {
public static class Property<E> {
private Property(String name) { this.name = name; }
private final String name;
public String getName() { return name; }
}
public static final Property<String> NAME = new Property<String>("name");
// ... other properties
private Map<Property<?>, Object> propertyMap =
new HashMap<Property<?>, Object>();
#SuppressWarnings("unchecked")
public <E> E getProperty(Property<E> property){
return (E) propertyMap.get(property);
}
public <E> void setProperty(Property<E> property, E value){
propertyMap.put(property, value);
}
}
The usage is type-safe and checked at compile-time:
Properties p = new Properties();
p.setProperty(Properties.NAME, "a string"); // only strings allowed for NAME
String s = p.getProperty(Properties.NAME); // can only get strings for NAME
Enums can't be generic, so we need a normal class
public class Prop<T>
{
// some predefined props
static public final Prop<String> NAME = new Prop<>("Name", String.class);
...
public Prop(String name, Class<T> type) // it's ok, anyone can create new kind of Prop
{...}
Class<T> getClassT() {...}
}
then set/get property methods can have stronger static type checking:
private Map< Prop,Object > propMap = new HashMap<>();
public <T> void setProperty(Prop<T> key, T value){
propMap.put(key, value);
}
#SuppressWarnings("unchecked")
public <T> T getProperty(Prop<T> key)
{
return (T)propMap.get(key);
}
so that this won't compile
setProperty(Prop.NAME, new Integer(1)); // fail
int x = getProperty(Prop.NAME); //fail
note that, each entry in the propMap has a key Prop<X> and value X for some X, and X can be different from entry to entry. We cannot really express that constraint on Map in Java; but the constraint is indeed enforced by app logic (i.e. setProperty() only inserts such entries)
In getProperty we must suppress unchecked warning. It is justified, since we know the value for the key must be of type T, due to previously mentioned constraint. One trick to avoid explicitly suppressing the warning is by Class.cast()
public <T> T getProperty(Prop<T> key)
{
return key.getClassT().cast( propMap.get(key) );
}
but it's only a trick since essentially we moved #SupressWarnings to Class.cast(). This is a worse version in performance and in semantic clarity.

Type erasure and collections

I am having a specific problem implementing a parametrised class Parameter, but this is something I have come across before with generics, so a general solution would be good..
The class Parameter stores a value of one of a strict number of classes:
public class Parameter<T> {
/*
* Specify what types of parameter are valid
*/
private static final Set<Class<?>> VALID_TYPES;
static {
Set<Class<?>> set = new HashSet<Class<?>>();
set.add( Integer.class );
set.add( Float.class );
set.add( Boolean.class );
set.add( String.class );
VALID_TYPES = Collections.unmodifiableSet(set);
}
private T value;
public Parameter(T initialValue) throws IllegalArgumentException {
// Parameter validity check
if (!VALID_TYPES.contains(initialValue.getClass())) {
throw new IllegalArgumentException(
initialValue.getClass() + " is not a valid parameter type");
}
value = initialValue;
}
public T get() { return value; }
public void set(T value) {
this.value = value;
}
}
This is all fine, until I try and store instances of Parameter in a collection. For example:
Parameter<Integer> p = new Parameter<Integer>(3);
int value = (Integer)p.get();
p.set(2); // Fine
ArrayList<Parameter<?>> ps = new ArrayList<Parameter<?>>();
ps.add(p);
value = (Integer)(ps.get(0).get());
ps.get(0).set(4); // Does not compile due to type erasure
What would others do in this situation to get round this?
Thanks
Well, you can't directly work around this.. But perhaps you could remember the class of the initial value?
class Parameter<T> {
// ...
private T value;
private final Class<?> klass;
public Parameter(T initialValue) throws IllegalArgumentException {
if (!VALID_TYPES.contains(initialValue.getClass()))
throw new IllegalArgumentException(...);
value = initialValue;
klass = initialValue.getClass();
}
#SuppressWarnings("unchecked")
public void set(Object value) {
if (value != null && value.getClass() != klass)
throw new IllegalArgumentException(...);
this.value = (T)value;
}
However, you will lose compile-time type checks on set()..
It's not type erasure - you try to assign an integer value to an object type variable. This only works if the parameter type is Integer, then the compiler knows that the integer has to be inboxed.
Try this instead:
ps.get(0).set(new Integer(4));
What you can do right away: remove the <?> expression entirely. It will replace the compiler error by a compiler warning. Not brilliant at all but compiles.

Categories

Resources