Post #194,510
2/14/05 5:27:22 AM
|
OT: Inner/outer classes.
I'm a terrible programmer who's trying (rather amusingly badly) to learn Java.
What are inner and outer classes, and why and where would I use them?
Short words, please; in programming terms, I am a bear of very little brain.
Peter [link|http://www.ubuntulinux.org|Ubuntu Linux] [link|http://www.kuro5hin.org|There is no K5 Cabal] [link|http://guildenstern.dyndns.org|Home] Use P2P for legitimate purposes!
|
Post #194,519
2/14/05 8:13:23 AM
|
Simple explanation:
"Inners" are the kind that collect lint. "Outers" are suitable for marbles or other adornments.
Not so simple explanation: an inner class is declared within the file of another class. There are several levels of inner classes, too, with each level having varying access to the containing class. They're useful mainly for callbacks; if you see someone talking about closures or anonymous blocks, then the closest Java analog is an inner class.
Regards,
-scott anderson
"Welcome to Rivendell, Mr. Anderson..."
|
Post #194,544
2/14/05 10:26:56 AM
|
Wheras an "outer" class is just an ordinary class.
|
Post #194,558
2/14/05 11:44:54 AM
|
There's some other types as well
but it's really just a compiler trick, as all classes will end up having their own .class file, not matter whether they are Outer or Inner (compiler does some name mangling to arrive at unique names). In addition to Inner and Outer classes, there's also anonymous classes and file level classes. Don't know if it helps but here's some toy code: \n// Outer Class declaration\npublic class Outer {\n\n // Outer Class properties/methods\n int propOuter;\n int methOuter(int k) {\n System.out.println("Hello Outer Class method");\n propOuter = k;\n return propOuter;\n }\n\n // Inner Class declaration\n class Inner {\n // Inner Class properties/methods\n int propInner;\n int methInner(int k) {\n System.out.println("Hello Inner Class method");\n propInner = k;\n methOuter(k); // visibility of Outer Class properties/methods\n return propInner;\n }\n }\n\n // Outer Class constructor\n Outer(int k) {\n propOuter = k;\n\n // instantiate an Inner Class from within the Outer Class instance\n Inner inner = new Inner();\n methOuter(5);\n inner.methInner(6);\n\n // do an anonymous class for grins\n Object anon = new Object() {\n int propAnon;\n public String toString() {\n System.out.println("Hello Anonymous Class method");\n propAnon = 2;\n propOuter = 3; // visibility of Outer Class properties/methods\n methOuter(4);\n return "reflection would be better";\n }\n };\n // call method on the anonymous class\n anon.toString();\n }\n\n // main static entry point - do some instantiating\n public static void main(String[] args) {\n // instantiate the Outer Class\n Outer outer = new Outer(0);\n\n // instantiate the FileLevel Class\n FileLevel filelevel = new FileLevel();\n filelevel.methFileLevel(1);\n }\n}\n\n// FileLevel Class declaration\nclass FileLevel {\n // FileLevel Class properties/methods\n int propFileLevel;\n int methFileLevel(int k) {\n System.out.println("Hello FileLevel Class method");\n propFileLevel = k;\n return propFileLevel;\n }\n}\n
|
Post #194,563
2/14/05 12:02:43 PM
|
Also how Java fakes friendship
There are two kinds of inner classes. Regardless of which you use, they are typically constructed by the owning outer class when asked for (see pattern "Factory Method").
The original driver for this monstrosity was (AFAIK) iterators on collections. You need a different implementation for an iterator depending on the kind of datastructure. So an iterator for an array is radically different from one that works on a binary tree. Furthermore, the iterator typically needs access to the guts of the collection that it is iterating. So the approach used was:
class LinkedList implements List { int _count; Node _head;
class ListIterator implements Iterator { Node _current; public boolean hasNext() { return _current._next == _head; } } public Iterator iterator() { return new ListIterator(); } ... }
ListIterator is an inner class - notice that it can see the _head ivar of its owner instance. This is because Java compiler magic inserts a hidden reference to the creating instance (call it "that") and then fixes up all references to the enclosing class's ivars to actually be something like that._head. If you decompile a .class file that has an inner class defined, you'll note a bunch of accessors defined that start with $. These are generated to provide sneaky access to the innards of the owning object - effectively replicating the "friend" declaration from C++.
I dislike this mess because it ends up with hidden costs - the inner class has more storage and the outer class has more methods than one would think from viewing the source code. Furthermore, you don't always need the "that" pointer or access to the enclosing object. Consider the Node structure in the above linked list. You'd like to make that an inner class too. But penalizing it with a "that" pointer is a waste of space. Thus we find YET ANOTHER use for the keyword "static".
class LinkedList implements List { static class Node { public Node _next; public Object _value; } // just a struct
}
which tells the compiler that we won't be accessing our containing object, thanks.
I nearly always make my inner classes static as I dislike compiler magic.
"Whenever you find you are on the side of the majority, it is time to pause and reflect" --Mark Twain
"The significant problems we face cannot be solved at the same level of thinking we were at when we created them." --Albert Einstein
"This is still a dangerous world. It's a world of madmen and uncertainty and potential mental losses." --George W. Bush
|
Post #194,564
2/14/05 12:08:47 PM
|
Inner class is declared inside another class
And "inside" here means "between curlies", not "in the same source file". It has some priviliges accessing its owner's internals. Unless the inner class is delcred "static", it can only be instantiated inside its owner's non-static member function. And it retains a pointer to the owner's instance after instantiation, so you can call owner's members (you can do it w/o any special syntax, just treat owner's member functions as if they are your own). This come in handy for things like callbacks, iterators, event handlers. A version of inner class is "anonymous" class, where you create an instance and define the class at the same time. You can do things like this: \nsomething.addListener(new SomethingListener() {\n public void eventHappened(SomethingEvent event) {\n processSomethingEvent(event);\n }\n}\n "processSomethingEvent()" here is an owner's method.
--
- I was involuntarily self-promoted into management.
[link|http://kerneltrap.org/node/4484|Richard Stallman]
|
Post #194,568
2/14/05 12:46:06 PM
|
Thanks all
I know this is Java baby-talk to most of you, so your patience is appreciated.
Peter [link|http://www.ubuntulinux.org|Ubuntu Linux] [link|http://www.kuro5hin.org|There is no K5 Cabal] [link|http://guildenstern.dyndns.org|Home] Use P2P for legitimate purposes!
|
Post #194,925
2/16/05 7:11:30 AM
|
Re: Thanks all
Well, I was kinda intimidated,
But now we can make the following statement
Outer Class = Regular Class Inner Class = Class defined in another class
Inner classes are used to implement: 1-Callbacks, 2-iterators, 3-closures, 4-anonymous classes, 5-Factory
So now we have 5 more terminology to hunt, I would say Callbacks (the term I kow the least about) is probably the most interesting and hardest to learn
But now we have a new question ... does this mean we can not implement (or implement better), callbacks, iterators, closures, factories without inner classes!
|
Post #194,933
2/16/05 9:23:42 AM
|
You misunderstand, on at least one issue:
"Systems" tries to sum up: Inner classes are used to implement: 1-Callbacks, 2-iterators, 3-closures, 4-anonymous classes, 5-Factory Nope, not #5 -- it's the other way around; the "Factory pattern" is often used to implement inner classes.
[link|mailto:MyUserId@MyISP.CountryCode|Christian R. Conrad] (I live in Finland, and my e-mail in-box is at the Saunalahti company.)
Your lies are of Microsoftian Scale and boring to boot. Your 'depression' may be the closest you ever come to recognizing truth: you have no 'inferiority complex', you are inferior - and something inside you recognizes this. - [link|http://z.iwethey.org/forums/render/content/show?contentid=71575|Ashton Brown]
|
Post #194,935
2/16/05 9:41:31 AM
|
No, they can be used as factories as well.
Spring uses this technique internally to its JDBC classes to provide PreparedStatement factories.
Regards,
-scott anderson
"Welcome to Rivendell, Mr. Anderson..."
|
Post #194,938
2/16/05 10:00:29 AM
|
s/callbacks/delegates/g
Or at least that's what they call them in C#. Java inner classes are a kludge deluxe, but I suppose they are useful in the right circumstances.
|
Post #194,944
2/16/05 10:29:53 AM
|
Yep, one of the uglier useful things in Java. :-P
Regards,
-scott anderson
"Welcome to Rivendell, Mr. Anderson..."
|
Post #194,960
2/16/05 11:37:54 AM
|
ICLRPD (new thread)
Created as new thread #194959 titled [link|/forums/render/content/show?contentid=194959|ICLRPD]
===
Purveyor of Doc Hope's [link|http://DocHope.com|fresh-baked dog biscuits and pet treats]. [link|http://DocHope.com|http://DocHope.com]
|
Post #194,974
2/16/05 12:11:15 PM
|
Great - another perfectly good term hijacked and hosed (new thread)
Created as new thread #194973 titled [link|/forums/render/content/show?contentid=194973|Great - another perfectly good term hijacked and hosed]
"Whenever you find you are on the side of the majority, it is time to pause and reflect" --Mark Twain
"The significant problems we face cannot be solved at the same level of thinking we were at when we created them." --Albert Einstein
"This is still a dangerous world. It's a world of madmen and uncertainty and potential mental losses." --George W. Bush
|
Post #194,943
2/16/05 10:29:47 AM
|
You can't do #3
Inner classes are the closest thing that Java has to closures.
But they aren't closures.
Cheers, Ben
I have come to believe that idealism without discipline is a quick road to disaster, while discipline without idealism is pointless. -- Aaron Ward (my brother)
|
Post #194,948
2/16/05 10:47:59 AM
|
For most uses...
The difference is vanishingly small.
The main difference is that a closure is a function (more or less) and an anonymous inner class is a class. Another difference is that anonymous classes can't change method local variables, but this can be worked around. So while the syntax is definitely not nearly as nice, the functionality is pretty much there.
You might find this interesting as well: [link|http://c2.com/cgi/wiki?BlocksInJava|http://c2.com/cgi/wiki?BlocksInJava]
Regards,
-scott anderson
"Welcome to Rivendell, Mr. Anderson..."
|
Post #194,951
2/16/05 11:02:12 AM
|
I did find that interesting. One major complaint down.
The fact that it is ugly isn't a new complaint. Java generally is ugly, I expect that.
Cheers, Ben
I have come to believe that idealism without discipline is a quick road to disaster, while discipline without idealism is pointless. -- Aaron Ward (my brother)
|
Post #194,977
2/16/05 12:17:55 PM
|
You'll find this interesting too - Bistro
[link|http://bistro.sourceforge.net/overview.htm|http://bistro.source....net/overview.htm]
Its a preprocessor that provides smalltalk-ish syntax and capabilities on top of Java. It generates inner classes for the closures. Its been around for at least 5 years. More of a proof of concept than a practical language - there's no debugger support or anything. But the paper is interesting.
Also, FWIW, Squeak doesn't have proper closures out of the box - it has BlockContext's instead which also don't do a good job of hanging onto local variables properly (there is a workaround you can use but you have to send the thing a message fixTemps to ask it to hang onto the temp vars).
There is a closure implementation done for Squeak, its waiting to be adopted. I think it will end up in there eventually.
"Whenever you find you are on the side of the majority, it is time to pause and reflect" --Mark Twain
"The significant problems we face cannot be solved at the same level of thinking we were at when we created them." --Albert Einstein
"This is still a dangerous world. It's a world of madmen and uncertainty and potential mental losses." --George W. Bush
|
Post #194,976
2/16/05 12:15:19 PM
|
They sorta stink as closures
Not what Ben or Todd would call a "closure" at all.
--
- I was involuntarily self-promoted into management.
[link|http://kerneltrap.org/node/4484|Richard Stallman]
|
Post #195,025
2/16/05 3:07:11 PM
|
You need Jim Weirich's...
...handy dandy [link|http://www.clug.org//ml/archive/programming/1998-11/msg00014.html|Y Combinator in Java].
(Me suspects that's why he switched to Ruby). :-)
|
Post #195,026
2/16/05 3:09:31 PM
|
Without being rude...
...what the devil IS that thing?
Peter [link|http://www.ubuntulinux.org|Ubuntu Linux] [link|http://www.kuro5hin.org|There is no K5 Cabal] [link|http://guildenstern.dyndns.org|Home] Use P2P for legitimate purposes!
|
Post #195,029
2/16/05 3:18:57 PM
2/16/05 3:23:52 PM
|
A recursive anonymous function...
...that takes a function as an argument and returns the function applied to the original function. Or in lambda terminology:
λx.x(λx)
[edit: The Y combinator is a good test of how well closures are implemented].
Edited by ChrisR
Feb. 16, 2005, 03:23:52 PM EST
|
Post #195,031
2/16/05 3:34:25 PM
|
Is it just interesting, or is it useful?
I would imagine such a thing would have to be carefully used if one is to avoid unpleasant runaway circumstances.
Peter [link|http://www.ubuntulinux.org|Ubuntu Linux] [link|http://www.kuro5hin.org|There is no K5 Cabal] [link|http://guildenstern.dyndns.org|Home] Use P2P for legitimate purposes!
|
Post #195,033
2/16/05 4:02:59 PM
|
Well, in Java it's pretty useless.
|
Post #195,039
2/16/05 5:16:04 PM
|
It's a pretty basic feature found in functional languages
like haskell. How useful it would be in the context of a language like java, I don't know. You might make it part of an interpreter for a functional language implemented in java...
--\n-------------------------------------------------------------------\n* Jack Troughton jake at consultron.ca *\n* [link|http://consultron.ca|http://consultron.ca] [link|irc://irc.ecomstation.ca|irc://irc.ecomstation.ca] *\n* Kingston Ontario Canada [link|news://news.consultron.ca|news://news.consultron.ca] *\n-------------------------------------------------------------------
|
Post #195,198
2/17/05 4:17:32 PM
|
Re: You need Jim Weirich's...
> ...handy dandy Y Combinator in Java [*].
Egads, does that bring back memories.
> (Me suspects that's why he switched to Ruby). :-)
Ummm ... My switch happened about 2 years latter, and not because of Y combinators in particular, but the economy of expression was certainly a driving factor (BTW: Y Combinator in Ruby: [link|http://www.rubygarden.org/ruby?TheScaryDoor|http://www.rubygarde...ruby?TheScaryDoor] ... slightly better, although the note that the title of the page is the Scary Door).
-- -- Jim Weirich jim@weirichhouse.org [link|http://onestepback.org|http://onestepback.org] --------------------------------------------------------------------- "Beware of bugs in the above code; I have only proved it correct, not tried it." -- Donald Knuth (in a memo to Peter van Emde Boas)
|
Post #195,229
2/17/05 5:39:07 PM
|
I've just followed the first step of execution
of this thing using the Lisp evaluation model. I can see it working. My head spins. I still don't understand how it works!
--
- I was involuntarily self-promoted into management.
[link|http://kerneltrap.org/node/4484|Richard Stallman]
|
Post #195,291
2/17/05 10:24:40 PM
|
I understand it!
Better yet, I think that I can explain it. This is all much simpler if we allow ourselves functions of 2 variables. It is fairly easy to build a recursive function of one variable starting with functions of 2 variables: \nF = proc { |recurse, n|\n if n == 0 then\n 1\n else\n n * recurse.call(recurse, n-1)\n end\n}\n\nY = proc { |builder, n|\n builder.call(builder, n)\n}\n\np Y.call(F, 5) # prints 120\n The idea being that Y calls F with F as an argument, and that sets up the recursion. You can even write that directly without the temporary variables Y and F just by expanding out Y and F in the expression Y.call(F,5): \np proc {|builder, n|\n builder.call(builder, n)\n }.call(\n proc { |recurse, n|\n if n == 0 then\n 1\n else\n n * recurse.call(recurse, n-1)\n end\n },\n 5\n )\n But a brilliant guy named Haskell Curry has a clever trick, you don't need functions of 2 variables if you have functions that return functions. The translation is perfectly mechanical. The idea is that something like: \nX = proc {|foo, bar|\n ...\n}\nX.call(this, that)\n can always be written as \nX = proc {|foo| proc{ |bar|\n ...\n}}\nX.call(this).call(that)\n where ... remains unchanged. So edit the declaration and the calls to functions of 2 variables, and we only need functions of one variable. Let's apply this mechanical translation to F above. We had: \nF = proc { |recurse, n|\n if n == 0 then\n 1\n else\n n * recurse.call(recurse, n-1)\n end\n}\n Since we now want both recurse and F to be functions of 1 variable, we get the following: \nF = proc { |recurse| proc { |n|\n if n == 0 then\n 1\n else\n n * recurse.call(recurse).call(n-1)\n end\n}}\n And likewise Y becomes: \nY = proc { |builder| proc { |n|\n builder.call(builder).call(n)\n}}\n and our call becomes: \np Y.call(F).call(5)\n Or the complex version with no intermediate variables becomes: \np proc {|builder| proc { |n|\n builder.call(builder).call(n)\n }}.call(\n proc { |recurse| proc { |n|\n if n == 0 then\n 1\n else\n n * recurse.call(recurse).call(n-1)\n end\n }}).call(\n 5\n )\n Which is, modulo formatting, exactly what Jim wrote. That runs, but following the execution should make your head spin... :-) Cheers, Ben
I have come to believe that idealism without discipline is a quick road to disaster, while discipline without idealism is pointless. -- Aaron Ward (my brother)
|
Post #195,296
2/17/05 11:01:29 PM
|
Read it out loud
Remember the old rhyme/mnemonic for spelling Mississippi when you were six? "Em - eye - crooked letter - crooked letter - eye ... crooked letter - crooked letter - eye ... humpback - humpback - eye." Now read this: p proc {|builder| proc { |n|\n builder.call(builder).call(n)
===
Purveyor of Doc Hope's [link|http://DocHope.com|fresh-baked dog biscuits and pet treats]. [link|http://DocHope.com|http://DocHope.com]
|
Post #195,395
2/18/05 1:21:18 PM
|
I detect glazed over eyes
I was going to cement that by writing a version of factorial that just used recursion (without assignment of course!) to reduce everything down to +1, -1. But Ruby lost track of how many braces were open. :-(
There's a moral there somewhere...
Cheers, Ben
I have come to believe that idealism without discipline is a quick road to disaster, while discipline without idealism is pointless. -- Aaron Ward (my brother)
|
Post #195,363
2/18/05 10:19:36 AM
|
This was as brilliant an explanation as I've ever seen
Thank you.
(not that my head spins any less)
--
- I was involuntarily self-promoted into management.
[link|http://kerneltrap.org/node/4484|Richard Stallman]
|