Java generics T vs Object

127

Zastanawiałem się, jaka jest różnica między następującymi dwiema deklaracjami metod:

public Object doSomething(Object obj) {....}

public <T> T doSomething(T t) {....}

Czy jest coś, co możesz / chciałbyś zrobić z jednym, ale nie z drugim? Nie mogłem znaleźć tego pytania w innym miejscu na tej stronie.

Abidi
źródło

Odpowiedzi:

112

W izolacji od kontekstu - bez różnicy. W obu ti objmożesz wywołać tylko metody Object.

Ale z kontekstem - jeśli masz klasę ogólną:

MyClass<Foo> my = new MyClass<Foo>();
Foo foo = new Foo();

Następnie:

Foo newFoo = my.doSomething(foo);

Ten sam kod z obiektem

Foo newFoo = (Foo) my.doSomething(foo);

Dwie zalety:

  • nie ma potrzeby przesyłania (kompilator ukrywa to przed tobą)
  • bezpieczeństwo czasu kompilacji, które działa. Jeśli Objectużywana jest wersja, nie będziesz mieć pewności, że metoda zawsze zwraca Foo. Jeśli zwróci Bar, będziesz mieć ClassCastException, w czasie wykonywania.
Bozho
źródło
14

Różnica polega na tym, że w pierwszym przypadku określamy, że obiekt wywołujący musi przekazać instancję Object (dowolna klasa), a otrzyma z powrotem inny obiekt (dowolną klasę, niekoniecznie tego samego typu).

W drugim zwracany typ będzie tego samego typu, jaki podano przy definiowaniu klasy.

Example ex = new Example<Integer>();

Tutaj określamy, jaki będzie typ T, co pozwala nam wymusić więcej ograniczeń na klasie lub metodzie. Na przykład możemy utworzyć instancję LinkedList<Integer>lub LinkedList<Example>i wiemy, że gdy wywołasz jedną z tych metod, otrzymamy z powrotem instancję typu Integer lub Example.

Głównym celem jest tutaj to, że kod wywołujący może określić typ obiektów, na których będzie działać klasa, zamiast polegać na rzutowaniu typów w celu wymuszenia tego.

Zobacz Java Generics * firmy Oracle.

* Zaktualizowany link.

Adam
źródło
13

Różnica polega na tym, że w przypadku metod ogólnych nie muszę rzutować i otrzymuję błąd kompilacji, gdy robię źle:

public class App {

    public static void main(String[] args) {

        String s = process("vv");
        String b = process(new Object()); // Compilation error
    }

    public static <T> T process(T val) {

        return val;
    }
}

Używając obiektu, który zawsze muszę rzucać i nie dostaję żadnych błędów, gdy robię źle:

public class App {

    public static void main(String[] args) {

        String s = (String)process("vv");
        String b = (String)process(new Object());
    }

    public static Object process(Object val) {

        return val;
    }
}
user1883212
źródło
tylko chciałbym wspomnieć, że nie musisz już rzucać obiektów, od Androida 6.
John Lord
2

Nie musisz robić dodatkowych rzutów klasowych. W pierwszym przypadku zawsze otrzymasz obiekt klasy java.lang.Object, który będziesz musiał przesłać do swojej klasy. W drugim przypadku T zostanie zastąpione klasą zdefiniowaną w sygnaturze ogólnej i nie będzie potrzebne żadne rzutowanie klas.

Andrey Adamovich
źródło
2

W czasie wykonywania nic. Ale w czasie kompilacji drugi sprawdzi typ, aby upewnić się, że typ parametru i typ zwracanej wartości są zgodne (lub są podtypami) dowolnego typu T (pierwszy przykład również sprawdza typ, ale każdy obiekt jest podtyp Object, więc każdy typ zostanie zaakceptowany).

Jonathan
źródło
2

T jest typem ogólnym. Oznacza to, że można go zastąpić dowolnym kwalifikującym się obiektem w czasie wykonywania. Możesz wywołać taką metodę w następujący sposób:

String response = doSomething("hello world");

LUB

MyObject response = doSomething(new MyObject());

LUB

Integer response = doSomething(31);

Jak widać, występuje tu polimorfizm.

Ale jeśli zadeklarowano, że zwraca Object, nie możesz tego zrobić, chyba że wpiszesz rzeczy rzutowane.

adarshr
źródło
Czy możemy powiedzieć, że <T>nie ma autoboxingu?
SMUsamaShah
0

w pierwszym przypadku przyjmuje parametr dowolnego typu egstring i zwraca typ foo. W drugim przypadku przyjmuje parametr typu foo i zwraca obiekt typu foo.

fastcodejava
źródło