List的子类
下面我们所讲的每个方法都是某个类所特有的方法,如继承自父类的add、remove这种方法,都不讲。
ArrayList
特性:
底层数据结构是数组,增删慢,查找快。
不同步, 线程不安全, 效率高。
存储null元素
容量会自动扩容
构造方法:
ArrayList(): 默认初始大小为10
ArrayList(int initialCapacity): 可以指定数组的初始大小
ArrayList(Collection c):将集合c中的元素传入ArrayList中
API:
void ensureCapacity(int minCapacity):
作用:避免频繁扩容。
如有必要,增加此 ArrayList 实例的容量,以确保它至少能够容纳最小容量参数所指定的元素数。
应用场景是刚开始我们不知道存储多少元素,用ArrayList():的构造方法去初始化了一个ArrayList,但到了某个时候,知道应该存多少元素了,就用这种方法。
void trimToSize():
将此 ArrayList 实例的容量调整为底层数组的实际存储元素的大小。
慎用,确保元素不会在添加的情况下用。
········································································································································································································
Vector在Collection提出之前就有了
Vector
特性:
底层是数组,增删慢,查找快
同步, 线程安全, 效率比较低
存储null元素
API:
Vector的很多API名字起得都很繁琐,它的API其实就类似于右侧—>后的collection中的API
void addElement(E obj) --> void add(E e)
void copyInto(Object[] anArray) --> Object[] toArray()
E elementAt(int index) --> E get(int index)
void insertElementAt(E obj, int index) --> void add(int index, E e)
void removeAllElements() --> void clear()
boolean removeElement(Object obj) --> boolean remove(Object obj)
void removeElementAt(int index) --> E remove(int index)
void setElementAt(E obj, int index) --> E set(int index)
Enumeration<E> elements() --> Iterator iterator()
int capacity() //返回此向量的当前容量。默认也是10
void setSize(int newSize) //设置此向量的大小。
capacity和size不一样,capacity是Vector占用的总空间的大小,而size是Vector中真正存储元素的总空间的大小
E firstElement()
E lastElement()
//返回第一个和最后一个元素,如果Vector为空,报错:NoSuchElement
int indexOf(Object o, int index)
//从index开始向后查找元素o,返回查到的第一个o的索引下标
int lastIndexOf(Object o, int index)
//从index开始向前查找元素o,返回查到的第一个o的索引下标
Enumeration<E> elements()
接下来重点看一下 Enumeration elements()这个方法,Enumeration是一个接口,这是一个古老的接口。其实可以类比Iterator这个接口。但是实际开发中还是用Iterator遍历Vector吧。
Enumeration: --> Iterator
boolean hasMoreElements() --> boolean hasNext()
E nextElement() --> E next()
public class VectorDemo2 {
public static void main(String[] args) {
Vector vector = new Vector();
vector.add("hello");
vector.add("world");
vector.add("java");
for(Enumeration e = vector.elements(); e.hasMoreElements(); ) {
String s = (String) e.nextElement();
System.out.println(s);
}
for(Iterator it = vector.iterator(); it.hasNext(); ) {
String s = (String) it.next();
System.out.println(s);
}
}
}
········································································································································································································
栈和队列:
········································································································································································································
LinkedList
LinkedList实现了Deque接口。
关于Deque(双端队列)
addFirst表示插入时队列为空会抛出异常。
removeFirst和getFirst表示取队列元素或移除队列元素时队空会抛出异常。
offerFirst表示插入队头时,队满会返回一个特殊值null。
pollFirst和peekFirst表示取队列元素或移除队列元素时队空会返回一个特殊值null。
后面两列和前面一样,只不过是在队尾进行操作。
我们发现,一个双端队列可以模拟栈和队列,可是竟然发现Deque没有isEmpty方法。
发现Deque继承自Queue接口,Queue接口继承自Collection接口,而Collection接口中有isEmpty方法。
Deque接口
概述:双端队列,可以在两端插入和删除
它包含了栈和队列的API,当你在一端插入一端删除时,就是队列
当你在一端进行插入删除时,就是队列。
LinkedList implements List, Deque
特性:
底层数据结构是链表,增删快,查找慢
不同步, 线程不安全, 效率高
允许null元素
实现了Deque这个接口,可以当作栈,队列和双端队列来使用
构造方法:
LinkedList() //默认初始大小为10
LinkedList(Collection c) //将集合c中的元素复制到LinkedList中
API:
Iterator descendingIterator() //逆向遍历LinkedList中的元素
boolean removeFirstOccurrence(Object o)
//从此双端队列移除第一次出现的指定元素。
boolean removeLastOccurrence(Object o)
//从此双端队列移除最后一次出现的指定元素。
在两端的操作
boolean offerFirst
boolean pollFirst
boolean peekFirst
栈的API:
void push(E e)
E pop()
E peek()
注意事项:Java中提供了Stack类,但是我们应该优先使用Deque, 而不应该使用Stack
为什么?
a. Stack同步的, 效率相对来说比较低。
b. Stack继承了Vector, 所以Stack拥有Vector中所有的方法, 使用起来不安全。
Stack extends Vector
Stack extends Vector,所以Stack不仅有自己的API,还有继承自Stack的API,那Stack就可以在任意指定的位置删除和插入元素了。
而Deque只能在两端进行操作,会比Stack安全很多。
Stack和Vector在java1.0时就有了,在很早以前设计的。
应当少用继承,多用组合。
······································································································································································································
练习1:去重
package com.cskaoyan.exercise;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
/*
1.去重
输入:[a, b, c, a, a, b, c]
输出:[a, b, c]
思路1:
1. 新键一个List result = new ArrayList();
2. 遍历原来的list, 判断元素是否在result中存在
存在:不添加
不存在:添加
3. 遍历结束后, 返回result
思路2:
1. 逆序遍历list
2. 获取元素, 截取从0到nextIndex的子列表,判断元素是否在子列表中存在
存在:删除元素
不存在:
3. 遍历结束后,完成去重
*/
public class Ex2 {
public static List disctinct1(List list) {
List result = new ArrayList();
for (Iterator it = list.iterator(); it.hasNext(); ) {
Object obj = it.next();
if (!result.contains(obj)) result.add(obj);
}
return result;
}
public static void disctinct2(List list) {
for (ListIterator it = list.listIterator(list.size()); it.hasPrevious(); ) {
Object previous = it.previous();
//subList(fromIndex,toIndex) 范围[fromIndex,toIndex)
if (list.subList(0, it.nextIndex()).contains(previous)) {
it.remove();
}
}
}
public static void main(String[] args) {
// [a, b, c, a, a, b, c]
List list = new ArrayList();
list.add('a');
list.add('b');
list.add('c');
list.add('a');
list.add('a');
list.add('b');
list.add('c');
// List result = disctinct1(list);
disctinct2(list);
System.out.println(list);
}
}
练习2:请用ArrayList实现栈数据结构,并测试。
以下代码这样的设计方式叫组合
a. 如果一个类持有某个类的成员,那么就能够"拥有"这个类的所有公共方法。(像继承)
但是我们可以对这些方法进行限制。
b. 组合还可以"加强"另一个的方法,如下面的push方法。
c. 可以组合多个对象
"加强"一个方法,既可以用 继承,也可以用 组合
继承只能单一继承,组合可以组合多个对象,如我还可以设计一个String类对象,在里面调用String的成员方法。
设计原则:
优先使用组合,而不是继承
什么情况下,可以使用继承呢?
如果两个类之间有"is a"的关系,可以使用继承。
GO语言不支持继承。
为啥不用继承,就向上一个代码中 Stack继承了Vector, 所以Stack拥有Vector中所有的方法, 使用起来不安全。
栈只要有在栈头插入和删除元素就行了,你却让有一个父类中的add(int index, E element) 方法,让Stack可以在任意位置插入元素。
下面两个练习:
3. 集合的嵌套遍历
4. 获取10个1-20之间的随机整数,要求集合中的数不能重复
留给晚上作业。
······································································································································································································
JDK5新特性
一、静态导入
导包
必须导入到类这一级别
作用:导入的类就好像定义在当前这个包下面。
静态导入:
必须导入到方法这一级别,并且必须是静态方法。
作用:导入的静态方法就好像定义在当前这个类中一样。
推荐:不要使用静态导入,可读性差。
别人以为你的sqrt方法是自己定义的
面试题:
总结:static的用法有哪些?
a. 静态代码块:对类进行初始化,类加载的时候执行,并且只执行一次
b. 静态变量:该变量是类所有,被该类的所有成员共享
c. 静态方法
d. 静态内部类
e. 静态导入:导入静态方法
package com.cskaoyan.jdk5;
import java.util.Date;
//静态导入
//使用Math的所用方法用import static java.lang.Math.*;
//使用Math的某一个方法用import static java.lang.Math.sqrt;
import static java.lang.Math.*;
public class StaticImportDemo1 {
public static void main(String[] args) {
//导包的两种方法
java.util.Date date = new java.util.Date();
Date date2 = new Date();
//静态导入
System.out.println(sqrt(1.0));
System.out.println(abs(-100));
System.out.println(max(2, 3));
}
}
······································································································································································································
泛型
不用泛型的话,会发现list里面就算添加了个1,编译时也不会报错,而运行时则会报:ClassCastException 类型转换异常。
public class GenericDemo1 {
public static void main(String[] args) {
List list = new ArrayList();
list.add("hello");
list.add("world");
list.add("java");
//编译时不会报错
list.add(1);
doSomething(list);
}
private static void doSomething(List list) {
for(Iterator it = list.iterator(); it.hasNext(); ) {
String s = (String) it.next(); // ClassCastException
s.toUpperCase();
}
}
}
用了泛型后,就不允许你add(1)。更不容易出错。
public class GenericDemo1 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("hello");
list.add("world");
list.add("java");
// list.add(1);
doSomething(list);
}
private static void doSomething(List list) {
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
String s = it.next();
System.out.println(s.toUpperCase());
}
}
}
泛型的好处:
a. 提高了程序的安全性
b. 将运行期遇到的问题转移到了编译期
c. 省去了类型强转的麻烦
设计原则:
及早失败原则(如果你的程序有问题,就让他尽早把问题显示出来)
这样方便你尽早定位出错的原因。就向扁鹊见蔡恒公,及早发现,及早治疗
对大家的要求:
a. 可以利用泛型操作集合
b. 能够看懂别人些的泛型代码
······································································································································································································
泛型类
把泛型定义在类上
格式:public class 类名<泛型类型1,…>
注意:参数化类型必须是引用类型
package com.cskaoyan.jdk5;
/*
泛型类:
泛型定义在类上面
作用域:整个类
格式: public class 类名<泛型类型1,…>
泛型的命名规则:
必须满足标识符的规则
业界规范:
一般用大写字母表示
T:type
E:element
K:key
V:value
*/
public class Tool2<T> { // 定义泛型, T就是泛型, 类似于形式参数
T obj; // 使用泛型
public T getObj() {
return obj;
}
public void setObj(T obj) {
this.obj = obj;
}
}
package com.cskaoyan.jdk5;
/*
创建对象的时候指定类型:
默认是Object类型
JDK7特性:<>棱形操作符,可以利用类型推断机制。
*/
public class GenericDemo2 {
public static void main(String[] args) {
// String是参数化类型, 类似实参
//就用下面这行代码初始化一个泛型类对象就行。
Tool2<String> tool2 = new Tool2<>();
tool2.setObj("hello");
// tool2.setObj(1);
String s = tool2.getObj();
System.out.println(s);
Tool2<Object> tool1 = new Tool2();
tool1.setObj("hello");
Object obj = tool1.getObj();
}
}
······································································································································································································
泛型方法
把泛型定义在方法上
格式:public <泛型类型> 返回类型 方法名(泛型类型 形参…)
为什么泛型要定义在返回值类型的前面呢?
因为返回值类型也可以是泛型,泛型必须先定义才能使用
问题:
有泛型方法的类一定是泛型类吗? 不是
类似这种问题,定义多个相似的方法,怎么改进?
public class Tool3 {
public String echo(String s) {
return s;
}
public Date echo(Date s) {
return s;
}
}
没有泛型之前可以这样做,缺点是需要强制类型转换
public class Tool3 {
public Object echo(Object obj) {
return obj;
}
}
有了泛型之后:
public class Tool3 {
// T t;
//泛型方法中的<T>是根据实参的类型来定义的
//传给他的是String,T就代表String
public <T> T echo(T t) {
return t;
}
}
······································································································································································································
泛型接口
把泛型定义在接口上
作用域:接口内
格式:public interface 接口名<泛型类型1…>
package com.cskaoyan.jdk5;
public interface Auto<T> {
T run(T t);
}
泛型的实现类:
1.普通类
package com.cskaoyan.jdk5;
public class Car implements Auto<String> {
@Override
public String run(String s) {
return null;
}
}
2.泛型类
package com.cskaoyan.jdk5;
//第一种情况,Auto中传递的是具体的参数
/*public class Bus<T> implements Auto<String> {
@Override
public String run(String s) {
return null;
}
}*/
//第二种情况,Auto中传递的是具体的参数
// Bus<T>是定义泛型T, Auto<T>是使用泛型T
//这两个T是同一个T
//创建对象时,就会给第一个T赋值,赋的值就会传递给第二个T
//然后第二个T会传递给泛型接口Auto中的T
public class Bus<T> implements Auto<T> {
@Override
public T run(T t) {
return null;
}
}
······································································································································································································
泛型通配符:
① 泛型通配符<?>
任意类型,如果没有明确,那么就是Object以及任意的Java类了
② ? extends E
向下限定,E及其子类
③ ? super E
向上限定,E及其父类
引入:
数组:
问题1:String是Object的子类吗? 是
问题2:String[]是Object[]的子类吗? 不是
父类的引用变量可以指向子类的对象。
JVM对数组进行特殊"照顾",但是这样也会引入一些问题。
public class GenericDemo4 {
public static void main(String[] args) {
String[] strs = {"hello", "world", "java"};
Object[] objs = strs; // JVM对数组进行特殊"照顾"
objs[1] = new Date(); // ArrayStoreException
//编译时不报错,运行时会报错,因为不能在String数组里添加Date元素
List<String> strs2 = new ArrayList<>();
strs2.add("hello");
strs2.add("world");
strs2.add("java");
//对于泛型,这样做是不允许的
//List<Object> objs2 = strs;
}
}
泛型通配符:
目的:提供类似数组的功能,但是不要引入数组中可能出现的问题。
public class GenericDemo5 {
public static void main(String[] args) {
Collection<Object> c1 = new ArrayList<Object>();
//根据引例代码,下面三行代码编译时不会通过的
/*Collection<Object> c2 = new ArrayList<Animal>();
Collection<Object> c3 = new ArrayList<Cat>();
Collection<Object> c4 = new ArrayList<Dog>();*/
//而将Object改成?就能解决问题
Collection<?> c11 = new ArrayList<Object>();
Collection<?> c2 = new ArrayList<Animal>();
Collection<?> c3 = new ArrayList<Cat>();
Collection<?> c4 = new ArrayList<Dog>();
//但是为了防止发生错误,不能添加元素。因为我只知道集合中存的元素是Object子类,
// 万一我指向的是cat类(如c3),但你添加的是一个dog类,就会出错。
// c2.add(new Dog());
// c2.add(new Animal());
// Collection<? extends Animal> c13 = new ArrayList<Object>();
Collection<? extends Animal> c23 = new ArrayList<Animal>();
Collection<? extends Animal> c33 = new ArrayList<Cat>();
Collection<? extends Animal> c43 = new ArrayList<Dog>();
//依然不可以添加元素,因为我只知道集合中存的元素是Animal子类,
//万一我指向的是cat类(如c33),但你添加的是一个dog类,就会出错。
/*c3.add(new Animal());
c3.add(new Dog());
c3.add(new Cat());*/
Collection<? super Animal> c14 = new ArrayList<Object>();
Collection<? super Animal> c24 = new ArrayList<Animal>();
// Collection<? super Animal > c3 = new ArrayList<Cat>();
// Collection<? super Animal> c4 = new ArrayList<Dog>();
//可以添加Animal和Animal子类对象,因为我知道里面的元素是Animal或Animal父类
//就可以指向Animal或Animal子类对象
c24.add(new Animal());
c24.add(new Dog());
c24.add(new Cat());
// c2.add(new Object());
}
}
class Animal {
}
class Dog extends Animal {
}
class Cat extends Animal {
}
······································································································································································································
作业:
- 生成20个 1-20 的随机整数,把其中不重复的整数存入 List 集合中(相同的整数只存一个)。
public class Homework02 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>(20);
for (int i = 0; i < 20; i++) {
int k = (int) (Math.random() * 20 + 1);
if (!list.contains(k)) {
list.add(k);
}
}
System.out.println(list);
}
}
或者
public class Homework02_teacher {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 20; i++) {
int num = r.nextInt(20) + 1;
if (!list.contains(num)) list.add(num);
}
System.out.println(list);
}
}
关于Math.random()和new random()
https://www.cnblogs.com/ningvsban/p/3590722.html
- 用双向链表实现LRU,要求可以指定缓存大小,并且可以存储任意类型的数据。
(要求用泛型,只需要实现添加方法即可)。
public class LRU<E> {
private static final int DEFAULT_CAPACITY = 100;
private int capacity;
private int size;
private Node head = new Node(null); //Dummy node
private Node end = new Node(null); //Dummy node
public LRU() {
head.next = end;
end.prev = head;
capacity = DEFAULT_CAPACITY;
}
public LRU(int capacity) {
if (capacity <= 0) {
throw new IllegalArgumentException("capacity=" + capacity);
}
head.next = end;
end.prev = head;
this.capacity = capacity;
}
private class Node {
E value;
Node prev;
Node next;
public Node(E value) {
this.value = value;
}
public Node(E value, Node prev, Node next) {
this.value = value;
this.prev = prev;
this.next = next;
}
}
public void add(E element) {
Node node = head.next;
while (node != end) {
if (node.value == element) break;
node = node.next;
}
// 存在
if (node != end) {
// 删除node
node.prev.next = node.next;
node.next.prev = node.prev;
// 在头部添加
node.prev = head;
node.next = head.next;
head.next.prev = node;
head.next = node;
} else {
Node x = new Node(element, head, head.next);
// 不存在,且容量未满
if (size < capacity) {
head.next.prev = x;
head.next = x;
size++;
} else {
// 不存在,且容量满了
// 删除最后一个元素
end.prev = end.prev.prev;
end.prev.next = end;
// 在头部添加
head.next.prev = x;
head.next = x;
}
}
}
}