📜 ⬆️ ⬇️

Generics in Kotlin vs. Generics in JAVA: similarities, differences, features


This article is about Generics in Kotlin - features of their use, similarities and differences with Generics in Java.

In a nutshell about Generics
In short, Generics is a way of saying that a class, interface or method will work not with some particular type, but simply with some kind. How exactly will be determined from the context. For example:

public interface List<E> extends Collection<E> { //... } 

It is not known in advance which objects of the class will be contained in the list, but this will be determined when using it:
')
 List<String> list = new ArrayList<>(); 

Now it’s not just a list, but a list of strings. Generics help ensure type safety: you can try to put any object in the List, but in List <String> only String or one of its descendants.

I will divide the story about Generics into two parts: the actual Generics and the use of Wildcards. While we are not talking about Wildcards, the use of Generics in Kotlin is not much different from Java.

The same generic classes:

 // Java public class SomeGenericClass <T> { private T mSomeField; public void setSomeField(T someData) { mSomeField = someData; } public T getSomeField() { return mSomeField; } } 

 // Kotlin class SomeGenericClass <T> { private var mSomeField: T? = null fun setSomeField(someData: T?) { mSomeField = someData } fun getSomeField(): T? { return mSomeField } } 

The same generic methods:

 // Java public <K> K makeSomething(K someData) { K localData = someData; //... return localData; } 

 // Kotlin fun <K> makeSomething(someData : K) : K { var localData = someData //... return localData } 

Generics can be further restricted in Java:

 // Java public <K extends Number> K makeSomething(K someData) { K localData = someData; //... return localData; } 

And in Kotlin:

 // Kotlin fun <K : Number> makeSomething(someData : K) : K { var localData = someData //... return localData } 

Such restrictions mean that instead of K, not any class can be used, but only satisfying the condition (in this case, Number or the class that inherits it).

 // makeSomething(1) // makeSomething(“string”) 

Constraints can be complex, for example, showing that an object passed to a method must inherit a class and implement an interface, for example:

 //Java public static <T extends Interaction & Fragment> SomeFragment newInstance(T interactor) { SomeFragment fragment = new SomeFragment(); fragment.setTargetFragment(interactor, 0); return fragment; } 

 //Kotlin fun <T> newInstance(interactor : T) : SomeFragment where T : Interaction, T : Fragment { val fragment = SomeFragment() fragment.setTargetFragment(interactor, 0) return fragment } 

Please note that Kotlin uses a different syntax for complex constraints: added some syntax sugar. You can omit the type parameter if it can be defined by context:

 // Kotlin val someGenericClassInstance = SomeGenericClass("This is String") 

And in Java it is necessary:

 // Java SomeGenericClass<String> someGenericClassInstance = new SomeGenericClass<>("This is String"); 

Thus, the main thing you need to know about Generics when switching from Java to Kotlin - do everything the same way you did in Java. Attempts to do something in a new way, “in Kotlinov style,” are likely to lead only to new difficulties.

Wildcards


We turn to the second part. Wildcards is a special case that causes the most difficulty in both Kotlin and Java. The main problem of Generics is their invariance: List <String> is not a descendant of List <Object>. Otherwise, errors like this might occur:

 //Java List<String> strs = new ArrayList<String>(); List<Object> objs = strs; //objs - List<Object>,      Integer objs.add(1); // strs - List<String>,   get()   String String s = strs.get(0); 

Generics invariance allows you to prevent this, but, on the other hand, introduces additional restrictions. Thus, when using conventional Generics, it is not possible to pass List <String> to a method that expects List <Object> as a parameter. In many cases it is convenient to have such an opportunity.

Wildcards allow you to allow this behavior, denoting that in this place is expected some type parameter, but not some specific. At the same time, Wildcards can also be specifically limited, which splits the question into 3 parts:


Restricted “from below” covariant Wildcards are used in cases when a generic class is expected from some class or its descendants. For example:

 // Java public interface Container<T> { T getData(); void putData(T data); } static void hideView(Container<? extends View> viewContainer) { viewContainer.getData().setVisibility(View.GONE); } 

Here, the hideView method expects an object that implements the Container interface, but not any and not just the containing View, but the containing View or some other class that inherits the View. This is called covariance.

In Kotlin, this behavior can be implemented in a similar way:

 // Kotlin interface Container<T> { fun getData() : T fun putData(data : T) } fun hideView (viewContainer : Container<out View>) { viewContainer.getData().visibility = View.GONE; } 

However, the use of the parameter declared as Wildcards is subject to additional restrictions.

In Java, covariant Wildcards can be used to obtain data without restrictions, while the data will be returned in accordance with the indicated boundary (in the example above, getData () will return the View, even if the container actually contained a TextView). But nothing can be put into it except null, otherwise it would cause the same problems that Generics would have had if they had not been invariant.

 //Java static void hideView(Container<? extends View> viewContainer) { //getData()  View        , , TextView viewContainer.getData().setVisibility(View.GONE); //    null,   ,         //   viewContainer.putData(null); //  - ,        , , Container<ImageView> viewContainer.putData(new View(App.getContext())); } 

In Kotlin, the limitations are almost the same. Due to the nature of the types in Kotlin, it is impossible even to put null inside this parameter. The out keyword perfectly describes what is happening.

 //Kotlin fun hideView (viewContainer : Container<out View>) { viewContainer.getData().visibility = View.GONE; //   ,   ,   View ( -   )  View? viewContainer.putData(null) } 

Contravariant Wildcards bounded “from above” are used to designate places where a generic class is expected from a certain class or its ancestors. The traditional example of contravariant Wildcards is comparators:

 // Java public static <T> void sort(List<T> list, Comparator<? super T> comparator) { //... } 

Suppose a List <String> is passed to the method as the first parameter, and a comparator from any of the String ancestors, for example, CharSequence: Comparator <CharSequence>, as the second parameter. Since the String is a descendant of CharSequence, any fields and methods necessary for the comparator will also be in the objects of the String class:

 //Java class LengthComparator implements Comparator<CharSequence> { @Override public int compare(CharSequence obj1, CharSequence obj2) { //   String       if (obj1.length() == obj2.length()) return 0; if (obj1.length() < obj2.length()) return -1; return 1; } } 

In Kotlin, the implementation is similar:

 // Kotlin fun <T> sort(list : List<T>, comparator: Comparator<in T>) { //… } 

Contravariant Wildcards have quite expected limitations: you can read the value from such Wildcards, but will return Object in Java and Any? in kotlin.

At this stage I repeat: switching from Java to Kotlin, you should do everything the same way you did. Although “Kotlin doesn't have any” is written in the official documentation about Wildcards, the type projections mechanism (discussed above) offered instead of this works in all usual cases in a similar way, no new approaches are required.

But not without innovations. In addition to type projections, a completely similar familiar Wildcards model in Java, Kotlin offers another mechanism - the declaration-side variance.

If it is known in advance that a generic class will be used only as a covariant (or only as contravariant), this can be indicated at the time of writing the generic class, and not at the time of its use. As an example, comparators are again suitable. Rewritten to Kotlin, java.util.Comparator might look like this:

 // Kotlin interface Comparator<in T> { fun compare(lhs: T, rhs: T): Int override fun equals(other : Any?): Boolean } 

And then its use will be as follows:

 // Kotlin fun <T> sort(list : List<T>, comparator: Comparator<T>) { //… } 

At the same time, restrictions on the use of the parameter comparator will be the same as if <in T> were indicated not on the side of the interface declaration, but on the side of its use.

Likewise, the covariant behavior can be defined with a class declaration.

The latter case is not disassembled - Wildcards without restrictions . Such, obviously, are used in cases when generic from any class is suitable:

 // Java public interface Container<T> { T getData(); void putData(T data); } static boolean isNull(Container<?> container) { return container.getData() != null; } 

In Kotlin, a similar mechanism is called star-projection. In all trivial cases, its only difference from unlimited Wildcards in Java is the use of the “*” symbol instead of the “?”:

 // Kotlin interface Container<T> { fun getData() : T fun putData(data : T); } fun isNull(container : Container<*>) : Boolean { return container.getData() != null; } 

In Java, unlimited Wildcards are used according to the following rules: you can only put null in them, the Object is always read. In Kotlin, nothing can be put inside, but an object of class Any can be read? ..

When sharing the declaration-side variance and star-projection, one must take into account that the limitations are summed up. So, when using contravariant Declaration-side variance (allowing you to put everything in, but counting Any only?) You can’t put anything inside (star-projection restriction) and the same Any? (in this their limitations coincide).

To read about Generics in general, please follow the links:
www.oracle.com/technetwork/articles/java/juneau-generics-2255374.html
www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html

Source: https://habr.com/ru/post/301174/


All Articles