Rzutowanie na tablicę typu uogólnionego?

0

Proszę, niech mi to ktoś wytłumaczy, bo bladego pojęcia nie mam co powstaje i czemu to w ogóle jest możliwe:

import java.io.*;
public class Main <T> {
	T tab[];
	Main(int size){
		tab = (T[]) new Reader[size];
	}
	public static void main(String args[]){
		Main<Double> tab = new Main<>(2);
	}
}

Według powyższego powstaje coś takiego:

import java.io.*;
public class Main {
	Double tab[];
	Main(int size){
		tab = (Double[]) new Reader[size];
	}
	public static void main(String args[]){
		Main tab = new Main(2);
	}
}

tylko, że to już nie chce się kompilować, bo niezgodność typów.

1

Nieprawda, powstaje coś takiego:

import java.io.*;
public class Main {
    Object tab[];
    Main(int size){
        tab = (Object[]) new Reader[size];
    }
    public static void main(String args[]){
        Main tab = new Main(2);
    }
}

W javie jest coś takiego, jak type erasure - typy generyczne istnieją wyłącznie na etapie kompilacji danej jednostki kodu. W kodzie wynikowym wszystkie odwołania do T zostają zamienione na Object.

  1. Kompilacja klasy Main<T> - wewnątrz niej zapis (T[]) new Reader[size] może być poprawny. A jeśli używasz ręcznego rzutowania, to to jest jak powiedzenie: droga Javo, wiem co robię i biorę na siebie upewnienie się, że to rzutowanie będzie działać. Użycie argumentu T jest zatem poprawne i klasa jest skompilowana poprawnie.
  2. Później robisz Main<Double> main - cóż, użycie jest poprawne. Kompilator nie patrzy już do środka Main czy ten Double czegoś nie zepsuje, bo tam ta informacja jest już wymazana. Inna sprawa, że w przypadku tamtego problematycznego rzutowania sam powiedziałeś kompilatorowi wcześniej, że bierzesz problemy na siebie.

Weź pod uwagę, że użycie typu generycznego nie jest równoznaczne z zapisaniem określonego typu ręcznie w kodzie. A najprostszy przykład to to, że przy pomocy typów generycznych da się zmusić kompilator do rzucenia wyjątku Exception bez deklarowania go... :)

1

Zamień kod na

import java.io.*;
public class Main <T> {
    T tab[];
    Main(int size){
        tab = (T[]) new Reader[size];
        System.out.println(tab);
    }

    public T[] getTab() {
        return tab;
    }

    public static void main(String args[]){
        Main<Double> tab = new Main<>(2);
        Double[] tab1 = tab.getTab();
        System.out.println(tab1.getClass());
    }
}

i obserwuj. W konstruktorze rzutujesz Reader na typ generyczny T, co kompilator sprowadza do rzutowania na Object. Następnie w metodzie main tworzysz obiekt tab, i w nim siedzi sobie tab typu Reader[] okrojona do API Object[]. Następnie wywołujesz getter i w tym momencie kompilator wstawił ci niejawne rzutowanie na Double[], bo dopiero w tym miejscu wie jakiego konkretnie typu się spodziewasz. Efekt wiadomy ClassCastException.

1

tylko, że to już nie chce się kompilować, bo niezgodność typów.

Jeśli kompilator wie że dane rzutowanie się nie powiedzie to kod się nie skompiluje. W innych przypadkach jest różnie.

W takim kodzie:

tab = (Double[]) new Reader[size];

od razu widać, że rzutowanie się nie powiedzie.

1

@BitemNet, odnośnie Twojego komentarza. Przeczytaj jeszcze raz moją poprzednią odpowiedź i zwróć uwagę na zwrot type erasure.

Ja w niej wskazywałem, że Reader ma być rzutowany na Double: Main<Double> tab = new Main<(2); ?

Nic takiego nie wskazałeś. Ten Double nie ma tutaj nic wspólnego z tym, co się dzieje wewnątrz Main. Dlaczego? Type erasure - w momencie użycia Java nie sprawdza już poprawności typów wewnątrz klasy Main, bo nie może tego zrobić, bo tam z definicji T jest już wymazane i zredukowane do typu Object. Rozumiem, co chcesz zrobić, ale po prostu Java tak nie działa - nie da się tego zrobić.

Twój zapis Main<Double> tak naprawdę nie robi nic, bo żeby miał on jakiekolwiek znaczenie, musiałbyś ten typ T wstawić w argumencie metody, jako typ zwracanej wartości, albo jako rzucany wyjątek, np.

import java.io.*;
public class Main <T> {
    T tab[];
    Main(int size){
        tab = (T[]) new Reader[size];
    }
   
   public void foo(T arg) {
      tab = (T[]) new Reader[size]; // popatrz na to
   }

    public static void main(String args[]){
        Main<Double> tab = new Main<>(2);
        tab.foo(3.14); // <<<---- tu kompilator może coś z tym Twoim `Double` zrobić
    }
}

I w tym wypadku faktycznie kompilator używa informacji, że jest typ Main<Double> do sprawdzenia, że wywołanie metody tab.foo() faktycznie przyjmuje Double jako argument. Ale znowu - sprawdzi Ci wywołanie tej metody, ale zakłada z góry, że wnętrze (oznaczone komentarzem popatrz na to) jest prawidłowe.

1 użytkowników online, w tym zalogowanych: 0, gości: 1