Java: the variable “may have already been initialized”, but I don’t understand how

At first, some context: all the code inserted below is within another class declared as the public class TheClass extends SomeProprietaryClass . I cannot declare these classes in another file for various reasons ... And the log messages are in French. And I'm the "last happy" programmer. At the heart of this problem is the problem ...

Now the code ... (maybe too much of this - shutdown on demand only to save the corresponding parts)

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

The enumeration for "materializing" the visibility of a member of a bundle:

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

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

Rusk:

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

Interface for palette 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 by // <-- HERE above, the Intellij IDEA I use and javac (1.6.0.29) will tell me that Variable bc might already have been assigned to , which is considered an error (and indeed, the code does not compile )

The problem is that I do not understand why ... My reasoning is this:

  • in the first try / catch block (and yes, .getProperty() makes a throw Throwable) when the exception is caught, bc gets assigned for success, and then I return how good it is;
  • in the second try / catch block, the constructor may fail, and in this case I will assign an empty palette, so it should be OK, although bc is final: the assignment is not executed (?) in try to execute the block, but it will happen in the catch block. ..

Except no, it is not. Since both IDEA and javac disagree with me, they are certainly right. But why?

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

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

+4
source share
3 answers

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

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

Now here is how bc can already be initialized. I hope you see where I come from.

Since the JAVAC parsing engine does not distinguish between one or more statements inside the try block, it does not see that your case is different from the following:

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

In this case, bc will be assigned twice. In other words, JAVAC does not care about where the Throwable source Throwable , it only cares that it can be there, and that bc can undergo a successful assignment in this try block.

+5
source

I do not think that the analysis is deep enough to really understand that there is only one statement in the try block, and the diagnostics are issued regardless of why you see it in your case.

+1
source

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; 
0
source

Source: https://habr.com/ru/post/1391051/


All Articles