… it would be interesting to see a specific, contextual example of where a static method does the job better than anything Kotlin can offer …
OK, how about something like this. Let’s say I want to be able to programmatically change the default color for a certain group of elements of a paint api. I need the default color for different types of elements to apply to all of the instances of a class of paint objects in the API.
file Shape.java:
package org.paintapi;
public abstract class Shape {
static private long defaultColor;
static public void setDefaultColor(long color) {
defaultColor = color;
}
}
file Text.java:
package org.paintapi;
final public class Text {
static private long defaultColor;
static public void setDefaultColor(long color) {
defaultColor = color;
}
}
abstract class Shape {
companion object {
var defaultColor: Long
}
}
class Text : Shape() {
companion object {
var defaultColor: Long // this default color is different from
// the one of Shape as it is in java
// if you want to access the default
// color of Shape you can use Shape.defaultColor
}
}
class Box: Shape() {
// this class does not have it's own default color
// defaultColor refers to Shape.defaultColor
}
What exactly is the advantage of static in this example?
I would not consider it magic, this simple inheritance. Yes, Kotlin behaves differently than java companion objects as you can access the members of your parents companion object from within a class.
Note that Box.defaultColor is not allowed but you can access defaultColor from within a Box.
Woops sry my bad. I missed that. You can still however use @svd’s example
abstract class DefaultColored(var defaultColor: Long)
abstract class Shape {
abstract fun paint() : Unit
companion object : DefaultColored(1)
}
class Text {
override fun paint() {
println("text: $defaultColor")
}
companion object : DefaultColored(2)
}
fun main(args: Array<String>) {
Text().paint()
}
I don’t see the advantage of statics here.
In this case I would like to see an explanation for this. @Radzell?
IMO this is exactly what a companion object is supposed to be. An object that comes with all other instances of a class that encapsulates shared data and functionality. I would actually argue that you are using static here to simulate the idea of a companion object.
@Wasabi375 introduced the shape hierarchy to show you that companion objects can fulffill the role of statics.
My example works as well without the Shape super class but then you have to create a companion object for Box as in:
abstract class DefaultColored(var defaultColor: Long)
class Text {
fun paint() {
println("text: $defaultColor")
}
companion object : DefaultColored(2)
}
class Box {
fun paint() {
println("box: $defaultColor")
}
companion object : DefaultColored(1)
}
fun main(args: Array<String>) {
Text().paint()
Box().paint()
}
I also dont see why introducing the class is a code smell if it makes the code DRY-er.
They make the intent clearer. A companion object holds everything that relates to all instances, or just to the class itself, neatly in one place, instead of having it potentially intermingled with the instance members. Makes the code easier to understand in my opinion, at least if you know what a companion object is supposed to do. Whereas the only argument for the statics solution of this seems to be “I know how they work”. You don’t need to use that extracted DefaultColored class, you can use it just like the statics in your example. But you can do it if the need arises, whereas with the statics you just can’t. At least not without major refactoring.
I personally would probably solve this specific example with a completely different approach, as I don’t feel like holding the default color of elements directly on those classes is a good solution. What, for example, if you have a few classes that should share the same default color, but can’t be of a common super class? You’d have to change their default colors in several places everytime that shared default color changes. I’d much rather have another place where default colors are defined and resolved, however that may look.
This may seem only tangentially related to the issue at hand (statics vs. package level vs. companions), but I notice this a lot of the time where statics are used, that there is usually a better high-level approach that circumvents the issue altogether.
Totally agree. Global variables are in general not a good idea (I am fine with global values and I know for each rule there are exceptions).
My experience as well. This normally goes even further so that Factories which would IMO be great as statics/companion objects normally seem to get their own class in Java which is why companion objects are even better as a concept as it actually aligns with the way we program.
That’s a weak argument to replace the concept of static with companion object. That said, simple compiler sugar would accomplish the same thing. For example, in Java syntax, it would look like this:
public class Test {
static {
private long defaultColor;
public int somethingElse;
}
}
A static ‘container’ block could also contain methods. (FWIW, such syntax sort of exists in Java today as static initializer blocks but which requires separate declaration from initialization.)
Such an approach could be extended to all class and interface member modifiers to save a few keystrokes and make members be “neatly in one place, instead of having it potentially intermingled with the instance members”.
The thing is as far as I am aware there is not one single argument for static except that it is familiar for java developers, while there are multiple for companion objects:
better represent the idea behind oop
can inherit from other classes
can implement interfaces
can be passed and stored like any other first class citizen (like functions and any other object)
and although a weak argument, group everything related to all instances
Also if you look at your example, your code would be used the same way if you replaced static with companion object. You would only need to add @JvmStatic for java interop if this is important for you.
I have one argument albeit one that is weak and it isn’t actually solved with what people are asking for. It is currently impossible to define a static extension method on a java based type. In Kotlin you can do this by eg. operator fun A.Companion.invoke(arg1:String), but only if A is originally a Kotlin type. You can not simulate a static method (operator invoke or not) on Java based classes.
Nobody has addressed my question about Radzell’s comment:
Your not supposed to use companion objects as a replacement for statics. You use package-level functions and extension functions. The only time you should ever use a companion object is for creating factory methods.
Yes and no. I think this statement is in regards to using statics as they are used in java.lang.Math. Companion objects are not intended to group functions together, you would not convert the Math library into a singleton object either.
Also in my experience statics are often used for global state, which is in many ways the result of badly designed code. So in this regard the statement is true.
On the other hand is it sometimes necessary to add functionality not to a instance of a class but to the class itself. So I don’t think it is wrong to use companion objects in the way you would use statics in java. Mostly this statement comes from the fact that static is often a code smell.
No the statement is not wrong. If you need to share state between objects of the same class what you’d want to do is make a package-level property that is private.
If you can’t make it a extension function then that means some how the receiver of that function is never tied to the class you are trying to modify and in that case I would question the relation between that class. Then I’d make it a package-level function.
I would suggest trying this route. I am seeing a lot of impure functions in these examples that are modifying static variables. This pattern will help with avoiding that issue.
Scope is a problem when moving the concept of a Java class static member to a package-level property of Kotlin. Name collisions in the package are the resulting problem that is solved in Kotlin by using long ugly names, like shapeDefaultColor and textDefaultColor in my example.
I am offended by language designers trying to fix “badly designed code” by crippling a language by removing ‘dangerous’ features or adding ‘protective’ features as requirements (e.g. goto, null, and Java’s checked exceptions). Good builders know when to use sharp tools - only when necessary - and use them sparingly. Making all knives dull to protect bad builders from themselves is arrogant and counter-productive. The use of one or two global variables (at the app, package or class level) can make the rest of the code more understandable and therefore maintainable to those who come later, and is a good thing.
I was talking about the statement by @Radzell.
Also Kotlin did not remove any features from java as far as I’m aware. The functionality of static was just shifted to companions and package level functions/properties.
The ability to declare a Kotlin reference as what is called a ‘platform type’ (which is how Java works) was removed from Kotlin. The use of a ‘platform type’ is useful in some instances, and would therefore be useful in programs that are portable between /JVM and /Native. And no, lateinit is a very poor substitute.
This is yet another example of my rant about the arrogance of many of today’s programming language designers.
IMO, the phrase ‘platform type’ is a misuse of the word platform, which I suspect was chosen because it connotes something that may not be available on some platforms, and is therefore ancillary and can be discarded.
I would say if you look at some of the comments of the jetbrains devs and some of the design decisions thats exactly what they are doing. They are borrowing from the old python adage that their should be one way and only way to do something. That goes for from everything from immutability and val, the default final classes, nullable types built into the type system, parameters immutable by default and immutable collections.
I like the fact that the language is opinionated. I don’t understand the desire to have the option to write “bad code” when we have years of data telling us idioms we should follow for a better dev experience.