跳转至

5.0 Class

String Type

String 类型用于储存一串 char, 在 Java 中 String 不是一个基本数据类型,属于一个 class,因此这意味着 String 的每个 instance 都是一个对象。

Literal

String hi = "hello";

运行之前就已经被编译器知道并准备好了

New

String s3 = new String(s);

String 类型是不可变 immutable 的,一旦被创建出来,内容就不能被改变

虽然有些方法比如 String s = "Hello" + "World"; 会让人觉得改变了但是实际上并不是在原有的对象上上进行修改了 s 而是创建了一个全新的 String 对象存放结果。那么如果就是想像修改数组一样修改字符的某个数组,先将其变为一个字符数组 char[]

  • toCharArray()

return 一个字符串副本

数组可变,修改完成后可以用这个修改过后的字符数组创建一个新的 String 对象

  • length()

直接返回字符串中字符的数量

  • charAt(int index)

获取位于特定索引的字符,从 0 开始

  • substring(int start, int end) substring(int start)

这些方法用来获取字符串的子串end 不包含

public class Substring {
    public static void main(String[] args) {
        String s = "CITS2005"; // 原始字符串
        String cits = s.substring(0, 4);
        String code = s.substring(4);
        System.out.println(cits + " -- " + code); // CITS -- 2005
    }
}
  • equals(Object other)

比较对象中的内容

public class ToCharArrayDemo {

    public static void main(String[] args) {
        String originalString = "Hello World";
        char[] charArray = originalString.toCharArray();
        System.out.println("使用 toCharArray() 转换后得到的字符数组 (内容): " + new String(charArray)); // 转换回 String
        if (charArray.length > 6) {
            charArray[3] = '_';
            System.out.println("修改后的字符数组 (内容): " + new String(charArray));
        }
        System.out.println("验证: 原始字符串 originalString 仍然是: " + originalString);

    }
}

MyString Class

为了更好的理解 String,引入一个练习如何自己实现一个 String 类

  • 数组是 .length
  • String.length()
public class MyString {
    private char[] chars;

    public MyString(char[] chars) {
        this.chars = new char[chars.length];
        for (int i = 0; i < chars.length; i++) {
            this.chars[i] = chars[i];
        }
    }

    public char charAt(int index) {
        return chars[index];
    }

    public int length() {
        return chars.length;
    }

    public boolean equals(MyString s) {
        // 先对比长度,再逐个对比 char[] 中元素
        if (length() != s.length())
            return false;
        for (int i = 0; i < length(); i++) {
            if (charAt(i) != s.charAt(i))
                return false;
        }
        return true;
    }

    /**
     * 将原始字符串(或其内部字符数组)中从 start 索引开始
     * 到 end - 1 索引结束的字符,逐一复制到新创建的字符数组 (newChars) 中。
     */
    public MyString substring(int start, int end) {
        // 1. 计算所需空间,生成一个新的 char[] 用于返回
        char[] newChars = new char[end - start];
        // 2. 循环遍历,不包含 end,这个算法可以动态同时满足两边的需求
        for (int i = start; i < end; i++) {
            newChars[i - start] = chars[i]; // (2, 4) 3 - 2 = chars[3]
        }
        return new MyString(newChars);
    }

    public MyString concatenate(MyString s) {
        char[] newChars = new char[chars.length + s.length()];
        for (int i = 0; i < chars.length; i++) {
            newChars[i] = chars[i];
        }
        for (int i = 0; i < s.length(); i++) {
            newChars[chars.length + i] = s.charAt(i);
        }
        return new MyString(newChars); // 由于不是 static 要生成对象
    }

    public class MyStringExample {
        public static void main(String[] args) {
            MyString s = new MyString("Hello".toCharArray());
            s.chars[0] = 'J';
            for (int i = 0; i < s.length(); i++)
                System.out.println(s.charAt(i));
        }
    }
}

Private and Public

软件工程两大准则:

  • data hiding
  • private 实现
  • encapsulation
  • 把代码和数据打包成容易使用的对象,这就是一个类。数据隐藏可以保护用户不受复杂性的影响,他们只需要了解公共部分就可以了。这就叫封装。
public class SafeArray {
    private int size;
    private int[] array;

    public SafeArray(int size) {
        this.size = size;
        this.array = new int[size];
    }

    private boolean isValidIndex(int index) {
        return index >= 0 && index < size;
    }

    public int get(int index) {
        if (isValidIndex(index))
            return array[index];
        System.out.println("Invalid index: " + index);
        return 0;
    }

    public void set(int index, int value) {
        if (isValidIndex(index))
            array[index] = value;
        else
            System.out.println("Invalid index: " + index);
    }
}

void set 以及 get 实现有限度的 public 访问机制

Pass by reference

  • 上次课我们看到了方法和构造函数可以有参数,这些参数可以是类
  • 这意味着传递进来的值是对象
  • 要理解这是怎么工作的,我们需要知道值传递和引用传递
  • 我们已经看到,存储对象的变量(或数组)实际上存储的是引用
  • 存储原始类型的变量直接存储值
  • 这是一样的。原始类型的值会被复制(值传递),对象则会被引用(引用传递)
  • 这很有道理。原始类型通常复制起来更高效(比如一个 int),但对象通常要大得多
public void copyInto(SafeArray other) {
    if (other.size != size) {
        System.out.println("Arrays are not the same size");
        return;
    }
    for (int i = 0; i < size; i++) {
        other.array[i] = array[i];
    }
}

传递了一个 SafeArray 类型的数组对象,确保大小一致后开始将输入对象数组替换

public 方法一样可以接入 private 变量,只要是在同一个类中

Return by reference

public SafeArray append(int value) {
    SafeArray newArray = new SafeArray(size + 1);
    for (int i = 0; i < size; i++)
        newArray.array[i] = array[i];
    newArray.array[size] = value;
    return newArray;
}

实现一个和 python 一样的 append 方法需要考虑内存,返回一个对象地址

Overloading

  • 在 Java 里,好几种方法可以叫同一个名字。
  • 这就是所谓的重载。
  • 只要它们的参数不一样,Java 就能知道你想调用哪个。

比如说如果不返回对象再建立一个 void 同名函数 append

比如建立一个不同参数的 constructor, 都可以被自动识别

Recursion

public class Fibonacci {
    public static void main(String[] args) {
        Fibonacci f = new Fibonacci();
        for (int i = 1; i <= 10; i++)
            System.out.println(f.fib(i));
    }

    public int fib(int n) {
        if (n <= 2)
            return 1;
        else
            return fib(n - 1) + fib(n - 2);
    }
}

static

该特殊字符只属于类,不属于对象

ClassName.myStaticMethod

public class StaticFib {
    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++)
            System.out.println(StaticFib.fib(i));
    }

    public static int fib(int n) {
        if (n <= 2)
            return 1;
        else
            return fib(n - 1) + fib(n - 2);
    }
}

可以看到第 4 行直接调用的是类名,如果不写类名直接用 fib(i) 自动查询当前类是否有该方法

Static Fields

public class StaticX {
    public static String x;
}

StaticX.x 也是这样访问

无论你创建多少个该类的对象,或者根本不创建对象,这个静态字段都只有一份存储空间,存在于类级别。不像实例字段,每个对象都有自己的那一份。所有实例都共享同一个字段:因为静态字段只有一份。

public class StaticXTest {
    public static void main(String[] args) {
        StaticX instance = new StaticX();
        StaticX instance2 = new StaticX();
        instance.x = "Hello";
        instance2.x = "Goodbye";
        StaticX.x = "World";
        System.out.println(StaticX.x + " " + instance.x + " " + instance2.x);
    }
}