String为什么被设计为不可变?
final 线程安全的,同一个字符串实例可以被多个线程共享,这样就不用去考虑线程同步的问题
字符串池的实现,运行时节约了很多heap空间
字符串如果可变会引发很严重的安全问题,譬如数据库的连接是以传入用户名和密码的字符串形式,Socket编程中主机名和端口都是以字符串的形式传入的
字符串不可变,保证了hash码的唯一,不需要重新计算,因此HashMap的键往往都使用字符串,字符串的处理速度要快过其他的键对象
String,StringBuffer,StringBuilder的区别
StringBuffer的父类是CharSequence,他是线程安全的,他没有实现equales方法 equales方法里面有 instance String的判断,再者线程安全会带来额外的系统开销
StringBuilder线程不安全
String线程安全
String的每次修改操作都是在内存中重新new一个对象出来(String str6 = "npm".toUpperCase();), 而StringBuilder和StringBuffer做修改实质上是进行数组的扩容,所以使用他俩时可以考虑初始化大小,减少扩容次数,提高代码的高效性。
String str7 = new String("TTT");
String str8 = "ttt".toUpperCase();
System.out.println(str7 == str8); //9,false
System.out.println(str7.equals(str8)); //10,true
toUpperCase 方法内部创建了新字符串对象
String str = "Hello world"和String str = new String("Hello word")的区别?
运行期间字面常量 "hello world" 被存储在运行时常量池(只保存了一份),而通过 new 关键字来生成对象是在堆区进行的,堆区进行对象生成的过程是不会去检测该对象是否已经存在的,所以通过 new 来创建的一定是不同的对象,即使字符串的内容是相同的。
语句 String str = new String("abc"); 一共创建了多少个对象?
在类加载过程中在运行时常量池中先创建了一个 "abc" 对象,在运行期间只创建了一个对象str
用 java 代码写一个方法从字符串中删除给定字符?
String removeChar(String str, char c) {
if (str == null) {
return null;
}
return str.replaceAll(Character.toString(c), "");
}
String str1 = "123";
System.out.println("123" == str1.substring(0)); // true
System.out.println("23" == str1.substring(1)); // false
解答:
substring 方法实现里面有个 index == 0 的判断,当 index 等于 0 就直接返回当前对象,否则新 new 一个 sub 的对象返回,而 == 又是地址比较,所以结果如注释。
String str5 = "NPM";
String str6 = "npm".toUpperCase();
System.out.println(str5 == str6); // false
System.out.println(str5.equals(str6)); // true
备注:
toUpperCase 方法内部创建了新字符串对象。
String str9 = "a1";
String str10 = "a" + 1;
System.out.println(str9 == str10); // true
备注:
两个字符串常量连接时(相加)得到的新字符串依然是字符串常量且保存在常量池中只有一份。
String str11 = "ab";
String str12 = "b";
String str13 = "a" + str12;
System.out.println(str11 == str13); //false
备注:
当字符串常量与 String 类型变量连接时得到的新字符串不再保存在常量池中,而是在堆中新建一个 String 对象来存放,很明显常量池中要求的存放的是常量,有 String 类型变量当然不能存在常量池中了。
String str14 = "ab";
final String str15 = "b";
String str16 = "a" + str15;
System.out.println(str14 == str16); // true
字符串常量与 String 类型常量连接,得到的新字符串依然保存在常量池中,因为对 final 变量的访问在编译期间都会直接被替代为真实的值。
private static String getBB() {
return "b";
}
String str17 = "ab";
final String str18 = getBB();
String str19 = "a" + str18;
System.out.println(str17 == str19); // false
赋值是通过方法调用返回的,那么它的值只能在运行期间确定,因此指向的不是同一个对象。
String str20 = "ab";
String str21 = "a";
String str22 = "b";
String str23 = str21 + str22;
System.out.println(str23 == str20); // false
System.out.println(str23.intern() == str20); // true
System.out.println(str23 == str20.intern()); // false
System.out.println(str23.intern() == str20.intern()); // true
对于调用 intern 方法如果字符串常量池中已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定)则返回字符串常量池中的字符串,否则将此 String 对象添加到字符串常量池中,并返回此 String 对象的引用。
Java 自增自减运算符
count++ 是一个有返回值的表达式,返回值是 count 自加前的值,比如
count = count++; //这个时候count打印值是0
在 C++ 中 count = count++; 与 count++ 是等效的,而在 java 等语言中 count = count++; 与 count++ 是不等效的,区别如这道题。
通过 JDK 提供的 AtomicInteger 类来直接保证自增自减并发安全,其实现基于 volatile 对象的 CAS 操作来保证并发安全。
面向对象
抽象性是指对一类事物的高度提炼以得到共同的共性部分,抽象不需要了解全部细节,而只是一种通用的描述约束,抽象可以是过程抽象或者数据抽象。
多态是对象在不同时刻表现出来的多种状态,是一种编译时期状态和运行时期状态不一致的现象。
一个方法只能有一个可变长参数,且这个可变长参数必须是该方法的最后一个参数,java 不允许存在一个方法具备多个变长参数或者变长参数不是方法的最后位置的情况。
void print(String... args) {
}
变长参数在编译成字节码后的表现是一个 String 数组类型的形参,等价于
void print(String[] args) {
}
Java 中代码块相关知识点
静态代码块作用于类级别,随着类的加载而被执行,只要类被加载了就会执行,而且只会加载一次,主要用于给类进行初始化。
构造代码块在每次创建一个对象时就会执行一次且优先于构造函数,主要用于初始化不同对象共性的初始化内容和初始化实例环境。
构造方法在每次创建一个对象时就会执行一次,同时构造方法是给特定对象进行初始化,而构造代码是给所有对象进行初始化;
所以通过分析得出他们三者的执行顺序为 静态代码块 > 构造代码块 > 构造方法。
类加载 -》 类实例化(即初始化,获取类的属性并初始化) -》 构造方法(实例化,实例化对象,涉及到构造函数)