/*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可能尚未初始化)。有人可以向我解释为什么会发生这种情况,即使我将两个变量分配给方程式吗?
感谢。
编辑:我按照建议做了,并且仅在我需要时才声明变量。我还添加了一些评论。答案 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
打印到 - 这会有所帮助。BigDecimal
用于显示目的。有关更多信息,请参阅我在.NET floating point上的文章 - 它与Java完全相关。EntryPoint
类是公开的),但这只是为了Stack Overflow而我不需要打开Eclipse。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)
linkCost
和link == '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是其他的)...但编译器是你的盟友不是你的敌人......