什么是枚举
- 一枚一枚可以列举出来的,才建议使用枚举类型
- 枚举编译之后也是生成class文件
- 枚举也是一种引用类型数据,但不继承Object
- 枚举中的每一个值都可以看做是常量
- 枚举语法
- enum 枚举类型名{
枚举值1, 枚举值2(全部大写)
}
public static void main(String[] args) {System.out.println(divide(10,0) == Result.SUCCESS ? "计算成功" : "计算失败");}public static Result divide(int a,int b){try{int c = a / b;return Result.SUCCESS;}catch (Exception e){return Result.FAIL;}}enum Result{SUCCESS , FAIL}
枚举类的注意事项
- 枚据类无法通过new进行实例化对象的创建,否则编译报错
- 当枚举类没有任何构造器的时候,JVM的编译器会提供一个private的无参构造器供其使用,当枚举类含有任何的构造器时,JVM的编译则不会提供
- 枚举类的构造器只能使用Private进行修饰,如果没有private关键字,也不是“缺省”,编译器会自动补全
- 枚举类的构造器中无法使用super(实参)
- 枚举类中枚举对象如果使用无参构造器进行对象的初始化,枚举对象后面的()可以省略不写
- 枚举类中的枚举对象必须编写在枚举类的第一行,否则编译报错
- 枚举类中的枚举对象隐式被public static final关键字进行修饰,如果显示修饰编译报错
- 所有的枚举类都隐式继承Enum类
- 所有的枚举类无法继承其他类,因为唯一的单继承机会给了Enum类
- 所有的枚举类可以实现多个接口
所有的枚举类不可以被其他类进行继承
enum Week{MONDAY(1,"星期一"),TUESDAY(2,"星期二"),WEDNESDAY(3,"星期三"),THURSDAY(4,"星期四"),FRIDAY(5,"星期五"),SATURDAY(6,"星期六"),SUNDAY(7,"星期日");private int number;private String decription;private Week(int number, String decription) {this.number = number;this.decription = decription;}public static Week getByNumber(int number){switch(number){case 1:return MONDAY;case 2:return TUESDAY;case 3:return WEDNESDAY;case 4:return THURSDAY;case 5:return FRIDAY;case 6:return SATURDAY;case 7:return SUNDAY;
异常
什么是异常?
- 异常在Java中以类的形式存在,可以实例化异常类
- 当程序出现异常时,JVM底层会实例化异常对象,并把异常信息打印在控制台上
- 异常的原始父类是Throwable
- Exception类的直接子类(RuntimeException类及其子类除外),都叫做编译时异常(受检异常,受控异常)
- 编译时异常表示必须在编写程序的时候预先对这种异常进行处理,如果不处理编译器报错
- RuntimeException以及他的子类,都属于运行时异常(非受检异常,非受控异常)
- 运行时异常表示在编写程序阶段,程序员可以预先处理,也可以不管
异常都是发生在运行阶段,编译阶段异常是不会发生的,
- 编译时异常一般发生的概率比较高
- 举个例子
你看到外面下雨了,出门之前会想到,如果不打伞就可能大概率生病,(生病也是一种异常)
“拿一把伞”就是对“生病异常”发生之前的一种处理方式
对于一些发生概率较高的异常,需要在运行之前对其进行预处理
虽然你哪了雨伞,但是你得出去才需要打伞 (异常只有运行时会发生)
- 运行时异常一般发生的概率比较低
- 举个例子
小明走在大街上,可能会被天上的飞机轮子砸到,(被砸到也是一种异常)
但是这种异常发生概率较低
在出门之前你没必要提前对这种发生概率较低的异常进行预处理
如果你预处理这种异常,你将活得很累
处理异常的两种方式
- 第一种方式——异常声明处理:
- 在方法声明的位置上使用throws关键字,谁调用这个方法,我就抛给谁,抛给调用者来处理
- 第二种方式——异常捕获处理:
- 使用try..catch语句对异常进行捕捉
一般main 方法不建议使用throws,因为这个异常如果真的发生了,一定会抛给JVM,JVM只有终止
关键字 throw 和 throws 区别
- throw关键字:
- 含义:抛出指定的异常对象
- 格式:throw new 异常类名(实参);
- throws关键字:
- 含义:处理异常的方式之一,抛出异常,将异常抛给调用者来处理
- 格式:修饰符 返回类型 方法名() throws 异常类名1,异常类名2 {}
异常代码执行
```java public static void main(String[] args) { System.out.println(“main begin”); try { m1(); System.out.println(“文件输入成功”); } catch (FileNotFoundException e) { e.printStackTrace(); System.out.println(“文件输入失败”); } System.out.println(“main over”); }
public static void m1() throws FileNotFoundException { System.out.println(“m1 begin”); m2(); System.out.println(“m1 over”); } public static void m2() throws FileNotFoundException { System.out.println(“m2 begin”); m3(); System.out.println(“m2 over”); } public static void m3() throws FileNotFoundException { System.out.println(“m3 begin”); new FileInputStream(“F:/新建文件夹 (2)新建文本文档.txt”); System.out.println(“m3 over”); }
代码执行结果main begin m1 begin m2 begin m3 begin 文件输入失败 main over<a name="UZgyO"></a>## 异常代码执行1. 执行方法时,代码出现异常,只要代码没有捕捉,异常后续的代码不会执行1. try语句块的某一行代码出现异常,该行后面的代码不会执行<a name="bhX2l"></a>## 处理编译时异常,应该上报还是捕捉,怎么选?- 如果希望调用者来处理,选择throws上报<a name="Bb3Rp"></a>## 异常对象有两个重要的方法1. 获取异常简单的描述信息:1. String msg = exception.getMessage();2. 打印异常追踪的堆栈信息:1. exception.printStackTrace();<a name="fmDhH"></a>## try..catch 中的finally子句1. 在finally子句中的代码是最后执行的,并且是一定会执行的(只有结束jvm才不会执行finally语句)1. finally必须必须和try一起出现,不能单独编写1. 通常在finally语句块中完成资源的释放,因为finally中的代码比较有保障,即使try语句的代码出现异常,finally也会正常执行1. try中有return,会先将值暂存,无论finally语句中对该值做什么处理,最终返回的都是try语句中的暂存值1. **当try与finally语句中均有return语句,会忽略try中的return**```javapublic static void main(String[] args) {FileInputStream fis = null;try {fis = new FileInputStream("E:/sasa");String s = null;s.toString();System.out.println("hello word");} catch(FileNotFoundException e){e.printStackTrace();} catch (IOException e) {e.printStackTrace();}finally {if (fis != null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}
try…catch… 可以联合使用
try {
System.out.println("try...");
return;
// System.out.println("sadasd");
}finally {
System.out.println("finally...");
}
//以上代码执行顺序:
// 先执行try
// 在执行 finally
//最后执行return(return语句只要执行方法必然结束)
自定义异常
- 写一个类继承Exception或者RuntimeException
- 提供两个构造方法,一个无参数的,一个带有String参数的
public class MyException extends Exception{ public MyException(){} public MyException(String s){} } public class Test throws MyException{ throw new MyException("sasas"); }集合
- 集合实际上就是一个容器,可以容纳其他类型的数据
- 集合不能直接存储基本数据类型的数据,另外集合也不能直接存储java对象,集合当中存储的都是Java对象的内存地址(或者说集合存储的都是引用)
- 每一个不同的集合,底层都会对应不同的数据结构,往不同的集合中存储元素,等于将数据放到了不同的数据结构中(你使用了不同的集合就是使用了不同的数据结构)
- 所有的集合类和集合接口都在java.util包下
在Java中集合分为两大类
- 一类是单个方式存储元素:
- 单个方式存储元素,这一类集合超级父接口:java.util.Collection

- 一类是以键值对的方式存储元素:
- 以键值对的方式存储元素,这一类集合中超级父类接口:java.util.Map
集合两大类总结
- ArrayList:底层是数组
- LinkedList:底层是双向链表
- Vector:底层是数组,线程安全的,效率较低,使用较少
- HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分
- TreeSet:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分
- HashMap:底层是哈希表
- HashTable:底层是哈希表,只不过线程安全的,效率较低,使用较少
- Properties:是线程安全的,并且key和value只能存储字符串String
TreeMap:底层是二叉树,TreeMap集合中的key可以自动按照大小顺序排序
集合存储元素的特点
List集合存储元的特点
- 有序可重复
- 有序:存进去的顺序和取出的顺序相同,每一个元素都有下标
- 可重复:存进去1,可以再存储一个1
- Set(Map)集合元素的特点
- 无序不可重复
- 无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标
- 不可重复:存进去1,就不能再存1了
- SortedSet(SortedMap)集合存储元素特点·
- 首先是无序不可重复的,但是SortedSet集合中的元素是可排序的
- 无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标
- 不可重复:存进去1,不能在存储1了
- 可排序:可以按照大小顺序进行排列
Map集合的key就是一个Set集合,在Set集合中放数据,实际上放到了Map集合的key部分
Collection集合
关于java.util.Collection中的常用方法
- Collection中能存放什么元素?
- 没有使用“泛型”之前,Collection中可以存储Object的所有子类型
- 使用了“泛型”之后,Collection中只能存储某个具体类型
Collection中常用的方法
- boolean add(E e) 向集合中添加元素
- int size() 获取集合中元素的个数
- void clear() 清空集合
- boolean contains(Object e) 判断当前集合中是否包含元素
- boolean remove(Object e) 删除集合中的某个元素
- boolean isEmpty() 判断集合是否为空
- Object[] toArray() 调用这个方法可以把集合转换成数组
public static void main(String[] args) { //多态 Collection c = new ArrayList(); //往集合中添加元素 c.add(200);//自动装箱,(Java5的新特性),实际上是放进去一个对象的内存地址,Integer x = new Integer(200) c.add(3.14);//存进去什么类型,取出的时候还是什么类型, //只不过这里println会调用toString()方法 c.add(new Object()); c.add(new Army()); c.add(true);//自动装箱 //获取集合中的元素个数 System.out.println("集合中元素个数是:" + c.size()); c.clear(); System.out.println("集合中元素个数是:" + c.size()); c.add("绿巨人"); System.out.println(c.contains("绿巨人"));//true System.out.println(c.contains("绿巨"));//false //删除集合中某个元素 System.out.println(c.remove("绿巨"));//false System.out.println(c.add(123));//true System.out.println(c.isEmpty());//false c.clear(); System.out.println(c.isEmpty());//true c.add(200); c.add(3.14); c.add(new Object()); c.add(new Army()); c.add(true); Object[] objs = c.toArray(); for (int i = 0; i < objs.length; i++) { System.out.println(objs[i]); }
迭代、遍历集合 的方法
- 第一步:获取集合对象的迭代器对象Iterator
- 第二步:通过以上获取的迭代器对象开始迭代/遍历集合
- Iterator对象中的方法:
- boolean hasNext() 如果仍有元素可以迭代。则返回true
- Object next()返回迭代的下一个元素
遍历集合时,一次循环只能调用一次iterator()方法
迭代器对象只能进行唯一的一轮迭代器,如果多轮迭代,需要每轮重新获取迭代器对象,否则发生没有元素异常NoSuchElementException
调用迭代器以后,不可以再对集合进行操作,否则会出现异常 java.util.ConcurrentModificationException
在迭代集合元素过程中,不能调用集合对象的remove方法,否则java.util.ConcurrentModificationException
Iterator it = c.iterator();
while(it.hasNext()){
Object obj = it.next();
//调用迭代器的remove方法,删除的一定是迭代器指向的额当前元素
it.remove();
System.out.println(obj);
}
深入集合Collection的Contains方法
源码:o.equals(elementData[i]) String类重写了equals()方法
public static void main(String[] args) { Collection c = new ArrayList(); String s1 = new String("abc"); c.add(s1); String s2 = new String("def"); String x = new String("abc"); System.out.println(c.contains(x)); //true }集合使用contains()方法比较内容时,类要重写equals()方法
深入集合Collection的remove方法
public static void main(String[] args) { Collection c = new ArrayList(); User u1 = new User("lisi"); c.add(u1); User u2 = new User("lisi"); c.remove(u2); System.out.println(c.size()); } class User{ private String name; public User(){} public User(String name){ this.name = name; } //重写equals()方法 public boolean equals(Object o){ if (null == o || !(o instanceof User)) return false; if (this == o) return true; User u = (User) o; return u.name == this.name; }remove在调用时,底层也会调用equals()方法,需要类重写equals方法
List接口特有的方法
void add(int index, E element);
- E get(int index)
- int indexof(Object o)
- int lastIndexOf(Object o)
- E remove(int index)
- E set(int index, E element)
public static void main(String[] args) { List myList = new ArrayList(); myList.add(1); myList.add(2); myList.add(1); myList.add(3); myList.add(4); myList.add(1,'a'); /* Iterator it =myList.iterator(); while(it.hasNext()){ System.out.println(it.next()); }*/ //List集合特有的遍历方法 for (int i = 0; i < myList.size(); i++) { System.out.println(myList.get(i)); } //获取指定对象第一次出现的索引 System.out.println(myList.indexOf('a'));//1 //获取指定对象最后一次出现的索引 System.out.println(myList.lastIndexOf(1));//3 //删除指定元素 myList.remove(1); //修改指定位置元素 myList.set(2,'b'); }ArrayList集合
- 默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量为10)
- 集合底层是一个Object[] 数组
- 构造方法:
- new ArrayList();
- new ArrayList(20);
- ArrayList集合的扩容是原容量的1.5倍
-
怎样将一个线程不安全的ArrayList集合转换成线程安全的
java.util.Collection 是集合接口
- java.util.Collections 是集合工具类
Collections.synchronizedList(myList)
LinkedList集合
单向链表数据结构
链表数据结构的基本单元Node
- 单向链表中任何一个节点Node中都有两个属性:
- 第一是存储数据
- 第二是存储下一节点的内存地址
链表的有点和缺点
- 链表优点:随机增删效率较高(因为链表上的元素在空间存储上内存地址不连续,所以增删元素不会有大量元素位移)
- 链表缺点:查询效率较低,每一次查找某个元素的时候需要从头节点开始往下遍历(不能通过数学表达式计算被查找元素的内存地址)
注意:ArrayLIst之所以检索效率比较高,不是单纯的因为下标的原因,是因为底层数组发挥的作用
LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历
双向链表
Vector
- 底层也是一个数组,是一个有序的集合
- 初始化容量也是10
- 扩容之后是原容量的2倍
- Vector集合可以存储null元素,在使用Vector集合元素之前,先针对元素进行非空校验,防止空指针异常
- Vector所有方法是线程同步的,都带有synchronized关键字,是线程安全的,效率比较低,使用较少
ArrayList集合和Vector集合的区别?
- 线程安全性:
- ArrayLIst集合:ArrayList集合是线程不安全的,只适用于单线程程序
- Vector集合:Vector集合是线程安全的,只是用于多线程程序,在单线程中使用效率较低
- 无参构造器底层的初始化容量:
- ArrayList集合:
- JDk6.0以前:初始化容量:10
- JDK7.0以后:初始化容量:0
- Vector集合:初始化容量:10
- ArrayList集合:
- 底层数组影响扩容的因素不同
- ArrayList集合:是否为第一次扩容
- Vector集合:增量参数是否大于0
底层数组的扩容规则不同
集合使用泛型来减少向下转型的操作
- <>里填类名称,表示集合只能存储这样类型的数据
```java
class Test
{ public E dosome(){} public static void main(String[] args){
} }//使用泛型 Test<String> test = new Test<>; //只能返回String类型的数据 String s = test.dosome;
<a name="e3k2H"></a>
## foreach(增强for循环)
1. 对数组遍历:
1. for(元素类型 变量名 :数组){
System.out.println(变量名);<br />}
```java
for (int i: arr) {
System.out.println(i);
}
- 对集合遍历
- for(元素类型 变量名 :集合){
System.out.println(变量名);
}
for(String s: strList){
System.out.println(s);
}
Map集合
- Map和Collection没有继承关系
Map集合以key和value的方式存储数据:键值对
V put(K key, V value) 向Map集合中添加键值对
- V get(Object key) 通过key来获取value
- void clear() 清空Map集合
- boolean containsKey(Object key) 判断Map中是否含有某个value,底层调用equals方法
- boolean containsValue(Object value) 判断Map中是否包含某个value,底层调用equals方法
- boolean isEmpty() 判断Map集合中元素个数是否为0
- Set
keySet() 获取Map集合所有的key(所有的键是一个set集合) - V remove(Object key) 通过key删除键值对
- int size() 获取Map集合中键值对的个数
- Collection
values() 获取Map集合中所有的value ,返回一个Collection - Set
> entrySet() 将Map集合转换成Set集合
遍历Map集合
第一种遍历方法
//第一种遍历方法,先获取Map集合中所有的key,再用for循环 Set<Integer> keys = map.keySet(); Iterator<Integer> it = keys.iterator(); while(it.hasNext()){ Integer i = it.next(); System.out.println(i + "=" + map.get(i)); }第二种遍历方法
//第二种遍历方法,使用Map接口中的forEach()默认方法 Map<Integer, String> map = new HashMap<>(); //遍历Map集合 map.forEach(new BiConsumer<Integer,String> (){ //重写BiConsumer中的accept方法 @Override public void accept(String key , String value){ System.out.println(key + "=" + value); } });第三种遍历方法
//第三种遍历方法,先使用entrySet()方法,再用增强for循环 //这种方式效率比较高,因为获取的key和value都是直接从node对象中获取的属性值 //这种方式适合于大数据量 Set<Map.Entry<Integer, String>> set = map.entrySet(); for(Map.Entry<Integer, String> s : set){ System.out.println(s.getKey() + "=" + s.getValue()); }HashMap集合
HashMap集合底层是一个哈希表/散列表的数据结构
- 哈希表是一个数组和单向链表的结合体,哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点
- 一维数组,这个数组中的每一个元素都是一个单向链表
- 如果单向链表长度超过8,单向列表会转换成红黑树
HashMap集合底层源代码
public class HashMap{ //HashMap底层实际上就是一个数组 Node<K,V>[] table; //静态内部类HashMap.Node static class Node<k,V>{ final int hash;//哈希值(哈希值是key的hashCode()方法的执行结果) final K key;//存储到Map集合中的那个key V value;//存储到Map集合中的那个value Node<K,V> next;//下一个节点的内存地址 } }HashMap集合的key部分特点:无序不可重复
- 放在HashMap集合key部分的元素以及放在HashSet集合中的元素,需要同时重写hashCode()+equals()方法
- HashMap集合的默认初始化容量是16,默认加载因子是0.75(当HashMap集合底层数组的容量达到75%的时候,数组开始扩容)
重点:HashMap集合初始化容量必须是2的幂,这也是官方推荐的,这是因为要达到散列均匀,提高存取效率
- 向Map集合中存、取,都是先调用key的hashCode方法,然后再调用equals方法(equals方法有可能调用有可能不调用)


- HashMap集合的key和value都可以为null
HashMap集合底层数组的初始容量、初始加载因子和扩容规则
初始容量和初始加载因子是多少取决于创建对象时的构造器
- HashMap()
- 初始容量:创建对象时没有进行初始化操作,会在第一次添加元素时进行初始化操作,初始容量为16
- 初始加载因子:0.75
- HashMap(int initialCapacity)
- 初始容量:自定义
- 初始加载因子:0.75
- HashMap(int initialCapacity , float loadFactor)
- 初始容量:自定义
- 初始加载因子:自定义
- HashMap(Map<? extends K , ? extends V> m)
- 初始容量:参数集合的长度
- 加载因子:参数集合长度的加载因子
- 第一次添加元素时的扩容规则:16
后续添加元素时的扩容规则:原来底层数组的长度 << 1 (原来数组长度的2 倍)
底层数组中链表对象和红黑树对象的转换规则
链表对象进行红黑树对象的转换
- 链表对象长度达到8时(链表对象达到8,但是数组长度没达到64,添加元素会将链表对象一部分元素转移到另一个数组中)
- 底层数组长度达到64时
- 红黑树对象进行链表对象的转换
- 红黑树结构中结点对象的数量降至6个
HashMap JDK7.0和JDK8.0的区别
- 底层数组的桶元素内容不同
- JDK7.0(包含)以前:null,链表对象
- JDK8.0(包含)以后:null,链表对象,红黑树对象
- 底层数组的数据类型不同
- JDK7.0(包含)以前:Entry
[] - JDK8.0(包含)以后:Node
[]
- JDK7.0(包含)以前:Entry
- 无参构造器底层数组的初始容量不同:
- JDK7.0(包含)以前:16
- JDK8.0(包含)以后:创建对象时没有进行初始化操作,会在第一次添加元素时进行初始化操作,初始容量为16
- hash算法不同:
- JDK7.0(包含)以前:
h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12);
h ^ (h >>> 7) ^ (h >>> 4); - JDK8.0(包含)以后:
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
- JDK7.0(包含)以前:
底层数组的扩容规则:
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型
- Properties被称为属性类对象
Properties是线程安全的
public static void main(String[] args) { Properties pp = new Properties(); pp.setProperty("1","zhangsan"); pp.setProperty("2","lisi"); System.out.println(pp.getProperty("1")); }TreeMap集合
TreeSet集合底层实际上是一个TreeMap
- TreeMap集合底层是一个二叉树
- 放到TreeSet集合中的元素等同于放到TreeMap集合key部分了
TreeSet集合中的元素,无序不可重复,但是可以按照元素的大小顺序进行排序,称为:可排序集合
放到TreeSet/TreeMap集合部分的key元素必须排序
因为TreeMap集合可以自动排序,所以必须实现java.lang.Comparable
接口,不然会出现异常 public class TreeSet01 { public static void main(String[] args) { User u1 = new User(2,"zhangsan"); User u2 = new User(2,"lisi"); User u3 = new User(4,"wangwu"); TreeSet<User> ts = new TreeSet<>(); ts.add(u1); ts.add(u2); ts.add(u3); for(User u : ts){ System.out.println(u); } } } class User implements Comparable<User>{ int age; String name; public User(int age, String name){ this.age = age; this.name = name; } /* compareTo方法的返回值很重要: 返回0表示相同,value会覆盖 返回>0,会继续在右子树上找 返回<0,会继续在左子树上找 */ public int compareTo(User u1) { if(this.age == u1.age) return this.name.compareTo(u1.name); return this.age - u1.age; } public String toString() { return "客户" + name + "年龄" + age ; } }在构造TreeSet或者TreeMap集合的时候给他传一个比较器对象
public static void main(String[] args) { //使用匿名内部类 TreeSet<Animal> ts = new TreeSet<>(new Comparator<Animal>() { public int compare(Animal o1, Animal o2) { return o1.age - o2.age; } }); ts.add(new Animal(2)); ts.add(new Animal(1)); ts.add(new Animal(0)); for(Animal a : ts){ System.out.println(a); } } } class Animal{ int age; public Animal(int age){ this.age = age; } }Comparable和Comparator怎么选择呢?
TreeSet/TreeMap是自平衡二叉树。遵循左小右大原则存放(存放是要依靠左小右大的原则,所以这个存放的时候要进行比较)
- 遍历二叉树的时候有三种方式:
- 前序遍历:根左右
- 中序遍历:左根右
- 后序遍历:左右根
- TreeSet/TreeMap集合采用的是:中序遍历方式。Itreator迭代器采用的是中序遍历方式:左根右
集合工具类
- java.util.Collection 集合接口 java.util.Collections 集合工具类,方便集合进行操作
- Collections.synchronizedList() 将集合变成线程安全的
- Collections.sort()给集合进行排序
- Collections.reverse(List<?> list) 反转指定列表中元素的顺序
Collections.shuffle(List<?> list) 使用默认随机源对指定列表进行置换
//类实现java.lang.Comparable 接口 class Animal implements Comparable<Animal>{ int age; public Animal(int age){ this.age = age; } } //直接用Collections工具类进行排序 Collections.sort(myList1); //自定义比较器对象,实现java.util.Comparator接口 Collections.sort(List集合,比较器对象); Collections.sort(myList, new Comparator<Animal>() { @Override public int compare(Animal o1, Animal o2) { return o1.age - o2.age; } });File类
遍历多级文件夹
public static void main (String[] args){ //创建文件对象 File file = new File("D:\jdk-18"); print(file); } public void print (File file){ //健壮性判断 if (file == null) { System.out.println("文件有误"); return; } //将文件转换为File类的数组 File[] files = file.listFiles(); //使用增强for循环,遍历file数组 for(File f : files){ if(f.isFile()){ System.out.println("文件:" + f); } if(f.isDirectory){ System.out.println("目录:" + f); //递归调用print方法继续遍历下一层目录 print(f); } } }IO流
IO流的分类
按照流的方向进行分类:
- 以内存为参照物
- 往内存中去,叫做输入(Input),或者叫做读(Read)
- 从内存中走出来,叫做输出(Output),或者叫做写(Write)
另一种方式是按照读取数据方式不同进行分类:
有的流是按照字节的方式读取数据,一次读取1个字节byte,等同于一次读取8个二进制位。<br /> 这种流是万能的,什么类型的文件都可以读取。包括:文本文件,图片,声音文件,视频文件等....<br /> 假设文件file1.txt,采用字节流的话是这样读的:<br /> a中国bc张三fe<br /> 第一次读:一个字节,正好读到'a'<br /> 第二次读:一个字节,正好读到'中'字符的一半。<br /> 第三次读:一个字节,正好读到'中'字符的另外一半。 有的流是按照字符的方式读取数据的,一次读取一个字符,这种流是为了方便读取<br /> 普通文本文件而存在的,这种流不能读取:图片、声音、视频等文件。只能读取纯<br /> 文本文件,连word文件都无法读取。<br /> 假设文件file1.txt,采用字符流的话是这样读的:<br /> a中国bc张三fe<br /> 第一次读:'a'字符('a'字符在windows系统中占用1个字节。)<br /> 第二次读:'中'字符('中'字符在windows系统中占用2个字节。)Java IO流这块有四大家族
- 以内存为参照物
四大家族的首领(全部都是抽象类):
- java.io.InputStream 字节输入流
- java.io.OutputStream 字节输出流
- java.io,Reader 字符输入流
- java.io.Writer 字符输出流
- 所有的流都实现了
- java.io.Closeable 接口,都是可关闭的,都有close()方法
- 流毕竟是一个管道,这个是内存和硬盘之间的通道,用完之后一定要关闭,不然会浪费很多资源
- 所有的输出流都实现了:
- java.io.Flushable接口,都是可刷新的,都有flush()方法
- 输出流在最终输出之后,一定要记得flush(),表示将通道/管道当中剩余未输出的数据强行输出完
- 注意:如果没有flush()可能会导致丢失数据
在java中只要“类名”以Stream结尾的都是字节流,以“Reader/Writer”结尾的都是字符流
java.io包下需要掌握的流有16个
文件专属:
java.io.FileInputStream(掌握)<br /> java.io.FileOutputStream(掌握)<br /> java.io.FileReader<br /> java.io.FileWriter转换流:(将字节流转换成字符流)
java.io.InputStreamReader<br /> java.io.OutputStreamWriter缓冲流专属:
java.io.BufferedReader<br /> java.io.BufferedWriter<br /> java.io.BufferedInputStream<br /> java.io.BufferedOutputStream数据流专属:
java.io.DataInputStream (必须知道写入文件时的顺序和规则才能读出文件内容)<br /> java.io.DataOutputStream标准输出流:
java.io.PrintWriter<br /> java.io.PrintStream(掌握)对象专属流:
java.io.ObjectInputStream(掌握)<br /> java.io.ObjectOutputStream(掌握)FileInputStream类常用的方法
int read() 返回流中读到的阿斯克码值 int read(byte[] bytes) 返回流中读到的byte类型的数组长度
public static void main(String[] args) { FileInputStream fis = null; int read; byte[] bytes = new byte[4]; try { fis = new FileInputStream("F:/a.txt"); while ((read = fis.read(bytes))!=-1){ System.out.print(new String(bytes,0,read)); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }int available() 返回流当中剩余的没有读到的字节数量
public static void main(String[] args) { FileInputStream fis = null; try { fis = new FileInputStream("F:/a.txt"); System.out.println(fis.available()); byte[] bytes = new byte[fis.available()]; System.out.println(new String(bytes)); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } System.out.println("sadasdasd"); }long skip(long n) 跳过几个字节不读
public static void main(String[] args) { FileInputStream fis = null; try { fis = new FileInputStream("F:/a.txt"); //从第四个字节开始读 fis.skip(3); int read = fis.read(); System.out.println(read); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } System.out.println("sadasdasd"); }FileOutputStream的使用
当写入文件不存在时会自动创建
new FileOutputStream(FIle file)谨慎使用,这种构造方法会先将原文件清空,然后重新写入
public static void main(String[] args) { FileOutputStream fos = null; try { //这种构造方法会清空文件内容再写入 //fos = new FileOutputStream("myfile"); //这种构造方法会在文件末尾追加内容 fos = new FileOutputStream("myfile",true); byte[] bytes ={'c','y','l','o','v','e','s','a','w'}; fos.write(bytes); //将String类型的字符串转换成byte数组再写入 fos.write("崔大脸".getBytes(StandardCharsets.UTF_8)); //写完之后一定要刷新 fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }finally{ if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } }文件复制,拷贝目录
使用FileInputStream + FileOutputStream 完成文件的拷贝
- 拷贝的过程应该是一边读,一边写
使用字节流拷贝文件时,文件类型随意,万能的
public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; try { fis = new FileInputStream("F:a.txt"); fos = new FileOutputStream("F:b.txt"); //定义一个一次读多少的byte数组 byte[] bytes = new byte[1024*1024]; int readCount = 0; while((readCount = fis.read(bytes)) != -1){ fos.write(bytes,0,readCount); } fos.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } }finally{ if (fis != null) { try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } }public class CopyAll { public static void main(String[] args) { // 拷贝源 File srcFile = new File("F:\\BaiduNetdiskDownload\\ja-netfilter-all"); // 拷贝目标 File destFile = new File("E:\\a\\"); // 调用方法拷贝 copyDir(srcFile, destFile); } /** * 拷贝目录 * @param srcFile 拷贝源 * @param destFile 拷贝目标 */ private static void copyDir(File srcFile, File destFile) { if(srcFile.isFile()) { // srcFile如果是一个文件的话,递归结束。 // 是文件的时候需要拷贝。 // ....一边读一边写。 FileInputStream in = null; FileOutputStream out = null; try { // 读这个文件 // D:\course\02-JavaSE\document\JavaSE进阶讲义\JavaSE进阶-01-面向对象.pdf in = new FileInputStream(srcFile); // 写到这个文件中 // C:\course\02-JavaSE\document\JavaSE进阶讲义\JavaSE进阶-01-面向对象.pdf String path = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\") + srcFile.getAbsolutePath().substring(3); out = new FileOutputStream(path); // 一边读一边写 byte[] bytes = new byte[1024 * 1024]; // 一次复制1MB int readCount = 0; while((readCount = in.read(bytes)) != -1){ out.write(bytes, 0, readCount); } out.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } return; } // 获取源下面的子目录 File[] files = srcFile.listFiles(); for(File file : files){ // 获取所有文件的(包括目录和文件)绝对路径 //System.out.println(file.getAbsolutePath()); if(file.isDirectory()){ // 新建对应的目录 //System.out.println(file.getAbsolutePath()); //D:\course\02-JavaSE\document\JavaSE进阶讲义 源目录 //C:\course\02-JavaSE\document\JavaSE进阶讲义 目标目录 String srcDir = file.getAbsolutePath(); String destDir = (destFile.getAbsolutePath().endsWith("\\") ? destFile.getAbsolutePath() : destFile.getAbsolutePath() + "\\") + srcDir.substring(3); File newFile = new File(destDir); if(!newFile.exists()){ newFile.mkdirs(); } } // 递归调用 copyDir(file, destFile); } }}java.io.FileReader的使用
文件字符输入流,读,只能输入普通文本
public static void main(String[] args) { FileReader fr = null; try { fr = new FileReader("F:a.txt"); char[] chars = new char[5]; int readCount = 0; /* fr.read(chars); for(char c : chars){ System.out.println(c); } */ while ((readCount = fr.read(chars))!= -1){ System.out.println(new String(chars,0,readCount)); } } catch (IOException e) { e.printStackTrace(); }finally{ if (fr != null) { try { fr.close(); } catch (IOException e) { e.printStackTrace(); } } } }java.io.FileWriter的使用
文件字符输出流,写,只能输出普通文本
- FileWrite 可以直接输出String类型的数据
```java
public static void main(String[] args) {
FileWriter fw = null;
try {
} catch (IOException e) {fw = new FileWriter("F:c.txt"); char[] chars = {'c','y'}; fw.write(chars); fw.write("崔裕是个大笨蛋"); //输出必须Flush fw.flush();
}finally{e.printStackTrace();
} }if (fw != null) { try { fw.close(); } catch (IOException e) { e.printStackTrace(); }
}
<a name="VXCBS"></a>
## java.io.BufferedReader的使用
1. BufferedReader:
1. 带有缓冲区的字符输入流
1. 使用这个流的时候不需要自定义char数组,或者说不需要自定义byte数组。自带缓冲
2. 当一个流的构造方法中需要一个流的时候,这个被传进来的叫做:节点流
2. 外部负责包装的这个流,叫做:包装流,还有一个名字叫做:处理流
```java
public static void main(String[] args) {
FileInputStream fi = null;
try {
fi = new FileInputStream("F:c.txt");
//将字节流转化为字符流
//这里fi是节点流,inputStreamReader是包装流
InputStreamReader inputStreamReader = new InputStreamReade+(fi);
//这里inputStreamReader 是节点流,bufferedReader 是包装流
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String s = null;
while ((s = bufferedReader.readLine()) != null){
System.out.println(s);
}
//包装流关闭,节点流也关闭
buffereReader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
标准字节输出流 java.io.PrintStream
public class Logger {
/*
记录日志的方法。
*/
public static void log(String msg) {
try {
// 指向一个日志文件
PrintStream out = new PrintStream(new FileOutputStream("log.txt", true));
// 改变输出方向
System.setOut(out);
// 日期当前时间
Date nowTime = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
String strTime = sdf.format(nowTime);
System.out.println(strTime + ": " + msg);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//测试工具类是否好用
Logger.log("调用了System类的gc()方法,建议启动垃圾回收");
Logger.log("调用了UserService的doSome()方法");
Logger.log("用户尝试进行登录,验证失败");
Logger.log("我非常喜欢这个记录日志的工具哦!");
}
java.io.File
- File类和四大家族没有关系,所以File类不能完成文件的读和写
- File对象代表什么?
- 文件和目录路径名的抽象表示形式
- C:/Drivers 这是一个File对象
- C:/a.txt 这也是一个File对象
- 一个File对象有可能对应的是目录,也可能是文件
- File只是一个路径名的抽象表示形式
掌握FIle类的常用方法
public static void main(String[] args) throws IOException { File f1 = new File("F:a.txt"); //判断文件是否存在 System.out.println(f1.exists()); File f2 = new File("F:d.txt"); if(!(f2.exists())){ //以文件的形式新建 f2.createNewFile(); } File f3 = new File("F:a"); if (!(f3.exists())){ //创建单个目录 f3.mkdir(); } File f4 = new File("F:a/b"); if (!(f4.exists())){ //创建多重目录 f4.mkdirs(); } }public static void main(String[] args) { File f1 = new File("F:/a/b"); //获取文件绝对路径 System.out.println("文件名" + f1.getAbsolutePath()); //获取父文件路径 System.out.println("父路径名" + f1.getParent()); //判断是否是一个目录 System.out.println(f1.isDirectory()); //判断是否是一个文件 System.out.println(f1.isFile()); //获取文件最后一次修改的时间,这个毫秒是从1970年到现在的总毫秒数 long haoMiao = f1.lastModified(); //将总毫秒转换成日期 Date time = new Date(haoMiao); ////重新定义时间格式 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS"); System.out.println(sdf.format(time)); //获取文件大小 System.out.println(f1.length()); }public static void main(String[] args) { File file = new File("E:/qq"); //返回一个File类型的数组 File[] list = file.listFiles(); for(File f : list){ System.out.println(f); } }序列化和反序列化

参加序列化和反序列化的对象,必须实现Serializable接口
- 注意:通过源代码发现,Serializable接口只是一个标志接口,接口当中什么代码也没有,只起到标识的作用
java虚拟机看到这个接口后,会为该类自定生成一个序列化版本号
凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。 这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。
/* 一次序列化多个对象呢? 可以,可以将对象放到集合当中,序列化集合。 提示: 参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口。 */ public class ObjectOutputStreamTest02 { public static void main(String[] args) throws Exception{ List<User> userList = new ArrayList<>(); userList.add(new User(1,"zhangsan")); userList.add(new User(2, "lisi")); userList.add(new User(3, "wangwu")); ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("users")); // 序列化一个集合,这个集合对象中放了很多其他对象。 oos.writeObject(userList); oos.flush(); oos.close(); } } public static void main(String[] args) throws Exception{ ObjectInputStream ois = new ObjectInputStream(new FileInputStream("users")); //Object obj = ois.readObject(); //System.out.println(obj instanceof List); List<User> userList = (List<User>)ois.readObject(); for(User user : userList){ System.out.println(user); } ois.close(); }Io和Properties联合使用
/* IO+Properties的联合应用。 非常好的一个设计理念: 以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。 将来只需要修改这个文件的内容,java代码不需要改动,不需要重新 编译,服务器也不需要重启。就可以拿到动态的信息。 类似于以上机制的这种文件被称为配置文件。 并且当配置文件中的内容格式是: key1=value key2=value 的时候,我们把这种配置文件叫做属性配置文件。 java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的。 这种以.properties结尾的文件在java中被称为:属性配置文件。 其中Properties是专门存放属性配置文件内容的一个类。 */ public class IoPropertiesTest01 { public static void main(String[] args) throws Exception{ /* Properties是一个Map集合,key和value都是String类型。 想将userinfo文件中的数据加载到Properties对象当中。 */ // 新建一个输入流对象 FileReader reader = new FileReader("chapter23/userinfo.properties"); // 新建一个Map集合 Properties pro = new Properties(); // 调用Properties对象的load方法将文件中的数据加载到Map集合中。 // 文件中的数据顺着管道加载到Map集合中,其中等号=左边做key,右边做value pro.load(reader); // 通过key来获取value呢? String username = pro.getProperty("username"); System.out.println(username); String password = pro.getProperty("password"); System.out.println(password); String data = pro.getProperty("data"); System.out.println(data); String usernamex = pro.getProperty("usernamex"); System.out.println(usernamex); } }多线程
进程和线程之间的关系
进程是一个应用程序(1个进程是一个软件),线程是一个进程中场景/执行单元,一个进程可以启动多个线程
- 对于java来说执行一个文件就相当于一个进程,JVM启动一个主线程调用main方法,同时再启动一个垃圾回收线程负责看护,回收垃圾
在Java语言中:
编写一个类,直接继承java.lang.Thread,重写run方法
public class ThreadTest01 { public static void main(String[] args) { //创建线程对象 Thread thread = new Thread(); //启动线程 thread.start(); for (int i = 0; i < 1000; i++) { System.out.println("主线程" + i); } } } class Thread extends java.lang.Thread { @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("分支线程" + i); } } }编写一个类,实现java.lang.Runnable接口,实现run方法
public class ThreadTest02 { public static void main(String[] args) { //采用匿名内部类的方式 Thread thread = new Thread(new Runnable(){ @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("分之线程" + i); } } }); thread.start(); for (int i = 0; i < 1000; i++) { System.out.println("主线程" + i); } } }线程的生命周期
怎么获取当前线程对象
怎么获取当前线程对象?
- Thread t = Thread.currentThread();
- 返回值t就是当前对象
- 获取线程对象的名字?
- String name = 线程对象.getName();
- 修改线程对象的名字
- 线程对象.setName(“线程名字”);
-
关于线程的sleep方法
static void sleep(long millis)
静态方法:Thread.sleep(1000);
- 参数是毫秒
- 作用:
- 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用
- 这行代码出现在A线程中,A线程就进入休眠
- 这行代码出现在B线程中,B线程就进入休眠
Thread.sleep()方法,可以做到间隔特定的时间,去执行特定的代码
关于线程的一道面试题
public class ThreadTest03 { public static void main(String[] args) { Thread t = new myThread(); t.setName("t"); t.start(); try { //这段代码在执行的时候等同于Thread.sleep() //sleep方法的作用是让当前线程进入休眠 //所以在这了是让主线程进入休眠 t.sleep(1000*5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("主线程"); } } class myThread extends Thread{ @Override public void run() { for (int i = 0; i < 10; i++) { //run()当中的异常不能throws,只能try catch //因为run()方法当中在父类中没有抛出任何异常,子类不能比父类抛出更多的异常 try { //这里是让分支线程进入休眠 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "线程" + i); } } }Java强行终止线程睡眠、终止线程执行
终止线程睡眠:线程对象.inteerup()
终止线程执行:在线程类里边加一个布尔标记
public class ThreadTest04 { public static void main(String[] args) { myThread01 my = new myThread01(); my.setName("t"); my.start(); try { Thread.sleep(1000*6); } catch (InterruptedException e) { e.printStackTrace(); } //终止线程睡眠 my.interrupt(); //合理终止线程执行 //my.run = false; } } class myThread01 extends Thread{ //布尔标记 boolean run = true; @Override public void run() { for (int i = 0; i < 10; i++) { if (run) { try { if(i !=0) Thread.sleep(1000*60*60); System.out.println(Thread.currentThread().getName() + "-----" + i); } catch (InterruptedException e) { e.printStackTrace(); } } else { return; } } }}synchronized
什么时候数据在多线程并发的环境下会出现安全问题
- 三个条件:多线程并发、有共享数据、共享数据有修改的行为
怎么解决线程安全问题呢?
当多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在<br /> 线程安全问题,怎么解决这个问题?<br /> 线程排队执行。(不能并发)。<br /> 用排队执行解决线程安全问题。<br /> 这种机制被称为:线程同步机制。 专业术语叫做:线程同步,实际上就是线程不能并发了,线程必须排队执行。<br /> <br /> 怎么解决线程安全问题呀?<br /> 使用“线程同步机制”。<br /> <br /> 线程同步就是线程排队了,线程排队了就会牺牲一部分效率,没办法,数据安全<br /> 第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。异步编程模型:
线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,<br /> 谁也不需要等谁,这种编程模型叫做:异步编程模型。<br /> 其实就是:多线程并发(效率较高。)<br /> 异步就是并发。 同步编程模型:<br /> 线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行<br /> 结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,<br /> 两个线程之间发生了等待关系,这就是同步编程模型。<br /> 效率较低。线程排队执行。 同步就是排队。<br />**局部变量永远不会存在线程安全问题,因为局部变量在栈中,一个线程一个栈,所以不共享**<br />**实例变量在堆中,堆只有一个,可能出现线程安全问题**<br />**堆和方法区都是多线程共享的,所以可能存在线程安全问题**
总结:
synchronized有三种写法:
第一种:同步代码块(同步代码块同一时间只能被一条线程所执行,其他线程在同步代码块外进行“阻塞”,同步代码块中的线程执行完毕后,再进行资源抢夺)
灵活
synchronized(线程共享对象){
同步代码块;
}
第二种:在实例方法上使用synchronized
表示共享对象一定是this
并且同步代码块是整个方法体。
第三种:在静态方法上使用synchronized
表示找类锁。(类锁只能在静态方法上使用synchronized)
类锁永远只有1把。
就算创建了100个对象,那类锁也只有一把。
对象锁:1个对象1把锁,100个对象100把锁。
类锁:100个对象,也可能只是1把类锁。
定时器
- 定时器的作用:间隔特定的时间,执行特定的程序
- 在Java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用
public class TimerTest { public static void main(String[] args) throws ParseException { //创建定时器对象 Timer timer = new Timer(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date firstTime = sdf.parse("2022-05-17 16:32:10"); //指定定时任务 //也可以使用匿名内部类 timer.schedule(new LongTimerTask(), firstTime, 1000*5); } } class LongTimerTask extends TimerTask{ @Override public void run() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(sdf.format(new Date())+ ":成功完成一次备份"); } }实现线程的第三种方式:实现Callable接口
- 这种方式实现的线程可以获取线程的返回值
- java.util.concurrent.Callable
public class ThreadTest05 { public static void main(String[] args) { //创建未来任务类对象 //匿名内部类 FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println("call begin"); Thread.sleep(1000); System.out.println("call over"); return 1+2; } }); //创建线程对象 Thread t = new Thread(task); t.start(); try { Integer i = task.get(); System.out.println(i); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }wait()和notify()方法
- wait和notify方法不是线程对象的方法,是普通Java对象都有的方法,
- wait方法和notify方法建立在线程同步的基础之上,因为多线程要同时操作一个仓库,有线程安全问题
- wait方法作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁
notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知:不会释放o对象上之前占有的锁, ```java public class ThreadTest16 { public static void main(String[] args) {
// 创建1个仓库对象,共享的。 List list = new ArrayList(); // 创建两个线程对象 // 生产者线程 Thread t1 = new Thread(new Producer(list)); // 消费者线程 Thread t2 = new Thread(new Consumer(list)); t1.setName("生产者线程"); t2.setName("消费者线程"); t1.start(); t2.start();} }
// 生产线程 class Producer implements Runnable { // 仓库 private List list;
public Producer(List list) {
this.list = list;
}
@Override
public void run() {
// 一直生产(使用死循环来模拟一直生产)
while(true){
// 给仓库对象list加锁。
synchronized (list){
if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。
try {
// 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序能够执行到这里说明仓库是空的,可以生产
Object obj = new Object();
list.add(obj);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
// 唤醒消费者进行消费
list.notifyAll();
}
}
}
}
// 消费线程 class Consumer implements Runnable { // 仓库 private List list;
public Consumer(List list) {
this.list = list;
}
@Override
public void run() {
// 一直消费
while(true){
synchronized (list) {
if(list.size() == 0){
try {
// 仓库已经空了。
// 消费者线程等待,释放掉list集合的锁
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 程序能够执行到此处说明仓库中有数据,进行消费。
Object obj = list.remove(0);
System.out.println(Thread.currentThread().getName() + "--->" + obj);
// 唤醒生产者生产。
list.notifyAll();
}
}
}
}
<a name="x7VQn"></a>
# Lambda表达式
<a name="r3qeF"></a>
## Lambda表达式使用前提
1. 函数式接口做方法参数传递
1. 函数式接口: 有且仅有一个抽象方法的接口
1. 可以使用注解: @FunctionalInterface 检测接口是否为函数式接口
<a name="N3FPJ"></a>
## Lambda表达式使用
1. 观察是否是函数式接口做方法参数传递 ; 如果是,考虑使用Lambda表达式
1. 调用方法传递实参时,写成匿名内部类的形式
1. 从new接口开始,到重写方法的方法名结束,选中,删除
1. 在重写方法的参数后面,方法体前面加上 ->
1. 再看看能不能根据省略规则再次删一删
<a name="kYeXq"></a>
## Lambda表达式省略规则
1. 重写方法的参数类型可以省略
1. 如果重写方法只有一个参数,那么类型也可以省略,所在的小括号也可以省略
1. 如果重写方法的方法体只有一句话,所在的方法体大括号可以省略
1. 如果重写方法的方法体只有一句话,且还是带return的,那么return,方法体的大括号,分号都可以省略
```java
public class Test02 {
public static void main(String[] args) {
ArrayList<Person> list = new ArrayList<>();
list.add(new Person("柳岩",36));
list.add(new Person("涛哥",18));
list.add(new Person("金莲",26));
Collections.sort(list, new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge()-o2.getAge();
}
});
System.out.println("=======lambda表达式=======");
Collections.sort(list, (Person o1, Person o2)-> {
return o1.getAge()-o2.getAge();
});
System.out.println("======lambda表达式简化形式");
Collections.sort(list, (o1,o2)-> o1.getAge()-o2.getAge());
System.out.println(list);
}
}
Stream流
Stream中的常用方法
forEach方法 : void forEach(Consumer<? super T> action)
/* forEach:逐一处理-->遍历 注意:forEach方法在Stream流中是一个终结方法 */ public static void main(String[] args) { Stream<String> stream = Stream.of("张无忌", "张三丰", "张翠山", "张三", "李四", "王五", "赵六", "田七"); stream.forEach(new Consumer<String>() { *//* accept方法中的s代表的就是Stream流中的每一个元素 *//* @Override public void accept(String s) { System.out.println(s); } }); System.out.println("===================="); stream.forEach(s -> System.out.println(s)); }统计个数方法: long count()
/* 1.作用:统计元素个数 2.注意:count方法,也是一个终结方法 */ public static void main(String[] args) { Stream<String> stream = Stream.of("张无忌", "张三丰", "张翠山", "张三", "李四", "王五", "赵六", "田七"); long count = stream.count(); System.out.println("count = " + count); }过滤方法 : Stream
filter(Predicate<? super T> predicate)
