简体   繁体   中英

Field access in Groovy for super class

In Groovy there is an @ operator which enables direct field access. However it looks like it won't work for fields declared in super class. Consider two Java (not Groovy) classes:

class Entity {
  private Long id;

  Long getId() {
    return id;
  }
}

class User extends Entity {
}

Then invoking direct access in Groovy

User user = new User();
user.@id = 1L

ends up with exception: groovy.lang.MissingFieldException: No such field: id for class User

When I try to use standard access user.id = 1L I get groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: id for class User

Is there any option to access field declared in super class?

You would probably need to declare the property as protected instead:

class Entity {
  protected Long id;

  Long getId() {
    return id * 2;
  }
}

class User extends Entity {
}

User user = new User();
user.@id = 1L

assert user.@id == 1L
assert user.id == 2L

This is a modified example for the direct access field operator.

You can access via regular Java reflection but I'm not sure how to make this more "Groovy".

User user = new User()
fields = user.getClass().superclass.declaredFields
idField = fields[0]
idField.accessible = true
idField.set(user, 2L)
println idField.get(user)

Private fields can't be accessed from the children classes (they aren't inherited). Although Groovy lets you access private fields easier than Java reflection, it still can't access fields which aren't exist.

Let me revive this question as I'm going through a similar thing recently. Here are a few thoughts...

As it's needed for tests perhaps a comparison by equals would do instead of checking/setting fields directly?

Pattern worth considering if the alternative is de-encapsulation. I mean, if there is no need to touch these fields in prod code ideally they shouldn't need being touched in tests either.

Entity would have own fields based equals and hashCode implemented. Lombok's @EqualsAndHashCode annotation can help with reducing the boilerplate.

Test goes then like expect: new Entity(id) == new Entity(id) (assuming there is such constructor).

If equality is not the way to go package scope might give better encapsulation with an appropriate package structure (main/src/java):

package foo.bar

public class Entity {
    Long id
}

Control over the id access is more flexible now and independent from the test code. While User extends Entity can have no access to the id if placed in a different package, the test code can have a class accessing it, like for example (src/test/groovy):

package foo.bar

class EntityAccess {
    private final Entity entity

    EntityAccess(Entity entity) {
        this.entity = entity
    }

    static EntityAccess access(Entity entity) {
        new EntityAccess(entity)
    }

    Long getId() {
        entity.id
    }

    void setId(Long id) {
        entity.id = id
    }
}

I'm sure the boilerplate can be reduced with some fancy Groovy AST annotations. The point is prod code can keep the fields hidden while in tests, with a static import for EntityAccess.access , a field can be accessed without any Groovy hax0rs:

given: access(entity).id = 5 to set or expect: access(entity).id == 5 to assert on entity's id.

If possible though, I would've rather kept both the Entity and User immutable and tested by equality without altering the objects state.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM