Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
618 views
in Technique[技术] by (71.8m points)

java - Casting between Interfaces and Classes

Yesterday I just started learning interfaces and have been doing some simple examples, I noticed that I have had lots of trouble understanding casting between classes and interfaces so I read Java Cast Interface to Class as well as Interfaces Oracle Java Tutorials

However when my book gave a small review question at the bottom, I noticed that I still didn't really understand completely, and the book I bought doesent have solutions. The following is the question ( I gave my attempt and reasoning which is probably wrong for some, so any help is good!)

Suppose the class Sandwich implements the Edible interface, and you are given the variable declarations

Sandwich sub = new Sandwich();

Rectangle cerealBox = new Rectangle(5, 10, 20, 30); 

Edible e = null;

Which of the following assignment statements are legal?

  1. e = sub;

    Since the Sandwich class implements the interface Edible, this is no problem. We can say e = sub with no issues. It Works

  2. sub = e;

    Since we are trying to change our object sub in the Sandwitch class to an interface type, we can't do this without casting. It won't work

  3. sub = (Sandwich) e

    Works! This fixes our old problem

  4. sub = (Sandwich) cerealBox;

    I have no clue.. but it should work? cerealBox is a Rectangle so with (Sandwich) we convert it to sub, which is part of Sandwich

  5. e =cerealBox;

    Don't think so. Rectangle isn't implemented the Edible interface so it shouldnt work

  6. e = (Edible) cerealBox;

    Should work now. (Edible) acts as if it implements the interface.

  7. e = (Rectangle) cerealBox;

    Not sure. I don't think this will work, I mean cerealBox is of type Rectangle, why are we making it a rectangle again?

  8. e = (Rectangle) null;

    Not sure at all

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

What you need to understand is that every object has one concrete class. It "is" an instance of that class, and it "is" an instance of every class that class inherits, all at the same time. Not only that but it "is" an instance of every interface any of those classes inherit, all at the same time.

So, assuming:

 class Animal
 class Mammal extends Animal implements Suckler
 class Dog extends Mammal implements Woofer

... if I create a new Dog() then that object "is" an Object (because all objects inherit Object), an Animal, a Mammal, a Suckler, a Dog and a Woofer, all at the same time.

However, a variable is not the same thing as an object. A variable points at an object, and a variable has a type. The type must be compatible with the object assigned, but that's all.

So:

Suckler s = new Dog();

works, but from that moment on, all the compiler knows about the object via the s variable is that it's a Suckler. It doesn't know it's a dog; it doesn't know it's a mammal. So we cannot then go:

Dog d = s;

... because the compiler can't guarantee that the variable pointed to by s is a Dog.

The type of the variable cannot ever be changed. s has type Suckler for the whole of its lifetime, no matter what. We could assign a Sheep or a Pig to s, but we won't be able to do anything to those objects except operations that are part of the Suckler definition.


I'm going to assume that the various classes and interfaces are defined like this:

 public interface Edible {
     ...
 }

 public class Sandwich implements Edible {
     ...
 }

 public class Rectangle {  // note, does not implement or extend anything
                           // (except Object)
     ...
 }

So:

Sandwich sub = new Sandwich();
Edible e = null;
e = sub;

This is fine. sub is a Sandwich, and Sandwich is a kind of Edible.


Sandwich sub = new Sandwich();
Edible e = null;
sub = e;

This won't compile. e is an Edible, but there could be any number of classes that implement Edible, as well as Sandwich. sub has to be a Sandwich and since the compiler can't be sure that e is a sandwich, it will refuse to compile.


Sandwich sub = new Sandwich();
Edible e = null;
sub = (Sandwich) e;

This works. As you've correctly worked out, the cast tells the compiler "OK, you can't be sure that e is a Sandwich, but as the coder, I'm telling you it is.

If you did this, and at runtime e was actually an Apple implements Edible, and not a Sandwich, the JRE would throw a ClassCastException. It's your job to make sure this doesn't happen -- avoiding casting is the best way to do this.


Sandwich sub = new Sandwich();
Rectangle cerealBox = new Rectangle(5, 10, 20, 30); 
sub = (Sandwich) cerealBox;

... will refuse to compile. Sandwich and Rectangle are not related to one another. The compiler knows that no Sandwich is also a Rectangle, so it refuses to compile.

The variable sub must always point to a Sandwich, and cerealBox must always point to a Rectangle. The only way a Rectangle could be a Sandwich is if Rectangle inherited Sandwich, or vice versa. Since neither of these is the case, it won't compile.

This is assuming the declarations above. It's possible for a class to implement multiple interfaces, so if it was public class Sandwich implements Edible, Rectangle {...}, this code would work.


Rectangle cerealBox = new Rectangle(5, 10, 20, 30); 
Edible e = null;
e = cerealBox;

... will not compile. A Rectangle is not an Edible.


Rectangle cerealBox = new Rectangle(5, 10, 20, 30); 
Edible e = null;
e = (Edible) cerealBox;

.. at first glance, you might think will not compile. A Rectangle is not an Edible, and you can't tell the compiler it is. However the compiler can't guarantee that there isn't a class like this:

public class Flapjack extends Rectangle implements Edible { ... }

A Flapjack would be a kind of Rectangle that is also an Edible, and since the compiler isn't clever enough to know that cerealBox is not a Flapjack, it must compile (it will fail in runtime).

A really clever compiler might be able to analyse the program logic to see that cerealBox has been initialised as new Rectangle(), and that there has been no opportunity for that to change at runtime. But the Java standard does not have that kind of sophisticated static analysis.

The writer of Rectangle could ensure that Square can't exist, by defining it as public final class Rectangle -- the final keyword forbids subclasses.


Rectangle cerealBox = new Rectangle(5, 10, 20, 30); 
Edible e = null;
e = (Rectangle) cerealBox;

... won't compile. e is still an Edible. You can't assign a Rectangle to it, because Edible and Rectangle are not related.


Edible e = null;
e = (Rectangle) null;

Casting null to a Rectangle is fine, however e is an Edible, and you can't assign a Rectangle to an Edible, since they are unrelated types.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

1.4m articles

1.4m replys

5 comments

57.0k users

...