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

时间:2009-10-18 17:34:30

标签: java eclipse

/*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 = 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);
    }   
}

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

感谢。

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

8 个答案:

答案 0 :(得分:24)

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

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

现在关于实际问题,最简单的方法是确保实际的所有可能流分配值或抛出异常。因此,对于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 - 但您应该考虑想要使用的确切异常类型。

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

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

编辑:

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

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

答案 1 :(得分:5)

linkCostlink == 'y'不是advPay'y'时,

'n'未初始化。

换句话说,当编译器可以在代码中找到一个未使用本地变量之前初始化的路径时,就会出现此错误。

答案 2 :(得分:4)

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

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

答案 3 :(得分:3)

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

答案 4 :(得分:2)

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,或者包含另一个将其设置为零的其他{}。

其他变量的类似解释。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

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

建议: 移除else if (numberOfWords > 35),只需else

至于switch (advPay),请添加default个案。在里面你可以放throw new AssertionError();

答案 7 :(得分:0)

避免此类问题的一种好方法是在检查之前将要分配的变量设置为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;

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

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

相关问题