5.0 Class
String Type
String 类型用于储存一串 char, 在 Java 中 String 不是一个基本数据类型,属于一个 class,因此这意味着 String 的每个 instance 都是一个对象。
Literal
运行之前就已经被编译器知道并准备好了
New
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
StaticX.x
也是这样访问
无论你创建多少个该类的对象,或者根本不创建对象,这个静态字段都只有一份存储空间,存在于类级别。不像实例字段,每个对象都有自己的那一份。所有实例都共享同一个字段:因为静态字段只有一份。