2021.01.19 第一阶段12

一、RandomAccess

1、迭代器对集合遍历的效率远高于其他遍历方式
2、ArrayList实现了RandomAccess接口,但是LinkedList没有实现RandomAccess接口
3、RandomAccess接口实际上就是一个标记接口
4、ArrayList使用for循环遍历比迭代器略快,但是其他的集合使用迭代器遍历的效率远高于for循环

二、LinkedLIst的源码分析

1、LinkedList内部没有容量限制,只要创建节点,链表就能扩容,因为链表可以无限长,并且链表间的每一个节点都是通过地址值进行的逻辑关联,在内存中并不属于连续的物理结构
2、在源码中的节点对象所属类型是一个私有静态成员内部类

	//节点、元素的个数,因为一个节点存一个元素,所以size是节点(元素)的个数
	transient int size = 0;
	//整个链表的头节点
	transient Node<E> first;
	//整个链表的尾节点
	transient Node<E> last;
	private static class Node<E> {
		E item;//节点中存储的数据
		Node<E> next;//当前节点存储的下一个节点的地址
		Node<E> prev;//当前节点存储的上一个节点的地址
		Node(Node<E> prev, E element, Node<E> next) {
			this.item = element;
			this.next = next;
			this.prev = prev;
		}
	}

3、以头部添加元素为例

public void addFirst(E e) {
	linkFirst(e);//LinkedList在头部增实际上实在调用linkFirst方法
}
private void linkFirst(E e) {
	final Node<E> f = first;//临时将原本的链表头节点地址存储
	//新建一个节点作为链表的新头,所以prev为null,next值指向旧的的头节点
	final Node<E> newNode = new Node<>(null, e, f);
	first = newNode;//将新节点作为头结点
	if (f == null)//如果原来没有任何节点存在
		last = newNode;//新节点也作为最后一个节点
	else//如果原来已经有节点存在
		f.prev = newNode;//新的头结点指向旧的头结点
	size++;
	modCount++;
}

三、泛型

(一)概述
1、泛型:广泛的类型。在定义一个类型的时候,类型中许多的方法的参数列表和返回值类型都不确定,可以使用一个符号来代替具体的类型,这个符号就是泛型。
2、使用:对于有泛型的类型,在类型后面加上【<>】,这个【<>】中写上具体的类型,就是一个泛(如果书写的是具体的类型,此时泛型就确定为这个具体的类型)。

ArrayList<String> list = new ArrayList<String>();

泛型的好处:
(1)没有泛型的时候,代码中隐藏了什么问题不能及时发现,等到运行的时候回直接出现异常,并且每一个数据都要向下转型,很麻烦
(2)有泛型:将运行期的问题提前暴露在编译期,让程序员能够及时发现问题避免造成更大的损失,而且加入泛型之后,省却了向下转型的麻烦
4、注意事项:
(1)泛型要保持前后的类型一致,在创建对象的时候,前面类型的泛型是什么,后面的泛型也要一致
(2)泛型的推断:【菱形泛型】。在JDK7开始,后面的泛型可以省略不写,像一个菱形。

ArrayList<String> list = new ArrayList<>();

(二)泛型类的定义
1、泛型类:带有泛型的类
2、格式

class 类型<泛型1, 泛型2, ......, 泛型n> {

}

3、说明:
(1)类名后面跟着的泛型,仅仅是泛型的声明,一旦泛型声明出来,就相当于这个类型有了泛型,这个泛型就能在整个类中使用
(2)泛型的声明:只需要是一个合法的标识符即可,一般情况下,都会使用一个大写字母来表示一个泛型
* E —— element,表示元素
K – Key,表示双列集合的键
V – Value,表示双列集合中的值
T – Type,表示类型
N – Number,表示数值类型
?-- 泛型的通配符*
4、注意:泛型确定的时机:当带有泛型类型对象创建出来的时候,泛型就可以确定为具体的类型,对象的泛型确定成什么具体的类型,类中所有使用了类泛型的地方,也就跟着变成什么类型
(三)泛型类练习
自己定义一个带泛型的类,只能在头部增删(模拟JVM栈内存的运行机制,写一段代码)

import java.util.LinkedList;
//自己定义一个带泛型的类,只能在头部增删(模拟JVM栈内存的运行机制,写一段代码)
public class Demo04_Exercise {
	public static void main(String[] args) {
		//先进后出后进先出
		MyStack<String> mt = new MyStack<>();
		mt.push("aaa");
		mt.push("bbb");
		mt.push("ccc");
		while(mt.stackLength() > 0) {
		System.out.println(mt.pop());
		}
	}
}
class MyStack<T> {
	private LinkedList<T> list;
	public MyStack() {
	list = new LinkedList<>();
	}
 //进栈
	public void push(T t) {
		list.addFirst(t);
	}
//弹栈
	public T pop() {
		return list.removeFirst();
	}
	public int stackLength() {
		return list.size();
	}
}

(四)泛型的注意事项
1、类上定义的泛型,在整个类中都能使用
2、泛型确定的时机:当一个泛型类对象创建出来的时候,泛型也就可以确定为具体的类型了
3、泛型只能写成引用数据类型,不能写成基本数据类型
4、当泛型确定为某个具体类型的时候,使用到泛型的内容,所需要的数据必须和泛型确定的数据类型保持一致
(五)泛型方法的定义
1、带有泛型的方法就是泛型方法
2、格式:
(1)方法使用类的泛型:没有特别的格式,参数列表和返回值类型直接使用类的泛型即可
修饰符 返回值类型 方法名称(参数列表) {
方法体
}

(2)方法使用自己的泛型:
权限修饰符号 【static】 <泛型1, 泛型2, …, 泛型n> 返回值类型 方法名称(参数列表) {
方法体
}

3、注意事项:
(1)静态方法不能够使用类的泛型,因为类泛型确定的时机是随着对象的泛型的确定而确定的,静态
和对象无关,所以静态方法不能使用类的泛型
(2)静态方法要想使用泛型,必须自己定义
(3)非静态方法既可以使用类的泛型,也可以使用自定义的泛型
(4)类的泛型在整个类中起作用,方法自定义的泛型在整个方法中起作用
(六)泛型方法的练习
定义一个静态方法,这个方法可以交换任意类型两个元素的值

public class Demo07_Exercise {
	public static void main(String[] args) {
		swap(3, 4);
		swap("aaa", "bbb");
	}
	public static <T> void swap(T a, T b) {
		System.out.println(a);
		System.out.println(b);
		T temp = a;
		a = b;
		b = temp;
		System.out.println(a);
		System.out.println(b);
	}
}

(七)泛型接口的定义和使用
1、带有泛型的接口就是泛型接口
2、格式:

interface 接口名<泛型1, 泛型2, ......, 泛型n> {}

3、说明:
(1)在接口声明上,定义好的泛型,在整个的接口中都能够使用
(2)当泛型接口被类实现的时候又分为两种情况:

interface MyInter<T> {
	void test1(T t);
	T test1();
}

① 情况一:当类实现接口的时候,接口的泛型确定为了具体的类型,类实现接口中抽象方法的时候,这些抽象方法使用了接口泛型的地方,类型也随之确定下来

class Inter1 implements MyInter<String> {
	@Override
	public void test1(String t) {
	}
	@Override
	public String test1() {
		return null;
	}
}

② 情况二:当类实现接口的时候,接口的泛型依然没有确定,类实现接口中抽象方法的时候,这些抽象方法使用了接口泛型的地方,就依然是个泛型
注意:这种格式类和接口名称之后都要带上泛型,并且泛型符号要保持一致

class Inter2<E> implements MyInter<E> {
	@Override
	public void test1(E t) {
	}
	@Override
	public E test1() {
		return null;
	}
}

(八)泛型的通配符(了解)
1、泛型的通配符【?】,用于限定泛型可以出现哪些类型
2、第一种形式:使用?来表示可以是任意类型。例如:在Collection中,方法removeAll(Collection<?> c)参数的泛型就是泛型通配符,表示参数的泛型对于调用者的泛型可以是任意的

import java.util.ArrayList;
public class Demo09_Wildcard {
	public static void main(String[] args) {
		ArrayList<String> list1 = new ArrayList<>();
		ArrayList<Integer> list2 = new ArrayList<>();
		
		list1.removeAll(list2);
	}
}

3、第二种形式:使用【? extends E】表示此处出现的泛型,必须是某个类型或者它的子类。例如:在Collection中,方法addAll(Collection<? extends E> c)。这个方法的参数的泛型表示:参数对象的泛型必须是调用者对象泛型本类或者子类,不能是其他类。这种情况也叫作确定泛型的上边界

ArrayList<Object> list1 = new ArrayList<>();
ArrayList<String> list2 = new ArrayList<>();

list1.addAll(list2);

4、第三种形式:使用【? super E】表示此处出现的泛型,必须是某个类型或者它的父类。例如:TreeSet中构造方法TreeSet(Comparator<? super E> comparator)的参数列表。这种情况也叫作确定泛型的下边界

四、Set

(一)Set概述
1、Set接口是Collection接口的子接口
2、特点:
(1)无序:存取顺序不一致
(2)不可重复:不能够存储重复元素。因为不存在类似索引的标志,区分元素仅仅是依照元素本身去区分,所以不能出现重复元素
3、Set的实现类:
由于Set是接口,所以创建对象需要使用实现类。例如:HashSet就是一个底层为哈希表的实现类
4、存储特点:
(1)元素无序不可重复
(2)如果进行重复元素的存储,会自动去重

(二)Set集合的遍历
1、Set接口中没有什么特有的方法,只能使用Collection中的方法,使用Collection的遍历方式即可
2、第一种:toArray(),将集合转为数组,遍历数组
3、第二种:T[] toArray(T[] a),提前准备好数组,将集合元素存储到我们准备好的数组中并且返回
(1)当提供的参数数组容量小于元素个数的时候,会在底层重新创建一个和元素个数相等大小的数组,用于存储集合元素
(2)当提供的参数数组容量等于元素个数的时候,会直接将这个数组用于存储集合元素并且返回,不会再提供新的数组
(3)当提供的参数数组容量大于元素个数的时候,会直接将这个数组用于存储集合元素并且返回,空余位置用默认值占位
4、第三种:迭代器遍历
5、第四种:增强for循环

格式:
for(元素的数据类型 标识符 : 要遍历的集合) {
}

(1)标识符:表示符直接表示当前一次遍历到的元素
(2)本质:底层是迭代器,如果用增强for循环遍历的同时使用集合对象修改集合,会出现并发修改
异常

五、HashSet保证元素唯一性的原理

(一)HashSet存储JDK提供的类型的元素
HashSet存储JDK提供的类型的元素,可以正常去重
(二)HashSet存储自定义类型的元素
1、HashSet并没有对自定义类型的对象去重,怀疑是对象有不同的地址,我们希望按照属性值去重
2、重写equals方法,按照属性值比较对象,不按照地址值比较对象,但是依然没有去重
3、在重写的equals方法中书写输出语句,发现equals方法根本没有执行
4、结合equals方法根本没有执行,结合去重没有成功,怀疑依然是按照地址值进行去重
5、地址值的由来:getClass().getName() + “@” + Integer.toHexString(hashCode())
6、地址值和hashCode方法相关,看到hashCode方法的三大常规协定,其中规定:对于两个不同的对象,调用hashCode方法不要求返回不同的哈希码值,而地址值的差别又是哈希码值不容而不同的,如果我们结合这个协定,让不同的对象具有相同的哈希码值,那么地址值就完全一样了,就可以排除地址值的干扰了
7、发现重写完hashCode方法,让不同的对象具有相同的哈希码值之后,地址就会完全相同,此时再进行代码执行的时候,发现重写过得equals方法执行了,去重也成功了
(三)HashSet保证元素唯一性的原理
在这里插入图片描述
(四)HashSet保证元素唯一性的操作
1、重写hashCode方法,让不同的对象返回相同的哈希码值,无法根据哈希码值区分不同对象,就只能调用equals判断
原则:相同的对象一定返回相同的哈希码值,不同的对象一定返回不同的哈希码值
2、重写equals方法,让其按照属性值比较,属性值相同的元素去重,属性值不同的元素存储
3、最终操作:alt +shift + s H

public class Person {
	private String name;
	private int age;
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public Person() {
		super();
	}
	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 String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + age;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		return result;
	}
	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Person other = (Person) obj;
		if (age != other.age)
			return false;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
	return true;
	}
	/*@Override
	public boolean equals(Object obj) {
		System.out.println("重写过的equals方法执行了");
		Person p = (Person)obj;
		return this.name.equals(p.name) && this.age == p.age;
	}*/
	/*@Override
	public int hashCode() {
		return 0;
	}*/
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值