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

Hel*_*rld 4 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);
    }   
}
Run Code Online (Sandbox Code Playgroud)

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

谢谢.

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

Jon*_*eet 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;
}
Run Code Online (Sandbox Code Playgroud)

对于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);
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

编辑:

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

  • 它更可测试.您可以轻松地为其中的每个部分编写单元测试.printAllCosts是不是非常容易测试,但你可能有一个过载,需要Writer打印 - 这将有所帮助.
  • 每个计算位都在逻辑位置.链接和图形有一小组可能的值 - Java枚举在这里很自然.(我知道它们可能超出你目前的技能水平,但很高兴看到可用的东西.)
  • 我不再使用二进制浮点数,因为它们不适合数字.相反,我在任何地方使用整数分数并转换BigDecimal为显示目的.有关更多信息,请参阅我关于.NET浮点的文章- 它与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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 循环复杂性并不是可读性的全部和全部.这个方法长125行,我确信它可以非常容易地分解.尽可能晚地声明变量不会删除有关未初始化变量的警告 - 它只是使警告易于理解.我支持我的两个建议. (2认同)
  • 如果我尝试使用您的示例代码并重构它以向您展示它的外观会有所帮助吗? (2认同)

elj*_*nso 5

linkCostlink == 'y'advPay不是'y'或没有初始化'n'.

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