Java - generická třída

Java - generická třída
« kdy: 13. 06. 2011, 20:55:59 »
Zdravím,

mám generickou třídu Vector2D:
Kód: [Vybrat]
public class Vector2D<T extends Number> {

    public T x, y;

    public Vector2D() {
        this.x = (T) (Number) 0;
        this.y = (T) (Number) 0;
    }

    public String getType() {
        if (x instanceof Float) {
            return "Float";
        } else if (x instanceof Integer) {
            return "Integer";
        } else {
            return null;
        }
    }
}

a v metodě main kód:
Kód: [Vybrat]
        Vector2D<Float> vector;
        vector = new Vector2D<Float>();
        System.out.println(vector.getType());

podle mě by se na výpisu mělo objevit: Float,
ale vždy se mi tam objeví: Integer

může mi někdo poradit pls, co dělám špatně?
Nikde na internetu jsem o tom nic nenašel.


Pavel

Re: Java - generická třída
« Odpověď #1 kdy: 13. 06. 2011, 21:54:26 »
Spusť
Kód: [Vybrat]
javap -c Vector2D a uvidíš jak se to přeložilo (vypíše se bajt kód).
Kód: [Vybrat]
Compiled from "Vector2D.java"
public class Vector2D extends java.lang.Object{
public java.lang.Number x;

public java.lang.Number y;

public Vector2D();
  Code:
   0: aload_0
   1: invokespecial #1; //Method java/lang/Object."<init>":()V
   4: aload_0
   5: iconst_0
   6: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   9: putfield #3; //Field x:Ljava/lang/Number;
   12: aload_0
   13: iconst_0
   14: invokestatic #2; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   17: putfield #4; //Field y:Ljava/lang/Number;
   20: return
...
}
Do x i y se ukládá Integer (řádek 6 a 14 se volá Integer.valueOf). Souvisí to s tím, jak jsou v javě implementovaná generika.

alefo

Re: Java - generická třída
« Odpověď #2 kdy: 13. 06. 2011, 22:28:37 »
V Jave platí zásada, že generiká po skompilovaní zabudnú (tzv. type erasure), inými slovami, v bajtkóde sa žiadne údaje o generických typoch neudržíavajú a všade namiesto T si predstavte Object.

Z vašej triedy tak ostane
Kód: [Vybrat]
public class Vector2D {

    public Object x, y;

    public Vector2D() {
        this.x = (Object) (Number) 0;
        this.y = (Object) (Number) 0;
    }

    public String getType() {
        if (x instanceof Float) {
            return "Float";
        } else if (x instanceof Integer) {
            return "Integer";
        } else {
            return null;
        }
    }
}

Ďalej: vaša 0 je literál, ktorý má dátový typ int (po autoboxingu, t. j. automatickej zmene primitívu na objektový typ je to Integer). Vy pretypujete Integer na Number a následne z neznámych dôvodov tento Number na Object. Obe pretypovania sú prakticky zbytočné, lebo pretypovaním nepresvedčíte kompilátor, že váš literál 0 je zrazu float, či double (float literály sa v Jave uvádzajú ako 0.0f).

Vo vašich inštančných premenných máte teda x, y rovné 0 typu int/Integer a bez ohľadu na generikum (ktoré sa zabudne) vidíte na výstupe správnu hodnotu "Integer".

Reálne riešenie: vždy musíte dodať nielen generikum, ale aj konkrétny typ v podobe inštancie Class-u.

Kód: [Vybrat]
public class Vector2D<T extends Number> {

    private T x;
    private T y;
   
    private Class<T> elementType;

    public Vector2D(Class<T> elementType) {
        this.x = (T) 0;
        this.y = (T) 0;
        this.elementType = elementType;
    }
   
    public String getType() {
      return elementType.getName();
    }

alefo

Re: Java - generická třída
« Odpověď #3 kdy: 13. 06. 2011, 22:39:10 »
Ešte sa opravím: tvrdenie
Kód: [Vybrat]
(T) 0 je blbosť, až tak ďaleko autoboxing nesiaha :-) Keďže vieme, že 0 je literál typu int, explicitne vyrobíme z toho Integer a ten pretypujeme na T. Keďže T extends Number a Integer extends Number, pretypovanie je korektné. Kompilátor vyhodí warning ,,Unchecked cast from Integer to T", ale vieme, že to nie je problém. Výsledná beživá trieda:

Kód: [Vybrat]
public class Vector2D<T extends Number> {

    private T x;
    private T y;
   
    private Class<T> elementType;

    public Vector2D(Class<T> elementType) {
        this.x = (T) Integer.valueOf(0);
        this.y = (T) Integer.valueOf(0);;
        this.elementType = elementType;
    }
   
    public String getType() {
      return elementType.getName();
    }
   
    public Class<T> getElementType() {
return elementType;
}
   
    public T getX() {
return x;
}
   
    public T getY() {
return y;
}
   
   
    public static void main(String[] args) {
Vector2D<Float> floatVector = new Vector2D<Float>(Float.class);
System.out.println(String.format("[%s, %s] of %s]", floatVector.getX(), floatVector.getY(), floatVector.getElementType()));
}
}

kuka

Re: Java - generická třída
« Odpověď #4 kdy: 14. 06. 2011, 10:06:21 »
Pretypovanim v jave se instance nemeni, Float z Inetegru takto urcite udelat nelze. Zkuste si schvalne zavolat nejakou metodu na tom, co vraci getX(). Tim pridanim parametru elementType se podarilo pouze vytvorit chybnou tridu, ktera se tvari, ze vraci Float, ale ve skutecnosti vraci Integer. Hezka ukazka toho, ze clovek je chytrejsi nez stroj a s trochou snahy jakekoliv kontrolni mechanismy o kontrolu obejde. A obzvlast me pobavilo

Kompilátor vyhodí warning ,,Unchecked cast from Integer to T", ale vieme, že to nie je problém.

:-)


alefo

Re: Java - generická třída
« Odpověď #5 kdy: 14. 06. 2011, 10:50:29 »
Kód: [Vybrat]
Tim pridanim parametru elementType se podarilo pouze vytvorit chybnou tridu, ktera se tvari, ze vraci Float, ale ve skutecnosti vraci Integer. Ups, máte pravdu, takto to nefunguje. (Pozorujem, že ono to padne až pri vrátení z gettru).

Máte nejaké nápady na vylepšenie? (Implementovať ZeroNumber extends Number, ktorá vracia všade nuly?)

kuka

Re: Java - generická třída
« Odpověď #6 kdy: 14. 06. 2011, 11:20:58 »
Tak především pokud by to byla "normální" třída, tak by měl konstruktor nějaké parametry typu T a nebylo by to třeba vůbec řešit. Pokud má jít o nějaké cvičení na téma "psí kusy s generiky", tak je třeba si uvědomit, že se třídou T pracovat napřímo nelze a typ Number žádnou metodu vracející "nulu" nemá, tedy žádné úplně přímočaré řešení není.

Pokud je množina podporovaných tříd pevně daná (viz javadoc Number), pak si lze snadno poradit přes instanceOf a za běhu vždy vytvořit "správné nuly". Alternativně přes factory, když je množina pevná.
To samozřejmě nebude fungovat pro vlastní rozšíření Number, což ovšem nemusí být na škodu - kdybych si vytvořil třídu VetsiNezSto extends Number, tak ji "automatizovaně" vymyslím jakou nulovou hodnotu? Prostě generika mi pomohou v tom, co mají podporované třídy společného a to v tomto případě nulovost není (alespoň ne na deklarativní úrovni).

Re: Java - generická třída
« Odpověď #7 kdy: 14. 06. 2011, 19:58:38 »
Takže jestli to dobře chápu, tak potřebuju předat parametr typu T každé metodě (včetně konstruktoru), ve které chci zjistit typ třídy, nebo předat konkrétní typ v podobě instance Class-u konstruktoru.

a kdybych chtěl sčítat vektory, šlo by to třeba takhle:
Kód: [Vybrat]
...

public Vector2D<T> add(Vector2D<T> v) {
        T x = add(this.x, v.getX());
        T y = add(this.y, v.getY());
        return new Vector2D<T>(x, y);
    }

    public T getX() {
        return x;
    }

    public T getY() {
        return y;
    }

    private T add(T t1, T t2) {
        Number n1 = (Number) t1;
        Number n2 = (Number) t2;
        if (t1 instanceof Integer) {
            return (T) (Number) (n1.intValue() + n2.intValue());
        } else if (t1 instanceof Float) {
            return (T) (Number) (n1.floatValue() + n2.floatValue());
        } else if (t1 instanceof Double) {
            return (T) (Number) (n1.doubleValue() + n2.doubleValue());
        } else {
            return null;
        }
    }
...

kuka

Re: Java - generická třída
« Odpověď #8 kdy: 14. 06. 2011, 23:33:32 »
Predevsim to neustale pretypovavani tam nemusi byt a je matouci. Pretypovavat T na Number neni treba, kdyz se pro celou tridu deklarovalo, ze T je potomek Number, a zbytecnost pretypovani T na T snad neni treba vysvetlovat. Podobne Integer nemusim pretypovavat na Number, kompilator zna tento vztah i bez toho.

Number nezarucuje zadnou jednotnou operaci "add", takze pri jeji implementaci generika tezkou mohou rozumne poslouzit. Pokud budes "umet" jen Integer, Float a Double, tak je nestastne tvarit se jako ze je to obecne pro Number a udelal bych tri tridy IntegerVector2D, FloatVector2D atd. Ty mohou mit spolecneho generickeho predka, ve kterem se naimplementuji operace, ktere jsou opravdu shodne pro vsechny ciselne typy, napriklad prohozeni prvku, transformace na vektor inetegeru (Number deklaruje metodu intValue) apod.

Re: Java - generická třída
« Odpověď #9 kdy: 15. 06. 2011, 05:12:43 »
Aha, tak už to snad chápu. Díky moc všem.

hm

Re: Java - generická třída
« Odpověď #10 kdy: 15. 06. 2011, 12:41:19 »
imho bych generickou tridu na tohle nedelal

Re: Java - generická třída
« Odpověď #11 kdy: 15. 06. 2011, 15:39:15 »
jj, už si to také myslím - s programováním teprv začínám, takže moje nápady nejsou vždy nejlepší, asi ten Vector udělám jenom float :)

hm

Re: Java - generická třída
« Odpověď #12 kdy: 15. 06. 2011, 17:57:20 »
ja myslel spise neco ve stylu

class MujVector{
Number x;
Number y;
public MujVector(Number x,Number y){
this.x=x;
this.y=y;
}

}.
.
.
ArrayList<MujVektor> = new ArrayList<MujVektor>();

natix

Re: Java - generická třída
« Odpověď #13 kdy: 15. 06. 2011, 21:57:57 »
NenadalM:
Pokud chceš v tom vektoru mít reálná čísla, použij místo floatu double. Float nemá vůbec smysl používat, 32 bitů je pro reálné číslo hrozně málo. Při přetypování z taky 32 bitového intu ve většině případů (s výjimkou malých čísel) nezískáš to stejné číslo, ale o kus jiné - čili vlastně přetypováním na typ, který by měl mít na první pohled mít větší přesnost, naopak přesnost ztrácíš.

Re: Java - generická třída
« Odpověď #14 kdy: 15. 06. 2011, 23:30:14 »
Zatim ten vektor mám jenom na souřadnice v 2D hrách, tam by float měl stačit.