Jak ustawić rodzaj zwracanej metody na rodzajowy?

588

Rozważ ten przykład (typowy w książkach OOP):

Mam Animalzajęcia, w których każdy Animalmoże mieć wielu przyjaciół.
I podklasy podoba Dog, Duck, Mouseitp, które jak dodać specyficzne zachowanie bark(), quack()itd.

Oto Animalklasa:

public class Animal {
    private Map<String,Animal> friends = new HashMap<>();

    public void addFriend(String name, Animal animal){
        friends.put(name,animal);
    }

    public Animal callFriend(String name){
        return friends.get(name);
    }
}

A oto fragment kodu z dużą ilością rzutowania:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

((Dog) jerry.callFriend("spike")).bark();
((Duck) jerry.callFriend("quacker")).quack();

Czy jest jakiś sposób, w jaki mogę użyć rodzajów ogólnych dla typu zwracanego, aby pozbyć się rzutowania, aby to powiedzieć

jerry.callFriend("spike").bark();
jerry.callFriend("quacker").quack();

Oto kod początkowy z typem zwracanym przekazywanym do metody jako parametr, który nigdy nie jest używany.

public<T extends Animal> T callFriend(String name, T unusedTypeObj){
    return (T)friends.get(name);        
}

Czy istnieje sposób na określenie typu zwrotu w czasie wykonywania bez użycia dodatkowego parametru instanceof? Lub przynajmniej przekazując klasę typu zamiast fałszywej instancji.
Rozumiem, że ogólne są przeznaczone do sprawdzania typu czasu kompilacji, ale czy istnieje na to obejście?

Satish
źródło

Odpowiedzi:

902

Możesz zdefiniować w callFriendten sposób:

public <T extends Animal> T callFriend(String name, Class<T> type) {
    return type.cast(friends.get(name));
}

Następnie nazwij to tak:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

Zaletą tego kodu jest brak generowania ostrzeżeń kompilatora. Oczywiście jest to tak naprawdę tylko zaktualizowana wersja castingu z dni przed generycznych i nie dodaje żadnego dodatkowego bezpieczeństwa.

Laz
źródło
29
... ale nadal nie ma sprawdzania typu czasu kompilacji między parametrami wywołania callFriend ().
David Schmitt
2
To najlepsza jak dotąd odpowiedź - ale powinieneś zmienić addFriend w ten sam sposób. Utrudnia to pisanie błędów, ponieważ potrzebujesz literału klasy w obu miejscach.
Craig P. Motlin
@Jaider, niezupełnie to samo, ale to zadziała: // Klasa zwierzęca publiczna T CallFriend <T> (nazwa ciągu) gdzie T: Animal {return friends [name] as T; } // Klasa wywołująca jerry.CallFriend <Dog> („spike”). Bark (); jerry.CallFriend <Duck> („quacker”). Quack ();
Nestor Ledon
124

Nie. Kompilator nie może wiedzieć, jaki typ jerry.callFriend("spike")zwróci. Ponadto twoja implementacja po prostu ukrywa obsadę w metodzie bez żadnego dodatkowego bezpieczeństwa typu. Rozważ to:

jerry.addFriend("quaker", new Duck());
jerry.callFriend("quaker", /* unused */ new Dog()); // dies with illegal cast

W tym konkretnym przypadku utworzenie talk()metody abstrakcyjnej i odpowiednie jej zastąpienie w podklasach przydałoby się znacznie lepiej:

Mouse jerry = new Mouse();
jerry.addFriend("spike", new Dog());
jerry.addFriend("quacker", new Duck());

jerry.callFriend("spike").talk();
jerry.callFriend("quacker").talk();
David Schmitt
źródło
10
Chociaż metoda mmyers może działać, myślę, że ta metoda jest lepszym programowaniem OO i pozwoli ci zaoszczędzić trochę kłopotów w przyszłości.
James McMahon
1
To jest właściwy sposób, aby uzyskać ten sam wynik. Zwróć uwagę, że celem jest uzyskanie pochodnego zachowania specyficznego dla klasy w środowisku wykonawczym bez wyraźnego pisania kodu, aby wykonać brzydkie sprawdzanie typu i rzutowanie. Metoda zaproponowana przez @laz działa, ale wyrzuca bezpieczeństwo przez okno. Ta metoda wymaga mniej wierszy kodu (ponieważ implementacje metod są późno powiązane i sprawdzone w czasie wykonywania), ale nadal pozwala łatwo zdefiniować unikalne zachowanie dla każdej podklasy Animal.
dcow
Ale pierwotne pytanie nie dotyczy bezpieczeństwa typu. Sposób, w jaki go czytam, pytający chce tylko wiedzieć, czy istnieje sposób na wykorzystanie generycznych, aby uniknąć konieczności rzucania.
laz
2
@laz: tak, pierwotne pytanie - jak postawiono - nie dotyczy bezpieczeństwa typu. Nie zmienia to faktu, że istnieje bezpieczny sposób, aby to zaimplementować, eliminując awarie rzucania klas. Zobacz także weblogs.asp.net/alex_papadimoulis/archive/2005/05/25/…
David Schmitt
3
Nie zgadzam się z tym, ale mamy do czynienia z Javą i wszystkimi jej decyzjami / wątkami projektowymi. Widzę to pytanie jako próbę nauczenia się, co jest możliwe w generycznych programach Java, a nie jako xyproblem ( meta.stackexchange.com/questions/66377/what-is-the-xy-problem ), który wymaga ponownej inżynierii. Jak każdy wzorzec lub podejście, zdarzają się chwile, kiedy kod, który podałem, jest odpowiedni i czasy, w których wymagane jest coś zupełnie innego (na przykład to, co sugerujesz w tej odpowiedzi).
laz
114

Możesz to zaimplementować w następujący sposób:

@SuppressWarnings("unchecked")
public <T extends Animal> T callFriend(String name) {
    return (T)friends.get(name);
}

(Tak, to jest legalny kod; patrz Java Generics: Typ ogólny zdefiniowany tylko jako typ zwracany ).

Typ zwrotu zostanie wyprowadzony z dzwoniącego. Zwróć jednak uwagę na @SuppressWarningsadnotację: która mówi, że ten kod nie jest bezpieczny . Musisz to zweryfikować samodzielnie, lub możesz uzyskać ClassCastExceptionsw czasie wykonywania.

Niestety, sposób, w jaki go używasz (bez przypisywania wartości zwracanej do zmiennej tymczasowej), jedynym sposobem na zadowolenie kompilatora jest wywołanie go w następujący sposób:

jerry.<Dog>callFriend("spike").bark();

Chociaż może to być nieco ładniejsze niż casting, prawdopodobnie lepiej jest dać Animalklasie abstrakcyjną talk()metodę, jak powiedział David Schmitt.

Michael Myers
źródło
Łączenie metod nie było tak naprawdę intencją. nie mam nic przeciwko przypisywaniu wartości zmiennej Subtyped i używaniu jej. Dzięki za rozwiązanie.
Sathish
działa to doskonale podczas łączenia łańcuchowego metod!
Hartmut P.
Naprawdę podoba mi się ta składnia. Myślę, że w C # to jerry.CallFriend<Dog>(...wydaje mi się, że wygląda lepiej.
andho
Ciekawe, że własna java.util.Collections.emptyList()funkcja środowiska JRE jest implementowana dokładnie w ten sposób, a jego javadoc reklamuje się jako bezpieczny.
Ti Strga
31

To pytanie jest bardzo podobne do punktu 29 w Effective Java - „Rozważ bezpieczne typy heterogenicznych pojemników”. Odpowiedź Laz jest najbliższa rozwiązaniu Blocha. Jednak zarówno put, jak i get powinny używać literału Class dla bezpieczeństwa. Podpisy stałyby się:

public <T extends Animal> void addFriend(String name, Class<T> type, T animal);
public <T extends Animal> T callFriend(String name, Class<T> type);

Wewnątrz obu metod należy sprawdzić, czy parametry są rozsądne. Aby uzyskać więcej informacji, zobacz Efektywna Java i javadoc klasy .

Craig P. Motlin
źródło
17

Dodatkowo możesz poprosić metodę o zwrócenie wartości w danym typie w ten sposób

<T> T methodName(Class<T> var);

Więcej przykładów tutaj w dokumentacji Oracle Java

isuru chathuranga
źródło
17

Oto prostsza wersja:

public <T> T callFriend(String name) {
    return (T) friends.get(name); //Casting to T not needed in this case but its a good practice to do
}

W pełni działający kod:

    public class Test {
        public static class Animal {
            private Map<String,Animal> friends = new HashMap<>();

            public void addFriend(String name, Animal animal){
                friends.put(name,animal);
            }

            public <T> T callFriend(String name){
                return (T) friends.get(name);
            }
        }

        public static class Dog extends Animal {

            public void bark() {
                System.out.println("i am dog");
            }
        }

        public static class Duck extends Animal {

            public void quack() {
                System.out.println("i am duck");
            }
        }

        public static void main(String [] args) {
            Animal animals = new Animal();
            animals.addFriend("dog", new Dog());
            animals.addFriend("duck", new Duck());

            Dog dog = animals.callFriend("dog");
            dog.bark();

            Duck duck = animals.callFriend("duck");
            duck.quack();

        }
    }
webjockey
źródło
1
Co robi Casting to T not needed in this case but it's a good practice to do. Mam na myśli to, że jeśli jest właściwie załatwione w czasie wykonywania, co oznacza „dobra praktyka”?
Farid
Chciałem powiedzieć, że jawne rzutowanie (T) nie jest konieczne, ponieważ deklaracja typu zwracanego <T> w deklaracji metody powinna wystarczyć
webjockey
9

Jak powiedziałeś, zaliczenie zajęć byłoby OK, możesz napisać to:

public <T extends Animal> T callFriend(String name, Class<T> clazz) {
   return (T) friends.get(name);
}

A następnie użyj tego w ten sposób:

jerry.callFriend("spike", Dog.class).bark();
jerry.callFriend("quacker", Duck.class).quack();

Nie jest to idealne rozwiązanie, ale jest tak daleko, jak to możliwe w przypadku generycznych programów Java. Istnieje sposób na zaimplementowanie kontenerów heterogenicznych Typesafe (THC) przy użyciu tokenów supertypowych , ale to znowu ma swoje problemy.

Fabian Steeg
źródło
Przykro mi, ale to jest dokładnie ta sama odpowiedź, którą ma Laz, więc albo go kopiujesz, albo on ciebie.
James McMahon
To fajny sposób na przekazanie Typu. Ale nadal nie jest bezpieczny, jak powiedział Schmitt. Nadal mógłbym przejść inną klasę, a rzutowanie bombarduje. Wydaje się, że mmyers druga odpowiedź, aby ustawić typ zwrotu, wydaje się lepsza
Sathish
4
Nemo, jeśli sprawdzisz czas publikacji, zobaczysz, że opublikowaliśmy je prawie dokładnie w tym samym momencie. Nie są też dokładnie takie same, tylko dwie linie.
Fabian Steeg
@Fabian zamieściłem podobną odpowiedź, ale istnieje istotna różnica między slajdami Blocha a tym, co zostało opublikowane w Effective Java. Używa klasy <T> zamiast TypeRef <T>. Ale to wciąż świetna odpowiedź.
Craig P. Motlin
8

Opierając się na tym samym pomyśle, co tokeny supertypowe, możesz utworzyć identyfikator maszynowy zamiast łańcucha:

public abstract class TypedID<T extends Animal> {
  public final Type type;
  public final String id;

  protected TypedID(String id) {
    this.id = id;
    Type superclass = getClass().getGenericSuperclass();
    if (superclass instanceof Class) {
      throw new RuntimeException("Missing type parameter.");
    }
    this.type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
  }
}

Ale myślę, że może to pokonać cel, ponieważ musisz teraz utworzyć nowe obiekty id dla każdego łańcucha i trzymać się ich (lub zrekonstruować je z poprawnymi informacjami o typie).

Mouse jerry = new Mouse();
TypedID<Dog> spike = new TypedID<Dog>("spike") {};
TypedID<Duck> quacker = new TypedID<Duck>("quacker") {};

jerry.addFriend(spike, new Dog());
jerry.addFriend(quacker, new Duck());

Ale możesz teraz używać tej klasy tak, jak pierwotnie chciałeś, bez rzutów.

jerry.callFriend(spike).bark();
jerry.callFriend(quacker).quack();

To po prostu ukrywa parametr typu wewnątrz identyfikatora, chociaż oznacza to, że możesz później pobrać typ z identyfikatora, jeśli chcesz.

Musisz także zaimplementować metody porównywania i mieszania TypedID, jeśli chcesz mieć możliwość porównania dwóch identycznych instancji identyfikatora.

Mike Houston
źródło
8

„Czy istnieje sposób na określenie typu zwrotu w czasie wykonywania bez dodatkowego parametru za pomocą instanceof?”

Jako alternatywne rozwiązanie można wykorzystać taki wzór użytkownika . Stwórz streszczenie Animal i spraw, by wdrożył:

abstract public class Animal implements Visitable {
  private Map<String,Animal> friends = new HashMap<String,Animal>();

  public void addFriend(String name, Animal animal){
      friends.put(name,animal);
  }

  public Animal callFriend(String name){
      return friends.get(name);
  }
}

Odwiedzalny oznacza po prostu, że realizacja Animal jest gotowa przyjąć gościa:

public interface Visitable {
    void accept(Visitor v);
}

A implementacja odwiedzającego może odwiedzić wszystkie podklasy zwierzęcia:

public interface Visitor {
    void visit(Dog d);
    void visit(Duck d);
    void visit(Mouse m);
}

Na przykład implementacja Dog wyglądałaby następująco:

public class Dog extends Animal {
    public void bark() {}

    @Override
    public void accept(Visitor v) { v.visit(this); }
}

Sztuczka polega na tym, że ponieważ pies wie, jaki to typ, może uruchomić odpowiednią przeciążoną metodę wizyty użytkownika v, przekazując parametr „this”. Inne podklasy implementowałyby accept () dokładnie w ten sam sposób.

Klasa, która chce wywołać metody specyficzne dla podklasy, musi następnie zaimplementować interfejs Visitor w następujący sposób:

public class Example implements Visitor {

    public void main() {
        Mouse jerry = new Mouse();
        jerry.addFriend("spike", new Dog());
        jerry.addFriend("quacker", new Duck());

        // Used to be: ((Dog) jerry.callFriend("spike")).bark();
        jerry.callFriend("spike").accept(this);

        // Used to be: ((Duck) jerry.callFriend("quacker")).quack();
        jerry.callFriend("quacker").accept(this);
    }

    // This would fire on callFriend("spike").accept(this)
    @Override
    public void visit(Dog d) { d.bark(); }

    // This would fire on callFriend("quacker").accept(this)
    @Override
    public void visit(Duck d) { d.quack(); }

    @Override
    public void visit(Mouse m) { m.squeak(); }
}

Wiem, że jest o wiele więcej interfejsów i metod, niż się spodziewałeś, ale jest to standardowy sposób, aby uzyskać kontrolę nad każdym konkretnym podtypem z dokładnie zerowym sprawdzaniem instancji i rzutowaniem typu zero. Wszystko to odbywa się w sposób agnostyczny w standardowym języku, więc nie jest to tylko Java, ale każdy język OO powinien działać tak samo.

Antti Siiskonen
źródło
6

Niemożliwe. Skąd Mapa ma wiedzieć, jaką podklasę Zwierząt otrzyma, mając tylko klucz String?

Jedynym sposobem byłoby to możliwe, gdyby każde zwierzę zaakceptowało tylko jeden typ przyjaciela (wówczas może to być parametr klasy Animal) lub metoda callFriend () otrzyma parametr typu. Ale tak naprawdę wygląda na to, że brakuje Ci punktu dziedziczenia: chodzi o to, że podklasy można traktować jednolicie tylko przy użyciu wyłącznie metod nadklasy.

Michael Borgwardt
źródło
5

Napisałem artykuł, który zawiera dowód koncepcji, klasy wsparcia i klasę testową, która pokazuje, w jaki sposób tokeny supertypowe mogą być pobierane przez twoje klasy w czasie wykonywania. Krótko mówiąc, umożliwia delegowanie do alternatywnych implementacji w zależności od rzeczywistych parametrów ogólnych przekazywanych przez program wywołujący. Przykład:

  • TimeSeries<Double> deleguje się do prywatnej klasy wewnętrznej, która używa double[]
  • TimeSeries<OHLC> deleguje się do prywatnej klasy wewnętrznej, która używa ArrayList<OHLC>

Zobacz: Używanie TypeTokens do pobierania ogólnych parametrów

Dzięki

Richard Gomes - Blog

Richard Gomes
źródło
Rzeczywiście, dziękuję za podzielenie się spostrzeżeniami, twój artykuł naprawdę wszystko wyjaśnia!
Yann-Gaël Guéhéneuc
3

Jest tu wiele świetnych odpowiedzi, ale takie podejście zastosowałem do testu Appium, w którym działanie na jednym elemencie może skutkować przejściem do różnych stanów aplikacji w zależności od ustawień użytkownika. Chociaż nie jest zgodny z konwencjami z przykładu OP, mam nadzieję, że komuś pomoże.

public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //signInButton.click();
    return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
}
  • MobilePage to superklasa, którą rozszerza ten typ, co oznacza, że ​​możesz użyć dowolnego z jego elementów podrzędnych (duh)
  • type.getConstructor (Param.class itp.) pozwala na interakcję z konstruktorem typu. Ten konstruktor powinien być taki sam między wszystkimi oczekiwanymi klasami.
  • newInstance pobiera zadeklarowaną zmienną, którą chcesz przekazać konstruktorowi nowych obiektów

Jeśli nie chcesz wyrzucać błędów, możesz je złapać w następujący sposób:

public <T extends MobilePage> T tapSignInButton(Class<T> type) {
    // signInButton.click();
    T returnValue = null;
    try {
       returnValue = type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return returnValue;
}
MTen
źródło
Według mojego zrozumienia, jest to najlepszy i najbardziej elegancki sposób korzystania z Generics.
cbaldan
2

Nie bardzo, ponieważ, jak mówisz, kompilator wie tylko, że callFriend () zwraca zwierzę, a nie psa lub kaczkę.

Czy nie możesz dodać abstrakcyjnej metody makeNoise () do Animal, która byłaby zaimplementowana jako kora lub szarlatan za pomocą jej podklas?

sk.
źródło
1
co jeśli zwierzęta mają wiele metod, które nawet nie podlegają wspólnemu działaniu, które można wyabstrahować? Potrzebuję tego do komunikacji między podklasami z różnymi działaniami, w których nie przeszkadza przekazywanie typu, a nie instancja.
Sathish
2
Naprawdę właśnie odpowiedziałeś na swoje pytanie - jeśli zwierzę ma wyjątkową akcję, musisz rzucić na to konkretne zwierzę. Jeśli zwierzę ma akcję, którą można zgrupować z innymi zwierzętami, możesz zdefiniować metodę abstrakcyjną lub wirtualną w klasie bazowej i użyć jej.
Matt Jordan
2

To, czego tu szukasz, to abstrakcja. Koduj więcej przeciwko interfejsom i powinieneś robić mniej castingu.

Poniższy przykład jest w języku C #, ale koncepcja pozostaje taka sama.

using System;
using System.Collections.Generic;
using System.Reflection;

namespace GenericsTest
{
class MainClass
{
    public static void Main (string[] args)
    {
        _HasFriends jerry = new Mouse();
        jerry.AddFriend("spike", new Dog());
        jerry.AddFriend("quacker", new Duck());

        jerry.CallFriend<_Animal>("spike").Speak();
        jerry.CallFriend<_Animal>("quacker").Speak();
    }
}

interface _HasFriends
{
    void AddFriend(string name, _Animal animal);

    T CallFriend<T>(string name) where T : _Animal;
}

interface _Animal
{
    void Speak();
}

abstract class AnimalBase : _Animal, _HasFriends
{
    private Dictionary<string, _Animal> friends = new Dictionary<string, _Animal>();


    public abstract void Speak();

    public void AddFriend(string name, _Animal animal)
    {
        friends.Add(name, animal);
    }   

    public T CallFriend<T>(string name) where T : _Animal
    {
        return (T) friends[name];
    }
}

class Mouse : AnimalBase
{
    public override void Speak() { Squeek(); }

    private void Squeek()
    {
        Console.WriteLine ("Squeek! Squeek!");
    }
}

class Dog : AnimalBase
{
    public override void Speak() { Bark(); }

    private void Bark()
    {
        Console.WriteLine ("Woof!");
    }
}

class Duck : AnimalBase
{
    public override void Speak() { Quack(); }

    private void Quack()
    {
        Console.WriteLine ("Quack! Quack!");
    }
}
}
Nestor Ledon
źródło
To pytanie dotyczy kodowania, a nie koncepcji.
2

W moim lib kontraktorze wykonałem następujące czynności:

public class Actor<SELF extends Actor> {
    public SELF self() { return (SELF)_self; }
}

podklasowanie:

public class MyHttpAppSession extends Actor<MyHttpAppSession> {
   ...
}

przynajmniej działa to w bieżącej klasie i przy silnym typowaniu odwołania. Wielokrotne dziedziczenie działa, ale wtedy staje się naprawdę trudne :)

R.Moeller
źródło
1

Wiem, że to zupełnie inna sprawa niż ta, o którą pytał. Innym sposobem rozwiązania tego byłoby odbicie. Mam na myśli, że nie czerpie to korzyści z Generics, ale pozwala w pewien sposób naśladować zachowanie, które chcesz wykonać (zrobić szczekanie psa, kwakanie itp.) Bez konieczności rzucania rzutami:

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

abstract class AnimalExample {
    private Map<String,Class<?>> friends = new HashMap<String,Class<?>>();
    private Map<String,Object> theFriends = new HashMap<String,Object>();

    public void addFriend(String name, Object friend){
        friends.put(name,friend.getClass());
        theFriends.put(name, friend);
    }

    public void makeMyFriendSpeak(String name){
        try {
            friends.get(name).getMethod("speak").invoke(theFriends.get(name));
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    } 

    public abstract void speak ();
};

class Dog extends Animal {
    public void speak () {
        System.out.println("woof!");
    }
}

class Duck extends Animal {
    public void speak () {
        System.out.println("quack!");
    }
}

class Cat extends Animal {
    public void speak () {
        System.out.println("miauu!");
    }
}

public class AnimalExample {

    public static void main (String [] args) {

        Cat felix = new Cat ();
        felix.addFriend("Spike", new Dog());
        felix.addFriend("Donald", new Duck());
        felix.makeMyFriendSpeak("Spike");
        felix.makeMyFriendSpeak("Donald");

    }

}

źródło
1

co powiesz na

public class Animal {
private Map<String,<T extends Animal>> friends = new HashMap<String,<T extends Animal>>();

public <T extends Animal> void addFriend(String name, T animal){
    friends.put(name,animal);
}

public <T extends Animal> T callFriend(String name){
    return friends.get(name);
}

}

gafadr
źródło
0

Istnieje inne podejście, możesz zawęzić typ zwracany, gdy zastąpisz metodę. W każdej podklasie należy zastąpić funkcję callFriend, aby zwrócić tę podklasę. Kosztem byłoby wielokrotne deklaracje callFriend, ale można wyodrębnić wspólne części metody zwanej wewnętrznie. Wydaje mi się to o wiele prostsze niż rozwiązania wspomniane powyżej i nie wymaga dodatkowego argumentu, aby określić typ zwrotu.

FeralWhippet
źródło
Nie jestem pewien, co rozumiesz przez „zawęzić typ zwrotu”. Afaik, Java i większość języków pisanych nie przeciąża metod ani funkcji opartych na typie zwracanych danych. Na przykład public int getValue(String name){}jest nie do odróżnienia z public boolean getValue(String name){}punktu widzenia kompilatorów. Aby rozpoznać przeciążenie, trzeba zmienić typ parametru lub dodać / usunąć parametry. Może po prostu cię źle zrozumiałem ...
The One True Colter
w java możesz przesłonić metodę w podklasie i określić bardziej „wąski” (tj. bardziej szczegółowy) typ zwracany. Zobacz stackoverflow.com/questions/14694852/… .
FeralWhippet
-3
public <X,Y> X nextRow(Y cursor) {
    return (X) getRow(cursor);
}

private <T> Person getRow(T cursor) {
    Cursor c = (Cursor) cursor;
    Person s = null;
    if (!c.moveToNext()) {
        c.close();
    } else {
        String id = c.getString(c.getColumnIndex("id"));
        String name = c.getString(c.getColumnIndex("name"));
        s = new Person();
        s.setId(id);
        s.setName(name);
    }
    return s;
}

Możesz zwrócić dowolny typ i otrzymać bezpośrednio jak. Nie trzeba wybierać tekstu.

Person p = nextRow(cursor); // cursor is real database cursor.

Jest to najlepsze, jeśli chcesz dostosować dowolny inny rodzaj rekordów zamiast prawdziwych kursorów.

użytkownik6083049
źródło
2
Mówisz, że „nie ma potrzeby typowania rzutów”, ale wyraźnie wiąże się to z rzutowaniem typów: (Cursor) cursorna przykład.
Sami Laine,
Jest to całkowicie niewłaściwe użycie leków generycznych. Ten kod musi cursorbyć a Cursori zawsze zwróci Person(lub null). Użycie generics usuwa sprawdzanie tych ograniczeń, czyniąc kod niebezpiecznym.
Andy Turner,