集合(下) ---有用
1. Set集合
1.1 概述
Set集合是Collection集合的子体系, 它的元素特点是无序, 唯一.
注意;
Set集合是一个接口, 所以不能通过new的方式直接创建它的对象.
Set集合中没有带索引的方法, 所以不能通过普通for循环遍历.
Set集合的常用子类主要有两个, 分别是HashSet集合和TreeSet集合
1.2 案例一: Set入门
1.2.1 需求
创建Set集合对象, 存储字符串数据: "hello", "world", "java", "world"
通过两种方式, 遍历Set集合.
1.2.2 参考代码
略.
1.3 哈希值
1.3.1 概述
所谓的哈希值指的是JDK根据对象的地址, 或者字符串, 或者数字 算出来的int类型的数值.
1.3.2 如何获取哈希值?
可以通过Object#hashCode()方法, 获取指定对象的哈希值, 具体如下:
public int hashCode(); //根据对象, 获取其对应的哈希值.
1.3.3 案例: 哈希值入门
需求
定义学生类, 属性为姓名和年龄.
在测试类的main方法中, 创建两个学生对象, 分别获取它们的哈希值, 并打印.
测试: 重写Object#hashCode()方法, 实现不同对象的哈希值也是相同的.
测试: 同一对象哈希值肯定相同, 不同对象哈希值一般不同.
参考代码
略.
结论
同一个对象多次调用hashCode()方法, 返回的哈希值是相同的.
默认情况下, 不同对象的哈希值是不同的. 但是通过方法重写, 可以实现不同对象的哈希值是相同的.
同一对象哈希值肯定相同, 不同对象哈希值一般不同.
1.4 HashSet集合
1.4.1 特点
底层数据结构是哈希表.
对集合的迭代顺序不做任何保证, 也就是说不保证元素的存取顺序一致.
没有带索引的方法, 所以不能通过普通for循环遍历.
由于是Set集合, 所以是不包含重复元素的集合.
总结:
HashSet集合的特点是: 无序, 唯一, 元素无索引, 它的底层数据结构是: 哈希表.
1.4.2 案例一: 存储字符串并遍历
需求
定义HashSet集合, 存储字符串"hello", "world", "java", "world"
遍历HashSet集合, 打印每一个元素值, 并观察程序的运行结果.
参考代码
略.
1.4.3 HashSet保证元素唯一性源码分析
通过我们观察源码, 发现HashSet集合保证元素的唯一性, 和下边这行代码相关:
//已知: HashSet中已经有了"hello", "world", 我们现在要往里边添加"world"
//hash: "world"的哈希值, key: "world"
//p.hash: 集合中已经存在的元素的哈希值 k: 集合中的元素对象: "hello", "world"
( true && ( false || ( false && ) )
if (p.hash == hash && ((k = p.key) == key || ( key != null && key.equals(k)))){
//走到这里, 说明是同一个元素, 不添加.
} else {
//走这里, 说明不是同一个元素, 就添加.
}
具体的判断流程:
判断两个对象(集合中已经存在的某个元素 和 要添加的元素)的哈希值是否相同.
– 哈希值相同: 说明可能是同一个元素, 程序继续往下执行.
– 哈希值不同: 说明不是同一个元素, 就添加.
比较两个对象的地址值是否相同
– 地址值相同: 说明是同一个元素, 不添加.
– 地址值不同: 说明可能不是同一个对象, 程序继续往下执行.
判断要添加的元素是否为null
– 如果为null: 不是同一个元素, 就添加.
– 如果不为null: 说明可能是同一个元素, 程序继续往下执行.
比较对象的各个属性值是否相同
– 如果相同: 说明是同一个元素, 不添加.
– 如果不同: 说明不是同一个元素, 就添加.
问题: 为什么要设计的这么复杂呢? 直接通过equals()比较两个属性的属性值不就好了吗?
答案: 确实可以这样设计, 但是效率比较低, 程序之所以写的这么繁琐, 就是为了降低调用equals()方法的次数, 从而实现 节约资源, 提高效率!
结论
HashSet保证元素的唯一性, 依赖hashCode()和equals()方法.
1.4.4 常见数据结构之哈希表
- JDK8以前, 底层采用数组 + 链表的形式实现, 可以理解为: 一个元素为链表的数组.
- JDK8以后, 在长度比较长的时候, 底层实现了优化.
图解
解释:
"hello"的哈希值是99162322
"world"的哈希值是113318802
"java"的哈希值是3254818
"world"的哈希值是113318802
"通话"的哈希值是1179395
"重地"的哈希值是1179395
存储的时候, 会将上述的哈希值和16进行取余操作, 然后根据余数值进行存储.
1.4.5 案例二: 存储学生并遍历
需求
定义学生类, 属性为姓名和年龄.
创建HashSet集合, 用来存储学生对象, 并往其中添加3个学生的信息.
遍历集合, 并把结果打印到控制台上.
要求: 如果学生对象的各个属性值都相同, 我们就认为它们是同一个对象.
参考代码
略.
细节: 在Student类中重写hashCode()和equals()方法, 即可保证元素的唯一性.
1.5 LinkedHashSet集合
LinkedHashSet集合是HashSet集合的子类.
1.5.1 特点
底层的数据结构是通过哈希表 + 链表实现的, 具有可预测的迭代次序.
由链表保证元素有序, 也就是说元素的存取顺序是一致的.
由哈希表保证元素唯一, 也就是说没有重复的元素.
总结:
LinkedHashSet集合的特点是: 有序, 唯一.
1.5.2 案例: LinkedHashSet入门
需求
创建LinkedHashSet集合对象, 存储字符串"hello", "world", "java", "world"
遍历集合, 并将结果打印到控制台上.
参考代码
略.
2. 可变参数
2.1 概述
如果遇到让我们定义一些方法, 分别获取2个整数, 3个整数, 4个整数的和, 此时我们只能通过方法重载的形式来实现, 但是这样做比较繁琐, 我们发现上述的这些方法, 只有参数列表是变化的, 那能不能优化这个问题呢?
答案肯定是可以的, 我们可以通过可变参数来优化这个问题.
可变参数又称参数个数可变,它用作方法的形参出现,那么方法参数个数就是可变的了
2.2 格式
修饰符 返回值类型 方法名(数据类型… 变量名) { }
2.3 注意事项
这里的变量其实是一个数组.
大白话翻译: 可变参数的底层就是一个数组.
如果一个方法有多个参数,其中包含可变参数,可变参数要放在最后.
大白话解释: 方法的形参列表有且只能有一个可变参数, 并且可变参数要放到形参列表的最后.
2.4 案例一: 求整数和
2.4.1 需求
定义getSum()方法, 用来获取n个整数的和(n可能是任意的一个数字).
在main方法中, 调用getSum()方法.
2.4.2 参考代码
略.
3. Map集合入门
3.1 概述
Map集合是双列集合的顶层接口, 它是用来存储键值对对象的, 其中键具有唯一性, 而值是可以重复的.
即: Map集合的数据结构只针对于键有效.
3.2 格式
public interface Map<K, V> //K: 键的类型, V: 值的类型.
例如:
- itheima001 刘亦菲
- itheima002 赵丽颖
- itheima003 高圆圆
3.3 创建对象
因为Map是接口, 不能通过new关键字直接创建它的对象, 我们可以通过多态的形式, 创建其子类对象. 从而实现创建Map集合对象的这个需求.
注意: Map集合的常用子类主要有两个, 分别是: HashMap和TreeMap.
3.4 案例一: Map入门
3.4.1 需求
定义Map集合, 键是学号, 值是学生的名字. (键值都是字符串类型).
往Map集合中添加3对元素.
打印Map集合对象.
3.4.2 提示
本案例涉及到的Map集合的方法如下:
- public V put(K key, V value)
解释:
往双列集合中添加元素, 键不存在则直接添加, 并返回null.
键存在则用新值覆盖旧值, 并返回被赋值之前的值.
3.4.3 参考代码
import java.util.HashMap;
import java.util.Map;
public class Demo01 {
public static void main(String[] args) {
//1. 创建集合对象. 键是学号, 值是学生的名字
Map<String, String> hm = new HashMap<>();
//2. 创建元素对象.
//3. 把元素对象添加到集合中.
hm.put("itheima001","刘亦菲");
hm.put("itheima002","赵丽颖");
hm.put("itheima003","高圆圆");
//4. 打印集合.
System.out.println(hm);
}
}
3.5 Map集合的成员方法
3.5.1 方法描述
方法名 |
说明 |
V put(K key,V value) |
添加元素 |
V remove(Object key) |
根据键删除键值对元素 |
void clear() |
移除所有的键值对元素 |
boolean containsKey(Object key) |
判断集合是否包含指定的键 |
boolean containsValue(Object value) |
判断集合是否包含指定的值 |
boolean isEmpty() |
判断集合是否为空 |
int size() |
集合的长度,也就是集合中键值对的个数 |
3.5.2 需求
定义Map集合, 键是丈夫, 值是妻子. (键值都是字符串类型).
分别测试上述的7个方法.
3.5.3 参考代码
略.
3.6 Map集合的获取功能
3.6.1 方法描述
方法名 |
说明 |
V get(Object key) |
根据键获取值 |
Set keySet() |
获取所有键的集合 |
Collection values() |
获取所有值的集合 |
Set<Map.Entry<K,V>> entrySet() |
获取所有键值对对象的集合 |
3.6.2 案例一: 测试获取功能
需求
定义Map集合, 键是丈夫, 值是妻子. (键值都是字符串类型).
先通过代码测试上述的3个方法. 即: get(), keySet(), values()
参考代码
略.
3.6.3 案例二: Map集合遍历方式一
需求
定义Map集合, 键是丈夫, 值是妻子. (键值都是字符串类型).
往集合中添加3对键值对元素.
遍历Map集合.
思路分析
我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合.
把所有的丈夫给集中起来
遍历丈夫的集合,获取到每一个丈夫
根据丈夫去找对应的妻子
步骤分析
获取所有键的集合。
解释: 用Map#keySet()方法实现
遍历键的集合,获取到每一个键。
解释: 用增强for实现
根据键去找值。
解释: 用Map#get(K key)方法实现
参考代码
//案例: 遍历Map集合. 根据键获取值.
public class Demo02 {
public static void main(String[] args) {
//1. 创建集合对象. 键是学号, 值是学生的名字
Map<String, String> hm = new HashMap<>();
//2. 创建元素对象.
//3. 把元素对象添加到集合中.
hm.put("杨过", "小龙女");
hm.put("乔峰", "阿朱");
hm.put("段誉", "王语嫣");
//4. 打印集合.
//4.1 获取集合中所有的键.
Set<String> keys = hm.keySet();
//4.2 遍历, 获取到每一个键.
for (String key : keys) {
//4.3 根据键, 获取值, 然后输出.
String value = hm.get(key);
System.out.println(key + "..." + value);
}
}
}
3.6.4 案例三: Map集合遍历方式二
需求
定义Map集合, 键是丈夫, 值是妻子. (键值都是字符串类型).
往集合中添加3对键值对元素.
遍历Map集合.
思路分析
我们刚才存储的元素都是成对出现的,所以我们把Map看成是一个夫妻对的集合.
获取所有结婚证的集合
遍历结婚证的集合,得到每一个结婚证
根据结婚证获取丈夫和妻子
步骤分析
获取所有键值对对象的集合
解释:
通过Map#Set<Map.Entry<K,V>> entrySet()方法实现.
entrySet()方法的作用是: 获取所有键值对对象的集合
遍历键值对对象的集合,得到每一个键值对对象
解释:
用增强for实现,得到每一个Map.Entry
Map.Entry 就表示键值对对象的类型.
根据键值对对象获取键和值
解释:
用Map.Entry的getKey()方法得到键
用Map.Entry的getValue()方法得到值
参考代码
//案例: 遍历Map集合. 根据键值对对象获取键和值.
public class Demo03 {
public static void main(String[] args) {
//1. 创建集合对象. 键是学号, 值是学生的名字
Map<String, String> hm = new HashMap<>();
//2. 创建元素对象.
//3. 把元素对象添加到集合中.
hm.put("杨过", "小龙女");
hm.put("乔峰", "阿朱");
hm.put("段誉", "王语嫣");
//4. 打印集合.
//4.1 获取集合中所有的 键值对对象的集合.
Set<Map.Entry<String, String>> entrys = hm.entrySet();
//4.2 遍历, 获取到每一个 键值对对象.
for (Map.Entry<String, String> entry : entrys) {
//4.3 根据键值对对象, 获取键和值, 然后输出.
String key = entry.getKey();
String value = entry.getValue();
System.out.println(key + "..." + value);
}
}
}
4. Map集合的案例
4.1 案例一: 键是String值是Student
4.1.1 需求
创建HashMap集合, 键是学号(String), 值是学生对象(Student).
往HashMap集合中添加3组数据.
通过两种方式, 遍历HashMap集合.
4.1.2 参考代码
略.
4.2 案例二: 键是Student值是String
4.2.1 需求
创建HashMap集合, 键是学生对象(Student), 值是居住地(String).
往HashMap集合中添加3组数据.
通过两种方式, 遍历HashMap集合.
注意: HashMap集合想保证键的唯一性, 依赖hashCode()和equals()这两个方法.
4.2.2 参考代码
略.
4.3 案例三: ArrayList嵌套HashMap
4.3.1 需求
定义ArrayList<HashMap<String, String>>集合, 存储三个元素, 每个元素都是一个双列集合, 具体如下:
第一个双列集合, 记录的信息如下:
孙策 大乔
周瑜 小乔
第二个双列集合, 记录的信息如下:
郭靖 黄蓉
杨过 小龙女
第三个双列集合, 记录的信息如下:
令狐冲 任盈盈
林平之 岳灵珊
把上述的三个双列集合当做元素对象, 添加到ArrayList集合中.
遍历ArrayList集合, 输出每个元素.
4.3.2 参考代码
略.
4.4 案例四: HashMap嵌套ArrayList
4.4.1 需求
创建一个HashMap集合,存储三个键值对元素,键表示书名(String),值是书中人物名(ArrayList)
第一个ArrayList集合的元素: (三国演义)
诸葛亮
赵云
第二个ArrayList集合的元素: (西游记)
唐僧
孙悟空
第三个ArrayList集合的元素: (水浒传)
武松
鲁智深
遍历HashMap集合, 输出每一个元素.
4.4.2 参考代码
略.
4.5 案例五: 统计每个字符的次数
4.5.1 需求
键盘录入一个字符串,要求统计字符串中每个字符出现的次数。
举例:键盘录入“aababcabcdabcde” 在控制台输出:“a(5)b(4)c(3)d(2)e(1)”
4.5.2 参考代码
//案例: 统计字符串中每个字符的次数.
public class Demo04 {
public static void main(String[] args) {
/*
需求:
1. 键盘录入一个字符串,要求统计字符串中每个字符串出现的次数。
2. 举例:键盘录入“aababcabcdabcde” 在控制台输出:“a(5)b(4)c(3)d(2)e(1)”
*/
//1. 创建Scanner对象.
Scanner sc = new Scanner(System.in);
//2. 创建TreeMap集合对象, 字符作键, 次数做值.
TreeMap<Character, Integer> tm = new TreeMap<>();
//3. 提示用户录入字符串, 并接收.
System.out.println("请录入一个字符串: ");
String result = sc.nextLine();
//4. 将用户录入的字符串转成字符数组.
char[] chs = result.toCharArray();
//5. 遍历, 获取到每一个字符.
for (char ch : chs) {
//6. 判断该字符在双列集合中是否存在.
if (!tm.containsKey(ch)) {
//6.1 不存在, 直接存储, 次数记录为1.
tm.put(ch, 1);
} else {
//6.2 存在, 就将次数+1, 然后存储.
tm.put(ch, tm.get(ch) + 1);
}
}
//7. 将双列集合中的数据拼接成指定的字符串.
StringBuilder sb = new StringBuilder();
for (Character key : tm.keySet()) {
sb.append(key).append("(").append(tm.get(key)).append(")");
}
//8. 打印结果.
String s = sb.toString();
System.out.println(s);
}
}
5. Collections集合工具类
5.1 概述
Collections类是针对集合操作的工具类.
5.2 常用方法
5.2.1 常用方法如下
方法名 |
说明 |
public static void sort(List list) |
将指定的列表按升序排序 |
public static void reverse(List<?> list) |
反转指定列表中元素的顺序 |
public static void shuffle(List<?> list) |
使用默认的随机源随机排列指定的列表 |
5.2.2 需求
定义ArrayList集合, 存储5个整数.
分别测试上述的3个方法.
5.2.3 参考代码
略.
6. 斗地主案例
6.1 概述
通过代码实现斗地主过程中的洗牌,发牌和看牌动作.
6.2 版本一: 无序的牌
参考代码
public class PokerDemo {
public static void main(String[] args) {
//创建一个牌盒,也就是定义一个集合对象,用ArrayList集合实现
ArrayList<String> array = new ArrayList<String>();
//往牌盒里面装牌
/*
♦2,♦3,♦4...♦K,♦A
♣2,...
♥2,...
♠2,...
小王,大王
*/
//定义花色数组
String[] colors = {"♦", "♣", "♥", "♠"};
//定义点数数组
String[] numbers = {"2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A"};
for (String color : colors) {
for (String number : numbers) {
array.add(color + number);
}
}
array.add("小王");
array.add("大王");
//洗牌,也就是把牌打撒,用Collections的shuffle()方法实现
Collections.shuffle(array);
// System.out.println(array);
//发牌,也就是遍历集合,给三个玩家发牌
ArrayList<String> lqxArray = new ArrayList<String>();
ArrayList<String> lyArray = new ArrayList<String>();
ArrayList<String> fqyArray = new ArrayList<String>();
ArrayList<String> dpArray = new ArrayList<String>();
for (int i = 0; i < array.size(); i++) {
String poker = array.get(i);
if (i >= array.size() - 3) {
dpArray.add(poker);
} else if (i % 3 == 0) {
lqxArray.add(poker);
} else if (i % 3 == 1) {
lyArray.add(poker);
} else if (i % 3 == 2) {
fqyArray.add(poker);
}
}
//看牌,也就是三个玩家分别遍历自己的牌
lookPoker("林青霞", lqxArray);
lookPoker("柳岩", lyArray);
lookPoker("风清扬", fqyArray);
lookPoker("底牌", dpArray);
}
//看牌的方法
public static void lookPoker(String name, ArrayList<String> array) {
System.out.print(name + "的牌是:");
for (String poker : array) {
System.out.print(poker + " ");
}
System.out.println();
}
}
6.3 版本二: 有序的牌
代码演示
public class PokerDemo {
public static void main(String[] args) {
//创建HashMap,键是编号,值是牌
HashMap<Integer, String> hm = new HashMap<Integer, String>();
//创建ArrayList,存储编号
ArrayList<Integer> array = new ArrayList<Integer>();
//创建花色数组和点数数组
String[] colors = {"♦", "♣", "♥", "♠"};
String[] numbers = {"3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2"};
//从0开始往HashMap里面存储编号,并存储对应的牌。同时往ArrayList里面存储编号
int index = 0;
for (String number : numbers) {
for (String color : colors) {
hm.put(index, color + number);
array.add(index);
index++;
}
}
hm.put(index, "小王");
array.add(index);
index++;
hm.put(index, "大王");
array.add(index);
//洗牌(洗的是编号),用Collections的shuffle()方法实现
Collections.shuffle(array);
//发牌(发的也是编号,为了保证编号是排序的,创建TreeSet集合接收)
TreeSet<Integer> lqxSet = new TreeSet<Integer>();
TreeSet<Integer> lySet = new TreeSet<Integer>();
TreeSet<Integer> fqySet = new TreeSet<Integer>();
TreeSet<Integer> dpSet = new TreeSet<Integer>();
for (int i = 0; i < array.size(); i++) {
int x = array.get(i);
if (i >= array.size() - 3) {
dpSet.add(x);
} else if (i % 3 == 0) {
lqxSet.add(x);
} else if (i % 3 == 1) {
lySet.add(x);
} else if (i % 3 == 2) {
fqySet.add(x);
}
}
//调用看牌方法
lookPoker("林青霞", lqxSet, hm);
lookPoker("柳岩", lySet, hm);
lookPoker("风清扬", fqySet, hm);
lookPoker("底牌", dpSet, hm);
}
//定义方法看牌(遍历TreeSet集合,获取编号,到HashMap集合找对应的牌)
public static void lookPoker(String name, TreeSet<Integer> ts, HashMap<Integer, String> hm) {
System.out.print(name + "的牌是:");
for (Integer key : ts) {
String poker = hm.get(key);
System.out.print(poker + " ");
}
System.out.println();
}
}
7. Lambda表达式
7.1 体验Lambda表达式
7.1.1 需求
已知接口Animal中有一个抽象方法eat()
在测试类AnimalTest中定义show(Animal an)方法, 实现调用Animal#eat()方法.
并在main方法中, 调用AnimalTest#show()方法.
7.1.2 实现方式
- 实现方式1
– 匿名内部类的方式
- 实现方式2
– Lambda表达式的方式改进
7.1.3 代码演示
略.
7.1.4 总结: 函数式编程思想
函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
而我们要学习的Lambda表达式就是函数式思想的体现
---看到
7.2 Lambda表达式的标准格式
7.2.1 格式
(形式参数) -> {代码块}
7.2.2 解释:
- 形式参数:如果有多个参数,参数之间用逗号隔开, 如果没有参数,留空即可
- ->:由英文中画线和大于符号组成,固定写法。代表指向动作
- 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
- 组成Lambda表达式的三要素:形式参数,箭头,代码块
7.2.3 前提
有一个接口, 且接口中有且仅有一个抽象方法.
7.3 Lambda表达式练习1
- 目标
– 无参无返回值抽象方法的练习
- 需求:
– 定义一个接口(Eatable),里面定义一个抽象方法:void eat();
– 定义一个测试类(EatableDemo),在测试类中提供两个方法
- 一个方法是:useEatable(Eatable e)
- 一个方法是主方法,在主方法中调用useEatable方法
- 示例代码
//接口
public interface Eatable {
void eat();
}
//实现类
public class EatableImpl implements Eatable {
@Override
public void eat() {
System.out.println("一天一苹果,医生远离我");
}
}
//测试类
public class EatableDemo {
public static void main(String[] args) {
//在主方法中调用useEatable方法
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();
}
}
7.4 Lambda表达式练习2
- 目标
有参无返回值抽象方法的练习
- 需求
– 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);
– 定义一个测试类(FlyableDemo),在测试类中提供两个方法
- 一个方法是:useFlyable(Flyable f)
- 一个方法是主方法,在主方法中调用useFlyable方法
- 示例代码
public interface Flyable {
void fly(String s);
}
public class FlyableDemo {
public static void main(String[] args) {
//在主方法中调用useFlyable方法
//匿名内部类
useFlyable(new Flyable() {
@Override
public void fly(String s) {
System.out.println(s);
System.out.println("飞机自驾游");
}
});
System.out.println("--------");
//Lambda
useFlyable((String s) -> {
System.out.println(s);
System.out.println("飞机自驾游");
});
}
private static void useFlyable(Flyable f) {
f.fly("风和日丽,晴空万里");
}
}
7.5 Lambda表达式练习3
- 目标
有参有返回值抽象方法的练习
- 需求
– 定义一个接口(Addable),里面定义一个抽象方法:int add(int x,int y);
– 定义一个测试类(AddableDemo),在测试类中提供两个方法
- 一个方法是:useAddable(Addable a)
- 一个方法是主方法,在主方法中调用useAddable方法
- 示例代码
public interface Addable {
int add(int x,int y);
}
public class AddableDemo {
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);
}
}
7.6 Lambda表达式的省略模式
7.6.1 省略的规则
- 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,和return关键字
7.6.2 代码演示
public interface Addable {
int add(int x, int y);
}
public interface Flyable {
void fly(String s);
}
public class LambdaDemo {
public static void main(String[] args) {
// useAddable((int x,int y) -> {
// return x + y;
// });
//参数的类型可以省略
useAddable((x, y) -> {
return x + y;
});
// useFlyable((String s) -> {
// System.out.println(s);
// });
//如果参数有且仅有一个,那么小括号可以省略
// useFlyable(s -> {
// System.out.println(s);
// });
//如果代码块的语句只有一条,可以省略大括号和分号
useFlyable(s -> System.out.println(s));
//如果代码块的语句只有一条,可以省略大括号和分号,如果有return,return也要省略掉
useAddable((x, y) -> x + y);
}
private static void useFlyable(Flyable f) {
f.fly("风和日丽,晴空万里");
}
private static void useAddable(Addable a) {
int sum = a.add(10, 20);
System.out.println(sum);
}
}
7.7Lambda表达式的注意事项
- 使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
- 必须有上下文环境,才能推导出Lambda对应的接口
– 根据局部变量的赋值得知Lambda对应的接口
Runnable r = () -> System.out.println("Lambda表达式");
– 根据调用方法的参数得知Lambda对应的接口
new Thread(() -> System.out.println("Lambda表达式")).start();
7.8Lambda表达式和匿名内部类的区别
- 所需类型不同
– 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
– Lambda表达式:只能是接口
- 使用限制不同
– 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
– 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
- 实现原理不同
– 匿名内部类:编译之后,产生一个单独的.class字节码文件
– Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成