如何避免“局部变量可能尚未初始化”?

/*This is a program that calculates Internet advertising rates based on what features/options you choose. * * */ import java.util.Scanner; public class InternetAdvertising { public static void main(String[] args) { Scanner in = new Scanner(System.in); int numberOfWords; //I assigned 0 values to both as Eclipse suggested float textCost = 0; float linkCost = 0; float graphicCost; //<=25 words is a flat fee of $.40 per word plus Base fee of $3.00 final float TEXT_FLAT_FEE = 0.40F; final float TEXT_BASE_FEE = 3.00F; //<=35 words is $.40 for the first 25 words and //an additional $.35 per word up to and including 35 words plus Base fee of $3.00 final float LESS_OR_EQUAL_THAN_THIRTYFIVE = 0.35F; //Over 35 words is a flat fee of $.32 per word with no base fee final float MORE_THAN_THIRTYFIVE = 0.32F; System.out.println("Welcome!"); System.out.print("Enter the number of words in your ad: "); numberOfWords = in.nextInt(); if (numberOfWords <= 25) { textCost = TEXT_BASE_FEE + (TEXT_FLAT_FEE * numberOfWords); } else if (numberOfWords  35) { textCost = numberOfWords * MORE_THAN_THIRTYFIVE; } String addLink, advancePay; char link, advPay; final float LINK_FLAT_FEE = 14.95F; final float THREE_MONTH_ADV_DISCOUNT = 0.10F; System.out.print("Would you like to add a link (y = yes or n = no)? "); addLink = in.next(); link = addLink.charAt(0); link = Character.toLowerCase(link); if (link == 'y') { System.out.print("Would you like to pay 3 months in advance " + "(y = yes or n = no)? "); advancePay = in.next(); advPay = advancePay.charAt(0); advPay = Character.toLowerCase(advPay); switch (advPay) { case 'y': linkCost = (3 * LINK_FLAT_FEE) - (3 * LINK_FLAT_FEE) * THREE_MONTH_ADV_DISCOUNT; break; case 'n': linkCost = LINK_FLAT_FEE; break; } } else { linkCost = 0; } String addGraphic; char graphic; System.out.print("Would you like to add graphics/pictures” + “(S = Small, M = Medium, L = Large or N = None)? "); addGraphic = in.next(); graphic = addGraphic.charAt(0); graphic = Character.toUpperCase(graphic); graphic = Character.toLowerCase(graphic); switch (graphic) { case 's': graphicCost = 19.07F; break; case 'm': graphicCost = 24.76F; break; case 'l': graphicCost = 29.33F; break; default: graphicCost = 0; } float gst, totalBeforeGst, totalAfterGst; final float GST_RATE = 0.05F; totalBeforeGst = textCost + linkCost + graphicCost; //textCost & linkCost would not initialize gst = totalBeforeGst * GST_RATE; totalAfterGst = totalBeforeGst + (totalBeforeGst * GST_RATE); System.out.printf("\t\t%-16s %11s\n", "Category", "Cost"); System.out.printf("\t\t%-16s %11.2f\n", "Text", textCost); //linkCost would not initialize System.out.printf("\t\t%-16s %11.2f\n", "Link", linkCost); //textCost would not initialize System.out.printf("\t\t%-16s %11.2f\n", "Graphic", graphicCost); System.out.printf("\t\t%-16s %11.2f\n", "Total", totalBeforeGst); System.out.printf("\t\t%-16s %11.2f\n", "GST", gst); System.out.printf("\t\t%-16s %11.2f\n", "Total with GST", totalAfterGst); } } 

我差不多完成了这段代码,Eclipse建议我为textCostlinkCost分配0个值。 有没有其他方法可以解决这个问题。 如果我没有指定0值,则会收到错误(局部变量XXX可能尚未初始化)。 有人可以向我解释为什么会发生这种情况,即使我有两个变量分配方程式?

谢谢。

编辑:我按照建议做了,并且仅在我需要它时才声明变量。 我还添加了一些评论。

在深入研究代码之前,有三点建议:

  • 尽可能晚地声明变量,以便更容易理解代码。
  • 重构这个巨大的方法 – 目前这个方法难以置信。
  • 使常量static final字段。 它们与方法的任何特定调用无关,因此它们不应该是局部变量。

现在关于实际问题,最简单的方法是确保每个可能的流实际上分配一个值或抛出一个exception。 因此,对于textCost ,请将代码更改为:

 if (numberOfWords <= 25) { textCost = TEXT_BASE_FEE + (TEXT_FLAT_FEE * numberOfWords); } else if (numberOfWords <= 35) { textCost = TEXT_BASE_FEE + (TEXT_FLAT_FEE * 25) + (numberOfWords - 25) * LESS_OR_EQUAL_THAN_THIRTYFIVE; } else // Note - no condition. { textCost = numberOfWords * MORE_THAN_THIRTYFIVE; } 

对于linkCost ,将switch语句更改为:

 switch (advPay) { case 'y': linkCost = (3 * LINK_FLAT_FEE) - (3 * LINK_FLAT_FEE) * THREE_MONTH_ADV_DISCOUNT; break; case 'n': linkCost = LINK_FLAT_FEE; break; default: throw new Exception("Invalid value specified: " + advPay); } 

现在你可能不想在这里抛出exception。 你可能想再次循环,或类似的东西。 您可能不想使用裸露的Exception - 但您应该考虑您想要使用的确切exception类型。

这并不总是可行的。 编译器确定明确赋值的规则相对简单。 如果您真的无法更改代码以使编译器满意,则可以只分配一个虚拟初始值。 我建议尽可能避免这种情况。 在你的第一种情况下,值总是会被​​分配 - 但在第二种情况下,当advPay既不是'y'也不是'n'时,你真的没有给出一个值,这可能导致以后难以诊断的问题。 编译器错误可以帮助您发现此类问题。

但是,我强烈建议你重构这个方法。 我怀疑你会发现,当每个方法中只有大约10行代码可以解释时,为什么事情没有明确分配,以及当每个变量在第一次使用之前或第一次使用时被声明时,你会发现它更容易理解。

编辑:

好的,根本重构的代码如下。 我不会声称它是世界上最好的代码,但是:

  • 它更可测试。 您可以轻松地为其中的每个部分编写unit testing。 printAllCosts并不是非常容易测试的,但是你可能会有一个过载,它会让Writer打印到 - 这会有所帮助。
  • 每个计算位都在逻辑位置。 链接和图形有一小组可能的值 - Java枚举在这里很自然。 (我知道它们可能超出你目前的技能水平,但很高兴看到可用的东西。)
  • 我不再使用二进制浮点数,因为它们不适合数字。 相反,我在任何地方使用整数个分数并转换为BigDecimal用于显示目的。 有关更多信息,请参阅我关于.NET浮点的文章 - 它与Java完全相关。
  • 广告本身现在封装在一个类中。 您可以在需要时在此处添加更多信息。
  • 代码在一个包中。 不可否认,目前这一切都在一个文件中(这就是为什么只有EntryPoint类是公开的),但这只是为了Stack Overflow而我不需要打开Eclipse。
  • 有JavaDoc解释发生了什么 - 至少对于一些方法。 (我可能会在实际代码中添加更多内容。我在这里的时间不多了。)
  • 我们validation用户输入,除了字数 - 我们在单个例程中执行validation,这应该是可以合理测试的。 然后我们可以假设每当我们要求输入时,我们就会得到一些有效的东西。
  • EntryPoint的静态方法数量略有惊人。 它感觉不到OO - 但我发现这通常是程序入口点的方式。 请注意,那里的费用没有任何关系 - 基本上只是用户界面。

这里有比以前更多的代码 - 但它(IMO)更易读和可维护的代码。

 package advertising; import java.util.Scanner; import java.math.BigDecimal; /** The graphic style of an advert. */ enum Graphic { NONE(0), SMALL(1907), MEDIUM(2476), LARGE(2933); private final int cost; private Graphic(int cost) { this.cost = cost; } /** Returns the cost in cents. */ public int getCost() { return cost; } } /** The link payment plan for an advert. */ enum LinkPlan { NONE(0), PREPAID(1495), // 1 month POSTPAID(1495 * 3 - (1495 * 3) / 10); // 10% discount for 3 months up-front private final int cost; private LinkPlan(int cost) { this.cost = cost; } /** Returns the cost in cents. */ public int getCost() { return cost; } } class Advertisement { private final int wordCount; private final LinkPlan linkPlan; private final Graphic graphic; public Advertisement(int wordCount, LinkPlan linkPlan, Graphic graphic) { this.wordCount = wordCount; this.linkPlan = linkPlan; this.graphic = graphic; } /** * Returns the fee for the words in the advert, in cents. * * For up to 25 words, there's a flat fee of 40c per word and a base fee * of $3.00. * * For 26-35 words inclusive, the fee for the first 25 words is as before, * but the per-word fee goes down to 35c for words 26-35. * * For more than 35 words, there's a flat fee of 32c per word, and no * base fee. */ public int getWordCost() { if (wordCount > 35) { return 32 * wordCount; } // Apply flat fee always, then up to 25 words at 40 cents, // then the rest at 35 cents. return 300 + Math.min(wordCount, 25) * 40 + Math.min(wordCount - 25, 0) * 35; } /** * Displays the costs associated with this advert. */ public void printAllCosts() { System.out.printf("\t\t%-16s %11s\n", "Category", "Cost"); printCost("Text", getWordCost()); printCost("Link", linkPlan.getCost()); printCost("Graphic", graphic.getCost()); int total = getWordCost() + linkPlan.getCost() + graphic.getCost(); printCost("Total", total); int gst = total / 20; printCost("GST", gst); printCost("Total with GST", total + gst); } private void printCost(String category, int cents) { BigDecimal dollars = new BigDecimal(cents).scaleByPowerOfTen(-2); System.out.printf("\t\t%-16s %11.2f\n", category, dollars); } } /** * The entry point for the program - takes user input, builds an * Advertisement, and displays its cost. */ public class EntryPoint { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); System.out.println("Welcome!"); int wordCount = readWordCount(scanner); LinkPlan linkPlan = readLinkPlan(scanner); Graphic graphic = readGraphic(scanner); Advertisement advert = new Advertisement(wordCount, linkPlan, graphic); advert.printAllCosts(); } private static int readWordCount(Scanner scanner) { System.out.print("Enter the number of words in your ad: "); // Could add validation code in here return scanner.nextInt(); } private static LinkPlan readLinkPlan(Scanner scanner) { System.out.print("Would you like to add a link (y = yes or n = no)? "); char addLink = readSingleCharacter(scanner, "yn"); LinkPlan linkPlan; if (addLink == 'n') { return LinkPlan.NONE; } System.out.print("Would you like to pay 3 months in advance " + "(y = yes or n = no)? "); char advancePay = readSingleCharacter(scanner, "yn"); return advancePay == 'y' ? LinkPlan.PREPAID : LinkPlan.POSTPAID; } private static Graphic readGraphic(Scanner scanner) { System.out.print("Would you like to add graphics/pictures? " + "(s = small, m = medium, l = large or n = None)? "); char graphic = readSingleCharacter(scanner, "smln"); switch (graphic) { case 's': return Graphic.SMALL; case 'm': return Graphic.MEDIUM; case 'l': return Graphic.LARGE; case 'n': return Graphic.NONE; default: throw new IllegalStateException("Unexpected state; graphic=" + graphic); } } private static char readSingleCharacter(Scanner scanner, String validOptions) { while(true) { String input = scanner.next(); if (input.length() != 1 || !validOptions.contains(input)) { System.out.print("Invalid value. Please try again: "); continue; } return input.charAt(0); } } } 

link == 'y'并且advPay不是'y''n'时, linkCost不会被初始化。

换句话说,当编译器可以找到通过代码的路径时,如果局部变量在使用之前未初始化,则会出现此错误。

发生这种情况是因为赋值发生在条件内,如果条件不满足,则永远不会发生赋值

为了避免错误,你必须在条件之外分配一个值(最常见的是0)。

Eclipse执行的分析用于确定是否在每个代码路径上分配变量都不够智能,无法实现对numberOfWords的测试永远不会全部失败。 通常,因为不可能静态地评估每个可能的条件,所以编译器/语法检查器不会尝试评估它们中的任何一个。 如果用“else”替换了最后一个“else if”,它应该可以工作,因为无论测试条件如何,都会发生至少一次对textCost赋值。

Eclipse警告你,因为你的初始化发生在条件语句中。 如果没有满足任何条件,则textCost将被取消初始化。

 if (numberOfWords <= 25) { //assign a value to textCost } else if (numberOfWords <= 35) { //assign a value to textCost } else if (numberOfWords > 35) { //assign a value to textCost } 

Eclipse可能没有意识到(numberOfWords <= 35)(numberOfWords > 35)涵盖所有可能性。

您可以在声明时将其初始化为0,或者包含另一个将其设置为零的其他{}。

其他变量的类似解释。

错误消息告诉您,这些变量并不总是初始化。 这是因为您的初始化仅在某些条件下发生(它们位于if语句中)。 希望这可以帮助..

即使您知道将访问与numberOfWords进行比较的3个分支之一,编译器也不知道。 它会错误地认为可以输入else子句并且textCost变量将保持单元化。

switch (advPay)类似。 即使您知道将访问其中一个,编译器也不会。

建议: else if (numberOfWords > 35)将其删除, else if (numberOfWords > 35)删除else

至于switch (advPay) ,添加default情况。 在里面你可以throw new AssertionError();一个throw new AssertionError();

避免此类问题的一种好方法是在检查之前将要分配的变量设置为finaluninitialized 。 这将强制您在使用/读取之前设置一个值。

 final textCostTmp; if (condition1) { textCostTmp = ...; } else if (condition2) { textCostTmp = ...; } // if you use textCostTmp here the compiler will complain that it is uninitialized ! textCost = textCostTmp; 

要解决此问题,请不要初始化变量,因为您可能会错过else情况。 唯一合适的解决方案是添加一个else案例来涵盖所有可能的情况! 我认为初始化非最终变量是不好的做法,除了一些罕见的情况,比如循环中的计数器。

建议的方法将迫使您现在和将来处理所有可能的情况(更容易维护)。 编译器有时有点愚蠢(不能认为numberOfWords> 35是其他的)…但编译器是你的盟友不是你的敌人……