Java:变量“可能已经初始化”,但我不明白如何

时间:2012-01-15 01:47:26

标签: java final

首先是一些上下文:下面粘贴的所有代码都在另一个声明为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是最终的:分配不会发生(?)在try块中,但发生在catch块中......

除了否,它没有。由于IDEA和javac都不同意我的意见,他们肯定是对的。但为什么呢?

(而且,BreadCrumb.EMPTY在课堂上被宣布为private static final,我想知道我怎么可以访问它...附属问题)

编辑final关键字(here存在已知错误,感谢@MiladNaseri链接到它),但是应该注意这一点错误,变量v仅在catch块中分配 - 但在上面的代码中,我在try块中分配它,并且仅在catch块中分配它抛出异常。此外,应该注意,错误仅发生在第二个catch块中。

3 个答案:

答案 0 :(得分:5)

好的,假设在第一个try块中,在执行property = object.getProperty(FIELD_BC);时发生异常。因此,JVM将进入catch块,并在此过程中初始化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 :(得分:1)

我认为分析不够深入,无法真正理解try块中只有一个语句,无论如何都会发出诊断信息,这就是为什么你会在你的情况下看到它。

答案 2 :(得分:0)

请改为尝试:

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;