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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1585513/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 16:29:46  来源:igfitidea点击:

How to avoid 'the local variable may not have been initialized'?

javaeclipse

提问by HelloWorld

/*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 .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 .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 = TEXT_BASE_FEE + (TEXT_FLAT_FEE * 25) + (numberOfWords - 25) * LESS_OR_EQUAL_THAN_THIRTYFIVE;
        }

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

I'm almost done with this code and Eclipse suggests that I assign 0 values to textCostand linkCost. Is there any other way to go around this problem. If I don't assign 0 values they get an error (The local variable XXX may not have been initialized). Can someone explain to me why this happens even though I have both variables assigned with equations?

我几乎完成了这段代码,Eclipse 建议我将 0 值分配给textCostlinkCost。有没有其他方法可以解决这个问题。如果我不分配 0 值,它们会出错(局部变量 XXX 可能尚未初始化)。有人可以向我解释为什么即使我用方程分配了两个变量也会发生这种情况吗?

Thanks.

谢谢。

EDIT: I did as suggested and declared the variables only when I'm going to need it. I also added some comments.

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

采纳答案by Jon Skeet

Three suggestions before I delve any deeper into the code:

在我深入研究代码之前,提出三个建议:

  • Declare variables as late as you can to make it easier to understand the code.
  • Refactor this giant method - it's unreadably huge at the moment.
  • Make the constants static finalfields. They're not related to any particular call to the method, so they shouldn't be local variables.
  • 尽可能晚地声明变量,以便更容易理解代码。
  • 重构这个巨大的方法 - 目前它是巨大的。
  • 制作常量static final字段。它们与对方法的任何特定调用无关,因此它们不应该是局部变量。

Now as to the actual question, the simplest way is to make sure that every possible flow actually doesassign a value or throw an exception. So for textCost, change your code to:

现在对于实际问题,最简单的方法是确保每个可能的流实际上分配了一个值或抛出了异常。因此,对于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;
}

For linkCost, change your switch statement to something like:

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

Now you may not want to throw an exception here. You might want to loop round again, or something like that. You probably don'twant to use just bare Exception- but you should think about the exact exception type you dowant to use.

现在您可能不想在这里抛出异常。您可能想再次循环,或类似的东西。你可能希望只使用裸Exception-但你应该想想你确切的异常类型想用。

It's not alwayspossible to do this. The rules by the compiler to determine definite assignment are relativelystraightforward. In cases where you really can't change the code to make the compiler happy like this, you can just assign a dummy initial value. I'd recommend trying to avoid this wherever possible though. In your first case, the value really would always be assigned - but in the second case you really weren'tgiving a value when advPaywas neither 'y' nor 'n' which could lead to a hard-to-diagnose problem later on. The compiler error helps you spot this sort of problem.

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

Again though, I strongly suggest you refactor this method. I suspect you'll find it a lot easier to understand why things aren't definitely assigned when there's only about 10 lines of code to reason about in each method, and when each variable is declared just before or at its first use.

不过,我再次强烈建议您重构此方法。我怀疑您会发现当每个方法中只有大约 10 行代码需要推理时,以及当每个变量在其第一次使用之前或第一次使用时声明时,为什么没有明确分配的东西会更容易理解。

EDIT:

编辑:

Okay, the radically refactored code is below. I'm not going to claim it's the best code in the world, but:

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

  • It's more testable. You could easily write unit tests for each part of it. printAllCostsisn't terribly easily testable, but you could have an overload which took a Writerto print to - that would help.
  • Each bit of calculation is in a logical place. Links and graphics have a small set of possible values - Java enums are a natural fit here. (I'm aware they may well be beyond your current skill level, but it's good to see what will be available.)
  • I'm not using binary floating point numbers any more, because they're inappropriate for numbers. Instead, I'm using an integer number of cents everywhere and converting to BigDecimalfor display purposes. See my article on .NET floating pointfor more information - it's all relevant to Java really.
  • The advert itself is now encapsulated in a class. You could add a lot more information here as and when you needed to.
  • The code is in a package. Admittedly it's all in one file at the moment (which is why only the EntryPointclass is public) but that's just for the sake of Stack Overflow and me not having to open up Eclipse.
  • There's JavaDoc explaining what's going on - at least for a few methods. (I would probably add some more in real code. I'm running out of time here.)
  • We validate user input, except for the word count - and we perform that validation in a single routine, which should be reasonably testable. We can then assume that whenever we've asked for input, we've got something valid.
  • The number of static methods in EntryPointis slightly alarming. It doesn't feel terribly OO - but I find that's often the way around the entry point to a program. Note that there's nothing to do with fees in there - it's justthe user interface, basically.
  • 它更具可测试性。您可以轻松地为它的每个部分编写单元测试。printAllCosts不是非常容易测试,但你可能有一个Writer需要打印到的过载- 这会有所帮助。
  • 计算的每一位都在一个逻辑位置。链接和图形具有一小组可能的值 - Java 枚举在这里很合适。(我知道它们很可能超出您当前的技能水平,但很高兴看到可用的内容。)
  • 我不再使用二进制浮点数,因为它们不适用于数字。相反,我在任何地方都使用整数美分并转换为BigDecimal用于显示目的。有关更多信息,请参阅我关于.NET 浮点数的文章- 它确实与 Java 相关。
  • 广告本身现在封装在一个类中。您可以根据需要在此处添加更多信息。
  • 代码在一个包中。诚然,目前所有这些都在一个文件中(这就是为什么只有EntryPoint类是公开的)但这只是为了 Stack Overflow 和我不必打开 Eclipse。
  • 有 JavaDoc 解释了正在发生的事情 - 至少对于一些方法。(我可能会在实际代码中添加更多内容。我在这里没时间了。)
  • 我们验证用户输入,除了字数——我们在单个例程中执行该验证,这应该是可合理测试的。然后我们可以假设,每当我们要求输入时,我们都会得到一些有效的东西。
  • 静态方法的数量EntryPoint有点惊人。感觉不是很糟糕 - 但我发现这通常是程序入口点的方式。请注意,那里与费用无关 -基本上只是用户界面。

There's more code here than there was before - but it's (IMO) much more readable and maintainable code.

这里的代码比以前更多 - 但它(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 .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);
        }
    }
}

回答by josemrb

this happens because the assignment occurs inside a conditional, if the condition is not met, the assignment never occurs

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

to avoid the error you have to assign a value (the most common would be 0) outside the conditional.

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

回答by andyp

the error message tells you, that those variables aren't alwaysinitialized. This is, because your initialization happens only under certain conditions (they are located in if-statements). Hope this helps..

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

回答by Nicholas Riley

The analysis Eclipse performs to determine whether the variable is assigned on every code path isn't intelligent enough to realize that the tests on numberOfWordswill never all fail. Typically, because it's not possible to statically evaluate every possible condition, a compiler/syntax checker won't attempt to evaluate any of them. If you replaced the last "else if" with an "else" it should work, as at least one assignment to textCostwill occur regardless of the conditions being tested.

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

回答by RichN

Even though you know one of the 3 branches of the comparison with numberOfWords will be visited, the compiler doesn't know that. It will wrongly assume that it is possible to enter the else clause and the textCost variable will remain unitiliazed.

即使您知道将访问与 numberOfWords 比较的 3 个分支之一,编译器也不知道这一点。它会错误地假设可以输入 else 子句并且 textCost 变量将保持统一。

Similarly with the switch (advPay). Even though you knowthat one of the two will be visited, the compiler doesn't.

switch (advPay). 即使您知道将访问两者之一,编译器也不会。

Suggestion: Remove the else if (numberOfWords > 35)make it just an else.

建议:删除else if (numberOfWords > 35)使其只是一个else.

As for the switch (advPay), add a defaultcase. Inside you can put a throw new AssertionError();.

至于switch (advPay),加个defaultcase。在里面你可以放一个throw new AssertionError();.

回答by hexium

Eclipse is warning you because your initializations are happening inside conditionals. If none of the conditions are satisfied, textCostwill be uninitialized.

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 probably isn't recognizing that (numberOfWords <= 35)and (numberOfWords > 35)cover all possibilities.

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

You could either initialize it to 0 on declaration, or include an additional else {} which sets it to zero.

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

Similar explanation for the other variable.

其他变量的类似解释。

回答by eljenso

linkCostis not initialized when link == 'y'and advPayis not 'y'or 'n'.

linkCostlink == 'y'advPay不是'y'或时未初始化'n'

In other words, you get this error when the compiler can find a path through your code where a local variable is not initialized before it is used.

换句话说,当编译器可以通过您的代码找到一个路径,其中局部变量在使用之前没有初始化时,您会收到此错误。

回答by Christophe Roussy

A good way to avoid such issues is to set the to be assigned variable as finaland uninitializedbefore the checks. This will force you to set a value before you can use/read it.

一个很好的方式,以避免此类问题是设置要分配变量final未初始化的检查之前。这将强制您在使用/读取之前设置一个值。

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

To solve this DO NOT initialize the variable as you may miss the missing elsecase. The only proper solution is to add an elsecase to cover all possible cases ! I consider it bad practice to initialize non final variables except for some rare scenarios like a counter in a loop.

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

The proposed approach will force you to handle all possible cases, now and in the future (easier to maintain). The compiler is a bit stupid at times (cannot figure that numberOfWords > 35 is the else)...but the compiler is your ally not your enemy...

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