Namek Dev
a developer's log
NamekDev

Serializing Java: treat all your fields

March 18, 2017
Serializing Java: treat all your fields

All your Java fields belong to us. Reflection is powerful but you have to remember a few rules before dealing with class fields.

This is Serializing Java - emerging series about writing your own serializer in case you didn’t like other serializers.

Don’t let modifiers constrain you

Any private field seems to be not modifiable outside of the owner class. However, it’s as simple as getting a Field  and setting it to be accessible:

Field field = obj.class.getDeclaredField("_myPrivateField");
field.setAccessible(true); // without this line, the next one would throw ReflectionException
field.set(obj, newValue);

Oh, and remember final  fields? Check this out:

static void setFinalStatic(Field field, Object newValue) throws Exception {
    field.setAccessible(true);

    // remove final modifier from field
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    field.set(null, newValue);
}

Boom! final  is mutable.

Mutable Strings

And so, mutable are strings, too! Probably everyone knows why equals()  is essential to comparing strings (because comparing references may not work!).

Hey, look mum, that’s soo clever:

static void mutate(String s) throws Exception {
    Field value = String.class.getDeclaredField("value");
    value.setAccessible(true);
    value.set(s, s.toUpperCase().toCharArray());
}

Suddenly, you realize that all your String fields were never immutable.

Fields of inner class

Inner class (in Java: non-static class) can be instantiated only in a context of parent class instance.

Too complicated? Let’s have an example:

class Game {
  Entity createEntity() {
    return new Entity();
  }

  class Entity {
    GlobalId id;
    String name;
  }

  static class GlobalId {
    int hash;
    Object ref;
  }
}

In this case you can do new Game.GlobalId()  but you can’t new Game.Entity()  outside of Game class. Thanks to that, Entity  has access to all fields of parent Game  object while GlobalId  doesn’t have that connection.

Then there’s the thing - through Reflection we could call getDeclaredFields()  for both Entity  and GlobalId  classes. You’d expect to get 2 fields per class. However, Entity  has these fields:

  • id
  • name
  • this$0

What? Let’s filter that (let’s give some Kotlin here):

val fields = ReflectionUtils.getDeclaredFields(type)
    .filter { !it.name.equals("this$0") }

Seems simple. But. Noticed that zero at the end of this$0 ? Do you expect to see this$1 ? Yes! The trailing number depends on how deep the inner class is and to which this  would refer.

Let’s fix that:

val fields = ReflectionUtils.getDeclaredFields(type)
    .filter { !it.name.startsWith("this$") } // cover hidden field in non-static inner class

Summary

Often there is more trouble and possibilites than you expect. Thus, behold the dychotomy of power and lack of full determinism - that’s in terms of available Reflection power and implicit in-memory dependencies.

References

Artemis Entity Tracker, Daj Się Poznać, Get Noticed 2017, java, Serializing Java
comments powered by Disqus