1. 泛型
泛型概念: 泛型是一种未知的数据类型,当我们不知道是用什么数据类型的时候,就可以使用泛型。泛型也可以看成是一个变量,用来接收数据类型。
Java泛型中的标记符含义:
E - Element (在集合中使用,因为集合中存放的是元素)
T - Type(Java 类)
K - Key(键)
V - Value(值)
N - Number(数值类型)
? - 表示不确定的java类型
S、U、V - 2nd、3rd、4th types
ArrayList集合在定义的时候,不知道集合中都会存储什么类型的数据,所以使用泛型。创建集合对象的时候,就可以确定数据类型是什么。
1.1 使用泛型的好处
创建集合对象,不使用泛型
好处: 集合不使用泛型,默认的数据类型就是Object类型,可以存储任意类型的数据。
弊端: 不安全,会引发异常。
创建集合对象,使用泛型
好处:
- 避免了类型转换的麻烦,存储的是什么类型,取出的就是什么类型。
- 把运行期异常,提升到了编译期。
弊端: 泛型是什么类型,只能存储什么类型的数据。
import java.util.ArrayList;
import java.util.Iterator;
public class DemoGeneric {
public static void main(String[] args) {
show1();
show2();
}
public static void show1() {
ArrayList list = new ArrayList();
list.add("abc");
list.add(1);
//使用迭代器遍历list集合
Iterator it = list.iterator();
while(it.hasNext()){
//取出的元素也是Object类型
Object obj = it.next();
System.out.println(obj);
//想要使用String特有方法,需要向下转型
String s = (String)obj;
System.out.println(s.length());
//会抛出ClassCastException异常,1不能转成
}
}
public static void show2() {
ArrayList<String> list = new ArrayList<>();
list.add("abc");
//list.add(1);
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
}
}
1.2 定义和使用含有泛型的类
定义一个含有泛型的类,模拟ArrayList集合。
创建对象的时候确定泛型的数据类型
public class GenericClass<E> {
private E name;
public E getName() {
return name;
}
public void setName(E name) {
this.name = name;
}
}
public class DemoGenericClass {
public static void main(String[] args) {
//不写泛型默认Object类型
GenericClass gc = new GenericClass();
gc.setName("abc");
Object obj = gc.getName();
System.out.println(obj);
//创建GenericClass对象,泛型使用Integer类型
GenericClass<Integer> gc1 = new GenericClass<>();
gc1.setName(1);
Integer in = gc1.getName();
System.out.println(in);
//创建GenericClass对象,泛型使用String类型
GenericClass<String> gc2 = new GenericClass<>();
gc2.setName("AAA");
String s = gc2.getName();
System.out.println(s);
}
}
1.3 定义和使用含有泛型的方法
- 定义含有泛型的方法,泛型定义在方法的修饰符和返回值类型之间
- 格式:
修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
方法体;
}
- 含有泛型的方法,在调用方法的时候确定泛型的数据类型。
- 传递什么类型的数据,反省就是什么类型。
public class GenericMethod {
//定义一个含有泛型的方法
public <M> void method(M m){
System.out.println(m);
}
//定义一个含有泛型的静态方法
public static <S> void method2(S s){
System.out.println(s);
}
}
public class DemoGenericMethod {
public static void main(String[] args) {
GenericMethod gm = new GenericMethod();
//传递的参数是什么类型,泛型就是什么类型
gm.method(1);
gm.method("abc");
gm.method(true);
//静态方法不建议创建对象使用
GenericMethod.method2("静态方法");
GenericMethod.method2(10);
}
}
1.4 定义和使用含有泛型的接口
含有泛型的接口:
第一种使用方式:定义接口的实现类,指定接口的泛型
public interface Iterator<E> {
E next();
}
Scanner类实现了Iterator接口,并指定接口的泛型为String,所以重写的next方法泛型默认就是String。
第二种使用方法:接口使用什么泛型,实现类就使用什么泛型,相当于定义了一个含有泛型的类,创建对象的时候确定泛型的类型。
1.5 泛型通配符
?:代表任意的数据类型
使用方法:不能直接创建对象使用,只能作为方法的参数使用。
import java.util.ArrayList;
import java.util.Iterator;
public class DemoGeneric02 {
public static void main(String[] args) {
//ArrayList<?> list = new ArrayList<?>(); 错误写法
ArrayList<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(1);
list2.add(2);
printArray(list1);
printArray(list2);
}
//定义一个方法,可以遍历任何类型的ArrayList集合
public static void printArray(ArrayList<?> list) {
//使用迭代器遍历
Iterator<?> it = list.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
}
}
泛型的上限限定:? extends E 代表使用的泛型只能是E类型的子类/本身
泛型的下限限定:? super E 代表使用的泛型只能是E类型的父类/本身
2. 数据结构
2.1 栈
栈是Vector的一个子类,它实现了一个标准的先进后出的栈,栈本身最重要的就是 push 和 pop。
堆栈只定义了默认构造函数,用来创建一个空栈。堆栈除了包括由Vector定义的所有方法,也定义了自己的一些方法。
创建格式:
Stack<数据类型> 对象名 = new Stack<>();
基本方法:
- boolean empty() 测试堆栈是否为空。
- Object peek( ) 查看堆栈顶部的对象,但不从堆栈中移除它。
- Object pop( ) 移除堆栈顶部的对象,并作为此函数的值返回该对象。
- Object push(Object element) 把项压入堆栈顶部。
- int search(Object element) 返回对象在堆栈中的位置,以 1 为基数。
import java.util.Stack;
public class DemoStack {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
//入栈
stack.push("a");
stack.push("b");
//判断是否为空
System.out.println(stack.empty());
//返回栈顶,但不出栈
System.out.println(stack.peek());
//出栈
String s = stack.pop();
System.out.println(s);
//找a的位置
int i = stack.search("a");
System.out.println(i);
}
}
2.2 队列
队列(Queue)是一种比较特殊的线性结构。它只允许在表的前端进行删除操作,而在表的后端进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头。
队列中最先插入的元素也将最先被删除,对应的最后插入的元素将最后被删除。因此队列又称为“先进先出”的线性表,与栈刚好相反。
基本方法:
- boolean add(E e):增加一个元素。如果队列已满,则抛出一个IIIegaISlabEepeplian异常.
- E remove():移除并返回队列头部的元素 ,如果队列为空,则抛出一个NoSuchElementException异常.
- E element():返回队列头部的元素。如果队列为空,则抛出一个NoSuchElementException异常.
- boolean offer(E e):添加一个元素并返回true。如果队列已满,则返回false.
- E poll():移除并返问队列头部的元素。如果队列为空,则返回null.
- E peek():返回队列头部的元素 。如果队列为空,则返回null.
尽量使用offer()方法添加元素,使用poll()方法移除元素。add()和remove()方法在失败的时候会抛出异常。
import java.util.LinkedList;
import java.util.Queue;
public class DemoQueue {
public static void main(String[] args) {
Queue<String> que = new LinkedList<String>();
//添加元素到队头
que.offer("a");
que.offer("b");
//移除并返回队头
String s = que.poll();
System.out.println(s); //a
//返回队头,不移除
String s2 = que.peek();
System.out.println(s2); //b
}
}
2.3 链表
链表是一种根据元素节点逻辑关系排列起来的一种数据结构。利用链表可以保存多个数据,这一点类似于数组的概念,但是数组本身有一个缺点: 数组的长度固定,不可改变,在长度固定的情况下首选的肯定是数组,但是在现实的开发之中往往要保存的内容长度是不确定的,那么此时就可以利用链表这样的结构来代替数组的使用。
2.4 红黑树
红黑树是一颗二叉查找树,且具有如下特性:
- 每个节点要么是黑色,要么是红色。
- 根节点是黑色。
- 每个叶子节点是黑色(注意:这里叶子节点,是指为空的叶子节点)。
- 如果一个节点是红色的,则它的子节点必须是黑色的。
- 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑色节点。
红黑树的三个基本操作:
1. 左旋:
private Node rotateLeft(Node h){
Node x = h.right;
//把x的左结点赋值给h的右结点
h.right = x.left;
//把h赋值给x的左结点
x.left = h;
x.color = h.color;
h.color = RED;
x.N = h.N;
h.N = 1+ size(h.left) + size(h.right);
return x;
}
2. 右旋:
private Node rotateRight(Node h){
Node x = h.left;
h.left = x.right;
x.right = h;
x.color = h.color;
h.color = RED;
x.N = h.N;
h.N = 1+ size(h.left) + size(h.right);
return x;
}
3. 颜色转换:
private void flipColors(Node h){
h.color = RED;//父结点颜色变红
h.left.color = BLACK;//子结点颜色变黑
h.right.color = BLACK;//子结点颜色变黑
}
3. List集合介绍和常用方法
java.util.List接口 extends Collection接口。
List接口的特点:
- 有序的集合,存储元素和取出元素的顺序是一致的。
- 有索引,包含了一些带索引的方法。
- 允许存储重复的元素。
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):用特定元素替换集合中指定位置的元素,返回值为更新前的元素。
注意: 操作索引的时候,一定要防止越界异常
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class DemoList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
System.out.println(list); //[a, b]
//在a,b中间加个t
list.add(1, "t");
System.out.println(list); //[a, t, b]
//移除b
String remove = list.remove(2);
System.out.println(list); //[a, t]
//用A替换掉a
String set = list.set(0, "A");
System.out.println(list); //[A, t]
//List集合遍历有三种方式
//普通for循环遍历
for (int i = 0; i < list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
//使用迭代器
Iterator<String> it = list.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}
//使用增强for循环
for(String s: list)
System.out.println(s);
}
}
3.1 ArrayList集合
java.util.ArrayList集合数据存储的结构是数组结构。元素增删慢,查询快。
具体使用方法见以前
3.2 LinkedList集合
java.util.LinkedList集合数据存储的结构是链表结构,方便元素添加,删除的集合。
java.util.LinkedList集合 implements List接口。
LinkedList集合的特点:
- 底层是一个链表结构:查询慢,增删快。
- 里面包含了大量操作首尾元素的方法。
注意: 使用LinkedList集合特有的方法,不能使用多态。
基本方法:
- public void addFirst(E e):将指定元素插入此列表的开头;
- public void addLast(E e):将指定元素添加到此列表的结尾(等效于add);
- public void push(E e):将元素推入此列表所表示的堆栈(等效于addFirst);
- public E getFirst():返回此列表的第一个元素;
- public E getLast():返回此列表的最后一个元素;
- public E removeFirst():移除并返回此列表的第一个元素;
- public E removeLast():移除并返回此列表的最后一个元素;
- public E pop():从此列表所表示的堆栈中弹出一个元素(等效于removeFirst);
- public boolean isEmpty():如果此列表为空,返回true。
import java.util.LinkedList;
public class DemoLinkedList {
public static void main(String[] args) {
LinkedList<String> link = new LinkedList<>();
link.add("a");
link.add("b");
link.add("c");
link.addFirst("fff");
System.out.println(link); //[fff, a, b, c]
link.addLast("fff");
System.out.println(link); //[fff, a, b, c, fff]
link.push("push");
System.out.println(link); //[push, fff, a, b, c, fff]
System.out.println(link.getFirst()); //push
System.out.println(link.getLast()); //fff
System.out.println(link.removeFirst()); //push
System.out.println(link.removeLast()); //fff
System.out.println(link.pop()); //fff
}
}
3.3 Vector集合
Vector集合是JDK1.0版本最早期的集合。
Vector类可以实现可增长的对象数组。它包含可以使用整数索引进行访问的组件。但是,Vector的大小可以根据需要增大或减小,以适应创建Vector后进行添加或移除的操作。
3.4 HashSet集合介绍
Set接口:
java.util.Set接口和java.util.List接口一样,同样继承于Collection接口,它与Collection接口中的方法基本一致。
与List接口不同的是:
- Set接口中的元素无序,没有带索引的方法,也不能使用普通的for循环遍历。
- 会保证存入的元素不出现重复。
java.util.HashSet集合 implements Set接口
HashSet特点:
- 不允许存储重复的元素;
- 没有索引,没有带索引的方法,不能使用普通for循环遍历;
- 是一个无序的集合,存储元素和取出元素的顺序有可能不同;
- 底层是一个哈希表结果(查询的速度很快)。
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class DemoSet {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(1);
//使用迭代器遍历
Iterator<Integer> it = set.iterator();
while(it.hasNext()){
Integer in = it.next();
System.out.println(in);
} //1 2
//使用增强for循环遍历
for(Integer i : set){
System.out.println(i);
} //1 2
}
}
3.5 哈希值
哈希值: 是一个十进制的整数,由系统随即给出(就是对象的地址值,是一个逻辑地址,是模拟出来得到的地址,不是数据实际存储的物理地址)。
在Object类中有一个方法,可以获取对象的哈希值:int hashCode() 返回该对象的哈希码值。
3.6 HashSet集合存储数据的结构(哈希表)
JDK1.8版本之前:哈希表 = 数组 + 链表。
JDK1.8版本以后:哈希表 = 数组 + 链表;哈希表 = 数组 + 红黑树(提高查询速度)。
数组结构:把元素进行分组(哈希值相同为一组),再用链表/红黑树结构把相同哈希值的元素连接起来(哈希冲突:元素不同,但哈希值相同)