• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Tim Cooke
  • Campbell Ritchie
  • paul wheaton
  • Ron McLeod
  • Devaka Cooray
Sheriffs:
  • Jeanne Boyarsky
  • Liutauras Vilda
  • Paul Clapham
Saloon Keepers:
  • Tim Holloway
  • Carey Brown
  • Piet Souris
Bartenders:

casting objects

 
Sheriff
Posts: 4012
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi there,
As yet I haven't had to deal directly with casting objects, but wanted to check that I'm understanding the concepts. Do I have this right:
Unlike casting primitive types, which has to do with the types' sizes in bits, casting objects has to do with respecting the class hierarchy. So an object of a class higher up in the hierarchy, a superclass, has to be explicitly cast down to a lower class.
Upcasting is done automatically and "sideways" casting is not allowed.
Am I close?
Does class casting mean the same thing as casting objects?
Thanks folks,
Pauline
 
Chicken Farmer ()
Posts: 1932
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
That sounds just about right to me. I have found doing the "is a" test works really great. If you have class Truck and class DodgeRam which extends Truck (a subclass), the following could be done:
Truck o = DodgeRam (DodgeRam is a Truck, no cast needed)
DodgeRam o = Truck (Truck is not a DodgeRam, NEED A CAST!)
Truck o = Bicycle (Bicycle is not a truck, can not be cast either)
Hopefully that should make sense, and I believe I got all of that right
Here's a URL about casting. It's a pretty good article, might help!
http://www.javaworld.com/javaworld/jw-12-1999/jw-12-performance.html

Jason

[This message has been edited by jason adam (edited July 05, 2001).]
[This message has been edited by jason adam (edited July 05, 2001).]
 
Ranch Hand
Posts: 41
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Also, to carry on from jason's example.
Truck t = new DodgeRam();
DodgeRam dr = (DodgeRam) t;
The explicid cast is needed here, since the compiler will go by the class of the object t's reference (which is Truck) and not by what the actual class of the object in t (which is a DodgeRam).
 
whippersnapper
Posts: 1843
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Paul selby:
Also, to carry on from jason's example.
Truck t = new DodgeRam();
DodgeRam dr = (DodgeRam) t;
The explicid cast is needed here, since the compiler will go by the class of the object t's reference (which is Truck) and not by what the actual class of the object in t (which is a DodgeRam).


The explicit downcast is needed only if you want to use methods of DodgeRam that are not also methods of Truck.
Part of the point of the upcasting the reference to Truck:
Truck t = new DodgeRam();
is that you're saying that you want to deal with t in terms of the more general interface (here I'm using interface to mean, roughly, the set of public methods that I can use to interact with this object) of Truck.
However, if DodgeRam overrides any methods of Truck, the DodgeRam versions of those methods will be called (not the Truck versions), even though the reference t is of type Truck, because Java chooses which method to execute based on the actual type of the object and not the type of the reference. (It's called "late binding.")
Again, an explicit downcast like this
DodgeRam dr = (DodgeRam) t;
is necessary only if you plan to use methods of DodgeRam that are not also methods of Truck.
[This message has been edited by Michael Matola (edited July 05, 2001).]
 
Michael Matola
whippersnapper
Posts: 1843
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Pauline McNamara:
So an object of a class higher up in the hierarchy, a superclass, has to be explicitly cast down to a lower class..


Only if you want to use methods of the subclass that are not also defined on the superclass (or that class's superclass, etc.)


Upcasting is done automatically and "sideways" casting is not allowed.


Depends on what you mean by "automatically."
These are some explicit upcasts
Truck t = new DodgeRam() ;
List l = new ArrayList() ;
The upcast occurs because the code says to do an upcast.
Now suppose you have a List l and add t to it
l.add( t ) ;
References to the DodgeRam object will then be upcast to Object. The add() method of ArrayList that we're calling is defined to take a formal parameter of type Object. Here the upcast occurs because we're sending a method a specific DodgeRam object (using a more general Truck reference), but the method is defined to work with more general Object references, which you can see from its signature
public boolean add(Object o)

[This message has been edited by Michael Matola (edited July 05, 2001).]
 
jason adam
Chicken Farmer ()
Posts: 1932
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Yeah, what Michael said
 
Pauline McNamara
Sheriff
Posts: 4012
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Great examples and refinements, thanks everybody.
Michael, by "automatically" I had imagined something like your example
l.add( t ) ;
an implicit cast, I guess we call it. Excellent clarification!
Fascinating stuff...
 
Pauline McNamara
Sheriff
Posts: 4012
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
So, to go back to the terminology question, do "casting objects" and "class casting" mean the same thing?
cheers,
Pauline
[This message has been edited by Pauline McNamara (edited July 05, 2001).]
 
Pauline McNamara
Sheriff
Posts: 4012
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Jason thanks for the article link, just printing it out now...
 
village idiot
Posts: 1208
1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Oh my God, did anybody really understand that? I am like, so lost.
 
Paul Selby
Ranch Hand
Posts: 41
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Here is a small quibble.
Truck t = new DodgeRam();
DodgeRam dr = t; // compile will reject this cast
DodgeRam dr = (DodgeRam) t; // so an explicid cast is need
What you had said about methods is correct, just that the above is also correct.
 
Michael Matola
whippersnapper
Posts: 1843
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
The point I was trying to make had to do with when casting is necessary and when it is not necessary.
Given what you've written about casting, Paul, how do you explain that the following code:

produces the following output?
 
Paul Selby
Ranch Hand
Posts: 41
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Never mind me, Micheal. My contribution to the comments on casting was probably not really worthwhile. At the time I thought it was a good, relevant point to make, or to go with an old line "it seemed like a good idea at the time, your Honour".
Anyway, yes this would be the output of the code:
I'm a Truck.
I'm a DodgeRam truck.
The significant thing for me to note here, is that the class of the object is used to determine which of the methods (between Truck and Dodgeram classes) will be used. It is the class of the actual object and not the class of the object's reference, that is used. So, when "I'm a DodgeRam Truck" appears it is because of overriding. OK, I know you know this already. The other thing I should add is that when I say "object's reference" I mean the variable of t2. So, the class of t2's reference is Truck, whilst the actual object is of the DodgeRam class. Does that make sense?
The other thing you mention was that just because overriding makes use of the method of the subclass, does not mean that other methods of the subclass are accessable whilst using the superclass object reference. So, that's fine too.
Now to the point I was putting forth.
Obviously the example below is casting. The subclass is being placed into the reference/variable of the superclass. Different classes, so casting is needed.:
DodgeRam d1 = new DodgeRam();
Truck t2 = (Truck) d1;
Obviously the example below is not casting. Same class is involved with both the object and the reference variable.:
DodgeRam d1 = new DodgeRam();
DodgeRam d2 = d1;
Okey-dokey. Now in the below example, an object is being placed into a reference variable of the same class, however its previous reference is of a different class.
Truck t2 = new DodgeRam();
DodgeRam d2 = (Truck) t2;
The compiler will go by the class of the object's reference. So in this class it will look at t2 as being of the Truck class not of the class of the actual object it contains (which is a DodgeRam).
Overriding goes by the class of the actual object and Casting goes by the class of the object's reference variable. At the time , it stuck me as interesting, what can I say? Mea culpa.
 
tumbleweed
Posts: 5089
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Paul selby:
Never mind me, Micheal. My contribution to the comments on casting was probably not really worthwhile. At the time I thought it was a good, relevant point to make, or to go with an old line "it seemed like a good idea at the time, your Honour".


No way !!!. Any comment that contributes to an ongoing discussion is worthwhile. So if I catch you say someting stupid like the quote above I'm deleting your post ok
 
Pauline McNamara
Sheriff
Posts: 4012
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
OK guys, help me out here nice and easy, I'm doing my best to follow along but need to digest the last example a little more slowly:

Originally posted by Paul selby:
...
Okey-dokey. Now in the below example, an object is being placed into a reference variable of the same class, however its previous reference is of a different class.
Truck t2 = new DodgeRam();
DodgeRam d2 = (Truck) t2;
The compiler will go by the class of the object's reference. So in this class it will look at t2 as being of the Truck class not of the class of the actual object it contains (which is a DodgeRam).
Overriding goes by the class of the actual object and Casting goes by the class of the object's reference variable. At the time , it stuck me as interesting...


It IS interesting - ohmygawd I think I am going geek.
OK, what throws me off here is the cast:
Truck t2 = new DodgeRam();
DodgeRam d2 = (Truck) t2;
Not like this?
Truck t2 = new DodgeRam();
DodgeRam d2 = (DodgeRam)t2 ;
thanks,
Pauline

 
Ranch Hand
Posts: 31
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Paul selby:

DodgeRam d1 = new DodgeRam();
Truck t2 = (Truck) d1;


Paul,
Can u please elaborate on the above 2 lines of code?? I have a doubt if in the second line, if we don't use the casting, does it give a compilation error or a run-time error (when the methods which belong exclusively to the sub-class are accessed in the code using t2).
Thanks!
-S
P.S: Pauline, I assure I will not digress this thread of yours by any further questions (just bear with this one, thanks!)
 
Michael Matola
whippersnapper
Posts: 1843
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Paul, there's certainly no need to apologize or to feel that your post wasn't worthwhile.
I just misunderstood it to mean something that I now see I don't think you intended, and I was just trying to clear things up. I think we're pretty much talking about the same things, only approaching them from slightly different angles.


Overriding goes by the class of the actual object and Casting goes by the class of the object's reference variable. At the time , it stuck me as interesting, what can I say? Mea culpa.


It is interesting. And something that all of us learning Java need to grapple with at some point.
Two issues, though, with you more recent post.
(1)


Now to the point I was putting forth.
Obviously the example below is casting. The subclass is being placed into the reference/variable of the superclass. Different classes, so casting is needed.:
DodgeRam d1 = new DodgeRam();
Truck t2 = (Truck) d1;


This is the case Suma MM is asking about, too. But I have a different issue.
Explicit casting is not necessary here, although I'd admit it looks tidier to do it. (This example is different from your original -- was it supposed to be?)
Your code and the following code

both compile without error and produce the exact results on the following test code (add the following method to DodgeRam)
public String onlyDodgeRamsDoThis()
{
return "Only Dodge Rams do this." ;
}

Trying to compile line (A) gives an error in either case, as expected.

(2)


Okey-dokey. Now in the below example, an object is being placed into a reference variable of the same class, however its previous reference is of a different class.
Truck t2 = new DodgeRam();
DodgeRam d2 = (Truck) t2;


I don't understand what the above code is intended to show. And as Pauline pointed out, what you probably wanted was this

That is, get a DodgeRam handle for the DodgeRam object.
Your code, in fact, won't compile. You get an "incompatible types" error.
The complilation error makes sense if you remember that an object reference variable can, in general, do 4 things:
(1) Be declared but uninitialized. (Note that only object reference variables local in scope can be unitialized. If they're members of a class, they automatically get initialized to null.)
Truck t ;
(2) Refer to an instance of a class of the same type as the reference variable.
Truck t = new Truck() ;
(3) Refer to an instance of a class that is a subclass of the type of the reference variable.
Truck t = new DodgeRam() ;
or
DodgeRam d = new DodgeRam() ;
Truck t = (Truck)d ;
or
DodgeRam d = new DodgeRam() ;
Truck t = d ;
(4) Refer to null.
Truck t = null ;

Your code is attempting to use an object reference variable to refer to an instance of a class of the same type as the reference variable but explicity cast to a reference of that class's superclass, and the compiler complains.
 
Pauline McNamara
Sheriff
Posts: 4012
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Suma MM:
P.S: Pauline, I assure I will not digress this thread of yours by any further questions (just bear with this one, thanks!)


Hi Suma,
Your questions or comments are welcome in any thread! I don't consider a thread to be owned by any one person, but rather a discussion that we all share.
Thanks for joining in,
Pauline
[This message has been edited by Pauline McNamara (edited July 08, 2001).]
 
Paul Selby
Ranch Hand
Posts: 41
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Johannes, I repent. Evidently, I stand corrected.
Yes, I made a mistake on my last lines of code. There had been some experimenting with the compiler to check what I was saying. I must have become confused over what it exactly it was I was meant to be typing in the first place. So, as Pauline had pointed out:
This ...
Truck t2 = new DodgeRam();
DodgeRam d2 = (Truck) t2;
... was meant to be this:
Truck t2 = new DodgeRam();
DodgeRam d2 = (DodgeRam)t2 ;
The other thing Suma and Micheal were asking about was this:
DodgeRam d1 = new DodgeRam();
Truck t2 = (Truck) d1;
Yes, you are both right, it compiles. I had though the explicit (Truck) cast not necessary, originally. I had tried it with my compiler to verify it, but my compiler spat it back at me. I must have been typing the wrong thing.
Micheal, yes, we were looking at it from different angles. This has been good, a part from a few mistakes here and there.
 
Pauline McNamara
Sheriff
Posts: 4012
6
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Paul selby:
This has been good.


Yeah! This was a great exercise for me, thanks for the examples.
 
Ranch Hand
Posts: 47
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Ok. I realize that this is a very old thread. But, I have a question now that I've searched this topic out.
What if I WANT to call a method on the superclass? Let me explain a bit more...
public class Truck
{
public string truckMethod()
{
return "truck method";
}
}
public class DodgeRam extends Truck
{
public string truckMethod()
{
return "Dodge Ram method";
}
}
public class MyClass
{
public static void main(String[] args)
{
Truck t1 = new Truck();
System.out.println(t1.truckMethod());
DodgeRam dr1 = new DodgeRam();
System.out.println(dr1.truckMethod());
Truck t2 = new DodgeRam();
System.out.println(t2.truckMethod());
}
}
The output will look like:
truck method
Dodge Ram method
Dodge Ram method
Question:
Can I get t2 to call the superclasses truckMethod in MyClass? Nevermind the obvious "Why would I want to do this." I can't answer that question. This question arose from my study of C# and I'll just leave it at that.
Is it possible to get t2 to use Truck's truckMethod?
 
Ranch Hand
Posts: 133
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
You can't call the superclasses methods, at least not from the outside. DodgeRam could call super.truckMethod() and get to it, but there's no way to get to it from MyClass.
 
Sheriff
Posts: 7023
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Assuming appropriate access modifiers, a child can use the parent's toys either through inheritance or with super. A child cannot use the grandparent's toys through inheritance or super if the parent is hiding or overriding said toys. Said child could create an instance of the grandparent, and then use the grandparent's toys (heh heh - eat that parent).
 
Michael Matola
whippersnapper
Posts: 1843
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Jason Kilgrow writes:
Can I get t2 to call the superclasses truckMethod in MyClass? Nevermind the obvious "Why would I want to do this." I can't answer that question. This question arose from my study of C# and I'll just leave it at that.
Is it possible to get t2 to use Truck's truckMethod?
Short answer ("instead of"): if you want an instance of DodgeRam to use Truck's truckMethod(), then don't override truckMethod() in DodgeRam. Rely on inheritance. (But I don't think that's what you're asking.)
(You could also code thus, but it's sort of pointless and redundant.)

Longer ("in addition to"): Yes you can, but not gracefully. And any time you find yourself doing the sort of thing I'll show in a minute, it's high time to reconsider your design.
First, note that "intended" use of a call to a superclass method is from within an overridden subclass method (both methods have the same signature). The overridden subclass method wants to do what the superclass method does plus something else, so it calls it:

But methods other than truckMethod() can call super.truckMethod() too:

So
DodgeRam d = new DodgeRam() ;
System.out.println( d.truckMethod() ) ;
System.out.println( d.almostTruckMethod() ) ;
produces the output
Dodge Ram method
truck method
Again, if you find yourself doing this sort of thing, I think it's time to reconsider your design (or at least the names of your methods).
Think carefully about your original code. You had a Truck class with a truckMethod() instance method. You also had a DodgeRam class that extended Truck and *overrode* truckMethod(). What is the point of overriding a method? (In other words, of polymorphism?) In essence, when you wrote DodgeRam's truckMethod() to override Truck's, you were implying that there's some programming context in which you expect to have both Truck and DodgeRam instances. You want to be able to issue truckMethod() calls to objects of either type (perhaps not even knowing at compile time whether the object receiving the call is an instance of Truck or DodgeRam) and have the system itself sort out which truckMethod() code to execute, based on the type of the object receiving the call.
If you didn't mean to imply all this, then maybe the question should be a little larger than the one you asked. Maybe you should be asking why override truckMethod() to begin with. When you override a method, you're saying you want to have the subclass receive the same calls as the superclass, but respond to them differently.
Nevermind the obvious "Why would I want to do this." I can't answer that question.
Nope. Can't let that one go. Tell us what you're trying to do, and maybe somebody can come up with a better way of doing it.
[ May 20, 2002: Message edited by: Michael Matola ]
 
Michael Matola
whippersnapper
Posts: 1843
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Dirk Schreckmann:
Assuming appropriate access modifiers, a child can use the parent's toys either through inheritance or with super. A child cannot use the grandparent's toys through inheritance or super if the parent is hiding or overriding said toys. Said child could create an instance of the grandparent, and then use the grandparent's toys (heh heh - eat that parent).


This is similar to my bad code scenario above. If a grandchild thinks it needs to subvert its (immediate) parent and use its grandparent's version of a method that the parent overrode, then it's time to consider whether the parent's overriding was appropriate to begin with.
 
Ranch Hand
Posts: 68
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
On a lighter note... I wouldn't want a truck that has the words 'dodge' and 'ram' in its name. Scary
 
Jason Kilgrow
Ranch Hand
Posts: 47
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Michael Matola:

This is similar to my bad code scenario above. If a grandchild thinks it needs to subvert its (immediate) parent and use its grandparent's version of a method that the parent overrode, then it's time to consider whether the parent's overriding was appropriate to begin with.


But I can only use super in DodgeRam (the child class) and NOT in a class that instantiates Truck and/or DodgeRam. Correct?
If this is correct, then the answer to my question is "You can't do that". And I'm fine with that just as long as I know that I can't do it.
If I'm not correct, please explain.
[ May 23, 2002: Message edited by: Jason Kilgrow ]
 
Michael Matola
whippersnapper
Posts: 1843
5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Originally posted by Jason Kilgrow:

But I can only use super in DodgeRam (the child class) and NOT in a class that instantiates Truck and/or DodgeRam. Correct?

Nyes. A more accurate way of saying what you're getting at is that a direct call to a superclass method (or constructor) can take place only in a class that is an immediate subclass of the given superclass. (Actually I think also can take place only in an instance method in a clsss that...)
(By direct call I mean a call using the keyword super and not anything like the sillyness I showed in my example above.)
(Your way of stating the same idea was not entirely correct. For example, the DodgeRam class can be written such that it creates a DodgeRam instance. I know you didn't mean that DodgeRam itself can't make a super call.)

If this is correct, then the answer to my question is "You can't do that". And I'm fine with that just as long as I know that I can't do it.
If I'm not correct, please explain.

Right. A class can call super only on its own immediate superclass.
Think of it this way. All this business about calling superclass methods can be thought of as an implementation detail of a given class, which should be hidden from users. You as an external user of that class can only interact with that class or its instances through its public interface (constructors, methods, fields).
You can create a public method that calls the superclass and use that, as I showed, but that's the only way I know how to do the sort of thing you were asking about, and I don't think it's very good style.
And I'm fine with that just as long as I know that I can't do it.
But is there something you're writing in which you think you need to do this sort of thing?
 
Jason Kilgrow
Ranch Hand
Posts: 47
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Michael Matola:
Right. A class can call super only on its own immediate superclass.
Think of it this way. All this business about calling superclass methods can be thought of as an implementation detail of a given class, which should be hidden from users. You as an external user of that class can only interact with that class or its instances through its public interface (constructors, methods, fields).


Ok. Thank you. That makes sense.


But is there something you're writing in which you think you need to do this sort of thing?[/QB]


Actually, no. It's purely academic. The question came up by trying to learn C#. There was a discussion in my book about the use of "virtual" and "overload" keywords. C# implements early binding by default and in our Truck...DodgeRam example, the Truck method would be called unless the Truck method was defined as vitrual (via the "vitrual" keyword on the method) or you use the new keyword on your overloading method.. And I got wondering if there was a way to do it in Java.
In some ways, I think that the C# philosophy is very, very bad. What if I want to overload Object.toString()? Unless Object.toString() is defined as virtual or you use the new keyword on your overloading method, you will always get Object.toString(). However, in other ways, I like the flexibility C# provides me in being able to do it both ways.
Anyway...purely academic at this point. Thanks for your help!!
 
With a little knowledge, a cast iron skillet is non-stick and lasts a lifetime.
reply
    Bookmark Topic Watch Topic
  • New Topic