Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow base classes for data classes #31

Closed
udalov opened this issue Jun 23, 2016 · 8 comments
Closed

Allow base classes for data classes #31

udalov opened this issue Jun 23, 2016 · 8 comments
Assignees
Labels
Milestone

Comments

@udalov
Copy link
Member

udalov commented Jun 23, 2016

Proposal

@stangls
Copy link

stangls commented Apr 7, 2017

👍 👍 👍

@dzharkov
Copy link
Contributor

dzharkov commented Feb 14, 2018

The feature has been released in Kotlin 1.1, thus I'm closing the KEEP. Do not hesitate to open a YouTrack issue for any additional suggestions.

@grodzickir
Copy link

Probably this has been said somewhere, but why exactly can't a data class inherit from another data class?
Let's say I have
data class Base(val a: Int)

and

data class Inherited(val b: Int):Base

I'd expect it to be generated that for Inherited class 'a' is component1 and 'b' is component2 and all the additional methods to be generated with this in mind.

It would be super useful to create hierarchies like Resource and Book or Info and DetailedInfo or multitude of other use cases where I need to add variables to a class without repeating the ones from the base class.

@gildor
Copy link
Contributor

gildor commented Jul 12, 2018

@grodzickir There is no way to implement hashCode and equals correctly for such hierarchy. It's not something Kotlin only, check for example AutoValue, they have exactly the same approach, you can extend abstract classes and interfaces, but cannot extend other value classes.
This is just a general limitation. For more details you check Effective Java "Obey the general contract when overriding equals" chapter

@grodzickir
Copy link

OK, I've read a bit about this and I've found that there is no way of doing it only if we want to comply to Liskov Substitution Principle, that is "you can use a subclass everywhere where you use a superclass".

I may be missing something here, but I think that generating proper equals() and hashCode() methods is perfectly possible, as we don't need to comply to LSP with them (heck, we can't even make a subclass right now).

The equals() and hashCode() functions are meant to be used especially in HashMaps and other structures where an instance of Base class should never be equal to an instance of Inherited class.

That said, even IntelliJ can generate a proper equals() function using getClass() method for comparison instead of 'instanceof' comparison which breaks the symmetry (according to mentioned Effective Java chapter). It even gives an option to accept subclasses but that makes equals() method not compliant.

So not accepting the subclasses is the right behaviour in generated functions. A subclass instance never should equal to the superclass instance.

@stangls
Copy link

stangls commented Jul 12, 2018

@grodzickir: can you provide an example for equals and hashcode? I think you are right but want to make sure we understand the same thing.

@grodzickir
Copy link

grodzickir commented Jul 13, 2018

@stangls I'll post the example in Java as I'm more sure about it's correctness.
(Equals methods generated in IntelliJ with "IntelliJ Default" setting and without accepting subclasses checkbox checked.)

public class Base {
    private int a;
   //getters, setters

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Base base = (Base) o;
        return a == base.a;
    }
}
public class Inherited extends Base {
    private int b;
    //getters, setters
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        if (!super.equals(o)) return false;
        Inherited inherited = (Inherited) o;
        return b == inherited.b;
    }
}

Checking for classes equality instead of 'instanceof' makes sure we are getting truly transitive and symmetrical methods. (I didn't include the hashCode implementation for brevity as it's straightforward)

@gildor
Copy link
Contributor

gildor commented Jul 13, 2018

@grodzickir Even if you don't care about LSP, there is another important thing.
To inherit data class you should make it open, so you lose SmartCast for most of cases.
Check this discussion https://discuss.kotlinlang.org/t/classes-final-by-default/166

And if you need a Base class, do you really want to make it data class? Why not just open class or abstract class

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants