集合
1.Collection
1.1集合知识回顾
集合的特点:提供一种存储空间可变的存储模型,存储的数据容量可随时发生改变
1.2集合类体系结构
Collection:单列
- List:可重复
- ArrayList
- LinkedList
- …
- Set:不可重复
- HashSet
- TreeSet
- …
Map:双列
- HashMap
- …
1.3Collection集合概述和使用
Collection集合概述
- 是单例集合的顶层接口,它表示一组对象,这些对象也称为Collection的元素
- JDK不提供此接口的任何直接实现,它提供更具体的子接口(如Set和List)实现
创建Collection集合的对象
- 多态的方式
- 具体的实现类ArrayList
1.4Collection集合常用方法
- boolean add(E e):添加元素,永远返回true。ps:Alt+7 打开一个窗口,能够看到类的所有信息
- boolean remove(Object o):从集合中移除指定的元素
- void clear():清空集合中的元素
- boolean contains(Object o):判断集合中是否存在指定的元素
- boolean isEmpty():判断集合是否为空
- int size():集合的长度,也是集合中元素的个数
1.5Collection集合的遍历
Iterator:迭代器,集合的专用遍历方式
- Iterator
iterator():返回此集合中元素的迭代器,通过集合的Iterator()方法得到 - 迭代器是通过集合的iterator方法得到的,所以我们说它是依赖于集合而存在的
Iterator中的常用方法:
- E next():返回迭代中的下一个元素
- boolean hasNext():如果迭代具有更多元素,则返回true
1.6集合的使用步骤
步骤一:创建集合对象
步骤二:添加元素到集合
- 创建元素
- 添加元素到集合
步骤三:遍历集合
- 通过集合对象获取迭代器对象
- 通过迭代器对象的hasNext()方法判断是否还有元素
- 通过迭代器对象的next方法获取下一个
2.List
2.1List集合概述和特点
List集合概述
- 有序集合(也称为序列),用户可以精确控制列表中每个元素的插入位置,用户可以通过整数索引访问元素,斌搜索列表中的元素
- 与Set集合不同,列表通常允许重复的元素
List集合特点
- 有序:存储和取出的元素顺序一致
- 可重复:存储的元素可以重复
2.2List集合特有方法
void add(int index,E element):在此集合中的指定位置插入指定的元素
E remove(int index):删除指定索引处的元素,返回被删除的元素
E set(int index,E element):修改指定索引处的元素,返回被修改的元素
E get(int index):返回指定索引处的元素
List循环遍历:
1 | for(int i = 0; i < list.size(); i++) { |
案例:遍历学生信息
1 | package com.bilibili.Collection; |
2.3并发修改异常
并发修改异常:
- ConcurrentModificationException:当不允许这样的修改时,可以通过检测到对象的并发修改的方法来抛出此异常
产生原因:
- 迭代器遍历的过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致
解决方案:
- 用for循环遍历,然后用集合对象做对应的操作即可
2.4ListIterator
ListIterator:列表迭代器
- 通过List集合的ListIterator()方法得到,所以说它是List集合特有的迭代器
ListIterator中的常用方法:
- E next():返回迭代中的下一个元素
- boolean hasNext():如果迭代具有更多元素,则返回true
- E previous():返回列表中的上一个元素
- boolean hasPrevious():如果此列表迭代器在相反方向列表时具有更多元素,则返回true
- void add(E e):将指定的元素插入列表
ListIterator不会触发并发修改异常
2.5增强for循环
增强for:简化数组和Collection集合的遍历
- 实现Iterable接口的类允许其对象称为增强型for语句的目标
- 它是JDK5之后出现的,其内部原理是一个Iterator迭代器
- 内部原理就是一个Iterator迭代器
增强for的格式
- 格式
1 | for(元素数据类型 变量名: 数组或是Collection集合) { |
- 范例:
1 | int[] arr = {1,2,3,4,5}; |
2.6数据结构
常见数据结构值栈
数据进入栈模型的过程称为:压/进栈
数据离开栈模型的过程称为:弹/出栈
栈是一种数据先进后出的模型
常见数据结构之队列
- 数据从后端进入队列模型的过程称为:入队列
- 数据从前端离开队列模型的过程称为:出队列
常见数据结构之数组
数组时一种查询快,增删慢的模型
查询数据通过索引定位,查询任意数据耗时相同,查询效率高
删除数据时,要求将原始数据删除,同时后面每个数据迁移,删除效率低
添加数据时,添加位置后的每个数据后移,再添加元素,添加效率极低
常见数据结构之链表
- 链表是一种增删快的模型(对比数组)
- 链表是一种查询慢的模型(对比数组)
2.7 List集合子类特点
List集合常用子类:ArrayList,linkedList
- ArrayList:底层数据结构是数组,查询快,增删慢
- LinkedList:底层数据结构是链表,查询慢,增删快
LinkedList集合的特有功能
- public void addFirst(E e):在该列表开头插入指定元素
- public void addLast(E e):将指定的元素追加到此列表的末尾
- public E getFirst():返回此列表中的第一个元素
- public E getLast():返回此列表中的最后一个元素
- public E removeFirst():从此列表中删除并返回第一个元素
- public E removeLat():从此列表中删除并返回最后一个元素
3.1Set集合概述和特点
Set集合特点
- 不包含重复元素的集合
- 没有带索引的方法,所以不能使用普通的for循环遍历
Set集合案例:存储字符串并遍历
1 | package com.bilibili.Collection; |
3.2哈希值
哈希值:是JDK根据对象的地址或字符串或数字计算出来的int类型的数值
- ```java
public int hashCode()//返回对象的哈希码值,默认情况下,相同对象的哈希值是相同的,不同的对象的哈希值是不同的
//通过方法重写,可以实现不同对象的哈希值是相同的1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
* 对象的哈希值特点:
* 同一个对象多次调用hashCode()方法返回的哈希值是相同的
* 默认情况下,不同对象的哈希值是不同的。而重写hashCode()方法,可以实现让不同对象的哈希值相同
### 3.3HashSet集合概述和特点
#### Hash集合特点
* 底层数据结构是哈希表
* 对集合的迭代顺序不作任何保证,也就是说不保证存储和取出的元素顺序一致
* 没有带索引的方法,所以不能使用普通for循环遍历
* HashSet集合存储元素:要保证元素唯一性,需要重写hashCode()和equals()
### 3.4常见数据结构之哈希表
#### 哈希表
* JDK8之前,底层采用数组+链表实现,可以说是一个元素为链表的数组
* JDK8以后,在长度比较长的时候,底层实现了优化
### 3.5LinkedHashSet集合概述和特点
#### LinkedHashSet集合特点
* 哈希表和链表实现的Set接口,具有可预测的迭代次序
* 由链表保证元素有序,也就是说元素的存储和取出的顺序是一致的
* 由哈希表保证元素唯一,也就是说没有重复的元素
### 3.6TreeSet集合概述和特点
#### TreeSet集合特点
* 元素有序,这里的顺序不是指存储和取出的顺序,而是按照一定的规则进行排序,具体排序方法取决于构造方法
* TreeSet():根据其元素的自然排序进行排序
* TreeSet(Comparator comparator):根据指定的比较器进行排序
* 没有带索引的方法,所以不能使用普通的for循环遍历
* 由于是Set集合,所以不包含重复元素的集合
### 3.8自然排序comparable的使用
* 存储学生对象并遍历,创建TreeSet集合使用无参构造方法
* 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母进行顺序排序
* ```java
package com.bilibili.Collection;
import java.util.TreeSet;
public class TreeSetDemo2 {
public static void main(String[] args) {
// 创建集合对象
TreeSet<Student1> ts = new TreeSet<Student1>();
Student1 s1 = new Student1("xishi",29);
Student1 s2 = new Student1("wangzhaojun",28);
Student1 s3 = new Student1("diaochan",30);
Student1 s4 = new Student1("yangyuhaun",33);
Student1 s5 = new Student1("linqingxia",33);
Student1 s6 = new Student1("linqingxia",33);
ts.add(s1);
ts.add(s2);
ts.add(s3);
ts.add(s4);
ts.add(s5);
ts.add(s6);
for(Student1 s : ts) {
System.out.println(s.getName() + "," + s.getAge());
}
}
}
//Student1类
class Student1 implements Comparable<Student1>{
String name;
int age;
public Student1(String name, int age) {
this.name = name;
this.age = age;
}
public Student1() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student1 student1 = (Student1) o;
return age == student1.age && Objects.equals(name, student1.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
@Override
public int compareTo(Student1 s) {
// return 0;
// return 1;
// return -1;
// 按照年龄从小到大排序
// int num = this.age - s.age;
int num = s.age - this.age;
// 年龄相同时,按照姓名的字母进行顺序排序
int num2 = num == 0 ? this.name.compareTo(s.name):num;
return num2;
}
}
- ```java
结论
- 用TreeSet集合存储自定义对象,无参构造方法使用的是自然排序对元素进行排序的
- 自然排序,就是让元素所属的类实现comparable接口,重写comparaTo(T o)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
3.9比较器排序Comparator的使用
- 存储学生对象并遍历,创建TreeSet集合使用带参构造方法
- 要求:按照年龄从小到大排序,年龄相同时,按照姓名的字母顺序排序
结论
- 用TreeSet集合存储自定义对象,带参构造方法使用的是比较器排序对元素进行排序的
- 比较器排序,就是让集合构造方法接收Comparator的实现类对象,重写compare(T o1, T o2)方法
- 重写方法时,一定要注意排序规则必须按照要求的主要条件和次要条件来写
4.泛型
4.1泛型概述
泛型:是JDK5中引入的特性,它提供了编译时类型安全检测机制,该机制允许在编译时检测到非法的类型,它的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。
一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参,那么如何理解参数化类型?
顾名思义,就是将类型由原来具体的类型参数化,然后在调用/使用时传入具体的类型
这种参数类型可以用在类、方法和接口中,分别被称为泛型类、泛型方法、泛型接口
泛型的定义格式:
- <类型>:指定一种类型的格式。这里的类型可以看成是形参
- <类型1,类型2…>:指定多种类型的格式,多种类型之间用逗号隔开。这里的类型可以看成是形参
- 将来具体调用时给定的类型可以看成是实参,并且实参的类型只能是引用数据类型
泛型的好处:
- 把运行期间的问题提前到了编译期间
- 避免了强制类型转换
4.2泛型类
泛型类的定义格式:
- 格式:修饰符 class 类名<类型>{}
- 范例:public class Generic
{} - 此处T可以写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
4.3泛型方法
泛型方法的定义格式:
- 格式:修饰符 <类型> 返回值类型 方法名(类型 变量名){}
- 范例:public
void show(T t){}
4.4泛型接口
泛型接口的定义形式:
- 格式:修饰符 interface 接口名 <类型> {}
- 范例:public interface Generic
{}
4.5类型通配符
为了表示各种泛型List的父类,可以使用类型通配符
- 类型通配符:<?>
- List<?>:表示元素类型未知的List,它的元素可以匹配任何的类型
- 这种带通配符的List仅表示它是各种泛型List的父亲,并不能把元素添加到其中
如果说我们不希望List<?>是任何泛型List的父类,只希望它代表某一类泛型List的父类,可以使用类型通配符的上限
- 类型通配符上限:<? extends 类型>
- List<? extends Number>:它表示的类型是Number或者其子类型
除了可以指定类型通配符的上限,我们也可以指定类型通配符的下限
- 类型通配符下限:<? super 类型>
- List<? super Number>:它表示的类型是Number或者其父类型
4.6可变参数
可变参数又称为参数个数可变,用作方法的形参出现,那么方法参数个数就是可变的了
- 格式:修饰符 返回值类型 方法名(数据类型… 变量名){}
- 范例:public static int sum(int… a){}
可变参数注意事项
- 这里的变量其实是一个数组
- 如果一个方法有多个参数,包括可变参数,可变参数要放在最后
4.7可变参数的使用
Arrays工具类中有一个静态方法:
- public static
List asList(T… a):返回由指定数组支持的固定大小的列表
List接口中有一个静态方法:
- public static
List of(E… elements):返回包含任意元素的不可变列表
Set接口中有一个静态方法:
public static
Java图形界面开发
awt
awt编译乱码:-Dfile.encoding=gbk
去掉窗体边框:setUndecorated
5.Map
5.1Map集合概述和使用
Map集合概述
- Interface Map<K,V> K:键的类型;V:值的类型
- 将键映射到值的对象;不能包含重复的键;每个键可以映射到最多一个值
创建Map集合的对象
多态的方式
具体的实现类HashMap
```java
// 创建集合对象public static void main(String[] args) { Map<String,String> map = new HashMap<String,String>();
// V put(K key,V value)将指定的值与该映射中的指定键相关联
map.put("itheima001","林青霞"); map.put("itheima002","张曼玉"); map.put("itheima003","王祖贤"); System.out.println(map); }
}
//键是唯一的,键重复时,新值替代旧值1
2
3
4
5
6
7
8
9
10
11
### 5.2Map集合的基本功能
```java
V put(K key,V value)//添加元素
V remove(Object key)//根据键删除键值对元素
void clear()//移除所有的键值对元素
boolean containsKey(Object key)//判断集合是否包含指定的键
boolean containsValue(Object value)//判断集合是否包含指定的键
boolean isEmpty()//判断集合是否为空
int size()//集合的长度,也就是集合中键值对的个数5.3Map集合的获取功能
1
2
3
4
5
6
7V get(Object key)//根据键获取值
Set<K> keySet()//获取所有键的集合
Collection<V> values()//获取所有值的集合
Set<Map.Entry<K,V>>entrySet()//获取所有键值对对象的集合5.4Map集合的遍历(方式1)
遍历思路:
- 获取所有键的集合,用KeySet方式实现
- 遍历键的集合,获取到每一个键,用foreach实现
- 根据键去找值,用get(Object key)方法实现
5.5Map集合的遍历(方式2)
遍历思路:
- 获取所有键值对对象的集合
- Set<Map.Entry<K,V>>entrySet():获取所有键值对对象的集合
- 遍历键值对对象的集合,得到每一个键值对对象
- 用foreach实现,得到每一个Map.Entry
- 根据键值对对象获取键和值
- 用getKey()得到键
- 用getValue()得到值
案例:统计字符串中每个字符出现的次数
1 | public static void main(String[] args) { |
6.Collections
6.1Collections概述和使用
Collections类的概述
- 是针对集合操作的工具类
Collections类的常用方法
public static<T extends Comparable<? super T>> void sort(List
list):将指定的列表按升序排列 public static void reverse(List<?> list):反转指定列表中元素的顺序
public static void shuffle(List<?> list):使用默认的随机源排序指定的列表
```java
Listlist = new ArrayList (); //添加元素
list.add(30);
list.add(20);
list.add(10);
list.add(40);
list.add(50);Collections.sort(list);
Collections.reverse(list);
Collections.shuffle(list);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
*
## awt布局
BorderLayout
FlowLayout
GridLayout
GridBagLayout
CardLayout
BoxLayout(swing)
## awt中常用组件
Button 按钮
Canvas 画布
Checkbox 复选框
CheckboxGroup 将多个Checkbox 组合成一组,一组Checkbox中将只有一个会被选中,即全部变成单选框组件
Choice 下拉选框
Frame 窗口,在GUI程序里通过该类创建窗口
Label 标签类,用于放置提示性文本
List 列表框组件,可以添加多条项目
Panel 不能单独存在容器内,必须存在其他容器中
Scrollbar 滚动条组件。如果需要用户输入某个范围内的值,就可以使用滚动条组件,定义一个滚动条时,必须指定它的方向,初始值,滑块的大小,最小值和最大值。
ScrollPane 带水平及垂直滚动条的容器
TextArea 多行容器
TextField 单行文本框
### Dialog 对话框
Dialog(Frame owner,String title,boolean modal)
模式对话框 不可以对父类容器做操作
非模式对话框 可以对父类容器做操作
### FileDialog 文件对话框
FileDialog(Frame parent,String title,int mode) parent指父窗口 mode指文件对话类型。 例如FileDialog.LOAD,用于打开了文件 FileDialog.SAVE用于保存文件
记得设置fileDialog窗口可视化
String getDirectory()获取被打开或保存文件的绝对路径
String getFile() 获取被打开或者保存的文件名
## GUI常见事件和事件监听器
### 低级事件
ComponentEvent 组件事件
ContainerEvent 容器事件
WindowEvent 窗口事件
FocusEvent 焦点事件
KeyEvent 键盘事件
MouseEvent 鼠标事件
PaintEvent 组件绘制事件
### 高级事件
ActionEvent 动作事件,党按钮,菜单项被单击,在TextFiled中按Enter时触发
AjustmentEvent 调节事件,在滑动条上移动滑块调节数值时触发该事件
ItemEvent 选项事件,当用户选中某项,或取消选中某项时触发该事件
TextEvent 文本事件,当文本框、文本域中的文本发生改变时触发该事件
### 事件监听器
MouseMotionListener 鼠标移动
## 菜单组件
MenuBar 菜单条,菜单的容器
Menu 菜单组件,菜单项的容器,也是MenuItem的子类,所以可以作为菜单项使用
PopupMenu 上下文菜单组件(右键菜单组件)
MenuItem 菜单项组件
CheckboxMenuItem 复选框菜单组件
## 绘图
### 绘图组件
paint(Graphics g) 绘制组件的外观
update(Graphics g) 内部调用paint方法,刷新组件外观
repaint() 调用uodate方法,刷新组件外观
#### Graphics类
setColor(Color c) 设置颜色
setFont(Font font) 设置字体
drawLine() 绘制直线
drawRectIO 绘制矩形
drawRoundRect() 绘制圆角矩形
drawOyal() 绘制椭圆形
drawPolygon() 绘制多边形
drawArc() 绘制圆弧
drawPolyline() 绘制折线
fillRoundRect() 填充圆角矩形区域
fillOval() 填充椭圆区域
fillRect() 填充多边形区域
fillRect() 填充圆弧对应的扇形区域
drawImage() 绘制位图
#### BUfferedImage位图对象
位图使用步骤:
----------------
1.创建Image的子类对象BufferedImage(int width,int height,int ImageType),创建时需要指定位图的宽高以及类型属性;此时相当于在内存中生成了一张图片。
2.调用BufferedImage对象的getGraphics()方法获取画笔,此时就可以往内存中的图片上绘图了。
3.调用组件中paint方法提供的Graphics对象的drawImage()方法,一次性内存中的图片BufferedImage绘制到特定的组件上。
### ImageIO的使用
static BufferedImage read(File input) 读取本地磁盘图片文件
static BufferedImage read(InputStream input) 读取本地磁盘图片文件
static boolean write(RenderedImage im,String formatName,File output) 往本地磁盘中输出图片文件
----------
## Swing
概述:Swing由百分百纯java实现,不再依赖于本地平台的GUI,因此可以在所有平台上都保持相同的界面外观。独立于本地平台的Swing组件被称为轻量组件,依赖于本地平台的AWT被称为重量级组件。
特征:Swing组件采用MVC(Model-View-Controller,即模型-视图-控制器)设计模式
模型:用于维护组件的各种状态
视图:是组件的可视化表现
控制器:用于控制对于各种事件/组件做出响应。
----------
### Swing为组件设置边框
#### 常见Border
BevelBorder
LineBorder
EmptyBorder
EtchedBorder
TitledBorder
MatteBorder
CompoundBorder
#### 特殊的Border
TitleBorder:它的作用并不是直接为其他组件添加边框,而是为其他边框设置标题,创建该类对象时,需要传入一个其他的Border对象。
CompoundBorder:用来组合其他两个边框,创建该类对象时,需要传入其他两个Border对象,一个作为内边框,一个作为外边框。
#### 给组件设置边框步骤
1.使用BorderFactory或者XxxBorder创建Border的实例对象。
2.调用Swing组件的setBorder(Border b)方法为组件设置边框。
### 使用JtoolBar创建工具条
### JColorChooser和JFileChooser
#### JColorChooser
JColorChooser用于创建颜色选择器对话框,只需调用它的静态方法就可快速生成一个颜色选择对话框。
public static Color showDialog(Component component,String title,Color initialColor)
参数:component:指定当前对话框的父组件。
title:当前对话框的名称
initialColor:指定默认选中的颜色
返回值:返回用户选中的颜色
#### JFileChooser
##### 1.创建JFileChooser对象
JFileChooser chooser = new JFileChooser("D:\\"); //指定默认打开的本地磁盘路径
##### 2.调用JFileChoose的一系列可选方法,进行初始化
setSelectedFile(File file)/setSelectedFiles(File[] selectedFiles):设置默认选中的文件
setMultiSelectionEnabled(boolean b):设置是否允许多选,默认是单选
setFileSelectionMode(int mode):设置可以选择内容,例如文件、文件夹等,默认只能选择文件
##### 3.打开文件对话框
showOpenDialog(Component parent):打开文件加载对话框,并指定父组件
showSaveDialog(Component parent):打开文件保存对话框,并指定父组件
##### 4.获取用户选择的结果
File getSelectedFile():获取用户选择的一个文件
File[] getSelectedFiles():获取用户选择的多个文件
### JOptionPane
----------
基本描述:
通过JOptionPane可以非常方便地创建一些简单地对话框,Swing已经为这些对话框添加了相应的组件,无须手动添加组件。JOptionPane提供了如下4个方法来创建对话框。
#### showMessageDialog/showInternalMessageDialog
消息对话框,告知用户某事已发生,用户只能单击“确定”按钮,类似于JavaScript的alert函数。
#### showConfirmDialog/showInternalConfirmDialog
确认对话框,向用户确认某个问题,用户可以选择yes、no-cancel等选项。类似于JavaScript的confirm函数。该方法返回用户的单击了哪个按钮
#### showInputDialog/showInternalInputDialog
输入对话框,提示要求输入某些信息,类似于JavaScript的prompt函数。该方法返回用户输入的字符串。
#### showOptionDialog/showInternalOptionDialog
自定义选项对话框,允许使用自定义选项,可以取代showConfirmDialog所产生的对话框,只是用起来更复杂。
---------
#### 例子
以上方法都有很多重载形式,选择其中一种最全的形式,参数解释如下:
showXxxDialog(Component parentComponent,
Object message,
String title,
int optionType,
int messageType,
Icon icon,
Object[] options,
Object initialValue)
参数解释:
parentComponent:当前对话框的父组件
message:对话框上显示的信息,信息可以是字符串、组件、图片等
title:当前对话框的标题
optionType:当前对话框上显示的按钮类型:DEFAULT_OPTION、YES_NO_OPTION、YES_NO_CANCEL_OPTION、OK_CANCEL_OPTION
messageType:当前对话框的类型:ERROR_MESSAGE、INFORMATION_MESSAGE、WARING_MESSAGE
icon:当前对话框左上角的图标
options:自定义下拉列表的选项
initialValue:自定义选项中的默认选中项
----------
当用户与对话框交互结束后,不同类型对话框的返回值如下:
showMessageDialog:无返回值。
showInputDialog:返回用户输入或选中的字符串。
showConfirmDialog:返回一个整数代表用户的选项。
showOptionDialog:返回一个整数代表用户的选项,如果用户选第一项,则返回0;如果用户选则第二项,则返回1,以此类推。
----------
对showConfirmDialog所产生的对话框,有如下几个返回值
YES OPTION:用户单击了“是”按钮后返回。
NO OPTION:用户单击了“否”按钮后返回。
CANCEL OPTION:用户单击了“取消”按钮后返回。
OK OPTION:用户单击了“确定”按钮后返回。
CLOSED OPTION:用户单击了对话框右上角的“X”按钮后返回。
### Swing中的特殊容器
---------
#### 使用JSplitPane
通过如下构造方法可以创建JSplitPane对象
JSplitPane(int newOrientation,Component newLeftComponent,Component newRightCompnent)
newOrientation:指定JSplitPane容器的分割方向,如果值为JSplitPane.VERTICAL_SPLIT,为纵向分割,JSplitPane.HORIZONTAL_SPLIT,为横向分割
newLeftComponent:左侧或上侧的组件
newRughtComponent:右侧或下侧的组件
设置是否开启连续布局的支持(可选)
setContinuousLayout(boolean newContinuousLayout)
默认是关闭的,如果设置为true,则打开连续布局的支持,但由于连续布局支持需要不断地重绘,所以效率会低一些
设置是否支持“一触即展”地支持(可选)
setOneTouchExpandable(boolean new Value);
默认关闭
其他设置
setDividerLocation(double proportionalLocation):设置分割条地位置为JSplitPane的某个百分比
setDividerLocation(int location):通过像素值设置分割条的位置
setDividerSize(int newSize):通过像素值设置分割条大小
setLeftComponent(Component comp):设置指定位置的组件
#### JTabbedPane
1.创建JTabbedPane
JTabbedPane(int tabPlacement,int tableLayoutPolicy)
tabPlacement:指定标签的放置位置,可以选择SwingConstants中的四个常量:TOP、LEFT、BOTTOM、RIGHT
tableLayoutPolicy:指定当前窗口不能容纳标签页标题时的布局策略,可以选用JTabbedPane.WRAP_TAB_LAYOUT和JTabbedPane.SCROLL_TAB_LAYOUT。
2.通过JTabbedPane对象对标签进行增删改查
增加标签:
addTab(String title,Icon icon,Component component,String tip)
title:标签的名称
icon:标签的图标
component:标签对应的组件
tip:光标放到标签上的提示
插入标签页:
insertTab(String title,Icon icon,Component component,String tip,int index)
title:标签的名称
icon:标签的图标
component:标签对应的组件
tip:光标放到标签上的提示
index:在哪个索引处插入标签页
修改标签页对应的组件:
setComponentAt(int index,Component component)
index:在哪个索引处插入标签页
component:标签对应的组件
删除组件:
removeTabAt(int index)
index:删除哪个索引处的标签
3.设置当前选中的标签页
setSelectedIndex(int index)
4.设置JtabbedPane的其他属性
setDisabledIconAt(int index,Icon disabledIcon)
将指定位置的禁用图标设置为icon,该图标也可以是null表示不使用禁用图标。
setEnableAt(int index,boolean enabled)
设置指定位置的标签页是否启用
setTitleAt(int index,String title)
设置指定位置标签页的标题为title,该title可以是null,表示该标签页的标题为空
setToolTipTextAt(int index,String toolTipText)
设置指定位置标签页的提示文本
5.为JTabbedPane设置监听器
addChangeListener(changeListener listener)
#### JProgressBar、ProgressMonitor、BoundedRangeModel实现进度条
1.创建JProgress对象
public JProgressBar(int orient,int min,int max):
orient:方向
min:最小值
max:最大值
2.设置属性
setBorderPainted(boolean b):设置进度条是否有边框
setIndeterminate(boolean newValue):设置当前进度条是不是进度不确定的进度条,如果是,则将看到一个滑块在进度条中左右移动
setStringPainted(boolean b):设置进度条是否显示当前完成的百分比
3.获取和设置当前进度条的进度状态
setValue(int n):设置当前进度值
double getPercentComplete():获取进度条的完成百分比
String getString():返回进度字符串的当前值
创建进度对话框
ProgressMonitor的用法与ProgressBar的用法基本相似
public ProgressMonitor(Component parentComponent,Object message,String note,int min,int max)
parentComponent:对话框的父组件
message:对话框的描述信息
note:对话框的提示信息
min:进度条的最小值
max:进度条的最大值
#### JList、JComboBox实现列表框
JList和JComboBox极其相似,他们都只有一个列表框,只是JComboBox的列表框需要以下拉方式显示出来;JList和JComboBox都可以通过setRenderer()方法来改变列表项的表现形式。甚至维护这两个组件的Model都是相似的,JList使用ListModel,JComboBox使用JComboBoxModel,而ComboBoxModel是ListModel的子类。
1.简单列表框
使用JList或JComboBox实现简单列表框的步骤:
1.1创建JList或JComboBox对象
JList(final E[] listData):创建JList对象,把listData数组中的每项内容转换成一个列表项展示
JList(final Vector<? extends E> listData):创建JList对象,把listData数组中的每项内容转换成一个列表项展示
JComboBox(E[] items)
JComboBox(Vector<E> items)
1.2设置JList或JComboBox的外观行为
详见API
1.3设置监听器
JList通过addListSelectionListener完成,JComboBox通过addItemListener完成
##### 不强制储存列表项的ListModel和ComboBoxModel
JList和JComboBox只负责外观的展示,而组件底层的状态数据则由相对应的Model来维护
##### 强制存储列表项的DefaultListModel和DefaultComboBoxModel
DefaultListModel提供的方法:
add(int index, E element)
addElement(E obj)
insertElementAt(E obj,int index)
remove(int index)
removeAllElements()
removeElement(E element)
removeElementAt(int index)
removeRange(int fromIndex,int toIndex)
set(int index,E element)
setElementAt(E obj,int index)
##### 使用ListCellRenderer改变列表外观
实际上,JList和JComboBox还可以支持图标列表项,如果在创建JList或者JComboBox时传入图标数组,则创建的JList和JComboBox的列表项就是图标。
如果希望列表项是更复杂的组件,例如希望像QQ一样每个列表既有图标又有字符串,此时需要使用ListCellRenderer接口的实现类对象,自定义每个条目组件的渲染过程。
```java
public interface ListCellRenderer<E> {
Component getListCellRendererComponent(
JList<? extends E> list,//列表组件
E value,//当前列表项的内容
int index,//当前列表项的索引
boolean isSelected,//当前列表项是否被选中
boolean cellHasFocus);//当前列表项是否获取了焦点
}
通过JList的setCellRenderer(ListCellRenderer<? super E>cellRenderer)方法,把自定义的ListCellRenderer对象传递给JList,就可以按照自定义的规则来绘制列表项组件了。
绘制组件大小:setPreferredSize()
JTree、TreeModel实现树
树也是图形化用户界面中使用广泛的GUI组件,例如使用Windows资源管理器时,将看到如下图所示的目录树
按照结点是否包含子节点,可以把结点分为下面两类:
普通节点:包含子节点的结点;
叶子节点:没有子节点的结点;
按照结点是否具有唯一的父节点,可以把结点分为下面两类:
根节点:没有父节点的结点,计算机中,一棵树只能有一个根节点
普通节点:具有唯一父节点的结点
创建树
当一个结点具有子节点时,该节点有两种状态:
展开状态:当父节点处于展开状态时,其子节点是可见的;
折叠状态:当父节点属于折叠状态时,其子节点都是不可见的;
JTree常用构造方法
JTree(TreeModel newModel):使用指定的数据模型来创建JTree对象,它默认显示根节点。
JTree(TreeNode root):使用root作为根节点创建JTree对象,它默认显示根节点。
JTree(TreeNode root,boolean askAllowsChildren):使用root作为根节点创建JTree对象,它默认显示根节点,其他详见API。
TreeNode继承体系及使用:
接口:TreeNode->接口:MutableTreeNode->DefaultMutableTreeNode;
JTree的其他外观设置方法:
tree.putClientProperty(“JTree.lineStyle”,”None”):设置结点之间没有连接线
tree.putClientProperty(“JTree.lineStyle”,”Horizontal”):设置结点之间只有水平分割线
DefaultMutableTreeNode其他的成员方法
获取下一个兄弟结点
getNextSibling();
获取父节点
getParent();
判断是否为叶子结点
isLeaf();
判断是否为根节点
isRoot();
详见API
编辑树节点
JTree生成的树默认是不可编辑的,调用JTree的setEditable(boolean b)方法可使JTree对象变成可编辑状态
编辑树节点的步骤
1.获取被选中的结点
获取当前被选中的结点,会有两种方式:
一:
通过JTree对象的某些方法,例如:TreePath getSelectionPath()等,得到一个TreePath对象,包含了从根节点到当前结点路径上的所有结点
调用TreePath对象的 Object GetlastPathComponent() 方法,得到当前选中结点
二:
调用TreePath对象的 Object GetlastPathComponent() 方法,得到当前选中结点
2.调用DefaultModel数据模型有关增删改查的一系列方法完成编辑,方法执行后,重绘Tree
监听结点事件
为JTree添加监听器:
1.addTreeExpansionListener(TreeExpansionListener tel):添加树结点展开折叠事件的监听器
2.addTreeSelectionListener(TreeSelectionListener tsl):添加树节点选择事件的监听器
修改JTree的选择模式:
JTree专门提供了一个TreeSelectionModel对象来保存该JTree选中状态的信息。也就是说,JTree组件背后隐藏了两个model对象,其中TreeModel用于保存该JTree所有的节点数据,而TreeSelectionModel用于保存在JTree的所有选中状态的信息。
程序可以改变JTree的选择模式,但必须先获取该JTree对应的TreeSelectionModel对象,再调用该对象的setSelectionModel(int mode);方法来设置该JTree的选择模式,其中model可以有如下三种取值:
1.TreeSelectionModel.CONTIGUOUS_TREE_SELECTION:可以连续选中多个TreePath。
2.TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION:该选项对于选择没有任何限制。
3.TreeSelectionModel.SINGLE_TREE_SELECTION:每次只能选择一个TreePath。
使用DefaultTreeCellRenderer改变结点外观
JTree默认的外观是比较单一的,但它提供了如下几种改变结点外观的方式:
1.使用DefaultTreeCellRenderer直接改变结点的外观,这种方式可以改变整棵树所有结点的字体、颜色和图标。
2.为JTree指定DefaultTreeCellRenderer 的拓展类对象作为 JTree 的结点绘制器,该绘制器负责为不同结点使用不同的字体、颜色和图标。这种方法较为常用。
3.为JTree指定一个实现 TreeCellRenderder 接口的节点绘制器,改绘制器可以为不同节点自由绘制任意内容,这是最复杂但是最灵活的节点绘制器。
DefaultTreeCellRenderer 提供了如下几个方法来修改节点的外观:
详见
JTable、TableModel实现表格
表格也是GUI程序中常用的组件,表格是一个由多行、多列组成的二维显示区
创建简单表格
使用JTable创建简单表格步骤:
1.创建一个一维数组,存储表格中每一列的标题
2.创建一个二维数组,存储表格中每一行数据
3.根据第一步和第二部创建的一维数组和二维数组,创建JTable对象
4.把JTable添加到其他容器中显示
JTable调整列宽
JTable使用TableColumn来表示表格中的每一列,JTable中表格列的所有属性,如最佳宽度、是否可调整宽度、最小和最大宽度等都保存在该TableColumn中。
1.setMaxWidth(int maxWidth):设置该行最大列宽,如果指定的maxWidth小于该列的最小宽度,则maxWidth被设置为最小宽度。
2.setMinWidth(int minWidth):设置该列的最小宽度。
3.setPreferredWidth(int preferredWidth):设置该列的最佳宽度。
JTable调整表格选择模式:
与JList类似的是,JTable使用了一个ListSelectionModel表示该表格的选择状态,程序可以通过ListSelectionModel.setSelectionModel(int mode)控制JTable的选择模式。JTable的选择模式有如下三种:
1.ListSelectionModel.MULTIPLE_INTERVAL_SELECTION:没有任何限制
2.ListSelectionModel.SINGLE_INTERVAL_SELECTION:选择单个连续区域
1.ListSelectionModel.SINGLE_SELECTION:只能选择单行
TableModel
与JList、JTree类似的是,JTable采用了 TableModel 来保存表格中所有状态数据:与ListModel类似的是,TableModel也不强制保存该表格显示的数据。虽然在前面程序中看到的是直接利用一个二维数组来创建JTable对象,但也可以通过TableModel 对象来创建表格
自定义TableModel步骤:
1.自定义类,继承AbstractTableModel抽象类,重写下面几个方法:
1 | int getColumnCount()//返回表格列的数量 |
2.创建自定义类对象,根据该对象,创建JTable对象
DefaultTableModel
Swing本身为AbstractTableModel提供了一个DefaultTableModel实现类,程序可以通过使用DefaultTableModel实现类来创建JTable对象。通过DefaultTableModel对象创建JTable对象后,就可以调用它提供的方法来添加数据行、插入数据行、删除数据行和移动数据行。DefaultTableModel提供了如下几个方法来控制数据行操作:
1 | addColumn(Object columnName)/addColumn(Object columnName, Object[] columnData)//添加一列/添加一列并加入数据 |
Swing杀青
Java I/O流
File类
File(String pathName)
通过指定的路径名字符串转换为抽象路径名来创建新的File实例
File(String parent,String child)
从父路径名字符串和子路径名字符串创建新的File实例
File(File parent,String child)
从父抽象路径名和子路径名字符串创建新的File实例
File类中常用方法
File类创建功能
createNewFile():当具有该名称的文件不存在时,创建一个由该抽象路径名命名的新空文件
mkdir():闯将由此抽象路径命名的目录
mkdirs():创建由此抽象路径命名的目录,包括任何必须但不存在的父目录
File类判断和获取功能
isDirectory():测试此抽象路径名表示的File是否为目录
isFile():测试此抽象路径名表示的File是否为文件
exist():判断File对象指向的文件是否存在,返回一个boolean类型的值。
getAbsolutePath():返回此抽象路径名的绝对路径名字符串
getPath():将此抽象路径名转换为路径名字符串
getName():返回由此路径名表示的文件或目录的名称
list():返回此抽象路径名表示的目录中的文件和目录的名称字符串数组
listFiles():返回此抽象路径名表示的目录中的文件和目录的File对象数组
File类删除功能
delete():删除由此抽象路径名表示的文件或目录
字节流
IO流概述:
IO:输入输出(Input/Output)
流:是一种抽象概念,是对数据传输的总称。也就是说数据在设备间的传输称为流,流的本质是数据传输
IO流就是用来处理设备间传输问题的
常见应用:文件复制,文件上传,文件下载
IO分类
按照数据的流向
输入流:读数据
输出流:写数据
按照数据类型来分
字节流:字节输入/输出流
字符流:字符输入/输出流
用记事本打开字符流读得懂,字符流读不懂
字节流写数据
字节流抽象基类
InputStream:这个抽象类是表示字节输入流的所有类的超类
OutputStream:这个抽象类是表示字节输出流的所有超类
子类名特点:子类名称都是以其父类名作为子类名的后缀
FileOutputStream:文件输入流用于将数据写入File
FileOutputStream(String name):创建文件输出流以指定的名称写入文件
输入流方法:
void write(int b):将指定的字节写入此文件输出流
最后都要释放资源
void close():关闭此文件输出流并释放与此流相关联的任何系统资源
使用字节输出流写数据的步骤:
1.创建字节输出流对象(调用系统功能创建了文件,创建字节输出流对象,让字节输出流对象指向文件)
2.调用字节输出流对象的写数据方法
3.释放资源(关闭此文件输出流并释放与此流相关联的任何系统资源)
所有有关IO的操作必须关闭
字节流写数据的3种方式
void write(int b):将指定的字节写入此文件输出流,一次写一个字节数据
void write(byte[] b):将b.length字节从指定的字节数组写入此文件输出流,一次写一个字节数据
void write(byte[] b,int off,int len):将len字节从指定的字节数组开始,从偏移量off开始写入此文件输出流,一次写一个字节数组的部分数据
FileOutputStream fos = new FileOutputStream(“1.txt”) 等价于 FileOutputStream fos = new FileOutputStream(new File(“1.txt”))
byte[] getByte() 返回字符串对应的字节数组
字节流写数据的两个小问题
1.字节流写数据如何实现换行?
windows:\r\n
linux:\n
mac:\r
2.字节流写数据如何实现追加写入?
public FileOutputStream(String name,boolean append)
创建文件输出流以指定的名称写入文件
如果第二个参数为true,则字节将写入文件的末尾而不是开头
字节流写数据加异常处理
finally:在异常处理时提供finally块来执行所有清除操作,比如IO中的释放资源
try {
可能出现异常的代码
} catch 异常类名 变量名 {
异常的处理代码
} finally {
执行所有清除操作
}
JDK7改进方案:
try(定义流对象) {
可能出现异常的代码
} catch(异常类名 变量名) {
异常的处理代码
}
自动释放资源
JDK9改进方案:
定义输入流对象:
定义输出流对象:
try(输入流对象;输出流对象) {
可能出现异常的代码
} catch(异常类名 变量名) {
异常的处理代码
}
自动释放资源
字节流读数据
字节流读数据(一次读一个字节数据)
FIleInputStream:从文件系统中的文件获取输入字节
FileInputStream(String name):通过打开与实际文件的连接来创建一个FileInputStream,该文件由文件系统中的路径名name命名。
使用字节流读取数据的步骤:
1.创建字节输入流对象
2.调用字节输入流对象的读数据方法
3.释放资源
int read():从输入流读取一个字节的数据
数据的下一个字节,如果达到文件的末尾,值为-1
遍历文件:
1 | FileInputStream fis = new FileInputStream(File file); |
字节流案例:复制文本文件
分析:
1.其实就是把文本内容从一个文件中读取出来(数据源),然后写入到另一个文件中(目的地)
2.数据源:
1.txt—读数据—InputStream—FileInputStream
3.目的地:
D:\\1.txt—写数据—OutputStream—FileOutputStream
字节流读数据(一次一个字节数组数据)
int read(byte[] b):从该输入流读取最多b.length个字节的数据到一个字节数组
String (byte[] bytes,int offset,int length)
数据的下一个字节,如果达到文件的末尾,值为-1
字节流案例:复制图片
分析:
1.根据数据源创建字节输入流对象
2.根据目的地创建字节输出流对象
3.读写数据,复制图片(一次读取一个字节数组,一次写入一个字节数组)
4.释放资源
字节缓冲流
BufferedOutputStream:该类实现缓冲输出流。通过设置这样的输出流,应用程序可以向底层输出流写入字节,而不必为写入的每个字节导致底层系统的调用
BufferedInputStream:创建BufferedInputStream将创建一个内部缓冲数组。当从流中读取或跳过字节时,内部缓冲区将根据需要从所包含的输入流中重新填充,一次很多字节。
构造方法:
字节缓冲输出流:BufferedOutputStream(OutputStream out)
字节缓冲输入流:BufferedInputStream(InputStream in)
question:为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
answer:字节缓冲流仅仅提供缓冲区,而真正的读写数据还是得依靠基本得字节流对象进行操作
字符流
tips:为什么会出现字符流
一个汉字存储:
如果是GBK编码,占用两个字节
如果是UTF-8编码,占用三个字节
getBytes(charName name);
字符流 = 字节流 + 编码表
tips:如何识别中文?
汉字在存储的时候,无论选择哪种编码存储,第一个字节都是负数
编码表
基础知识:计算机中储存的信息都是二进制数表示的
按照某种规则,将字符存储到计算机中,称为编码。反之,将存储在计算机中的二进制数按照某种规则解析显示出来,称为解码。按A方式编码储存就必须按A方式编码解析
字符编码:就是一套自然语言的字符与二进制数之间的对应规则
字符集:
是一个系统所支持的所有字符的集合,包括各国文字、标点符号、图形符号、数字等
计算机要准确的存储和识别各种字符集符号,就需要进行字符编码,一套字符集必然至少有一套字符编码。常见的字符集有ASCII字符集、GBXXX字符集、Unicode字符集
字符串中的编码解码问题
编码:
byte[] getBytes():使用平台的默认字符集将该String编码为一系列字节,将结果存储到新的字节数组中
byte[] getBytes(String charsetName):使用指定的字符集将该String编码为一系列字节,将结果存储到新的字节数组中
解码:
String(byte[] bytes):通过使用平台的默认字符集解码指定的字节数组来构造新的String
String(byte[] bytes,String charsetName):通过指定的字符集解码指定的字节数组来构造新的String
字符流抽象基类
Reader:字符输入流的抽象类
Writer:字符输出流的抽象类
字符流中编码和解码相关的两个类:
InputSteamReader
OutputStreamWriter
OutputStreamWriter字符流写数据的五种方式
一次写一个字符
write(char c);
一次写一个数组
write(char[] chs)
一次写入一个数组的一部分
write(char[] chs,off,length)
一次写一个字符串
write(String str)
一次写一个字符串的一部分
write(String str,off,length)
字符流刷新方法
flush() 还可继续写数据
close() 关闭流,释放资源,但是在关闭前会先刷新流,一旦关闭就不能写数据
Tips:字符流相当于字节流有缓冲,主要还是通过FileOutputStream写数据,记得要用flush()刷新,close()在关闭前也会刷新。
InputStreamReader读数据的两种方式
int read() 一次读一个字符数据
int read(char[] chs) 一次读一个字符数组
FileReader和FileWriter
FileReader:用于读取字符文件的便携类
FileReader(String fileName)
FileWriter:用于写入字符文件的便携类
FileWriter(String fileName)
字符缓冲流BufferedReader和BufferedWriter
BufferedWriter:将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入,可以指定缓冲区大小,或者可以接受默认大小。默认值足够大,可用于大多数用途
BufferedReader:从字符输出流读取文本,缓冲字符,以提供字符,数组和行的高效读取,可以指定缓冲区大小,或者可以使用默认大小。默认值足够大,可用于大多数用途
构造方法
BufferWriter(Writer out)
BufferReader(Reader in)
字符缓冲流的特有功能
BufferedWriter:
void newLine():写一行分隔符,行分隔符字符串由系统属性定义
BufferedReader:
public String readLine():读一行文字,结果包含行的内容的字符串,不包括任何终止字符,如果流的结尾已经到达,则为null
IO流小结
案例
复制多级文件夹
需求:把“E:\itcast” 复制到D盘目录下
思路:
1.创建数据源File对象,路径是D:\\itcast
2.创建目的地File对象,路径是D:\\
3.写方法实现文件夹的复制,参数为数据源File对象和目的地File对象
4.判断数据源FIle是否是目录
是:
A:在目的地下创建和数据源File名称一样的目录
B:获取数据源File下所有文件或者目录的File数组
C:遍历该File数组,得到每一个File对象
D:把该File作为数据源File对象,递归调用复制文件夹的方法
不是:说明是文件,直接复制,用字节流
特殊操作流
对象序列化流
对象序列化:就是将对象保存到磁盘中,或者在网络中传输对象
这种机制就是使用一个字节序列表示一个对象,该字节序列包含:对象的类型、对象的数据和对象中存储的属性等信息。字节序列写道文件后,相当于文件中持久保存了一个对象的信息
要实现序列化和反序列化就要使用对象序列化流和对象反序列化流:
- 对象序列化流:ObjectOutputStream
- 将java对象的原始数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。可以通过使用流的文件来实现对对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象。
- 构造方法:ObjectOutputStream(OutputStream out):创建一个写入指定的OutputStream的ObjectOutputStream
- 序列化对象的方法:void writeObject(Object obj):将指定的对象写入ObjectOutputStream
- 注意:
- 一个对象想要被序列化,该对象所属的类必须实现Serializable接口
- Serializable是一个标记接口,实现该接口,不需要重写任何方法
- 对象反序列化流:ObjectInputStream
- ObjectInputStream反序列化前先使用ObjectOutputStream编写的原始数据和对象
- 构造方法:ObjectInputStream(InputStream in):创建从指定的InputStream读取的ObjectInputStream
- 反序列化的方法:Object readObject():从ObjectInputStream读取一个对象
序列化中存在的问题
用对象序列化流序列化了一个对象后,假如我们修改了对象所属的类文件,读取数据会不会出问题呢?
- 会出问题,抛出InvalidClassException异常
2.如果出问题了,如何解决呢?
- 给对象所属的类加一个serialVersionUID
- private static final serialVersionUID = 42L;
3.如果对象中某个成员不想被序列化,又该如何实现呢?
- 给该成员变量加transient关键字修饰,该关键字标记的成员变量不参与序列化过程
Properties
Properties概述:
- 是一个Map体系的集合类
- Properties可以保存到流中或从流中加载
Properties作为集合的特有方法:
Object setProperty(String key, String value) :设置集合的键和值,都是String类型,底层调用Hashtable方法put
String getProperty(String key):使用此属性列表中指定的键搜索属性
Set
stringPropertyNames():从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 ```java
public static void main(String[] args) {
// 创建集合对象
Properties prop = new Properties();
// 设置集合的键和值
prop.setProperty("itheima001","林青霞"); prop.setProperty("itheima002","张曼玉"); prop.setProperty("itheima003","王祖贤");
// 搜索属性
System.out.println(prop.get("itheima001")); Set<String> names = prop.stringPropertyNames(); for(String key : names) {
// System.out.println(key);
System.out.println(prop.get(key)); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
##### Properties和IO流集合的方法:
* void load(InputStream in):从输入字节流读取属性列表(键和元素对)
* void load(Reader reader):从输入字符流读取属性列表(键和元素对)
* void store(OutputStream out, String comments):将此属性列表(键和元素对)写入此Properties表中,以适合于使用load(InputStream)方法的格式写入输出字节流
* void store(Writer writer, String comments):将此属性列表(键和元素对)写入此Properties表中,以适合于使用load(Reader)方法的格式写入输出字符流
## 实现多线程
#### 1.1进程
##### 进程:是正在运行的程序
* 是系统进行资源分配和调用的独立单位
### 1.2线程
##### 线程:是进程中的单个顺序控制流,是一条执行路径
* 单线程:一个进程如果只有一条执行路径,则称为单线程程序
* 多线程:一个进程如果有多条执行路径,则称为多线程程序
##### 举例:
* 记事本案例:单线程程序
* 扫雷程序:多线程程序
### 1.3多线程的实现方式
##### 方式1:继承Thread类
* 定义一个类MyThread继承Thread类
* 在MyThread类中重写run()方法
* 创建MyThread类的对象
* 启动线程:start()方法
##### 方式2:实现Runnable接口
* 定义一个类MyRunnable实现Runnable接口
* 在MyRunnable类中重写run()方法
* 创建MyRunnable类的对象
* 创建Thread类的对象,把MyRunnable对象作为构造方法的参数
* ```java
MyRunnable my = new MyRunnable();//创建MyRunnable类的对象
Thread t1 = new Thread(my);//Thread(Runnable target)
Thread t2 = new Thread(my,"线程1");//Thread(Runnable target, String name)
启动线程
相比继承Thread类,实现Runnable接口的好处
- 避免了Java单继承的局限性
- 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想
两个小问题:
- 为什么要重写run()方法?
- 因为run()是用来封装被线程执行的代码
- run()方法和start()方法的区别?
- run():封装线程执行的代码,直接调用,相当于普通方法的调用
- start():启动线程,然后由JVM调用此线程的run()方法
1.4设置和获取线程名称
Thread类中设置和获取线程名称的方法
void setName(String name):将此线程的名称更改为等于参数name
String getName():返回此线程的名称
通过构造方法也可以设置线程名称
```java
public MyThread(String name) { super(name); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
##### 如何获取main()方法所在的线程名称?
* public static Thread currentThread():返回对当前正在执行的线程对象的引用
### 1.5线程调度
##### 线程有两种调度模型
* 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
* 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同。那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些
* Java使用的是抢占式调度模型
##### Thread类中设置和获取线程优先级的方法
* public final getPriority():返回此线程的优先级
* public final void setPriority(int newPriority):更改此线程的优先级
* 线程默认优先级为:5,最高优先级为:10,最低优先级为:1(线程优先级只是表示线程获取CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看见你想要的效果)
### 1.6线程控制
* static void sleep(long millis):使当前正在执行的程序停留(暂停执行)指定的毫秒数
* void join():等待这个线程死亡
* void setDaemon(boolean on):将此线程标记为守护线程,当运行的线程都是守护线程使,Java虚拟机将退出
### 1.7线程生命周期

### 卖票案例数据安全问题的解决
#### 为什么会出现问题?
* 是否是多线程环境
* 是否有共享数据
* 是否有多条语句操作共享数据
#### 如何解决多线程安全问题?
* 基本思想:让程序没有安全问题的环境
#### 怎么实现?
* 把多条语句操作共享数据的代码锁起来,让任意时刻只能有一个线程执行即可
### 1.8同步代码块
锁多条语句操作共享数据,可以使用同步代码块实现
* 格式:
* ```java
synchronized(任意对象) {
多条语句操作共享数据的代码
}synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
同步的好处和弊端
- 好处:解决了多线程的数据安全问题
- 弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
1.9同步方法
同步方法:就是把synchronized关键字加到方法上
- 格式:修饰符 synchronized 返回值类型 方法名(方法参数) {}
同步方法的锁对象是什么呢?
- this
同步静态方法:就是把synchronized关键字加到静态方法上
- 格式:修饰符 static synchronized 返回值类型 方法名(方法参数) {}
同步静态方法的锁对象是什么呢?
- 类名.class
1.10线程安全的类
StringBuffer
- 线程安全,可变的字符序列
- 从JDK5开始,被StringBuilder替代。通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步
Vector
- 从Java2平台v1.2开始,该类改进了List接口,使其成为Java Collections FrameWork的成员。与新的集合实现不同,Vector被同步。如果不需要线程安全的实现,建议使用ArrayList代替Vector
Hashtable
- 该类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或值
- 从Java平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections FrameWork的成员。与新的集合实现不同,Hashtable 被同步。如果不需要线程安全的实现,建议使用HashMap代替Hashtable
1.11Lock锁
虽然我们可以理解同步代码块和同步方法的锁对线问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何枷锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
- void lock():获得锁
- void unlock():释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock的构造方法
- ReentrantLock():创建一个ReentrantLock的实例
2.1生产者消费者模式概述
- 生产者线程用于生产数据
- 消费者线程用于消费数据
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
- 生产者生产数据后直接放置在共享区域中,并不关心消费者的行为
- 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
为了体现生产和消费过程中的等待和唤醒,Java提供了几个方法,这几个方法在Object类中
Object类的等待和唤醒方法:
void wait():导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法
Java网络编程
1.1网络编程概述
计算机网络
- 是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统
网络编程
- 在网络通信协议下,实现网络互连的不同计算机上运行的程序可以进行数据交换
1.2网络编程三要素
IP地址
- 想要让网络中的计算机能够互相通信,必须为每一台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而IP地址就是这个标识号。也就是设备的标识
端口
- 网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区别这些应用程序呢?如果说IP地址可以唯一标识网络中的设备,那么多口号就可以唯一标识设备中的应用程序了,也就是应用程序的标识
协议
- 通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式,传输效率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议
1.3IP地址
IP地址:是网络中设备的唯一标识
IP地址分为两大类
- IPv4:是给每个连接在网络上的主机分配一个32bit地址。按照TCP/IP规定,IP地址用二进制来表示,每个地址长32bit,也就是4个字节。例如一个采用二进制形式的IP地址是 “11000000 10101000 00000001 01000010 ”,这么长的地址,处理起来也费劲,为了方便使用,IP地址经常被写成十进制的形式,中间使用符号 “.” 分隔不同的字节。于是,上面的IP地址可以表示为 “192.168.1.66”。IP地址的这种表示法叫做 ”点分十进制表示法“,这显然比1和0容易记忆的多
- IPv6:由于互联网的蓬勃发展,IP地址的需求量愈来愈大,但是网络地址资源有限,使得IP分配越发紧张。为了扩大地址空间,通过IPv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组16进制数,这样就解决了网络地址资源数量不够的问题
常用命令:
- ipconfig:查看本机IP地址
- ping IP地址:检查网络是否连通
特殊IP地址:
- 127.0.0.1:是回送地址,可以代表本机地址,一般用来测试使用
1.4InetAddress的使用
为了方便我们对IP地址的获取和操作,Java提供了一个类InetAddress供我们使用
InetAddress:此类表示Internet协议(IP)地址
- static InetAddress getByName(String host):确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址
- String getHostName():获取此IP地址的主机名
- String getHostAddress():返回文本显示中的IP地址字符串
1.5端口
端口:设备上应用程序的唯一标识
端口号:
用两个字节表示的整数,它的取值范围是065535.其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败
1.6协议
协议:计算机网络中,连接和通信的规则被称为网络通信协议
UDP协议:
- 用户数据报协议(User Datagram Protocol)
- UDP是无连接通信协议,即在数据传输时,数据的发送和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。
- 由于UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输
- 例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接型,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议
TCP协议
- 传输控制协议(Transmission Control Protocol)
- TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收端建立逻辑连接,然后再传输数据,它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端向服务器发出连接请求,每次连接的创建都需要经过”三次握手“
- 三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器端的三次交互,以保证连接的可靠
- 第一次握手,客户端向服务器端发出连接请求,等待服务器确认
- 第二次握手,服务器端向客户端会送一个响应,通知客户端收到了请求
- 第三次握手,客户端再次向服务器发送确认信息,确认连接
- 完成三次握手,建立连接后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证数据的安全,所以应用十分广泛。例如上传文件,下载文件和浏览网页等
三次握手
1:服务器:准备就绪
2:客户端:连接请求
3:服务器:响应请求
2UDP通信程序
2.1UDP通信原理
UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象
因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念
2.2UDP发送数据
发送数据的步骤
1.创建发送端的Socket对象(DatagramSocket)
- DatagramSocket()
2.创建数据,并把数据打包
- ```java
DatagramPacket(byte[] buf, int length, InetAddress address, int port)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
* 3.调用DatagramSocket对象的方法发送数据
* void send(DatagramPacket p)
* 4.关闭发送端
* void close()
#### 2.3UDP接收数据
##### 接收数据的步骤
* 1.创建接收端的Socket对象(DatagramSocket)
* DatagramSocket(int port)
* 2.创建一个数据包,用于接收数据
* DatagramPacket(byte[] buf, int length)
* 3.调用DatagramSocket对象的方法接收数据
* void receive(DatagramPacket p)
* 4.解析数据包,并将数据在控制台显示
* byte[] getData()
* int getLength()
* 5.关闭接收端
* void close()
#### 2.4UDP通信程序联系
##### 按照下面的要求实现程序
* UDP发送数据:数据来自于键盘录入,直到输入的数是886,发送数据结束
* UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收
### 3.TCP通信程序
#### 3.1TCP通信原理
TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信
-------
Java对基于TCP协议的网络提供了良好的封装,使用Socket对象来代替两端的通信端口,并通过Socket产生IO流来进行网络通信
Java为客户端提供了Socket类,为服务器端提供了ServerSocket类
#### 3.2TCP发送数据
##### 发送数据的步骤
* 创建客户端的Socket对象(Socket)
* Socket(String host, int port)
* 获取输出流,写数据
* OutputStream getOutputStream()
* 释放资源
* void close()
#### 3.3TCP接收数据
##### 接收数据的步骤
* 1.创建服务器端的Socket对象(ServerSocket)
* ServerSocket(int port)
* 2.监听客户端连接,返回一个Socket对象
* Socket accept()
* 3.获取输入流,读数据,并把数据显示在控制台
* InputStream getInputStream()
* 4.释放资源
* void close()
##### 小Tips:程序一直等待用shutdownOutput()方法
## Lambda表达式
### 1.1函数式编程思想概述
在数学中,函数就是有输入量、输出量的一套计算方案,也就是“拿数据做操作”
面向对象思想强调“必须通过对象的形式来做事情”
函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
##### Lambda表达式就是函数式思想的体现
### 1.2体验Lambda表达式
##### 需求:启动一个线程,在控制台输出一句话:多线程程序启动了
* 方式1:
* 定义一个类MyRunnable实现Runnable接口,重写run()方法
* 创建MyRunnable类的对象
* 创建Thread类的对象,把MyRunnable的对象作为构造参数传递
* 启动线程
* 方式2:
* 匿名内部类
* 方式3:
* Lambda表达式:
* ```java
new Thread( () -> {
System.out.println("多线程程序启动了");
} ).start();
- ```java
1.3Lambda表达式的标准格式
匿名内部类中重写run()方法的代码分析
1 | new Thread(new Runnable() { |
- 方法形式参数为空,说明调用方法时不用传参
- 方法返回值类型为void,说明方法执行没有结果返回
- 方法体中的内容,是我们具体要做的事情
Lambda表达式的代码分析
1 | new Thread( () -> { |
- ():里面没有内容,可以看成是方法形式参数为空
- ->:用箭头指向后面要做的事情
- {}:包含一段代码,我们称之为代码块,可以看成是方法体中的内容
- 组成Lambda表达式的三要素:形式参数、箭头、代码块
Lambda表达式的格式
- 格式:(形式参数) -> {代码块}
- 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
- ->:由英文中画线和大于符号构成,固定写法。代表指向动作
- 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
1.4Lambda表达式的练习
Lambda表达式的使用前提
- 有一个接口
- 接口中有且仅有一个抽象方法
练习1
定义一个接口(Eatable),里面定义一个抽象方法:void eat();
定义一个测试类(EatableDemo),在测试类中提供两个方法
一个方法是:useEatable(Eatable e)
一个方法是主方法:在主方法中调用useEatable方法
```java
public static void main(String[] args) {Eatable e = new EatableImpl(); useEatable(e); // 匿名内部类 useEatable(new Eatable() { @Override public void eat() { System.out.println("一天两天是哪天"); } }); // Lambda表达式 useEatable( () -> { System.out.println("一天两天是哪天"); } );
}
private static void useEatable(Eatable e) {e.eat();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
##### 两个参数且有返回值的情况
```java
public static void main(String[] args) {
//在主方法中调用useAddable方法
useAddable((int x,int y) -> {
return x + y;
});
}
private static void useAddable(Addable a){
int sum = a.add(10,20);
System.out.println(sum);
}
1.5Lambda表达式的省略模式
省略规则:
- 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,甚至是return
1.6Lambda表达式的注意事项
注意事项:
- 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
- 必须有上下文环境,才能推导出Lambda对应的接口
- 根据局部变量的赋值得知Lambda对应的接口:Runnable r = () -> Sytem.out.println(“Lambda表达式”);
- 根据调用方法的参数得知Lambda对应的接口:new Thread(() -> System.out.println(“Lambda表达式”)).start();
1.7Lambda表达式和匿名内部类的区别
所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多余一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
接口组成更新
1.1接口组成更新概述
接口的组成
- 常量
- public static final
- 抽象方法
- public abstract
- 默认方法(Java 8)
- 静态方法(Java 8)
- 私有方法(Java 9)
1.2接口中的默认方法
接口中默认方法的定义格式:
- 格式:public default 返回值类型 方法名(参数列表) {}
- 范例:public default void show() {}
接口中默认方法的注意事项:
- 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
- public可以省略,default不能省略
1.3接口中的静态方法
接口中静态方法的定义格式:
- 格式:public static 返回值类型 方法名(参数列表) {}
- 范例:public static void show() {}
接口中静态方法的注意事项:
- 静态方法只能通过接口名调用,不能通过实现类名或对象名调用
- public可以省略,static不能省略
1.4接口中私有方法
接口中私有方法的定义格式:
- 格式1:private 返回值类型 方法名(参数列表) {}
- 范例1:private void show() {}
- 格式2:private static 返回值类型 方法名(参数列表) {}
- 范例2:private static void method() {}
接口中私有方法的注意事项:
- 默认方法可以调用私有的静态方法和非静态方法
- 静态方法只能调用私有的静态方法
方法引用
1.1体验方法引用
在使用Lambda表达式时,我们实际传进去的代码就是一种解决方案:拿参数做操作
那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要重写重复逻辑呢?
1 | // 方法引用符::: |
1.2方法引用符
方法引用符
- :: 该符号为引用运算符,而它所在的表达式被称为方法引用
回顾一下我们在体验方法引用中的代码
- Lambda表达式:usePrintable(s -> System.out.println(s));
- 分析:拿到参数s后通过Lambda表达式,传递给System.out.printlb方法去处理
- 方法引用:usePrintable(System.out::println);
- 分析:直接使用System.out中的println方法来取代Lambda,代码更加简洁
推导与省略
- 如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
- 如果使用方法引用,也是同样可以根据上下文进行推导
- 方法引用时Lambda的孪生兄弟
1.3Lambda表达式支持的方法引用
常见的引用方式:
- 引用类方法
- 引用对象的实例方法
- 引用类的实例方法
- 引用构造器
1.4引用类方法
引用方法,其实就是引用类的静态方法
- 格式:类名::静态方法
- 范例:Integer::parseInt
- Integer类的方法:public static int parseInt(String s)将此String类转换为int类型数据
- Lambda表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数
1.5引用对象的实例方法
引用对象的实例方法,其实就是引用类中的成员方法
- 对象::成员方法
- 范例:”HelloWorld”::toUpperCase
- String类中的方法:public String toUpperCase() 将此String所有字符转换为大写
- Lambda表达式被对象的实例方法替代的时候,它的形式参数全部传递给该方法作为参数
1.6引用类的实例方法
引用类的实例方法,其实就是引用类的成员方法
- 格式:类名::成员方法
- 范例:String::subString
- String类中的方法:public String subString(int beginIndex, int endIndex)从beginIndex开始到endIndex结束,截取字符串。返回一个字串,子串的长度为endIndex-beginIndex
- Lambda表达式被类的实例方法替代的时候,第一个参数作为调用者,后面的参数全部传递给该方法作为参数
1.7引用构造器
引用构造器,其实就是引用构造方法
- 格式:类名::new
- 范例:Student::new
- Lambda表达式被构造器替代时,它的形式参数全部传递给构造器作为参数
函数式接口
1.1函数式接口概述
函数式接口:有且仅有一个抽象方法的接口
Java中的函数式编程体现就是Lambda表达式,所以函数式接口就是可以用于Lambda使用的接口
只有确保接口中有且仅有一个抽象方法,Java中的Lambda才能顺利地进行推导
如何检测一个接口是不是函数式接口呢?
- @FunctionalInterface
- 放在接口定义的上方:如果接口是函数式接口,编译通过;如果不是,编译失败
注意:
- 我们自己定义函数式接口的时候,@FunctionalInterface是可选的,就算我不写这个注解,只要保证满足函数式接口定义的条件,也照样是函数式接口。但是,建议加上该注解
1.2函数式接口作为方法的参数
需求
定义一个类(RunnableDemo),在类中提供两个方法
一个方法是:startThread(Runnable r) 方法参数Runnable是一个函数式接口
一个是主方法,在主方法调用startThread方法
```java
public class RunnableDemo {public static void main(String[] args) { //在主方法调用startThread方法 //匿名内部类的方式 startThread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "线程启动了"); } }); //Lambda表达式 startThread(() -> System.out.println(Thread.currentThread().getName() + "线程启动了")); } private static void startThread(Runnable r) { //Thread t = new Thread(r); //t.start(); new Thread(r).start(); }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
### 1.3函数式接口作为方法的返回值
##### 需求
* 定义一个类(ComparatorDemo),在类中提供两个方法
* 一个方法是:Comparator<String> getComparator()方法返回值Comparator是一个函数式接口
* 一个方法是主方法,在主方法中调用getComparator方法
##### 实现
* ```java
public class ComparatorDemo {
public static void main(String[] args) {
//构造使用场景
//定义集合,存储字符串元素
ArrayList<String> array = new ArrayList<String> ();
array.add("cccc");
array.add("aa");
array.add("b");
array.add("ddd");
System.out.println("排序前:" + array );
//Collections.sort(array);
Collections.sort(array,getComparator());
System.out.println("排序后:" + array);
}
public static Comparator<String> getComparator() {
//匿名内部类实现
Comparator<String> comp = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.length() - s2.length();
}
};
return comp;
//Lambda表达式实现
return (String s1,String s2) -> {
return s1.length() -s2.length();
};
}
}
如果方法的返回值是一个函数式接口,我们可以使用Lambda表达式作为结果返回
```java
private ComparatorgetComparator() { return (s1,s2) -> s1.length() = s2.length();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
### 1.4常用的函数式接口
* Supplier接口
* Consumer接口
* Predicate接口
* Function接口
### 1.5Supplier接口
##### Supplier<T>:包含一个无参的方法
* T get():获得结果
* 该方法不需要参数,它会按照某种实现逻辑(由Lambda表达式实现)返回一个数据
* Supplier<T>接口也被称为生产型接口,如果我们指定了接口的泛型是什么类型,那么接口中的get方法就会生产什么类型的数据供我们使用
### 1.6Comsumer接口
##### Consumer<T>:包含两个方法
* void accpet(T t):对给定的参数执行此操作
* default Consumer<T> andThen(Consumer after):返回一个组合的Consumer,依次执行此操作,然后执行after操作
* Consumer<T>接口也被称为消费型接口,它消费的数据的数据类型由泛型指定
##### 练习
* String[] strArray = {"林青霞,30","张曼玉,35","王祖贤,33"};
* 要求:
* 把打印姓名的动作作为第一个Consumer接口的Lambda实例
* 把打印年龄的动作作为第二个Consumer接口的Lambda实例
* 将两个Consumer接口按照顺序组合到一起使用
### 1.7Predicate接口
##### Predicate接口:常用的四个方法
* boolean test(T t):对给定的参数进行判断(判断逻辑由Lambda表达式实现),返回一个布尔值
* default Predicate<T> negate():返回一个逻辑的否定,对应逻辑非
* default Predicate<T> and(Predicate other):返回一个组合判断,对应短路与
* default Predicate<T> or(Predicate other):返回一个组合判断,对应短路与
* Predicate<T>接口通常用于判断参数是否满足指定条件
### 1.8Function接口
##### Function<T,R>:常用的两个方法
* R apply(T,t):将此函数应用于给定的参数
* default<V> Function andThen(Function after):返回一个组合函数,首先将该函数应用于输入,然后将after函数应用于结果
* Function<T,R>接口通常用于对参数进行处理,转换(处理逻辑由Lambda表达式实现),然后返回一个新的值
## Stream流
### 1.1体验Stream流
##### 使用Stream流完成过滤操作
* list.stream().filter(s -> s.startsWith("张")).filter(s ->s.length() == 3).forEach(System.out::println);
* 直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:生成流、过滤姓、过滤名字长度、遍历
### 1.2Stream流的生成方式
##### Stream流的使用
* 生成流
* 通过数据源(集合、数组等)生成流
* list.stream()
* 中间操作
* 一个流后面可以跟随零个或多个中间操作,其重要目的是打开流,做出某种程度的数据过滤/映射,然后返回一个新的流,交给下一个操作使用
* filter()
* 终结操作
* 一个流只能有一个终结操作,当这个操作被执行后,流就被使用“光”了,无法再被操作,所以这必定是流的最后一个操作
* forEach()
##### Stream流的常见生成方式
* Collection体系的集合可以使用默认方法stream()生成流
* default Stream<E> stream()
* Map体系的集合间接的生成流
* 数组可以通过Stream接口的静态方法of(T... values)生成流
### 1.3Stream流的常见中间操作方法
* Stream<T> filter(Predicate p):用于对流中的数据进行过滤
* Predicate接口中的方法:boolean test(T t):对给定的参数进行判断,返回一个布尔值
* Stream<T> limit(long maxSize):返回此流中的元素组成的流,截取前指定参数个数的数据
* Stream<T> skip(long n):跳过指定参数个数的数据,返回由该流的剩余元素组成的流
* static<T> Stream<T> concat(Stream a,Stream b):合并a和b两个流为一个流
* Stream<T> distinct():返回由该流的不同元素(根据Object.equals(Object))组成的流
* Stream<T> sorted():返回由此流的元素组成的流,根据自然顺序排序
* Stream<T> sorted(Comparator comparator):返回由该流的元素组成的流,根据提供的Comparator进行排序
* <R> Stream<R> map(Function mapper):返回由给定函数应用于此流的元素的结果组成的流
* Function接口中的方法:R apply(T t)
* IntStream mapToInt(ToIntFunction mapper):返回一个IntStream其中包括将给定函数应用于此流的元素的结果
* IntStream:表示原始int流
* ToIntFunction接口中的方法:int applyAsInt(T value)
### 1.4Stream流的常见终结操作方法
##### Stream流的常见终结操作方法
* void forEach(Consumer action):对此流的每个元素执行操作
* Consumer接口中的方法:void accept(T t):对给定的参数执行此操作
* long count():返回此流中的元素数
### 1.5Stream流的收集操作
##### Stream流的收集方法
* R collect(Collector collector)
* 但这个收集方法的参数是一个Collector接口
##### 工具类Collectors提供了具体的收集方式
* public static <T> Collector toList():把元素收集到List集合中
* public static <T> Collector toSet():把元素收集到Set集合中
* public static Collector toMap(Function keyMapper, Function valueMapper):把元素收集到Map集合中
## 类加载器
### 1.1类加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化
-----------
##### 类的加载
* 就是指将class文件读入内存,并为之创建一个java.lang.Class对象
* 任何类被使用时,系统都会为之建立一个java.lang.Class对象
##### 类的连接
* 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
* 准备阶段:负责为类的类变量分配内存,并设置默认初始化值
* 解析阶段:将类的二进制数据中的符号引用替换为直接引用
##### 类的初始化
* 在该阶段,主要就是对类变量进行初始化
##### 类的初始化步骤
* 假如类还未被加载和连接,则程序先加载并连接该类
* 假如该类的直接父类还未被初始化,则先初始化其父类
* 加入类中有初始化语句,则系统依次执行这些初始化语句
* 注意:在执行第二个步骤时,系统对直接父类的初始化步骤也遵循初始化步骤1-3
##### 类的初始化时机
* 创建类的实例
* 调用类的类方法
* 访问类或者接口的类变量,或者为该类变量赋值
* 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
* 初始化某个类的子类
* 直接使用java.exe命令来运行某个主类
### 1.2类加载器
##### 类加载器的作用
* 负责将.class文件加载到内存中,并为之生成对应的java.lang.Class对象
* 虽然我们不用过分关心类加载机制,但是了解这个机制我们就能更好地理解程序的运行
##### JVM的类加载机制
* 全盘负责:就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另一个类加载器来载入
* 父类委托:就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
* 缓存机制:保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区
##### ClassLoader:是负责加载类的对象
##### Java运行时具有以下的内置类加载器
* Bootstrap class loader:它是虚拟机的内置类加载器,通常表示为null,并且没有父null
* Platform class loader:平台类加载器可以看到所有平台类,平台类包括由平台类加载器或其祖先定义的Java SE平台API,其实现类和JDK特定的运行时类
* System class loader:它被称为应用程序类加载器,与平台类加载器不同。系统类加载器通常用于定义应用程序类路径,模块路径和JDK特定工具上的类
* 类加载器的继承关系:System的父加载器为Platform,而Platform的父加载器为Bootstrap
##### ClassLoader中的两个方法
* static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器
* ClassLoader getParent():返回父类加载器进行委派
## 反射
### 1.1反射概述
Java反射机制:是指在运行时去获取一个类的变量和方法信息。然后通过获取到的信息来创建对象,调用方法的一种机制。由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译器就完成确定,在运行期仍然可以扩展
### 1.2获取Class类的对象
我们要想通过反射区使用一个类,首先我们要获取到该类的字节码文件对象,也就是类型为Class类型的对象,这里我们提供三种方式获取Class类型的对象
* 使用类的class属性来获取该类对应的Class对象。举例:Student.class将会返回Student类对应的Class对象
* 调用对象的getClass()方法,返回该对象所属类对应的Class对象
* 该方法时Object类中的方法,所有的Java对象都可以调用该方法
* 使用Class类中的静态方法forName(String className),该方法需要传入字符串参数,该字符串参数的值是某个类的全路径,也就是完整包名的路径
### 1.3反射获取构造方法并使用
##### Class类中用于获取构造方法的方法
* Constructor<?>[] getConstructors():返回所有公共构造方法对象的数组
* Constructor<?>[] getDeclaredConstructors():返回所有构造方法对象的数组
* Constructor<T> getConstructor(Class<?>... parameterTypes):返回单个公共构造方法对象
* Constructor<T> getDeclaredConstructor(Class<?>...parameterTyprs):返回单个构造方法对象
Constructor类中用于创建对象的方法
* T newInstance(Object...initargs):根据指定的构造方法创建对象
### 1.4反射获取构造方法并使用练习
##### 练习1:通过反射实现如下操作
* Student s = new Student("林青霞",30,"西安");
* System.out.println(s);
* ```java
public static void main(String[] args) throws ClassNotFoundException,NoSuchMethodException
{
Class<?> c = Class.forName("com.bilibili.reflect.Student");
Constructor<?> con = c.getConstructor(String.class,int.class,String.class);
Object obj = con.newInstance("林青霞",30,"西安");
System.out.println(obj);
}基本类型也可以通过.class得到对应的Class类型
public void setAccessible(boolean flag):值为true,取消访问检查(暴力访问)
1.5反射获取成员变量并使用
Class类中用于获取成员变量的方法
- Field[] getFields():返回所有公共成员变量对象的数组
- Field[] getDeclaredFields():返回所有成员变量对象的数组
- Field getField(String name):返回单个公共成员变量对象
- Field getDeclaredField(String name):返回单个成员变量对象
Field类中用于给成员变量赋值的方法
- void set(Object obj,Object value):给obj对象的成员变量赋值为value
1.6反射获取成员方法并使用
Class类中用于获取成员变量的方法
- Method[] getMethods():返回所有公共成员方法对象的数组,包括继承的
- Methods[] getDeclaredMethods():返回所有成员方法对象的数组,不包括继承的
- Method getMethod(Srting name,Class<?> …parameterTypes):返回单个公共成员方法对象、
- Method getDeclaredMethod(String name,Class<?> …parameterTypes):返回单个成员方法对象
Method类中用于调用成员方法的方法
- Object invoke(Object obj,Object… args):调用obj对象的成员方法,参数是args,返回值是Object类型
1.7反射练习
练习1:越过泛型检查
1 | package com.bilibili.reflect; |
运行配置文件指定内容
1 | Properties prop = new Properties(); |
模块化
1.1模块的基本使用
模块的基本使用步骤
- 创建模块(按照以前的讲解方式创建模块,创建包,创建类,定义方法)
- 在模块的src目录下新建一个名为module-info.java的描述性文件,该文件专门定义模块名,访问权限,模块依赖等信息。描述性文件中使用模块导出和模块依赖进行配置使用
- 模块中所有未导出的包都是模块私有的,他们是不能在模块之外被访问的
- 模块导出格式:exports 包名;
- 一个模块要访问其他的模块,必须明确指定依赖哪些模块,未明确指定依赖的模块不能访问
- 模块依赖格式:requires 模块名;
- 注意:写模块名报错,需要按下Alt+Enter提示,然后选择模块依赖
1.2模块服务的使用
服务
从Java6开始,Java提供了一种服务机制,允许服务提供者和服务使用者之间完成解耦,简单的说,就是服务使用者只面向接口编程,但不清楚服务提供者的实现类
Java9允许将服务接口定义在一个模块中,并使用uses语句来声明该服务接口,然后针对该服务接口提供不同的服务实现类,这些服务实现类可以分布在不同的模块中,服务实现模块则使用provides语句为服务接口指定实现类。服务使用者只需要面向接口编程即可
模块服务的使用步骤
在myOne模块下创建一个包com.itheima_03,在该包下提供一个接口,接口中定义一个抽象方法(MyService)
在com.itheima_03包下创建一个包impl,在该包下提供接口的两个实现类Itheima和Czxy
在myOne这个模块下的描述性文件中添加如下配置
- 模块导出:exports com.itheima_03;
- 服务提供:provides MyService with Itheima;指定MyService的服务实现类是Itheima
在myTwo这个模块下的描述性文件中添加如下配置
- 声明服务接口:uses MyService;
在myTwo这个模块的使用MyService接口提供的服务
- ServiceLoader:一种加载服务实现的工具