JAVA基础教程
学习JAVA网站 : how2j
第三讲 集合框架 - ArrayList
3.1 与数组的区别
- 使用数组的局限性
如果要存放多个对象,可以使用数组,但是数组有局限性: 比如 声明长度是10的数组,不用的数组就浪费了,超过10的个数,又放不下。
package collection;
import charactor.Hero;
public class TestCollection {
public static void main(String[] args) {
//数组的局限性
Hero heros[] = new Hero[10];
//声明长度是10的数组
//不用的数组就浪费了
//超过10的个数,又放不下
heros[0] = new Hero("盖伦"); //放不下要报错
heros[20] = new Hero("提莫");
}
}
复制代码
- 重写了toString的Hero
package charactor;
public class Hero {
public String name;
public float hp;
public int damage;
public Hero() { }
// 增加一个初始化name的构造方法
public Hero(String name) {
this.name = name;
}
// 重写toString方法
public String toString() {
return name;
}
}
复制代码
- ArrayList存放对象
为了解决数组的局限性,引入容器类的概念。 最常见的容器类就是ArrayList。 容器的容量"capacity"会随着对象的增加,自动增长;只需要不断往容器里增加英雄即可,不用担心会出现数组的边界问题。
package collection;
import java.util.ArrayList;
import charactor.Hero;
public class TestCollection {
@SuppressWarnings("rawtypes")
public static void main(String[] args) {
//容器类ArrayList,用于存放对象
ArrayList heros = new ArrayList();
heros.add( new Hero("盖伦"));
System.out.println(heros.size());
//容器的容量"capacity"会随着对象的增加,自动增长
//只需要不断往容器里增加英雄即可,不用担心会出现数组的边界问题。
heros.add( new Hero("提莫"));
System.out.println(heros.size());
}
}
复制代码
3.2 常用方法
- 增加
add 有两种用法 第一种是直接add对象,把对象加在最后面
heros.add(new Hero("hero " + i));
复制代码
第二种是在指定位置加对象
heros.add(3, specialHero);
复制代码
public class TestCollection {
public static void main(String[] args) {
ArrayList heros = new ArrayList();
// 把5个对象加入到ArrayList中
for (int i = 0; i < 5; i++) {
heros.add(new Hero("hero " + i));
}
System.out.println(heros);
// 在指定位置增加对象
Hero specialHero = new Hero("special hero");
heros.add(3, specialHero);
System.out.println(heros.toString());
}
}
复制代码
- 判断是否存在
通过方法contains 判断一个对象是否在容器中 判断标准: 是否是同一个对象,而不是name是否相同
ArrayList heros = new ArrayList();
Hero specialHero = new Hero("special hero");
heros.add(specialHero);
复制代码
- 获取指定位置的对象
通过get获取指定位置的对象,如果输入的下标越界,一样会报错。
heros.get(5)
复制代码
- 获取对象所处的位置
indexOf用于判断一个对象在ArrayList中所处的位置 与contains一样,判断标准是对象是否相同,而非对象的name值是否相等
heros.indexOf(specialHero)
复制代码
- 删除
remove用于把对象从ArrayList中删除
//remove可以根据下标删除ArrayList的元素。
heros.remove(2);
//也可以根据对象删除
heros.remove(specialHero);
复制代码
- 替换
set用于替换指定位置的元素
heros.set(5, new Hero("hero 5")
复制代码
- 获取大小
size 用于获取ArrayList的大小
System.out.println(heros.size());
复制代码
- 转换为数组
toArray可以把一个ArrayList对象转换为数组。 需要注意的是,如果要转换为一个Hero数组,那么需要传递一个Hero数组类型的对象给toArray(),这样toArray方法才知道,你希望转换为哪种类型的数组,否则只能转换为Object数组
public class TestCollection {
public static void main(String[] args) {
ArrayList heros = new ArrayList();
// 初始化5个对象
for (int i = 0; i < 5; i++) {
heros.add(new Hero("hero " + i));
}
Hero specialHero = new Hero("special hero");
heros.add(specialHero);
System.out.println(heros);
Hero hs[] = (Hero[])heros.toArray(new Hero[]{});
System.out.println("数组:" +hs);
}
}
复制代码
- 把另一个容器所有对象都加进来
addAll 把另一个容器所有对象都加进来
heros.addAll(anotherHeros);
复制代码
- 清空
clear 清空一个ArrayList
heros.clear();
复制代码
3.3 List接口
- ArrayList和List
ArrayList实现了接口List,常见的写法会把引用声明为接口List类型。
public static void main(String[] args) {
//ArrayList实现了接口List
//常见的写法会把引用声明为接口List类型
//注意:是java.util.List,而不是java.awt.List
//接口引用指向子类对象(多态)
List heros = new ArrayList();
heros.add(new Hero("盖伦"));
System.out.println(heros.size());
}
复制代码
- List接口的方法
因为ArrayList实现了List接口,所以List接口的方法ArrayList都实现了。 在ArrayList 常用方法章节有详细的讲解,在此不作赘述
3.4 泛型Generic
不指定泛型的容器,可以存放任何类型的元素;指定了泛型的容器,只能存放指定类型的元素以及其子类。
Item.java
package property;
public class Item {
String name;
int price;
public Item(){ }
//提供一个初始化name的构造方法
public Item(String name){
this.name = name;
}
public void effect(){
System.out.println("物品使用后,可以有效果");
}
}
复制代码
TestCollection.java
//对于不使用泛型的容器,可以往里面放英雄,也可以往里面放物品
List heros = new ArrayList();
heros.add(new Hero("盖伦"));
//本来用于存放英雄的容器,现在也可以存放物品了
heros.add(new Item("冰杖"));
//引入泛型Generic
//声明容器的时候,就指定了这种容器,只能放Hero,放其他的就会出错
List<Hero> genericheros = new ArrayList<Hero>();
genericheros.add(new Hero("盖伦"));
//如果不是Hero类型,根本就放不进去
//genericheros.add(new Item("冰杖"));
//除此之外,还能存放Hero的子类
genericheros.add(new APHero());
//并且在取出数据的时候,不需要再进行转型了,因为里面肯定是放的Hero或者其子类
Hero h = genericheros.get(0);
复制代码
- 泛型的简写
为了不使编译器出现警告,需要前后都使用泛型,像这样:
List<Hero> genericheros = new ArrayList<Hero>();
复制代码
- 练习-支持泛型的ArrayList
借助泛型和前面学习的面向对象的知识,设计一个ArrayList,使得这个ArrayList里,又可以放Hero,又可以放Item,但是除了这两种对象,其他的对象都不能放
@SuppressWarnings({ "serial", "rawtypes" })
public class newArrayList extends ArrayList
{
@SuppressWarnings("unchecked")
public boolean add(Object o) {
if (o instanceof Hero || o instanceof Item) {
super.add(o);
return true;
}
else {
System.out.println(o.getClass().getName() + "非Hero和Item以及它们的子类,容器不能承放!");
return false;
}
}
}
List h = new newArrayList();
复制代码
3.5 遍历
- 用for循环遍历
for (int i = 0; i < heros.size(); i++) {
Hero h = heros.get(i);
System.out.println(h);
}
复制代码
- 迭代器遍历
使用迭代器Iterator遍历集合中的元素
//第二种遍历,使用迭代器
System.out.println("--------使用while的iterator-------");
Iterator<Hero> it= heros.iterator();
//从最开始的位置判断"下一个"位置是否有数据
//如果有就通过next取出来,并且把指针向下移动
//直到"下一个"位置没有数据
while(it.hasNext()){
Hero h = it.next();
System.out.println(h);
}
//迭代器的for写法
System.out.println("--------使用for的iterator-------");
for (Iterator<Hero> iterator = heros.iterator(); iterator.hasNext();) {
Hero hero = (Hero) iterator.next();
System.out.println(hero);
}
复制代码
- 用增强型for循环
使用增强型for循环可以非常方便的遍历ArrayList中的元素,这是很多开发人员的首选。
不过增强型for循环也有不足: 1)无法用来进行ArrayList的初始化 2)无法得知当前是第几个元素了,当需要只打印单数元素的时候,就做不到了。 必须再自定下标变量。
// 第三种,增强型for循环
System.out.println("--------增强型for循环-------");
for (Hero h : heros) {
System.out.println(h);
}
复制代码
第三讲 集合框架 - 其他集合
3.6 LinkedList
序列分先进先出FIFO,先进后出FILO;FIFO在Java中又叫Queue 队列;FILO在Java中又叫Stack 栈。
- LinkedList 与 List接口
与ArrayList一样,LinkedList也实现了List接口,诸如add,remove,contains等等方法。 详细使用,请参考 ArrayList 常用方法,在此不作赘述。
接下来要讲的是LinkedList的一些特别的地方。
- 双向链表 - Deque
除了实现了List接口外,LinkedList还实现了双向链表结构Deque,可以很方便的在头尾插入删除数据。
什么是链表结构: 与数组结构相比较,数组结构,就好像是电影院,每个位置都有标示,每个位置之间的间隔都是一样的。 而链表就相当于佛珠,每个珠子,只连接前一个和后一个,不用关心除此之外的其他佛珠在哪里。
//LinkedList是一个双向链表结构的list
LinkedList<Hero> ll =new LinkedList<Hero>();
//所以可以很方便的在头部和尾部插入数据
//在最后插入新的英雄
ll.addLast(new Hero("hero1"));
ll.addLast(new Hero("hero2"));
ll.addLast(new Hero("hero3"));
System.out.println(ll);
//在最前面插入新的英雄
ll.addFirst(new Hero("heroX"));
System.out.println(ll);
//查看最前面的英雄
System.out.println(ll.getFirst());
//查看最后面的英雄
System.out.println(ll.getLast());
//查看不会导致英雄被删除
System.out.println(ll);
//取出最前面的英雄
System.out.println(ll.removeFirst());
//取出最后面的英雄
System.out.println(ll.removeLast());
//取出会导致英雄被删除
System.out.println(ll);
复制代码
- 队列 - Queue
LinkedList 除了实现了List和Deque外,还实现了Queue接口(队列)。 Queue是先进先出队列 FIFO,常用方法: offer 在最后添加元素 poll 取出第一个元素 peek 查看第一个元素
//和ArrayList一样,LinkedList也实现了List接口
List ll =new LinkedList<Hero>();
//所不同的是LinkedList还实现了Deque,进而又实现了Queue这个接口
//Queue代表FIFO 先进先出的队列
Queue<Hero> q= new LinkedList<Hero>();
//加在队列的最后面
System.out.print("初始化队列:\t");
q.offer(new Hero("Hero1"));
q.offer(new Hero("Hero2"));
q.offer(new Hero("Hero3"));
q.offer(new Hero("Hero4"));
System.out.println(q);
System.out.print("把第一个元素取poll()出来:\t");
//取出第一个Hero,FIFO 先进先出
Hero h = q.poll();
System.out.println(h);
System.out.print("取出第一个元素之后的队列:\t");
System.out.println(q);
//把第一个拿出来看一看,但是不取出来
h=q.peek();
System.out.print("查看peek()第一个元素:\t");
System.out.println(h);
System.out.print("查看并不会导致第一个元素被取出来:\t");
System.out.println(q);
复制代码
3.7 HashMap
- HashMap的键值对
HashMap储存数据的方式是—— 键值对
package collection;
import java.util.HashMap;
public class TestCollection {
public static void main(String[] args) {
HashMap<String,String> dictionary = new HashMap<>();
dictionary.put("adc", "物理英雄");
dictionary.put("apc", "魔法英雄");
dictionary.put("t", "坦克");
System.out.println(dictionary.get("t"));
}
}
复制代码
- 键不能重复,值可以重复
对于HashMap而言,key是唯一的,不可以重复的。 所以,以相同的key 把不同的value插入到 Map中会导致旧元素被覆盖,只留下最后插入的元素。 不过,同一个对象可以作为值插入到map中,只要对应的key不一样
package collection;
import java.util.HashMap;
import charactor.Hero;
public class TestCollection {
public static void main(String[] args) {
HashMap<String,Hero> heroMap = new HashMap<String,Hero>();
heroMap.put("gareen", new Hero("gareen1"));
System.out.println(heroMap);
//key为gareen已经有value了,再以gareen作为key放入数据,会导致原英雄,被覆盖
//不会增加新的元素到Map中
heroMap.put("gareen", new Hero("gareen2"));
System.out.println(heroMap);
//清空map
heroMap.clear();
Hero gareen = new Hero("gareen");
//同一个对象可以作为值插入到map中,只要对应的key不一样
heroMap.put("hero1", gareen);
heroMap.put("hero2", gareen);
System.out.println(heroMap);
}
}
复制代码
- 练习-查找内容性能比较
准备一个ArrayList其中存放3000000(三百万个)Hero对象,其名称是随机的,格式是hero-[4位随机数] hero-3229 hero-6232 hero-9365 ...
因为总数很大,所以几乎每种都有重复,把名字叫做 hero-5555的所有对象找出来 要求使用两种办法来寻找
- 不使用HashMap,直接使用for循环找出来,并统计花费的时间
- 借助HashMap,找出结果,并统计花费的时间
public class TestCollection {
public static void main(String[] args) {
ArrayList<Hero> heros = new ArrayList<Hero>();
Random r = new Random();
//不适用hashmap 用for找出并统计时间
long start = System.currentTimeMillis();
for (int i = 0;i < 3000000;i++){
String name = "hero-" + (r.nextInt(9999-1000) + 1000);
heros.add(new Hero(name));
}
int sum = 0;
for (Hero hero:heros){
if (hero.name.equals("hero-5555"))
sum++;
}
long end = System.currentTimeMillis();
System.out.println("string总共耗时了:"+(end-start)+"ms,hero-5555共有:" + sum + "个。");
}
}
复制代码
string总共耗时了:1965ms,hero-5555共有:350个。
ublic class TestCollection {
public static void main(String[] args) {
ArrayList<Hero> heros = new ArrayList<Hero>();
Random r = new Random();
HashMap<String,Integer> num = new HashMap<String, Integer>();
//不适用hashmap 用for找出并统计时间
long start = System.currentTimeMillis();
for (int i = 0;i < 3000000;i++){
String name = "hero-" + (r.nextInt(9999-1000) + 1000);
heros.add(new Hero(name));
if (num.get(name) == null)
num.put(name,0);
else
num.put(name,num.get(name) + 1);
}
int sum = num.get("hero-5555");
long end = System.currentTimeMillis();
System.out.println("string总共耗时了:"+(end-start)+"ms,hero-5555共有:" + sum + "个。");
}
}
复制代码
string总共耗时了:3181ms,hero-5555共有:330个。
3.8 HashSet
- 元素不能重复
Set中的元素,不能重复:
package collection;
import java.util.HashSet;
public class TestCollection {
public static void main(String[] args) {
HashSet<String> names = new HashSet<String>();
names.add("gareen");
System.out.println(names);
//第二次插入同样的数据,是插不进去的,容器中只会保留一个
names.add("gareen");
System.out.println(names);
}
}
复制代码
- 没有顺序
Set中的元素,没有顺序。严格的说,是没有按照元素的插入顺序排列。
HashSet的具体顺序,既不是按照插入顺序,也不是按照hashcode的顺序。关于hashcode有专门的章节讲解: hashcode 原理。
以下是HashSet源代码中的部分注释
/**
* It makes no guarantees as to the iteration order of the set;
* in particular, it does not guarantee that the order will remain constant over time.
*/
复制代码
不保证Set的迭代顺序; 确切的说,在不同条件下,元素的顺序都有可能不一样。
换句话说,同样是插入0-9到HashSet中, 在JVM的不同版本中,看到的顺序都是不一样的。 所以在开发的时候,不能依赖于某种臆测的顺序,这个顺序本身是不稳定的
public static void main(String[] args) {
HashSet<Integer> numbers = new HashSet<Integer>();
numbers.add(9);
numbers.add(5);
numbers.add(1);
// Set中的元素排列,不是按照插入顺序
System.out.println(numbers);
}
复制代码
- 遍历
Set不提供get()来获取指定位置的元素,所以遍历需要用到迭代器,或者增强型for循环。
package collection;
import java.util.HashSet;
import java.util.Iterator;
public class TestCollection {
public static void main(String[] args) {
HashSet<Integer> numbers = new HashSet<Integer>();
for (int i = 0; i < 20; i++) {
numbers.add(i);
}
//Set不提供get方法来获取指定位置的元素
//numbers.get(0)
//遍历Set可以采用迭代器iterator
for (Iterator<Integer> iterator = numbers.iterator(); iterator.hasNext();) {
Integer i = (Integer) iterator.next();
System.out.println(i);
}
//或者采用增强型for循环
for (Integer i : numbers) {
System.out.println(i);
}
}
}
复制代码
- HashSet和HashMap的关系
通过观察HashSet的源代码(如何查看源代码) 可以发现HashSet自身并没有独立的实现,而是在里面封装了一个Map. HashSet是作为Map的key而存在的 而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。
private static final Object PRESENT = new Object();
复制代码
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
{
//HashSet里封装了一个HashMap
private HashMap<E,Object> map;
private static final Object PRESENT = new Object();
//HashSet的构造方法初始化这个HashMap
public HashSet() {
map = new HashMap<E,Object>();
}
//向HashSet中增加元素,其实就是把该元素作为key,增加到Map中
//value是PRESENT,静态,final的对象,所有的HashSet都使用这么同一个对象
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//HashSet的size就是map的size
public int size() {
return map.size();
}
//清空Set就是清空Map
public void clear() {
map.clear();
}
//迭代Set,就是把Map的键拿出来迭代
public Iterator<E> iterator() {
return map.keySet().iterator();
}
}
复制代码
3.9 Collection
- Collection是一个接口
Collection是 Set List Queue和 Deque的接口 Queue: 先进先出队列 Deque: 双向链表
**注:**Collection和Map之间没有关系,Collection是放一个一个对象的,Map 是放键值对的 **注:**Deque 继承 Queue,间接的继承了 Collection
3.10 Collections
Collections是一个类,容器的工具类,就如同Arrays是数组的工具类
- 反转
reverse 使List中的数据发生翻转
//初始化集合numbers
List<Integer> numbers = new ArrayList<>();
Collections.reverse(numbers);
复制代码
- 混淆
shuffle 混淆List中数据的顺序
//初始化集合numbers
List<Integer> numbers = new ArrayList<>();
Collections.shuffle(numbers);
复制代码
- 排序
sort 对List中的数据进行排序
//初始化集合numbers
List<Integer> numbers = new ArrayList<>();
Collections.sort(numbers);
复制代码
- 交换
//初始化集合numbers
List<Integer> numbers = new ArrayList<>();
Collections.swap(numbers,0,5);
复制代码
- 滚动
rotate 把List中的数据,向右滚动指定单位的长度
//初始化集合numbers
List<Integer> numbers = new ArrayList<>();
Collections.rotate(numbers,2);
复制代码
- 线程安全化
synchronizedList 把非线程安全的List转换为线程安全的List。 因为截至目前为止,还没有学习线程安全的内容,暂时不展开。 线程安全的内容将在多线程章节展开。
List<Integer> numbers = new ArrayList<>();
System.out.println("把非线程安全的List转换为线程安全的List");
List<Integer> synchronizedNumbers = (List<Integer>) Collections.synchronizedList(numbers);
复制代码