From 5c5c056de67f7c7788b64c1b903c408f3a46d904 Mon Sep 17 00:00:00 2001 From: blueli <34030887+blueblueblueblueblueblue@users.noreply.github.com> Date: Mon, 10 Sep 2018 15:45:26 +0800 Subject: [PATCH 1/7] =?UTF-8?q?Update=20Java=20=E5=9F=BA=E7=A1=80.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- notes/Java 基础.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notes/Java 基础.md b/notes/Java 基础.md index d8f17cef..2cd0c825 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -196,9 +196,9 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地 ## String Pool -字符串常量池(String Poll)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Poll 中。 +字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Pool 中。 -当一个字符串调用 intern() 方法时,如果 String Poll 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Poll 中字符串的引用;否则,就会在 String Poll 中添加一个新的字符串,并返回这个新字符串的引用。 +当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。 下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。 From e697abdc3d00a74e9b43ce1cb81cb9b4145202fb Mon Sep 17 00:00:00 2001 From: CyC2018 <1029579233@qq.com> Date: Mon, 10 Sep 2018 16:01:25 +0800 Subject: [PATCH 2/7] auto commit --- notes/Java 基础.md | 6 +-- notes/Java 容器.md | 6 +-- notes/Java 并发.md | 120 +++++++++------------------------------------ notes/设计模式.md | 4 +- 4 files changed, 31 insertions(+), 105 deletions(-) diff --git a/notes/Java 基础.md b/notes/Java 基础.md index d8f17cef..b3aed5d6 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -196,9 +196,9 @@ String 不可变性天生具备线程安全,可以在多个线程中安全地 ## String Pool -字符串常量池(String Poll)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Poll 中。 +字符串常量池(String Pool)保存着所有字符串字面量(literal strings),这些字面量在编译时期就确定。不仅如此,还可以使用 String 的 intern() 方法在运行过程中将字符串添加到 String Poll 中。 -当一个字符串调用 intern() 方法时,如果 String Poll 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Poll 中字符串的引用;否则,就会在 String Poll 中添加一个新的字符串,并返回这个新字符串的引用。 +当一个字符串调用 intern() 方法时,如果 String Pool 中已经存在一个字符串和该字符串值相等(使用 equals() 方法进行确定),那么就会返回 String Pool 中字符串的引用;否则,就会在 String Pool 中添加一个新的字符串,并返回这个新字符串的引用。 下面示例中,s1 和 s2 采用 new String() 的方式新建了两个不同字符串,而 s3 和 s4 是通过 s1.intern() 方法取得一个字符串引用。intern() 首先把 s1 引用的字符串放到 String Pool 中,然后返回这个字符串引用。因此 s3 和 s4 引用的是同一个字符串。 @@ -219,7 +219,7 @@ String s6 = "bbb"; System.out.println(s4 == s5); // true ``` -在 Java 7 之前,String Poll 被放在运行时常量池中,它属于永久代。而在 Java 7,String Poll 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 +在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 - [StackOverflow : What is String interning?](https://stackoverflow.com/questions/10578984/what-is-string-interning) - [深入解析 String#intern](https://tech.meituan.com/in_depth_understanding_string_intern.html) diff --git a/notes/Java 容器.md b/notes/Java 容器.md index fd1ee97e..84189928 100644 --- a/notes/Java 容器.md +++ b/notes/Java 容器.md @@ -13,7 +13,7 @@ * [HashMap](#hashmap) * [ConcurrentHashMap](#concurrenthashmap) * [LinkedHashMap](#linkedhashmap) - * [WeekHashMap](#weekhashmap) + * [WeakHashMap](#weakhashmap) * [附录](#附录) * [参考资料](#参考资料) @@ -974,7 +974,7 @@ void afterNodeAccess(Node e) { // move node to last ### afterNodeInsertion() -在 put 等操作之后执行,当 removeEldestEntry() 方法返回 ture 时会移除最晚的节点,也就是链表首部节点 first。 +在 put 等操作之后执行,当 removeEldestEntry() 方法返回 true 时会移除最晚的节点,也就是链表首部节点 first。 evict 只有在构建 Map 的时候才为 false,在这里为 true。 @@ -1034,7 +1034,7 @@ public static void main(String[] args) { [3, 1, 4] ``` -## WeekHashMap +## WeakHashMap ### 存储结构 diff --git a/notes/Java 并发.md b/notes/Java 并发.md index 03aaa9fd..add3982a 100644 --- a/notes/Java 并发.md +++ b/notes/Java 并发.md @@ -44,9 +44,10 @@ * [内存模型三大特性](#内存模型三大特性) * [先行发生原则](#先行发生原则) * [十一、线程安全](#十一线程安全) - * [线程安全定义](#线程安全定义) - * [线程安全分类](#线程安全分类) - * [线程安全的实现方法](#线程安全的实现方法) + * [不可变](#不可变) + * [互斥同步](#互斥同步) + * [非阻塞同步](#非阻塞同步) + * [无同步方案](#无同步方案) * [十二、锁优化](#十二锁优化) * [自旋锁](#自旋锁) * [锁消除](#锁消除) @@ -739,6 +740,7 @@ java.util.concurrent(J.U.C)大大提高了并发性能,AQS 被认为是 J. ```java public class CountdownLatchExample { + public static void main(String[] args) throws InterruptedException { final int totalThread = 10; CountDownLatch countDownLatch = new CountDownLatch(totalThread); @@ -787,6 +789,7 @@ public CyclicBarrier(int parties) { ```java public class CyclicBarrierExample { + public static void main(String[] args) { final int totalThread = 10; CyclicBarrier cyclicBarrier = new CyclicBarrier(totalThread); @@ -821,6 +824,7 @@ Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的 ```java public class SemaphoreExample { + public static void main(String[] args) { final int clientCount = 3; final int totalRequestCount = 10; @@ -865,6 +869,7 @@ FutureTask 可用于异步获取执行结果或取消执行任务的场景。当 ```java public class FutureTaskExample { + public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask futureTask = new FutureTask(new Callable() { @Override @@ -970,6 +975,7 @@ produce..produce..consume..consume..produce..consume..produce..consume..produce. ```java public class ForkJoinExample extends RecursiveTask { + private final int threshold = 5; private int first; private int last; @@ -1274,19 +1280,13 @@ Thread 对象的结束先行发生于 join() 方法返回。 # 十一、线程安全 -## 线程安全定义 +多个线程不管以何种方式访问某个类,并在在主调代码中不需要进行同步,都能表现正确的行为。 -一个类在可以被多个线程安全调用时就是线程安全的。 +线程安全有以下几种实现方式: -## 线程安全分类 +## 不可变 -线程安全不是一个非真即假的命题,可以将共享数据按照安全程度的强弱顺序分成以下五类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。 - -### 1. 不可变 - -不可变(Immutable)的对象一定是线程安全的,不需要再采取任何的线程安全保障措施。只要一个不可变的对象被正确地构建出来,永远也不会看到它在多个线程之中处于不一致的状态。 - -多线程环境下,应当尽量使对象成为不可变,来满足线程安全。 +不可变(Immutable)的对象一定是线程安全的,不需要再采取任何的线程安全保障措施。只要一个不可变的对象被正确地构建出来,永远也不会看到它在多个线程之中处于不一致的状态。多线程环境下,应当尽量使对象成为不可变,来满足线程安全。 不可变的类型: @@ -1321,99 +1321,23 @@ public V put(K key, V value) { } ``` -### 2. 绝对线程安全 - -不管运行时环境如何,调用者都不需要任何额外的同步措施。 - -### 3. 相对线程安全 - -相对线程安全需要保证对这个对象单独的操作是线程安全的,在调用的时候不需要做额外的保障措施。但是对于一些特定顺序的连续调用,就可能需要在调用端使用额外的同步手段来保证调用的正确性。 - -在 Java 语言中,大部分的线程安全类都属于这种类型,例如 Vector、HashTable、Collections 的 synchronizedCollection() 方法包装的集合等。 - -对于下面的代码,如果删除元素的线程删除了 Vector 的一个元素,而获取元素的线程试图访问一个已经被删除的元素,那么就会抛出 ArrayIndexOutOfBoundsException。 - -```Java -public class VectorUnsafeExample { - private static Vector vector = new Vector<>(); - - public static void main(String[] args) { - while (true) { - for (int i = 0; i < 100; i++) { - vector.add(i); - } - ExecutorService executorService = Executors.newCachedThreadPool(); - executorService.execute(() -> { - for (int i = 0; i < vector.size(); i++) { - vector.remove(i); - } - }); - executorService.execute(() -> { - for (int i = 0; i < vector.size(); i++) { - vector.get(i); - } - }); - executorService.shutdown(); - } - } -} -``` - -```html -Exception in thread "Thread-159738" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 3 - at java.util.Vector.remove(Vector.java:831) - at VectorUnsafeExample.lambda$main$0(VectorUnsafeExample.java:14) - at VectorUnsafeExample$$Lambda$1/713338599.run(Unknown Source) - at java.lang.Thread.run(Thread.java:745) -``` - - -如果要保证上面的代码能正确执行下去,就需要对删除元素和获取元素的代码进行同步。 - -```java -executorService.execute(() -> { - synchronized (vector) { - for (int i = 0; i < vector.size(); i++) { - vector.remove(i); - } - } -}); -executorService.execute(() -> { - synchronized (vector) { - for (int i = 0; i < vector.size(); i++) { - vector.get(i); - } - } -}); -``` - -### 4. 线程兼容 - -线程兼容是指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用,我们平常说一个类不是线程安全的,绝大多数时候指的是这一种情况。Java API 中大部分的类都是属于线程兼容的,如与前面的 Vector 和 HashTable 相对应的集合类 ArrayList 和 HashMap 等。 - -### 5. 线程对立 - -线程对立是指无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。由于 Java 语言天生就具备多线程特性,线程对立这种排斥多线程的代码是很少出现的,而且通常都是有害的,应当尽量避免。 - -## 线程安全的实现方法 - -### 1. 互斥同步 +## 互斥同步 synchronized 和 ReentrantLock。 -### 2. 非阻塞同步 +## 非阻塞同步 互斥同步最主要的问题就是线程阻塞和唤醒所带来的性能问题,因此这种同步也称为阻塞同步。 互斥同步属于一种悲观的并发策略,总是认为只要不去做正确的同步措施,那就肯定会出现问题。无论共享数据是否真的会出现竞争,它都要进行加锁(这里讨论的是概念模型,实际上虚拟机会优化掉很大一部分不必要的加锁)、用户态核心态转换、维护锁计数器和检查是否有被阻塞的线程需要唤醒等操作。 -**(一)CAS** +### 1. CAS 随着硬件指令集的发展,我们可以使用基于冲突检测的乐观并发策略:先进行操作,如果没有其它线程争用共享数据,那操作就成功了,否则采取补偿措施(不断地重试,直到成功为止)。这种乐观的并发策略的许多实现都不需要将线程阻塞,因此这种同步操作称为非阻塞同步。 乐观锁需要操作和冲突检测这两个步骤具备原子性,这里就不能再使用互斥同步来保证了,只能靠硬件来完成。硬件支持的原子性操作最典型的是:比较并交换(Compare-and-Swap,CAS)。CAS 指令需要有 3 个操作数,分别是内存地址 V、旧的预期值 A 和新值 B。当执行操作时,只有当 V 的值等于 A,才将 V 的值更新为 B。 -**(二)AtomicInteger** +### 2. AtomicInteger J.U.C 包里面的整数原子类 AtomicInteger 的方法调用了 Unsafe 类的 CAS 操作。 @@ -1450,17 +1374,17 @@ public final int getAndAddInt(Object var1, long var2, int var4) { } ``` -**(三)ABA** +### 3. ABA 如果一个变量初次读取的时候是 A 值,它的值被改成了 B,后来又被改回为 A,那 CAS 操作就会误认为它从来没有被改变过。 J.U.C 包提供了一个带有标记的原子引用类 AtomicStampedReference 来解决这个问题,它可以通过控制变量值的版本来保证 CAS 的正确性。大部分情况下 ABA 问题不会影响程序并发的正确性,如果需要解决 ABA 问题,改用传统的互斥同步可能会比原子类更高效。 -### 3. 无同步方案 +## 无同步方案 要保证线程安全,并不是一定就要进行同步。如果一个方法本来就不涉及共享数据,那它自然就无须任何同步措施去保证正确性。 -**(一)栈封闭** +### 1. 栈封闭 多个线程访问同一个方法的局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机栈中,属于线程私有的。 @@ -1491,7 +1415,7 @@ public static void main(String[] args) { 100 ``` -**(二)线程本地存储(Thread Local Storage)** +### 2. 线程本地存储(Thread Local Storage) 如果一段代码中所需要的数据必须与其他代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。如果能保证,我们就可以把共享数据的可见范围限制在同一个线程之内,这样,无须同步也能保证线程之间不出现数据争用的问题。 @@ -1597,7 +1521,7 @@ ThreadLocal 从理论上讲并不是用来解决多线程并发问题的,因 在一些场景 (尤其是使用线程池) 下,由于 ThreadLocal.ThreadLocalMap 的底层数据结构导致 ThreadLocal 有内存泄漏的情况,应该尽可能在每次使用 ThreadLocal 后手动调用 remove(),以避免出现 ThreadLocal 经典的内存泄漏甚至是造成自身业务混乱的风险。 -**(三)可重入代码(Reentrant Code)** +### 3. 可重入代码(Reentrant Code) 这种代码也叫做纯代码(Pure Code),可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。 diff --git a/notes/设计模式.md b/notes/设计模式.md index 536072c2..d44d0e33 100644 --- a/notes/设计模式.md +++ b/notes/设计模式.md @@ -2952,7 +2952,7 @@ Java 利用缓存来加速大量小对象的访问时间。 - 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。 - 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。 - 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。 -- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个持久化对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。 +- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。

@@ -3004,6 +3004,7 @@ public class HighResolutionImage implements Image { ```java public class ImageProxy implements Image { + private HighResolutionImage highResolutionImage; public ImageProxy(HighResolutionImage highResolutionImage) { @@ -3027,6 +3028,7 @@ public class ImageProxy implements Image { ```java public class ImageViewer { + public static void main(String[] args) throws Exception { String image = "http://image.jpg"; URL url = new URL(image); From 07dff03dd49c3fb7d60ad9946bad0a878d67bc01 Mon Sep 17 00:00:00 2001 From: Yongtao Zhang Date: Tue, 11 Sep 2018 14:32:05 +0800 Subject: [PATCH 3/7] =?UTF-8?q?Update=20=E6=95=B0=E6=8D=AE=E5=BA=93?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F=E5=8E=9F=E7=90=86.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 高性能 MYSQL 在事务隔离级别中还有一个概念为加锁读。 --- notes/数据库系统原理.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/notes/数据库系统原理.md b/notes/数据库系统原理.md index 1bcd2533..1ce774fa 100644 --- a/notes/数据库系统原理.md +++ b/notes/数据库系统原理.md @@ -294,12 +294,12 @@ SELECT ... FOR UPDATE; ---- -| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | -| :---: | :---: | :---:| :---: | -| 未提交读 | √ | √ | √ | -| 提交读 | × | √ | √ | -| 可重复读 | × | × | √ | -| 可串行化 | × | × | × | +| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | 加锁读 | +| :---: | :---: | :---:| :---: | :---: | +| 未提交读 | √ | √ | √ | × | +| 提交读 | × | √ | √ | × | +| 可重复读 | × | × | √ | × | +| 可串行化 | × | × | × | √ | # 五、多版本并发控制 From b88cef16b9032c0a8c98fa0cd01a55ac49307337 Mon Sep 17 00:00:00 2001 From: Yif_Corleone Date: Thu, 13 Sep 2018 18:08:37 +0800 Subject: [PATCH 4/7] =?UTF-8?q?Java=20=E5=9F=BA=E7=A1=80=20219=E8=A1=8C?= =?UTF-8?q?=E7=AC=94=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 应是 s5 == s6 --- notes/Java 基础.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/notes/Java 基础.md b/notes/Java 基础.md index 9da25851..22c94e60 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -216,7 +216,7 @@ System.out.println(s3 == s4); // true ```java String s5 = "bbb"; String s6 = "bbb"; -System.out.println(s4 == s5); // true +System.out.println(s5 == s6); // true ``` 在 Java 7 之前,String Pool 被放在运行时常量池中,它属于永久代。而在 Java 7,String Pool 被移到堆中。这是因为永久代的空间有限,在大量使用字符串的场景下会导致 OutOfMemoryError 错误。 From d167aaf9961f7346afe850a49f903c6be819d658 Mon Sep 17 00:00:00 2001 From: Yif_Corleone Date: Thu, 13 Sep 2018 18:24:14 +0800 Subject: [PATCH 5/7] =?UTF-8?q?String=20poll=E7=AC=94=E8=AF=AF=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=20227~272=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- notes/Java 基础.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notes/Java 基础.md b/notes/Java 基础.md index 9da25851..8dcbff2a 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -226,9 +226,9 @@ System.out.println(s4 == s5); // true ## new String("abc") -使用这种方式一共会创建两个字符串对象(前提是 String Poll 中还没有 "abc" 字符串对象)。 +使用这种方式一共会创建两个字符串对象(前提是 String Pool 中还没有 "abc" 字符串对象)。 -- "abc" 属于字符串字面量,因此编译时期会在 String Poll 中创建一个字符串对象,指向这个 "abc" 字符串字面量; +- "abc" 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 "abc" 字符串字面量; - 而使用 new 的方式会在堆中创建一个字符串对象。 创建一个测试类,其 main 方法中使用这种方式来创建字符串对象。 @@ -267,7 +267,7 @@ Constant pool: // ... ``` -在 Constant Poll 中,#19 存储这字符串字面量 "abc",#3 是 String Poll 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Poll 中的字符串对象作为 String 构造函数的参数。 +在 Constant Pool 中,#19 存储这字符串字面量 "abc",#3 是 String Pool 的字符串对象,它指向 #19 这个字符串字面量。在 main 方法中,0: 行使用 new #2 在堆中创建一个字符串对象,并且使用 ldc #3 将 String Pool 中的字符串对象作为 String 构造函数的参数。 以下是 String 构造函数的源码,可以看到,在将一个字符串对象作为另一个字符串对象的构造函数参数时,并不会完全复制 value 数组内容,而是都会指向同一个 value 数组。 From b751fc80239a6ef8991c2c8019937bee2a87883b Mon Sep 17 00:00:00 2001 From: Yif_Corleone Date: Fri, 14 Sep 2018 11:32:07 +0800 Subject: [PATCH 6/7] =?UTF-8?q?Java=20=E5=9F=BA=E7=A1=80=EF=BC=8C=E5=AF=B9?= =?UTF-8?q?final=E5=85=B3=E9=94=AE=E5=AD=97=E8=A1=A5=E5=85=85=E6=9E=84?= =?UTF-8?q?=E9=80=A0=E5=99=A8=E7=9B=B8=E5=85=B3=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- notes/Java 基础.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/notes/Java 基础.md b/notes/Java 基础.md index bc856d4d..c5aac14e 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -1042,6 +1042,10 @@ private 方法隐式地被指定为 final,如果在子类中定义的方法和 声明类不允许被继承。 +**4. 构造器** + +声明类不允许被`new`实例化,多用于`Singleton`模式中。如果该类有子类需要继承,若该类无其他构造器,则不允许被继承。 + ## static **1. 静态变量** From f2e8961f3706020cd17dd4e758851c7c9f4a312e Mon Sep 17 00:00:00 2001 From: Yif_Corleone Date: Fri, 14 Sep 2018 14:13:33 +0800 Subject: [PATCH 7/7] =?UTF-8?q?Java=20=E5=9F=BA=E7=A1=80=20=E2=80=9C?= =?UTF-8?q?=E9=9A=90=E5=BC=8F=E7=B1=BB=E5=9E=8B=E8=BD=AC=E6=8D=A2=E2=80=9D?= =?UTF-8?q?=E4=B8=AD++=E5=92=8C+=3D=E6=95=88=E6=9E=9C=E4=B8=80=E6=A0=B7?= =?UTF-8?q?=EF=BC=8C=E4=B9=9F=E4=BC=9A=E6=89=A7=E8=A1=8C=E9=9A=90=E5=BC=8F?= =?UTF-8?q?=E7=9A=84=E7=B1=BB=E5=9E=8B=E8=BD=AC=E6=8D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- notes/Java 基础.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/notes/Java 基础.md b/notes/Java 基础.md index c5aac14e..9992d1ec 100644 --- a/notes/Java 基础.md +++ b/notes/Java 基础.md @@ -368,10 +368,11 @@ short s1 = 1; // s1 = s1 + 1; ``` -但是使用 += 运算符可以执行隐式类型转换。 +但是使用 += 或 ++ 运算符可以执行隐式类型转换。 ```java s1 += 1; +// s1++; ``` 上面的语句相当于将 s1 + 1 的计算结果进行了向下转型: