Skip to content

Commit bacdea0

Browse files
committed
Update Java Notes
1 parent 81f0010 commit bacdea0

1 file changed

Lines changed: 229 additions & 52 deletions

File tree

JavaSE.md

Lines changed: 229 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2572,41 +2572,96 @@ public class CodeDemo {
25722572

25732573
### Object
25742574

2575-
Objectjava.lang.ObjectObject类是Java中的祖宗类。一个类或者默认继承Object类,或者间接继承Object类,Object类的方法是一切子类都可以直接使用.
2575+
#### 概述
2576+
2577+
Object类是Java中的祖宗类,一个类或者默认继承Object类,或者间接继承Object类,Object类的方法是一切子类都可以直接使用
25762578

25772579
Object类常用方法:
25782580

25792581
* `public String toString()`:
25802582
默认是返回当前对象在堆内存中的地址信息:类的全限名@内存地址,例:Student@735b478;
25812583
直接输出对象名称,默认会调用toString()方法,所以省略toString()不写;
25822584
如果输出对象的内容,需要重写toString()方法,toString方法存在的意义是为了被子类重写
2583-
25842585
* `public boolean equals(Object o)`:
2585-
默认是比较两个对象的地址是否相同,如果只是比较两个对象的地址可以用“==”替代equals。
2586-
所以equals存在的意义是为了被子类重写,以便程序员可以自己来定制比较规则。
2586+
默认是比较两个对象的内容是否相同,如果只是比较两个对象的地址可以用“==”替代equals
2587+
所以equals存在的意义是为了被子类重写,以便程序员可以自己来定制比较规则
2588+
* `protected Object clone()`:创建并返回此对象的副本
2589+
2590+
只要两个对象的内容一样,就认为是相等的:
2591+
2592+
```java
2593+
public boolean equals(Object o) {
2594+
// 1.判断是否自己和自己比较,如果是同一个对象比较直接返回true
2595+
if (this == o) return true;
2596+
// 2.判断被比较者是否为null ,以及是否是学生类型。
2597+
if (o == null || this.getClass() != o.getClass()) return false;
2598+
// 3.o一定是学生类型,强制转换成学生,开始比较内容!
2599+
Student student = (Student) o;
2600+
return age == student.age &&
2601+
sex == student.sex &&
2602+
Objects.equals(name, student.name);
2603+
}
2604+
```
2605+
2606+
**面试题**:== 和equals的区别
2607+
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的**地址**是否相同,即是否是**指相**同一个对象。比较的是真正意义上的指针操作。
2608+
equals用来比较的是两个对象的**内容**是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断
2609+
2610+
2611+
2612+
***
2613+
2614+
2615+
2616+
#### 深浅克隆
2617+
2618+
克隆就是制造一个对象的副本。根据所要克隆的对象的成员变量中是否含有引用类型,可以将克隆分为两种:浅克隆(Shallow Clone) 和 深克隆(Deep Clone),默认情况下使用Object中的clone方法进行克隆就是浅克隆,即完成对象域对域的拷贝
2619+
2620+
浅拷贝(shallowCopy):被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象,简而言之就是增加了一个指针指向原来对象的内存地址
2621+
2622+
深拷贝(deepCopy):深拷贝是一个整个独立的对象拷贝,深拷贝会拷贝所有的属性并拷贝属性指向的动态分配的内存,简而言之就是把所有属性复制到一个新的内存,增加一个指针指向这个新的内存
2623+
2624+
* 使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误
2625+
2626+
2627+
2628+
Cloneable 接口是一个标识性接口,即该接口不包含任何方法(包括clone()),但是如果一个类想合法的进行克隆,那么就必须实现这个接口,在使用clone()方法时,若该类未实现 Cloneable 接口,则抛出异常
2629+
2630+
* Clone & Copy:`Student s = new Student`
2631+
2632+
`Student s1 = s`:只是copy了一下reference,s和s1指向内存中同一个object,对对象的修改会影响对方
2633+
2634+
`Student s2 = s.clone()`:会生成一个新的Student对象,并且和s具有相同的属性值和方法
2635+
2636+
* Shallow Clone & Deep Clone:
2637+
浅克隆:Object中的clone()方法在对某个对象克隆时对其仅仅是简单地执行域对域的copy
2638+
2639+
![](https://gitee.com/seazean/images/raw/master/JavaSE/Object浅克隆.jpg)
2640+
2641+
对八种基本类型的克隆是没有问题的,String 在克隆时只是克隆了它的引用,因为**String是在内存中不可以被改变的对象**,所以在使用克隆时,我们可以将 String类型视为与基本类型,只需浅克隆即可
2642+
2643+
但当对一个引用类型进行克隆时只是克隆了它的引用,克隆对象和原始对象共享了同一个对象成员变量
25872644

2588-
* 需求:只要两个对象的内容一样,我们就认为他们是相等的。
2645+
深克隆 : 在对整个对象浅克隆后,还需对其引用变量进行克隆,并将其更新到浅克隆对象中去
25892646

25902647
```java
2591-
public boolean equals(Object o) {
2592-
// 1.判断是否自己和自己比较,如果是同一个对象比较直接返回true
2593-
if (this == o) return true;
2594-
// 2.判断被比较者是否为null ,以及是否是学生类型。
2595-
if (o == null || this.getClass() != o.getClass()) return false;
2596-
// 3.o一定是学生类型,强制转换成学生,开始比较内容!
2597-
Student student = (Student) o;
2598-
return age == student.age &&
2599-
sex == student.sex &&
2600-
Objects.equals(name, student.name);
2648+
public class Student implements Cloneable{
2649+
private String name;
2650+
private Integer age;
2651+
private Date date;
2652+
2653+
@Override
2654+
protected Object clone() throws CloneNotSupportedException {
2655+
Student s = (Student) super.clone();
2656+
s.date = (Date) date.clone();
2657+
return s;
2658+
}
2659+
//.....
26012660
}
26022661
```
26032662

26042663

26052664

2606-
**面试题**== 和equals的区别
2607-
== 比较的是变量(栈)内存中存放的对象的(堆)内存地址,用来判断两个对象的**地址**是否相同,即是否是**指相**同一个对象。比较的是真正意义上的指针操作。
2608-
equals用来比较的是两个对象的**内容**是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。
2609-
26102665

26112666

26122667
***
@@ -2879,16 +2934,17 @@ public class Demo1_25 {
28792934

28802935

28812936

2882-
#### 不可变的好处
2937+
#### 不可变好处
28832938

28842939
* 可以缓存 hash 值
28852940
因为 String 的 hash 值经常被使用,例如 String 用做 HashMap 的 key。不可变的特性可以使得 hash 值也不可变,只需要进行一次计算。
28862941
* String Pool 的需要
28872942
如果一个String对象已经被创建过了,就会从 String Pool中取得引用。只有 String是不可变的,才可能使用 String Pool。
2888-
28892943
* 安全性
28902944
String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 的那一方以为现在连接的是其它主机,而实际情况却不一定是。
2891-
* String 不可变性天生具备线程安全,可以在多个线程中安全地使用。
2945+
* String 不可变性天生具备线程安全,可以在多个线程中安全地使用
2946+
2947+
28922948

28932949

28942950

@@ -11271,24 +11327,6 @@ public class Demo1_27 {
1127111327

1127211328

1127311329

11274-
***
11275-
11276-
11277-
11278-
### 深浅拷贝
11279-
11280-
浅拷贝(shallowCopy)只是增加了一个指针指向已存在的内存地址
11281-
11282-
深拷贝(deepCopy)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新的内存
11283-
11284-
* 使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误
11285-
11286-
浅复制:仅仅是指向被复制的内存地址,如果原地址发生改变,那么浅复制出来的对象也会相应的改变
11287-
11288-
深复制:在计算机中开辟一块新的内存地址用于存放复制的对象
11289-
11290-
11291-
1129211330

1129311331

1129411332

@@ -13396,6 +13434,149 @@ public class Candy11 {
1339613434

1339713435

1339813436

13437+
***
13438+
13439+
13440+
13441+
### 对象创建
13442+
13443+
#### 创建时机
13444+
13445+
一个Java对象的创建过程往往包括 **类初始化** 和 **类实例化** 两个阶段
13446+
13447+
Java对象创建时机:
13448+
13449+
1. 使用new关键字创建对象: 由执行类实例创建表达式而引起的对象创建
13450+
13451+
2. 使用Class类的newInstance方法 (反射机制)
13452+
13453+
3. 使用Constructor类的newInstance方法(反射机制)
13454+
13455+
```java
13456+
public class Student {
13457+
private int id;
13458+
public Student(Integer id) {
13459+
this.id = id;
13460+
}
13461+
public static void main(String[] args) throws Exception {
13462+
Constructor<Student> c = Student.class.getConstructor(Integer.class);
13463+
Student stu = c.newInstance(123);
13464+
}
13465+
}
13466+
```
13467+
13468+
使用newInstance方法的这两种方式创建对象使用的就是Java的反射机制,事实上Class的newInstance方法内部调用的也是Constructor的newInstance方法
13469+
13470+
4. 使用Clone方法创建对象:用clone方法创建对象的过程中并不会调用任何构造函数,要想使用clone方法,我们就必须先实现Cloneable接口并实现其定义的clone方法
13471+
5. 使用(反)序列化机制创建对象:当我们反序列化一个对象时,JVM会给我们创建一个单独的对象,在此过程中,JVM并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口
13472+
13473+
从Java虚拟机层面看,除了使用new关键字创建对象的方式外,其他方式全部都是通过转变为invokevirtual指令直接创建对象的
13474+
13475+
13476+
13477+
***
13478+
13479+
13480+
13481+
#### 创建过程
13482+
13483+
当一个对象被创建时,虚拟机就会为其分配内存来存放对象的实例变量及其从父类继承过来的实例变量(即使这些从超类继承过来的实例变量有可能被隐藏也会被分配空间),同时这些实例变量也会被赋予默认值(零值),然后开始进行对象初始化: **实例变量初始化**、**实例代码块初始化** 、 **构造函数初始化**
13484+
13485+
1. 实例变量初始化与实例代码块初始化
13486+
13487+
对实例变量直接赋值或者使用实例代码块赋值,编译器会将其中的代码放到类的构造函数中去,并且这些代码会被放在对超类构造函数的调用语句之后 (Java要求构造函数的第一条语句必须是超类构造函数的调用语句),构造函数本身的代码之前
13488+
13489+
2. 构造函数初始化
13490+
13491+
**Java要求在实例化类之前,必须先实例化其超类,以保证所创建实例的完整性**,在准备实例化一个类的对象前,首先准备实例化该类的父类,如果该类的父类还有父类,那么准备实例化该类的父类的父类,依次递归直到递归到Object类。然后从Object类依次对以下各类进行实例化,初始化父类中的变量和执行构造函数
13492+
13493+
13494+
13495+
***
13496+
13497+
13498+
13499+
#### 承上启下
13500+
13501+
1. 一个实例变量在对象初始化的过程中会被赋值几次?
13502+
13503+
JVM在为一个对象分配完内存之后,会给每一个实例变量赋予默认值,这个实例变量被第一次赋值
13504+
在声明实例变量的同时对其进行了赋值操作,那么这个实例变量就被第二次赋值
13505+
在实例代码块中又对变量做了初始化操作,那么这个实例变量就被第三次赋值
13506+
在构造函数中也对变量做了初始化操作,那么这个实例变量就被第四次赋值
13507+
在Java的对象初始化过程中,一个实例变量最多可以被初始化4次
13508+
13509+
2. 类的初始化过程与类的实例化过程的异同?
13510+
13511+
类的初始化是指类加载过程中的初始化阶段对类变量按照代码进行赋值的过程;
13512+
而类的实例化是指在类完全加载到内存中后创建对象的过程
13513+
13514+
3. 假如一个类还未加载到内存中,那么在创建一个该类的实例时,具体过程是怎样的?(**经典案例**)
13515+
13516+
```java
13517+
public class StaticTest {
13518+
public static void main(String[] args) {
13519+
staticFunction();//调用静态方法,触发初始化
13520+
}
13521+
13522+
static StaticTest st = new StaticTest();
13523+
13524+
static { //静态代码块
13525+
System.out.println("1");
13526+
}
13527+
13528+
{ // 实例代码块
13529+
System.out.println("2");
13530+
}
13531+
13532+
StaticTest() { // 实例构造器
13533+
System.out.println("3");
13534+
System.out.println("a=" + a + ",b=" + b);
13535+
}
13536+
13537+
public static void staticFunction() { // 静态方法
13538+
System.out.println("4");
13539+
}
13540+
13541+
int a = 110; // 实例变量
13542+
static int b = 112; // 静态变量
13543+
}/* Output:
13544+
2
13545+
3
13546+
a=110,b=0
13547+
1
13548+
4
13549+
*///:~
13550+
```
13551+
13552+
`static StaticTest st = new StaticTest();`:
13553+
13554+
* 实例初始化化不一定要在类初始化结束之后才开始
13555+
13556+
* 在同一个类加载器下,一个类型只会被初始化一次。所以一旦开始初始化一个类,无论是否完成后续都不会再重新触发该类型的初始化阶段了(只考虑在同一个类加载器下的情形)。因此,在实例化上述程序中的st变量时,**实际上是把实例初始化嵌入到了静态初始化流程中,并且在上面的程序中,嵌入到了静态初始化的起始位置**,这就导致了实例初始化完全发生在静态初始化之前,这也是导致a为110 b为0的原因
13557+
13558+
代码等价于:
13559+
13560+
```java
13561+
public class StaticTest {
13562+
<clinit>(){
13563+
a = 110; // 实例变量
13564+
System.out.println("2"); // 实例代码块
13565+
System.out.println("3"); // 实例构造器中代码的执行
13566+
System.out.println("a=" + a + ",b=" + b); // 实例构造器中代码的执行
13567+
类变量st被初始化
13568+
System.out.println("1"); //静态代码块
13569+
类变量b被初始化为112
13570+
}
13571+
}
13572+
```
13573+
13574+
13575+
13576+
13577+
13578+
13579+
1339913580
***
1340013581

1340113582

@@ -13481,9 +13662,9 @@ public class Candy11 {
1348113662

1348213663
##### 准备
1348313664

13484-
类变量是被 static 修饰的变量,准备阶段为**静态变量分配内存并设置初始值**,使用的是方法区的内存:
13665+
准备阶段为**静态变量分配内存并设置初始值**,使用的是方法区的内存:
1348513666

13486-
* 类变量也叫静态变量,就是在变量前加了static 的变量
13667+
* 类变量也叫静态变量,就是是被 static 修饰的变量
1348713668
* 实例变量也叫对象变量,即没加static 的变量
1348813669

1348913670
实例变量不会在这阶段分配内存,它会在对象实例化时随着对象一起被分配在堆中。实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只进行一次,实例化可以进行多次
@@ -13548,11 +13729,11 @@ class D {
1354813729

1354913730
###### 概述
1355013731

13551-
初始化阶段才真正开始执行类中定义的 Java 程序代码,在准备阶段,类变量已经赋过一次系统要求的初始值;在初始化阶段,通过程序制定的计划去初始化类变量和其它资源
13732+
初始化阶段才真正开始执行类中定义的 Java 程序代码,在准备阶段,类变量已经赋过一次系统要求的初始值;在初始化阶段,通过程序制定的计划去初始化类变量和其它资源,执行<clinit>
1355213733

1355313734
在编译生成class文件时,编译器会产生两个方法加于class文件中,一个是类的初始化方法clinit, 另一个是实例的初始化方法init
1355413735

13555-
类构造器<clinit>()与实例构造器<init>()不同,它不需要程序员进行显式调用。在一个类的生命周期中,类构造器<clinit>()最多会被虚拟机调用一次,而实例构造器<init>()则会被虚拟机调用多次,只要程序员创建对象
13736+
类构造器<clinit>()与实例构造器<init>()不同,它不需要程序员进行显式调用。在一个类的生命周期中,类构造器<clinit>()最多会被虚拟机**调用一次**,而实例构造器<init>()则会被虚拟机调用多次,只要程序员创建对象
1355613737

1355713738

1355813739

@@ -13572,8 +13753,6 @@ class D {
1357213753
* 虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时始化一个类,只会有一个线程执行这个类的 <clinit>() 方法,其它线程都阻塞等待,直到活动线程执行 <clinit>() 方法完毕
1357313754
* 如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个线程阻塞,在实际过程中此种阻塞很隐蔽
1357413755

13575-
JVM 在对static变量内存分配的时候,对类的处理顺序是:类{}块—>类构造函数—>static 块—>再是方法块
13576-
1357713756
特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问
1357813757

1357913758
```java
@@ -13596,17 +13775,17 @@ public class Test {
1359613775

1359713776
init指的是实例构造器,主要作用是在类实例化过程中执行,执行内容包括成员变量初始化和代码块的执行
1359813777

13599-
初始化即调用 <init>()V ,虚拟机会保证这个类的构造方法的线程安全,先为实例变量分配内存空间,再执行赋默认值,然后根据源码中的顺序执行赋初值或代码块,没有成员变量初始化和代码块则不会执行
13778+
实例化即调用 <init>()V ,虚拟机会保证这个类的构造方法的线程安全,先为实例变量分配内存空间,再执行赋默认值,然后根据源码中的顺序执行赋初值或代码块,没有成员变量初始化和代码块则不会执行
1360013779

1360113780
创建对象的过程:**父类的类构造器<clinit>() -> 子类的类构造器<clinit>() -> 父类的成员变量和实例代码块 -> 父类的构造函数 -> 子类的成员变量和实例代码块 -> 子类的构造函数**
1360213781

1360313782

1360413783

1360513784

1360613785

13607-
###### 初始化时机
13786+
###### 时机
1360813787

13609-
类的初始化是懒惰的
13788+
类的初始化是懒惰的,初始化时机:
1361013789

1361113790
**主动引用**:虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随之发生):
1361213791

@@ -13626,7 +13805,7 @@ init指的是实例构造器,主要作用是在类实例化过程中执行,
1362613805
* 通过子类引用父类的静态字段,不会导致子类初始化,只会触发父类的初始化
1362713806
* 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法
1362813807
* 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
13629-
* 类对象.class 不会触发初始化,Class.forName会
13808+
* 补充:类对象.class 不会触发初始化,Class.forName会
1363013809

1363113810

1363213811

@@ -13650,8 +13829,6 @@ init指的是实例构造器,主要作用是在类实例化过程中执行,
1365013829

1365113830

1365213831

13653-
13654-
1365513832
****
1365613833

1365713834

0 commit comments

Comments
 (0)