AP CSA Note

全文中 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 中 doubleint 为向零取整。这意味着上文代码中 b 的值为 -1 而不是 -2

For-Loop & While-Loop

和 C++ 完全一样,不写了。

Enhanced For-Loop

int arr[] = {1, 2, 3, 4};

for (int element : arr) { // Enhanced For-Loop
    System.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-Loop
    System.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: 114

int 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 的 append

a.add(0, 514);
// 在指定索引处添加元素
// 类似 Python 的 insert

System.out.println(a);
// Output: [514, 114]

修改元素

// a 此时为 [514, 114]

a.set(0, 1919);
// 修改指定索引的元素
// 类似于 Python 中 a[0] = 1919

System.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) { // Constructor
        this.name = name;
        this.age = age;
    }

    public String getName() { // Accessor / Getter
        return name;
    }

    public int getAge() {
        return age;
    }

    public void setName(String name) { // Mutator / Setter
        this.name = name;
    }

    public void incAge() {
        age++;
    }

    public void studentCard() {
        System.out.println("Student");
    }

    @Override
    public 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:
    }

    @Override
    public 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 {
    @Override
    public void makeSound() {
        System.out.println("Dog is barking");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Cat is meowing");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal myAnimal;  // 编译时类型是 Animal

        myAnimal = new Dog();  // 运行时类型是 Dog
        myAnimal.makeSound();  // 调用 Dog 的 makeSound 方法

        myAnimal = new Cat();  // 运行时类型是 Cat
        myAnimal.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 key
        return mid;
    else if (key < arr[mid]) // key in left half of array
        return binarySearch(arr, key, low, mid - 1);
    else // key in right half of array
        return 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);
    else
        return 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 key
            return mid;
        else if (key < arr[mid]) // key in left half of array
            high = mid - 1;
        else // key in right half of array
            low = 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\) 次迭代。
posted @ 2025-03-18 20:46  August_Light  阅读(11)  评论(0)    收藏  举报