环境搭建

✅ 下载 JDK 8/17,添加环境变量
✅ 下载 IntelliJ IDEA

编写第一个程序(Hello World)

1
2
3
4
5
public class Main {
public static void main(String[] args) {
System.out.printf("Hello, world!");
}
}

Java 运行机制

执行流程

  1. 编写.java文件:在Java中,源代码通常被编写为.java文件

  2. 编译.java文件:使用Java编译器(javac命令)将.java文件编译成字节码文件(.class文件)

  3. 执行.class文件:使用Java虚拟机(java命令)执行.class文件,产生程序执行结果

  4. 程序执行结果:程序执行的结果通常是控制台输出或图形用户界面

注意事项

  1. Java对大小写敏感

  2. 每个.java文件只能有一个public类,其他类不限

  3. 每一个类编译后,都是单独的.class

  4. 文件名需要跟public类名一致

  5. 不同的类可以有相同的方法名

Java 程序基础

转义字符

类型 转义字符 作用
制表符 \t 对齐文本
换行符 \n 换行
反斜杠 \\ 显示反斜杠
单引号 \‘ 显示单引号
双引号 \“ 显示双引号
回车符 \r 回车,与换行相当
unicode编码 \uXXXX 编码对应的字符

标识符

合法标识符

1
2
3
4
5
6
username
user_name
a1
b2
$username
_username

非法标识符

1
2
3
4
2sum
your name
#yourname
int //此为关键字

常用关键字

abstract continue finally interface public transient
boolean default float long return true
break do for native short try
byte double if new static void
case else implements null synchronized volatile
catch extends import package super while
char false instanceof private this const
class final int protected throw goto

注释

单行注释

1
int width = 5; //定义整型变量width,并赋值为5

多行注释

1
2
3
/* 第一行
第二行
第三行 */

方法注释

1
2
3
4
5
6
7
8
/**
该方法用于求一组数中的最大值

@author 作者名
@param a 一组数
@return 返回最大值
*/
public int GetMax(int[] a)

String 转数值的常用方法

1
2
Integer.parseInt();
Float.parseFloat();

程序练习①

计算长方形的周长和面积

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Main {
public static void main(String[] args) {
float length, width;
if (args.length == 0) {
System.out.println("你没有输入参数!");
return;
}
length = Float.parseFloat(args[0]);
width = Float.parseFloat(args[1]);

float perimeter = 2f * (length + width);
float area = length * width;

System.out.println("周长:" + String.format("2 * (%f + %f) = %f",
length, width, perimeter));
System.out.println("面积:" + String.format("%f * %f = %f",
length, width, area));
}
}
1
java Main.class 11 45
Text
1
2
周长:2 * (11.000000 + 45.000000) = 112.000000
面积:11.000000 * 45.000000 = 495.000000

计算整数平方(使用 System.in 输入数据)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.io.*;

public class Main {
public static void main(String[] args) {
int data = 0,result;
System.out.print("输入一个整数:");

try{
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
data = Integer.parseInt(bufferedReader.readLine());
}
catch(IOException e){
System.out.println("输入的类型不正确");
e.printStackTrace(System.err);
}

result = data * data;
System.out.printf("输入的内容:%d\n平方值:%d", data, result);
}
}
Text
1
2
3
输入一个整数:1145
输入的内容:1145
平方值:1311025

Scanner 类输入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.io.InputStream;
import java.util.Scanner;

public class Main {
public static void main(String[] args) {
InputStream inputStream = System.in;
Scanner scanner = new Scanner(inputStream);

System.out.print("请输入名称:");
String name = scanner.next();

System.out.print("请输入价格:");
int price = scanner.nextInt();

System.out.printf("名称:%s\n", name);
System.out.printf("价格:%d\n", price);
}
}
Text
1
2
3
4
请输入名称:maimai
请输入价格:80000
名称:maimai
价格:80000

基本数据类型

数据类型 关键字 占用内存空间 取值范围(十进制)
字节型 byte 1B (8bit) -128~127
短整型 short 2B (16bit) -32768~32767
整型 int 4B (32bit) -2147483648~2147483647
长整型 long 8B (64bit) -9223372036854775808~9223372036854775807
单精度浮点型 float 4B (32bit) -3.4E38~3.4E38
双精度浮点型 double 8B (64bit) -1.7E308~1.7E308
布尔型 boolean 1B (8bit) true/false
字符型 char 2B (16bit) 0~65535

八进制和十六进制

八进制以0开头

十六进制以0x开头

1
2
3
4
int a = 05; //5
int b = 011; //9
int c = 0xF; //15
int d = 0x11; //17

按位运算符

类型 符号
按位与(AND) &
按位或(OR) |
按位异或(XOR) ^
按位取反 ~

位移运算符

符号 作用
>> 符号位不动,其余位右移,符号位后边正数补0,负数补1。又称带符号右移(除以2)
>>> 符号位一起右移,左边补0。又称无符号右移
<< 左移,右边补0。左移没有符号位(乘以2)

数据类型自动转换

不会出现问题的类型转换,编程语言可以做自动类型转换,比如低精度的数字向高精度的数字转换

自动类型转换可以发生在算数运算,也可以发生在赋值

数值精度顺序:double>float>long>int>short>byte

if-else 与 else if 语句

1
2
3
4
5
6
7
8
9
10
int a = 1145;
int b = 1919;

if (a > b) {
System.out.println("a大于b");
} else if (a < b) {
System.out.println("a小于b");
} else {
System.out.println("a等于b");
}

for,foreach 与 while 循环语句

break跳出整个循环,continue跳出当前循环,进行下一个循环

循环可嵌套

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 逐行输出0-9
for (int i = 0; i < 10; i++) {
System.out.println(i);
}

// foreach 逐行输出a数组内容
int[] a = {1,1,4,5};
for (int num: a) {
System.out.println(num);
}

// 逐行输出0-9
while (i < 10) {
System.out.println(i);
i++;
}

switch 语句

if-else两值比较的替代方案(可读性更好)

如果没有break截断,程序会一直执行下去

1
2
3
4
5
6
7
8
9
10
switch (n) {
case 1:
//...
break;
case 2:
//...
break;
default:
//...
}

变量的作用域

代码块里使用外层代码块的变量

代码块里创建变量

不能在外层代码块里使用内层代码块的变量。是否可以使用变量,也称作变量在某个代码块的可见性。也就是说,外层代码块创建的变量对内层代码块可见。内层代码块中创建的变量对外层代码块不可见

内层命名空间不可以重复定义外层代码块的变量,但是可以使用外层代码块的变量

代码块无论嵌套多少层,都遵守上述变量可见性的

作用域和命名空间

同一个命名空间中的变量不可以重名

为了避免变量名冲突,所以必须有命名空间

常量修饰符

标识符常量使用关键字 final 进行修饰,变量名采用大写字母

1
final double PI = 3.1425;

条件运算符

又称三目运算符,格式如下

1
表达式1 ? 表达式2 : 表达式3

表达式1 == true时取表达式2的结果,反之取表达式3的结果

条件表达式可嵌套使用,例如

1
2
int y, x = -1;
y = (x > 0) ? 5 : (x < 0) ? -8 : 0;

程序练习②

输出九九乘法表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void main(String[] args) {
for (int i = 1; i <= 9; i++) {
String content = "";
for (int j = 1; j <= 9; j++) {
if (j > i) {
break;
}
content += String.format("%d*%d=%d\t", i, j, i * j);
}
System.out.println(content);
}
}
}
Text
1
2
3
4
5
6
7
8
9
1*1=1	
2*1=2 2*2=4
3*1=3 3*2=6 3*3=9
4*1=4 4*2=8 4*3=12 4*4=16
5*1=5 5*2=10 5*3=15 5*4=20 5*5=25
6*1=6 6*2=12 6*3=18 6*4=24 6*5=30 6*6=36
7*1=7 7*2=14 7*3=21 7*4=28 7*5=35 7*6=42 7*7=49
8*1=8 8*2=16 8*3=24 8*4=32 8*5=40 8*6=48 8*7=56 8*8=64
9*1=9 9*2=18 9*3=27 9*4=36 9*5=45 9*6=54 9*7=63 9*8=72 9*9=81

猜数字游戏

其中用到了Math.random()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import java.util.Scanner;

public class RandomNumber {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

int rangeStart = 1;
int rangeEnd = 15;
int guessTotal = 5;

int totalGameCount = 0;
int totalCorrectCount = 0;

boolean stopGame = false;

System.out.println("输入负数结束游戏");

while (!stopGame) {
int guessLeft = guessTotal;
int mod = rangeEnd - rangeStart;
double randNum = Math.random();

//实现 (rangeStart, rangeEnd) 随机数范围
int num = ((int) (randNum * rangeEnd * 100)) % mod;

num += rangeStart;
if (num <= rangeStart) {
num = rangeStart + 1;
} else if (num >= rangeEnd) {
num = rangeEnd - 1;
}

System.out.printf("剩余机会:%d%n", guessLeft);
while (guessLeft > 0) {
System.out.printf("请输入数字,范围(%d, %d):", rangeStart, rangeEnd);
int guess = scanner.nextInt();

if (guess < 0) {
stopGame = true;
break;
}
totalGameCount++;
guessLeft--;
if (guess > num) {
System.out.println("输入的数字太大");
} else if (guess < num) {
System.out.println("输入的数字太小");
} else {
totalCorrectCount++;
System.out.println("数字正确");
break;
}
}
System.out.printf("进行了%d次游戏,其中猜对了%d次%n", totalGameCount, totalCorrectCount);
}
}
}

数组

数组是相同类型的变量的集合

每个元素都有一个固定的编号,称为索引(index),从0开始递增,类型为int

可以像造作变量一样读写数组中的任何一个元素

1
2
3
int[] intArray = new int[10];

System.out.println(intArray[0]); //输出第一个元素

二维数组

二维数组是一维数组的自然延伸

1
double[][] scores = new double[3][6];

程序练习③

随机生成成绩并查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.util.Scanner;

public class QueryGrades {
public static void main(String[] args) {
String[] names = {"语文", "数学", "英语", "物理", "化学", "生物"};
int namesCount = names.length;

Scanner scanner = new Scanner(System.in);

System.out.print("请输入要生成多少年的成绩:");
int yearCount = scanner.nextInt();

double[][] scores = new double[yearCount][namesCount];

// 随机生成成绩
for (int i = 0; i < yearCount; i++) {
for (int j = 0; j < namesCount; j++) {
scores[i][j] = 80 + Math.random() * 20;
}
}

//查询成绩
while (true) {
System.out.print("要查询第几年的成绩(无效数字将结束查询):");
int year = scanner.nextInt();
if (year <= 0){
break;
}

System.out.print("要查看的课程编号:");
int course = scanner.nextInt();

System.out.printf("第%d年的%s成绩是:%f%n", year, names[course - 1], scores[year - 1][course - 1]);
}
}
}

Java 面向对象

类(class)和对象

1
2
3
4
5
6
// 一个学生的类
public class Student {
String name;
int age;
int gender;
}

操作对象的变量(点操作符)

1
2
3
4
5
6
7
8
Student stu1 = new Student(); // 创建一个学生类的对象

// 给类的变量赋值
stu1.name = "王小美";
stu1.age = 20;
stu1.gender = 1;

System.out.println(stu1.name); // 输出王小美

类和对象的关系

类是对象的模版,对象是类的一个实例

一个Java 程序中类名相同的类只能有一个,也就是类型不会重名

一个类可以有很多对象

一个对象只能根据一个类来创建

引用和类以及对象的关系

引用必须是、只能是一个类的引用

引用只能指向其所属的类型的类的对象

相同类型的引用之间可以赋值

只能通过指向一个对象的引用,来操作一个对象,比如访问某个成员变量

package 和 import

为了避免类在一起混乱,可以把类放在文件夹里。这时就需要用 package 语句告诉 Java 这个类在哪个 packace里。 package 语句要和源文件的目录完全对应,大小写要一致

package 读作包。一般来说,类都会在包里,而不会直接放在根目录

不同的包里可以有相同名字的类

一个类只能有一个 package 语句,如果有 package 语句,则必须是类的第一行有效代码

1
package com.phone;

当使用另一个包里的类的时候,需要带上包名

每次使用都带包名很繁琐,可以在使用的类的上面使用 import 语句,一次性解决问题,就可以直接使用类了。就好像我们之前用过的 Scanner 类

import 语句可以有多个

如果需要 import 一个包中的很多类,可以使用*通配符

1
import com.phone.parts.*;

类的全限定名

包名 + 类名 = 类的全限定名。也可以简称为类的全名

同一个 Java 程序中全限定名字不可重复

属性访问修饰符

被 public 修饰的属性,可以被任意包中的类访问

没有访问修饰符的属性,称作缺省的访问修饰符,可以被本包内的其他类和自己的对象

访问修饰符是一种限制或者允许属性访问的修饰符

程序练习④

简易超市

Text
1
2
3
4
5
6
7
8
com
└─bluesdawn
├─person
│ └─Customer.java
├─supermarket
│ ├─LittleSuperMarket.java
│ └─Merchandise.java
└─Run.java
1
2
3
4
5
6
7
package com.bluesdawn.person;

public class Customer {
public String name;
public double money;
public boolean isDrivingCar;
}
1
2
3
4
5
6
7
8
9
10
package com.bluesdawn.supermarket;

public class LittleSuperMarket {
public String superMarketName;
public String address;
public int parkingCount;
public double incomingSum;
public Merchandise[] merchandises;
public int[] merchandiseSold;
}
1
2
3
4
5
6
7
8
9
package com.bluesdawn.supermarket;

public class Merchandise {
public String name;
public String id;
public int count;
public double soldPrice;
public double purchasePrice;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package com.bluesdawn;

import com.bluesdawn.person.Customer;
import com.bluesdawn.supermarket.LittleSuperMarket;
import com.bluesdawn.supermarket.Merchandise;

import java.util.Scanner;

public class Run {
public static void main(String[] args) {
LittleSuperMarket littleSuperMarket = new LittleSuperMarket();
littleSuperMarket.superMarketName = "SBGA超市";
littleSuperMarket.address = "霓虹";
littleSuperMarket.parkingCount = 100;
littleSuperMarket.merchandises = new Merchandise[100];
littleSuperMarket.merchandiseSold = new int[littleSuperMarket.merchandises.length];

Merchandise[] all = littleSuperMarket.merchandises;

for (int i = 0; i < all.length; i++) {
Merchandise m = new Merchandise();
m.count = 514;
m.id = "ID" + i;
m.name = "乌蒙地插" + i;
m.purchasePrice = Math.random() * 35000;
m.soldPrice = (1 + Math.random()) * 40000;

all[i] = m;
}

System.out.println("开始营业");

boolean open = true;
Scanner scanner = new Scanner(System.in);

while (open) {
System.out.println("--------------------------");
System.out.println("本店名称:" + littleSuperMarket.superMarketName);
System.out.println("本店地址:" + littleSuperMarket.address);
System.out.println("本店停车位:" + littleSuperMarket.parkingCount);
System.out.println("本店营业额:" + littleSuperMarket.incomingSum);
System.out.println("本店商品种类:" + littleSuperMarket.merchandises.length);
System.out.println("--------------------------");

Customer customer = new Customer();
customer.name = "编号" + (int) (Math.random() * 10000);
customer.money = (1 + Math.random()) * 50000;
customer.isDrivingCar = Math.random() > 0.5;

if (customer.isDrivingCar) {
if (littleSuperMarket.parkingCount > 0) {
System.out.println(customer.name + "顾客驾车,车位编号" + littleSuperMarket.parkingCount);
littleSuperMarket.parkingCount--;
} else {
System.out.println("车位已满,欢迎下次光临");
continue;
}
} else {
System.out.println("欢迎" + customer.name + "光临本店");
}
System.out.println("本店提供" + all.length + "种商品,欢迎选购");

double totalCost = 0;
while (true) {
System.out.print("请输入商品编号:");
int index = scanner.nextInt();

if (index < 0) {
break;
}

if (index >= all.length) {
System.out.println("商品编号不正确,请确保编号在" + (all.length - 1) + "(含)之内");
}

Merchandise m = all[index];

System.out.println("要选购的是" + m.name + "\n单价是" + m.soldPrice);
System.out.print("要购买多少台:");
int numToBuy = scanner.nextInt();

if (numToBuy <= 0) {
System.out.println("顾客取消购买");
}

if (numToBuy > m.count) {
System.out.println("此商品没有那么多的库存");
}

if (numToBuy * m.soldPrice > customer.money) {
System.out.println("顾客所带的钱不够,剩余" + customer.money);
continue;
}

totalCost += numToBuy * m.soldPrice;

m.count -= numToBuy;
littleSuperMarket.merchandiseSold[index] += numToBuy;

customer.money -= totalCost;
}

if (customer.isDrivingCar) {
littleSuperMarket.parkingCount++;
}

System.out.println("顾客" + customer.name + "共消费了" + totalCost);

littleSuperMarket.incomingSum = totalCost;

System.out.print("还继续营业吗(Y/N):");
String status = scanner.next();
if (status.matches("^([Nn][Oo]|[Nn])$")) {
open = false;
}
}

System.out.println("结束营业");
System.out.println("--------------------------");
System.out.println("今天的营业额为" + littleSuperMarket.incomingSum);
System.out.println("营业情况如下:");

for (int i = 0; i < littleSuperMarket.merchandiseSold.length; i++) {
Merchandise m = all[i];
int numSold = littleSuperMarket.merchandiseSold[i];
if (numSold > 0) {
double incoming = m.soldPrice * numSold;
double netIncoming = (m.soldPrice - m.purchasePrice) * numSold;
System.out.printf("售出了%d台%n销售额为%f%n净利润为%f%n", numSold, incoming, netIncoming);
}
}
System.out.println("--------------------------");
}
}

类的行为(方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.bluesdawn.supermarket;

public class Merchandise {
public String name;
public String id;
public int count;
public double soldPrice;
public double purchasePrice;
}

// public 访问修饰符
// void 返回值类型(无返回值)
// describe() 方法名
// 内部代码为方法体
public void describe() {
System.out.println("内容");
}

方法的返回值类型

返回值类型可以是自定义的类名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//无返回值
public void print() {
System.out.println("Hello world!");
}

//整型
public int getInt() {
return 2;
}

//双精度浮点型
public double getDouble() {
return 1.414;
}

//布尔型
public boolean getBoolean() {
return true;
}

方法传递参数

1
2
3
public int add(int a, int b) {
return a + b;
}

参数和返回值是怎么传递的

参数和方法里的局部变量可以认为是一样的东西。只是在方法调用之前,会用实参给参数的形参赋值

发生在代码块里的,就让它留在代码块里。方法执行完毕,参数和方法的局部变量的数据就会被删除回收。就好像演草纸,作用是计算一个值,算好之后,演草纸就可以扔了

调用一个有返回值的方法时,就好像访问一个成员变量

1
2
3
4
5
6
7
8
// 参数的传递,其实就是赋值。左边是形参,右边是括号里的形参
// 参数本身可以是一个表达式,只要表达式的值类型可以和参数类型匹配就可以
double totalCost = m.buy((c + 2) * 5);
System.out.println("商品总价为:" + totalCost);

// 对于引用类型,参数同样是一个表达式
boolean biggerThan = m.totalValueBiggerThan(littleSuperMarket.merchandises[index + 1]);
System.out.println(biggerThan);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 方法里的代码并不能改变实参的值
// 方法里使用的参数相当于一个局部变量。使用方法前,会用实参给局部变量赋值
int paramPrime = 7;
Merchandise paramRef = littleSuperMarket.merchandises[2];

m.willOutsideValueChangeIfParameterVallueChangeHerePrime(paramPrime);
// 形参实参赋值:int intVal = paramPrime;
// 方法里执行:intVal = 99999999;
m.willOutsideValueChangeIfParameterVallueChangeHereRef(paramRef);
// 形参实参赋值:Merchandise m = paramRef;
// 方法里执行:m = gift;

// 调用后输出发现实参没有发生变化
System.out.println(paramPrime);
System.out.println(paramRef);

this 自引用

方法里隐藏着一个this自引用,指向调用这个方法的对象。

使用一个对象调用方法,也叫做在这个对象上调用方法。因为方法可以访问这个对象的值

访问一个成员变量的完整形态,是“this.成员变量的名字”

1
2
3
4
public void addCount(int count) {
this.count += count;
System.out.println("该方法使用的对象是:" + this);
}

Java 进阶

把操作成员的变量的代码都放在类里

  • 初始化成员变量

  • 简单访问和设置成员变量的值 (Java Bean)

  • 专有的一些计算逻辑

  • 用类定义成员变量,并把操作成员变量的代码都放在类里,就是封装

    • 可以集中管控,自己的成员变量别人不可以乱来,避免出现非法的状态,比如库存为负数

    • 代码逻辑可以公用,避免代码重复,修改的时候只需改一处

    • 封装的好,可以更好的抽象一类事物

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bluesdawn.supermarket;

public class Merchandise {
public String name;
public String id;
public int count;
public double soldPrice;
public double purchasePrice;

public void init(String name, String id, int count, double soldPrice, double purchasePrice) {
this.name = name;
this.id = id;
this.count = count;
this.soldPrice = soldPrice;
this.purchasePrice = purchasePrice;
}

public void describe() {
System.out.printf("名称:%s%n编号:%s%n库存:%d%n单价:%f%n进价:%f%n毛利润:%f%n",
name, id, count, soldPrice, purchasePrice, soldPrice - purchasePrice);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package com.bluesdawn;

import com.bluesdawn.supermarket.Merchandise;

public class Run {
public static void main(String[] args) {
Merchandise merchandise = new Merchandise();
// 使用自身方法实现成员变量初始化
merchandise.init("书桌", "DESK1145", 40, 99, 59);
merchandise.describe();
}
}

方法的签名和重载

方法签名:方法名+依次参数类型。注意,返回值不属于方法签名。方法签名是一个方法在一个类中的唯一标识

同一个类中方法可以重名,但是签名不可以重复。一个类中如果定义了名字相同,签名不同的方法,就叫做方法的重载

看代码:重写我们的购买方法,理解方法签名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public double buy() {
return buy(1);
}

public double buy(int count) {
return buy(count, false);
}

public double buy(int count, boolean isVip) {
if (this.count < count) {
return -1;
}
this.count -= count;
double totalCost = count * soldPrice;
if (isVip) {
return totalCost * 0.95;
} else {
return totalCost;
}
}

重载参数的匹配规则

依次使用 byte,short,int,long,float,double 类型的参数调用buy方法,哪个方法会被调用呢?

无论是否重载参数类型可以不完全匹配的规则是“实参数可以自动类型转换成形参类型”

重载的特殊之处是,参数满足自动自动类型转换的方法有好几个,重载的规则是选择最“近”的去调用

构造实例的方法

构造方法 (constructor) 的方法名必须与类名一样,而且构造方法没有返回值

构造方法也可以重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.bluesdawn.supermarket;

public class Merchandise {
public String name;
public String id;
public int count;
public double soldPrice;
public double purchasePrice;

public Merchandise(String name, String id, int count, double soldPrice, double purchasePrice) {
this.name = name;
this.id = id;
this.count = count;
this.soldPrice = soldPrice;
this.purchasePrice = purchasePrice;
}

public void describe() {
System.out.printf("名称:%s%n编号:%s%n库存:%d%n单价:%f%n进价:%f%n毛利润:%f%n",
name, id, count, soldPrice, purchasePrice, soldPrice - purchasePrice);
}
}
1
2
3
4
5
6
7
8
9
10
11
package com.bluesdawn;

import com.bluesdawn.supermarket.Merchandise;

public class Run {
public static void main(String[] args) {
// 使用构造方法实现成员变量初始化
Merchandise merchandise = new Merchandise("书桌", "DESK1145", 40, 99, 59);
merchandise.describe();
}
}

静态变量

静态变量使用 static 修饰符

静态变量如果不赋值,Java也会给它赋以其类型的初始值

静态变量一般使用全大写字母加下划线分割。这是一个习惯用法

所有的代码都可以使用静态变量,只要根据防范控制符的规范,这个静态变量对其可见即可

比如 public 的静态变量,所有的代码都可以使用它

1
public static double DISCOUNT_FOR_VIP = 0.95;
1
2
// 引入一个类的所有静态变量
import static com.bluesdawn.supermarket.Merchandise.*;

静态变量一旦变化,所有使用这个静态变量的地方的值都会变

静态方法

静态方法可以访问静态变量,包括自己类的静态变量和在访问控制符允许的别的类的静态变量

静态方法不属于某个实例,隐藏没有this自引用

1
2
3
public static double getVIPDiscount() {
return DISCOUNT_FOR_VIP;
}

静态代码块

1
2
3
4
5
6
7
static {
BASE_DISCOUNT = 0.99;
VIP_DISCOUNT = 0.85;
SVIP_DISCOUNT = 0.75;

System.out.println("静态代码块里的SVIP_DISCOUNT" + SVIP_DISCOUNT);
}

String 的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
String.length(); // 字符串长度

String.toUpperCase(); //大写字母
String.toLowerCase(); //小写字母

String.charAt(1); // 第2个字符

String.substring(5); // 6+
String.substring(1, 5); // 2-5

String.split("_"); // 分割字符串
String.indexOf("_"); // 第一次出现_的索引
String.lastIndexOf("_");
String.contains("apple"); // 字符串是否包含apple
String.equals(anotherString); // 比较两个字符串是否相同
String.equalsIgnoreCase(anotherString); // 比较两个字符串是否相同(不区分大小写)

String.trim(); //清除字符串前后的空格

程序练习⑤

简易实现ai智障对话

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package com.bluesdawn.talk;

public class Talk {
public String answer(String question) {
String ret;
ret = handleCanStart(question);
if (ret != null) {
return ret;
}
ret = handleAskTail(question);
if (ret != null) {
return ret;
}
return handleUnknown(question);
}

public String handleCanStart(String question) {
String[] canStart = new String[]{"会", "能", "有", "敢", "在"};
for (String s : canStart) {
if (question.startsWith(s)) {
return s + "!";
}
}
return null;
}

public String handleAskTail(String question) {
String[] askTail = new String[]{"吗?", "吗?", "吗"};
for (String s : askTail) {
if (question.endsWith(s)) {
return question.replace(s, "!");
}
}
return null;
}

public String handleUnknown(String question) {
return question + "!";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.bluesdawn.talk;

import java.util.Scanner;

public class Main {
public static void main(String[] args) {
Talk talk = new Talk();
Scanner in = new Scanner(System.in);
while (true) {
String input = in.next();
if ("exit".equals(input)) {
System.out.println("再见");
break;
}
String answer = talk.answer(input);
System.out.println(answer);
}
}
}

System 扩展

1
System.currentTimeMillis(); //取当前毫秒

StringBuilder

1
2
3
4
5
6
7
8
9
// 要注意的是以下代码都是对stringBuilder原对象进行操作
StringBuilder stringBuilder = new StringBuilder("此内容可空");

stringBuilder.append("某些内容").append("114514"); // 往StringBuilder添加字符串,可嵌套

stringBuilder.toString();
stringBuilder.reverse.toString(); // 字符串反转
stringBuilder.delete(0, 4).toString(); // 删除前四个字符
stringBuilder.insert(3, "LLLLL").toString(); // 在第三个字符后插入LLLLL
1
2
3
4
5
@Override
public StringBuilder append(String str) {
super.append(str);
return this;
}

类的继承

1
2
3
4
5
6
public class 子类名 extends 要继承的父类 {
public double buy(int count) {
System.out.println("购买成功");
return super.buy(count); // 可以使用super调用父类的方法和属性
}
}

父类与子类的引用赋值关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
        Phone ph = new Phone(
"手机001", "Phone001", 100, 1999, 999,
4.5, 3.5, 4, 128, "索尼", "安卓"
);

// >> TODO 可以用子类的引用给父类的引用赋值,也就是说,父类的引用可以指向子类的对象

MerchandiseV2 m = ph;
MerchandiseV2 m2 = new Phone(
"手机002", "Phone002", 100, 1999, 999,
4.5, 3.5, 4, 128, "索尼", "安卓"
);

// >> TODO 但是反之则不行,不能让子类的引用指向父类的对象。因为父类并没有子类的属性和方法呀

// Phone notDoable = new MerchandiseV2();

// >> TODO 重点
// >> TODO 因为子类继承了父类的方法和属性,所以父类的对象能做到的,子类的对象肯定能做到
// TODO 换句话说,我们可以在子类的对象上,执行父类的方法
// >> TODO 当父类的引用指向子类的实例(或者父类的实例),只能通过父类的引用,像父类一样操作子类的对象
// TODO 也就是说"名"的类型,决定了能执行哪些操作


// >> TODO ph和m都指向同一个对象,通过ph可以调用getBrand方法
// TODO 因为ph的类型是Phone,Phone里定义了getBrand方法
ph.getBrand();
// >> TODO ph和m都指向同一个对象,但是通过m就不可以调用getBrand方法
// TODO 因为m的类型是MerchandiseV2,MerchandiseV2里没有你定义getBrand方法
// m.getBrand();

// TODO 如果确定一个父类的引用指向的对象,实际上就是一个子类的对象(或者子类的子类的对象),可以强制类型转换
Phone aPhone = (Phone) m2;

// MerchandiseV2是Phone的父类,Phone是shellColorChangePhone的父类
ShellColorChangePhone shellColorChangePhone = new ShellColorChangePhone(
"手机002", "Phone002", 100, 1999, 999,
4.5, 3.5, 4, 128, "索尼", "安卓"
);

// TODO 父类的引用,可以指向子类的对象,即可以用子类(以及子类的子类)的引用给父类的引用赋值
MerchandiseV2 ccm = shellColorChangePhone;

// TODO 父类的引用,可以指向子类的对象。
// TODO 确定MerchandiseV2的引用ccm是指向的是Phone或者Phone的子类对象,那么可以强制类型转换
Phone ccp = (Phone) ccm;

// TODO 确定MerchandiseV2的引用ccm是指向的是ShellColorChangePhone或者ShellColorChangePhone的子类对象
// TODO 那么可以强制类型转换
ShellColorChangePhone scp = (ShellColorChangePhone) ccm;

// TODO 会出错,因为m2指向的是一个Phone类型的对象,不是ShellColorChangePhone的对象
ShellColorChangePhone notCCP = (ShellColorChangePhone) m2;

instanceOf 操作符

1
2
3
4
5
6
if (m instanceof Phone) {
// 先判断,再进行强制类型转换
// 如果引用是null,则返回false
Phone phone = (Phone)m;
System.out.println(phone.getBrand());
}

final 修饰符

修饰class时,该类无法被继承

修饰变量时,该变量无法被外部修改

修饰方法时,该方法不能被子类方法覆盖

修饰数组时,该数组内的元素可以被赋值,但不能直接指向另一个数组

可变参数

1
public static void main(String... args)

反射

1
import java.lang.reflect.Field;
1
2
3
4
5
6
7
8
9
10
11
12
13
Class clazz = m100.getClass();

clazz.getName();
clazz.getSimpleName();

Field countField = clazz.getField("count");
countField.getType();
countField.get(m100); //通过反射获取count值

Method buyMethod = clazz.getMethod("buy", int.class);
Method equalsMethod = clazz.getMethod("equals", Object.class);

buyMethod.invoke(m100, 10); //通过反射调用

enum 枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum MusicGame {
ARCAEA("616"),
PHIGROS("鸽游"),
MAIMAI("SBGA");
private String company;

// 构造方法
MusicGame(String company) {
this.company = company;
}

public String GetCompany() {
return company;
}

@Override
public String toString() {
return "MusicGame{" +
"company='" + company + '\'' +
'}';
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Run {
public static void main(String[] args) {
for (MusicGame musicGame : MusicGame.values()) {
System.out.println("-------------------------");
System.out.println(musicGame.GetCompany());
System.out.println(musicGame.ordinal()); // 获取index
System.out.println(musicGame.name()); // 获取枚举名称
System.out.println("-------------------------");
}

System.out.println(MusicGame.valueOf("MAIMAI")); // 通过名称寻找枚举

// 用户输入寻找
Scanner scanner = new Scanner(System.in);
System.out.print("请输入音游名称:");
String musicGameName = scanner.next();
MusicGame musicGame = MusicGame.valueOf(musicGameName.trim().toUpperCase());
System.out.println(musicGame);
}
}

接口

基本实现方式

1
2
3
4
public interface Glass {
void Test1();
int GetCount();
}
1
2
3
4
public interface Ceramics {
void Test2();
int Test3();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Phone extends Merchandise implements Glass, Ceramics {
private int count;
// 必须实现接口的方法
public void Test1() {
//...
}

public int GetCount() {
return count;
}

public void Test2() {
//...
}

public void Test3() {
//...
}
}
1
2
3
4
5
6
Phone phone = new Phone();

// 子类可给父类赋值
Glass glass = phone;
Ceramics ceramics = phone;
Merchandise merchandise = phone;

接口可以有缺省和私有方法,带方法体

1
2
3
4
5
6
7
8
public interface Ceramics {
default int add(int a, int b) {
return a + b;
}
private String hello() {
return "真假陶瓷";
}
}

Collection 集合

简易链表实现(不完全)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.util.*;

public class MyLinkedList implements List {
static class ListNode {
ListNode prev;
ListNode next;
Object value;

public ListNode(ListNode prev, ListNode next, Object value) {
this.prev = prev;
this.next = next;
this.value = value;
}
}

private ListNode start = null;
private ListNode tail = null;

private int size = 0;

@Override
public int size() {
return size;
}

@Override
public boolean isEmpty() {
return size == 0;
}

@Override
public boolean contains(Object o) {
ListNode curr = start;

while (curr != null) {
if (Objects.equals(curr.value, o)) {
return true;
}
curr = curr.next;
}
return false;
}

@Override
public boolean add(Object o) {
ListNode newNode = new ListNode(tail, null, o);
if (start==null){
start=newNode;
}
if(tail!=null){
tail.next=newNode;
}

tail = newNode;
size++;
return true;
}

@Override
public void clear() {
start = null;
tail = null;
size = 0;
}

@Override
public Object get(int index) {
if (index > size || index < 0) {
throw new IndexOutOfBoundsException();
}
ListNode curr = start;
for (int i = 0; i < index; i++) {
curr = curr.next;
}
return curr.value;
}

//其他实现的方法...
}
1
2
3
new ArrayList();
new LinkedList();
new HashSet(); // 元素不重复

Generics 泛型

ArrayList

1
2
3
4
List<int> newList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
newList.add(i);
}

泛型自定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class MyGenericClass<First, Second> {

// 这两个引用都是Object类型
private First first;

private Second second;

public MyGenericClass(First first, Second second) {
this.first = first;
this.second = second;
}

public First getFirst() {
return first;
}

public void setFirst(First first) {
this.first = first;
}

public Second getSecond() {
return second;
}

public void setSecond(Second second) {
this.second = second;
}


public <Another> Another getAnother(Object val) {
return (Another) val;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class DefineGenericTypesAppMain {
public static void main(String[] args) throws NoSuchFieldException {

Field field2 = MyGenericClass.class.getDeclaredField("first");
System.out.println("first的类型是" + field2.getType());

MyGenericClass<String, Object> test = new MyGenericClass<>("inst1", new Object());
MyGenericClass<String, Object> test2 = new MyGenericClass<>("inst2", "aaabbb");

String first = test.getFirst();
System.out.println(first);
// String second = test.getSecond();

// TODO >> 方法的类型参数也是一样,换到了使用的地方做类型强制转换
String another = test.getAnother("safe");
// String another = test.getAnother(new Object());
// String another = (String) test.getAnother(new Object());


// TODO >> 如果泛型信息缺失了,编译器也无法帮忙检查出类型不匹配,只能给出 unchecked 编译警告
MyGenericClass mc = new MyGenericClass("", "");
MyGenericClass<GrandParent, Parent> cast = mc;

// TODO >> 会出错,因为cast指向的实例其实里面存的是两个String
// GrandParent a = cast.getFirst();
// TODO >> 只调用这个方法,不会出错
cast.getFirst();

}
}