Skip to content

Commit

Permalink
知识点更新
Browse files Browse the repository at this point in the history
  • Loading branch information
hollis.zhl committed Jun 20, 2021
1 parent cee1839 commit 215fa0e
Show file tree
Hide file tree
Showing 23 changed files with 349 additions and 376 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,4 @@ fabric.properties
# Editor-based Rest Client
.idea/httpRequests

.DS_Store
5 changes: 0 additions & 5 deletions docs/basics/java-basic/constructor.md

This file was deleted.

114 changes: 92 additions & 22 deletions docs/basics/java-basic/final-string.md
Original file line number Diff line number Diff line change
@@ -1,39 +1,109 @@
String在Java中特别常用而且我们经常要在代码中对字符串进行赋值和改变他的值但是为什么我们说字符串是不可变的呢

* * *
首先我们需要知道什么是不可变对象

## 定义一个字符串
不可变对象是在完全创建后其内部状态保持不变的对象这意味着一旦对象被赋值给变量我们既不能更新引用也不能通过任何方式改变内部状态

String s = "abcd";

可是有人会有疑惑String为什么不可变我的代码中经常改变String的值啊如下

![String-Immutability-1][1]
```
String s = "abcd";
s = s.concat("ef");

`s`中保存了string对象的引用下面的箭头可以理解为存储他的引用”。
```

## 使用变量来赋值变量

String s2 = s;

这样操作不就将原本的"abcd"的字符串改变成"abcdef"了么

![String-Immutability-2][2]
但是虽然字符串内容看上去从"abcd"变成了"abcdef"但是实际上我们得到的已经是一个新的字符串了

s2保存了相同的引用值因为他们代表同一个对象
![][1]

## 字符串连接
如上图在堆中重新创建了一个"abcdef"字符串"abcd"并不是同一个对象

s = s.concat("ef");

所以一旦一个string对象在内存()中被创建出来他就无法被修改而且String类的所有方法都没有改变字符串本身的值都是返回了一个新的对象

![string-immutability][3]
如果我们想要一个可秀改的字符串可以选择StringBuffer 或者 StringBuilder这两个代替String

`s`中保存的是一个重新创建出来的string对象的引用
### 为什么String要设计成不可变

## 总结
在知道了"String是不可变"的之后大家是不是一定都很疑惑为什么要把String设计成不可变的呢有什么好处呢

一旦一个string对象在内存()中被创建出来他就无法被修改特别要注意的是String类的所有方法都没有改变字符串本身的值都是返回了一个新的对象
这个问题困扰过很多人甚至有人直接问过Java的创始人James Gosling

如果你需要一个可修改的字符串应该使用StringBuffer 或者 StringBuilder否则会有大量时间浪费在垃圾回收上因为每次试图修改都有新的string对象被创建出来
在一次采访中James Gosling被问到什么时候应该使用不可变变量他给出的回答是:

[1]: http://www.programcreek.com/wp-content/uploads/2009/02/String-Immutability-1.jpeg
[2]: http://www.programcreek.com/wp-content/uploads/2009/02/String-Immutability-2.jpeg
[3]: http://www.programcreek.com/wp-content/uploads/2009/02/string-immutability-650x279.jpeg
> I would use an immutable whenever I can.

那么他给出这个答案背后的原因是什么呢是基于哪些思考的呢

其实主要是从缓存安全性线程安全和性能等角度触发的

Q缓存安全性线程安全和性能这有都是啥
A你别急听我一个一个给你讲就好了

#### 缓存

字符串是使用最广泛的数据结构大量的字符串的创建是非常耗费资源的所以Java提供了对字符串的缓存功能可以大大的节省堆空间

JVM中专门开辟了一部分空间来存储Java字符串那就是字符串池

通过字符串池两个内容相同的字符串变量可以从池中指向同一个字符串对象从而节省了关键的内存资源

```
String s = "abcd";
String s2 = s;
```


对于这个例子s和s2都表示"abcd"所以他们会指向字符串池中的同一个字符串对象

![][2]

但是之所以可以这么做主要是因为字符串的不变性试想一下如果字符串是可变的我们一旦修改了s的内容那必然导致s2的内容也被动的改变了这显然不是我们想看到的

#### 安全性

字符串在Java应用程序中广泛用于存储敏感信息如用户名密码连接url网络连接等JVM类加载器在加载类的时也广泛地使用它

因此保护String类对于提升整个应用程序的安全性至关重要

当我们在程序中传递一个字符串的时候如果这个字符串的内容是不可变的那么我们就可以相信这个字符串中的内容

但是如果是可变的那么这个字符串内容就可能随时都被修改那么这个字符串内容就完全可信了这样整个系统就没有安全性可言了

#### 线程安全

不可变会自动使字符串成为线程安全的因为当从多个线程访问它们时它们不会被更改

因此一般来说不可变对象可以在同时运行的多个线程之间共享它们也是线程安全的因为如果线程更改了值那么将在字符串池中创建一个新的字符串而不是修改相同的值因此字符串对于多线程来说是安全的

#### hashcode缓存

由于字符串对象被广泛地用作数据结构它们也被广泛地用于哈希实现如HashMapHashTableHashSet等在对这些散列实现进行操作时经常调用hashCode()方法

不可变性保证了字符串的值不会改变因此hashCode()方法在String类中被重写以方便缓存这样在第一次hashCode()调用期间计算和缓存散列并从那时起返回相同的值

在String类中有以下代码

```
private int hash;//this is used to cache hash code.
```


#### 性能

前面提到了的字符串池hashcode缓存等都是提升性能的提现

因为字符串不可变所以可以用字符串池缓存可以大大节省堆内存而且还可以提前对hashcode进行缓存更加高效

由于字符串是应用最广泛的数据结构提高字符串的性能对提高整个应用程序的总体性能有相当大的影响

### 总结

通过本文我们可以得出这样的结论字符串是不可变的因此它们的引用可以被视为普通变量可以在方法之间和线程之间传递它们而不必担心它所指向的实际字符串对象是否会改变

我们还了解了促使Java语言设计人员将该类设置为不可变类的其他原因主要考虑的是缓存安全性线程安全和性能等方面

[1]: https://www.hollischuang.com/wp-content/uploads/2021/03/16163108328434.jpg
[2]: https://www.hollischuang.com/wp-content/uploads/2021/03/16163114985563.jpg
67 changes: 0 additions & 67 deletions docs/basics/java-basic/inheritance-composition.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/basics/java-basic/replace-in-string.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ replaceAll和replaceFirst的区别主要是替换的内容不同,replaceAll是

//使用replaceFirst将第一个数字替换成H
System.out.println(string.replaceFirst("\\d","H"));//abcH23adb23456aa
//使用replaceFirst将所有数字替换成H
//使用replaceAll将所有数字替换成H
System.out.println(string.replaceAll("\\d","H"));//abcHHHadbHHHHHaa
7 changes: 0 additions & 7 deletions docs/basics/java-basic/scope.md

This file was deleted.

2 changes: 2 additions & 0 deletions docs/basics/java-basic/string-append.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Java中想要拼接字符串最简单的方式就是通过"+"连接两个字符串

有人把Java中使用+拼接字符串的功能理解为运算符重载其实并不是Java是不支持运算符重载的这其实只是Java提供的一个语法糖

>运算符重载在计算机程序设计中运算符重载英语operator overloading是多态的一种运算符重载就是对已有的运算符重新进行定义赋予其另一种功能以适应不同的数据类型
Expand Down
4 changes: 3 additions & 1 deletion docs/basics/java-basic/substring.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
String是Java中一个比较基础的类每一个开发人员都会经常接触到而且String也是面试中经常会考的知识点String有很多方法有些方法比较常用有些方法不太常用今天要介绍的substring就是一个比较常用的方法而且围绕substring也有很多面试题
String是Java中一个比较基础的类每一个开发人员都会经常接触到而且String也是面试中经常会考的知识点

String有很多方法有些方法比较常用有些方法不太常用今天要介绍的substring就是一个比较常用的方法而且围绕substring也有很多面试题

`substring(int beginIndex, int endIndex)`方法在不同版本的JDK中的实现是不同的了解他们的区别可以帮助你更好的使用他为简单起见后文中用`substring()`代表`substring(int beginIndex, int endIndex)`方法

Expand Down
2 changes: 0 additions & 2 deletions docs/basics/java-basic/switch-string.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
Java 7switch的参数可以是String类型了这对我们来说是一个很方便的改进到目前为止switch支持这样几种数据类型:`byte` `short` `int` `char` `String` 。但是作为一个程序员我们不仅要知道他有多么好用还要知道它是如何实现的switch对整型的支持是怎么实现的呢对字符型是怎么实现的呢String类型呢有一点Java开发经验的人这个时候都会猜测switch对String的支持是使用equals()方法和hashcode()方法那么到底是不是这两个方法呢接下来我们就看一下switch到底是如何实现的

<!--more-->

### switch对整型支持的实现

下面是一段很简单的Java代码定义一个int型变量a然后使用switch语句进行判断执行这段代码输出内容为5那么我们将下面这段代码反编译看看他到底是怎么实现的
Expand Down
29 changes: 0 additions & 29 deletions docs/basics/java-basic/variable.md

This file was deleted.

15 changes: 12 additions & 3 deletions docs/basics/object-oriented/characteristics.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@

我们说面向对象的开发范式其实是对现实世界的理解和抽象的方法那么具体如何将现实世界抽象成代码呢这就需要运用到面向对象的三大特性分别是封装性继承性和多态性

### 封装(Encapsulation)

Expand All @@ -8,7 +8,7 @@

#### 封装举例

如我们想要定义一个矩形先定义一个Rectangle类并其中通过封装的手段放入一些必备数据
如我们想要定义一个矩形先定义一个Rectangle类并其中通过封装的手段放入一些必备数据

/**
* 矩形
Expand Down Expand Up @@ -42,6 +42,8 @@
return this.length * this.width;
}
}

我们通过封装的方式"矩形"定义了"长度""宽度"这就完成了对现实世界中的"矩形"的抽象的第一步

### 继承(Inheritance)

Expand Down Expand Up @@ -69,6 +71,7 @@
}
}

现实世界中"正方形""矩形"的特例或者说正方形是通过矩形派生出来的这种派生关系在面向对象中可以用继承来表达

### 多态(Polymorphism)

Expand All @@ -78,4 +81,10 @@

最常见的多态就是将子类传入父类参数中运行时调用父类方法时通过传入的子类决定具体的内部结构或行为

关于多态的例子我们后面的章节中还会深入展开
关于多态的例子我们第二章中深入开展介绍

在介绍了面向对象的封装继承多态的三个基本特征之后我们基本掌握了对现实世界抽象的基本方法

莎士比亚说:"一千个读者眼里有一千个哈姆雷特",说到对现实世界的抽象虽然方法相同但是运用同样的方法最终得到的结果可能千差万别那么如何评价这个抽象的结果的好坏呢

这就要提到面喜爱那个对象的五大基本原则了有了五大原则我们参考他们来评价一个抽象的好坏
Loading

0 comments on commit 215fa0e

Please sign in to comment.