1.JAVA中==和equals的区别
区别:一个是运算符,一个是方法
==比较变量的值是否相同
①如果比较的对象是基本数据类型,则比较数值是否相等
②如果比较的是引用数据类型,则比较的是对象的内存地址是否相等
equals方法比较对象的内容是否相同
equals方法存在于Object类中,而Object类定义了equals方法
public boolean equals(Object obj) {return (this == obj);}
①如果类未重写equals方法,会调用Object父类中的equals方法(实际使用的也是==操作符)
②如果类重写了equals方法,则调用自己的equals方法(一般是比较对象的内容是否相等)
@SpringBootApplication
public class SpringBootDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringBootDemoApplication.class, args);System.out.println("Hello World");User user1 = new User("18","520");User user2 = new User("18","520");System.out.println(user1.equals(user2));}
}
实体类
@ApiModel(value = "用户实体类")
public class User {@ApiModelProperty(value = "用户姓名")public String userName;@ApiModelProperty(value = "用户密码")public String passWord;public User(String userName, String passWord) {this.userName = userName;this.passWord = passWord;}
}
比较两个对象是否相等,结果如下
Hello World
false
虽然两个变量的内容是一样的,但由于User类没有重写equals方法,导致调用的equals是父类Object的方法,结果会返回false
重写equals可以使用到@Data,相当于@Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode这5个注解的合集, @EqualsAndHashCode默认是false,表示不调用父类的属性
对添加了@Data的注解的,刚开始测试的操作会返回true,因为user1和user2的内容是一样的
2.String,StringBuffer,StringBuilder区别
2.1 String
String数组是final修饰的,所以线程是安全的,但是不可变的
@SpringBootApplication
public class SpringBootDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringBootDemoApplication.class, args);System.out.println("Hello World");String s1 = "Hello";String s2 = s1;System.out.println("修改前 s1 的身份哈希码:" + System.identityHashCode(s1));System.out.println("修改前 s2 的身份哈希码:" + System.identityHashCode(s2)); // 与 s1 相同s1 += " World";System.out.println("修改后 s1 的身份哈希码:" + System.identityHashCode(s1)); // 新值(新对象)System.out.println("修改后 s2 的身份哈希码:" + System.identityHashCode(s2)); // 旧值(原对象未变)}
}
结果如下,String是引用数据类型,刚开始s1和s2的指向的内容是一样的,所以修改前的身份哈希值是相等的,当s1拼接字符后引用地址发生变化,s1地址身份也发生了变化,但是s2不变
Hello World
修改前 s1 的身份哈希码:1988584481
修改前 s2 的身份哈希码:1988584481
修改后 s1 的身份哈希码:205010614
修改后 s2 的身份哈希码:1988584481
2.2 StringBuffe
可变的,父类AbstractStringBuilder的数组是可变的
方法都用了synchronized,所以线程是安全的
2.3 StringBuilder
可变的,父类AbstractStringBuilder的数组是可变的
线程不安全,无锁,无final修饰符
3.Java之String系列--创建对象的个数及其原理
方式 1:字面量赋值 String s = "abc";
对象个数:0 个或 1 个(取决于常量池是否已存在该字面量)。
- 存在:将s指向常量池"abc"的引用,不创建新对象(对象个数0)
- 不存在:在常量池创建一个String对象(内容为"abc"),并将s指向该对象(对象个数1)
方式 2:new String("abc")
检查常量池
- 若常量池中不存在
"abc"
,则先在常量池创建 1 个对象(内容为"abc"
)。 - 若已存在,则跳过此步。
创建堆对象
无论常量池是否存在,都会在 堆内存 中创建 1 个新的 String
对象(内容为 "abc"
),并将 s
指向堆对象。
所以创建对象的个数为1-2,取决于是否有常量池对象
4.Java之String系列--intern方法的作用及原理
不同版本的jdk对intern方法是有差异的(待梳理)
intern() 方法行为(Java 1.8):
当调用 str.intern() 时,会先检查常量池中是否存在与 str 内容相同的字符串:
- 存在:返回常量池中该字符串的引用。
- 不存在:将 str 的引用 添加到常量池(而非复制对象),并返回 str 本身的引用。
== 比较:
比较的是 对象引用地址,而非字符串内容(内容比较需用 equals())。
@SpringBootApplication
public class SpringBootDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringBootDemoApplication.class, args);String s1 = new String("Hello World");s1.intern();String s2 = "Hello World";System.out.println(s1 == s2);System.out.println("---------------------------------------");String s3 = new String("Hello") + new String("World");s3.intern();String s4 = "HelloWorld";System.out.println(s3 == s4);}
}
false
---------------------------------------
true----- s1 == s2
String s1 = new String("Hello World")
这个操作会创建两个对象
对象1:字符串字面量 "Hello World" 被加入 常量池(首次出现时自动入池)。
对象2:在 堆内存 中创建一个新的 String 对象(s1 指向该对象)。
此时:
常量池中存在 "Hello World"(引用地址记为 P)。
s1 指向堆中新建的对象(引用地址记为 H1)。
s1.intern()
调用intern(),常量池存在"Hello world"即地址P
itern()直接返回P,但未改变s1的指向,H1
String s2 = "Hello World"
直接使用字面量赋值,JVM会优先检查常量池
发现常量池存在"Hello World"地址P,因为s2指向P
s1指向H1(堆中新建对象),s2指向P(常量池的引用),地址不同----- s3 == s4
+ 操作符对字符串对象进行拼接时,底层通过StringBuilder 实现,最终返回一个新的堆对象为("HelloWorld")。
关键的是:此时常量池不存在"HelloWorld"字面量,因为拼接是动态生成,未出现字面量"HelloWorld"
s3指向新建的堆对象(引用地址为H3,内容为"HelloWorld"),此时常量池无引用
s3的intern()
s3调用intern(),但是常量池不存在"HelloWorld",因此会将s3的引用H3添加到常量池
但s3的指向仍是堆对象H3
String s4 = "HelloWorld";
s4使用字面量赋值,此时常量池存在"HelloWorld"的引用,即H3,因此s4直接指向H3
s3和s4的指向均指向H3,结果true
5.Java之String系列--String不可变的含义、原因、好处
5.1 String不可变的含义
String不可变的含义是:将一个已有字符串"123"重新赋值成"456",不是在原内存地址上修改数据,而是重新指向一个新对象,新地址。
5.2 String为什么不可变
String的内部数据是一个char数组,被final修饰的,创建后不可改变。
package java.lang;public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[];/** Cache the hash code for the string */private int hash; // Default to 0// 其他代码
}
5.3 String不可变的好处
使多线程安全
加快字符串的处理:这也就是一般将String作为Map的Key的原因,处理速度要快过其它的键对象,所以HashMap中的键往往都使用String。
避免安全问题等等
6.Java--static--用法/使用位置/实例
用法1:修饰成员属性
给属性加了static关键字之后,对象就不再拥有该属性了,该属性会由类去管理,即多个对象只对应一个属性。一般用于定义一些常量。
用法2:修饰成员方法
static修饰成员方法的作用是可以使用"类名.方法名"的方式操作方法,避免了先要new出对象的繁琐和资源消耗。
用法3:修饰代码块
在静态代码块中,可以访问静态变量,调用静态方法。
静态代码块(static)只在类加载的时候执行一次,实例代码块在创建对象的时候执行,加载的时候也会执行一次。
@Data
@Component
@NoArgsConstructor
@ApiModel(value = "用户实体类")
public class User {@ApiModelProperty(value = "用户姓名")private String userName;@ApiModelProperty(value = "用户密码")private String passWord;public User(String userName, String passWord) {this.userName = userName;this.passWord = passWord;}{System.out.println("代码块执行了...");}static {System.out.println("静态代码块执行了");}}
创建两个User对象,观察代码块的执行情况
@SpringBootApplication
public class SpringBootDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringBootDemoApplication.class, args);System.out.println("第一次创建");User user1 = new User();System.out.println("第二次创建");User user2 = new User();}
}
执行结果,类第一次加载的时候会执行代码块(无论是否有static修饰),后续创建过程中,只会调用无static修饰的代码块(每次执行都会调用)
静态代码块执行了
代码块执行了...第一次创建
代码块执行了...
第二次创建
代码块执行了...
用法4:静态导包
7.Java--异常/Exception--类型/原理
异常的层次结构
Throwable有两个直接的子类: Error、Exception。
Error
JVM内部的严重问题,比如资源不足等,无法恢复
Exception
可恢复。分RuntimeException和其他Exception
或者说分为非受检异常(unchecked exception)和受检异常(checked exception)。
RuntimeException(unchecked exception)
处理或者不处理都可以(不需try...catch...或在方法声明时throws)
其他Exception(checked exception)
Java编译器要求程序必须捕获(try...catch)或声明抛出(方法声明时throws)这种异常