java入门-W2

一. 输入输出

  • 输入的作用,就是由使用者告诉程序要操作的数据
    • 例如,我要通过饿了么订餐,你得告诉程序你要吃什么,送货地址是什么吧
  • 输出的作用,就是由程序向使用者展现执行的结果
    • 还是订餐的例子,程序向你展示骑手接单没有,送到哪里了
  • 将来输入输出来源会有多种,比如 app,网页,终端程序等等

1. System.out

之前已经介绍过【标准输出】:System.out

打开 jshell 用一下,回忆下对象和方法使用格式

对象.方法(参数);

套用一下,对象是 System.out,方法是 println,参数是 “你好”

jshell> System.out.println("你好");
你好

小技巧

  • jshell 中用 Tab 键可以提示对象有哪些方法
  • jshell 中省略 ; 也不会报错

2. System.in

再来看看输入,对象是 System.in,方法叫 read,没有参数

jshell> System.in.read();

运行后,可以看到光标一闪一闪,表示正在等待用户的输入,这时输入小 a

jshell> System.in.read();
a
$1 ==> 97
  • 会显示 97,称之为返回值,代表 read() 读入的结果

    • 因为计算机中所有符号,都是用数字表示,参看下表
  • 前面的 $1 是一个【变量】,将来它就代表 97,也就是刚才输入的小 a

  • 【变量】可以反复被使用

\ 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
0000
0016
0032 ! " # $ % & ( ) * + , - . /
0048 0 1 2 3 4 5 6 7 8 9 : ; < = > ?
0064 @ A B C D E F G H I J K L M N O
0080 P Q R S T U V W X Y Z [ \ ] ^ _
0096 ` a b c d e f g h i j k l m n o
0112 p q r s t u v w x y z { | } ~

System.in 的缺点

  1. 字符被转成的数字,不便人类阅读
  2. 只能输入一个字符

3. Scanner

用 Scanner 改进,System.in 是 java 为我们提供好的对象,而 Scanner 需要我们自己创建,语法是

jshell> new Scanner(System.in);
$2 ==> java.util.Scanner...

将来这个 $2 就代表刚才的 Scanner 对象,我们称之为【变量】

Scanner 对象里面最常用的方法是 nextLine,用法如下

jshell> $2.nextLine();
你好啊
$3 ==> "你好啊"

4. 变量名

$2$3 这样作为变量名虽然也可以,但如果用更有意义的名称来表示,更方便人类阅读、记忆。例如

jshell> var scanner = new Scanner(System.in)
scanner ==> java.util.Scanner[delimiters=\p{javaWhitespace}+] ... \E][infinity string=\Q∞\E]

jshell> var line = scanner.nextLine()
hello
line ==> "hello"
  • scanner 就代指输入对象
  • line 就代指用 nextLine() 读取到的字符串值
  • var 是关键字,代表某种类型,具体有哪些类型后面再展开

5. 关键字

变量取名时要注意两个规则,不能以数字开头,不能是关键字

什么是关键字呢?关键字就是 java 中有特殊意义的单词,例如见过的有 class,var,new 等等,如果用 idea 中可以通过特殊颜色强调哪些单词是关键字,可以看到这些蓝色的单词都属于关键字

至java 17 为止,共有 67 个关键字,参看这两份表格,这些关键字,都会在今后的课程中陆续学到

二. 类型、变量、运算符

1. 字符与字符串

字符值与字符串值

像这样用双引号引起来的值,在 Java 里称为字符串,字符串顾名思义,由多个字符组成,单个字符用单引号表示,例如

jshell> 'a'
$4 ==> 'a'

jshell> "abc"
$5 ==> "abc"
  • 单引号里必须由一个字符
  • 双引号里可以有零个、一个、多个字符

转义字符

比如我需要输出一个单引号字符值,''' 这样写行不行?本意是想表示中间的单引号,但遗憾的是java把前两个单引号当成了一对,把它当作了那个空字符了

怎么办呢

为了把真正的单引号跟语法的单引号区分开,需要给它加一个反斜杠标记,告诉java,我想表示真正的单引号,而不是语法中的单引号。试一下。

jshell> System.out.println("\'")
'

这种结合了反斜杠的具有特殊含义的字符,称之为转义字符(Escape Character)

常见的有七个:\' \" \\ \n \t \b \r 刚才已经讲过单引号转义了

继续来看几个例子

jshell> System.out.println("\"") // 双引号转义
"

jshell> System.out.println("\\") // 反斜杠本身转义
\

jshell> System.out.println("1\n2") // 换行
1
2

jshell> System.out.println("123\t4") // 缩进
123     4

jshell> System.out.println("123\b4") // 退格
124

jshell> System.out.println("123\r4") // 回车,退格是光标退一格,回车是退到头
423

文本块

最后再再来看看文本块,如果有一段文字内,其中需要有很多的转义字符,那么可读性会变得很差,例如

jshell> System.out.println("床前\"明月\"光,\n疑是地上霜。")

因此在 java 14 这个版本引入了文本块来进行改善。

jshell> System.out.println("""
                           床前"明月"光,
                           疑是地上霜。""")

文本块本质上还是属于字符串值,由一对 三个双引号作为起始和结束标记,中间如果想表示双引号、换行这两个特殊字符,无需再转义表示

  • 一个注意事项是 “”" 后需要换个行,不要紧接着写字符。

2. 类型

何为类型

现在让用户输入两个数,求得相加结果

jshell> scanner.nextLine()
1
$22 ==> "1"

jshell> scanner.nextLine()
2
$23 ==> "2"

jshell> $22 + $23
$24 ==> "12"

显然,这并不是我们想要的结果,它是输入的值当作了字符串,+ 号执行的是字符串连接操作,解决办法如下

jshell> scanner.nextInt()
1
$25 ==> 1

jshell> scanner.nextInt()
2
$26 ==> 2

jshell> $25 + $26
$27 ==> 3

nextLine() 和 nextInt() 返回的类型是不同的

  • 前者返回的是字符串,类型为 String,+ 表示两个字符串连接
  • 后者返回的是整数,类型为 int,+ 表示两个整数相加

数字类型

类型名 说明 数字范围 类型后缀
byte 整数类型,用1个字节表示 [ − 2 7 , 2 7 ) [-2^7,2^7) [27,27) [ − 128 , 128 ) [-128,128) [128,128)
short 整数类型,用2个字节表示 [ − 2 15 , 2 15 ) [-2^{15},2^{15}) [215,215)
int 整数类型,用4个字节表示 [ − 2 31 , 2 31 ) [-2^{31},2^{31}) [231,231)
long 整数类型,用8个字节表示 [ − 2 63 , 2 63 ) [-2^{63},2^{63}) [263,263) L
float 浮点小数,用4个字节表示 [ − 1.9999999 ∗ 2 127 , 1.9999999 ∗ 2 127 ] [-1.9999999 * 2^{127},1.9999999 * 2^{127}] [1.99999992127,1.99999992127] F
double 浮点小数,用8个字节表示 [ − 1.9999999 ∗ 2 1023 , 1.9999999 ∗ 2 1023 ] [-1.9999999 * 2^{1023},1.9999999 * 2^{1023}] [1.999999921023,1.999999921023] D
  • [] 包含等于,() 不包含等于

  • 类型后缀

    • 不区分大小写,但建议用大写,因为小写的 L 与 1 容易混淆
    • 尾符号 D 可以省略
  • float 和 double 精度不同,即小数点后的位数

    • float 的精度二进制是 23,换算成十进制是 6~7
    • double 的精度二进制是 52,换算成十进制是 15~16

字符类型

类型名 说明 范围
char 字符类型,配合单引号 [ 0 , 2 16 ) [0,2^{16}) [0,216) [ 0 , 65536 ) [0, 65536) [0,65536)
String 字符串类型,配合双引号或文本块 -

3. 变量与运算符

变量

变量的定义格式为

类型 变量名 =;
  • 从语法可以看到,变量由类型和名称组成,类型决定了变量能存储的数据大小与数据格式,名字用来代表后面的值
  • 这个语法其实咱们前面见过类似的,var scanner = new Scanner(System.in),这里 var 是类型,因为之前我们还没有学习具体有哪些类型,因此使用了 var 来代表某种类型,scanner是变量名,后面这一串就是值,也是 Scanner 对象

= 称之为赋值运算符,可以用来更新变量代表的值。

例如:

int a = 10

这行代码的意思是,定义了整型变量a,更新它的初始值为10

再来一句:

a = 20

这时候为啥不用写前面的类型了呢,因为变量定义只需一次,定义好之后变量就可以反复使用了,这行代码的意思是,将 a 所代表的值更新为 20

变量可以用来保存运算的结果,它自身也能参与运算

运算符

int a = 5 + 3

结果为 8

int a = 5 - 3

结果为 2

int a = 5 * 3

结果为 15

int a = 5 / 3

结果为 1,整数除法有两个注意点

  • 整数除法,只会保留商,而小数部分会被舍弃,并不考虑四舍五入
  • 除0是不合法的,会出错
int a = 5 % 3

结果为 2

小数加减乘除与整数类似,只是小数除法可以保留小数点后的数字,而且可以除零,例如

jshell> 5.0 / 3.0
$40 ==> 1.6666666666666667

jshell> 5.0 / 0.0
$41 ==> Infinity

增强赋值运算符

int a = 20; 
a = a + 10; 

意思是,获取 a 的原有值 20,执行加 10 运算,将新的结果重新赋值给 a,a 代表的值被更新成了 30。可以用 += 增强赋值运算符达到类似的效果

a += 10;

先不要看 = 号,获取 a 的原有值 30,执行加 10 运算,运算的结果 40 赋值给 a

a -= 10;

先拿 a 的原有值 40 与后面的 10 做减法,再将结果 30 赋值给 a

自增自减运算符

两个特殊运算符 ++ 与 –

++ 是让变量自增1,-- 是让变量自减1,举个例子

int a = 10;
a++;

结果为 11,++ 和 – 既可以写在变量之前,也可以写在变量之后,这两种写法的区别,我们到高级篇再讲,目前暂不用去了解

4. 练习 - 房贷计算器

【等额本息还款】法计算房贷

术语

  • 等额本息是指一种贷款的还款方式,是在还款期内,每月偿还同等数额的贷款(包括本金和利息)

  • 每月偿还的贷款可以通过下述公式计算

    p ∗ r ∗ ( 1 + r ) m / ( ( 1 + r ) m − 1 ) p * r * (1 + r)^m / ((1 + r)^m - 1) pr(1+r)m/((1+r)m1)

    • p 为贷款本金 principal
    • r 为月利率 monthlyInterestRate
    • m 为还款月数 months

公式中这些都是什么意思呢

例如:贷款 200 万元 ,对应公式中的 p,200 万就是贷款本金,年利率 6%,月利率 mr 就是 6% / 12 = 0.5%,假设 10 年还清,这时还款月数就是 360 个月。套入公式计算即可得到每月还多少钱

要完成这个计算,有一点没学过的是求这里的 1+ mr 的 m 次方,计算它需要用一个求幂方法,这个方法是 jdk 核心类库中 Math 这个类提供的,Math的字面意思是数学,Math 类中提供了很多与数学计算相关的方法,如果你以后有这方面需求,就找它。

Math.pow()

pow 是 static 方法,语法为 类名.方法名(参数值),它需要两个参数,参数1是底数,参数2是指数

jshell> Math.pow(2.0, 1)
$42 ==> 2.0

jshell> Math.pow(2.0, 2)
$43 ==> 4.0

jshell> Math.pow(2.0, 3)
$44 ==> 8.0

解答

打开 idea,编写 Calculator 类

public class Calculator {
    
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入贷款本金 p");
        double p = scanner.nextDouble();
        
        System.out.println("请输入年利率 r%");
        double yr = scanner.nextDouble();
        double mr = yr / 100.0 / 12.0;
        
        System.out.println("请输入贷款月数 m");
        int m = scanner.nextInt();
        
        System.out.println(p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1));
    }
}

数字格式化

对结果的数字进行格式化,让它以货币的格式来显示

需要借助核心类库中一个 NumberFormat 对象,字面意思是数字格式化,使用它的 getCurrencyInstance 方法来获取一个货币格式化对象,再使用它的 format 方法把 double 小数格式化为货币格式,格式化时也会保留两位小数

例子

System.out.println(NumberFormat.getCurrencyInstance(Locale.CHINA).format(1000000.00));   System.out.println(NumberFormat.getCurrencyInstance(Locale.US).format(1000000.00));
System.out.println(NumberFormat.getCurrencyInstance(Locale.GERMANY).format(1000000.00));
System.out.println(NumberFormat.getCurrencyInstance(Locale.KOREA).format(1000000.00));

输出

¥1,000,000.00
$1,000,000.00
1.000.000,00 €
₩1,000,000

如果 Locale 省略不写,默认为中国

房贷计算器可以改写为

double payment = p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1);       System.out.println(NumberFormat.getCurrencyInstance().format(payment));

查阅 Javadoc

javadoc 就是 java documentation 的缩写,我们下载的 jdk 中已经自带了,无需额外再下载。那怎么查阅 javadoc 呢,如果大家用的是 idea,那么可以通过一些快捷键来查阅java文档

  • 比如想看看类的文档,这时先按 Ctrl + N 查找类,假设我想看 Math 类的文档,输入要查阅的类名 Math,回车,可以跳转到这个类

  • 接下来我想看看方法的文档怎么办呢,按一下 ctrl + f12,列出当前类的所有方法,绿色表示可以使用方法,橙色带锁的,表示是该类一种特殊的私有方法,不能直接使用。找感兴趣的方法时,如果你懂一些英文单词,那么会有一定优势,例如你想找一个平方根方法,它对应的英文是 sqrt,这时敲入这几个字母,就会定位到方法,同样可以用翻译查看该方法的功能

  • 可以查到它的作用:返回一个数的平方根,这是方法名,查看后面括号内可以得知,需要一个参数,代表要求平方根的那个数字,是一个double 小数,方法名称前还有个 double 表示它的结果类型也是一个 double 小数

  • Math 中的方法大部分都是 static 方法,也就是配合类名使用的方法,之前也说过,用法为 类名.方法名(参数)

在平时写代码时,如果忘记了某个方法的作用,可以光标定位到该方法,按 Ctrl + Q 进行查阅,效果是类似的

三. 条件语句

编程时有一种重要的语句叫做条件语句,之前我们学过的都属于顺序语句,也就是从上至下,依次要执行每一行代码。

但是有的情况下,我们并不希望所有代码都执行,而是希望满足条件的代码才执行

例如:要对用户输入数据的合法性进行检查:

  • 贷款本金必须大于0
  • 贷款月数范围在 1 ~ 360 之间
  • 年利率范围在 1% ~ 36% 之间

如果你输入的值连这些条件都不满足,有必要去计算每月还款金额吗?

这种情况下,就要用到条件语句了,它的语法格式为

if(条件) {
    // 条件为 true 执行这里
} else {
    // 条件为 false 执行这里
}

什么意思呢,if 本意是如果,如果条件成立,执行代码1,else 本意是否则,即条件不成立,执行代码2,其中 else { } 语句块不是必须的,可以省略

那么条件这部分怎么写呢?对于数字类型可以借助比较运算符的运算结果来充当条件,参考下面的表格,这种表格列出了所有比较运算符

比较运算符 含义
a == b 判断 a 与 b 是否相等
a > b 判断 a 是否 > b
a >= b 判断 a 是否 >= b
a < b 判断 a 是否 < b
a <= b 判断 a 是否 <= b
a != b 判断 a 与 b 是否不相等

1. boolean 类型

判断的结果是布尔类型,可以充当条件,它的取值非真即假,真用 true 表示,假用 false 表示

2. 单条件

jshell> int a = 1000;
a ==> 1000

jshell> if(a > 0){
   ...>     System.out.println("ok");
   ...> } else {
   ...>     System.out.println("必须>0");
   ...> }
ok

jshell> a = -1000;
a ==> -1000

jshell> if(a > 0){
   ...>     System.out.println("ok");
   ...> } else {
   ...>     System.out.println("必须>0");
   ...> }
必须>0

房贷计算器改写如下

public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入贷款本金 p");
    double p = scanner.nextDouble();
    if(p > 0) {
        System.out.println("请输入年利率 r%");
        double yr = scanner.nextDouble();
        double mr = yr / 100.0 / 12.0;

        System.out.println("请输入贷款月数 m");
        int m = scanner.nextInt();

        double payment = p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1);
        System.out.println(NumberFormat.getCurrencyInstance().format(payment));
    } else {
        System.out.println("贷款金额必须大于0");
    }

}

3. 多条件

刚才我们在判断贷款本金的时候,只需要有一个大于 0 的条件就可以了,但是接下来我们要去检查年利率的时候,他是在一个范围之间,这就必须有两个条件,一个条件呢是要让年利率大于等于 1%,第二个条件呢,是让上年利率必须小于等于 36%,而且呢这两个条件你必须同时成立

多个条件可以用逻辑运算符连接

逻辑运算符 含义
条件1 && 条件2 && 意思是并且,两个条件必须同时成立,结果为 true
条件1 || 条件2 || 意思是或者,两个条件有一个成立,结果为 true
! 条件 ! 意思是取反

举例

jshell> int b = 120;
b ==> 120

jshell> if(b >= 1 && b <= 360) {
   ...>     System.out.println("ok");
   ...> } else {
   ...>     System.out.println("必须在1~360之间");
   ...> }
ok

jshell> b = 0
b ==> 0

jshell> if(b >= 1 && b <= 360) {
   ...>     System.out.println("ok");
   ...> } else {
   ...>     System.out.println("必须在1~360之间");
   ...> }
必须在1~360之间

jshell> b = 361
b ==> 361

jshell> if(b >= 1 && b <= 360) {
   ...>     System.out.println("ok");
   ...> } else {
   ...>     System.out.println("必须在1~360之间");
   ...> }
必须在1~360之间

房贷计算器改写如下

public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入贷款本金 p");
    double p = scanner.nextDouble();
    if(p > 0.0) {
        System.out.println("请输入年利率 r%");
        double yr = scanner.nextDouble();
        if (yr >= 1.0 && yr <= 36.0) {
            double mr = yr / 100.0 / 12.0;
            System.out.println("请输入贷款月数 m");
            int m = scanner.nextInt();
            if (m >= 1 && m <= 360) {
                double payment = 
                    p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1);
                System.out.println(NumberFormat.getCurrencyInstance()
                                   .format(payment));
            } else {
                System.out.println("贷款月数范围在 1 ~ 360 之间");
            }
        } else {
            System.out.println("年利率范围在 1% ~ 36% 之间");
        }
    } else {
        System.out.println("贷款金额必须大于0");
    }
}

4. 相反条件

我们这段代码,逻辑上没错,但你会发现不容易阅读

多层 if 嵌套导致代码的可读性变得很差,一旦大家写代码时出现了两层以上的 if 语句,就要小心了。如何改进呢?

这里给同学们介绍一种方法:可以去除 else 提高代码可读性

比如,现在有两个分支,c 是一个条件,要么走分支1,要么走分支2,用下面的 if else 可以表示

if(c) {
    // 分支1
} else {
    // 分支2
}

等价于

if(!c) {
    // 分支2
} else {
    // 分支1
}

能不能不写 else 呢?假设进入了分支2,分支2的代码执行后,程序还会继续向下执行,导致分支1也被执行

if(!c) {
    // 分支2
}
// 分支1 

可以改写为下面的形式,这样就避免了 else 出现

if(!c) {
    // 分支2
    return; 
}
// 分支1

总结一下,以上代码的等价转换,有一句口诀:条件取反,if else 倒置,return 一加, else 可去

变换有一定的规律:

原条件 相反条件1 相反条件2
p > 0.0 !(p > 0.0) p <= 0.0
yr >= 1.0 && yr <= 36.0 !(yr >= 1.0 && yr <= 36.0) yr < 1.0 || yr > 36.0
m < 1 || m > 360 !(m < 1 || m > 360) m >= 1 && m <= 360
  • 用逻辑与连接的两个条件,可以两个条件分别取反,然后&&变||

    • 年利率>=1.0 并且<=36.0 的相反条件是:年利率<1.0 或者 年利率>36.0
  • 用逻辑或连接的两个条件,可以两个条件分别取反,然后||变&&

这种逻辑变换规律称之为反演规则,公式记不住没关系,简单的反演我们自己就能想出来,复杂的变换才用公式保证正确性

房贷计算器改写如下

public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入贷款本金 p");
    double p = scanner.nextDouble();
    if (p <= 0.0) {
        System.out.println("贷款金额必须大于0");
        return;
    }
    System.out.println("请输入年利率 r%");
    double yr = scanner.nextDouble();
    if (yr < 1.0 || yr > 36.0) {
        System.out.println("年利率范围在 1% ~ 36% 之间");
        return;
    }
    double mr = yr / 100.0 / 12.0;
    System.out.println("请输入贷款月数 m");
    int m = scanner.nextInt();
    if (m < 1 || m > 360) {
        System.out.println("贷款月数范围在 1 ~ 360 之间");
        return;
    }
    double payment = p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1);
    System.out.println(NumberFormat.getCurrencyInstance().format(payment));

}

四. 循环语句

1. 循环语句语法

for(初始化变量; 终止条件; 更新变量) {
    // 循环体代码
}
  1. 初始化部分仅执行一次
  2. 执行终止条件,如果为 true 继续循环,如果为 false 退出循环
  3. 执行循环体代码
  4. 执行迭代部分,从 2 开始执行

例如

for(int i = 0; i < 3; i++) {
    System.out.println(i);
}

执行流程如下

  1. 声明一个变量 i,初始值为 0,用来控制循环次数
  2. 判断 i < 3,此时 i 取值是 0,条件为 true,继续循环
  3. System.out.println(0)
  4. i++,自增为 1
  5. 判断 i < 3,此时 i 取值是 1,条件为 true,继续循环
  6. System.out.println(1)
  7. i++,自增为 2
  8. 判断 i < 3,此时 i 取值是 2,条件为 true,继续循环
  9. System.out.println(2)
  10. i++,自增为 3
  11. 判断 i < 3,此时 i 取值是 3,条件为 false,退出循环

2. 变量的作用范围

上例中 i 的作用范围,仅在循环语句的 {} 内有效,现在要求求 1~100 的整数和,则需要把 sum 这个变量定义在 {} 外层

int sum = 0;
for (int i = 1; i <= 100; i++) {
    sum += i;
}
System.out.println(sum);

这里能否这样写

for (int i = 1; i <= 100; i++) {
    int sum = 0;
    sum += i;
}
System.out.println(sum);

不行,变量有它们各自的作用范围,从变量的定义开始,找到包围它的,右 } 括号为止。

  • 在它上面用它
  • 在右括号外用它

3. 练习 - 贷款计算详情

现在需要计算每月偿还的利息、偿还的本金、剩余的本金

例如,借款 1000 元,利息 100%,两月还清,根据公式计算出来每月还款 1333.33

月份 本月还款 偿还本金 偿还利息 剩余本金
1 1333.33 333.33 1000.00 666.67
2 1333.33 666.67 666.67 0
总额 2666.67 1000.00 1666.67

可以看到

  • 偿还利息 = 剩余本金 ∗ 月利率 偿还利息 = 剩余本金 * 月利率 偿还利息=剩余本金月利率
  • 偿还本金 = 每月还款 − 偿还利息 偿还本金 = 每月还款 - 偿还利息 偿还本金=每月还款偿还利息

房贷计算器改写如下

public static void main(String[] args) {

    Scanner scanner = new Scanner(System.in);
    System.out.println("请输入贷款本金 p");
    double p = scanner.nextDouble();
    if (p <= 0.0) {
        System.out.println("贷款金额必须大于0");
        return;
    }
    System.out.println("请输入年利率 r%");
    double yr = scanner.nextDouble();
    if (yr < 1.0 || yr > 36.0) {
        System.out.println("年利率范围在 1% ~ 36% 之间");
        return;
    }
    double mr = yr / 100.0 / 12.0;
    System.out.println("请输入贷款月数 m");
    int m = scanner.nextInt();
    if (m < 1 || m > 360) {
        System.out.println("贷款月数范围在 1 ~ 360 之间");
        return;
    }
    double payment = p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1);
    System.out.println(NumberFormat.getCurrencyInstance().format(payment));

    double totalInterest = 0.0;                 // 总利息
    for (int i = 1; i <= m; i++) {
        double interest = p * mr;               // 每月偿还利息
        double principal = payment - interest;  // 每月偿还本金
        p -= principal;                         // 剩余本金
        totalInterest += interest;
        System.out.print(i);
        System.out.print("\t本月还款:" + 
                         NumberFormat.getCurrencyInstance().format(payment));
        System.out.print("\t偿还本金:" + 
                         NumberFormat.getCurrencyInstance().format(principal));
        System.out.print("\t偿还利息:" + 
                         NumberFormat.getCurrencyInstance().format(interest));
        System.out.println("\t剩余本金:" + NumberFormat.getCurrencyInstance().format(p));
    }
    System.out.print("总还款额:" + 
                     NumberFormat.getCurrencyInstance().format(payment * m));
    System.out.println("\t总利息:" + 
                       NumberFormat.getCurrencyInstance().format(totalInterest));
}

五. 方法

将来代码多了,全部写在 main 方法里,会显得非常凌乱,难于阅读。这节课的目标是使用方法来改写前面的代码。

1. 定义方法

先来了解一下定义方法的语法

[访问修饰符] [static] 返回结果类型 方法名([参数类型1 参数名1, 参数类型2 参数名2, ...]) {
    // 方法体
    return 返回结果
}
  • 其中用 [] 的([] 不是语法的一部分)是可选部分
  • 访问修饰符先省略不写,后面再讲
  • static 这里先写上,这个前面提过,用 static 修饰的方法不属于对象
  • 方法执行完毕后可能会有返回结果
    • 如果没有返回结果,返回值类型填写 void
    • 如果有返回结果,填写实际的类型,例如
      • 返回整数,填写 int
      • 返回小数,填写 double
  • 方法名随便起,但应当做到望文生义
  • 参数是方法执行时需要传入的数据,可以有零到多个,格式为【参数类型 参数名】,多个参数之间用逗号分隔
  • {} 内写方法的具体代码
  • 最后 return 用来结束方法的运行,如果方法有返回结果,也需要配合 return 把结果传递给调用者

例如,想计算一下两个整数的和

class Test {
    static int add(int a, int b) {
        return a + b;
    }
}

2. 调用方法

回忆一下 Math.pow(2.0, 2) 就是一个由 java 提供好的 static 方法,它怎么用呢,Math 是类名,pow 是方法名,括号内是参数,对于我们自己写的 static 方法,用法是类似的:

类名.方法名([参数值1, 参数值2, ...])

Test.add(100,200);

怎么拿到返回结果呢?

int c = Test.add(100,200);

如果是调用本类 static 方法,可以省略前面的类名

3. 方法的意义

学完了方法的定义、调用流程,再来看看方法的意义

方法的一个意义在于隐藏实现细节:

例如,对于前面例子中的【等额本息】方式计算房贷,如果没有方法,那就要求编程者必须非常清楚计算公式

double payment = p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1);

假设有一位资深程序员(例如你)提供了计算房贷方法,那么编程者就只需要知道:

计算等额本金还款,需要一个名字叫calculate的方法

它需要三个参数,… ,至于具体的计算过程,被隐藏在了方法内部

double payment = calculate(p, mr, m);
  • p 贷款本金
  • mr 月利率
  • m 贷款月数

对于使用它的小白程序员来讲,无需了解它的实现细节,直接拿来用就可以了。小白程序员是站在你的肩膀上编程

方法的另一个意义在于减少重复代码、提高代码的可维护性:

对比以下代码,第一段是没用方法,如果有一处写错了,所有重复的地方都得修改

double p1 = p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1);
double p2 = p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1);
double p3 = p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1);

用了方法,万一写错,只需要一个地方的代码需要修改

static double calculate(double p, double mr, int m) {
    return p * mr * Math.pow((1 + mr), m) / (Math.pow((1 + mr), m) - 1);
}

4. 练习 - 贷款计算 - 方法改写

public class Calculator3 {
    public static void main(String[] args) {
        double p = inputAndCheckP();
        double mr = inputAndCheckMr();
        int m = inputAndCheckM();
        double payment = Calculator3.calculate(p, mr, m);
        printDetails(p, mr, m, payment);
    }

    static double inputAndCheckP() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入本金");
        double p = scanner.nextDouble();
        if(p <= 0) {
//            System.out.println("贷款金额必须 > 0");
            // throw new 异常类型("提示信息")
            throw new IllegalArgumentException("贷款金额必须 > 0");
        }
        return p;
    }

    static double inputAndCheckMr() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入年利率");
        double yr = scanner.nextDouble();
        if(yr < 1.0 || yr > 36.0) {
            throw new IllegalArgumentException("年利率必须是 1 ~ 36");
        }
        return yr / 12.0 / 100;
    }

    static int inputAndCheckM() {
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入还款月数");
        int m = scanner.nextInt();
        if(m < 1 || m > 360) {
            throw new IllegalArgumentException("贷款月数必须是 1 ~ 360");
        }
        return m;
    }

    static void printDetails(double p, double mr, int m, double payment) {
        for (int i = 0; i < m; i++) {
            double payInterest = p * mr;                    // 偿还利息
            double payPrincipal = payment - payInterest;    // 偿还本金
            p -= payPrincipal;                              // 剩余本金
            System.out.print ("月份:" + (i + 1));
            System.out.print("\t本月还款:" + NumberFormat.getCurrencyInstance().format(payment));
            System.out.print("\t偿还本金:" + NumberFormat.getCurrencyInstance().format(payPrincipal));
            System.out.print("\t偿还利息:" + NumberFormat.getCurrencyInstance().format(payInterest));
            System.out.println("\t剩余本金:" + NumberFormat.getCurrencyInstance().format(p));
        }
        System.out.println("总还款额:" + NumberFormat.getCurrencyInstance().format(payment * m));
    }


    /**
     * 以等额本息方式计算每月还款金额
     * @param p 本金
     * @param mr 月利率
     * @param m 还款月数
     * @return 每月还款金额
     */
    static double calculate(double p, double mr, int m) {
        double pow = Math.pow(1 + mr, m);
        return p * mr * pow / (pow - 1);
    }
}

说明1

大家抽取方法时有一个原则,就是把一组完整功能,所对应的多行代码抽取为一个方法,这里我们把计算还款总额和计算还款详情,分别抽取了两个方法

  • calculate
  • printDetails

抽取时,要点如下

  • 方法名要见文知义
  • 返回值不着急写,看看方法的外部需不需要用到这个方法内的变量
    • 需要,返回值定义为结果类型
    • 不需要,返回值定义为 void
  • 参数也不着急写,看方法内缺哪些变量定义,以它们为方法参数

说明2

对于 calculate 这种比较重要的方法定义,最好给它加一个文档,你得告诉将来这个方法的使用者,怎么用这个方法,每个参数是什么意思。

先写斜杠两个星号的开始,不用着急写它的结束,直接一回车。idea 就会自动生成一段 javadoc 文档,你可以在这里介绍方法的作用

  • 在 @param 这里对每个参数进行说明
  • 在 @return 这里对返回值进行说明。

说明3

如果在某些验证不通过,想让剩余代码不要运行,可以利用 throw 语法

  • return 这种语法叫正常返回,也就是当方法调用后,代码还会继续运行
  • 还有就是这种throw 语法,它称为异常返回,如果没有额外处理,代码从方法调用后就中断运行

六. package

1. package

随着我们写的类越来越多,把他们都放在一块儿来管理,感觉比较的不规范,因此,我们要引入一个新的package语法,对源文件进行一个更好的管理。

其实这个package说白了就是Java中一种目录结构

|-1
    |- 从属于包1 的类
|-2
    |- 从属于包2 的类

语法:

package 包名; // 告诉下面的类从属于此包

class{
    
}

包的命名一般都是域名的倒置,如

  • baidu.com 域名的倒置就是 com.baidu
  • bilibilicom 域名的倒置就是 com.bilibili

2. import

与 package 关系非常密切的一个语法:import,如果你的类想使用另外一个类,而两个类不同包,这时就必须用 import,把另一个类导入进来才能使用

package com.itheima.a;

import java.util.Scanner;

class Calulator {
    public static void main(String[] args) {
        // 要用到 Scanner, 这时就用用到上面的 import 语句
    }
}
  • 有一种特殊情况不需要 import 导入,即 java.lang 包下的类使用时,都不需要 import

你可能感兴趣的:(java,开发语言)