javafunctional-interface

How to make a static version of a Map holding getters/setters of an object?


I have a map I build when called on object that provides caller with variable names as key and getter/setter pair as values. This works as expected. My issue is that I build it every time I call for it, and I am wondering if there is a way to declare it static and supply only the object I want to invoke it on, that way I'm not building the map every time since getters and setters don't change at runtime.

What I have:

package main;
import org.javatuples.Pair;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Supplier;

public interface MapPackHelperI 
{
     public Map<String, Pair<Supplier, Consumer>> getNameToGetterSetterMap();    
}
package main;

import java.util.List;
import java.util.Map;
import java.util.HashMap;
import org.javatuples.Pair;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class SomeStruct implements MapPackHelperI
{
    private long somelong;
    private String somestring;
    private List<Float> somelistfloat;
    private SomeEnum someenum;

    public  Map<String, Pair<Supplier, Consumer>> getNameToGetterSetterMap()
    {
        Map<String, Pair<Supplier, Consumer>> nameToGetterSetterMap = new HashMap<>();        
        nameToGetterSetterMap.put("somelong", Pair.with( this::getSomelong, (Consumer<Long>)this::setSomelong));
        nameToGetterSetterMap.put("somestring", Pair.with( this::getSomestring, (Consumer<String>)this::setSomestring));
        nameToGetterSetterMap.put("somelistfloat", Pair.with( this::getSomelistfloat, (Consumer<List<Float>>)this::setSomelistfloat));
        nameToGetterSetterMap.put("someenum", Pair.with( this::getSomeenum, (Consumer<SomeEnum>)this::setSomeenum));
        return nameToGetterSetterMap;
    }

    public long getSomelong() {
        return this.somelong;
    }

    public void setSomelong(long somelong) {
        this.somelong = somelong;
    }

    public String getSomestring() {
        return this.somestring;
    }

    public void setSomestring(String somestring) {
        this.somestring = somestring;
    }

    public List<Float> getSomelistfloat() {
        return this.somelistfloat;
    }

    public void setSomelistfloat(List<Float> somelistfloat) {
        this.somelistfloat = somelistfloat;
    }

    public SomeEnum getSomeenum() {
        return this.someenum;
    }

    public void setSomeenum(SomeEnum someenum) {
        this.someenum = someenum;
    }

   // ... hashcode, toString, and equals, not relevant for the example
}

This allows me to do from elsewhere:

public static String serialize(MapPackHelperI objectToSerialize) 
{
   Map<String, Pair<Supplier, Consumer>> nameToGetterSetterMap = objectToSerialize.getNameToGetterSetterMap();
   for(Entry<String, Pair<Supplier, Consumer>> current : nameToGetterSetterMap.entrySet())
   {
       String name = current.getKey();
       Supplier getter = current.getValue().getValue0();
       //code that serializes into string with name and getter regardless of the MapPackHelperI I pass in
   }

  // return the string representation of the object. I have another method that uses the same map to go the other way with the setter.
}

Like I said, this works, but I'm instantiating and filling the map each time I call on it.

Is there a way to have SomeStruct have a public static Map<String, Pair<Supplier, Consumer>> nameToGetterSetterMap = new HashMap<>(); (or some equivalent) such that public Map<String, Pair<Supplier, Consumer>> getNameToGetterSetterMap() takes as parameter an instantiated SomeStruct and just applies the calls to that particular object.

I tried doing a static map and filling it with SomeStruct::getSomelong and so on, but the compiler said I can't do that.


Solution

  • Member field

    The answer seems so obvious that I must be missing something: Cache the newly constructed map as a private member field.

    public class SomeStruct implements MapPackHelperI
    {
        …
        private Map<String, Pair<Supplier, Consumer>> map = 
            Map.of( 
                "somelong", Pair.with( this::getSomelong, (Consumer<Long>)this::setSomelong) ,
                "somestring", Pair.with( this::getSomestring, (Consumer<String>)this::setSomestring) ,
                "somelistfloat", Pair.with( this::getSomelistfloat, (Consumer<List<Float>>)this::setSomelistfloat) ,
                "someenum", Pair.with( this::getSomeenum, (Consumer<SomeEnum>)this::setSomeenum)
            ) ;
    
        public  Map<String, Pair<Supplier, Consumer>> getNameToGetterSetterMap()
        {
            return this.map ;
        }
    
    …
    

    Be aware that the Map implementation returned by Map.of is shallowly unmodifiable.

    You should be able to make that map member variable static. But unless you know for certain you are doing a zillion instantiations of SomeStruct, I would consider that a micro-optimization that is not worth the bother.

    private static Map<String, Pair<Supplier, Consumer>> map = …
    

    By the way, that JavaTuples library looks nifty. But with records in Java 16+, you might consider writing your own little Pair class.