Как сделать метод, возвращающий тип, универсальным? - PullRequest
526 голосов
/ 16 января 2009

Рассмотрим этот пример (типично для книг ООП):

У меня есть Animal класс, где у каждого Animal может быть много друзей.
И подклассы типа Dog, Duck, Mouse и т. Д., Которые добавляют специфическое поведение, например bark(), quack() и т. Д.

Вот класс Animal:

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);
    }
}

А вот фрагмент кода с большим количеством типов:

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

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

Можно ли как-нибудь использовать обобщенные типы для возвращаемого типа, чтобы избавиться от приведения типов, чтобы я мог сказать

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

Вот некоторый начальный код с типом возврата, переданным методу в качестве параметра, который никогда не используется.

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

Есть ли способ выяснить тип возвращаемого значения во время выполнения без дополнительного параметра, использующего instanceof? Или, по крайней мере, передав класс типа вместо фиктивного экземпляра.
Я понимаю, что дженерики предназначены для проверки типов во время компиляции, но есть ли обходной путь для этого?

Ответы [ 19 ]

5 голосов
/ 07 апреля 2009

Я написал статью, которая содержит доказательство концепции, вспомогательные классы и тестовый класс, который демонстрирует, как супер-токены могут быть получены вашими классами во время выполнения. В двух словах, он позволяет вам делегировать альтернативные реализации в зависимости от фактических общих параметров, передаваемых вызывающей стороной. Пример:

  • TimeSeries<Double> делегат закрытого внутреннего класса, который использует double[]
  • TimeSeries<OHLC> делегатов в закрытый внутренний класс, который использует ArrayList<OHLC>

См: Использование TypeTokens для получения общих параметров

Спасибо

Ричард Гомес - Блог

2 голосов
/ 30 июня 2017

Здесь есть много хороших ответов, но именно этот подход я выбрал для теста Appium, когда действие над одним элементом может привести к переходу в разные состояния приложения в зависимости от настроек пользователя. Хотя это не соответствует соглашениям примера OP, я надеюсь, что это кому-то поможет.

public <T extends MobilePage> T tapSignInButton(Class<T> type) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    //signInButton.click();
    return type.getConstructor(AppiumDriver.class).newInstance(appiumDriver);
}
  • MobilePage - это суперкласс, который расширяет тип, что означает, что вы можете использовать любого из его дочерних элементов (дух)
  • type.getConstructor (Param.class и т. Д.) Позволяет вам взаимодействовать с конструктор типа. Этот конструктор должен быть одинаковым для всех ожидаемых классов.
  • newInstance принимает объявленную переменную, которую вы хотите передать конструктору новых объектов

Если вы не хотите выдавать ошибки, вы можете их перехватить так:

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;
}
2 голосов
/ 21 мая 2014

То, что вы ищете здесь, это абстракция. Кодируйте больше интерфейсов, и вам придется меньше кастовать.

Пример ниже приведен на C #, но концепция остается той же.

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!");
    }
}
}
2 голосов
/ 16 января 2009

Не совсем, потому что, как вы говорите, компилятор знает только, что callFriend () возвращает Animal, а не Dog или Duck.

Не можете ли вы добавить абстрактный метод makeNoise () в Animal, который будет реализован в виде лая или кряка его подклассами?

1 голос
/ 25 октября 2013

Я знаю, что это совершенно другое, что тот спросил. Другим способом решения этой проблемы было бы отражение. Я имею в виду, что это не использует преимущества Generics, но позволяет вам каким-то образом эмулировать поведение, которое вы хотите выполнить (собачий лай, крякание утки и т. Д.), Не заботясь о приведении типа:

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");

    }

}
1 голос
/ 28 июня 2015

В моем lib kontraktor я сделал следующее:

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

подклассы:

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

по крайней мере, это работает внутри текущего класса и при наличии строго типизированной ссылки. Многократное наследование работает, но потом становится очень сложно:)

1 голос
/ 06 февраля 2015

а как же

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);
}

}

0 голосов
/ 08 мая 2017

Существует другой подход, вы можете сузить тип возвращаемого значения, когда вы переопределяете метод. В каждом подклассе вы должны переопределить callFriend, чтобы вернуть этот подкласс. Стоимость будет состоять из нескольких объявлений callFriend, но вы можете выделить общие части для метода, вызываемого внутри. Это кажется мне намного проще, чем решения, упомянутые выше, и не требует дополнительного аргумента для определения типа возвращаемого значения.

0 голосов
/ 18 марта 2016
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;
}

Вы можете вернуть любой тип и получить непосредственно как. Не нужно печатать.

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

Лучше всего, если вы хотите настроить другие записи вместо реальных курсоров.

...