admin管理员组文章数量:1122854
Java笔记整理五(Iterator接口,泛型,常见数据结构(栈,队列,数组,链表,红黑树,集合),jdk新特性,异常,多线程,Lambda表达式)
Java笔记整理五
1.1Iterator接口
Collection
接口与Map
接口主要用于存储元素,而Iterator
主要用于迭代访问(即遍历)Collection
中的元素,因此Iterator
对象也被称为迭代器。
public Iterator iterator()
: 获取集合对应的迭代器,用来遍历集合中的元素的。
迭代:即Collection集合元素的通用获取方式。在取元素之前先要判断集合中有没有元素,如果有,就把这个元素取出来,继续在判断,如果还有就再取出出来。一直把集合中的所有元素全部取出。这种取出方式专业术语称为迭代。
public E next()
:返回迭代的下一个元素。public boolean hasNext()
:如果仍有元素可以迭代,则返回 true。
迭代器的使用步骤(重点):
1. 使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态)
2. 使用Iterator接口中的方法hasNext判断还有没有下一个元素
3. 使用Iterator接口中的方法next取出集合中的下一个元素
1.使用集合中的方法iterator()获取迭代器的实现类对象,使用Iterator接口接收(多态) 注意: Iterator接口也是有泛型的,迭代器的泛型跟着集合走,集合是什么泛型,迭代器就是什么泛型 多态 接口
实现类对象 Iterator it = coll.iterator();
发现使用迭代器取出集合中元素的代码,是一个重复的过程所以我们可以使用循环优化 不知道集合中有多少元素,使用while循环
循环结束的条件,hasNext方法返回falsewhile(it.hasNext()){String e = it.next();System.out.println(e);}
迭代器的实现原理
coll.iterator(); 获取迭代器实现类对象,并且吧指针(索引)指向集合-1索引。
it.hasNext() 判断有没有下一个元素。it.next(); 1.取出下一个元素2.把指针向后移动一位
增强for循环
底层使用迭代器,简化书写
所有单列集合都可使用
格式:for(元素的数据类型 变量 : Collection集合or数组){ //写操作代码
}int[] arr = {3,5,6,87};//使用增强for遍历数组for(int a : arr){//a代表数组中的每个元素System.out.println(a);}
2.泛型
一种未知的数据类型,是一种变量用来接收数据类型。
E e:Element 元素 未知的数据类型T t : Type 类型
ArrayList 集合在定义的时候,不知道会存储什么数据,所以类型使用泛型。
创建集合对象的时候,就会确定泛型的数据类型
ArrayList <String> list = new ArrayList <String>();
会把数据类型作为参数传递,赋值给泛型E。
使用泛型的好处:
创建集合对象,不使用泛型
- 好处:默认类型是object类型,可以存储任意类型的数据。
- 弊端:不安全,会引发异常
创建集合对象,使用泛型
- 好处:
- 避免了类型转换的麻烦,存储是什么类型,取出是什么类型。
- 把运行期的异常(代码运行后抛出的异常),提升到编译期(写代码时会报错)
- 弊端:泛型是什么类型,就只能存储什么类型的数据。
泛型的定义和使用
定义和使用含有泛型的类
定义格式:
修饰符 class 类名<代表泛型的变量> { }
例如,API中的ArrayList集合:
class ArrayList<E>{ public boolean add(E e){ }public E get(int index){ }....
}
使用泛型: 即什么时候确定泛型。
在创建对象的时候确定泛型
例如,ArrayList<String> list = new ArrayList<String>();
此时,变量E的值就是String类型,那么我们的类型就可以理解为:
class ArrayList<String>{ public boolean add(String e){ }public String get(int index){ }...
}
定义和使用含有泛型的方法
定义含有泛型的方法:泛型定义在方法的修饰符和返回值之间+。
格式:
修饰符 <代表泛型的变量> 返回值类型 方法名(参数){ }
-
含有泛型的方法,在调用方法的时候确定泛型的数据类型。传递什么类型的参数,泛型就是什么类型。
-
含有泛型的静态方法:
推荐使用类名.方法名调用
含有泛型的接口
定义格式:
修饰符 interface接口名<代表泛型的变量> { }
例如,
public interface MyGenericInterface<E>{public abstract void add(E e);public abstract E getE();
}
使用格式: 1、定义实现类实现接口确定泛型的类型 2、始终不确定泛型的类型,直到创建对象时,确定泛型的类型
泛型的通配符
当使用泛型类或者接口时,传递的数据中,泛型类型不确定,可以通过通配符<?>表示。但是一旦使用泛型的通配符后,只能使用Object类中的共性方法,集合中元素自身方法无法使用。
此时只能接收数据,不能往该集合中存储数据。
不能创建对象使用,只能作为方法的参数使用
泛型没有继承概念
举个例子大家理解使用即可:
public static void main(String[] args) {Collection<Intger> list1 = new ArrayList<Integer>();getElement(list1);Collection<String> list2 = new ArrayList<String>();getElement(list2);
}
public static void getElement(Collection<?> coll){}
//?代表可以接收任意类型
通配符高级使用----受限泛型
泛型的上限:
- 格式:
类型名称 <? extends 类 > 对象名称
- 意义:
只能接收该类型及其子类
泛型的下限:
- 格式:
类型名称 <? super 类 > 对象名称
- 意义:
只能接收该类型及其父类型
比如:现已知Object类,String 类,Number类,Integer类,其中Number是Integer的父类
public static void main(String[] args) {Collection<Integer> list1 = new ArrayList<Integer>();Collection<String> list2 = new ArrayList<String>();Collection<Number> list3 = new ArrayList<Number>();Collection<Object> list4 = new ArrayList<Object>();getElement(list1);getElement(list2);//报错getElement(list3);getElement(list4);//报错getElement2(list1);//报错getElement2(list2);//报错getElement2(list3);getElement2(list4);}
// 泛型的上限:此时的泛型?,必须是Number类型或者Number类型的子类
public static void getElement1(Collection<? extends Number> coll){}
// 泛型的下限:此时的泛型?,必须是Number类型或者Number类型的父类
public static void getElement2(Collection<? super Number> coll){}
常见数据结构
常用结构有:栈、队列、数组、链表和红黑树。
栈
- 栈:stack,又称堆栈,它是运算受限的线性表,其限制是仅允许在标的一端进行插入和删除操作,不允许在其他任何位置进行添加、查找、删除等操作。
特点
先进后出 (即,存进去的元素,要在后它后面的元素依次取出后,才能取出该元素)。
-
压栈:就是存元素。即,把元素存储到栈的顶端位置,栈中已有元素依次向栈底方向移动一个位置。
-
弹栈:就是取元素。即,把栈的顶端位置元素取出,栈中已有元素依次向栈顶方向移动一个位置。
-
栈的入口、出口的都是栈的顶端位置。
队列
- 队列:queue,简称队,它同堆栈一样,也是一种运算受限的线性表,其限制是仅允许在表的一端进行插入,而在表的另一端进行删除。
简单的说,采用该结构的集合,对元素的存取有如下的特点:
- 先进先出(即,存进去的元素,要在后它前面的元素依次取出后,才能取出该元素)。例如,小火车过山洞,车头先进去,车尾后进去;车头先出来,车尾后出来。
- 队列的入口、出口各占一侧。例如,下图中的左侧为入口,右侧为出口。
数组
- 数组:Array,是有序的元素序列,数组是在内存中开辟一段连续的空间,并在此空间存放元素。
特点:
查找元素快:通过索引,可以快速访问指定位置的元素
增删元素慢
- 指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
- **指定索引位置删除元素:**需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。
链表
- 链表:linked list,由一系列结点node(链表中每一个元素称为结点)组成,结点可以在运行时i动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。我们常说的链表结构有单向链表与双向链表
单向链表:
-
查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素
-
增删元素快:
-
增加元素:只需要修改连接下个元素的地址即可。
-
删除元素:只需要修改连接下个元素的地址即可。
-
红黑树
- 二叉树:binary tree ,是每个结点不超过2的有序树(tree) 。
每个结点上都最多只能有两个子结点。
二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。
红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。
红黑树的约束:
节点可以是红色的或者黑色的
根节点是黑色的
叶子节点(特指空节点)是黑色的
每个红色节点的子节点都是黑色的
任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同
红黑树的特点:
速度特别快,趋近平衡树,查找叶子元素最少和最多次数不多于二倍
List集合
java.util.List
接口继承自Collection
接口,是单列集合的一个重要分支,习惯性地会将实现了List
接口的对象称为List集合。
1.在List集合中允许出现重复的元素,所有的元素是以一种线性方式进行存储的。
3.有索引,在程序中可以通过索引来访问集合中的指定元素。
2.List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
public void add(int index, E element)
: 将指定的元素,添加到该集合中的指定位置上。public E get(int index)
:返回集合中指定位置的元素。public E remove(int index)
: 移除列表中指定位置的元素, 返回的是被移除的元素。public E set(int index, E element)
:用指定元素替换集合中指定位置的元素,返回值的更新前的元素。
使用迭代器遍历list集合:
Iterator<String> it=list.iterator();while(it.hasNext()){String s=it.next();System.out.println(s);}
操作索引,注意防止索引越界异常;
ArrayList集合
java.util.ArrayList
集合数据存储的结构是数组结构。元素增删慢,查找快,由于日常开发中使用最多的功能为查询数据、遍历数据,所以ArrayList
是最常用的集合。
LinkedList集合
java.util.LinkedList
集合数据存储的结构是链表结构。方便元素添加、删除的集合。
特点:
1.底层是一个链表结构,查询快,增删满
2.里边包含了大量首位元素操作的方法。
public void addFirst(E e)
:将指定元素插入此列表的开头。public void addLast(E e)
:将指定元素添加到此列表的结尾。public E getFirst()
:返回此列表的第一个元素。public E getLast()
:返回此列表的最后一个元素。public E removeFirst()
:移除并返回此列表的第一个元素。public E removeLast()
:移除并返回此列表的最后一个元素。public E pop()
:从此列表所表示的堆栈处弹出一个元素。public void push(E e)
:将元素推入此列表所表示的堆栈。public boolean isEmpty()
:如果列表不包含元素,则返回true。
LinkedList是List的子类,List中的方法LinkedList都是可以使用
##set接口
java.util.Set
接口和java.util.List
接口一样,同样继承自Collection
接口
特点
1.不允许存储重复元素
2.无索引,没有带索引方法,不能使用for循环遍历集合
可使用迭代器 ,和foreach遍历
Set
集合有多个子类,
java.util.HashSet
extendjava.util.LinkedHashSet
HashSet集合介绍
特点:
1.允许存储重复的元素;
2.没有索引,没有带索引的方法,不可使用普通for循环遍历
3.是一个无序的集合,存储元素和取出元素的顺序不一致
4.底层是一个哈希表结构(查询速度非常快)
有良好的存取和查找性能。保证元素唯一性的方式依赖于:hashCode
与equals
方法。
哈希值: 是一个十进制整数,由系统随机给出(就是对象地址值,是一个逻辑地址,模拟出来得到地址,不是数据实际存储的物理地址)
int hasCode():返回对象的哈希码值
哈希表结构
jdk1.8之后
哈希表是由数组+链表或数组+红黑树
数组结构:把元素进行分组,相同哈希值为一组,链表/红黑树把相同哈希值的元素连接到一起
如果链表的长度超过八位,就会把链表转换为红黑树,提高查询速度。
HasSet存储不相同元素原理!
HasSet存储自定义类型元素
给HashSet中存放自定义类型元素时,需要重写对象中的hashCode和equals方法,建立自己的比较方式,才能保证HashSet集合中的对象唯一
Linked HashSet集合介绍
特点:底层是一个哈希表(数组+链表/红黑树)+链表:多一条链表(记录元素存储顺序),保证元素有序
可变参数
在JDK1.5之后,如果我们定义一个方法需要接受多个参数,并且多个参数类型一致,我们可以对其简化成如下格式:
修饰符 返回值类型 方法名(参数类型... 形参名){ }
其实这个书写完全等价与
修饰符 返回值类型 方法名(参数类型[] 形参名){ }
使用前提:
当方法的参数列表数据类型已经确定,但是参数的个数不确定,可以使用可变参数。
可变参数原理:
可变擦书底层是一个数组,跟据传递参数的个数不同,创建不同长度的数组,来存储这些参数
传递的参数的个数可以是0个或者N个
int sum = 0;for (int a : arr) {sum += a;}return sum;}public static void main(String[] args) {// 求 这几个元素和 6 7 2 12 2121int sum2 = getSum(6, 7, 2, 12, 2121);System.out.println(sum2);}
可变参数注意事项:
1.一个方法的参数列表只能由一个可变参数
2.如果方法的参数由多个,那么可变参数必须写在参数列表的末尾
Collections
java.utils.Collections
是集合工具类,用来对集合进行操作。部分方法如下:
public static <T> boolean addAll(Collection<T> c, T... elements)
:往集合中添加一些元素。public static void shuffle(List<?> list) 打乱顺序
:打乱集合顺序。public static <T> void sort(List<T> list)
:将集合中元素按照默认规则排序。public static <T> void sort(List<T> list,Comparator<? super T> )
:将集合中元素按照指定规则排序。
public class CollectionsDemo {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<Integer>();//原来写法//list.add(12);//list.add(14);//list.add(15);//list.add(1000);//采用工具类 完成 往集合中添加元素 Collections.addAll(list, 5, 222, 1,2);System.out.println(list);//排序方法 Collections.sort(list);System.out.println(list);}
}
结果:
[5, 222, 1, 2]
[1, 2, 5, 222]
void sort(List<T> list):
被排序的集合里边存储的元素,必须实comparable,重写就扣中的方法compareTo定义的排序规则。
Comparable接口
的排序规则: this-o(参数) 升序排序 o-this 降序排序
void sort(List list,Comparator<? super T> )
Comparator和compara区别:
comparable:自己(this)和别人(参数)比较,自己需要实现compara接口,重写比较规则comparaTo方法。
Comparator:相当于找一个第三方裁判,比较两个。
Collections.sort(list,newComparator<String>() {@Overridepublic int compare(String o1, String o2) {return o2.charAt(0) - o1.charAt(0);}});
public int compare(String o1, String o2):比较其两个参数的顺序。
两个对象比较的结果有三种:大于,等于,小于。
如果要按照升序排序,
则o1 小于o2,返回(负数),相等返回0,01大于02返回(正数)
如果要按照降序排序
则o1 小于o2,返回(正数),相等返回0,01大于02返回(负数)
简述Comparable和Comparator两个接口的区别。
Comparable:强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo方法被称为它的自然比较方法。只能在类中实现compareTo()一次,不能经常修改类的代码实现自己想要的排序。实现此接口的对象列表(和数组)可以通过Collections.sort(和Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无需指定比较器。
Comparator强行对某个对象进行整体排序。可以将Comparator 传递给sort方法(如Collections.sort或 Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
Map集合
Collection
中的集合,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。Map
中的集合,元素是成对存在的(理解为夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。Collection
中的集合称为单列集合,Map
中的集合称为双列集合。- 需要注意的是,
Map
中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
java.util.Map<K,V>集合 Map集合的特点:
1.Map集合是一个双列集合,一个元素包含两个值。一个称为key,一个称为Value
2.Map集合中的元素,Key和Value的数据类型可以相同,亦可以不同。
3.Map集合中的元素,Key是不允许重复的,Value是可以重复的。
4.Map集合中的元素,Key和value是一一对应。
java.util.Map<K,V> implements Map<K,V>接口
####HashMap集合的特点:
1.HashMap集合底层是哈希表,查询的速度特点快
JDK1.8之前:数组+单向链表
JDK1.8之后:数据+单向链表/红黑树(链表的长度超过8):提高查询的速度
2.HashMap集合是一个无序的集合,存储元素和取出的速度又可能不一致
java.util.LinkedHashMap<K,V> extends HashMap<K,V>集合
LinkedHashMap的特点:
1.LinkedHashMap集合底层是哈希表+链表(可预知的迭代顺序)
2.LinkedHashMap集合一个有序的集合,存储元素和取出元素的顺序是一致的。
- HashMap<K,V>:存储数据采用的哈希表结构,元素的存取顺序不能保证一致。由于要保证键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
- LinkedHashMap<K,V>:HashMap下有个子类LinkedHashMap,存储数据采用的哈希表结构+链表结构。通过链表结构可以保证元素的存取顺序一致;通过哈希表结构可以保证的键的唯一、不重复,需要重写键的hashCode()方法、equals()方法。
tips:Map接口中的集合都有两个泛型变量<K,V>,在使用时,要为两个泛型变量赋予数据类型。两个泛型变量<K,V>的数据类型可以相同,也可以不同。
Map接口中常用的如下:
-
public V put(K key, V value)
: 把指定的键与指定的值添加到Map集合中。存储键值对的时候,key不重复,返回值V是null。key重复,会使用新的value替换重复的value,返回被替换的value. -
public V remove(Object key)
: 把指定的键 所对应的键值对元素 在Map集合中删除,返回被删除元素的值。k 存在v返回被删除的值。
key不存在,v返回null。 -
public V get(Object key)
根据指定的键,在Map集合中获取对应的值。key存在返回对应的value值,key不存在,返回null, -
boolean containsKey(Object key)
判断集合中是否包含指定的键。
包含返回true,不包含返回false -
public Set<K> keySet()
: 获取Map集合中所有的键,存储到Set集合中。 -
public Set<Map.Entry<K,V>> entrySet()
: 获取到Map集合中所有的键值对对象的集合(Set集合)。
集合第一种遍历方式,通过键找值的方式
实现步骤:
public Set keySet()`: 实现步骤:
1.使用Map集合中的方法keyset(),把Map集合所有的Key取出来,存储到一个Set集合中。
2.遍历Set集合,获取Map集合中的每一个Key
3.通过Map集合中的方法get(key),找到Value
Set<String> keys = map.keySet();// 遍历键集 得到 每一个键for (String key : keys) {//key 就是键//获取对应值String value = map.get(key);System.out.println(key+"的CP是:"+value);}
Map.Entry<K,V>:在Map接口中有一个内部接口Entry
作用:当Map集合一创建,那么就会在Map集合中创建一个Entry对象,用来记录键与值(键值对对象,键与值的映射关系)
Set<Map.Entry<K,V>> entrySet(),把Map集合内部的多个Entry对象取出来,存储到一个Set集合中
Map集合的第二种遍历方式,使用Entry对象遍历。
Set<Map.Entry<K,V>>entrySet(),返回此映射中包含的映射关系的Set视图。实现步骤:
- 使用Map集合中的方法entrySet(),把Map集合中多个Entry对象取出来,存储到一个Set集合中。
- 遍历Set集合,获取每一个Entry对象,
- 使用Entry对象中的方法getKey()和getValue()获取键与值
Set<Entry<String,String>> entrySet = map.entrySet();
// 遍历得到每一个entry对象
for (Entry<String, String> entry : entrySet) {
// 解析
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key+" "+value);
}
java.util.LinkedHashMap<K,V> extends HashMap<K,V>
LinkedHashMap
Map接口的哈希表和链表列表实现,具有可预知迭代顺序。
底层原理:
哈希表—+链表(记录元素顺序)
key不允许重复,无序,存取顺序一致。
java.util.Hashtable<K,V> 集合implements HashMap<K,V>接口
Hashtable:底层也是哈希表,是一个线程安全的集合,是单线程的集合,速度慢
HashMap:底层是一个哈希表,是一个线程不安全的集合:
,是多线程的集合,速度快。
Hashtable和Vetor集合一样,在Jdk1.2以后被取代,Properties集合是一个唯一和IO流相结合的集合,不能存储null值
JDK新特性:
List接口,Set接口,Map接口:里边增加了一个静态方法of,可以给集合一次性添加多个元素。
StaticListof(E…elements)
使用前提:
当集合中存储的元素的个数已经确定了,不在改变时使用
注意:
1.of方法只是用于LIst接口,Set接口,Map接口,不适用于接口实现类。
2.of方法的返回值时一盒不能改变的集合,集合不能在使用add,put方法添加元素,会抛出异常。
3.Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常-。
异常
程序执行中,出现非正常情况,导致JVM退出。,产生异常时,创建异常对象并抛出一个异常对象,Java处理方式是中断处理。
java.lang.Throwable
,其下有两个子类:java.lang.Error
与java.lang.Exception
Error:严重错误Error,无法通过处理的错误,只能事先避免。
- Exception:表示异常,异常产生后程序员可以通过代码的方式纠正,使程序继续运行,是必须要处理的。
- java.lang.Throwable:所有异常超类
- Exception:编译器异常,写代码时出现
- RuntimeException:运行期异常,运行过程中出现
- Error:错误,只能修改代码处理
异常处理流程
异常处理关键字:
1.抛出异常throw,用来抛出一个指定的异常对象
格式: throw new 异常类名(参数);
注意:
1.必须在方法内
2.new 对象必须在exception及其子类对象
3.抛出指定异常必须处理。
若创建的是RuntimeException及其子类对象,不处理,默认交给JVM处理(打印中断)
若创建编译异常则必须处理,要么throws或者try catch
2.Objects非空判断
public static <T> T requireNonNull(T obj) {if (obj == null)throw new NullPointerException();return obj;
}
异常处理的第一种方式
交给别人处理
1.throws
运用于方法声明之上,用于表示当前方法不处理异常,而是提醒该方法的调用者来处理异常(抛出异常).最终交给JVM处理 --中断处理
修饰符 返回值类型 方法名(参数) throws 异常类名1,异常类名2…{ }
hrows用于进行异常类的声明,若该方法可能有多种异常情况产生,那么在throws后面可以写多个异常类,用逗号隔开。
注意:
1.必须写在方法声明处,
2.声明的异常必须是exception及其子类
3.方法内部如果抛出了多个异常对象,那么后边也必须声明多个异常,若有子父类关系,那么直接声明父类异常
4.调用一个声明抛出异常的方法,我们就必须处理声明的异常,要么继续使用throws声明抛出,交给方法调用者处理,最终交给JVM
要么 try catch 自己处理
2.捕获异常try…catch
- 该方法不处理,而是声明抛出,由该方法的调用者来处理(throws)。
- 在方法中使用try-catch的语句块来处理异常。
异常处理的第二种方式
自己处理异常
- 捕获异常:Java中对异常有针对性的语句进行捕获,可以对出现的异常进行指定方式的处理。
捕获异常语法如下:
try{编写可能会出现异常的代码
}catch(异常类型 e:定义一个异常变量,用来接收try抛出的异常对象){处理异常的代码//记录日志/打印异常信息/继续抛出异常
}
**try:**该代码块中编写可能产生异常的代码。
**catch:**用来进行某种异常的捕获,实现对捕获到的异常进行处理。
注意:try和catch都不能单独使用,必须连用。
1.如果try可能抛出多个异常对象,那么就可以使用多个catch
来处理这些异常对象
2.若try
中产生了异常,那么就会执行catch中异常处理逻辑,执行完则继续执行try catch之后的代码
,若没有产生异常,那么就不i会执行catch中的处理逻辑,执行完try中的代码,继续执行try catch 之后的代码
如何获取异常信息:
Throwable类中定义了一些查看方法:
-
public String getMessage()
:获取异常的描述信息,原因(提示给用户的时候,就提示错误原因。简短信息 -
public String toString()
:获取异常的类型和异常描述信息(不用)。详细信息 -
public void printStackTrace()
:打印异常的跟踪栈信息并输出到控制台。默认调用此方法,信息最全面
包含了异常的类型,异常的原因,还包括异常出现的位置,在开发和调试阶段,都得使用printStackTrace。
finally 代码块
finally:有一些特定的代码无论异常是否发生,都需要执行。
try…catch…finally:自身需要处理异常,最终还得关闭资源。
1不能单独使用
2一般用于资源释放,无论程序是否出现异常,最后都要释放资源
当只有在try或者catch中调用退出JVM的相关方法,此时finally才不会执行,否则finally永远会执行。
如果finally中有return语句,永远返回finally中的结果
异常注意事项
-
多个异常使用捕获又该如何处理呢?
- 多个异常分别处理。
- 多个异常一次捕获,多次处理。
- 多个异常一次捕获一次处理。
try{编写可能会出现异常的代码 }catch(异常类型A e){ 当try中出现A类型异常,就用该catch来捕获.处理异常的代码//记录日志/打印异常信息/继续抛出异常 }catch(异常类型B e){ 当try中出现B类型异常,就用该catch来捕获.处理异常的代码//记录日志/打印异常信息/继续抛出异常 }
一个try多个catch注意事项:
-
catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的catch处理。
-
运行时异常被抛出可以不处理。即不捕获也不声明抛出。
-
如果finally有return语句,永远返回finally中的结果,避免该情况.
- 如果父类抛出了多个异常,子类重写父类方法时,抛出和父类相同的异常或者是父类异常的子类或者不抛出异常。
- 父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该异常,只能捕获处理,不能声明抛出
父类异常什么样,子类异常就什么样
自定义异常
根据自己业务的异常情况来定义异常类.
异常类如何定义:
- 自定义一个编译期异常: 自定义类 并继承于
java.lang.Exception
。
必须处理,要么throws,要么try catch - 自定义一个运行时期的异常类:自定义类 并继承于
java.lang.RuntimeException
。
抛出运行期异常,无需处理,交给JVM处理,中断处理
格式:public class XXXException extends Exception |RuntimeException{
添加一个空参构造方法
添加一个带异常信息的构造方法
}
// 业务逻辑异常
public class RegisterException extends Exception {/*** 空参构造*/public RegisterException() {}/**** @param message 表示异常提示*/public RegisterException(String message) {super(message);}
}
多线程
-
并发:指两个或多个事件在同一个时间段内发生。
-
并行:指两个或多个事件在同一时刻发生(同时发生)。
-
进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创建、运行到消亡的过程。
-
线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。
简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程
线程调度:
-
分时调度
所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
-
抢占式调度
优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。
创建多线程大的第一种方式:
java.lang.Thread
类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
创建Thread类的子类,通过继承Thread类来创建并启动多线程的步骤如下:
1.创建一个Thread类的子类
2.在Thread类的子类中重写Thread类中的run()方法,设置线程任务。
3.创建Thread类的子类对象
4.调用Thread类中的方法,start方法,开启新的线程,执行run方法
public class Demo01 {public static void main(String[] args) {//创建自定义线程对象MyThread mt = new MyThread("新的线程!");//开启新线程mt.start();//在主方法中执行for循环for (int i = 0; i < 10; i++) {System.out.println("main线程!"+i);}}
}
自定义线程类:
public class MyThread extends Thread {//定义指定线程名称的构造方法public MyThread(String name) {//调用父类的String参数的构造方法,指定线程的名称super(name);}/*** 重写run方法,完成该线程执行的逻辑*/@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(getName()+":正在执行!"+i);}}
}
Threa类中常用方法:
获取线程名称
public String getName():获取当前线程名称
public static Thread currentThread():返回当前正在执行线程对象的引用。
设置线程名称
1.使用Threa类中的setName(名字)方法,使之与参数name相同。
2.创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类,让父类Threa给子线程起义和名字Thread(String name)分配新的Thread对象。
public static void sleep (long millis):使正在执行的线程以指定毫秒数暂停。
创建多线程大的第二种方式:
实现Runnable接口的实现类,该类实现run方法,分类该类的实例,在创建Thread时作为一个参数来传递并启动。
实现步骤:
1.创建一个runnable接口的实现类
2.在实现类中重写run方法
3.创建一个runnable接口的实现类对象
4.创建Thread类对象,构造方法中传递runnable接口的实现类对象
5.调用Thread类中的start方法,开启新的线程执行run方法
两种方法的区别, 实现runnable接口创建多线程程序的好处:
1.避免的单继承的局限性,一个类只能继承一个类,实现runnable接口还可以继承其他类。
2.增强的程序的扩展性,降低了程序的耦合性。实现接口方式,把设置线程任务和开启新线程进行了分离(解耦),
实现类中重写run方法,
创建Thread类对象,调用start放啊,开启新的线程。
尽量使用实现runnable接口的方式
匿名内部类实现线程的创建
匿名:没有名字
内部类:写在其他类内部
匿名内部类,简化代码
把子类继承父类,创建子类对象合一步完成
把实现实现类接口,重写接口的方法,创建实现类对象合一步完成
匿名内部类实现类对象,没有名字
格式: new 父类(){ 重写父类接口方法 };
线程安全问题:
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。有三种方式完成同步操作:
- 同步代码块。
- 同步方法。
- 锁机制。
同步代码块: synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
格式:
synchronized(同步锁){ 需要同步操作的代码 }
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
- 锁对象 可以是任意类型。
- 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着
(BLOCKED)。
public class Ticket implements Runnable{
private int ticket = 100;
Object lock = new Object();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
synchronized (lock) {
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐);
}
}}}}
同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。
使用步骤:
1.把访问共享数据的代码抽取出来,放到一个方法中。
2.在方法中添加synchronized修饰符
格式:
public synchronized void method(){
可能会产生线程安全问题的代码
}
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
使用同步方法代码:
public class Ticket implements Runnable{
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
sellTicket();
}
}
/*
* 锁对象 是 谁调用这个方法 就是谁
* 隐含 锁对象 就是 this
*
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐);
}
}
}
静态同步方法:锁对象是谁?不能是this
this是创建对象之后产生的,静态方法优先于对象。
静态方法的锁对象是本类的classshuxing—class文件对象
Lock锁
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,
同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
public void lock() :加同步锁。
public void unlock() :释放同步锁。
使用步骤:
1.在成员位置创建一个ReentrantLock implements Lock 接口
2.在可能出现安全问题的代码前调用Lock接口的中的方法lock获取锁
3.在可能出现安全问题的代码前调用Lock接口的中的方法unlock释放锁
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while(true){
lock.lock();
if(ticket>0){//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在卖:"+ticket‐‐);
}
lock.unlock();
}
}
}
等待唤醒机制
线程状态
线程状态 导致状态发生条件
- NEW(新建) 线程刚被创建,但是并未启动。还没调用start方法。
- Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
- Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
- Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
- Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep .Object.wait。
- Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
Timed Waiting(计时等待)
们在run方法中添加了sleep语句,这样就
强制当前正在执行的线程休眠(暂停执行),以“减慢线程”。
其实当我们调用了sleep方法之后,当前执行的线程就进入到“休眠状态”,其实就是所谓的Timed Waiting(计时等待)
- 进入 TIMED_WAITING 状态的一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协
作关系。 - 为了让其他线程有机会执行,可以将Thread.sleep()的调用放线程run()之内。这样才能保证该线程执行过程
中会睡眠 - sleep与锁无关,线程睡眠到期自动苏醒,并返回到Runnable(可运行)状态。
小提示:sleep()中指定的时间是线程不会运行的最短时间。因此,sleep()方法不能保证该线程睡眠到期后就
开始立刻执行。
3 BLOCKED(锁阻塞)一个正在阻塞等待一个监视器锁(锁对象)的线程处于这一状态。
线程A与线程B代码中使用同一锁,如果线程A获取到锁,线程A进入到Runnable状态,那么线程B就进入到Blocked锁阻塞状态。
4 Waiting(无限等待):一个正在无限期等待另一个线程执行一个特别的(唤醒)动作的线程处于这一状态。
一个调用了某个对象的 Object.wait 方法的线程会等待另一个线程调用此对象的
Object.notify()
方法 或 Object.notifyAll()方法。
void wait()
:在其他线程调用此对象的notify()方法或notifyAll方法前,导致当前方法等待。
void notify()
:唤醒此对象监视器上等待的单个的线程。会继续执行wait方法之后的代码。如果有多个等待线程,随机唤醒一个。
void notifyAll()
:唤醒所有等待线程
注意:
线程代码必须用同步代码块包裹起来,保证等待和唤醒只能有一个在执行。
同步使用的锁对象必须保证唯一 只有锁对象才能调用wait 和notify 方法
public class WaitingTest {
public static Object obj = new Object();
public static void main(String[] args) {
// 演示waiting
new Thread(new Runnable() {
@Override
public void run() {
while (true){
synchronized (obj){
try {
System.out.println( Thread.currentThread().getName() +"=== 获取到锁对象,调用wait方法,进入waiting状态,释放锁对象");
obj.wait(); //无限等待
//obj.wait(5000); //计时等待, 5秒 时间到,自动醒来
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() + "=== 从waiting状
态醒来,获取到锁对象,继续执行了");
}
}
}
},"等待线程").start();
new Thread(new Runnable() {
@Override
public void run() {
// while (true){ //每隔3秒 唤醒一次
try {
System.out.println( Thread.currentThread().getName() +"‐‐‐‐‐ 等待3秒钟");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj){
System.out.println( Thread.currentThread().getName() +"‐‐‐‐‐ 获取到锁对
象,调用notify方法,释放锁对象");
obj.notify();
}
}
// }
},"唤醒线程").start();
}
}
进入Timed Waiting(计时等待)两种方式:
1.使用sleep方法,在毫秒值结束之后,进入runnab/block状态
2.使用wait方法,在毫秒值之后,若还没有被notify唤醒,就会自动醒来,线程进入到runnable/blocked状态。
线程间通信
多个线程处理同一个资源,但是处理的动作(线程的任务)却不相同。
等待与唤醒机制,线程间通信
注意:
wait方法与notify方法必须要由同一个锁对象调用
wait和notify方法属于Object类的方法,锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。
wait和notify方法必须在同步代码块或同步函数中使用,因为 通过锁对象调用这两个方法
1.3 生产者与消费者问题
等待唤醒机制其实就是经典的“生产者与消费者”的问题。
就拿生产包子消费包子来说等待唤醒机制如何有效利用资源:
包子铺线程生产包子,吃货线程消费包子。
当包子没有时(包子状态为false),吃货线程等待,
包子铺线程生产包子(即包子状态为true),并通知吃货线程(解除吃货的等待状态)
,因为已经有包子了,那么包子铺线程进入等待状态。
接下来,吃货线程能否进一步执行则取决于锁的获取情况。
如果吃货获取到锁,那么就执行吃包子动作,包子吃完(包子状态为false),
并通知包子铺线程(解除包子铺的等待状态),吃货线程进入等待。
包子铺线程能否进一步执行则取决于锁的获取情况。
代码演示:
包子资源类:
public class BaoZi {String pier ;String xianer ;boolean flag = false ;//包子资源 是否存在 包子资源状态
}
吃货线程类:
public class ChiHuo extends Thread{private BaoZi bz;public ChiHuo(String name,BaoZi bz){super(name);this.bz = bz;}@Overridepublic void run() {while(true){synchronized (bz){if(bz.flag == false){//没包子try {bz.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("吃货正在吃"+bz.pier+bz.xianer+"包子");bz.flag = false;bz.notify();}}}
}
包子铺线程类:
public class BaoZiPu extends Thread {private BaoZi bz;public BaoZiPu(String name,BaoZi bz){super(name);this.bz = bz;}@Overridepublic void run() {int count = 0;//造包子while(true){//同步synchronized (bz){if(bz.flag == true){//包子资源 存在try {bz.wait();} catch (InterruptedException e) {e.printStackTrace();}}// 没有包子 造包子System.out.println("包子铺开始做包子");if(count%2 == 0){// 冰皮 五仁bz.pier = "冰皮";bz.xianer = "五仁";}else{// 薄皮 牛肉大葱bz.pier = "薄皮";bz.xianer = "牛肉大葱";}count++;bz.flag=true;System.out.println("包子造好了:"+bz.pier+bz.xianer);System.out.println("吃货来吃吧");//唤醒等待线程 (吃货)bz.notify();}}}
}
测试类:
public class Demo {public static void main(String[] args) {//等待唤醒案例BaoZi bz = new BaoZi();ChiHuo ch = new ChiHuo("吃货",bz);BaoZiPu bzp = new BaoZiPu("包子铺",bz);ch.start();bzp.start();}
}
执行效果:
包子铺开始做包子
包子造好了:冰皮五仁
吃货来吃吧
吃货正在吃冰皮五仁包子
包子铺开始做包子
包子造好了:薄皮牛肉大葱
吃货来吃吧
吃货正在吃薄皮牛肉大葱包子
包子铺开始做包子
包子造好了:冰皮五仁
吃货来吃吧
吃货正在吃冰皮五仁包子
线程池
- **线程池:**其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
- 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
- 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
JDK1.5之后提供java.util.concurrent.Executor
:线程池工厂类,用来生成线程池
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量),创建一个可重用固定线程数的线程池。返回的线程池ExecutorService 实现类对象,我们可以用ExecutorService接口接收
java.util.concurrent.ExecutorService
:线程池接口,用来从线程池中获取线程,调用start方法,执行线程任务。
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行,提交一个runnable任务用于执行。-
Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
关闭/销毁线程池方法:
void shutdown():
使用线程池中线程的步骤: 线程池会一直开启,使用完了会自动把线程归还给线程池,线程可以继续使用。
- 使用线程池工厂类Executor中提供的方法newFixedThreadPool(int nThreads)创建一个指定线程数量的线程池对象。
- 创建一个类实现Runnable接口,重写run方法,设置线程任务。
- 调用ExecutorService中的方法submit(Runnable task)传递线程任务(实现类),提交Runnable接口子类对象,开启线程,执行run方法。
- 若不用了,关闭线程池(一般不做)。
线程池测试类代码实现:
public class ThreadPoolDemo {public static void main(String[] args) {// 创建线程池对象ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象// 创建Runnable实例对象MyRunnable r = new MyRunnable();//自己创建线程对象的方式// Thread t = new Thread(r);// t.start(); ---> 调用MyRunnable中的run()// 从线程池中获取线程对象,然后调用MyRunnable中的run()service.submit(r);// 再获取个线程对象,调用MyRunnable中的run()service.submit(r);service.submit(r);// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。// 将使用完的线程又归还到了线程池中// 关闭线程池//service.shutdown();}
}
Lambda表达式
##函数式编程思想
函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。
面向对象的思想:
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.
函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
冗余的Runnable代码
传统写法
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable
接口来定义任务内容,并使用java.lang.Thread
类来启动该线程。代码如下:
public class Demo01Runnable {public static void main(String[] args) {// 匿名内部类Runnable task = new Runnable() {@Overridepublic void run() { // 覆盖重写抽象方法System.out.println("多线程任务执行!");}};new Thread(task).start(); // 启动线程}
}
Lambda表达式 实现多线程
public class Demo02LambdaRunnable {public static void main(String[] args) {new Thread(() -> System.out.println("多线程任务执行!")).start(); // 启动线程}
}
Lambda标准格式
Lambda省去面向对象的条条框框,格式由3个部分组成:
- 一些参数
- 一个箭头
- 一段代码
Lambda表达式的标准格式为:
(参数类型 参数名称) -> { 代码语句 }
格式说明:
- 小括号内的语法与传统方法参数列表一致:接口中抽象方法的参数列表,无参数则留空;多个参数则用逗号分隔。
->
是新引入的语法格式,代表指向动作。把参数传递给方法体- 大括号内的语法与传统方法体要求基本一致。重写接口的抽象方法的方法体。
使用Lambda标准格式(无参无返回)
传统写法
如果使用传统的代码对Person[]
数组进行排序,写法如下:
import java.util.Arrays;
import java.util.Comparator;public class Demo06Comparator {public static void main(String[] args) {// 本来年龄乱序的对象数组Person[] array = {new Person("古力娜扎", 19),new Person("迪丽热巴", 18),new Person("马尔扎哈", 20) };// 匿名内部类Comparator<Person> comp = new Comparator<Person>() {@Overridepublic int compare(Person o1, Person o2) {return o1.getAge() - o2.getAge();}};Arrays.sort(array, comp); // 第二个参数为排序规则,即Comparator接口实例for (Person person : array) {System.out.println(person);}}
}
Lambda写法
import java.util.Arrays;public class Demo07ComparatorLambda {public static void main(String[] args) {Person[] array = {new Person("古力娜扎", 19),new Person("迪丽热巴", 18),new Person("马尔扎哈", 20) };Arrays.sort(array, (Person a, Person b) -> {return a.getAge() - b.getAge();});for (Person person : array) {System.out.println(person);}}
}
使用Lambda标准格式(有参有返回)
给定一个计算器Calculator
接口,内含抽象方法calc
可以将两个int数字相加得到和值:
public interface Calculator {int calc(int a, int b);
}
在下面的代码中,请使用Lambda的标准格式调用invokeCalc
方法,完成120和130的相加计算:
public class Demo08InvokeCalc {public static void main(String[] args) {// TODO 请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果ß}private static void invokeCalc(int a, int b, Calculator calculator) {int result = calculator.calc(a, b);System.out.println("结果是:" + result);}
}
解答
public static void main(String[] args) {invokeCalc(120, 130, (int a, int b) -> {return a + b;});
}
Lambda省略格式
所以凡是可以根据上下文推导得知的信息,都可以省略。
例如上例还可以使用Lambda的省略写法:
public static void main(String[] args) {invokeCalc(120, 130, (a, b) -> a + b);
}
省略规则
在Lambda标准格式的基础上,使用省略写法的规则为:
-
():小括号内参数的类型可以省略;
-
(): 如果小括号内有且仅有一个参,则小括号可以省略;
-
{}:如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。({},return,分号)要省略三个一起省略。
-
使用Lambda必须具有接口,且要求接口中有且仅有一个抽象方法。
无论是JDK内置的Runnable
、Comparator
接口还是自定义的接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda。 -
使用Lambda必须具有上下文推断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
备注:有且仅有一个抽象方法的接口,称为“函数式接口”。
本文标签: Java笔记整理五(Iterator接口泛型常见数据结构(栈,队列,数组,链表,红黑树,集合),jdk新特性,异常,多线程Lambda表达式)
版权声明:本文标题:Java笔记整理五(Iterator接口,泛型,常见数据结构(栈,队列,数组,链表,红黑树,集合),jdk新特性,异常,多线程,Lambda表达式) 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1694399720a251612.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论