全文中 Main.java
为文件名。
Debug and Run
编译:
javac Main.java
运行:
java Main
起手式
public class Main {public static void main(String[] args) {// Code}
}
其中 Main
需要与文件名一致。
Output
public class Main {public static void main(String[] args) {System.out.println("Hello World");}
}
Primitive Data Types
除了 int
以外还有这么几种常用的:
double a = -1.14514;
boolean b = true;
char c = 'c';
long d = 1145141919810l;
其中 long
是 64 位带符号长整型。
Type Casting
double a = -1.14514;
int b = (int)a; // double 转 int 要强转
double c = b; // int 转 double 可以隐式转换
char
-> int
-> long
-> double
可以隐式转换。反过来必须强制转换。
Java 中 double
转 int
为向零取整。这意味着上文代码中 b
的值为 -1
而不是 -2
。
For-Loop & While-Loop
和 C++ 完全一样,不写了。
Enhanced For-Loop
int arr[] = {1, 2, 3, 4};for (int element : arr) { // Enhanced For-LoopSystem.out.print(element + " ");
}
ArrayList<Integer> a = new ArrayList<Integer>();
a.add(4);
a.add(2);
a.add(3);
a.add(5);
for (Integer num : a) { // Enhanced For-LoopSystem.out.print(num + " ");
}
在 Enhanced For-Loop 的遍历过程中,可以调用元素的方法,但是不可以改变元素的内存地址,比如赋值一个新的对象。(Example:Practice Test 1 Problem 14)
二维数组的 Enhanced For-Loop
int arr[][] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}
};for (int row[] : arr) {for (int element : row) {System.out.println(element);}
}
Array
// 1D Array
int[] a = {1, 1, 4, 5, 1, 4};
int[] b = new int[6];
System.out.println(b.length); // Output: 6// 2D Array
int[][] aa = new int[3][5];
System.out.println(aa[2].length); // Output: 5
Non-Primitive Data Types
Primitive data type 一般小写开头,而 non-primitive data type 一般大写开头。
对于这一类 data type 值得注意的是,比较两个 Object 时应当使用 .equals()
而避免使用 ==
,因为 ==
比较的是引用。
String
String s = "Hello World";
System.out.println(s);
值得注意的是,新版本 Java 中 ==
比较两个 String
似乎也是可以的,因为字符串字面量会被保存起来:
String s1 = "tiansuo", s2 = "tiansuo";
System.out.println(s1.equals(s2)); // Output: true
System.out.println(s1 == s2); // Output: true
可以使用 compareTo
方法比较两个字符串的字典序(负数为小于,正数为大于):
String s1 = "abcd", s2 = "abcdef", s3 "bacd";
System.out.println(s1.compareTo(s2)); // -2,小于
System.out.println(s3.compareTo(s1)); // 1,大于
System.out.println(s3.compareTo(s3)); // 0,等于
💡 多字符串拼接
普通的 +
在多字符串拼接时,时间复杂度是错误的,因为每次拼接都会生成一个新的字符串对象。
因此 StringBuilder
闪亮登场!适用于单线程下的多字符串拼接。
GPT 给我写了一个示范:
StringBuffer sb = new StringBuffer();
sb.append("Hello");
sb.append(", ");
sb.append("World");
sb.append("!");
String result = sb.toString();
System.out.println(result); // Output: Hello, World!
Wrapper Classes
每一个 primitive data type 都有对应的 wrapper class:
Primitive Data Type | Wrapper Class |
---|---|
int |
Integer |
long |
Long |
double |
Double |
boolean |
Boolean |
char |
Character |
Wrapper Class: Integer
Integer
有 Autoboxing 与 Unboxing 的机制,意味着可以把它们当作正常的 int
使用而不会产生混乱。
Type Casting: int
-> Integer
以下这两种都是可以的:
Integer a = Integer.valueOf(114);
Integer b = 514;
这种已经过时,不建议使用:
Integer c = new Integer(1919);
Type Casting: Integer
-> int
以下两种都是可以的:
Integer a = 114;int b = a;
System.out.println(b); // Output: 114int c = a.intValue();
System.out.println(c); // Output: 114
Attributes
System.out.println(Integer.MAX_VALUE); // 2147483647
System.out.println(Integer.MIN_VALUE); // -2147483648
Array List
在文件开头需要一个起手式:import java.util.ArrayList;
ArrayList
的元素类型只能是 non-primitive data type。
创建一个以 Integer
为元素的 ArrayList
:
ArrayList<Integer> a = new ArrayList<Integer>();
ArrayList
的 index 从 0 开始。
加入元素
a.add(114);
// 在末尾添加元素
// 类似 Python 的 appenda.add(0, 514);
// 在指定索引处添加元素
// 类似 Python 的 insertSystem.out.println(a);
// Output: [514, 114]
修改元素
// a 此时为 [514, 114]a.set(0, 1919);
// 修改指定索引的元素
// 类似于 Python 中 a[0] = 1919System.out.println(a);
// Output: [1919, 114]
删除元素
// a 此时为 [1919, 114]a.remove(0);
// 删除指定索引的元素
// 类似于 Python 中 del a[0]System.out.println(a);
// Output: [114]
访问元素
// a 此时为 [114]System.out.println(a.get(0));
// 访问指定索引的元素
// a.get(0) 类似于 Python 中 a[0]
// Output: 114
清除
a.clear();
System.out.println(a); // Output: []
System.out.println(a.size()); // Output: 0
Class
这是一个 class
的示例:
class Student {private String name;private int age;public Student(String name, int age) { // Constructorthis.name = name;this.age = age;}public String getName() { // Accessor / Getterreturn name;}public int getAge() {return age;}public void setName(String name) { // Mutator / Setterthis.name = name;}public void incAge() {age++;}public void studentCard() {System.out.println("Student");}@Overridepublic String toString() {return name + "(" + age + ")";}public boolean equals(Student other) {return name == other.name && age == other.age;}
}
@Override
告诉编译器,这个函数覆盖了已有的函数(不写也不要紧!)。如果编译器发现并没有已有的函数,会报 CE。toString
是本来就有的,所以可以写。equals
虽然也是本来就有的,但是参数类型不一样(默认是Object
),所以不能写。
toString()
类似于 Python 的__str__
。this
类似于 Python 的self
。
Inheritance (继承)
以下是一个继承的例子。HighSchoolStudent
是一个继承自 Student
的 class。用 extends
表示继承。
class HighSchoolStudent extends Student {private final int gaokaoScore;public static final int MAX_GAOKAO_SCORE = 750;public HighSchoolStudent(String name, int age, int gaokaoScore) {super(name, age);// TODO:}@Overridepublic void studentCard() {System.out.println("HighSchoolStudent");}
}
final
关键字类似于 C++ 的const
。super
类似于 Python 的super().__init__
,用于调用父类的构造函数。
Dynamic Binding
GPT 给我写了一个示例代码解释这个机制:
class Animal {public void makeSound() {System.out.println("Animal is making a sound");}
}class Dog extends Animal {@Overridepublic void makeSound() {System.out.println("Dog is barking");}
}class Cat extends Animal {@Overridepublic void makeSound() {System.out.println("Cat is meowing");}
}public class Main {public static void main(String[] args) {Animal myAnimal; // 编译时类型是 AnimalmyAnimal = new Dog(); // 运行时类型是 DogmyAnimal.makeSound(); // 调用 Dog 的 makeSound 方法myAnimal = new Cat(); // 运行时类型是 CatmyAnimal.makeSound(); // 调用 Cat 的 makeSound 方法}
}
感觉解释得够清楚了。
总之,程序在运行时根据实际的对象类型选择调用对应的方法,而不是在编译时决定。
这是一个典型的 Polymorphism(多态)的例子。
The Math
Class
考试中不会考:import static java.lang.Math.*;
可以避免每个函数的 Math.
前缀。
abs(x)
绝对值(整数与浮点数都可)。
💡 Math.abs(-2147483648)
返回 -2147483648
。
pow(a, b)
\(a^b\),参数与返回值皆为浮点数。
以下情况下,pow
可以运行,否则会寄:
- \(a > 0\)。
- \(a = 0\) 且 \(b > 0\)。
- \(a < 0\) 且 \(b\) 为整数。
sqrt(x)
浮点数平方根。
random()
\([0,1)\) 随机浮点数。
💡 密码学安全伪随机数
Math.random
生成的随机浮点数不是密码学安全的。SecureRandom
可以生成密码学安全的随机数,但是性能可能有所下降。
import java.security.SecureRandom;public class Main {public static void main(String[] args) {SecureRandom rand = new SecureRandom();for (int i = 1; i <= 10; i++) {int a = rand.nextInt();System.out.println(a);}}
}
PI
\(\pi\)。
Sort
- Selection Sort
- Insertion Sort
- Merge Sort
- Quicksort(最坏 \(O(n^2)\) 的版本)
💡 Built-in Sort Algorithm
Array
起手式:import java.util.Arrays;
Arrays.sort
可以给数组排序。
int[] a = {5, 2, 9, 1, 3};
Arrays.sort(a);
for (int x : a)System.out.print(x + " ");
ArrayList
起手式:import java.util.Collections;
Collections.sort
可以给 ArrayList
排序。
ArrayList<Integer> a = new ArrayList<>();
a.add(5);
a.add(2);
a.add(9);
a.add(1);
a.add(3);Collections.sort(a);
Binary Search
二分属于 divide-and-conquer(分治)的算法。
public static int binarySearch(int[] arr, int key, int low, int high) {if (low > high) // Base case. No elements left in array.return -1;int mid = (low + high) / 2;if (key == arr[mid]) // found the keyreturn mid;else if (key < arr[mid]) // key in left half of arrayreturn binarySearch(arr, key, low, mid - 1);else // key in right half of arrayreturn binarySearch(arr, key, mid + 1, high);
}public static int binarySearch(int[] arr, int key) {return binarySearch(arr, key, 0, arr.length - 1);
}
public static int binarySearch(int[] arr, int key, int low, int high)
的被调用次数称为这个算法的迭代次数。
用这个代码测试迭代次数:
private static int it = 0;
public static int binarySearch(int[] arr, int key, int low, int high) {System.out.println("Iteration " + (++it));if (low > high)return -1;int mid = (low + high) / 2;if (key == arr[mid])return mid;else if (key < arr[mid])return binarySearch(arr, key, low, mid - 1);elsereturn binarySearch(arr, key, mid + 1, high);
}public static int binarySearch(int[] arr, int key) {it = 0;return binarySearch(arr, key, 0, arr.length - 1);
}
递归可以改写为迭代写法:
public static int binarySearch(int[] arr, int key) {int low = 0;int high = arr.length - 1;while (low <= high) {int mid = (low + high) / 2;if (key == arr[mid]) // found the keyreturn mid;else if (key < arr[mid]) // key in left half of arrayhigh = mid - 1;else // key in right half of arraylow = mid + 1;}// If we get to here, then key is not in array.return -1;
}
Example: Practice Test 1 Problem 18
有一个元素为 \(\{0,1,\cdots,63\}\) 的数组。
- 查找 \(0\) 需要 \(6\) 次迭代。
- 确认 \(-1\) 不存在,需要 \(7\) 次迭代。
- 查找 \(62\) 需要 \(6\) 次迭代。