Динамическое приведение в ошибке компилятора Java - PullRequest
1 голос
/ 10 августа 2011

У меня есть 3 класса A, B и C. Единственное, что у них общего, - это функция getName (), ничего больше.

У меня есть массив типа Class, в котором хранятся вышеуказанные классы.

Class[] classes = {A.class,B.class,C.class};

У меня есть список, содержащий объекты типов A, B и C

List<?> list = new ArrayList<?>();
list.add(new A());
list.add(new B());
list.add(new C());

Я хочу получить доступ к объектам в списке через цикл.Я также хочу получить доступ к методам объектов A, B и C. Итак, я пытаюсь привести их с помощью массива классов.

for(int i = 0; i < classes.length;i++)
{
    classes[i].cast(list.get(i)).getName(); //A,B,and C have method getName() in common
}

Это дает мне ошибку компилятора: «Метод getName ()не определено для типа Object

Есть ли способ для компилятора игнорировать этот тип приведения до времени выполнения (я уверен, что он будет работать во время выполнения)? Или есть другое решение, которое делает то, что я ожидаю (яищем решение, которое не предполагает изменения кода классов A, B и C)

Ответы [ 7 ]

4 голосов
/ 10 августа 2011

Вы должны реализовать интерфейс с методом getName(), назовем его Nameable:

public interface Nameable {

    public String getName();
}

Затем эти три класса A B и C реализуют Nameable,Это позволит вам объявить ваш список как List<Nameable>, и при вызове этого метода при обращении к его элементам не потребуется приведения.

РЕДАКТИРОВАТЬ: Хорошо, так как вы не можете изменитьэти классы, которые вы могли бы делать что-то похожее на то, что предлагает @JB Nizet.Однако не ищите динамически метод внутри цикла, так как это дорого.Статически ищите эти методы один раз для каждого класса и храните их в Map, где ключом является Class:

private static final Map<Class<?>, Method> getNameMap = new HashMap<Class<?>, Method>();
static {
    try {
        getNameMap.put(A.class, A.class.getMethod("getName"));
        getNameMap.put(B.class, B.class.getMethod("getName"));
        getNameMap.put(C.class, B.class.getMethod("getName"));
    }
    catch (NoSuchMethodException nsme) {
        //handle method not being found
    }
}

Затем найдите метод из цикла:

for (Object obj : list) {
    try {
        Class<?> objClass = obj.getClass();
        Object returned = getNameMap.get(objClass).invoke(obj);
        String name = (String)returned; //I'm just assuming these methods return a String that you want to use for something
    }
    catch (IllegalAccessException iae) {
        //handle method not being accessable
    }
    catch (InvocationTargetException ite) {
        Throwable cause = ite.getCause();
        //handle cause exception
    }
}
2 голосов
/ 10 августа 2011

Ответом является использование оператора instanceof .Это кажется разумным, если классы в Списке известны.Вот некоторый код:

    public static void main(String[] args)
    {
        A aObject;
        B bObject;
        C cObject;
        List list = new ArrayList();

        list.add(new A());
        list.add(new B());
        list.add(new C());
        list.add(new String());

        for (Object current : list)
        {
            if (current instanceof A)
            {
                aObject = (A)current;
                System.out.println(aObject.getName());
            }
            else if (current instanceof B)
            {
                bObject = (B)current;
                System.out.println(bObject.getName());
            }
            else if (current instanceof C)
            {
                cObject = (C)current;
                System.out.println(cObject.getName());
            }
            else
            {
                System.out.print("Unexpected class: ");
                System.out.println(current.getClass());
            }
        }
    }

Вот (немного) более подробный (чем в других ответах) пример того, как сделать это с помощью Reflection.Это кажется разумным, если классы в Списке неизвестны или их много.В закомментированной строке ниже используется метод MethodUtils класса Apache Commons Lang.

    public static void main(String[] args)
    {
        List list = new ArrayList();
        Method getNameMethod;

        list.add(new A());
        list.add(new B());
        list.add(new C());
        list.add(new String());

        for (Object current : list)
        {
//          getNameMethod = MethodUtils.getAccessibleMethod(current.getClass(), "getName", (Class[])null);
            try
            {
                getNameMethod = current.getClass().getMethod("getName");
            }
            catch (SecurityException exception1)
            {
                getNameMethod = null;
            }
            catch (NoSuchMethodException exception1)
            {
                getNameMethod = null;
            }

            if (getNameMethod != null)
            {
                try
                {
                    System.out.println(getNameMethod.invoke(current, (Object[])null));
                }
                catch (IllegalArgumentException exception)
                {
                    // TODO Implement this catch block.
                }
                catch (IllegalAccessException exception)
                {
                    // TODO Implement this catch block.
                }
                catch (InvocationTargetException exception)
                {
                    // TODO Implement this catch block.
                }
            }
            else
            {
                System.out.print("Unexpected class: ");
                System.out.println(current.getClass());
            }
        }
    }

2 голосов
/ 10 августа 2011

Вам не нужно приводить: экземпляр A является экземпляром A, и приведение не изменит его тип.Поскольку во время компиляции вы не знаете, какой тип элемента, приведение не поможет вам.

Что вам нужно сделать, это вызвать метод getName с помощью отражения:

Class<?> clazz = list.get(i).getClass();
Method m = clazz.getMethod("getName");
m.invoke(list.get(i));
0 голосов
/ 10 августа 2011

Используя отражение, можно создать служебный метод для вызова метода getName () для любого объекта, который определяет его соответствующим образом (я не догадывался об аргументах и ​​возвращал String):

static String executeGetName(Object o) throws SecurityException,
        NoSuchMethodException, IllegalArgumentException,
        IllegalAccessException, InvocationTargetException {
    Method method = o.getClass().getMethod("getName");
    return (String) method.invoke(o);
}
0 голосов
/ 10 августа 2011

Если вы вообще не хотите (или не можете) изменить определение классов A, B и C, то наиболее вероятный вариант, который у вас есть, - это вызвать метод с помощью отражения..

В настоящее время статическая типизация Java не может гарантировать, что элементы внутри list имеют a getName() метод - потому что все, что он может гарантировать, это то, что они являются экземплярами java.lang.Object.

Даже ваш состав не помогает, потому что вы страдаете от той же самой проблемы с вашим массивом classes.Обратите внимание, что Class<E> на самом деле является общим, с параметром типа, соответствующим фактическому классу, в который будет выполняться приведение.Поскольку элементы массива просто имеют необработанный тип Class (примерно эквивалентный Class<? extends Object>), результат вызова classes[i].cast() является, опять же, экземпляром java.lang.Object.И поэтому вы не можете вызвать getName для него.


Если не существует какого-либо общего суперинтерфейса этих трех классов, который определяет getName(), вы не сможете удовлетворить компилятор, что этодопустимый метод для вызова их в этом стиле.

Один очень простой способ обойти это просто обработать каждый класс по-разному - использовать проверки instanceof и затем явно привести к A, B или C.Это сделало бы компилятор счастливым и обеспечило бы некоторую статическую проверку типов (например, если метод был переименован в A, ваш код перестал бы компилироваться), но утроил бы объем кода, который вам пришлось бы писать, и запускаетриск того, что вы пропустите новый класс D позже.

Другой вариант - отражение - возьмите метод getName непосредственно от объектов во время выполнения и вызовите его:

for(int i = 0; i < list.length;i++)
{
    final Object obj = list.get(i);
    final Method m = obj.getClass.getMethod("getName"); //A,B,and C have method getName() in common
    m.invoke(obj);

    // Exception-handling code omitted
}

Это «просто работает», но плохо сказывается на производительности, а также означает, что любые изменения в ваших классах не будут приниматься до тех пор, пока вы не получите NoSuchMethodException во время выполнения.

Лучшее решение на самом делеввести интерфейс и изменить классы.

0 голосов
/ 10 августа 2011

Вы должны использовать интерфейсы для этой цели, чтобы сделать

interface Nameable { public String getName() }

class A implements Nameable { ... }
class B implements Nameable { ... }
...

Это явно идет вразрез с вашим ограничением (не изменять код A, B, C), но это правильный способ сделать это.

Если вы действительно хотите, чтобы источник A, B, C оставался нетронутым, вы можете использовать средние объекты:

interface Nameable { public String getName() }
class AC extends A implements Nameable { ... }
class BC extends B implements Nameable { ... }
class CC extends C implements Nameable { ... }

или, наконец, вы можете использовать также композицию

class AC implements Nameable
{
   A instance;
   public String getName()
   {
     return instance.getName();
   }
}

но я лично придерживаюсь первого решения, которое является правильным ОО-способом сделать это.

0 голосов
/ 10 августа 2011

Поскольку компилятор не может проверить, есть ли у объектов в этом списке соответствующий getName() метод.Что, если в этом списке был Объект класса D?Решение было бы определить интерфейс с методом getName()

import java.util.ArrayList;
import java.util.List;

public class InterfaceExample {

interface I {
    String getName();
}

static class A implements I{
    ...
}

static class B implements I {
    ...
}

static class C implements I {
    ...
}

public static void main(String[] args) {
    List<I> list = new ArrayList<I>();
    list.add(new A());
    list.add(new B());
    list.add(new C());
    for(I i : list) {
        i.getName();
    }
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...