Java: переменная «возможно, уже была инициализирована», но я не понимаю, как - PullRequest
5 голосов
/ 15 января 2012

Сначала немного контекста: весь код, вставленный ниже, находится внутри другого класса, объявленного как public class TheClass extends SomeProprietaryClass. Я не могу объявить эти классы в другом файле по разным причинам ... И сообщения журнала на французском языке. И я «последний счастливый» вид программиста. Что лежит в основе проблемы здесь ...

Теперь код ... (вероятно, слишком много - извлечение по требованию, чтобы сохранить только соответствующие части)

Пользовательское исключение:

private static final class BreadCrumbException
    extends Exception
{
    private BreadCrumbException(final String message)
    {
        super(message);
    }

    private BreadCrumbException(final String message, final Throwable cause)
    {
        super(message, cause);
    }
}

Перечисление для "материализации" видимости элемента крошки:

private enum Visibility
{
    MAINPAGE("R"),
    MENU("M"),
    BREADCRUMB("A"),
    COMMERCIAL("C");

    private static final Map<String, Visibility> reverseMap
        = new HashMap<String, Visibility>();

    private static final String characterClass;

    static {
        final StringBuilder sb = new StringBuilder("[");

        for (final Visibility v: values()) {
            reverseMap.put(v.flag, v);
            sb.append(v.flag);
        }

        sb.append("]");
        characterClass = sb.toString();
    }

    private final String flag;

    Visibility(final String flag)
    {
        this.flag = flag;
    }

    static EnumSet<Visibility> fromBC(final String element)
    {
        final EnumSet<Visibility> result = EnumSet.noneOf(Visibility.class);

        for (final String s: reverseMap.keySet())
            if (element.contains(s))
                result.add(reverseMap.get(s));

        return result;
    }


    static String asCharacterClass()
    {
        return characterClass;
    }

    static String asString(final EnumSet<Visibility> set)
    {
        final StringBuilder sb = new StringBuilder();

        for (final Visibility v: set)
            sb.append(v.flag);

        return sb.toString();
    }

    @Override
    public String toString()
    {
        return flag;
    }
}

Элемент крошки:

private static class BreadCrumbElement
{
    private static final Pattern p
        = Pattern.compile(String.format("(%s+)(\\d+)",
        Visibility.asCharacterClass()));

    private final String element;
    private final String menuID;
    private final EnumSet<Visibility> visibility;

    BreadCrumbElement(final String element)
    {
        final Matcher m = p.matcher(element);

        if (!m.matches())
            throw new IllegalArgumentException("Élément de fil d'ariane invalide: " + element);

        this.element = element;
        visibility = EnumSet.copyOf(Visibility.fromBC(m.group(1)));
        menuID = m.group(2);
    }

    public boolean visibleFrom(final Visibility v)
    {
        return visibility.contains(v);
    }

    @Override
    public boolean equals(final Object o)
    {
        if (this == o)
            return true;
        if (o == null || getClass() != o.getClass())
            return false;

        final BreadCrumbElement that = (BreadCrumbElement) o;

        return element.equals(that.element);
    }

    @Override
    public int hashCode()
    {
        return element.hashCode();
    }

    @Override
    public String toString()
    {
        return element;
    }

    public String getMenuID()
    {
        return menuID;
    }
}

Панировочные сухари:

private static class BreadCrumb
    implements Iterable<BreadCrumbElement>
{
    private static final BreadCrumb EMPTY = new BreadCrumb();

    private final List<BreadCrumbElement> elements
        = new LinkedList<BreadCrumbElement>();

    private String bc;

    BreadCrumb(final String bc)
        throws BreadCrumbException
    {
        final Set<BreadCrumbElement> set = new HashSet<BreadCrumbElement>();
        BreadCrumbElement e;

        for (final String element: bc.split("\\s+")) {
            e = new BreadCrumbElement(element);
            if (!set.add(e))
                throw new BreadCrumbException("Élément dupliqué "
                    + "dans le fil d'Ariane : " +  element);
            elements.add(e);
        }

        if (elements.isEmpty())
            throw new BreadCrumbException("Fil d'ariane vide!");

        if (!elements.get(0).visibleFrom(Visibility.MAINPAGE))
            throw new BreadCrumbException("Le fil d'Ariane ne "
                + "commence pas à l'accueil : " + bc);

        set.clear();
        this.bc = bc;
    }

    private BreadCrumb()
    {
    }

    BreadCrumb reverse()
    {
        final BreadCrumb ret = new BreadCrumb();
        ret.elements.addAll(elements);
        Collections.reverse(ret.elements);
        ret.bc = StringUtils.join(ret.elements, " ");
        return ret;
    }

    public Iterator<BreadCrumbElement> iterator()
    {
        return elements.iterator();
    }

    @Override
    public String toString()
    {
        return bc;
    }
}

Интерфейс к рендереру хлебных крошек:

public interface BreadCrumbRender
{
    List<CTObjectBean> getBreadCrumb()
        throws Throwable;

    String getTopCategory();

    String getMenuRoot();

    String getContext();
}

Реализация интерфейса выше, который является источником моих проблем:

private class CategoryBreadCrumbRender
    implements BreadCrumbRender
{
    private final BreadCrumb bc;
    private final CTObject object;

    CategoryBreadCrumbRender(final CTObject object)
    {
        this.object = object;
        final String property;

        // FIELD_BC is declared as a private static final String earlier on.
        // logger is also a private static final Logger
        try {
            property = object.getProperty(FIELD_BC);
        } catch (Throwable throwable) {
            logger.fatal("Impossible d'obtenir le champ " + FIELD_BC
                + " de l'objet", throwable);
            bc = BreadCrumb.EMPTY;
            return;
        }

        try {
            bc = new BreadCrumb(property);
        } catch (BreadCrumbException e) {
            logger.fatal("Impossible d'obtenir le fil d'Ariane", e);
            bc = BreadCrumb.EMPTY; // <-- HERE
        }
    }
    // ....

В точке, отмеченной // <-- HERE выше, Intellij IDEA, которую я использую, и javac (1.6.0.29) оба сообщают мне, что Variable bc might already have been assigned to, что считается ошибкой (и действительно, код не компилируется).

Проблема в том, что я не понимаю, почему ... Я рассуждаю так:

  • в первом блоке try / catch (и да, .getProperty() действительно выбрасывает Throwable), когда исключение перехватывается, bc успешно назначается, а затем я возвращаюсь, пока все хорошо;
  • во втором блоке try / catch конструктор может потерпеть неудачу, и в этом случае я назначаю пустую крошку, так что все должно быть в порядке, даже если bc является окончательным: присвоение не происходит (?) В попробуйте блок, но вместо этого происходит в блоке catch ...

Кроме нет, это не так. Поскольку и IDEA, и Javac не согласны со мной, они, безусловно, правы. Но почему?

(а также, BreadCrumb.EMPTY объявлено private static final в классе, интересно, как вообще я могу получить к нему доступ ... Вспомогательный вопрос)

EDIT : есть известная ошибка с ключевым словом final ( здесь , спасибо @MiladNaseri за ссылку на него), однако следует отметить, что в этой ошибке переменная v назначается только в блоках catch, но в приведенном выше коде я назначаю ее в блоках try и назначаю ее в блоках catch только в случае возникновения исключения. Также следует отметить, что ошибка возникает только во втором блоке catch.

Ответы [ 3 ]

5 голосов
/ 15 января 2012

Хорошо, предположим, что в первом блоке try при выполнении property = object.getProperty(FIELD_BC); возникает исключение.Таким образом, JVM войдет в блок перехвата и инициализирует bc по пути.

Затем во втором блоке try также возникает исключение, в результате которого BreadCrumb.EMPTY присваивается bc, эффективно переопределяяего первоначальное значение.

Так вот, bc может уже инициализироваться.Я надеюсь, вы понимаете, откуда я.

Поскольку механизм анализа JAVAC не проводит различий между одним или несколькими утверждениями внутри блока try, он не видит в вашем случае ничего отличного от приведенного ниже:

try {
    bc = null;
    String x = null;
    System.out.println(x.toString());
} catch (Throwable e) {
    bc = null;
}

В этом случае bc будет назначен дважды.Другими словами, JAVAC не будет заботиться о том, где находится источник Throwable, он заботится только о том, что он может быть там, и что bc может пройти успешное назначение в этом блоке try.

1 голос
/ 15 января 2012

Я не думаю, что анализ достаточно глубок, чтобы действительно понять, что в блоке try есть только одно утверждение, и диагностика выдается независимо от того, что именно, поэтому вы видите это в своем случае.

0 голосов
/ 15 января 2012

Попробуйте вместо этого:

BreadCrumb tmp = null;
try {
    tmp = new BreadCrumb(property);
} catch (BreadCrumbException e) {
    logger.fatal("Impossible d'obtenir le fil d'Ariane", e);
    tmp = BreadCrumb.EMPTY;
}
bc = tmp;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...