How do I subclass an AutoValue class?
Asked Answered
P

2

15

I'm using an AutoValue extension to generate my Parcelable Android classes. The documentation specifically states that one @AutoValue cannot extend another: https://github.com/google/auto/blob/master/value/userguide/howto.md#inherit

Another developer was helping me out with this, and suggested "If you have common fields, you can put them in an interface that both implementations implement." I'm assuming this means the library favors composition over inheritance.

I admit, I'm kind of lost. If someone could provide a simple concrete example of the simplest way to "subclass" an AutoValue class, it would be appreciated. Here's a simple class:

@AutoParcelGson
public abstract class User implements Parcelable {

    public abstract String username();
    public static User create(String username) {
        return builder().username(username).build();
    }

    public static Builder builder() {
        return new AutoParcelGson_User.Builder();
    }

    @AutoParcelGson.Builder
    public interface Builder {
        Builder username(String username);
        User build();
    }
}

I'd like to have another @AutoValue class called Customer that has a few extra fields. Also, @AutoParcelGson is the @AutoValue extension that I am using, but it is the same behavior.

Praenomen answered 30/9, 2016 at 18:35 Comment(0)
S
1

The documentation of AutoValue says: «This ability is intentionally not supported, because there is no way to do it correctly. See Effective Java, 2nd Edition Item 8: "Obey the general contract when overriding equals".» A good book, i learned a lot. In 3rd Edition, it’s item 10. In short: Let’s say you extend your User class by BetterUser. BetterUser betterUser.equals(User user) might be true in some cases, but in those cases user.equals(betterUser) is not defined. Symmetry is violated. And that’s only one of the problems; the more you try, the worse it gets. Btw, that is not specific for AutoValue classes but a general problem of inheritance.

The only consistent solution is making a new class BetterUser which doesn't extend User but has a field of type User. In that way, you can define the behaviour of BetterUser as you like, without any impact to other classes. Or perhaps, if User has only few fields, just copy&paste code.

Sclar answered 28/7, 2019 at 14:7 Comment(1)
"just copy&paste code" in this context is a very bad advice, a recipe form disaster as the code will divert over time and keeping them in sync is bound to give you problems. Maybe don't use @AutoValue might be a better adviceRevisionism
I
-2

Here's a working code example. The test in my case prints {"newString":"foobar","myInt":1,"myString":"asdf"}.

import com.fasterxml.jackson.annotation.JsonProperty;
public abstract class BaseEvent {
  @JsonProperty("myInt")
  public abstract int myInt();
  @JsonProperty("myString")
  public abstract String myString();
  public static abstract class Builder <B extends Builder<B>> {
    @JsonProperty("myInt")
    public abstract B myInt(int myInt);
    @JsonProperty("myString")
    public abstract B myString(String myString);
  }
}



import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.auto.value.AutoValue;
@AutoValue
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonSerialize(as = ConcreteEvent.class)
@JsonDeserialize(builder = ConcreteEvent.Builder.class)
public abstract class ConcreteEvent extends BaseEvent {
  public static Builder builder() {
    return Builder.builder();
  }
  @JsonProperty("newString")
  public abstract String newString();
  @AutoValue.Builder
  @JsonIgnoreProperties(ignoreUnknown = true)
  public static abstract class Builder extends BaseEvent.Builder<Builder> {
    @JsonCreator
    public static Builder builder() {
      return new AutoValue_ConcreteEvent.Builder();
    }
    @JsonProperty("newString")
    public abstract Builder newString(String newString);
    public abstract ConcreteEvent build();
  }
}





import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import org.junit.Assert;
import org.junit.Test;
public class ConcreteEventTest {
  @Test
  public void concreteEventTest() throws IOException  {
    ConcreteEvent e = ConcreteEvent.builder().myInt(1).myString("asdf").newString("foobar").build();
    ObjectMapper mapper = new ObjectMapper();
    String jsonStr = mapper.writeValueAsString(e);
    ConcreteEvent newEvent = mapper.readValue(jsonStr, ConcreteEvent.class);
    System.out.println(jsonStr);
    Assert.assertEquals(newEvent, e);
  }
}
Irfan answered 17/8, 2020 at 21:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.