JAVA开发讲义(二)-Java程序设计之数据之谜三
程序之美
在程序开发中,格式化输出我们经常会遇到,所以也可以这样说,文本的读写和格式化的输入输出是考验一个程序员是否优秀的一个重要的指标,也是程序员们的基本功。相信很多在校的大学生们,当你们翻看书本时,第一节课,第一个例子,我相信每一本教程都是交给你们怎么去输出一个“hello world”,输入和输出,文本的读写是程序编写的入口和出口,毫不夸张的说是你完成一个程序的咽喉,其重要性就不用我再说了吧。
既然输入输出如此重要,我们这一篇的开篇就来介绍输入输出,同时也会介绍文本的读写,并且配有相应的实例代码,帮助小伙伴们理解与消化,我相信,经过这一篇的讲解,不管时在校的,还是就业的,或者是正在不断面试的朋友们,你们都能有所收获,有所提升。
输入输出
输入与输入,说白了就是流操作,也可以理解为IO操作。流操作分为输入流和输出流。
输出流,我们最长见的就是System.out.println()。
输入流,我们最常见的就是InputStream,只能读取数据。
IO流的类比较多,但核心体系就是由File、 InputStream 、OutputStream、Reader、Writer和Serializable(接口)组成的。
流的输入和输出是相对的,主要看参照物是程序还是文件。根据流的方向我们将其分为输入流(InputStream)与输出流(OuputStream):
输入流:只能读取数据,不能写入数据。
输出流:只能写入数据,不能读取数据。
文本的输入和输出
当然对于流的输入,我们也可以使用Scanner类的各种方法实习那输入操作了,例如,nextLine方法可实现输入一行。如下:
public class InputTest{
public static void main(String[] args){
Scanner in = new Scanner(System.in);
//get first input
System.out.print("hello,this is a good boy!");
String name = in.nextLine();
//get second input
System.out.print("How old are you?");
int age = in.nextInt();
//display output on console
System.out.println("boy name:" + name + " age:" + age);
}
}
输入是可见的,所以Scanner类不适用于从控制台读取密码等隐藏字符,如果要输入隐藏字符等需要引用Console类来实现这个目的,如下:
Console cons = System.console();
String userName = cons.readLine("User name: ");
char[] passwd = cons.readPassword("Password: ");
保证密码的安全,将其存储于一个数组中,便于后续对密码的处理,保存或者显示等。下面提示Console两个函数,在我们进行界面操作时会经常用到:
static char[] readPasswrod(String prompt, Object...args);
static String readLine(String prompt, Object...args);
显示字符串prompt并读取用户输入,直到输入行结束。输入格式由args进行控制。
读取文件
Scanner in = new Scanner(Paths.get("myfile.txt"), "UTF-8");
文件中包含反斜杠符号,需要每个反斜杠之前加一个额外的反斜杠,进行相应的转义,如:
“c:\work\myfile.txt”
写入文件
PrintWriter out = new PrintWriter("myfile.txt", "UTF-8");
如果文件不存在,创建文件,存在就执行文件写入。这里着重说下读写文件的位置,选择相对路径还是绝对路径,如果觉得定位文件比较烦恼,可以使用绝对路径,如:windows “c:\work\myfile.txt”, linux /home/me/work/myfile.txt。这里"/“相当于”\",绝对不要用一个不存在的文件去构造Scanner或PrintWriter,否则会产生比较严重的异常。
格式化输出
格式化输出就是使用Sysetm.out.print(x)将x输出到控制台上。这个函数的调用,可以支持到最大非0数字的打印输出。例如
double x = 1000.0 / 3.0;
System.out.print(x);
结果为:333.3333333333335
在C语言中我们打印多用printf方法。java SE 自从5.0之后沿用了这个函数,例如:
System.out.printf("%8.2f", x);
可以用8个字符的宽度和小数点后两个字符的精度打印。结果如下:
333.33
当然也可以输出多个参数,如:
String name = "jerry";
int age = 30;
System.out.printf("your name is %s, age is %d", name, age);
在System.out.printf中,每一个%开始的格式说明符都将被参数替换,并且是按照顺序,依次替换。有的朋友会有疑问,说,如果我格式说明符或者参数缺失怎么办?
好了,我们看下如下:
String name = "jerry";
int age = 30;
boolean isMale = false;
System.out.printf("your name is %s, isMale:%b\n", name, age, isMale);
结果会是什么?
结果是:your name is jerry, isMale:true
小伙伴们会大跌眼镜,明明 boolean isMale = false; 为什么输出出来为true?好了其实我们并没有真正输出isMale,而是以boolean类型输出了age,因为age=30,在Java中,以boolean类型进行输出,0为false,非0为true。我们稍作修改如下:
System.out.printf("your name is %s, age is %d, isMale:%b\n", name, age, isMale);
结果为:your name is jerry, age is 30, isMale:false
是不是一切正常了,说到这里小伙伴们理解了么?格式化输出,参数会根据前面的格式依次排队,如果类型不一致,它会转化为相近的格式进行输出,除非不能进行相应的转化,才报出异常。
用于printf的转化符如下表:
另外可以给控制格式化输出增加各种标志。如逗号标志增加了分组的分隔符。如下:
System.out.printf("%,.2f", 100000.0 / 3.0);
金钱表示方法,每三位有一个逗号,进行分组。
结果为:33,333.33
0标志要对数字进行前补0等,具体的用于System.out.printf的标志如下图所示。
可以使用s转换符格式化任意的对象。除了使用System.out.printf函数外,我们还可以使用String.format进行字符串转换,创建一个新的格式字符串,而不进行打印输出。如下:
String name = "jerry";
int age = 30;
boolean isMale = false;
String message = String .format("your name is %s, age is %d, isMale:%b", name, age, isMale);
这样结果就被保存到message中。
日期与时间的格式化输出:
System.out.printf("%tc", new Date());
结果为:
Tue Nov 16 15:11:21 CST 2021
日期和时间的转换符详见下表
import java.util.Date;
public class DateDemo {
public static void main(String[] args) {
// 初始化 Date 对象
Date date = new Date();
//c的使用
System.out.printf("全部日期和时间信息:%tc%n",date);
//f的使用
System.out.printf("年-月-日格式:%tF%n",date);
//d的使用
System.out.printf("月/日/年格式:%tD%n",date);
//r的使用
System.out.printf("HH:MM:SS PM格式(12时制):%tr%n",date);
//t的使用
System.out.printf("HH:MM:SS格式(24时制):%tT%n",date);
//R的使用
System.out.printf("HH:MM格式(24时制):%tR",date);
}
}
结果为:
全部日期和时间信息:Tue Nov 16 15:13:27 CST 2021
年-月-日格式:2021-11-16
月/日/年格式:11/16/21
HH:MM:SS PM格式(12时制):03:13:27 PM
HH:MM:SS格式(24时制):15:13:27
HH:MM格式(24时制):15:13
为了避免多次操作才能格式化一部分的信息,采用一个格式化的字符串指出要被格式化的参数索引。索引必须紧跟在%后面,并以$终止。如下:
System.out.printf("%1$s %2$tB %2$te, %2$tY", "Due data:", new date());
结果如下:
Due data: November 16, 2021
另外还可以选择使用<标志。指示前面格式说明中的参数将再次使用。如:
System.out.printf("%s %tB %<te, %<tY", "Due date:", new date());
参数索引值从1开始,而不是从0开始,%1$对应了第一个参数格式变化。下图为格式说明符的语法图。
控制流程
java和很多其它程序一样,使用条件语句和循环结构确定空值流程。再次我们分别对循环语句,条件语句和switch语句进行讲解。
块作用域
块就是复合语句,一个大括号括起来的若干条简单的语句。块可以嵌套在另外一个块中。如下:
public static void main(String[] args){
int m;
{
int f = 0;
System.out.printf("%d\n", f);
}
}
块嵌套没有问题,但是不能在嵌套的两个块中声明同名的变量。如下是错误的。
public static void main(String[] args){
int m;
{
int m = 0;
System.out.printf("%d\n", f);
}
}
条件语句
说起来条件语句,小伙伴们可能更家熟悉了。
if(condition){satement}
条件需用括号括起来,执行语句最好用大括号括起来。在java中,一般的条件语句格式如下所示:
if(condition) statement1 else statement2
例如
boolean maxValue(int a, int b){
boolean ret = false;
if(a > b){
ret = true;
System.out.printf("a > b\n");
}
else{
ret = false;
System.out.printf("a < b\n");
}
return ret;
}
小伙伴们不难发现有些时候是只有if没有else的,else是可选的,与最临近的if构成一组。当然,有良好的编程习惯,我们用大括号将他们括起来,代码就更加清晰了。如下图为if和else交替出现的情况。
循环
当条件为true时,while循环执行一条语句(也可以是一个语句块)。格式为:while(condition){statement}
如果开始条件就为false,那么while循环体一次也不执行。如下图:
package com.ji;
import java.util.Scanner;
/**
*
* 功能:需要多长时间才能够存储一定数量的退休金,假设每年存入相同数量的金额,而且利率是固定的。
*/
public class Retirement {
public static void main(String[] args) {
//read input
Scanner in = new Scanner(System.in);
System.out.println("你需要存储多少退休金?");
double goal = in.nextDouble(); //当前目标
System.out.println("你每年存入固定的金额为多少?");
double payment = in.nextDouble(); // payment:支付
System.out.println("固定利率为%: ");
double interestRate = in.nextDouble();
double balance = 0;
int years = 0;
while(balance < goal) {
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
years++;
}
System.out.println("你可以在" + years + "年退休");
}
}
上述实例中有一个计数器balance,当总值超过目标值循环停止,进行输出。
while循环出来上述写法外,还有另外一种格式:
do{statement}while(condition);
上述例子可以如下表达:
public class Retirement {
public static void main(String[] args) {
//read input
Scanner in = new Scanner(System.in);
System.out.println("你需要存储多少退休金?");
double goal = in.nextDouble(); //当前目标
System.out.println("你每年存入固定的金额为多少?");
double payment = in.nextDouble(); // payment:支付
System.out.println("固定利率为%: ");
double interestRate = in.nextDouble();
double balance = 0;
int years = 0;
do{
balance += payment;
double interest = balance * interestRate / 100;
balance += interest;
years++;
}while(balance < goal);
System.out.println("你可以在" + years + "年退休");
}
}
确定循环
for循环语句是支持迭代的一种通用结构,利用每次迭代之后更新的计数器或类似的变量来控制迭代次数。如下图所示。
for循环语句的三个部分对同一个计数器变量进行初始化、检测和更新。如若不遵守这个规则,编写的循环晦涩难懂,可读性差。
另外循环的时候小伙伴么最容易犯的错误就是死循环,死循环就是,循环永远为真,不退出循环,除非我们的意愿就是要一个死循环,否则这样的做法是不可取的。当然也不是说就不可以死循环,比如我们做的服务程序,有时为了让其永久执行,就可以使用死循环处理。for循环和while循环殊途同归,for循环是While循环的一种简化形式。二者可以相互转换。
如下为for循环的典型案例,这个程序用来计算抽奖中奖的概率。例如,如果必须从1~50之间的数字中取6个数字抽奖,那么会有(50 x 49 x 48 x 47 x 46 45) / (1 x 2 x 3 x 4 x 5 x 6)种可能的结果,所有中奖的几率为1/15890700,具体实现如下:
import java.util.Scanner;
public class LotteryOdds {
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner in = new Scanner(System.in);
System.out.println("How many numbers do you need to draw?");
int k = in.nextInt();
System.out.println("What is the highest number you can draw?");
int n = in.nextInt();
int lotteryOdds = 1;
for(int i =1;i<=k;i++){
lotteryOdds = lotteryOdds*(n-i+1)/i;
System.out.println("Your odds are 1 in "+lotteryOdds+".Good luck");
}
}
}
上述公式 我们可以用for循环如下实现:
int lotteryOdds = 1;
for(int i = 0; i <= k; i ++){
lotteryOdds = lotteryOdds * (n - i + 1) / i;
}
多重选择:Switch语句
在处理多个选项时,for或While语句就显得生硬笨拙,施展不开拳脚,同样Java有一套完整的Switch语句。如下实例:
Scanner in = new Scanner(System.in);
System.out.print(“Select an option(1,2,3,4)”);
int choice = in.nextInt();
switch(choice){
case 1:
…
break;
case 2:
…
break;
case 3:
…
break;
case 4:
…
break;
default:
…
break;
}
小伙伴们都知道,switch语句从和他匹配的选项值开始执行,一直到遇到break语句为止。如果没有匹配的case标签,而有default语句,就执行这个语句。如果case语句的结尾没有break语句,是相当危险的,常常会引发错误。上述代码的流程图如下:
case 的标签可以是:
1、类型为char、byte、short或int的常量表达式。
2、枚举常量。
3、从Java SE 7开始,case 标签还可以是字符串字面量。
String input = …;
switch(niput.toLowerCase()){
case “yes”😕/ok since java SE 7
…
break;
}
中断控制流程语句
熟知的小伙伴一定知道中断控制流程语句,对,就是终端循环的语句,一个是goto,一个是break,
其实他们很好理解,goto就是跳转到goto关键字指定的位置开始执行:比如如下代码:
public class JavaLabel {
public static void main(String[] args) {
//此处可以自定义名
outer:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (j == 5) {
break outer;
}
System.out.println(j);
}
}
}
}
当break后有关键字是,就直接跳转到制定标签接着执行,如果没有关键字标签,那么就结束本次循环,继续向下执行。
说到break,我再顺便唠叨一句continue吧,break 是直接跳出循环,执行下面的语句,而continue则是结束本次循环,开启下次循环。如下代码:
for(int count = 1; count < 100; count ++){
System.out.print("Enter a number, -1 ot quit: ");
n = in.nextInt();
if(n < 0)continue;
sum += n;//not executed if n < 0
}
如果n < 0,则continue语句跳到count++语句。
好了,今天就先讲这么多吧,希望我今天讲的能帮助到有需要的小伙伴,也很感谢朋友能在百忙之中阅读这篇文章,您的进步是我最大的幸福。