• 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:
  • Campbell Ritchie
  • Jeanne Boyarsky
  • Ron McLeod
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • paul wheaton
  • Rob Spoor
  • Devaka Cooray
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
  • Frits Walraven
  • Tim Moores
Bartenders:
  • Mikalai Zaikin

Method matching.

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

Looking into method-matching this evening, and I have found a bit of a peculiar case... Consider this code:



My key starting point when examining/practicing method matching, is this post: https://coderanch.com/t/417622/java-programmer-SCJP/certification/Golden-Rules-widening-boxing-varargs

Now, this code is in regards to point number 4:


4. While overloading, Widening + vararg and Boxing + vararg can only be used in a mutually exclusive manner i.e. not together.



The problem with this code is that for (Byte...a) and (Number... a) the rule applies - the code does not compile with either of these methods if the (byte... a) method exists.

However, if we instead use the (Object... a) method it compiles and runs fine, and this puzzles me.

Obviously, both (Number... a) and (Object... a) are actually making use of boxing+widening+varargs, so I admit it is an extension of rule 4 in the post above. But still, I feel that if the rule applies for (Number... a) then why shouldn't it apply for (Object... a) ? They are both invoked by the same procedure; boxing and widening the byte, yet somehow (Number... a) conflicts with (byte... a), while (Object... a) does not.

Does anyone have an explanation why?

// Andreas


 
Saloon Keeper
Posts: 15484
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Calling the Object... method does not entail widening, only boxing.

First the byte is boxed to a Byte. A Byte is a subclass of Object, so there is no widening. Widening only happens between primitives.
 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:Calling the Object... method does not entail widening, only boxing.



Well, in this case I am referring to conversion with the same word, widening, in the sense that a byte gets converted/widened to a Number/Object, after it has first been boxed to a Byte.

Stephan van Hulst wrote:First the byte is boxed to a Byte. A Byte is a subclass of Object, so there is no widening. Widening only happens between primitives.



Exactly right, the Byte is converted to an Object. But the same conversion happens if we use the Number... method instead, except in this case we get a conflict with int... and a compiler error. So why is it conflicting with Number... and not Object... ? The only difference (that I can see), is that Object... is one step less conversion in the inheritance tree.

// Andreas
 
Stephan van Hulst
Saloon Keeper
Posts: 15484
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Having go(byte... a) and go(Number... a) in the same class compiles fine for me.

[edit]

Well, in this case I am referring to conversion with the same word, widening, in the sense that a byte gets converted/widened to a Number/Object, after it has first been boxed to a Byte.


Well, you may think I'm nitpicking, you shouldn't refer to this as widening, because there is no conversion, and nothing gets widened. It's merely an upcast.

Widening is an actual conversion where the value of a primitive is changed into an equivalent value of a larger primitive type. When you cast one object reference to a supertype, nothing gets changed.
 
Stephan van Hulst
Saloon Keeper
Posts: 15484
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Based on some observation, I would compile the following rules. The rules apply from top to bottom, meaning if rule 1 is enough to make a decision between two methods, you can ignore rules 2 - 6.

1) Widening + Boxing; not considered
2) Single argument > Varargs
3) Varargs = Varargs + Boxing; ambiguous
4) Widening > Boxing
5) Unboxing > Casting up
6) Specific upcast > General upcast


See below.
 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:Having go(byte... a) and go(Number... a) in the same class compiles fine for me.

[edit]

Well, in this case I am referring to conversion with the same word, widening, in the sense that a byte gets converted/widened to a Number/Object, after it has first been boxed to a Byte.


Well, you may think I'm nitpicking, you shouldn't refer to this as widening, because there is no conversion, and nothing gets widened. It's merely an upcast.

Widening is an actual conversion where the value of a primitive is changed into an equivalent value of a larger primitive type. When you cast one object reference to a supertype, nothing gets changed.



I don't think youre nitpicking, on the contrary I think using the correct term is very important, especially in terms like programming. On the other hand I am not sure that I "agree" 100% with your definition of widening/converting/upcasting, but let me instead get back on the subject...

As you say you can compile the above methods fine, and I agree you can - as long as you do not have a method-call with an argument that could be considered ambiguous! Please look at this code, it's your code from the last post you made, I just added a main method:



If you compile this , it will give you an error, but only as long as you have the method call with a byte argument. You can also proceed here, after your first attempt, by commenting out Number... and you will be left with int... and Object... which match the byte. In this case the int... will be chosen and if you uncomment that method too, finally the Object... will be chosen.

Now why is that? In order to reach Number..., the byte must be boxed to Byte and then upcast. The same thing goes for Object... but Object... is not conflicting with int... as Number... is, and I cannot quite understand why, except MAYBE because Object is treated as a special case due to its alpha-class status.

EDIT: stupid small but important errors fixed, i just woke up here

EDIT2: Another thing Stephan, I noticed that compiling your code with a method-call using a Byte argument fails too, also in this case will it give a compiler error because int... and Number... are conflicting. I swear I feel like this whole thing is going in circles, there are so many combinations and it's very hard to find a "red line" to follow that explains all scenarios.

// Andreas
 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Alright, right now it all seems strangely clear to me (I'm sure this feeling will pass in a minute:) ... Here are my conclusions:

There are so many different scenarios that it's very hard to come up with descriptive rules that covers all of them, but right now this seems to cover most of them for me:

byte => int > Byte > byte...
reads: "byte will be widened to another primitive before it is boxed - and it will be boxed and widened as a wrapper, before it matches a primitive vararg or a wrapper vararg"

Byte => Number > byte > Byte...
reads: "Byte will widened to a superclass before it is unboxed - and it will be unboxed and widened as a primitive, before it matches a pimitive vararg or wrapper vararg"

There are some subtle and important things in the background here. For instance, if all you have is methods with varargs of either a wrapper or a primitive, then there will be a compiler error due to ambiguity. This will not happen however, if you have a more specific method that doesn't use varargs (any of the ones mentioned above, before the vararg versions) and that matches the argument (either via primitive widening or reference widening/conversion).

One of the most important things to keep in mind is, whether we are dealing with a primitive or a wrapper, the argument will always favour a widening/upcast operation over a boxing/unboxing operation:
- byte will prefer to stay a primitive, over being boxed
- Byte will prefer staying as a object, over being unboxed to a primitive

// Andreas
 
Stephan van Hulst
Saloon Keeper
Posts: 15484
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Okay, this is bizarre. Compiling and running works fine under Netbeans (which I have been doing all the time), but it fails using the console.

I'm using jdk1.6.0_25, how about you?
 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Good question, I believe it's 1.6.0.240, atleast that's what it says under "uninstall programs".

And yeah I've been using the console, not that I have any idea why it would work in Netbeans though

// Andreas
 
Stephan van Hulst
Saloon Keeper
Posts: 15484
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Okay, I found the culprit. Apparently Netbeans bends the rules a bit when you use the "Compile on Save" option, which is default. I'll try to find out why this makes a difference.
 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
And just as things were starting to make sense and pan out, I have found another strange behaviour...

Calling go() with an argument of type byte, will be ambiguous if you have only varargs methods of type byte... int... long... etc.

In the case of an argument of type Byte, and method parameters of type Byte... Number... Object... there is no problem, they will match in turn, from the bottom up. But strangely enough this is not the case for primitives.

Sigh...

// Andreas
 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
And here is my last rule for this weird special case:

An argument of type byte or Byte wil always be ambiguous if: (excluding Object...) the only methods that match are 2 or more vararg methods, AND atleast one of the methods is a primitive vararg method.

It seems to be that Object... is truly a special case of varargs, I magine it is because it is the above-all-catchall parameter, that can catch pretty much anything, and as opposed to Object will lead to a method parameter of type Object[].

// Andreas
 
Ranch Hand
Posts: 394
Eclipse IDE Oracle Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Andreas wrote:An argument of type byte or Byte wil always be ambiguous if: (excluding Object...) the only methods that match are 2 or more vararg methods, AND atleast one of the methods is a primitive vararg method.



The following program does NOT approve of the above, it compiles!

This is what java does to us when we think we're done...

Regards

Ikpefua.
 
Stephan van Hulst
Saloon Keeper
Posts: 15484
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Okay Andreas, I now tested using javac. I think you will agree to these hard and fast rules:

1) Everything > Object...
2) Single > Varargs
3) Widening + Boxing; not considered
4) Varargs = (Varargs + Widening) = (Varargs + Boxing) = (Varargs + Unboxing); ambiguous
5) Widening > Boxing
6) Upcasting > Unboxing
7) Specific upcasting > General upcasting

Widening and boxing apply only to primitives. Unboxing and upcasting apply only to wrappers.

[edited a million times for typos]
 
Stephan van Hulst
Saloon Keeper
Posts: 15484
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Ikpefua Jacob-Obinyan wrote:The following program does NOT approve of the above, it compiles!



Because it's not ambiguous. You can't Widen + Box, so Integer... *never* applies to byte.
 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:

Ikpefua Jacob-Obinyan wrote:The following program does NOT approve of the above, it compiles!



Because it's not ambiguous. You can't Widen + Box, so Integer... *never* applies to byte.



Indeed, you had me thinking "what the h*ll" there for a moment Jacob But like Stephan pointed out, this is just not an ambiguous method call, Integer is never a valid option here.

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

Stephan van Hulst wrote:Okay Andreas, I now tested using javac. I think you will agree to these hard and fast rules:

1) Everything > Object...
2) Single > Varargs
3) Widening + Boxing; not considered
4) Varargs = (Varargs + Widening) = (Varargs + Boxing) = (Varargs + Unboxing); ambiguous
5) Widening > Boxing
6) Upcasting > Unboxing
7) Specific upcasting > General upcasting

Widening and boxing apply only to primitives. Unboxing and upcasting apply only to wrappers.

[edited a million times for typos]



Hi again Stephan.

I believe that all seems correct, atleast I am unable to find anything that directly contradicts my own conclusions from my previous post. Oh, and by "specific upcasting > general upcasting" I assume you are talking about explicit upcasting contra "conversion" ?

In case you're interested, I will add one small special case - an array of any class, will actually not agree with rule #1

MyClass[] <- MySuperClass[] <- Object[] <- Object

So it will match method signatures according to:

MyClass.../MyClass[] > MySuperClass.../MySuperClass[] > Object.../Object[] > Object
So in the case of an array like this, Object < Object...

Regards,
Andreas.
 
Stephan van Hulst
Saloon Keeper
Posts: 15484
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
No, by specific upcasting I mean that Number will be chosen before Object. Casting to Number is more specific than casting to Object.

You are correct about the array of objects. Rule 7 actually applies to this, but it's overriden by rule 1, because it's higher in the list. The problem is that rule 1 has to be higher than rule 4, and rule 6 and 7 have to be lower than rule 4. So maybe we just need to alter rule 1 to read: "Everything > Object..., except if the argument is Object[]". Not very pretty, but it works.

At this moment I'm starting to get *really* curious what the JLS has to say about overloading.
 
Ikpefua Jacob-Obinyan
Ranch Hand
Posts: 394
Eclipse IDE Oracle Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Andreas wrote:Indeed, you had me thinking "what the h*ll" there for a moment Jacob But like Stephan pointed out, this is just not an ambiguous method call, Integer is never a valid option here


Yea Andreas, I had to carefully read again and took note of "method match" and indeed Integer was NOT a valid option.
You know I was at that same time revising some of those weird array syntax...bla bla bla mix up

Stephan wrote:"Everything > Object..., except if the argument is Object[]"


I think that's ideal

Stephan wrote:
At this moment I'm starting to get *really* curious what the JLS has to say about overloading.



Same here.

Cheers guys!

Ikpefua
 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Well seems like we are all in agreement here. Although personally, the shortest (and for me easiest) way to remember these rules are undoubtedly these 2 lines:

Andreas Svenkson wrote:
byte => int > Byte > byte...
reads: "byte will be widened to another primitive before it is boxed - and it will be boxed and widened as a wrapper, before it matches a primitive vararg or a wrapper vararg"

Byte => Number > byte > Byte...
reads: "Byte will widened to a superclass before it is unboxed - and it will be unboxed and widened as a primitive, before it matches a pimitive vararg or wrapper vararg"



Keeping in mind the specific cases of Object[]/primitive[][], and that Object... is nt conflicting with a primitive vararg method, they sum everything up for

One just has to keep in mind that each case above has its own inheritance tree which will be invoked (I think of it vertically), before the next step in the chain is valid.

So for byte => int > Byte > byte...

... the byte will prefer to widen, to int or all the way up to double if such a method exists, BEFORE it goes over to boxing. And once it does box, It will also "widen" as an object from Byte -> Number -> Object before it goes to vararg methods.

Same thing if the argument is Byte, it will prefer to widen up to Object BEFORE it unboxes to any primitive. Once a primitive, it will also prefer to widen up to double before it matches vararg methods.

So there is a kind of symmetri going on: widening -> unboxing / boxing -> varargs
Regardless of the argument being a primitive or an object.

Cheers,
Andreas.
 
Ikpefua Jacob-Obinyan
Ranch Hand
Posts: 394
Eclipse IDE Oracle Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:Calling the Object... method does not entail widening, only boxing.
First the byte is boxed to a Byte. A Byte is a subclass of Object, so there is no widening. Widening only happens between primitives.


Stephan at the moment we seem to be 'loosing grounds' to Andreas here ... The K & B book extensively explains and effectively confirms that you CAN box and widen (pages 250,251,252, 253 and 254). In page 254 it says

You can box and then widen. (An int can become an Object, via Integer.)



Regards

Ikpefua

 
Stephan van Hulst
Saloon Keeper
Posts: 15484
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hmm it seems the language specification indeed uses the term widening, instead of upcasting.

So to clarify my above posts, I used the term "widening" to refer to primitive widening *only*. I use the term upcasting to refer to object reference widening *only*.

You are not allowed to widen a byte to an int, and then box to an Integer.
 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:Hmm it seems the language specification indeed uses the term widening, instead of upcasting.

So to clarify my above posts, I used the term "widening" to refer to primitive widening *only*. I use the term upcasting to refer to object reference widening *only*.

You are not allowed to widen a byte to an int, and then box to an Integer.



Yeah, I got what you meant even before so no worries

// Andreas
 
Ikpefua Jacob-Obinyan
Ranch Hand
Posts: 394
Eclipse IDE Oracle Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
@Andreas the two basic rules (and for you easiest) is a good idea, I used that strategy to completely 'take control' of Regex, as in all that we need to know for the exams, just make rules to the best of your understanding, provided they are in accordance or coherent with the topic in question.

Cheers guys!

Ikpefua
 
Ikpefua Jacob-Obinyan
Ranch Hand
Posts: 394
Eclipse IDE Oracle Java
  • Likes 1
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hello guys...
@Andreas I thought I should mention this too, I noticed that a method with Var-Arg parameter type can be invoked WITHOUT arguments.

This is one of those wicked things one might see on the exams and you'll select compilation fails without thinking twice.
I "intuitevly" noticed that we are studying at the same pace more or less, I am 10hrs per day at the moment, and I am in the same topic as yourself "operators" and guess what? I learnt just yesterday that compound operators carry out implicit casts.

Cheers

Ikpefua.
 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Just as you think you know everything about a topic

Good catch Jacob, and as I'm sure you know by now, it seems that such a no-arg method-call will match ANY vararg method; primitive, object or whatever.

PS: Where did you learn about the implicit casting of compound assignments? I can't seem to find it in K&B.

Cheers,
Andreas.

 
Ikpefua Jacob-Obinyan
Ranch Hand
Posts: 394
Eclipse IDE Oracle Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Andreas wrote:PS: Where did you learn about the implicit casting of compound assignments? I can't seem to find it in K&B.



You unintentionally skipped it... go to chapter 3, page 197, its right there at the bottom of the page here is a caption:

In fact, +=, -=, *=, and /= will all put implicit cast.

 
Andreas Svenkson
Ranch Hand
Posts: 179
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Indeed there it was, I had even underlined it and yet I missed it yesterday evening... Just a good example of that studying too late in the evening isn't such a good thing

// Andreas
 
Ikpefua Jacob-Obinyan
Ranch Hand
Posts: 394
Eclipse IDE Oracle Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
@Andreas...I have just CONFIRMED my exams for Monday June 20...So I will sort of coordinate myself with you, it is the 5th time I am going through the K & B book, I will be through on Friday and do mock tests Saturday and Sunday, Then Monday I will go there and get-the-job-done...Wish me luck!...
 
Ranch Hand
Posts: 637
Eclipse IDE Firefox Browser Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Andreas Svenkson wrote:Indeed there it was, I had even underlined it and yet I missed it yesterday evening... Just a good example of that studying too late in the evening isn't such a good thing



Off topic : i suggest that you make "flash-cards" for small things like these (for every chapter) after you underlined them. Then for remembering, recite them regularly like a parrot.
I made a few just 2 weeks before the exam, they were helpful.

As an example, see page 336.

PS : I wonder if ALL such small things will make a BIG difference when one makes advanced code.
 
Stephan van Hulst
Saloon Keeper
Posts: 15484
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
It makes a big difference when one debugs advanced code. Especially when it's not your own code.
 
Ranch Hand
Posts: 125
Postgres Database BSD Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Guys, in addition to what my friend Ikpefua wrote, something that the book does not explicitly say:

Compiler prefers : WIDENING then BOXING then VARARGS -- INDIVIDUALLY NOT COMBINATION

Varargs works as a last "catchall" resort.

Compiler CANNOT widen then box

Compiler CAN box/unbox then widen

Ikpefua

I have written notes on each one of the 9 chapters + one additional of mine + over 140 programs. If you are interested i can send it all over.
 
Achilleas Achix
Ranch Hand
Posts: 125
Postgres Database BSD Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Ikpefua Jacob-Obinyan wrote:@Andreas...I have just CONFIRMED my exams for Monday June 20...So I will sort of coordinate myself with you, it is the 5th time I am going through the K & B book, I will be through on Friday and do mock tests Saturday and Sunday, Then Monday I will go there and get-the-job-done...Wish me luck!...




 
Ikpefua Jacob-Obinyan
Ranch Hand
Posts: 394
Eclipse IDE Oracle Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
@Achilleas!...thank you sooo much! ...I thought you went on vacation somewhere in the mediterranean -Cyprus, Croatia- etc.

Achilleas wrote:I have written notes on each one of the 9 chapters + one additional of mine + over 140 programs. If you are interested i can send it all over.


Please do that A.S.A.P thanks.

Regards

Ikpefua
 
Ikpefua Jacob-Obinyan
Ranch Hand
Posts: 394
Eclipse IDE Oracle Java
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Rahul wrote:recite them regularly like a parrot.



Rahul...I cant stop laughing ...imagining the recitation of java in a parrots mode

Regards

Ikpefua
 
With a little knowledge, a cast iron skillet is non-stick and lasts a lifetime.
reply
    Bookmark Topic Watch Topic
  • New Topic