String类
在C语言中已经涉及到字符串了,但是在C语言中要表示字符串只能使用字符数组或者字符指针,可以使用标准库提供的字符串系列函数完成大部分操作,但是这种将数据和操作数据方法分离开的方式不符合面向对象的思想,而字符串应用又非常广泛,因此Java语言专门提供了String类
常用方法
构造字符串
这三种方式输出结果一致
我们用图解释:
这图有个缺陷 在字符串中 少了空格符号(是我大意了)
注意:
1. String是引用类型,内部并不存储字符串本身,在String类的实现源码中,String类实例变量如下:
2. 在Java中用双引号 引起来的也是String类型对象
String对象的比较
字符串的比较是常见操作之一,比如:字符串排序
Java中总共提供了4种字符串比较方式:
1. 用 == 比较是否引用同一个对象
注意:对于内置类型,==比较的是变量中的值;对于引用类型==比较的是引用中的地址
2. boolean equals(Object anObject) 方法:按照字典序比较
字典序:按照字符的前后顺序
3.int compareTo(String s) 方法: 按照字典序进行比较
equals返回的是boolean类型,而compareTo返回的是int类型。具体比较方式:
1. 先按照字典次序大小比较,如果出现不等的字符,直接返回这两个字符的大小差值
2. 如果前k个字符相等(k为两个字符长度最小值),返回值为两个字符串长度差值
4. int compareToIgnoreCase(String str) 方法:与compareTo方式相同,但是忽略大小写比较
字符串查找
字符串查找也是字符串中非常常见的操作,String类提供的常用查找的方法:
1.char charAt(int index)
功能:返回index位置上的字符 如果index为负数或者越界 抛出IndexOutOfBoundsException异常
2.int indexOf(int ch)
功能:返回字符ch 第一次出现的位置 没有则返回-1
3.int indexOf(int ch, int fromIndex)
功能:从fromIndex位置开始找,从后往前找ch第一次出现的位置,没有则返回-1
4.int indexOf(String str)
功能:返回str第一次出现的位置 没有则返回-1
5.int indexOf(String str, int fromIndex)
功能:从fromIndex位置开始找str第一次出现的位置,没有返回-1
6.int laseIndexOf(int ch)
功能:从后往前找,返回字符ch第一次出现的位置,没有返回-1
7.int lastIndexOf(int ch, int fromIndex)
功能:从fromIndex位置开始找,从后往前找ch第一次出现的位置,没有则返回-1
8.int lastIndexOf(String str)
功能:从后往前找,返回str第一次出现的位置,没有返回-1
9.int lastIndexOf(String str, int fromIndex)
功能:从fromIndex位置开始找,从后往前找str第一次出现的位置,没有则返回-1
相互转化
1.数值和字符串
注意:Integer、Double等是Java中的包装类型 之后会讲到
2.大小写转换
3.字符串和数组
4.格式化
字符串替换
使用一个指定的新的字符串替换掉已有的字符串数据,可用的方法如下:
功能:替换所有的指定内容 把字符串regex替换为replacement
功能:替换首个内容 把第一个字符串regex替换为replacement
代码演示:
注意事项:由于字符串是不可变对象 替换不修改当前字符串 而是产生一个新的字符串
我们这里只需要记住一个结论:只要涉及到String类型的转变 都不是在原有字符串上进行的修改
原理:会产生一个新的对象(在该对象中进行操作)
字符串拆分
可以将一个完整的字符串按照指定的分隔符划分为若干个字符串
1.String[] split(String regex)
功能:将字符串全部拆分
运行结果:
2.String[] split(String regex,int limit)
功能:将字符串以指定的格式 拆分为limit个组
运行结果:
拆分是特别常用的操作. 一定要重点掌握. 另外有些特殊字符作为分割符可能无法正确切分, 需要加上转义
举例:拆分IP地址
我们发现这里没有输出 这是因为’ . ’作为分割符无法正确切分 需要加上转义
注意事项:
1. 字符" | ", " * " , " + " ,‘ . ’ 都得加上转义字符,前面加上 "\\" .
2. 而如果是 "\" ,那么就得写成 "\\\\" .
3. 如果一个字符串中有多个分隔符,可以用"|"作为连字符
字符串截取
从一个完整的字符串之中截取出部分内容。可用方法如下:
1.String substring(int beginIndex)
功能:从指定索引截取到结尾
从下标为5的位置开始截取
2.String substring(int beginIndex,int endIndex)
功能:截取部分内容
从下标为0的位置 一直截取到下标为5的位置(不包含下标为5)
注意事项:
1. 索引从0开始
2. 注意前闭后开区间的写法, substring(0, 5) 表示包含 0 号下标的字符, 不包含 5 号下标
其他操作方法
1.String trim()
功能:去掉字符串中的左右空格 保留中间空格
2.String toUpperCase()
功能:字符串转大写
3.String toLowerCase()
功能:字符串转小写
这两个函数只转换字母
字符串的不可变性
String是一种不可变对象. 字符串中的内容是不可改变。字符串不可被修改,是因为:
1. String类在设计时就是不可改变的,String类实现描述中已经说明了
以下来自JDK1.8中String类的部分实现:
String类中的字符实际保存在内部维护的value字符数组中,该图还可以看出:
(1). String类被final修饰,表明该类不能被继承
(2). value被final修饰,表明value自身的值不能改变,即不能引用其它字符数组,但是其引用对象中的内容可以修改(如 final char[] array={‘a’,’b’,’c’}; array[0]=’g’;)
(3).value被private修饰 表示只能在当前类使用 所以在其他类中无法去改变value字符数组
2. 所有涉及到可能修改字符串内容的操作都是创建一个新对象,改变的是新对象
为什么 String 要设计成不可变的?(不可变对象的好处是什么?) (选学)
1. 方便实现字符串对象池. 如果 String 可变, 那么对象池就需要考虑写时拷贝的问题了.
2. 不可变对象是线程安全的.
3. 不可变对象更方便缓存 hash code, 作为 key 时可以更高效的保存到 HashMap 中.
那如果想要修改字符串中内容,该如何操作呢?
字符串修改
注意:尽量避免直接对String类型对象进行修改,因为String类是不能修改的,所有的修改都会创建新对象,效率非常低下
如:
这种方式不推荐使用 因为其效率非常低 中间创建了好多临时对象
接下来我们讲解以下该段代码的操作过程:
- 创建一个新的StringBuilder的对象 这里我们假设为temp
- 将s对象 append(追加) 到temp之后
- 将”world”字符串 append(追加) 到temp之后
- temp调用其toString方法构造一个新的String对象
- 将新的String对象的引用赋值给s
可以看到在对String类进行修改时 效率是非常慢的(在修改字符串时 先创建一个新的字符串对象) 因此:尽量避免对String的直接需要 如果要修改建议尽量使用StringBuilder或者StringBuffer
StringBuffer与StringBuilder
由于String的不可变性 为了方便字符串的修改 Java中又提供了StringBuffer和StringBuilder类
这两个类大部分功能是相同的
StringBuffer与StringBuilder都继承自AbstractStringBuilder类
AbstractStringBuilder中也是使用字符数组来保存字符串,但其是可变类
并且都提供了一系列插入、追加、改变字符串里的字符序列的方法,它们的用法基本相同
注意:虽然StringBuilder和StringBuffer是可变的 但它们并不是在”原本的字符串“上进行修改
当我们使用StringBuilder和StringBuffer的append、insert等方法时 只是在其内部维护的字符数组上执行操作(不是原本的字符串上修改)
下面可以看一下二者的用法
StringBuilder.append:添加任意类型数据的字符串形式,并返回当前对象自身
StringBuffer与之相同
StringBuilder.reverse:逆置字符串
StringBuffer和StringBuilder的区别
StringBuffer是线程安全的,这意味着它可以在多线程环境中安全地使用,而无需额外的同步措施。这是通过在其方法实现中添加适当的同步块来实现的。
StringBuilder则不是线程安全的,它假设在单线程环境中使用。由于没有同步开销,因此在单线程环境中使用StringBuilder通常比使用StringBuffer更快
结论:StringBuffer是线程安全的 但随着而来的是效率降低 资源耗费(相对于StringBuilder)
String和StringBuilder最大的区别在于String的内容无法修改,而StringBuilder的内容可
以修改。
频繁修改字符串的情况考虑使用StringBuilder
注意:String和StringBuilder类不能直接转换。
如果要想互相转换,可以采用如下原则:
1.String变为StringBuilder: 利用StringBuilder的构造方法或append()方法
I.使用构造方法
String str = "Hello, World!";
StringBuilder sb = new StringBuilder(str);
II.使用append方法
String str = "Hello, ";
StringBuilder sb = new StringBuilder();
sb.append(str); sb.append("World!");
StringBuilder变为String: 调用toString()方法
StringBuilder s1 = new StringBuilder("Hello, World!");
String str = s1.toString();
这里就不过多去解读其中的方法了 大家有兴趣的可以自行去学习
补充:
内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类。在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现
在外部类中,内部类定义位置与外部类成员所处的位置相同,因此称为成员内部类
public class Test {
class test(){
}
}
//Test是外部类
//test是内部类
【注意事项】
1. 定义在class 类名{}花括号外部的,即使是在一个文件里,都不能称为内部类
public class A{
}
class B{
}
// A 和 B是两个独立的类,彼此之前没有关系
2. 内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
内部类的分类
根据内部类定义的位置不同,一般可以分为以下几种形式:
1. 成员内部类(普通内部类:未被static修饰的成员内部类 , 静态内部类:被static修饰的成员内部类)
2. 局部内部类(不谈修饰符)、匿名内部类
注意:内部类其实日常开发中使用并不是非常多,大家在看一些库中的代码时候可能会遇到的比较多,日常开始中使用最多的是匿名内部类。
public class OutClass {
// 成员位置定义:未被static修饰 --->实例内部类
public class InnerClass1{
}
// 成员位置定义:被static修饰 ---> 静态内部类
static class InnerClass2{
}
public void method(){
// 方法中也可以定义内部类 ---> 局部内部类:几乎不用
class InnerClass5{
}
}
}
我们接下来详细讲一下内部类
实例内部类:未被static修饰的成员内部类
注意事项
1. 外部类中的任何成员都可以在实例内部类方法中直接访问
2. 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的约束
3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名
称.this.同名成员 来访问
4. 实例内部类对象必须在先有外部类对象前提下才能创建
5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用
6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象
静态内部类:被static修饰的内部成员类称为静态内部类
在Java中,静态内部类(也被称为嵌套静态类)可以访问外部类的静态成员,但不能直接访问外部类的非静态成员
静态内部类不需要外部类的实例就可以被创建和实例化,因此它与外部类实例没有直接的关联。由于静态内部类不持有外部类实例的引用,所以它无法直接访问外部类的非静态成员
结论:
1. 静态内部类可以访问外部类的静态成员变量和方法
2.静态内部类不能直接访问外部类的非静态成员变量和方法(除非通过外部类的实例去访问)
局部内部类
定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式
注意事项
1. 局部内部类只能在所定义的方法体内部使用
2. 不能被public、static等修饰符修饰
3. 编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class
4. 几乎不会使用
匿名内部类
匿名内部类是一种特殊的内部类,它没有类名,并且通常用于实现某个接口或继承一个类,而不需要给它一个明确的类名。匿名内部类常用于创建并立即使用一次的对象
下面是一个匿名内部类用于实现接口的简单示例:
public class OuterClass {
interface MyInterface {
void doSomething();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
// 使用匿名内部类实现MyInterface接口
MyInterface myObj = new MyInterface() {
@Override
public void doSomething() {
System.out.println("Doing something in anonymous inner class.");
}
}
myObj.doSomething();
}
}
在这个例子中,我们定义了一个名为OuterClass的类,它包含一个接口MyInterface。在main方法中,我们创建了一个MyInterface的匿名内部类实例,并重写了doSomething方法。然后,我们调用了这个匿名内部类实例的doSomething方法
匿名内部类也可以用于继承一个类,但是这种情况相对较少见,因为继承通常与特定的类名关联。然而,如果需要快速创建一个继承自某个类的子类实例,并且这个子类只会被使用一次,那么匿名内部类就可以派上用场。
需要注意的是,由于匿名内部类没有类名,所以它们不能定义构造函数。但是,它们可以访问外部类的所有成员(包括私有成员),因为它们是外部类的一个部分