views:

110

answers:

6

How to access i from the outer class?

  HashSet<Integer> hs=new HashSet<Integer>(){
        int i=30;
    };

I can do it like this

int k=new HashSet<Integer>(){
    int i=30;
}.i;

But if I get 'i' then I cannot get the instance of hashset.Is there a way to get both?The question just out of curiosity.It doesn't have much practical applications.I just want to know whether it can be done.

A: 

Well, to my mind, there is always the reflection way, but it seems a little overkill to me.

And as you set the class to be anonymous way, I cannot see any other way to do it than yours.

Riduidel
+1  A: 

I don't believe you can. You can't declare the type of a variable as an anonymous type1. You can introduce a named class within a method though:

import java.util.*;

public class Test
{
    public static void main(String[] args)
    {
        class Foo extends Hashtable
        {
            int i = 30;
        }

        Foo f = new Foo();
        System.out.println(f.i);
    }
}

It's horrible (really, really horrible) and I don't think I've ever seen it used in production code - but it works.


1 C# gets around this using var - the compiler uses the initialization expression to determine the variable's type. Anonymous types in Java are somewhat different to those in C#, but if Java had something like var, I believe your code would work.

Jon Skeet
I've used such "local classes" when refactoring old code with very long methods containing a large number of local variables. A local class lets you capture those (final) variables without having to pass them all to a nested static class constructor. Other than that, though, I've never seen or used them.
skaffman
A: 

Annonymous inner classes weren't designed for that purpose, so from what I remember there's no way you could do that. If you want you can create a "normal" inner class.

Zenzen
+2  A: 

It makes sense when you think about it.

The expression

new HashSet<Integer>(){
    int i=30;
}

is of type java.util.HashSet, and java.util.HashSet has no field called i, so

new HashSet<Integer>(){
   int i=30;
}.i

will not compile. Put another way, try breaking up the compound expression so the the result of the new is assigned to a variable x, then you call x.i. What type could you give x that would allow x.i to compile? There is no such type name, it's an anonymous class.

This is a "downside" on anonymous classes - they can only meaningfully override members that exist in the type being extended. If you add new members, they can only be accessed via reflection.

skaffman
+5  A: 

Reflection to the rescue, I guess:

HashSet<Integer> hs = new HashSet<Integer>() {
    int i = 30;
};
int i = hs.getClass().getDeclaredField("i").getInt(hs);
System.out.println(i);

P.S. I feel kind of dirty after writing that.

Andrei Fierbinteanu
No need to feel dirty.I've up-voted for you.How do you feel now?Thanks I just wanted to know if it is possible in some way.I'll accept your answer if no one else give's a better one.
Emil
Thanks! It's just that it's an abuse of the language, and you should probably never write something like this in production code. So I guess you could take it as an example of what not to do.
Andrei Fierbinteanu
@Anderi:I accepted your answer first.But Poly's giving much crazier answer's than I expected.Since this is a crazy question.I think crazy answer's are the best.
Emil
+4  A: 

Solution 1: Object registration system

The two snippets presented essentially made a choice between storing the reference to the anonymous class instance just created, or to immediately access its custom field. Doing one would seem to forfeit the ability to do the other.

One way to achieve a compromise is to have an object registration system, and use an instance initializer (JLS 8.6) in your anonymous class to self-register to this sytem.

Here's a simple proof-of-concept, using Object[] for a simple registration system:

    final Object[] registrar = { null };
    int k = new HashSet<Integer>(){
        { registrar[0] = this; }
        int i= 666;
    }.i;
    Set<Integer> set = (Set<Integer>) registrar[0];

    System.out.println(k); // 666
    System.out.println(set.getClass().isAnonymousClass()); // true

Essentially we use a 1-element Object[], and in the instance initializer of our anonymous class, we "register" this to this array. Note that this is just a proof-of-concept; in production code, you'd use a more robust and typesafe object registration system than this.


Solution 2: Tuples

This approach works nicely if your anonymous class may have multiple "fields". Simply pack them all into a tuple, making sure to include a reference to this in the anonymous class.

Here's a simple proof-of-concept, using Object[] for a tuple:

    Object[] tuple = new HashSet<Integer>(){
        Object[] tuple = { this, 666, 13, "XXX", "OMG ABUZE" };
    }.tuple;
    Set<Integer> set = (Set<Integer>) tuple[0];
    set.add(null);
    System.out.println(set.getClass().isAnonymousClass()); // true
    System.out.println(Arrays.toString(tuple));
    // [[null], 666, 13, XXX, OMG ABUZE]

In production code, you'd use a CustomTuple class that is more object-oriented and typesafe. Note that this is an analogous solution to the "how can my method return 2 values" problem: simply return 1 value that captures all of those information.

polygenelubricants
That was great.I was simply playing with Java.I wouldn't do it in production code.
Emil
@Emil: Thanks for the accept, it lead me to revise my answer to bring it up to standard. I now think either solution is pretty nice, and quite idiomatic.
polygenelubricants
+1 Wow, your answers are always amazing. "OMG ABUZE" indeed :)) Mindsplosion.
Andrei Fierbinteanu