Java题集练习2
1.String直接赋值和new的对象个数,内存
String直接赋值会在字符串常量池中创建一个字符串,而使用new创建对象会储存在堆内存中,每使用new关键字就可以创建一个对象,但如果是String直接赋值同一个字符串第二次至多次时,由于第一次赋值该字符串已存在于字符串常量池中,所以不会创建新的对象,都使用常量池中的同一对象。
2.为什么字符串类型数据输出的时候,输出结果是字符串的值而不是字符串对象的地址(NEW出来的字符串对象)
因为字符串类型重写了Object类中的toString()方法,可以直接打印字符串的值而不是字符串对象的地址。
3.+连接String的对象个数
在java中,连接字符串的对象个数是基于所使用的连接方式而不同的。常见的连接方式包括使用“+”运算符,‘String.concat()’方法,以及‘StringBuilder’或‘StringBuffer’
-
使用“+”运算符
-
Java中每次使用“+”连接字符串时,都会创建一个新的’String‘对象,因为String类型是不可变的对象
-
编译器通常会优化一些简单的字符串连接操作。但在循环中使用“+”会生成多个中间’String‘对象
-
String s = "a" + "b" + "c"; // 编译器会优化成 "abc"
-
使用
String.concat()
:-
concat()
方法也会创建一个新的String
对象,它本质上和+
的作用类似。 -
String s = "a".concat("b").concat("c");
-
-
使用
StringBuilder
或StringBuffer
:-
StringBuilder
是可变的,不会每次连接时创建新的对象。因此,在循环中频繁连接字符串时,StringBuilder
是最有效的方式。 -
StringBuilder sb = new StringBuilder(); sb.append("a").append("b").append("c");
-
4.查找api学习split方法
我们知道,split方法常用于拆分字符串,比如字符串aa,bbb,cccc 我们想将它分为aa bbb cccc,就需要使用split方法将他们隔开,并删除逗号,以上问题可以这样来实现:
public class Split {
public static void main(String[] args) {
String str = "aa,bbb,cccc";
String[] strings = str.split(",");
for (String i :
strings) {
System.out.println(i);
}
}
}
由此一来,我们把一个字符串分为了我们需要的三个字符串,但是,split的用法并不止于此,看下面这段代码
public class Split {
public static void main(String[] args) {
String str = "";
String[] strings = str.split(",");
System.out.println(strings.length);
}
}
如果是一个空的字符串,我们使用split方法后打印他的长度,绝大部分人会认为长度为0,但结果为1,原因我们在后面详细说明,再来看看这段代码
public class Split {
public static void main(String[] args) {
String str = ",";
String[] strings = str.split(",");
System.out.println(strings.length);
}
}
如果是一个只有逗号的字符串,它的长度会是几呢,有的人会认为是2,因为逗号前后都是一个空的字符串,也有人认为长度是1,只算逗号前的那个空字符串,但是结果是0,为什么是0呢?
我们先来看看split方法的源码
public String[] split(String regex) {
return split(regex, 0);
}
public String[] split(String regex, int limit) {
具体实现...
}
我们可以看到,split方法其实是有两个参数的,只不过我们平时使用的是他的重载方法,它默认把第二个参数limit设置为0
regex参数指的是:
1 如果表达式不匹配输入的任何内容,返回的数组只具有一个元素,即此字符串(空字符也是一个字符串)
2 可以匹配的情况下,每一个字符串都由另一个匹配给定表达式的子字符串终止,或者由此字符串末尾终止(数组中的字符串按照他们在此字符串中出现的顺序排列)
limit参数指的是:
1 limit>0:模式匹配将被最多应用n-1次,数组的长度将不会大于n,数组的最后一项将包含所有超出最后匹配的定界符的输入
2 limit<0: 模式匹配将应用尽可能多的次数,而且数组的长度是任何长度。
3 lilmit=0: 模式匹配将被应用尽可能多的次数,数组可以是任何长度,并且结尾空字符串将被丢弃。
讲解完参数我们知道,我们使用的split方法limit参数是默认设置为0的,这也是为什么第二种情况他的长度是1,因为它与逗号不匹配,所以返回的是空字符串,而最后一种情况长度是0,是因为
5.StringBuffer和String的相同不同
StringBuff和String的相同点:
1 都用来处理字符串
2 两个类都提供了length(),toString(),charAt(),subString()方法,而且在两个类中的用法完全相同
3 字符在字符串中的位置都是从0索引处开始的
不同点:
1 String是不可变类,StringBuffer是可变类
2 String类覆盖了Object类中的equals方法,而StringBuffer并没有覆盖
3 两个类都覆盖了Object类中的toString方法,但是各自实现的方式是不一样的,String类会返回当前String实例本身的引用,而StringBuffer返回一个以当前StringBuffer缓冲区中的所有字符为内容的新的String对象的引用
6 8种基本数据类型的数据 存放在哪个区
8中基本数据类型的数据都存放在栈内存中,而引用类型的数据存放在堆内存中
7 String,基本类型,包装类的转换,并写出案例
代码实现
public class StringTransformNormal {
public static void main(String[] args) {
String intString = "123";
char c = 'a';
//String类型转换为基本数据类型的包装类
Integer integer = Integer.parseInt(intString);
System.out.println(integer * 10);
//基本数据类型转换为String类型
String cString = Character.toString(c);
System.out.println(cString);
}
}
8 不变模式,如果String不是不变模式会出现什么问题
不变模式,就是为了尽可能的去除并行中的同步操作,提高并行程序的性能,可以使用一种不可改变的对象,依靠对象的不变性,可以确保其在没有同步操作的多线程环境中依然始终保持内部状态的一致性和正确性。并且,不变模式通过回避问题而不是解决问题的态度来处理多线程并发访问控制。
不变模式的主要使用场景
1 当对象被创建后,其内部状态和数据不再发生变化
2 当对象需要被共享,被多线程频繁访问
实现方法
- 去除setter方法以及所有修改自身属性的方法。
- 将所有属性设置为私有,并用final标记,确保不能被修改
- 确保没有子类可以重载
- 有一个可以创建完整对象的构造函数
关于String中的不变模式
在JDK中,不变模式有很多应用,如Integer,Long等所有元数据类的包装类,当然,最典型的就是String类了。
对于String类而言,内部是这样定义的:public final class String 是一个被final关键字修饰的类,我们来看看上面说到final关键字的作用,这就意味着:
- String中的数据只能在String对象被构建时赋值,之后就永远不能改变
- String类不会有子类重载它的方法,也确保了String类生成的对象就是String对象,而不可能是其他类的对象
String如果不是不变模式会存在的问题
1 安全问题:
String基本约定中有一个最重要的条例是immutable,意思为不可被复写,由于String几乎是每个类都会使用的类,所以保护他的不变模式是非常重要的,如果没有声明为final,String的子类就可能被复写,复写后的String有非常大的风险,例如会破坏HashMap键值的唯一性
2 性能问题:
String类在堆中还维护着一个字符串常量池中,每个字符串的值其实都指向同一个内存地址,所以在大量使用字符串的情况下,使用不变模式可以节省内存空间,提高效率
9 .equals和= = 的对比,String类和其他类 举例
在比较八种基本数据类型时,类型之间的比较只能使用==,因为.euqals方法是String类重写了Object类中的equals方法,用于与其他数据比较,比如,我想比较int和long类型数据,代码如下
public static void main(String[] args) {
int a = 10;
long b = 10L;
System.out.println(a == b);
//结果为true,因为两个类型的数据都是10,他们的值是相等的
}
那么,String类与八种基本数据类型之间的比较是怎样表现的呢,我们看下面这段代码
public static void main(String[] args) {
String str = "123";
String s = "123";
int a = 123;
System.out.println(str.equals(a));
//结果为false
System.out.println(str == s);
//结果为true,因为两个字符串的值是相同的
//String类型不可用于==比较八种基本数据类型
}
我们可以看到,第一段打印的结果之所以为false,是因为equals方法内部具有两重判断,第一重判断是判断两个数据的内存地址是否相同,如果不相同则直接为false,如果相同则进行第二重判断来判断是否两个数据的值是相同的。
第二段打印我们可以看出,在八种基本数据类型和String类型之间是无法通过==进行比较的,但可以在String类型内部进行比较,也可以用于其他引用数据类型之间的比较,比较的是两个引用类型的地址,由于两个字符串的值都是“123”,所以结果为true。那么我们可以设想一下,str和s如果使用equals方法进行比较,结果会是什么呢?
public static void main(String[] args) {
String str = "123";
String s = "123";
int a = 123;
System.out.println(str.equals(s));
//结果为true
}
不难看出,结果肯定是为true的,这涉及到String类型创建字符串的知识,String类型创建的对象都会存储在字符串常量池中,所以str和s代表的是同一个地址,都是已经保存在字符串常量池中的“123”。
综上所述,我总结出了equals方法与的区别,我将分别叙述与equals方法的作用来说明
==运算符
-
在八种基本数据类型之间进行比较时,比较的是他们的值
-
在引用数据类型之间比较时,比较的是他们在内存中的地址
equals方法
-
第一重判断:判断两个数据的内存地址是否相同,不相同返回为false
-
第二重判断:再判断两个数据的值是否相同,相同返回true,反之
10 源码提升:打印地址的四种方法及源码
public static void main(String[] args) {
Print print = new Print();
//First 直接打印
System.out.println(print);
//Second 使用toString方法
System.out.println(print.toString());
//Third 使用hashCode方法
System.out.println(print.hashCode());
//Forth 使用System.identityHashCode方法
System.out.println(System.identityHashCode(print));
}
其中,toString方法的源码中最后调用的是hashCode方法,只不过是前面进行了一个地址表示方式的转换,转换为十六进制数来表示。那么问题来了,如果重写了hashCode方法,哪一个打印方法的值会永远不变呢?显然是最后一种方法,第一种直接打印的方式也相当于是使用了hashCode方法,只有最后一种是自己创建的方法来打印地址;同时,如果重写了hashCode方法,则也需要重写equals方法,因为equals方法中也使用了hashCode方法来比较两个引用类型的地址是否相同。
11 浅拷贝和深拷贝
浅拷贝指的是复制一个对象的所有属性值,复制该对象的引用,而不是复制一个新对象出来。
深拷贝则是复制一个对象的所有数据,包括这个对象,拷贝出一个具有新地址的新对象。
简单来说,浅拷贝后的对象与他所拷贝的对象共用一个引用,即修改他们两个任意一个的属性值,另一个对象的属性值也会随之而改变;而深拷贝则是拷贝出了一个与被拷贝对象完全相同的新对象,但拷贝后改变他的属性值,原对象并不会随之而改变。
以上就是我所做的java题集练习第二章的所有内容,欢迎各位大佬纠察指正,谢谢!