());
655 | }
656 | ```
657 |
658 | 创建一个线程池,该线程池根据需要创建新线程,但是将在先前构造的线程可用时重用它们。 这些池通常将提高执行许多短暂的异步任务的程序的性能。 如果可用, execute将重用以前构造的线程。 如果没有可用的现有线程,则将创建一个新线程并将其添加到池中。 六十秒内未使用的线程将终止并从缓存中删除。 因此,保持空闲时间足够长的池不会消耗任何资源。 请注意,可以使用ThreadPoolExecutor构造函数创建具有相似属性但不同细节(例如,超时参数)的ThreadPoolExecutor 。
--------------------------------------------------------------------------------
/docs/notes/java/Java集合总结.md:
--------------------------------------------------------------------------------
1 | # 集合
2 |
3 | # ArrayList
4 |
5 | ArrayList 实现于 List、RandomAccess 接口。可以插入空数据,也支持随机访问。其中最重要的两个属性分别是: elementData 数组,以及 size 大小。 默认初始化容量为10,每次扩容会扩容1.5倍(新容量=旧容量+旧容量>>1)。有序、非线程安全的。
6 |
7 | **执行add(E)方法:**
8 |
9 | ```java
10 | /**
11 | * Appends the specified element to the end of this list.
12 | *
13 | * @param e element to be appended to this list
14 | * @return {@code true} (as specified by {@link Collection#add})
15 | */
16 | public boolean add(E e) {
17 | modCount++;
18 | add(e, elementData, size);
19 | return true;
20 | }
21 |
22 | /**
23 | * This helper method split out from add(E) to keep method
24 | * bytecode size under 35 (the -XX:MaxInlineSize default value),
25 | * which helps when add(E) is called in a C1-compiled loop.
26 | */
27 | private void add(E e, Object[] elementData, int s) {
28 | if (s == elementData.length)
29 | elementData = grow();
30 | elementData[s] = e;
31 | size = s + 1;
32 | }
33 | ```
34 |
35 | - 首先记录对该列表进行结构修改的次数
36 | - 然后执行添加元素,默认添加到末尾
37 | - 判断数组的容量是否满了,如果是就先进行扩容
38 | - 将元素添加到指定位置,修改size大小
39 |
40 | **执行add(index,e)方法,添加元素到指定位置:**
41 |
42 | ```java
43 | /**
44 | * Inserts the specified element at the specified position in this
45 | * list. Shifts the element currently at that position (if any) and
46 | * any subsequent elements to the right (adds one to their indices).
47 | *
48 | * @param index index at which the specified element is to be inserted
49 | * @param element element to be inserted
50 | * @throws IndexOutOfBoundsException {@inheritDoc}
51 | */
52 | public void add(int index, E element) {
53 | rangeCheckForAdd(index);
54 | modCount++;
55 | final int s;
56 | Object[] elementData;
57 | if ((s = size) == (elementData = this.elementData).length)
58 | elementData = grow();
59 | System.arraycopy(elementData, index,
60 | elementData, index + 1,
61 | s - index);
62 | elementData[index] = element;
63 | size = s + 1;
64 | }
65 | ```
66 |
67 | - check下标是否越界,并记录列表结构修改次数
68 | - 判断数组是否需要扩容
69 | - 通过System.arraycopy方法复制指定的元素向后移动
70 | - 将添加的元素赋值给指定的下标 ,修改size大小
71 |
72 | **扩容方法grow():**
73 |
74 | ```java
75 | private Object[] grow() {
76 | return grow(size + 1);
77 | }
78 | /**
79 | * Increases the capacity to ensure that it can hold at least the
80 | * number of elements specified by the minimum capacity argument.
81 | *
82 | * @param minCapacity the desired minimum capacity
83 | * @throws OutOfMemoryError if minCapacity is less than zero
84 | */
85 | private Object[] grow(int minCapacity) {
86 | return elementData = Arrays.copyOf(elementData,
87 | newCapacity(minCapacity));
88 | }
89 |
90 | /**
91 | * Returns a capacity at least as large as the given minimum capacity.
92 | * Returns the current capacity increased by 50% if that suffices.
93 | * Will not return a capacity greater than MAX_ARRAY_SIZE unless
94 | * the given minimum capacity is greater than MAX_ARRAY_SIZE.
95 | *
96 | * @param minCapacity the desired minimum capacity
97 | * @throws OutOfMemoryError if minCapacity is less than zero
98 | */
99 | private int newCapacity(int minCapacity) {
100 | // overflow-conscious code
101 | int oldCapacity = elementData.length;
102 | int newCapacity = oldCapacity + (oldCapacity >> 1);
103 | if (newCapacity - minCapacity <= 0) {
104 | if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
105 | return Math.max(DEFAULT_CAPACITY, minCapacity);
106 | if (minCapacity < 0) // overflow
107 | throw new OutOfMemoryError();
108 | return minCapacity;
109 | }
110 | return (newCapacity - MAX_ARRAY_SIZE <= 0)
111 | ? newCapacity
112 | : hugeCapacity(minCapacity);
113 | }
114 |
115 | private static int hugeCapacity(int minCapacity) {
116 | if (minCapacity < 0) // overflow
117 | throw new OutOfMemoryError();
118 | return (minCapacity > MAX_ARRAY_SIZE)
119 | ? Integer.MAX_VALUE
120 | : MAX_ARRAY_SIZE;
121 | }
122 | ```
123 |
124 | - 主要是通过旧的容量+旧的容量,然后右位移1位来计算新的容量
125 | - 通过容量创建一个新的数组,进行数组复制。
126 |
127 | # Vector
128 |
129 | 底层使用数组实现,扩容方式与List不同,如果初始化Vector时没有指定容量增量,那么会默认扩容2倍(新容量=旧容量+旧容量),如果指定了容量增量,那么扩容的容量就是(新容量=旧容量+容量增量),使用synchronized包装了类的方法,所以是线程安全的。并且有序。
130 |
131 | **执行add(E e)方法:**
132 |
133 | ```java
134 | /**
135 | * Appends the specified element to the end of this Vector.
136 | *
137 | * @param e element to be appended to this Vector
138 | * @return {@code true} (as specified by {@link Collection#add})
139 | * @since 1.2
140 | */
141 | public synchronized boolean add(E e) {
142 | modCount++;
143 | add(e, elementData, elementCount);
144 | return true;
145 | }
146 | /**
147 | * This helper method split out from add(E) to keep method
148 | * bytecode size under 35 (the -XX:MaxInlineSize default value),
149 | * which helps when add(E) is called in a C1-compiled loop.
150 | */
151 | private void add(E e, Object[] elementData, int s) {
152 | if (s == elementData.length)
153 | elementData = grow();
154 | elementData[s] = e;
155 | elementCount = s + 1;
156 | }
157 | ```
158 |
159 | - 判断是否需要扩容
160 | - 赋值元素到指定的下标
161 | - 修改容器大小
162 |
163 | # LinkedList
164 |
165 | 采用双向链表实现,get指定索引的值会先对链表的大小进行右位移1,来判断获取的索引值在链表的上半部分还是下半部分,如果是上半部分会从头部节点开始遍历查找,如果是下半部分会从尾部节点开始遍历查找,有序、非线程安全。
166 |
167 | **执行add(E e)方法:**
168 |
169 | ```java
170 | /**
171 | * Appends the specified element to the end of this list.
172 | *
173 | * This method is equivalent to {@link #addLast}.
174 | *
175 | * @param e element to be appended to this list
176 | * @return {@code true} (as specified by {@link Collection#add})
177 | */
178 | public boolean add(E e) {
179 | linkLast(e);
180 | return true;
181 | }
182 | /**
183 | * Links e as last element.
184 | */
185 | void linkLast(E e) {
186 | final Node l = last;
187 | final Node newNode = new Node<>(l, e, null);
188 | last = newNode;
189 | if (l == null)
190 | first = newNode;
191 | else
192 | l.next = newNode;
193 | size++;
194 | modCount++;
195 | }
196 | ```
197 |
198 | - 默认添加到链表的最后面,先取出lastNode的一个临时变量。
199 | - 将要添加的元素包装成一个newNode节点,将newNode节点的前置节点设置为当前链表的最后一个节点
200 | - 将newNode设置为新的last节点
201 | - 判断lastNode是否为空,如果为空,那么此时添加的是链表的第一个节点,那么直接设置firstNode等于newNode。如果不是就将newNode链接到lastNode后边
202 | - 修改改链表大小及结构修改次数
203 |
204 | **执行get(int index)方法:**
205 |
206 | ```java
207 | /**
208 | * Returns the element at the specified position in this list.
209 | *
210 | * @param index index of the element to return
211 | * @return the element at the specified position in this list
212 | * @throws IndexOutOfBoundsException {@inheritDoc}
213 | */
214 | public E get(int index) {
215 | checkElementIndex(index);
216 | return node(index).item;
217 | }
218 | /**
219 | * Returns the (non-null) Node at the specified element index.
220 | */
221 | Node node(int index) {
222 | // assert isElementIndex(index);
223 |
224 | if (index < (size >> 1)) {
225 | Node x = first;
226 | for (int i = 0; i < index; i++)
227 | x = x.next;
228 | return x;
229 | } else {
230 | Node x = last;
231 | for (int i = size - 1; i > index; i--)
232 | x = x.prev;
233 | return x;
234 | }
235 | }
236 | ```
237 |
238 | - 先判断要获取的索引是否超出链表大小
239 | - 通过将size左位移一位来判断index是在链表的上半部分还是下半部分
240 | - 如果在上半部分则通过头节点开始遍历查找
241 | - 如果在下半部分则通过尾节点开始遍历查找
242 |
243 | # HashSet
244 |
245 | 底层使用HashMap实现,所有添加到set中的元素最终都会添加到map的key中,value用一个final的object对象填充。无序不重复,非线程安全
246 |
247 | # TreeSet
248 |
249 | 底层使用NavigableMap实现,所有添加到set中的元素最终都会添加到map的key中,value用一个final的object对象填充。有序不重复,非线程安全
250 |
251 | # HashMap
252 |
253 | HashMap初始容量为16的Note数组,数组内的链表容量大于8时会自动转换为红黑树,只有当这个数大于2并值至少有8个才能满足树的假设,当这个值缩小到6的时候就会转换为链表。
254 |
255 | **在hashMap中get操作:**
256 |
257 | ```java
258 | public V get(Object key) {
259 | Node e;
260 | return (e = getNode(hash(key), key)) == null ? null : e.value;
261 | }
262 |
263 | /**
264 | * Implements Map.get and related methods.
265 | *
266 | * @param hash hash for key
267 | * @param key the key
268 | * @return the node, or null if none
269 | */
270 | final Node getNode(int hash, Object key) {
271 | Node[] tab; Node first, e; int n; K k;
272 | if ((tab = table) != null && (n = tab.length) > 0 &&
273 | (first = tab[(n - 1) & hash]) != null) {
274 | if (first.hash == hash && // always check first node
275 | ((k = first.key) == key || (key != null && key.equals(k))))
276 | return first;
277 | if ((e = first.next) != null) {
278 | if (first instanceof TreeNode)
279 | return ((TreeNode)first).getTreeNode(hash, key);
280 | do {
281 | if (e.hash == hash &&
282 | ((k = e.key) == key || (key != null && key.equals(k))))
283 | return e;
284 | } while ((e = e.next) != null);
285 | }
286 | }
287 | return null;
288 | }
289 | ```
290 |
291 | - 计算key的hash值,判断get的元素是不是firstNoe,如果直接返回
292 | - 如果不是firstNode,那么判断是否是树,如果是的话通过遍历树查找。
293 | - 否则遍历链表找到key相等的值。
294 |
295 | **在hashMap中put操作:**
296 |
297 | ```java
298 | public V put(K key, V value) {
299 | return putVal(hash(key), key, value, false, true);
300 | }
301 |
302 | /**
303 | * Implements Map.put and related methods.
304 | *
305 | * @param hash hash for key
306 | * @param key the key
307 | * @param value the value to put
308 | * @param onlyIfAbsent if true, don't change existing value
309 | * @param evict if false, the table is in creation mode.
310 | * @return previous value, or null if none
311 | */
312 | final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
313 | boolean evict) {
314 | Node[] tab; Node p; int n, i;
315 | if ((tab = table) == null || (n = tab.length) == 0)
316 | n = (tab = resize()).length;
317 | if ((p = tab[i = (n - 1) & hash]) == null)
318 | tab[i] = newNode(hash, key, value, null);
319 | else {
320 | Node e; K k;
321 | if (p.hash == hash &&
322 | ((k = p.key) == key || (key != null && key.equals(k))))
323 | e = p;
324 | else if (p instanceof TreeNode)
325 | e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);
326 | else {
327 | for (int binCount = 0; ; ++binCount) {
328 | if ((e = p.next) == null) {
329 | p.next = newNode(hash, key, value, null);
330 | if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
331 | treeifyBin(tab, hash);
332 | break;
333 | }
334 | if (e.hash == hash &&
335 | ((k = e.key) == key || (key != null && key.equals(k))))
336 | break;
337 | p = e;
338 | }
339 | }
340 | if (e != null) { // existing mapping for key
341 | V oldValue = e.value;
342 | if (!onlyIfAbsent || oldValue == null)
343 | e.value = value;
344 | afterNodeAccess(e);
345 | return oldValue;
346 | }
347 | }
348 | ++modCount;
349 | if (++size > threshold)
350 | resize();
351 | afterNodeInsertion(evict);
352 | return null;
353 | }
354 | ```
355 |
356 | - 计算key的hash值,算出元素在底层数组中的下标位置。如果下标位置为空,直接插入。
357 | - 通过下标位置定位到底层数组里的元素(也有可能是链表也有可能是树)。
358 | - 取到元素,判断放入元素的key是否==或equals当前位置的key,成立则替换value值,返回旧值。
359 | - 如果是树,循环树中的节点,判断放入元素的key是否==或equals节点的key,成立则替换树里的value,并返回旧值,不成立就添加到树里。
360 | - 否则就顺着元素的链表结构循环节点,判断放入元素的key是否==或equals节点的key,成立则替换链表里value,并返回旧值,找不到就添加到链表的最后。
361 | - 精简一下,判断放入HashMap中的元素要不要替换当前节点的元素,key满足以下两个条件即可替换:
362 | - **hash值相等。**
363 | - **==或equals的结果为true。**
364 |
365 | # LinkedHashMap
366 |
367 | 主体的实现都是借助于 HashMap 来完成的,只是对其中的 recordAccess(), addEntry(), createEntry() 进行了重写。
368 |
369 | 总的来说 `LinkedHashMap` 其实就是对 `HashMap` 进行了拓展,使用了双向链表来保证了顺序性。因为是继承与 `HashMap` 的,所以一些 `HashMap` 存在的问题 `LinkedHashMap` 也会存在,比如不支持并发等。
370 |
371 | `LinkedHashMap` 的排序方式有两种:
372 |
373 | - 根据写入顺序排序。
374 | - 根据访问顺序排序。
375 |
376 | # ConcurrentHashMap
377 |
378 | ## jdk1.7实现
379 |
380 | 
381 |
382 | 如图所示,是由 `Segment` 数组、`HashEntry` 数组组成,和 `HashMap` 一样,仍然是数组加链表组成。
383 |
384 | `ConcurrentHashMap` 采用了分段锁技术,其中 `Segment` 继承于 `ReentrantLock`。不会像 `HashTable` 那样不管是 `put` 还是 `get` 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 `CurrencyLevel` (Segment 数组数量)的线程并发。每当一个线程占用锁访问一个 `Segment` 时,不会影响到其他的 `Segment`。
385 |
386 | ### get方法
387 |
388 | `ConcurrentHashMap` 的 `get` 方法是非常高效的,因为整个过程都不需要加锁。
389 |
390 | 只需要将 `Key` 通过 `Hash` 之后定位到具体的 `Segment` ,再通过一次 `Hash` 定位到具体的元素上。由于 `HashEntry` 中的 `value` 属性是用 `volatile` 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值(**[volatile 相关知识点](https://github.com/crossoverJie/Java-Interview/blob/master/MD/Threadcore.md#%E5%8F%AF%E8%A7%81%E6%80%A7)**)。
391 |
392 | ### put 方法
393 |
394 | 内部利用HashEntry类存储数据。
395 |
396 | 虽然 HashEntry 中的 value 是用 `volatile` 关键词修饰的,但是并不能保证并发的原子性,所以 put 操作时仍然需要加锁处理。
397 |
398 | 首先也是通过 Key 的 Hash 定位到具体的 Segment,在 put 之前会进行一次扩容校验。这里比 HashMap 要好的一点是:HashMap 是插入元素之后再看是否需要扩容,有可能扩容之后后续就没有插入就浪费了本次扩容(扩容非常消耗性能)。
399 |
400 | 而 ConcurrentHashMap 不一样,它是在将数据插入之前检查是否需要扩容,之后再做插入操作。
401 |
402 | ## JDK1.8 实现
403 |
404 | 
405 |
406 | 1.8 中的 ConcurrentHashMap 数据结构和实现与 1.7 还是有着明显的差异。其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。也将 1.7 中存放数据的 HashEntry 改为 Node,但作用都是相同的。其中的 val和next 字段都用了 volatile 修饰,保证了可见性。
407 |
408 | ### put方法
409 |
410 | 
411 |
412 | - 根据 key 计算出 hashcode 。
413 | - 判断是否需要进行初始化。
414 | - `f` 即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
415 | - 如果当前位置的 `hashcode == MOVED == -1`,则需要进行扩容。
416 | - 如果都不满足,则利用 synchronized 锁写入数据。
417 | - 如果数量大于 `TREEIFY_THRESHOLD` 则要转换为红黑树
418 |
419 | ### get方法
420 |
421 | 
422 |
423 | - 根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
424 | - 如果是红黑树那就按照树的方式获取值。
425 | - 都不满足那就按照链表的方式遍历获取值。
--------------------------------------------------------------------------------
/docs/notes/java/【Java8系列】Lambda表达式.md:
--------------------------------------------------------------------------------
1 | #### 什么是Lambda?
2 |
3 | Lambda是一个匿名函数,我们可以把Lambda表达式理解为是一段可以传递的代码(将代码像参数一样进行传递,称为行为参数化)。Lambda允许把函数作为一个方法的参数(函数作为参数传递进方法中),要做到这一点就需要了解,什么是函数式接口,这里先不做介绍,等下一篇在讲解。
4 |
5 | 首先先看一下lambda长什么样? 正常写法:
6 |
7 | ```
8 | new Thread(new Runnable() {
9 | @Override
10 | public void run() {
11 | System.out.println("hello lambda");
12 | }
13 | }).start();
14 | ```
15 |
16 | lambda写法:
17 |
18 | ```
19 | new Thread(
20 | () -> System.out.println("hello lambda")
21 | ).start();
22 | ```
23 |
24 | 怎么样?是不是感觉很简洁,没错,这就是lambda的魅力,他可以让你写出来的代码更简单、更灵活。
25 |
26 | #### Lambda怎么写?
27 |
28 |  大家看一些上面的这个图,这就是lambda的语法,一个lambda分为三部分:参数列表、操作符、lambda体。以下是lambda表达式的重要特征:
29 |
30 | - `可选类型声明:` 不需要声明参数类型,编译器可以统一识别参数值。也就说(s) -> System.out.println(s)和 (String s) -> System.out.println(s)是一样的编译器会进行类型推断所以不需要添加参数类型。
31 |
32 | - `可选的参数圆括号:` 一个参数无需定义圆括号,但多个参数需要定义圆括号。例如:
33 |
34 | - s -> System.out.println(s) 一个参数不需要添加圆括号。
35 | - (x, y) -> Integer.compare(y, x) 两个参数添加了圆括号,否则编译器报错。
36 |
37 | - `可选的大括号:` 如果主体包含了一个语句,就不需要使用大括号。
38 |
39 | - s -> System.out.println(s) , 不需要大括号.
40 | - (s) -> { if (s.equals("s")){ System.out.println(s); } }; 需要大括号
41 |
42 | - `可选的返回关键字:` 如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
43 |
44 | Lambda体不加{ }就不用写return:
45 |
46 | ```
47 | Comparator com = (x, y) -> Integer.compare(y, x);
48 | ```
49 |
50 | Lambda体加上{ }就需要添加return:
51 |
52 | ```
53 | Comparator com = (x, y) -> {
54 | int compare = Integer.compare(y, x);
55 | return compare;
56 | };
57 | ```
58 |
59 | #### 类型推断
60 |
61 | 上面我们看到了一个lambda表达式应该怎么写,但lambda中有一个重要特征是 `可选参数类型声明`,就是说不用写参数的类型,那么为什么不用写呢?它是怎么知道的参数类型呢?这就涉及到类型推断了。
62 |
63 | **java8的泛型类型推断改进:**
64 |
65 | - 支持通过方法上下文推断泛型目标类型
66 | - 支持在方法调用链路中,泛型类型推断传递到最后一个方法
67 |
68 | ```
69 | List ps = ...
70 | Stream names = ps.stream().map(p -> p.getName());
71 | ```
72 |
73 | 在上面的代码中,ps的类型是List ``,所以ps.stream()的返回类型是Stream ``。map()方法接收一个类型为Function的函数式接口,这里T的类型即是Stream元素的类型,也就是Person,而R的类型未知。由于在重载解析之后lambda表达式的目标类型仍然未知,我们就需要推导R的类型:通过对lambda表达式lambda进行类型检查,我们发现lambda体返回String,因此R的类型是String,因而map()返回Stream ``。绝大多数情况下编译器都能解析出正确的类型,但如果碰到无法解析的情况,我们则需要:
74 |
75 | - 使用显式lambda表达式(为参数p提供显式类型)以提供额外的类型信息
76 | - 把lambda表达式转型为Function
77 | - 为泛型参数R提供一个实际类型。( map(p -> p.getName()))
78 |
79 | #### 方法引用
80 |
81 | 方法引用是用来直接访问类或者实例已经存在的方法或构造方法,提供了一种引用而不执行方法的方式。是一种更简洁更易懂的Lambda表达式,当Lambda表达式中只是执行一个方法调用时,直接使用方法引用的形式可读性更高一些。 方法引用使用 “ :: ” 操作符来表示,左边是类名或实例名,右边是方法名。 `(注意:方法引用::右边的方法名是不需要加()的,例:User::getName)`
82 |
83 | **方法引用的几种形式:**
84 |
85 | - 类 :: 静态方法
86 | - 类 :: 实例方法
87 | - 对象 :: 实例方法
88 |
89 | ```
90 | 例如:
91 | Consumer consumer = (s) -> System.out.println(s);
92 | 等同于:
93 | Consumer consumer = System.out::println;
94 |
95 | 例如:
96 | Function stringToInteger = (String s) -> Integer.parseInt(s);
97 | 等同于:
98 | Function stringToInteger = Integer::parseInt;
99 |
100 | 例如:
101 | BiPredicate, String> contains = (list, element) -> list.contains(element);
102 | 等同于:
103 | BiPredicate, String> contains = List::contains;
104 | ```
105 |
106 | **`注意:`**
107 |
108 | - Lambda体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保存一致
109 | - 若Lambda参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用ClassName::method
110 |
111 | #### 构造方法引用
112 |
113 | 语法格式:类名::new
114 |
115 | ```
116 | 例如:
117 | Supplier supplier = ()->new User();
118 |
119 | 等同于:
120 | Supplier supplier = User::new;
121 | ```
122 |
123 | **`注意:`** 需要调用的构造器方法与函数式接口中抽象方法的参数列表保持一致。
124 |
125 | #### Lambda是怎么实现的?
126 |
127 | 研究了半天Lambda怎么写,可是它的原理是什么?我们简单看个例子,看看真相到底是什么:
128 |
129 | ```
130 | public class StreamTest {
131 |
132 | public static void main(String[] args) {
133 | printString("hello lambda", (String s) -> System.out.println(s));
134 |
135 | }
136 |
137 | public static void printString(String s, Print print) {
138 | print.print(s);
139 | }
140 | }
141 |
142 | @FunctionalInterface
143 | interface Print {
144 | public void print(T t);
145 | }
146 | ```
147 |
148 | 上面的代码自定义了一个函数式接口,定义一个静态方法然后用这个函数式接口来接收参数。编写完这个类以后,我们到终端界面javac进行编译,然后用javap(javap是jdk自带的反解析工具。它的作用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。)进行解析,如下图:
149 |
150 | - 执行javap -p 命令 ( -p -private 显示所有类和成员)
151 |
152 | 看上图发现在编译Lambda表达式生成了一个 `lambda$main$0`静态方法,这个静态方法实现了Lambda表达式的逻辑,现在我们知道原来Lambda表达式被编译成了一个静态方法,那么这个静态方式是怎么调用的呢?我们继续进行
153 |
154 | - 执行javap -v -p 命令 ( -v -verbose 输出附加信息)
155 |
156 | ```
157 | public com.lxs.stream.StreamTest();
158 | descriptor: ()V
159 | flags: ACC_PUBLIC
160 | Code:
161 | stack=1, locals=1, args_size=1
162 | 0: aload_0
163 | 1: invokespecial #1 // Method java/lang/Object."":()V
164 | 4: return
165 | LineNumberTable:
166 | line 7: 0
167 |
168 | public static void main(java.lang.String[]);
169 | descriptor: ([Ljava/lang/String;)V
170 | flags: ACC_PUBLIC, ACC_STATIC
171 | Code:
172 | stack=2, locals=1, args_size=1
173 | 0: ldc #2 // String hello lambda
174 | 2: invokedynamic #3, 0 // InvokeDynamic #0:print:()Lcom/lxs/stream/Print;
175 | 7: invokestatic #4 // Method printString:(Ljava/lang/String;Lcom/lxs/stream/Print;)V
176 | 10: return
177 | LineNumberTable:
178 | line 10: 0
179 | line 12: 10
180 |
181 | public static void printString(java.lang.String, com.lxs.stream.Print);
182 | descriptor: (Ljava/lang/String;Lcom/lxs/stream/Print;)V
183 | flags: ACC_PUBLIC, ACC_STATIC
184 | Code:
185 | stack=2, locals=2, args_size=2
186 | 0: aload_1
187 | 1: aload_0
188 | 2: invokeinterface #5, 2 // InterfaceMethod com/lxs/stream/Print.print:(Ljava/lang/Object;)V
189 | 7: return
190 | LineNumberTable:
191 | line 15: 0
192 | line 16: 7
193 | Signature: #19 // (Ljava/lang/String;Lcom/lxs/stream/Print;)V
194 |
195 | private static void lambda$main$0(java.lang.String);
196 | descriptor: (Ljava/lang/String;)V
197 | flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
198 | Code:
199 | stack=2, locals=1, args_size=1
200 | 0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
201 | 3: aload_0
202 | 4: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
203 | 7: return
204 | LineNumberTable:
205 | line 10: 0
206 | }
207 | SourceFile: "StreamTest.java"
208 | InnerClasses:
209 | public static final #58= #57 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
210 | BootstrapMethods:
211 | 0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
212 | Method arguments:
213 | #28 (Ljava/lang/Object;)V
214 | #29 invokestatic com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
215 | #30 (Ljava/lang/String;)V
216 | ```
217 |
218 | 这里只贴出了一部分的字节码结构,由于常量池定义太长了,就没有粘贴。
219 |
220 | ```
221 | InnerClasses:
222 | public static final #58= #57 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
223 | BootstrapMethods:
224 | 0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
225 | Method arguments:
226 | #28 (Ljava/lang/Object;)V
227 | #29 invokestatic com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
228 | #30 (Ljava/lang/String;)V
229 | ```
230 |
231 | 通过这段字节码结构发现是要生成一个内部类,使用invokestatic调用了一个LambdaMetafactory.metafactory方法,并把 `lambda$main$0`作为参数传了进去,我们来看metafactory 的方法里的实现代码:
232 |
233 | ```
234 | public static CallSite metafactory(MethodHandles.Lookup caller,
235 | String invokedName,
236 | MethodType invokedType,
237 | MethodType samMethodType,
238 | MethodHandle implMethod,
239 | MethodType instantiatedMethodType)
240 | throws LambdaConversionException {
241 | AbstractValidatingLambdaMetafactory mf;
242 | mf = new InnerClassLambdaMetafactory(caller, invokedType,
243 | invokedName, samMethodType,
244 | implMethod, instantiatedMethodType,
245 | false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
246 | mf.validateMetafactoryArgs();
247 | return mf.buildCallSite();
248 | }
249 | ```
250 |
251 | 在buildCallSite的函数中,是函数spinInnerClass 构建了这个内部类。也就是生成了一个StreamTest$$Lambda$1.class这样的内部类,这个类是在运行的时候构建的,并不会保存在磁盘中。
252 |
253 | ```
254 | @Override
255 | CallSite buildCallSite() throws LambdaConversionException {
256 | final Class> innerClass = spinInnerClass();
257 | 以下省略。。。
258 | }
259 | ```
260 |
261 | 如果想看到这个构建的类,可以通过设置环境参数 System.setProperty("jdk.internal.lambda.dumpProxyClasses", " . "); 会在你指定的路径 . 当前运行路径上生成这个内部类。我们看下一下生成的类长什么样  从图中可以看出动态生成的内部类实现了我自定义的函数式接口,并且重写了函数式接口中的方法。
262 |
263 | 我们在javap -v -p StreamTest\$\$Lambda\$1.class看下:
264 |
265 | ```
266 | {
267 | private com.lxs.stream.StreamTest$$Lambda$1();
268 | descriptor: ()V
269 | flags: ACC_PRIVATE
270 | Code:
271 | stack=1, locals=1, args_size=1
272 | 0: aload_0
273 | 1: invokespecial #10 // Method java/lang/Object."":()V
274 | 4: return
275 |
276 | public void print(java.lang.Object);
277 | descriptor: (Ljava/lang/Object;)V
278 | flags: ACC_PUBLIC
279 | Code:
280 | stack=1, locals=2, args_size=2
281 | 0: aload_1
282 | 1: checkcast #15 // class java/lang/String
283 | 4: invokestatic #21 // Method com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
284 | 7: return
285 | RuntimeVisibleAnnotations:
286 | 0: #13()
287 | }
288 | ```
289 |
290 | 发现在重写的parint方法中使用invokestatic指令调用了lambda$main$0方法。
291 |
292 | **总结:** 这样实现了Lambda表达式,使用invokedynamic指令,运行时调用LambdaMetafactory.metafactory动态的生成内部类,实现了函数式接口,并在重写函数式接口中的方法,在方法内调用 `lambda$main$0`,内部类里的调用方法块并不是动态生成的,只是在原class里已经编译生成了一个静态的方法,内部类只需要调用该静态方法。
--------------------------------------------------------------------------------
/docs/notes/java/【Java8系列】NPE神器Optional.md:
--------------------------------------------------------------------------------
1 | #### Optional类入门
2 |
3 | Optional`` 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。你可以把Optional对象看成一种特殊的集合数据,它至多包含一个元素。
4 |
5 | **常用方法**:
6 |
7 | - Optional.of(T t) : 将指定值用 Optional 封装之后返回,如果该值为 null,则抛出一个 NullPointerException 异常。
8 | - Optional.empty() : 创建一个空的 Optional 实例。
9 | - Optional.ofNullable(T t) : 将指定值用 Optional 封装之后返回,如果该值为 null,则返回一个空的 Optional 对象。
10 | - get() : 如果该值存在,将该值用 Optional 封装返回,否则抛出一个 NoSuchElementException 异常。
11 | - orElse(T t) : 如果调用对象包含值,返回该值,否则返回t。
12 | - orElseGet(Supplier s) : 如果调用对象包含值,返回该值,否则返回 s 获取的值。
13 | - orElseThrow() :它会在对象为空的时候抛出异常。
14 | - map(Function f) : 如果值存在,就对该值执行提供的 mapping 函数调用。
15 | - flatMap(Function mapper) : 如果值存在,就对该值执行提供的mapping 函数调用,返回一个 Optional 类型的值,否则就返回一个空的 Optional 对象。
16 |
17 | `注意:Optional类的设计初衷仅仅是要支持能返回Optional对象的语法,并未考虑作为类的字段使用,也没有实现序列化接口,在领域模型中使用Optional,有可能引发程序故障。`
18 |
19 | #### 使用Optional实战
20 |
21 | 用Optional封装可能为null的值,我们在项目中很多时候都会遇到,掉一个方法然后返回一个null,最后需要不断的判空。比如获取Map中的不含指定键的值,它的get方法返回的就是一个null。
22 |
23 | ```java
24 | //例如:
25 | Object value = map.get("key");
26 |
27 | //使用Optional封装结果后可以这么写:
28 | Optional