Java:一个变量“可能已经初始化了”,但是我不知道如何初始化

时间:2022-04-22 18:20:55

First some context: all the code pasted below is within another class declared as public class TheClass extends SomeProprietaryClass. I cannot declare these classes in another file for various reasons... And log messages are in French. And I'm a "final happy" kind of programmer. Which is at the core of the problem here...

首先是一些上下文:下面粘贴的所有代码都在另一个声明为public类的类中,该类扩展了某个独资类。由于各种原因,我无法在另一个文件中声明这些类……日志信息是用法语写的。我是一名“最后的快乐”程序员。这是问题的核心……

Now, the code... (probably too much of it -- stripping on demand to only keep the relevant parts)

现在,代码…(可能太多了——按需剥离,只保留相关部件)

A custom Exception:

一个自定义异常:

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

An enum for "materializing" the visibility of a breadcrumb element:

将面包屑元素的可见性“具体化”的枚举:

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

A breadcrumb element:

面包屑路径元素:

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

A breadcrumb:

面包屑路径:

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

The interface to a breadcrumb renderer:

面包屑渲染器的接口:

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

    String getTopCategory();

    String getMenuRoot();

    String getContext();
}

The implementation of the interface above which is the source of my problems:

上述接口的实现是我问题的根源:

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
        }
    }
    // ....

At the point marked // <-- HERE above, Intellij IDEA, which I use, and javac (1.6.0.29) both tell me that Variable bc might already have been assigned to, which is considered an error (and indeed, the code does not compile).

在上面标记为// <的地方,我使用的intellij idea和javac(1.6.0.29)都告诉我,变量bc可能已经被赋值,这被认为是一个错误(实际上,代码没有编译)。< p>

Trouble is, I do not understand why... My reasoning is the following:

问题是,我不明白为什么……我的推理如下:

  • in the first try/catch block (and yes, .getProperty() does throw Throwable), when an exception is caught, bc gets assigned to successfully, and then I return, so far so good;
  • 在第一个try/catch块(是的,.getProperty()确实抛出了throw able)中,当一个异常被捕获时,bc被分配到成功,然后我返回,到目前为止还不错;
  • in the second try/catch block, the constructor may fail, in which case I assign an empty breadcrumb, so it should be OK, even though bc is final: the assignment doesn't happen (?) in the try block but happens in the catch block instead...
  • 在第二个try/catch块中,构造函数可能会失败,在这种情况下,我分配了一个空的breadcrumb,所以应该是OK的,即使bc是final的:赋值不会在try块中发生(?),而是在catch块中发生…

Except no, it doesn't. As both IDEA and javac disagree with me, they are certainly right. But why?

除了不,它不是。由于IDEA和javac都不同意我的观点,他们当然是对的。但是为什么呢?

(and also, BreadCrumb.EMPTY is declared private static final in the class, I wonder how come I can access it at all... Subsidiary question)

(而且,面包屑。EMPTY在类中被声明为私有静态final,我想知道为什么我可以访问它……子公司问题)

EDIT: there is a known bug with the final keyword (here, thanks to @MiladNaseri for linking to it), however it should be noted that in this bug, variable v is only ever assigned in catch blocks -- but in the code above, I assign it in try blocks and only assign it in catch blocks if an exception is thrown. Also, it should be noted that the error only occurs in the second catch block.

编辑:这是一个已知的缺陷与final关键字(在这里,感谢@MiladNaseri链接到它),但是需要注意的是,在这个bug,变量v只是在catch块分配,但在上面的代码中,我只在试块和分配分配在catch块如果抛出一个异常。另外,应该注意的是,错误只发生在第二个catch块中。

3 个解决方案

#1


5  

Okay, suppose that in the first try block, when doing property = object.getProperty(FIELD_BC); an exception occurs. So, JVM will enter the catch block, and initialize bc along the way.

假设在第一个try块中,当执行property = object.getProperty(FIELD_BC);发生异常。因此,JVM将输入catch块,并在此过程中初始化bc。

Then in the second try block, also an exception occurs, resulting in BreadCrumb.EMPTY being assigned to bc, effectively overriding its original value.

然后在第二个try块中,也会发生异常,导致BreadCrumb。空被分配给bc,有效地覆盖其原始值。

Now, that is how bc might have already been initialized. I hope you see where I'm coming from.

现在,这就是bc可能已经初始化的方式。我希望你们明白我的意思。

Since the JAVAC analysis engine does not draw a distinction between one or many statements inside the try block, it does not see your case any different than the below:

由于JAVAC分析引擎并不区分try块中的一个或多个语句,因此它认为您的情况与下面的情况没有任何不同:

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

In which case, bc will be assigned twice. In other words, JAVAC won't care that where the source of the Throwable lies, it only cares that it can be there, and that bc might undergo a successful assignment in that try block.

在这种情况下,bc将被分配两次。换句话说,JAVAC并不关心可抛出的源在哪里,它只关心它是否在那里,并且bc可能在try块中经历一个成功的赋值。

#2


1  

I don't think the analysis is deep enough to really understand that there is only one statement in the try block, and the diagnostic is issued no matter what, so that's why you're seeing it in your case.

我认为这个分析不够深入,不能真正理解try块中只有一个语句,无论如何诊断都会被发布,所以这就是为什么你在你的案例中看到它。

#3


0  

Try this instead:

试试这个:

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;

#1


5  

Okay, suppose that in the first try block, when doing property = object.getProperty(FIELD_BC); an exception occurs. So, JVM will enter the catch block, and initialize bc along the way.

假设在第一个try块中,当执行property = object.getProperty(FIELD_BC);发生异常。因此,JVM将输入catch块,并在此过程中初始化bc。

Then in the second try block, also an exception occurs, resulting in BreadCrumb.EMPTY being assigned to bc, effectively overriding its original value.

然后在第二个try块中,也会发生异常,导致BreadCrumb。空被分配给bc,有效地覆盖其原始值。

Now, that is how bc might have already been initialized. I hope you see where I'm coming from.

现在,这就是bc可能已经初始化的方式。我希望你们明白我的意思。

Since the JAVAC analysis engine does not draw a distinction between one or many statements inside the try block, it does not see your case any different than the below:

由于JAVAC分析引擎并不区分try块中的一个或多个语句,因此它认为您的情况与下面的情况没有任何不同:

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

In which case, bc will be assigned twice. In other words, JAVAC won't care that where the source of the Throwable lies, it only cares that it can be there, and that bc might undergo a successful assignment in that try block.

在这种情况下,bc将被分配两次。换句话说,JAVAC并不关心可抛出的源在哪里,它只关心它是否在那里,并且bc可能在try块中经历一个成功的赋值。

#2


1  

I don't think the analysis is deep enough to really understand that there is only one statement in the try block, and the diagnostic is issued no matter what, so that's why you're seeing it in your case.

我认为这个分析不够深入,不能真正理解try块中只有一个语句,无论如何诊断都会被发布,所以这就是为什么你在你的案例中看到它。

#3


0  

Try this instead:

试试这个:

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;