Could somebody explain this.
The java instanceof operator gives a compiler error when there is no possible way for an instance to refer to an object. Fine.
Let me present a bit of example code
public class InstanceOfConfusing {
class A {}
class B1 extends A {}
class B2 extends A {}
public static void main(String... args) {
InstanceOfConfusing ioc = new InstanceOfConfusing();
A a = ioc.new A();
B1 b1 = ioc.new B1();
B2 b2 = ioc.new B2();
boolean t1 = b1 instanceof A; // compiles, true
boolean t2 = b1 instanceof B1; // compiles, true
boolean t3 = b1 instanceof B2; // does not compile
boolean t4 = a instanceof B1; // compiles, false ***
}
}
I understand everything except the last line marked with ***. My question is simple, why does this compile?
If the compile gives the "Incompatible conditional operand types" error on the previous line because there is no way for an object of type B1 to ever refer to an instance of type B2, shouldn't it
also know that there is no way for a B1 to refer to an A?
I can't write a line like this: b1 = new A();
I would get a "Type mismatch" compiler error, and rightfully so. So why doesn't the compiler know there is no way for b1 here to reference an instance of type A? What am I misunderstanding?
You can assign with downcasting. After doing so, you can understand usage of a instanceof B1 and why it needs to be compiled.
B1 b = (B1) new A();
I think that's because B1 extends A, so you can do:
A a = new B1()
So with a instanceof B1, you can check whether a is an instance of B1 wrapped (for example) in an interface of abstract class A
Related
Here is my kotlin class:
class Test{
val x: String = run {
y
}
val y: String = run {
x
}
}
The variables x and y both end up as null, despite being declared as non-nullable strings.
You can run it here. As you can see, you end up with null pointer exceptions from trying to call methods on x or y.
Why is this possible? How can you really have null safety with this in mind?
Well this is what your class decompiles to in Java:
public final class Test {
#NotNull
private final String x;
#NotNull
private final String y;
#NotNull
public final String getX() {
return this.x;
}
#NotNull
public final String getY() {
return this.y;
}
public Test() {
Test $this$run = (Test)this;
int var3 = false;
String var5 = $this$run.y;
this.x = var5;
$this$run = (Test)this;
var3 = false;
var5 = $this$run.x;
this.y = var5;
}
}
So your backing fields, x and y are declared first. They're not assigned a value yet so, in Java, that means their value is null because that's the default for an unassigned object reference.
After the getters, you have the constructor which is where the assignation is taking place. There are a few weird variables going around, but
Test $this$run = (Test)this;
is basically creating a variable that refers to this, the current object. And we can kinda reduce the assignment code down to
this.x = this.y // y is null, so x is set to null
this.y = this.x // x is null, so y is set to null
Because that default value for object references is null, whichever of your assignments runs first will always be reading a null value from the other variable (which, remember, you haven't explicitly assigned a value to yet).
Basically the order of initialisation matters in Kotlin, you can't refer to something that hasn't been declared yet. Like this won't work either:
class Thing {
val a = b
val b = "hi"
}
On the line where a is being assigned, the value of b is currently undefined. It won't run on the JVM either, because that code decompiles to basically this:
public final class Thing {
#NotNull
private final String a;
#NotNull
private final String b;
public Thing() {
this.a = this.b;
this.b = "hi";
}
}
and that this.a = this.b line will fail because "b may not have been initialised yet". You can get around that with the same trick in the decompiled version of your code, with the other variable assigned to this:
public Thing() {
Thing thing = (Thing) this;
this.a = thing.b;
this.b = "hi";
}
which will run, but a ends up assigned with the default value of null.
So basically, the code you're using is a tricky way to get around that kind of error and ultimately give you unexpected behaviour. Obviously your example is unrealistic (the results of a = b = a are inherently undefined), but it can happen with this kind of code too, where initialisation goes through other functions:
class Wow {
val a = doSomething()
val b = 1
fun doSomething() = b
}
In this case a ends up 0 on the JVM, the default value for an int, because when assigning a it basically goes on a detour through a function that reads b before that's been assigned its value. Kotlin (currently) doesn't seem to be capable of checking the validity of this kind of thing - so you'll run into problems trying to initialise things via functions sometimes:
class Wow {
// unassigned var
var a: Int
val b = 1
init {
// calls a function that assigns a value to a
doSomething()
}
fun doSomething() { a = 5 }
}
That will fail because it can't determine that a has been initialised, even though the init block does so, because it's happening as a side effect of another function. (And you could bury that assignment in any number of chained calls, which is probably why it's not a thing that's been "fixed" - if you start making guarantees about that kind of thing, it needs to be consistent!)
So basically, during initialisation you can do things which the compiler isn't able to catch, and that's how you can get around things like non-null guarantees. It doesn't come up often, but it's something to be aware of! And I'm only familiar with the JVM side, I'm assuming the undefined behaviour is platform-specific.
According to the Kotlin docs, "Data inconsistency with regard to initialization" can result in a NullPointerException.
Here are a couple links related to the topic:
https://kotlinlang.org/docs/null-safety.html#nullable-types-and-non-null-types
https://kotlinlang.org/docs/inheritance.html#derived-class-initialization-order
Edit: An example of a wrong answer! It's not circular: x is initialised to null because y is null at the time x is initialised (being uninitialised).
Well, it's circular. x is not null because y is not null which is not null because x is not null.
So it's not a valid program. Meaningful type inference can only be applied to valid programs.
class C(val string: String) {
init {
println(string)
}
}
abstract class A {
abstract var string: String
val c = C(string)
}
class B : A() {
override var string = "string"
}
fun main() {
B()
}
kotlin playground for the problem
This code crash in runtime due to string var not initialized, how to do it right?
It's not a good practice and dangerous to use an abstract or open variable in the initialization of your class. And if you write this code in Android Studio or IntelliJ IDEA you will get this warning: Accessing non-final property string in constructor.
So what's happening here ? Well the super class which is A is going to be initialized first before totally initializing B, so this line of code val c = C(string) is going to run before even giving a value to string and that's what causing the error and you will get a NullPointerException because string is null.
How to fix this ? You can use lazy to initialize c like that:
val c by lazy { C(string) }
Now c is not going to be initialized only if you call it, so now it's safe
because you can't call it only if B is fully initialized.
You are initialising A's properties using non-final properties - in this case, you are initialising c with the abstract property string.
abstract var string: String
val c = C(string)
This in general could be unsafe. Subclasses could override the non-final property in such a way that it is initialised at a later point, which means any initialisation that depends on the non-final property in the superclass will get an undefined value.
In this case, this is exactly what happens. B overrides string so that it is initialised after A's primary constructor is called. As a result, when A's primary constructor is run, and c is initialised, string has the value of null.
To fix this, you can either make c lazy:
val c by lazy { C(string) }
This will only initialise c when you first access it, with whatever the value of string is at that time.
Alternatively, make c computed:
val c get() = C(string)
This will make a new C every time you access c, with the current value of string.
Let's say I have a function with arguments annotated by Jetbrain's #NotNull, like so:
public static boolean birthdaysAreEqual(#NotNull clazz b1, #NotNull Birthday b2) {
if (b1 == null || b2 == null) {
// Custom handling here
throw new NullPointerException("birthdays cannot be null");
}
return b1.isEqualTo(b2);
}
My IDE (IntelliJ) will warn me that the condition is always 'false'`, and suggest to remove the check, implying it is redundant.
But I'm able to call this function with null inputs, in which case the if conditional is useful for catching the problem early on and handling it.
Is it then not good behavior for the IDE to label the if condition as redundant? Or am I missing something (maybe another annotation that triggers actual input validation?)?
I have a nullable string variable ab. If I call toUpperCase via safe call operator after I assign null to it, kotlin gives error.
fun main(args: Array<String>){
var ab:String? = "hello"
ab = null
println(ab?.toUpperCase())
}
Error:(6, 16)
Overload resolution ambiguity:
#InlineOnly public
inline fun Char.toUpperCase(): Char defined in kotlin.text
#InlineOnly public inline fun String.toUpperCase(): String defined in kotlin.text
What's the problem here?
As stated in this doc about smart-casts:
x = y makes x of the type of y after the assignment
The line ab = null probably smart casts ab to Nothing?. If you check ab is Nothing? it is indeed true.
var ab: String? = "hello"
ab = null
println(ab?.toUpperCase())
println(ab is Nothing?) // true
Since Nothing? is subtype of all types (including Char? and String?), it explains why you get the Overload resolution ambiguity error. The solution for this error will be what Willi Mentzel mentioned in his answer, casting ab to the type of String before calling toUpperCase().
Remarks:
This kind of error will occur when a class implements two interfaces and both interface have extension function of the same signature:
//interface
interface A {}
interface B {}
//extension function
fun A.x() = 0
fun B.x() = 0
//implementing class
class C : A, B {}
C().x() //Overload resolution ambiguity
(C() as A).x() //OK. Call A.x()
(C() as B).x() //OK. Call B.x()
I'm not sure but that seems to be a bug due to smart casting (to Nothing?, subtype of every nullable type). This one works:
fun main(args: Array<String>) {
var ab: String? = "hello"
ab = makeNull()
println(ab?.toUpperCase())
}
fun makeNull(): String? = null
The only difference: The compiler does not know the null assignment directly, which seems to cause the error in your example. But still, yours should probably work too.
It really seems like a bug. The type String? is lost somehow upon assigning null, so you have to tell the compiler explicitely that it should deal with a String?.
fun main(args: Array<String>){
var ab: String? = "hello"
ab = null
println((ab as String?)?.toUpperCase()) // explicit cast
// ...or
println(ab?.let { it.toUpperCase() }) // use let
}
I believe this is due to smart casts used by Kotlin. In other words, Kotlin is able to infer that after this line of code:
ab = null
type of variable ab is simply null (this is not actual type you can use in Kotlin - I am simply referring to range of allowed values), not String? (in other words, there is no way ab might contain a String).
Considering that toUpperString() extension function is defined only for Char and String (and not Char? or String?), there is no way to choose between them.
To avoid this behaviour see answers proposed by other guys (e.g. explicit casting to String?), but this definitely looks like a feature (and quite a useful one) rather than a bug for me.
I decompiled your function and I figured: after the moment you make ab = null the compiler will smartcast it, putting null (ACONST_NULL) in every ocurrence of ab. Then as null has no type. you can't infer the type for the receiver of toUpperCase().
This is the java equivalent code generated from the kotlin byte code:
public final void main(#NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
String ab = "hello";
ab = (String)null;
Object var3 = null;
System.out.println(var3);
}
It looks as an issue that should be resolved by the kotlin team.
I just want to find an answer to this question
Let A be a parent class with B,C the child classes.
Now we create objects as follows,
A o1 = new A();
B o2 = new B();
C o3 = new C();
A o4 = new B();
B o5 = new A();
C o6 = new B();
From these which will all create errors and why.. I got confused a lot because from 6 we know that it is an error because b may have some specialized variables and 7 is possible and 8 is not possible.
If i'm wrong please correct me and also,
A o7 = o4;
B 08 = o5;
Kindly suggest me the right answers and with explanations and also give me links for tutorials with this kind of puzzles.
Assuming all objects are of pointer types-
1. A *o1 = new A(); // Correct
2. B *o2 = new B(); // Correct
3. C *o3 = new C(); // Correct
4. A *o4 = new B(); // A pointer to a derived class is type-compatible with a pointer to its base class. This is up casting which acts by default.
5. B *o5 = new A(); // Wrong because the other-wise relationship of the above comment isn't true. Though there is a separate concept called Down-casting which isn't valid here.
6. C *o6 = new B(); // Wrong : C, B has no hierarchial relationships
7. A *o7 = o4; // Correct because o4, o7 are both of same type
8. B *o8 = o5; // Wrong. Since, 5 itself is wrong.
Explanation for 4 & 5:
new B() calls A's constructor followed by B's constructor. So, we have sub-objects of type A*,B*. Since, there is a sub-object of type A*, the lvalue can point to it which is also equal to of type A*. This is helpful to access base class overridden virtual methods in derived class.
new A() constructs an object of type A* and returns it's address. So, return type is of A* but the receiving type is of B*. So, they both are incompatible and is wrong.
Example: Output results
#include <iostream>
using namespace std;
class A
{
public:
A(){
cout << " \n Constructor A \n";
}
virtual ~A(){
cout << "\n Destructor A \n";
}
};
class B: public A
{
public:
B(){
cout << " \n Constructor B \n";
}
~B(){
cout << "\n Destructor B \n";
}
};
class C: public A
{
public:
C(){
cout << " \n Constructor C \n";
}
~C(){
cout << "\n Destructor C \n";
}
};
int main()
{
A* obj1 = new A;
std::cout<< "******************************" << std::endl;
A* obj2 = new B;
std::cout<< "******************************" << std::endl;
A* obj3 = new C;
std::cout<< "******************************" << std::endl;
delete obj1;
std::cout<< "******************************" << std::endl;
delete obj2;
std::cout<< "******************************" << std::endl;
delete obj3;
std::cout<< "******************************" << std::endl;
return 0;
}
Results:
Constructor A
Constructor A
Constructor B
Constructor A
Constructor C
Destructor A
Destructor B
Destructor A
Destructor C
Destructor A
Notice that the order of destruction is reverse to the order of construction.
All will work except #5 & #6, in your second set, #2 will not work simply because #5 doesn't (also because 08 is not a valid variable name).
#5 doesn't work because B may have methods that are not a part of A, so new A() doesn't return an object compatible with B.
In general, you can do:
ChildOne c1 = new ChildOne();
Parent p1 = new ChildOne();
whereas you cannot do:
ChildOne c1 = new ChildTwo();
ChildOne p1 = new Parent();
This isn't a direct answer to your question, because I think the other guys have answered your case. However, I noticed that you seem to be under the illusion that objects get type checked against memory allocation.
They don't they are checked against class definitions. Let me give you an example which I hope will bring a sense of understanding:
Say we have two classes that have been defined in the same namespace, they have identical methods and properties. The only difference is their classname. As far as the compiler and runtime are concerned, these two classes would bear no relationship whatsoever.
You would have to define another OOP way of relating them. For example, you can have both classes inherit the same parent, or have them both implement the same interface.
Using the Interface method is often preferable, since many classes which bear little relationship can implement an Interface so that other objects can interact with them via the Interface methods.
OOP is quite tricky to understand, because it's really about abstraction, and bears very little relationship to coding the metal. Once you understand what the abstractions are for and how they are used together, things get easier. Good luck.
In your question, as you mentioned,
A is the superclass,
B,C are two different subclasses of A. i.e B extends A, C extends A
Let's go step by step
Now, your assginment of references to different objects is right upto 4th step.
i.e
A o1 = new A(); //Creates a new A() object and assigns it to ref. of type A. So Correct.
B o2 = new B(); //Creates a new B() object and assigns it to ref. of type B. So Correct.
C o3 = new C(); //Creates a new C() object and assigns it to ref. of type C. So Correct.
A o4 = new B(); //Subclass object being assigned to a superclass reference. That's also correct.
Now at the 5th step,
B o5 = new A(); //Will not compile since a superclass object is assigned to a subclass reference.
//This is not allowed in Java.
Reason : It is because the superclass has no idea what features did its subclass add to itself, while inheriting. So this assignment of superclass object to subclass reference is incorrect and futile.
Now at the 6th step,
C o6 = new B(); // There isn't any hiearchical relationship between B and C.
// They are just subclass of class A. This won't compile.
Now at the 7th step,
A o7 = o4; //Correct. Because both the references i.e o4 & o7 are of same type (A).
// Hence, will compile.
Now at the 8th step,
B 08 = o5; // It is wrong because the 5th step was itself wrong.
// So it will produce a compile time error.