Hello World

梦开始的地方

创建一个 hello.java,然后写入以下内容

public class hello {
    public static void main(String[] args) {
        System.out.println("Hello World");
    }
}

在与上述源文件相同目录下运行

javac hello.java; java hello

img

其中 javac hello.java 将源文件编译为一个可执行的 java 类,观察文件目录,可以发现生成了一个 hello.class 文件

这个 .class 文件包含了 JVM(Java虚拟机)能够执行的中间代码

img

有了 hello.class 文件之后,就可以使用 java hello 来加载并执行对应的类

这个 加载并执行 的过程比较复杂,了解即可,大致流程如下:

  1. 操作系统启动一个 JVM 实例,该实例会寻找命令中指定的类
  2. 如果能找到对应的类,JVM 会加载它作为入口类(将 xxx.class 文件加载到内存中)
  3. 对入口类进行链接操作,包括验证字节码、准备类、解析符号引用等
  4. JVM 会寻找入口类的 main 方法并创建一个新的线程执行该方法
  5. 程序按照 main 方法中的逻辑运行,期间 GC(垃圾回收)机制会监控并回收无用资源
  6. 当遇到异常或 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);
    }
}

img

基本数据类型

基本数据类型是 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;
    }
}

img

这里有一个编程规范,为了将常量和普通变量区分开,建议常量使用 全大写字母 来命名

运算符

编程语言的运算符设计都差不多,这里只做整理,不细讲

数学运算符

运算符 写法 描述
加减乘除 + - * / 普通的四则运算
取模 % 进行取模运算
自增自减 ++ -- 对一个整数进行加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++
        }
    }
}

img

初始化、循环条件、更新行为,都是可以缺省的,我们甚至可以这样写

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;
        }
    }
}

img

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);
    }
}

img

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);
    }
}

img

可以看到在 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
                }
            }
        }
    }
}

img

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);
        }
    }
}

img

遇到循环嵌套时,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();
    }
}

img

首先要导入 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 可以在控制台输出一些信息即可,现在要学习更多功能了

printlnprint 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");
    }
}

img

可以看到输出 111333 的时候使用了 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);
    }
}

img

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("");

img

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("");

img

使用 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));
    }
}

img