Hello World
梦开始的地方
创建一个 hello.java
,然后写入以下内容
public class hello {
public static void main(String[] args) {
System.out.println("Hello World");
}
}
在与上述源文件相同目录下运行
javac hello.java; java hello
其中 javac hello.java
将源文件编译为一个可执行的 java 类,观察文件目录,可以发现生成了一个 hello.class
文件
这个 .class 文件包含了 JVM(Java虚拟机)能够执行的中间代码
有了 hello.class
文件之后,就可以使用 java hello
来加载并执行对应的类
这个 加载并执行 的过程比较复杂,了解即可,大致流程如下:
- 操作系统启动一个 JVM 实例,该实例会寻找命令中指定的类
- 如果能找到对应的类,JVM 会加载它作为入口类(将 xxx.class 文件加载到内存中)
- 对入口类进行链接操作,包括验证字节码、准备类、解析符号引用等
- JVM 会寻找入口类的 main 方法并创建一个新的线程执行该方法
- 程序按照 main 方法中的逻辑运行,期间 GC(垃圾回收)机制会监控并回收无用资源
- 当遇到异常或 main 方法执行完毕,JVM 会清理资源并退出程序
进行这一系列操作之后,我们的终端就打印出了 Hello World
(并退出了程序)
到这里对于 hello.java
的一些写法其实还是有疑问,例如为什么类的声明前要添加 public
关键字?main
方法声明前添加的一堆修饰是干嘛的?System.out.println
是什么?这些问题都将在后续的学习中逐步解答
我们目前只需要知道一个 java 程序的固定写法形如以下这样:
public class 文件名 {
public static void main(String[] args) {
// 要执行的主程序
}
}
// 其他类的声明
并且记住使用 System.out.println
可以在控制台中输出一些信息即可
错误: 编码 GBK 的不可映射字符
如果源代码使用的是 UTF-8 等编码,并且含有中文字符时,执行 javac xxx.java
尝试编译可能会遇到此报错
我们需要指定指定一下编码类型
javac -encoding utf-8 xxx.java
变量与数据类型
基础内容,迅速掌握
变量创建与赋值
使用 数据类型 变量名 = 值
的方式来创建变量;使用 变量名 = 新值
的方式来改变变量的值
public class Main {
public static void main(String[] args) {
int x = 1;
System.out.println("x: " + x);
x = 2;
System.out.println("x: " + x);
}
}
基本数据类型
基本数据类型是 CPU 可以直接进行运算的类型,存储的是实际的值
上面的例子中,变量 x 是一个 int
类型的变量,用于存放整型数据。JVM 会为 x 分配一片内存空间存放其数据
Java 提供的基本数据类型有:
整数类型:byte,short,int,long
浮点数类型:float,double
字符类型:char
布尔类型:boolean
public class Main {
public static void main(String[] args) {
int x = 1;
double y = 3.14159;
char z = 'A';
boolean n = true;
}
}
引用数据类型
引用数据类型表示其指向的实际对象的引用,存储的是内存地址
用户自定义的类型、接口等都是引用数据类型。它们不直接存放数据的值,而是存放”数据在内存中的位置”
常用的引用数据类型有:
字符串类型:String
各种数组类型:int[]、double[]、String[] …
public class Main {
public static void main(String[] args) {
String x = "hello";
int[] y = new int[10];
}
}
一些相关语法
var 关键字
我们声明一个变量时,可能会遇到变量类型比较长的情况。就会出现这样的代码:
MyObject a = new MyObject();
为了避免这种冗长的声明,可以使用关键字 var
来创建变量,Java 会自动推导变量类型
public class Main {
public static void main(String[] args) {
var x = 1;
var y = 3.14;
var z = 'a';
var n = "Mike";
var m = new byte[5];
}
}
单引号和双引号
单引号是用作表示 “字符” 的,双引号是用作表示 “字符串” 的。不能混用
char a = 'a';
String b = "bbb";
// 这些写法是错误的:
// char a = "a";
// String b = 'bbb';
数组的声明与使用
数组有几种声明方法,这里以一维整型数组为例
// 1. 指定数组大小
int[] x = new int[5];
// 2. 直接初始化数组内元素
int[] y = {1, 2, 3, 4, 5};
// 3. 不指定大小也不初始化元素
int[] z;
创建数组后,我们可以通过索引来操作数组中存放的值。数组索引从 0 开始
public class Main {
public static void main(String[] args) {
int[] x = {1, 2, 3, 4, 5, 6, 7, 8};
int n;
// 从数组中取出值
n = x[0];
System.out.println("n: " + n);
// 更改数组中的值
x[1] = 100;
System.out.println("x[1]: " + x[1]);
}
}
上面展示的都是一维的数组,我们还可以创建更高维度的数组,这个在后续章节中再展开
final 关键字
使用 final
关键字修饰的变量将会成为 “常量”,常量在声明后不可再次赋值,否则编译器会报错
public class Main {
public static void main(String[] args) {
final double PI = 3.14159;
PI = 3;
}
}
这里有一个编程规范,为了将常量和普通变量区分开,建议常量使用 全大写字母 来命名
运算符
编程语言的运算符设计都差不多,这里只做整理,不细讲
数学运算符
运算符 | 写法 | 描述 |
---|---|---|
加减乘除 | + - * / |
普通的四则运算 |
取模 | % |
进行取模运算 |
自增自减 | ++ -- |
对一个整数进行加1或减1操作 |
移位 | << >> <<< >>> |
在二进制层面移位 |
位运算 | & \| ^ ~ |
分别是:与、或、异或、非 |
逻辑运算符
逻辑运算表达式的结果都是布尔值(true / false)
运算符 | 写法 | 描述 |
---|---|---|
比较运算符 | > >= == <= < != |
分别是:大于、大于等于、等于、小于等于、小于、不等于 |
逻辑与 | && |
一假全假,全真为真 |
逻辑或 | \|| |
一真全真,全假为假 |
逻辑非 | ! |
真变假、假变真 |
boolean isGreater = 5 > 3; // true
int age = 12;
boolean isZero = age == 0; // false
boolean isNonZero = !isZero; // true
boolean isAdult = age >= 18; // false
boolean isTeenager = age > 6 && age < 18; // true
选择结构与循环结构
依然很基础,都是些编程范式
选择结构
使用 if 的选择
if
选择语句的语法为
if (条件a) {
// 满足条件 a 时执行的代码
} else if (条件b) {
// 满足条件 b 时执行的代码
} else {
// 不满足任何条件时执行的代码
}
例子:使用 if 语句判断成绩是否及格
public class Main {
public static void main(String[] args) {
double score = 70;
if (score >= 80) {
System.out.println("优秀");
} else if (score >= 70) {
System.out.println("良好");
} else if (score >= 60) {
System.out.println("及格");
} else {
System.out.println("不及格");
}
}
}
使用 switch 的选择
switch
选择语句的语法为
switch (表达式) {
case 结果1:
// 满足结果1时执行的代码
break;
case 结果2:
// 满足结果2时执行的代码
break;
default:
// 没有满足任何结果时默认执行的代码
break;
}
例子:当 option
为指定值时,输出一些提示
public class Main {
public static void main(String args[]) {
int option = 1;
switch (option) {
case 1:
System.out.println("option is 1");
break;
case 2:
System.out.println("option is 2");
break;
case 3:
System.out.println("option is 3");
break;
default:
System.out.println("option is other");
break;
}
}
}
这种写法如果没有 break
的话,在执行完当前 case 的代码后还会继续执行下一个 case 的代码
public class Main {
public static void main(String args[]) {
String fruit = "apple";
switch (fruit) {
case "apple":
System.out.println("apple");
break;
case "pear":
case "mango":
System.out.println("pear or mango");
break;
default:
System.out.println("I don't know");
break;
}
}
}
很多时候我们使用 switch 语句就是想要将各个 case 中的逻辑独立开来,如果要管理的 case 比较多的话,就要写很多个 break
;并且如果出现了遗漏 break
语句的情况,则可能产生严重的错误
所以 java 12 及以上版本提供了一种新的写法
switch (表达式) {
case 结果1 -> {
// 满足结果1时执行的代码
}
case 结果2 -> {
// 满足结果2时执行的代码
}
default -> {
// 没有满足任何结果时默认执行的代码
}
}
这种写法的 switch 语句还能有返回值
public class Main {
public static void main(String[] args) {
String fruit = "apple";
int opt = switch (fruit) {
case "apple" -> 1; // 返回 1
case "pear", "mango" -> 2; // 返回 2
default -> {
System.out.println("I don't know what fruit is it");
yield 0; // 返回 0
}
};
System.out.println("opt = " + opt);
}
}
循环结构
我们在编写程序的时候,会发现很多行为是需要重复的,这时候可以使用循环来完成
for 循环
基本写法为
for (初始化; 循环条件; 更新行为) {
// 要在循环中做的事
}
初始化:循环开始前执行的行为,通常用于初始化循环计数器
循环条件:一个逻辑表达式,每次循环前都会检测该式子,为 “真” 时才进入循环,否则退出循环
更新行为:每次循环结束时都会执行该行为,通常用于更新循环计数器
例子:使用 for 循环打印整数 0 到 10
public class Main {
public static void main(String args[]) {
for (int i = 0; i <= 10; i++) {
// 即将进入循环, 检查 i <= 10 是否为真
System.out.println(i);
// 到达循环尾部, 执行 i++
}
}
}
初始化、循环条件、更新行为,都是可以缺省的,我们甚至可以这样写
for (;;) {
// do something...
}
// 不设置循环条件时, 编译器会将循环条件视为恒 "真"
通常来说不会使用缺省写法,不过确实能这样做就是了
while 和 do while
while 循环的语法为
while (循环条件) {
// 要在循环中做的事
}
和 for 循环一样,每次循环前都会检测 “循环条件”,为 “真” 时才进入循环,否则退出循环
例子:使用 while 循环打印 0 到 20 中的偶数
public class Main {
public static void main(String args[]) {
int n = 0;
while (n <= 20) {
if (n % 2 == 0) {
System.out.println(n);
}
n += 1;
}
}
}
do while 循环的语法为
do {
// 要在循环中做的事
} while (循环条件)
这个循环方式比较特殊,它是先执行循环中的代码,再判断循环条件
例子:使用 do while 循环求解自然数 1 到 100 的和
public class Main {
public static void main(String args[]) {
int n = 1;
int sum = 0;
do {
sum += n;
n += 1;
} while (n <= 100);
System.out.println("sum: " + sum);
}
}
break 和 continue
break
会退出整个循环
public class Main {
public static void main(String args[]) {
int sum = 0;
for (int i = 0; i < 1000; i++) {
System.out.println(i);
sum += i;
if (sum > 20) {
break;
}
}
System.out.println("sum: " + sum);
}
}
可以看到在 i
为 6 的时候就跳出循环了
如果有多层嵌套的循环,则 break
只会退出其所在层的循环
public class Main {
public static void main(String args[]) {
// 循环 a
for (int a = 0; a < 4; a++) {
System.out.println("a = " + a);
// 循环 b
for (int b = 0; b < 4; b++) {
System.out.println("b = " + b);
if (b == a) {
break; // 只会结束循环 b
}
}
}
}
}
break
会跳出当前循环,也就是整个循环都不会执行了;而 continue
则是提前结束本次循环,直接继续执行下次循环
public class Main {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
System.out.println("begin i = " + i);
if (i % 2 == 0) {
continue; // 结束本次循环
}
System.out.println("end i = " + i);
}
}
}
遇到循环嵌套时,continue 和 break 一样只会影响其所在的循环层级
在控制台输入和输出
大概是算法选手常用功能
输入
java 要读取控制台输入需要创建一个 Scanner
(扫描器)
import java.util.Scanner;
public class Main {
public static void main(String args[]) {
var scanner = new Scanner(System.in);
System.out.print("请输入你的年龄: ");
int age = scanner.nextInt();
System.out.println("age: " + age);
scanner.close();
}
}
首先要导入 Scanner
类
import java.util.Scanner;
然后创建一个 Scanner
实例,这里的 System.in
指的是标准输入流
var scanner = new Scanner(System.in);
接下来就可以使用 Scanner
提供的方法来读取控制台输入
scanner.nextLine(); // 读取一行并作为字符串返回
scanner.next(); // 读取下一个分隔符前的数据并作为字符串返回
scanner.nextInt(); // 读取下一个分隔符前的数据并作为整型返回
// 还有 nextDouble、nextByte 等等方法
最后关闭 Scanner
scanner.close();
输出
在编写 Hello World 的时候我说记住 System.out.println
可以在控制台输出一些信息即可,现在要学习更多功能了
println
是 print line 的缩写,表示在输出指定内容后将会换行
如果想要输出之后不自动换行,可以使用 print
public class Main {
public static void main(String[] args) {
System.out.print("111");
System.out.println("222");
System.out.print("333");
System.out.println("444");
}
}
可以看到输出 111
和 333
的时候使用了 print
,所以没有自动换行,而是直接和后面的输出内容拼接在一行中了
还可以使用 printf
来进行格式化输出 (print format)
下面是格式化输出使用的占位符
占位符 | 作用 |
---|---|
%d | 格式化输出整数 |
%x | 格式化输出十六进制整数 |
%f | 格式化输出浮点数 |
%e | 格式化输出科学计数法表示的浮点数 |
%s | 格式化字符串 |
例子:
public class Main {
public static void main(String[] args) {
String name = "Mike";
int age = 20;
double score = 115.5;
System.out.printf("name: %s, age: %d, score: %.2f", name, age, score);
}
}
printf
会将额外的入参按照顺序填入占位符
常用数组操作
好用,常用,且必须会用的数组操作
创建和使用多维数组
先来看一个简单的二维数组
public class Main {
public static void main(String[] args) {
int[][] x = {
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12}
};
}
}
这个二维整型数组在内存中的结构类似这样
更高维的数组也同理
public class Main {
public static void main(String[] args) {
int[][][] y = {
{
{ 1, 2, 3 },
{ 4, 5, 6 }
},
{
{ 7, 8, 9 },
{ 10, 11, 12 }
},
};
}
}
子数组的长度可以不相同,例如我们可以定义一个二维 char 数组如下
public class Main {
public static void main(String[] args) {
char[][] say {
{ 'h', 'i' },
{ 'h', 'e', 'l', 'l', 'o' }
}
}
}
要想读取多维数组中的值,我们依然使用索引操作
char[][] say {
{ 'h', 'i' },
{ 'h', 'e', 'l', 'l', 'o' }
}
char[] hi = say[0]; // {'h', 'i'}
char[] hello = say[1]; // {'h', 'e', 'l', 'l', 'o'}
char x = say[0][0]; // 'h'
char y = say[1][1]; // 'e'
使用 for 遍历数组
通过数组对象的 length
属性能获取数组的长度,可以配合普通 for 循环来遍历数组
// 一维数组
int[] x = { 1, 1, 4, 5, 1, 4 };
for (int i = 0; i < x.length; i++) {
System.out.printf("%d ", x[i]);
}
System.out.println("");
// 多维数组
int[][] y = {
{ 1, 2, 3, 4 },
{ 5, 6, 7 }
};
for (int i = 0; i < y.length; i++) {
for (int j = 0; j < y[i].length; j++) {
System.out.printf("%d ", y[i][j]);
}
}
System.out.println("");
java 还提供了 for each 语法,可以更加方便地遍历数组
// 一维数组
int[] x = { 1, 1, 4, 5, 1, 4 };
for (int n: x) {
System.out.printf("%d ", n);
}
System.out.println("");
// 多维数组
int[][] y = {
{ 1, 2, 3, 4 },
{ 5, 6, 7 }
};
for (int[] i: y) {
for (int j: i) {
System.out.printf("%d ", j);
}
}
System.out.println("");
使用 Arrays 工具类操作数组
Arrays
类提供了很多好用的操作数组的方法
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
int[] m = { 1, 1, 4, 5, 1, 4 };
// 对数组进行排序
Arrays.sort(m);
// 将数组转换成可以阅读的字符串
System.out.println(Arrays.toString(m));
String[][] n = new String[3][];
// 使用指定元素填充数组
Arrays.fill(n, new String[] { "h", "e", "l", "l", "o" });
// 将多维数组转换成可以阅读的字符串
System.out.println(Arrays.deepToString(n));
}
}