Data class without arguments deprecated in 1.0. Why?


#1

I used to have things like:

sealed class Node {
  data class Leaf() : Node()
  data class Branch(val left:Node, val right:Node) : Node()
}

Now data classes can't inherit and it is pretty annoying (I know that one has been delayed). But there is another problem with that. I can't create data classes without arguments in the constructor. We have generated code generating data classes for a protocol that describes how client and server communicates.

data class ExchangePackageRequest (
)

data class ExchangePackageResponse (
  val userAvailableVirtualMoneyAmount: AvailableVirtualMoneyAmount?,   
  val exchangePackagesToCoins: List<ExchangePackage>?,   
  val exchangePackagesToChips: List<ExchangePackage>?   
)


So ExchangePackageRequest doesn’t have arguments, but now it doesn’t compiles. It can’t be an object because it is created uniformly by reflection. Also if I skip the data part, it doesn’t generate the hashCode and equals methods. So it is a bit of a pain.
Why are you disallowing this?


#2

What is the point of generating hashCode() and equals() methods if they work as reference equality anyways?


#3

There may be a point to generate toString() method. Regarding to equals and hashCode its unclear how they should behave for data class with no properties, either as referential equality, or as type equality. However these two mean same for objects, so it may make some sence to have data objects.


#4

Consider the following:

object Object0

 
class Class0()
data class Class1(val a:Int)
data class Class2(val a:Int, val b:Int)

println(Class0::class.java.newInstance())
println(Object0::class.java.newInstance()) // fails

assertTrue(Class0() == Class0()) // fails
assertTrue(Class1(1) == Class1(1)) // ok
assertTrue(Class2(1, 2) == Class2(1, 2)) // ok


Reasons:

It’s pretty ugly to create classes that are in the same category ones with class and others with data class or even objects.

I’m creating classes by reflection. I get the VO/POJO fqname from a socket and I’m instantiating it.
Can’t create an instance of an object class, probably the constructor is private and that would lead to have specialized code that detects if it is an object to return the reference or not.

Comparing two non data classes without overriding equals/hashCode won’t work, so I would have to override equals.

toString from a normal class includes the simple name of the class + @hash, and data classes omit the @hash so that toString is different too

Implementarion:

  • hashCode should return always the same value for a data class without arguments: maybe a constant value calculated from the fqname
  • equals should return true if the two instances have the same type  and false otherwise
  • toString should return the simple name of the class

#5

Note that the current design, in a sense, forces you to make your system more efficient. At the cost of putting in a small amout of extra logic to figure out whether you need to create an instance of a class or to get a singleton instance of an object, you avoid creating unnecessary identical objects and reduce the pressure on the GC. I don't think this is actually bad.


#6

Well. We have lots and lots of other objects (views, animations, JsonObjects, JsonArrays...), but those are protocol messages that are serialized/deserialized from a socket and they are not too much. I don't mind creating other objects.

If you really want to optimize that much.
HaXe for example has abstracts: http://haxe.org/manual/types-abstract.html
I don’t think it is a very good design, but there are other alternatives.

For example, in the case a class is completely immutable, you can optimize it to do some stuff by value and not by reference.

For example

class Point(val x:Int, val y:Int) { operator fun plus(that:Point):Point = Point(this.x + that.x, this.y + that.y) }

class Object {
 val position:Point // this could end being just two play fields that are expanded
}

You could convert this one:

val a = Point(1, 2) + Point(3, 4)

into:

val temp0_x = 1
val temp0_y = 2

val temp1_x = 3
val temp1_y = 4

val temp2_x = 1 + 2
val temp2_y = 3 + 4</span>

Or just create a “struct class” that treats them as value types instead of reference types.
I know that JVM doesn’t support, but you could do stuff like this, and store in arrays with object pools.
Being able to have non-gc value types is a benefit that you have with .NET CLR.
Also with swift you have value types, but also you have predictable object lifecycle without unpredictable stops from the GC.

That would be a very high benefic in gaming, because you could use pretty small primitives without creating garbage: points, rectangles. That includes immutable things like IntRange and so on.

So I don’t see the point that forcing not being able to have “data class” without arguments because you are avoiding to create one single instance in my case, for example, each 50000 frames when I have to create probably dozes just for seralizing is going to help me with GC. I’m already doing lots of object pooling and lazy initialization in critical paths.

Also you have: var, val, fun, and that’s great because code is pretty organized and is pretty good to see things in column, and I have to put obejct or class with classes that falls ino the same category isntead of just put “data class MyDataClassWithoutArguments()”.

I don’t mind creating other object in that case. If I would mind I would use swift, that is going to be opensource pretty soon, with ARC and GC just for detecting leaks with non-weak references.
Also you are not using the “new” keyword for making explicit that you are creating garbage that will require being collected. And I think it is good, since it makes it more hard to read, and disallows you to change a class with a function without removing or adding new everywhere. And also DI, IoC, factories, are allocating without making it explicit. So Probably I know that if I’m using Class0() instead of Object, that () makes me think that maybe I’m creating something, because there is code executing. But again you can even create instances when using a getter that in Kotlin is a normal property that could be confused as a field.
So with GC you will always have to profile your APP, and I don’t think that making able to do “data class Class()” won’t hurt more than other things in the GC direction.


#7

First of all, when I'm writting English sometimes I don't know if it sounds rude, because my english skills are not good enough. I just wanted to provide reasons why I think it is not bad to have empty-argumented data classes. I like kotlin so much. And I think it is a great, well designed language.

I have moved the structure proposal to other thread to keep this one simpler and to make the other easier to find, also added an example that could allow it to be java-friendly without losing the huge allocation reduction: https://devnet.jetbrains.com/thread/473821


#8

Why do you need to prohibit empty data classes at all? For example:

 
class XYZ {
    
}

data class ABC {
  
}


Why is the (currently valid) class XYZ better than the invalic class ABC? What do you loose by allowing empty data classes, except that the compiler generates a bunch of methods that will probably never be called anyway?


#9

It’s quite disappointing to see that Kotlin’s data class does not have basic feature parity with AutoValue.

Now I have to add a random empty string field like “dataClassEmptyString” and hope nobody redefines it with the copy constructor.


#10

Sorry for resurrecting and old thread, but I think this really needs further discussion.

I have now run into wanting empty data classes for my project a few times. The primary reasons are:

  1. Sometimes I end up with multiple instances of an object because something was deserialized using a framework. While you might argue that the framework should be updated to use the singleton instance of the object, that isn’t always feasible or desired. As such, it would be really nice to have equals/hashCode/toString that treated ALL instances of the object as equal. Getting strange runtime errors that seem impossible is annoying (i.e. for object Foo, I have cases given val a: Foo; val b: Foo where a != b.
  2. Control over whether I get an instance or a singleton for future refactoring, etc… Maybe I think I’ll add a parameter at some point to my object and make it a data class. I don’t want the singleton/instance semantics to change. Give me control over how whether or not I’m using a singleton.
  3. Syntax similarity. Not a huge deal, but having everything use the syntax is nicer (i.e. data class Node() vs object Leaf().

In general, it just really feels like an arbitrary restriction that I can’t have an empty data class. Until this decision is reversed, I’ll do the data class MyData(val nothing: Nothing? = null) hack, but it’s annoying.


#11

Unfortunately you cannot have a generic object, the data class without arguments would have just worked in the following example:

sealed class Value {
data class SpecialCase1() : Value() // error
data class SpecialCase2() : Value() // error
data class Some(val v: T) :Value()
}

I guess you can come up with various workarounds (like having a class with manually defined hashCode/equals) , but I believe there is nothing wrong with this approach.


#12

As a new Kotlin user (I love the language so far), these small exceptions are very irritating. There is quite a lot to grasp in terms of idioms and language features (even coming with 20+ years Java experience and years of Haskell) without bumping into what seem like arbitrary restrictions like this. So, please reconsider this restriction. Learning that in many cases you can use a singleton is interesting but should be considered an optional optimization available to the user. Uniformity in the language rules is a huge help to new users like me!