1.内部类
分类:
- 成员内部类: 一般多用于框架, 是用来对类的功能进行增强的
- 局部内部类: 这个是我们常用的类型(它属于局部内部类的一种)
局部位置: 方法中, 或者方法的形参列表。
1.1匿名对象
概述: 没有名字的对象, 就叫匿名对象。
特点:用完以后就会变成垃圾对象, 由GC在不确定的时间回收。
eg: new Cat().eat();
适用于只使用一次对象的情况。
1.2匿名内部类
概述:匿名内部类就是没有名字的局部内部类。
格式:
new 类名或者接口名() {
//重写类或者接口中所有的抽象方法.
};
本质:就是一个继承了类或者实现了接口的匿名的子类对象。
eg:new Animal() {
//重写类或者接口中所有的抽象方法.
@Override
public void eat() {
System.out.println(“我是通过匿名内部类的形式创建的 Animal类的子类对象”);
} }.eat();
使用场景:
- 当对对象方法(也叫: 成员方法)仅调用一次的时候。
- 匿名内部类可以作为方法的实参进行传递。
技巧:当抽象类或者接口中的抽象方法不超过3个的时候(一般是只有1个), 就可以考虑使用匿名内部类.
2.集合
概述:集合是用来存储多个同类型数据的容器, 它的长度是可以变化的
体系图:
2.1 Collection集合
概述:Collection集合是单例集合的顶层接口, 里边定义了所有单列集合都共有的内容。
格式:
因为Collection是接口, 所以不能直接通过new关键字来创建它的对象,可以通过多态的方式, 创建其子类对象, 从而实现创建Collection接口对象。
Collection list= new ArrayList();
集合后边的<数据类型>是泛型的意思, 泛型是用来限定集合中存储元素的数据类型的,泛型一般只结合集合使用。泛型除了有基本数据类型,也可以是自定数据类型。
成员方法:
- public boolean add(E e) 添加元素.
- public boolean remove(Object obj) 从集合中移除指定的元素.
- public void clear() 清空集合对象
- public boolean contains(Objectobj) 判断集合中是否包含指定的元素
- public boolean isEmpty() 判断集合是否为空
- public int size() 获取集合的长度, 即集合中元素的个数
集合遍历:
根据集合对象获取其对应的迭代器对象,通过Collection#iterator()方法实现.
判断迭代器中是否有下一个元素,通过Iterator#hasNext()方法实现.
如果有, 就获取该元素,通过Iterator#next()方法实现.
Iterator it = coll.iterator();
//3.2 判断迭代器中是否有下一个元素.
while (it.hasNext()) {
//3.3 有就获取, 然后输出.
String s = it.next();
System.out.println(s); }
简化版:while (it.hasNext())
System.out.println(it.next());
2.2 List集合
概述:有序集合(也称为序列), 该界面的用户可以精确控制列表中每个元素的插入位置。且用户可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。
特点:
- 有序: 指的是元素的存储顺序和取出顺序是一致的.
- 可重复: 指的是List集合可以存储重复的元素.
- 元素有索引: 指的是List集合中每个元素都是由索引的, 且索引是从0开始的.
List特有的成员方法:
- public void add(int index, Eelement) 解释: 在集合的指定位置(索引), 插入指定的元素, 索引越界会报IndexOutOfBoundsException异常.
- public E remove(int index) 解释: 删除指定索引处的元素, 并返回被删除的元素, 索引越界会报IndexOutOfBoundsException异常.
- public E set(int index, Eelement) 解释: 修改指定索引处的元素为指定的值, 并返回修改前的元素, 索引越界会报IndexOutOfBoundsException异常.
- public E get(int index) 解释: 根据索引, 获取其对应的元素, 索引越界会报IndexOutOfBoundsException异常.
- public ListIterator listIterator(); //根据集合对象获取其对应的列表迭代器对象
List集合遍历:
使用list特有的size()、get()方法来遍历集合。
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
列表迭代器指的是**ListIterator接口, 它是List集合特有的迭代器.**该迭代器继承了Iterator迭代器,所以, 我们可以直接使用。
列表迭代器成员方法:
- hasNext(); 判断集合中是否有下一个元素.
- next(); 有就获取集合中的下一个元素.
- hasPrevious(); 判断集合中是否有上一个元素.
- previous(); 有就获取集合中的上一个元素.
有了这些方法List集合就可以正向遍历也可以逆向遍历,但是逆向遍历前必须进行一次正向遍历。
并发修改异常:
问:当使用普通迭代器(Iterator)遍历集合的同时, 又往集合中添加了元素, 就会报并发修改异常
原:迭代器是依赖于集合而存在, 当判断成功后, 集合中添加了新的元素, 而迭代器不知道, 所以就报错了
解:
方式1:通过 列表迭代器(ListIterator)来解决。
要求:必须使用列表迭代器中的添加元素的方法. ListIterator#add();这种方式添加的元素是在刚才迭代到的元素后边。
方式2:通过for循环 + size()方法来解决。
要求:这种方式添加的元素是在集合的最后位置添加的。
方式3:通过CopyOnWriteArrayList集合解决。
2.3 增强for循环
概述:用来简化数组和Collection集合的遍历的,它底层是一个迭代器。
格式:for(数据类型 元素名 : 要遍历的集合或者数组) {
//元素名: 表示的就是集合/数组中的每一个元素}
注意事项:增强for的,并且增强for来遍历的数组或者集合不能为null。
通过测试增强for循环是否会发生并发修改异常,可得增强for循环底层是普通迭代器iterator。
2.4 List集合子类
List集合是一个接口, 它的常用子类有两个, 分别是ArrayList,LinkedList.
ArrayList集合的特点: 底层数据结构是数组, 查询和修改快,增删慢.
LinkedList集合的特点: 底层数据结构是链表, 查询和修改慢, 增删快.
LinkedList的特有方法:
- public void addFirst(E e) 往列表的开头插入指定的元素
- public void addLast(E e) 往列表的末尾插入指定的元素
- public E removeFirst() 删除列表中的第一个元素, 并返回被删除的元素
- public E removeLast() 删除列表中的最后一个元素, 并返回被删除的元素.
- public E getFirst() 返回列表的第一个元素
- public E getLast() 返回列表的最后一个元素
2.5 Set集合
概述:Set集合是Collection集合的子体系, 它的元素特点是无序, 唯一.
特点:
- Set集合是一个接口, 所以不能通过new的方式直接创建它的对象。
- Set集合中没有带索引的方法, 所以不能通过普通for循环遍历.。
- Set集合的常用子类主要有两个, 分别是HashSet集合和TreeSet集合。
set集合遍历:
- 方式1:普通迭代器iterator遍历
- 方式2:增强for循环遍历
- 方式3:转数组遍历
Object[] objs = hs.toArray();
for (Object obj : objs) {
System.out.println(obj); }
哈希值指的是JDK根据对象的地址, 或者字符串, 或者数字算出来的int类型的数值。
获取哈希值方法:public int hashCode(); //根据对象, 获取其对应的哈希值.
注意:实际开发中, 我们认为, 如果同一个类的两个对象, 各个属性都相同, 那么它们就是同一个对象。但是object类的hashcode()方法是按照地址值算的。只要涉及到hashCode()方法了, 子类一般都会重写hashCode()方法, 采用属性值计算哈希值。同一对象哈希值肯定相同, 不同对象哈希值一般不同。
2.6 Hashset集合
特点:无序, 唯一, 元素无索引, 它的底层数据结构是: 哈希表(数组 + 链表)。
Hashset集合遍历:
- 方式1:普通迭代器iterator遍历
- 方式2:增强for循环遍历
hashset唯一性原理:
//p.hash: 集合中已经存在的元素的哈希值. hash: 要添加的元素的哈希值
//k: 就是集合中的元素对象"hello", “world” key: 要添加的元素 “world”if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) {
//走这里, 说明是同一个元素, 不添加元素.
} else {
//走这里, 说明不是同一个元素, 就添加元素.
}
四次判断: ( ?? && ( ?? || (?? && ?? )) )
- 判断两个对象(集合中已经存在的某个对象 和 要添加的元素)的哈希值是否相同.
哈希值相同: 说明可能是同一个元素, 程序继续往下运行.
哈希值不同: 说明不是同一个元素, 就添加.
- 比较两个对象的地址值是否相同
地址值相同: 说明是同一个元素, 不添加.
地址值不同: 说明可能不是同一个元素, 程序继续往下执行
- 判断要添加的元素是否为null
如果为null: 不是同一个元素, 就添加.
如果不为null: 说明可能是同一个元素, 程序继续往下运行.
- 比较两个对象的各个属性值是否相同.
如果相同: 说明是同一个元素, 不添加元素.
如果不同: 说明不是同一个元素, 就添加.
问:为什么要设计的这么复杂呢? 直接通过equals()比较两个对象的属性值不就好了吗?
答:确实可以直接比较equals()方法这样设计, 但是效率比较低. 程序之所以设计的这么繁琐, 就是为了降低调用equals()方法的次数,从而实现"节约资源, 提高效率"
结论:HashSet保证元素的唯一性, 依赖hashCode()和equals()方法
hash表:
JDK8以前,底层采用数组 + 链表的形式实现, 可以理解为: 一个元素为链表的数组,但是这样表的长度比较长,
JDK8以后,底层实现了优化. 存储的时候, 会将上述的哈希值和16进行取余操作, 然后根据余数值进行存储。
2.7 LinkedHashSet集合
概述:LinkedHashSet集合是HashSet集合的子类。
特点:
- 底层的数据结构是通过哈希表 + 链表实现的, 具有可预测的迭代次序。
- 由链表保证元素有序, 也就是说元素的存取顺序是一致的。
- 由哈希表保证元素唯一, 也就是说没有重复的元素。
2.8 可变参数
概述:可变参数又称参数个数可变,它用作方法的形参出现,那么方法参数个数就是可变的了。
格式:public static int getSum(int… a)即: 在数据类型的后边加上 3个点
注意:
- 可变参数的底层就是一个数组
- 方法的形参列表有且只能有一个可变参数, 且可变参数要放最后
2.9 Map
概述:Map集合是双列集合的顶层接口,它是用来存储键值对对象,其中键具有唯一性, 而值是可以重复的。
注意:Map是一个接口,不能直接new,可以通过多态的方式创建。常用子类是HashMap、TreeMap。
格式:public interface Map<K, V>
成员方法:
方法名 | 说明 |
---|---|
V put(K key,V value) | 添加元素 |
V remove(Object key) | 根据键删除键值对元素 |
void clear() | 移除所有的键值对元素 |
boolean containsKey(Object key) | 判断集合是否包含指定的键 |
boolean containsValue(Object value) | 判断集合是否包含指定的值 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 集合的长度,也就是集合中键值对的个数 |
put()方法返回值:public V put(K key, V value) 添加元素到双列集合, key不存在就直接添加, 并返回null,key存在, 会用新值覆盖旧值。 并返回旧值。
方法名 | 说明 |
---|---|
V get(Object key) | 根据键获取值 |
Set keySet() | 获取所有键的集合 |
Collection values() | 获取所有值的集合 |
Set<Map.Entry<K,V>> entrySet() | 获取所有键值对对象的集合 |
keySet()方法返回值是集合是key值,具有唯一性,所以是set。
values()方法返回值是集合value值,可以重复,所以是collection。
Map集合遍历:
方式1:根据键的集合获取其对应的值。
增强for循环
//获取所有key值
Set keys = hm.keySet();
for (String key : keys) {
//3.3 根据键获取其对应的值.
String value = hm.get(key);
System.out.println(key + “…” + value); }
普通迭代器
Set keys = hm.keySet();
while (it.hasNext()) {
String key = it.next();
System.out.println(key + “…” + hm.get(key)); }
方式2:根据键值对对象获取其对应的键和值,使用Map.Entry中的方法: getKey(), getValue()。
增强for循环
// 获取到所有的 键值对对象 的集合.
Set<Map.Entry<String, String>> entrys = hm.entrySet(); //需要导入: import java.util.Map;
for (Map.Entry<String, String> entry : entrys) {
// 根据 键值对对象 获取其对应的键和值.
System.out.println(entry.getKey() + “…” + entry.getValue());
}
普通迭代器
Set<Map.Entry<String, String>> entrys = hm.entrySet(); //需要导入: import java.util.Map;
// 获取到所有的 键值对对象 的集合.
Iterator<Map.Entry<String, String>> it = entrys.iterator();
// 遍历, 获取到每一个 键值对对象.
while (it.hasNext()) {
Map.Entry<String, String> entry = it.next();
// 根据 键值对对象 获取其对应的键和值.
System.out.println(entry.getKey() + “…” + entry.getValue()); }
综合案例(统计字符串字符出现次数):
public class Demo05 {
public static void main(String[] args) {
/*1.键盘录入一个字符串,要求统计字符串中每个字符出现的次数。 2.举例:键盘录入“aababcabcdabcde” 在控制台输出:“a(5)b(4)c(3)d(2)e(1)” */
//1. 定义Map集合, 字符做键, 该字符对应的次数作为值. 即: HashMap<Character, Integer> a:3, b:1
Map<Character, Integer> map = new HashMap<>();
//2. 提示录入, 然后遍历, 获取到每一个字符.
System.out.println("请录入一个字符串: ");
for (char ch : new Scanner(System.in).nextLine().toCharArray())
//6. 判断该字符在双列集合中是否存在.
map.put(ch, !map.containsKey(ch) ? 1 : map.get(ch) + 1);
//已知格式: a:3, b:1 目标格式: a(5)b(4)c(3)d(2)e(1)
//7. 走到这里, Map集合记录的就是我们要的结果, 将其拼接成字符串.
StringBuilder sb = new StringBuilder();
for (Character key : map.keySet())
sb.append(key).append(“(”).append( map.get(key)).append(“)”);
//8. 打印结果.
System.out.println(sb);}}
2.10 工具类Collections集合
概述:Collections类是针对集合操作的工具类
方法:
方法名 | 说明 |
---|---|
public static void sort(List list) | 将指定的列表按升序排序 |
public static void reverse(List<?> list) | 反转指定列表中元素的顺序 |
public static void shuffle(List<?> list) | 使用默认的随机源随机排列指定的列表 |
工具类使用方法:Collections.sort(list);
3.lambda
格式:(形式参数) -> {代码块}
解释:
- 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
- ->:由英文中画线和大于符号组成,固定写法。代表指向动作
- 代码块:是我们具体要做的事情,也就是以前我们写的方法体内容
- 组成Lambda表达式的三要素:形式参数,箭头,代码块
前提:有一个接口, 且接口中有且仅有一个抽象方法.
省略规则:
- 参数类型可以省略。但是有多个参数的情况下,不能只省略一个
- 如果参数有且仅有一个,那么小括号可以省略
- 如果代码块的语句只有一条,可以省略大括号和分号,和return关键字
注意事项:
使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
必须有上下文环境,才能推导出Lambda对应的接口,要么直接当接口的子类来使用,要么当方法的实参进行传递。
lambda和匿名内部类的区别:
- 所需类型不同
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
- 使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
- 实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成