I have a list for example of type People. My list can contain only elements of type Student or only elements of type Worker:
interface People {
val name: String
val age: Int
}
data class Student(
override val name: String,
override val age: Int,
val course: Int
) : People
data class Worker(
override val name: String,
override val age: Int,
val position: String
) : People
At some point I need to know the exact type of the list (student or worker).
Can I safely find out the exact type? So far I've written this code, but it doesn't look very good:
fun someLogic(items: List<People>): List<People> {
return (items as? List<Student>) ?: (items as? List<Worker>)
?.filter {}
....
}
Also, I get a warning:
Unchecked cast
Can you please tell me how to perform such transformations correctly?
At runtime, the type parameter you used to create the list is not available. e.g. it is impossible to distinguish between the following two situations:
val students: List<People> = listOf<Student>(student1, student2)
val people: List<People> = listOf<People>(student1, student2)
This is because of type erasure.
The only information you have at runtime that can help determine a list's element type is the type of its elements.
So if a list has no elements, there is no way of knowing what type of list it is. Though in most situations, you don't need to anyway.
So assuming the list can only be a list of all students, or a list of all workers, but not a list containing a mixture of students and workers, you can determine the type of the list by checking the first element.
when (items.firstOrNull()) {
null -> { /* cannot determine the type */ }
is Student -> { /* is a list of students */ }
is Worker -> { /* is a list of worker */ }
// you can remove this branch by making the interface sealed
else -> { /* someone made another class implementing People! */ }
}
If you want to get a List<Student> or List<Worker> out of this on the other hand, you can just use filterIsInstance:
val students = items.filterIsInstance<Student>()
val worker = items.filterIsInstance<Worker>()
whichever list is not empty, then the type of items is the type of that list.
If you want to check that List<People> is List<Student> you can use this extension function:
fun List<People>.isStudentList(): Boolean {
// returns true if no element is not Student, so all elements are Student
return all { it is Student }
}
And if you want to cast List<People> to List<Student>, you can use map, and this cast is safe so let's say that there is some People that the are not Student so the cast is going to return null instead of Student because of as? and the mapNotNull is going to exclude null elements so in worst cases where you pass a list that doesn't contain any Student this function is going to return an empty list:
fun List<People>.toStudentList(): List<Student> {
// This is going to loop through the list and cast each People to Student
return mapNotNull { it as? Student }
}
Or you can just use filterIsInstance<Student> this will work the same as toStudentList above:
list.filterIsInstance<Student>()
And the same approach can be used for Worker
I would solve the problem with more specific classes.
You can define:
interface PeopleList<P : People> : List<P>
class StudentList : PeopleList<Student> {
// add implementation
}
class WorkerList : PeopleList<Worker> {
// add implementation
}
You can then easily check the types of these lists. Each of those classes can then provide guarantees that you are not mixing Student and Worker objects in the same List, something you can't do with plain List<People> objects.
Note also you are better off writing your code avoiding checking types if at all possible. Much better to add methods to the PeopleList interface and force the subclasses to implement them, for example:
interface PeopleList<P : People> : List<P> {
fun doSomethingGood()
}
Then you can call these methods at the appropriate time, instead of checking the type. This approach keeps the functionality associated with the subtypes alongside those subtypes and not scattered through the code at the various points where you have to check the type of PeopleList.
Related
I do not fully understand how variance in Generics work. In the code below the classes are as follows Any -> Mammals -> Cats. Any is the supertype, there is a parameter called from in the copy function
From what I understand about the out and in keywords, out allows reference to any of it's subtype, can only be produced not consumed.
in allows reference to any of it's supertype, can only be consumed not produced.
However in the copytest function we are instantiating the function copy. I gave it a catlist1 argument in the from parameter. Since the parameter has an out keyword wouldn't it mean that we can only input parameters that are a subtype of catlist2?
To top of my confusion I have seen many conflicting definitions, for instance , In Kotlin, we can use the out keyword on the generic type which means we can assign this reference to any of its supertypes.
Now I am really confused could anybody guide me on how all of these works? Preferably from scratch, thanks!
class list2<ITEM>{
val data = mutableListOf<ITEM>()
fun get(n:Int):ITEM = data[n]
fun add(Item:ITEM){data.add(Item)}
}
fun <T> Copy(from: list2<out T>, to:list2<T>){
}
fun copytest(){
val catlist1 = list2<Cat>()
val catlist2 = list2<Cat>()
val mammallist = list2<Mammal>()
Copy(catlist1,mammallist)
}
I think maybe you're mixing up class-declaration-site generics and use-site generics.
Class-declaration-site generics
Defined at the class declaration site with covariant out, it is true you cannot use the generic type as the type of a function parameter for any functions in the class.
class MyList<out T>(
private val items: Array<T>
) {
fun pullRandomItem(): T { // allowed
return items.random()
}
fun addItem(item: T) { // Not allowed by compiler!
// ...
}
}
// Reason:
val cowList = MyList<Cow>(arrayOf(Cow()))
// The declaration site out covariance allows us to up-cast to a more general type.
// It makes logical sense, any cow you pull out of the original list qualifies as an animal.
val animalList: MyList<Animal> = cowList
// If it let us put an item in, though:
animalList.addItem(Horse())
// Now there's a horse in the cow list. That doesn't make logical sense
cowList.pullRandomItem() // Might return a Horse, impossible!
It is not logical to say, "I'm going to put a horse in a list that may have the requirement that all items retrieved from it must be cows."
Use-site generics
This has nothing to do with the class level restriction. It's only describing what kind of input the function gets. It is perfectly logical to say, "my function does something with a container that I'm going to pull something out of".
// Given a class with no declaration-site covariance of contravariance:
class Bag<T: Any>(var contents: T?)
// This function will take any bag of food as a parameter. Inside the function, it will
// only get things out of the bag. It won't put things in it. This makes it possible
// to pass a Bag of Chips or a Bag of Pretzels
fun eatBagContents(bagOfAnything: Bag<out Food>) {
eat(bagOfAnything.contents) // we know the contents are food so this is OK
bagOfAnything.contents = myChips // Not allowed! we don't know what kind of stuff
// this bag is permitted to contain
}
// If we didn't define the function with "out"
fun eatBagContentsAndPutInSomething(bagOfAnything: Bag<Food>) {
eat(bagOfAnything.contents) // this is fine, we know it's food
bagOfAnything.contents = myChips // this is fine, the bag can hold any kind of Food
}
// but now you cannot do this
val myBagOfPretzels: Bag<Pretzels> = Bag(somePretzels)
eatBagContentsAndPutInSomething(myBagOfPretzels) // Not allowed! This function would
// try to put chips in this pretzels-only bag.
Combining both
What could be confusing to you is if you saw an example that combines both of the above. You can have a class where T is a declaration site type, but the class has functions where there are input parameters where T is part of the definition of what parameters the function can take. For example:
abstract class ComplicatedCopier<T> {
abstract fun createCopy(item: T): T
fun createCopiesFromBagToAnother(copyFrom: Bag<out T>, copyTo: Bag<in T>) {
val originalItem = copyFrom.contents
val copiedItem = createCopy(originalItem)
copyTo.contents = copiedItem
}
}
This logically makes sense since the class generic type has no variance restriction at the declaration site. This function has one bag that it's allowed to take items out of, and one bag that it's allowed to put items into. These in and out keywords make it more permissive of what types of bags you can pass to it, but it limits what you're allowed to do with each of those bags inside the function.
I have a class with several properties. The properties, themselves, can also have their own properties. I would like to count the total number of properties. That includes both the properties in the "main" class and the properties' properties.
For instance, consider the following class
class Person {
val firstname: String = "Jurgen"
val lastname: String = "Klopp"
val address: Address = Address("Liverpool", "England")
}
where
class Address (
val city: String,
val coubtry: String
) { }
I would like the counting to add up to 5, since the Person class "contains" firstname, lastname, address, city and country. Note that the Address class also could have a another class (with its own properties) as its property. These properties should also be counted. Is it possible to count the total number of properties?
Please further note that the counting is intended to be applied to complex/multi-leveled AVRO structures (i.e. auto-generated AVRO classes).
I'm basing this off my other post on reflection, found here: How to get the relative class name of data classes
I created your two classes in a package called complexclasses.
My Main.kt looks like this:
import complexclasses.Person
import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
fun main(args: Array<String>) {
val foundParams = Person::class.getAllMembers()
println(foundParams)
}
private fun KClass<*>.getAllMembers(): MutableList<String> {
val params = mutableListOf<String>()
memberProperties.forEach { member ->
params.add(member.name)
if(member.returnType.toString().substring(0, 7) == "kotlin.") {
return#forEach
}
val clazz = Class.forName(member.returnType.toString()).kotlin
params.addAll(clazz.getAllMembers())
}
return params
}
When I run the program, it outputs:
[address, city, country, firstname, lastname]
Using reflection (and a little string manipulation) I can recursively crawl non-kotlin objects and print out the names of all their members.
I didn't test this with more complicated data structures, like Lists or Maps. It's possible those would require more work, but I think this is enough to get you started, at least.
I'm not familiar with AVRO so hopefully this works with it.
Let me know if you hit any issues, I'm glad to help if this doesn't meet your requirements.
Let's say I have the following class constructor:
class Car(val brand: Brand,val modelName: String, val version: Int){}
If for example, I want the version number to always start with 1. Is there a way to manipulate it in the class body to achieve this ?
Meaning:
val firstdigit:Int = abs(version).ToString().Substring(0,1)
And then parse it to Int. But how to replace the original first digit after that?
I'm just learning Kotlin and I got a bit stuck with this
Is this what you had in mind?
class Car(val brand: Brand, val modelName: String) {
val version = getNextVersion()
companion object {
private var nextVersion = 0
private fun getNextVersion(): Int {
nextVersion++
if (nextVersion.toString()[0] != '1') {
nextVersion = (10.0.pow(ceil(log10(nextVersion.toDouble())))).toInt()
}
return nextVersion
}
}
}
You already said in the comments that you want the number to increment per instance, so the caller shouldn't be providing that number in the first place really! But just generally, here's two approaches to sanitising your input parameters:
1) Make it the caller's responsibility to provide valid data
init {
require(version.toString().first() == '1') { "Needs to start with 1 thanks" }
}
require throws an IllegalArgumentException if it fails, which is the standard exception for "the value of this argument is invalid". Should the class be responsible for taking bad data and trying to "fix" it, or should the caller be handling that - and maybe not constructing an instance at all if it doesn't have valid data?
2. create a newInstance function that uses valid data, and keep the constructor private
class Thing private constructor(val number: Int){
companion object {
fun newInstance(num: Int): Thing {
return Thing(abs(num))
}
}
}
fun main() {
Thing.newInstance(-2).let { println(it.number)}
}
If it makes sense for the class itself to sanitise the input parameters, you can delegate construction to a function that takes care of that, and prevent things from calling the constructor directly with potentially bad data.
This can cause issues with e.g. serialisation libraries (which want to call the constructor directly) but in that case you could leave the constructor public, and just advise callers to call newInstance instead. Not ideal, but it's an option!
I'm trying to wrap my head around map and reduce operations in Kotlin. At least, I guess it's reduce what I'm trying to do.
Let's say that I have a class called Car that takes any number (varargs constructor) of CarPart. Then, I have a list of CarPart which I'll do a map operation and from the result of the operation I need to build one Car using each subelement, something along these lines:
class CarPart(val description: String)
class Car(vararg val carPart: CarPart)
val carParts = listOf(CarPart("Engine"), CarPart("Steering Wheel")))
carParts.map { it.description.toUpperCase() }
.map { CarPart(it) }
.reduce { acc, carPart -> Car(carPart) } <--- I'm struggling here, is reduce what I should be doing
to construct one car from all the subelement?
PS.1: I know that the class design could be better and not take a varargs, this is just an example of a legacy application I'm refactoring and originally that's a Java class taking varargs which I can't change now.
PS.2: The example of mapping to a String and then creating an object out of that String is just for the sake of the example. The actual code grabs an object within the list.
You can simply use a the spread operator (*) over an array:
val mappedCarParts = carParts
.map { it.description.toUpperCase() }
.map { CarPart(it) }
.toTypedArray()
val car = Car(*mappedCarParts)
// Or even:
val car = carParts
.map { it.description.toUpperCase() }
.map { CarPart(it) }
.toTypedArray()
.let{ Car(*it) }
You could just extract the constructor of the Car outside of the creation of the list. I don't see any reason as to why you'd want it inside.
val car = Car(
*carParts
.map { CarPart(it.description.uppercase(Locale.getDefault())) } //keep the .toUpperCase() if you are using an old version of Kotlin
.toTypedArray()
)
We need the spread operator there in order for the vararg to know that we are passing it the elements of the list and not the list itself.
There's an already built in way that fulfill my request?
I know Set are unordered
I need to do a vector based Set.
I need to know the position of any value of a generic type to place them in the corresponding index of the array, So I can avoid duplications of the elements.
I'm not ordering or defining an order of the Set.
I don't have any operators that missbehave or break any Set Costraint
Please note that I know this implementation is not efficient for any types that seems infinite like Integers.
I need to do it for an educational purpose.
I have already implemented List Ordered and Hash Table based ones.
For now I have this class, that works flawlessy:
package ads
class MySet<E : Enum<E>> {
//More details for clarify
private val maxSet = 127
private var myset = arrayOfNulls<Boolean>(maxSet)
private fun getOrdinal(eelement : E) : Int{
return eelement.ordinal
}
/*
more set operators that needs of getOrdinal
*/
fun insert(xelement: E){
myset[getOrdinal(xelement)] = true
}
}
import ads.MySet as RawSet
enum class MyColors{
Red,
Green,
Blue,
Yellow,
Black,
Mint;
}
fun main() {
val myfavc = RawSet<MyColors>()
val yourfavc = RawSet<MyColors>()
//Following operations...
myfavc.insert(MyColors.Red)
yourfavc.insert(MyColors.Blue)
}
I need now to make another class in the same way but working with whatever abstract type already defined in kotlin.
package ads
class MySet<T> {
//More details for clarify
private val maxSet = 127
private var myset = arrayOfNulls<Boolean>(maxSet)
private fun getOrdinal(telement : T) : Int{
/*
For any abstract type return the order of any element
checks if the integer is not greater than maxSet otherwhise it
throws an Exception or manages this istance in other way
*/
}
/*
more set operators that needs of getOrdinal
*/
fun insert(xelement: T){
myset[getOrdinal(xelement)] = true
}
}
import ads.MySet as RawSeT
//Istance using Int
fun main() {
val myfav = RawSet<Int>()
val yourfav = RawSet<Int>()
//Following operations...
myfav.insert(11)
yourfav.insert(123)
}
I guess there's no built-in way to do so in a generic way.
but I'm still learning kotlin, so maybe I'm missing something useful.
I'm not asking to do my paper.
I don't need a full alternative solutions that I should find out by myself but
I'm opened to read about any tips or resources that can help me to clarify how abstract types works in Kotlin(or Programming Languages) infos like :
How are ordered.
If any value of any type can be compared based on their position
(like c > a for chars).
Range of values/Max values represented for any "apparently
infinite" type like Integers.
Thanks you!
P.S. = Please consider that I'm not an english native, be patient!
Enums are implicitly ordered in declaration order and amount of instances of each enum is finite, so they could be ordered globally and their ordinal could be represented as Int.
Ints (as well as Bytes and Chars) are naturally ordered and have ranges (Int.MIN_VALUE..Int.MAX_VALUE, etc.), so each of them could have an ordinal represented as Int too.
All types implementing Comparable interface could be compared in pairs and any subset of their instances could be ordered, but it doesn't mean that each of them have some global Int ordinal among all possible instances, because set of Ints is finite (2^32 items), and set of all unique instances of generic type T could be countably infinite (like BigInteger) or even uncountable (like Double) (see wiki about cardinality).
All other types couldn't be even compared in pairs (without respectful Comparator<T>).
So you need to manually limit instances of each T that are about to be added in your set and either maually order them or provide respectful Comparator<T> to construct a Map<T, Int> which you'll need to use for subsequent ordinal evaluation:
class SetOfSomehowOrderedInstancesOfType<T>(private val order: Map<T, Int>) {
private val maxSet = order.size
private var myset = BooleanArray(order.size)
private fun getOrdinal(eelement: T): Int {
return order[eelement] ?: throw RuntimeException("Order unknown")
}
fun insert(xelement: T) {
myset[getOrdinal(xelement)] = true
}
}
Usage:
fun main() {
val myFavouriteRealNumbersInMyFavouriteOrder =
listOf(99.2123, -2355.12, 1.1, 3.14, 100.0, 123214214215.123331322145)
val myfavc = SetOfSomehowOrderedInstancesOfType<Double>(myFavouriteRealNumbersInMyFavouriteOrder.mapToIndex())
myfavc.insert(99.2123) //will be inserted with ordinal = 0
val myFavouriteRealNumbersInNaturalOrder = myFavouriteRealNumbersInMyFavouriteOrder.sorted()
val yourfavc = SetOfSomehowOrderedInstancesOfType<Double>(myFavouriteRealNumbersInNaturalOrder.mapToIndex())
yourfavc.insert(99.2123) //will be inserted with ordinal = 3
}
Alternatively you may define Orderable and Ordinator<T> interfaces (similar to Comparable<T> and Comparator<T>) and determine ordinal using them:
fun interface Ordinator<T> {
fun getOrderOf(x : T) : Int
}
interface Orderable {
val order : Int
}
class MySet<T>(private val ordinator: Ordinator<T>? = null) {
private val maxSet = 127
private var myset = BooleanArray(maxSet)
private fun getOrdinal(eelement: T) = when {
eelement is Orderable -> eelement.order
ordinator != null -> ordinator.getOrderOf(eelement)
else -> throw RuntimeException()
}
fun insert(xelement: T) {
myset[getOrdinal(xelement)] = true
}
}
Also you may define auxilary function, generalizing previous approach:
fun <T> ordinatorOf(order: List<T>) = object : Ordinator<T> {
private val order = order.mapToIndex()
override fun getOrderOf(x: T) = this.order[x] ?: throw RuntimeException()
}
Usage:
val stringsOrderedByTheirLength = MySet<String> { it.length }
stringsOrderedByTheirLength.insert("aaa") //will be inserted with ordinal = 3
val myFavouriteRealNumbersInMyFavouriteOrder =
listOf(99.2123, -2355.12, 1.1, 3.14, 100.0, 123214214215.123331322145)
val myfavc = MySet(ordinatorOf(myFavouriteRealNumbersInMyFavouriteOrder))
myfavc.insert(99.2123) //will be inserted with ordinal = 0
As somebody pointed out to me that some abstract types representing for an istance Real Numbers can't be done so easily.
The implementation with the enum class works flawlessy but it's been refused from my university professor because I need to use the same syntax for every implementation of the same Data Structure.
Other implementation I have
- HashTableSet<T>
- OrderedListSet<T>
they ask only for an abstract type that can be whatever type, they will work without problem.
I need to do it the same with the
ArraySet<E : Enum<E>>
So if this can't be done easily with Generics, directly...
I'm thinking about mixing them.
Like whatever T type I have
it creates an object called "Domain" so whatever element it insert, before placing them in the array of the set, it place them inside there in a specific order so it simulates what enum it was doing.
I guess that Enumerations can't be defined during the runtime, dinamically.
Then probably I have to define Domain<T> as a private class/object(not sure) that :
Collect the element of the istance of ArraySet(above MySet) inserts with its opeator .insert()
It orders the element comparing to the ones that are already inside
Once is ordered delete all the duples
Re-arrange the whole Boolean Array everytime insert is used based on how Domain grows. (or find out a different algo that partially re-arrange the boolean array)
What do you think?
Advice me more, thanks.