Avatar billede jakobgt Nybegynder
26. januar 2009 - 01:23 Der er 11 kommentarer og
1 løsning

subtilt newInstance() problem ved anonyme klasser.

Hej,

Jeg har et problem ved brugen af newInstance og anonyme klasser. Hvis jeg opretter den anonyme klasse i den statiske main funktion, finder dens Class (vha. getClass()) og derefter kalder newInstance på den er der ingen problemer. Hvis jeg til gengæld opretter et object først, kalder en metode på dette objekt, hvori jeg opretter en anonym klasse og gentager samme procedure, får jeg en InstantiationException.
Noget kode til at demonstrere det:



public class Tester {
    public static void main(String[] main) {
        //Dette virker fint, ingen problemer
        Object scriptss = new Object()
        {
        };
        Class pp = scriptss.getClass();

        Object ss = null;
        try {
            ss = pp.newInstance();
            System.out.println("Dette virker fint. Ingen problemer.");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }   
        //Her faar vi et problem, da vi nu oprettet et objekt.
        Tester ll = new Tester();
        ll.foo();
    }
   
    public void foo() {
        Object scriptss = new Object()
        {
        };
        Class pp = scriptss.getClass();

        Object ss = null;
        try {
            ss = pp.newInstance();
            System.out.println("Saa langt kommer vi dog ikke, for newInstance raiser en InstantiationException.");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }   
    }
   
}

Nogen, som har en forklaring? Og hvordan man kommer forbi det?
Avatar billede jakobgt Nybegynder
26. januar 2009 - 01:27 #1
Der er faktisk forskel på den generede kode for de 2 indre klasser:
Den første, som kommer fra Main metoden ser således ud:
class Tester$1
{

    Tester$1()
    {
    }
}

For den indre klasse som ligger i foo, ser det til gengæld således ud:
class Tester$2
{

    final Tester this$0;

    Tester$2()
    {
        this$0 = Tester.this;
        super();
    }
}

Kan det være noget med at super() kalder skal være øverst i constructor kaldet?
Avatar billede arne_v Ekspert
26. januar 2009 - 01:51 #2
Årsagen ses nok tydeligere ved den her variant:

public class Tester2 {
    public static class Foo {
    }
    public class Bar {
    }
    public static void main(String[] main) {
        //Dette virker fint, ingen problemer
        Object scriptss = new Foo();
        Class pp = scriptss.getClass();

        Object ss = null;
        try {
            ss = pp.newInstance();
            System.out.println("Dette virker fint. Ingen problemer.");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } 
        //Her faar vi et problem, da vi nu oprettet et objekt.
        Tester2 ll = new Tester2();
        ll.foo();
    }
 
    public void foo() {
        Object scriptss = new Bar();
        Class pp = scriptss.getClass();

        Object ss = null;
        try {
            ss = pp.newInstance();
            System.out.println("Saa langt kommer vi dog ikke, for newInstance raiser en InstantiationException.");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } 
    }
 
}
Avatar billede arne_v Ekspert
26. januar 2009 - 01:52 #3
Samme fejl, men forskellen mellem Foo og Bar er tydelig.
Avatar billede arne_v Ekspert
26. januar 2009 - 01:54 #4
Ingen sammenhæng mellem foo og Foo.
Avatar billede arne_v Ekspert
26. januar 2009 - 02:08 #5
En mulig løsning ses her:

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Tester3 {
    public static void main(String[] main) {
        //Dette virker fint, ingen problemer
        Object scriptss = new Object()
        {
        };
        Class pp = scriptss.getClass();

        Object ss = null;
        try {
            ss = pp.newInstance();
            System.out.println("Dette virker fint. Ingen problemer.");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } 
        //Her faar vi et problem, da vi nu oprettet et objekt.
        Tester3 ll = new Tester3();
        ll.foo();
    }
 
    public void foo() {
        Object scriptss = new Object()
        {
        };
        Class pp = scriptss.getClass();

        Object ss = null;
        try {
            Constructor m = pp.getDeclaredConstructor(new Class[] { Tester3.class });
            ss = m.newInstance(new Object[] { this });
            System.out.println("Saa langt kommer vi dog ikke, for newInstance raiser en InstantiationException.");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } 
    }
 
}
Avatar billede arne_v Ekspert
26. januar 2009 - 02:08 #6
Det ses at en ikke-static inner class har et ekstra argument til constructor.
Avatar billede jakobgt Nybegynder
26. januar 2009 - 13:25 #7
Okay, så det virker tilsyneladende som om at ved instantieringen af en non-static inner class, skal den omgivende klasse/objekt med som argument. Giver mening når man tænker på closures. Tester lige om din løsning virker.
Avatar billede jakobgt Nybegynder
26. januar 2009 - 13:51 #8
Det virker, gider du smide et svar. I tilfælde af at andre er min i situation, hvor classen skal instantieres et sted i koden, hvor man ikke "ved" hvilke klasse, som var den ydre klasse, kan man bruge det følgende:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;




public class Tester {
    public static void main(String[] main) {
        //Dette virker fint, ingen problemer
        Object scriptss = new Object()
        {
        };
        Class pp = scriptss.getClass();

        Object ss = null;
        try {
            ss = pp.newInstance();
            System.out.println("Dette virker fint. Ingen problemer.");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }   
        Tester ll = new Tester();
        ll.foo();
    }
   
    public void foo() {
        Object scriptss = new Object()
        {
        };
        Class pp = scriptss.getClass();

        Object ss = null;
        try {
            //Da jeg ikke ved hvad typen, parameterne har på constructoren, vælger jeg blot den
            //første
            Constructor[] cs = pp.getDeclaredConstructors();
            Constructor m = cs[0];
            ss = m.newInstance(new Object[] { this });
            System.out.println("Nu virker det også.");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }   
    }
   
}
Avatar billede jakobgt Nybegynder
26. januar 2009 - 14:04 #9
Hov, jeg var for hurtig der. Koden skulle ha' været:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;




public class Tester {
    public static void main(String[] main) {
        //Dette virker fint, ingen problemer
        Object scriptss = new Object()
        {
        };
        Class pp = scriptss.getClass();

        Object ss = null;
        try {
            ss = pp.newInstance();
            System.out.println("Dette virker fint. Ingen problemer.");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        }   
        Tester ll = new Tester();
        ll.foo();
    }
   
    public void foo() {
        Object scriptss = new Object()
        {
        };
        Class pp = scriptss.getClass();

        Object ss = null;
        try {
            //Da jeg ikke ved hvad typen, parameterne har på constructoren, vælger jeg blot den
            //første
            Constructor[] cs = pp.getDeclaredConstructors();
            Constructor m = cs[0];
            //Får dernæst fat på typerne af parameterne
            Class<?>[] pst = m.getParameterTypes();
            ss = m.newInstance(new Object[] { pst[0].newInstance()});
            System.out.println("Nu virker det også.");
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }   
    }
   
}
Avatar billede arne_v Ekspert
27. januar 2009 - 03:14 #10
Jeg vil nok anbefale at man forsøger at undgå at have behovet - det er ikke specielt
klar kode.

Og et svar.
Avatar billede jakobgt Nybegynder
27. januar 2009 - 12:51 #11
Det er rigtigt at du mister den statiske typecheckning, men der er dog visse tilfælde hvor du ikke kan komme udenom. F.eks. i mit tilfælde, hvor jeg vil dynamisk instantierer Java klasser, som implementerer et bestemt interface. Disse klasser kan både være almindelige, indre og anonyme klasser. Som jeg ser det er der ingen vej udenom end koden ovenfor.

En lille ting mere til løsningen: hvis den anonyme klasse og klassen som instantierer den ligger i 2 forskellige pakker, virker ovenstående kode ikke (man får en IllegalAccessException), da anonyme klasser pr. default er package private. Det kan ændres vha. flg. kode:

if (!Modifier.PUBLIC.equals(m.getModifiers()))
    Constructor.setAccessible(new AccessibleObject[] {m}, true);

Man ændrer simpelthen access niveauet på constructoren til public. Grimt, men virker.
Avatar billede jakobgt Nybegynder
27. januar 2009 - 12:51 #12
Tak forresten. :-)
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester