一、字符串的创建方式有哪些?
在Java中,有以下几种创建字符串的方式:
1.、直接赋值:使用双引号将字符串内容括起来,直接赋值给一个字符串变量。
String str1 = "Hello World";
2、使用new
关键字:使用new
关键字创建一个新的字符串对象。
String str2 = new String("Hello World");
3、使用字符数组:使用字符数组创建一个新的字符串对象。
char[] charArray = {'H', 'e', 'l', 'l', 'o'};
String str3 = new String(charArray);
4、使用StringBuilder
或StringBuffer
:使用StringBuilder
或StringBuffer
类来动态构建字符串。
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Hello");
stringBuilder.append(" ");
stringBuilder.append("World");
String str4 = stringBuilder.toString();
需要注意的是,字符串是不可变的,即一旦创建,其内容就不能被修改。因此,对字符串进行修改操作时,实际上是创建了一个新的字符串对象。
另外,Java中的字符串是引用类型,因此可以使用==
运算符来比较字符串的引用是否相等。但是,如果要比较字符串的内容是否相等,应该使用equals()
方法。
String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2); // true
String str3 = new String("Hello");
String str4 = new String("Hello");
System.out.println(str3 == str4); // false
System.out.println(str1.equals(str2)); // true
System.out.println(str3.equals(str4)); // true
二、String,StringBuffer,StringBuilder之间的区别是什么?
`String`、`StringBuffer`和`StringBuilder`是Java中用于处理字符串的类,它们之间的主要区别如下:
1. 可变性:
- `String`类是不可变的,一旦创建,其内容就不能被修改。每次对`String`进行修改操作,都会创建一个新的字符串对象。
- `StringBuffer`和`StringBuilder`类是可变的,可以对字符串进行修改操作,而不会创建新的对象。`StringBuffer`是线程安全的,适用于多线程环境;`StringBuilder`是非线程安全的,适用于单线程环境。
2. 性能:
- 由于`String`是不可变的,每次对字符串进行修改操作时,都会创建一个新的字符串对象,这会产生大量的临时对象,对性能有一定的影响。
- `StringBuffer`和`StringBuilder`是可变的,不会创建新的对象,因此在频繁的字符串拼接或修改操作时,性能更好。
3. 线程安全性:
- `String`是不可变的,因此是线程安全的。
- `StringBuffer`是线程安全的,它的方法都是同步的,可以在多线程环境中安全使用。
- `StringBuilder`是非线程安全的,它的方法没有进行同步处理,适用于单线程环境。
综上所述,如果需要频繁进行字符串的拼接或修改操作,并且在多线程环境中使用,应该使用`StringBuffer`。如果在单线程环境中使用,或者不需要线程安全性,可以使用`StringBuilder`,它的性能更好。如果不需要修改字符串的内容,或者需要保证字符串的不可变性,应该使用`String`类。
三、字符串String类能不能被继承,为什么?
在Java中,`String`类是被`final`修饰的,因此不能被继承。`final`关键字用于表示一个类不能被继承,或者一个方法不能被重写。
`String`类被设计为不可变的,即一旦创建,其内容就不能被修改。这种设计有助于提高字符串的安全性和性能。如果`String`类可以被继承,那么子类就有可能修改字符串的内容,这将违背`String`类的设计初衷。
另外,`String`类的不可变性还使得字符串可以被共享,从而节省内存空间。如果`String`类可以被继承,那么子类可能会修改字符串的内容,导致字符串无法被共享,从而增加了内存的开销。
因此,为了保护`String`类的不可变性和字符串的共享特性,Java语言设计者将`String`类声明为`final`,禁止其被继承。
四、字符串有哪些常用的API方法?
Java中的`String`类提供了许多常用的API,用于处理字符串。以下是一些常用的`String`类的API:
1. 字符串长度相关的方法:
- `length()`:返回字符串的长度。
- `isEmpty()`:判断字符串是否为空,即长度是否为0。
- `trim()`:去除字符串两端的空白字符。
2. 字符串比较相关的方法:
- `equals(Object obj)`:判断字符串是否与指定对象相等。
- `equalsIgnoreCase(String anotherString)`:忽略大小写,判断字符串是否与指定字符串相等。
3. 字符串查找相关的方法:
- `charAt(int index)`:返回指定索引位置的字符。
- `indexOf(int ch)`:返回指定字符在字符串中第一次出现的索引。
- `indexOf(String str)`:返回指定字符串在字符串中第一次出现的索引。
- `lastIndexOf(int ch)`:返回指定字符在字符串中最后一次出现的索引。
- `lastIndexOf(String str)`:返回指定字符串在字符串中最后一次出现的索引。
- `startsWith(String prefix)`:判断字符串是否以指定的前缀开始。
- `endsWith(String suffix)`:判断字符串是否以指定的后缀结束。
4. 字符串截取相关的方法:
- `substring(int beginIndex)`:返回从指定索引开始到字符串末尾的子字符串。
- `substring(int beginIndex, int endIndex)`:返回从指定索引开始到指定索引结束的子字符串。
5. 字符串替换相关的方法:
- `replace(char oldChar, char newChar)`:将字符串中的指定字符替换为新的字符。
- `replace(CharSequence target, CharSequence replacement)`:将字符串中的指定字符序列替换为新的字符序列。
6. 字符串拆分相关的方法:
- `split(String regex)`:按照指定的正则表达式拆分字符串为字符串数组。
7. 字符串转换相关的方法:
- `toUpperCase()`:将字符串转换为大写。
- `toLowerCase()`:将字符串转换为小写。
- `valueOf(Object obj)`:将指定对象转换为字符串。
这些只是`String`类中的一部分常用方法,还有其他更多的方法可以用于字符串的处理。
五、Java中常见的包装类有哪些,Integer存储元素的原理是什么?
Java中常见的包装类有以下几种:
1. `Boolean`:用于封装布尔类型的值。
2. `Byte`:用于封装字节类型的值。
3. `Short`:用于封装短整型的值。
4. `Integer`:用于封装整型的值。
5. `Long`:用于封装长整型的值。
6. `Float`:用于封装浮点型的值。
7. `Double`:用于封装双精度浮点型的值。
8. `Character`:用于封装字符类型的值。
这些包装类提供了一些方法,用于将基本数据类型转换为对象,以及进行对象之间的比较和运算。
对于Integer
类而言,它内部使用一个int
类型的字段来存储整型的值。当创建一个Integer
对象时,会将整型的值存储在该字段中。这样,就可以通过Integer
对象来操作整型的值。
Integer
类提供了一个缓冲池(Integer Cache),用于缓存一定范围内的整数对象。这个缓冲池默认缓存了范围在-128到127之间的整数对象。
六、引用传递和值传递之间的区别是什么?
在Java中,参数传递可以分为引用传递和值传递两种方式。
值传递(Pass-by-Value)是指将参数的值复制一份传递给方法或函数。在方法或函数内部对参数的修改不会影响原始值。
引用传递(Pass-by-Reference)是指将参数的引用(内存地址)传递给方法或函数。在方法或函数内部对参数的修改会影响原始值。
具体区别如下:
1. 值传递是将参数的值复制一份传递给方法或函数,因此在方法或函数内部对参数的修改不会影响原始值。而引用传递是将参数的引用(内存地址)传递给方法或函数,因此在方法或函数内部对参数的修改会影响原始值。
2. 值传递在方法或函数调用时会创建一个新的变量副本,方法或函数内部对参数的修改只会影响到副本,不会影响原始值。而引用传递在方法或函数调用时会共享同一份内存地址,方法或函数内部对参数的修改会直接影响到原始值。
3. 对于基本数据类型(如int、double等以及String类型),Java中使用的是值传递。即将变量的值复制一份传递给方法或函数,对参数的修改不会影响原始值。
4. 对于对象类型(如数组、类等),Java中使用的是引用传递。即将对象的引用(内存地址)传递给方法或函数,对参数的修改会影响原始对象。
需要注意的是,虽然Java中使用的是引用传递,但是并不意味着Java支持直接修改引用本身。在Java中,方法或函数无法修改传递的引用,只能通过引用来修改对象的状态。
七、什么是对象的浅拷贝,什么是深拷贝,浅拷贝和深拷贝之间的区别是什么?
对象的浅拷贝(Shallow Copy)和深拷贝(Deep Copy)是在对象复制过程中的两种不同方式。
浅拷贝是指创建一个新对象,然后将原始对象的字段值复制到新对象中。如果字段是基本数据类型,那么复制的是字段的值;如果字段是引用类型,那么复制的是字段的引用(内存地址)。这意味着浅拷贝创建的新对象和原始对象共享同一个引用类型的字段,对引用类型字段的修改会影响到原始对象和新对象。
深拷贝是指创建一个新对象,然后将原始对象的字段值复制到新对象中,并且对于引用类型字段,会递归地创建一个新的对象,并将其复制到新对象中。这样,深拷贝创建的新对象和原始对象完全独立,对引用类型字段的修改不会影响到原始对象和新对象。
区别如下:
1. 浅拷贝只复制对象的字段值,对于引用类型字段,复制的是引用(内存地址)。深拷贝不仅复制对象的字段值,还会递归地复制引用类型字段所引用的对象。
2. 浅拷贝创建的新对象和原始对象共享同一个引用类型字段,对引用类型字段的修改会影响到原始对象和新对象。深拷贝创建的新对象和原始对象完全独立,对引用类型字段的修改不会影响到原始对象和新对象。
3. 浅拷贝适用于对象的字段值较简单,没有引用类型字段,或者不需要对引用类型字段进行拷贝的情况。深拷贝适用于对象的字段值复杂,包含引用类型字段,需要完全独立的拷贝的情况。
在Java中,可以通过实现`Cloneable`接口和重写`clone()`方法来实现对象的浅拷贝。要实现对象的深拷贝,可以使用序列化和反序列化、手动递归拷贝等方式。
以下是浅拷贝的代码展示:
public class CloneTest {
public static void main(String[] args) throws CloneNotSupportedException {
//以下是实现了对象的浅拷贝
List<String> list=new ArrayList<>();
list.add("eat");
list.add("drink");
Person person=new Person("jack",10,list);
Person person1= (Person) person.clone();
System.out.println(person.getName()); //jack
System.out.println(person.getAge()); //10
System.out.println(person.getHobbies()); //[eat, drink]
System.out.println(person1.getName()); //jack
System.out.println(person1.getAge()); //10
System.out.println(person1.getHobbies()); //[eat, drink]
person1.setName("rose");
person1.getHobbies().add("play");
person1.setAge(20);
//浅拷贝只复制对象的字段值,对于引用类型字段,复制的是引用(内存地址)。
//创建的新对象和原始对象共享同一个引用类型字段,
//对引用类型字段的修改会影响到原始对象和新对象
//对于基本类型和String类型只是值传递,不会影响原始对象的值。
System.out.println(person.getName()); //jack
System.out.println(person.getAge()); //10
System.out.println(person.getHobbies()); //[eat, drink, play]
System.out.println(person1.getName()); //rose
System.out.println(person1.getAge()); //20
System.out.println(person1.getHobbies()); //[eat, drink, play]
}
}
class Person implements Cloneable {
private String name;
private Integer age;
private List<String> hobbies;
public Person(String name, Integer age, List<String> hobbies) {
this.name = name;
this.age = age;
this.hobbies = hobbies;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<String> getHobbies() {
return hobbies;
}
public void setHobbies(List<String> hobbies) {
this.hobbies = hobbies;
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
以下是深拷贝代码的实现:
class Student implements Cloneable {
private String name;
private List<String> hobbies;
public Student(String name, List<String> hobbies) {
this.name = name;
this.hobbies = hobbies;
}
public String getName() {
return name;
}
public List<String> getHobbies() {
return hobbies;
}
@Override
public Object clone() throws CloneNotSupportedException {
Student cloned = (Student) super.clone();
//通过手动创建一个新的ArrayList对象,
//并将原始对象的列表元素逐个拷贝到新的列表中,
//以实现对引用类型字段的深拷贝。
cloned.hobbies = new ArrayList<>(this.hobbies);
return cloned;
}
}
public class CloneTest1 {
public static void main(String[] args) throws CloneNotSupportedException {
//以下是对象深拷贝的结果
List<String> hobbies = new ArrayList<>();
hobbies.add("Reading");
hobbies.add("Swimming");
Student student = new Student("Alice", hobbies);
Student clone = (Student) student.clone();
System.out.println(student.getName()); // Alice
System.out.println(student.getHobbies()); // [Reading, Swimming]
System.out.println(clone.getName()); // Alice
System.out.println(clone.getHobbies()); // [Reading, Swimming]
clone.getHobbies().add("Cycling");
//通过深拷贝后的数据修改后的对象的列表不会影响到原始对象的列表。
System.out.println(student.getName()); // Alice
System.out.println(student.getHobbies()); // [Reading, Swimming]
System.out.println(clone.getName()); // Alice
System.out.println(clone.getHobbies()); // [Reading, Swimming, Cycling]
}
}