在编程过程中,我们有很多时候都会遇到存储一组数据的问题,这个时候我们第一反应就是用数组,但是,数组的长度在声明的时候就已经给定,不可改变,我们面临着数组的扩容问题:数组的大小固定,不可直接扩容,我们只可以通过数组复制的方法变相的给数组扩容:
利用Arrays.copyOf(param1,param2);
第一个参数是要复制的数组,第二个参数新的长度,返回的是一个数组。
public static void main(String[] args) {
int[] arr1 = {1,2,3,5,4};
int[] arr2 = Arrays.copyOf(arr1, 4);
//减少数组长度,默认从第一个开始
for (int i = 0; i < arr2.length; i++) {
System.out.println(arr2[i]);//1 2 3 5
}
System.out.println("增加长度");
int[] arr3 = Arrays.copyOf(arr1, 6);
arr3[arr3.length-1]= 520;
for (int i = 0; i < arr3.length; i++) {
System.out.println(arr3[i]);// 1 2 3 5 4 520
}
//截取指定范围的值到新数组中
int[] arr4 = Arrays.copyOfRange(arr1, 1, 3);
for (int i = 0; i < arr4.length; i++) {
System.out.print(arr4[i]+" ");//2 3 含投不含尾
}
}
public static void main(String[] args) {
int[] arr1 = {1,2,3,5,4};
int[] arr2 = Arrays.copyOf(arr1, 4);
//减少数组长度,默认从第一个开始
for (int i = 0; i < arr2.length; i++) {
System.out.println(arr2[i]);//1 2 3 5
}
System.out.println("增加长度");
int[] arr3 = Arrays.copyOf(arr1, 6);
arr3[arr3.length-1]= 520;
for (int i = 0; i < arr3.length; i++) {
System.out.println(arr3[i]);// 1 2 3 5 4 520
}
//截取指定范围的值到新数组中
int[] arr4 = Arrays.copyOfRange(arr1, 1, 3);
for (int i = 0; i < arr4.length; i++) {
System.out.print(arr4[i]+" ");//2 3 含投不含尾
}
}
所以,如果我们清楚的知道,要保存的数据的数量的时候,选择数组是一个很好的选择。如果我们需要根据情况动态的添加数据,这个时候就需要用到集合了;
集合的特点:集合是可变长度的,只可以存储对象,集合可以存储不同类型的对象。
集合的框架如下:(大概的:)
集合只能存放引用类型元素,并且保存的是元素的引用(地址),所以,对象的值改变会引起集合对象里的值发生变化。
Iterator接口:提供了统一的遍历集合元素的方式,【遵循问,取,(删)的过程】提供了方法:
boolean hasNext():判断集合是否还有元素可以遍历
E next():返回迭代的下一个元素
void remove():删除元素
Collection集合:collection是继承了Iterator接口的,所以可以用遍历器来遍历元素。
boolean add(E e):将给定的元素添加到集合当中,当成功添加后返回true,否则为false。
int size():获取集合的元素个数,集合没有长度,数组有长度 length
void clear():清空集合
boolean empty():判断集合是否为空集。当size等于0的时候,这个集合就是一个空集,空集并不是指null。
boolean contains(Object o):判断当前集合是否包含给定元素,判定标准是看给定的对象是否与集合现有的元素存在
equals比较为true,若有则包含,否则为不包含。
boolean remove(Object o):从集合中删除一个元素,根据equals的判定结果删除,一次就删除一个元素,对于有重复的,就只 删除第一个元素
boolean addAll(Collection<?> c): 将另一个集合的所有元素添加到另一个集合中
boolean containsAll(Collection<?> c): 判断当前集合是否包含给定集合中的所有元素,若包含则返回true
boolean removeAll(Collection<?> c):删除当前集合与给定集合的公有元素。
Iterator iterator():返回一个用于遍历当前集合的迭代器的实现类。(不同的集合都实现了一个可以遍历自身的迭代器实现类, 无需记住这些类是什么,就把它当做Iterator来用即可)
在用迭代器遍历集合的时候,不可以对集合进行操作元素(增,删,改),会报异常(ConcurrentModificationException)
只可以用迭代器的删除方法删除通过next取出的元素。
利用迭代器的原理,在jdk1.5之后,新推出了一个增强for循环,用来遍历数组或集合,但是不能取代传统的for循环,编译器认识这个for循环,然后还是会把它转换成传统的for循环交给虚拟机运行
因为增强for循环实质上是一个迭代器,所以在用它进行遍历的过程中,不可以通过集合的方式操作元素。
说到集合不免要提及泛型(也是在jdk1.5以后有的,也称为参数化类型):泛型在这提一点:
public class Fanxing {
public static void main(String[] args) {
Point<Integer> point1 = new Point<Integer>();
point1.setX(1);
point1.setY(2);
int x1 = point1.getX();
System.out.println("point1:"+point1.toString());
Point point2 = point1;
System.out.println("point2:"+point2.toString());
point2.setX("三");
System.out.println("point2:"+point2.toString());
x1 = point1.getX();
System.out.println("x1:"+x1);
System.out.println("point1:"+point1);
}
}
class Point<T>{
private T x;
private T y;
public T getX() {
return x;
}
public void setX(T x) {
this.x = x;
}
public T getY() {
return y;
}
public void setY(T y) {
this.y = y;
}
public Point(T x, T y) {
this.x = x;
this.y = y;
}
public Point() {
}
@Override
public String toString() {
return "Point [x=" + x + ", y=" + y + "]";
}
}
如果使用了带有了泛型的类,就一定要写出使用的泛型类型,否则,默认为Object,极容易发生错误:
point1刚开始一切正常,在赋值的时候经过了编译器的检查,是Integer类型,在调用get()方法取值的时候,编译器又给它加上了Integer类型(隐式转换),然后再返回回来。
而point2在声明的时候,没有加泛型类型,而且和point1指向了同一个对象。赋值的时候由于没加泛型类型,是以Object类型放进去,但是用point1取值的时候,由于它的进
和出都由编译器用Integer去检查,调用point1的get方法的时候,编译器会为他加上Integer,但是存进去是三,String类型,String不能直接转为Integer,所以会报一个类造型异常。
List接口:特点:可重复,并且有序,不仅可以用Iterator遍历,还可以用普通循环遍历。提供了一组通过下标操作元素的方法。
void add(int index,E element):将给定的元素插入到指定位置,原位置及后续元素都顺序向后移动。
E remove(int index):删除给定位置的元素,并将被删除的元素返回。
E get(int index):获取集合中指定下标对应的元素,下标从0开始。
E set(int index,E element):将给定的元素存入给定位置,并将原位置的元素返回。
List<?>subList(int fromIndex,int toIndex):返回此列表中指定的fromIndex(含)和toIndex之间的元素
对通过subList得到的子集合,对子集进行的操作也是在对原集合进行操作,可利用这个 特性把子集合清除,从而也把原集合的该位置上的元素也被清除了。
<T> T[] toArray(T[] a):这个是最常用的。
所有集合都提供了一个参数为Collection类型的构造方法,在创建当前集合的同时包含给定集合中的所有元素。
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("贺晓玲");
list.add("好好学习");
list.add("努力向上");
System.out.println(list);//[贺晓玲, 好好学习, 努力向上]
//把List集合转换为数组
Object[] array = list.toArray();//就算这个本来规定了泛型类型的,所以并不常用
/*
* 括号里,一般写size长度,如果大于size,则会报数组越界,如果小于size,会给我们自动添上,依然会装完所有的元素
*/
String[] array1 = list.toArray(new String[list.size()]);
for (String string : array1) {
System.out.print(string+" ");//贺晓玲 好好学习 努力向上
}
//数组转换为List,Arrays提供了一个静态方法
String[] array3 = {"one","two","three"};
List<String> list2 = Arrays.asList(array3);
System.out.println(list2);// [one, two, three]
//对集合元素操作就是对原数组进行操作,故是不可以对集合进行增删操作的(会报UnsupportedOperationException异常)
list2.set(1, "修改");
for (String string : array3) {
System.out.println(string+" ");//one 修改 three
}
/*
* 虽然说从数组转换过来的集合不可以进行增删操作,但是我们若是想修改,
* 可以new一个新的集合,来装上从数组返回回来的元素,再进行增删操作
*/
List<String> newList = new ArrayList<>(list2);
System.out.println(newList);//[one, 修改, three]
newList.remove(1);
System.out.println(newList);//[one, three]
}
对List进行排序:为什么不对Set排序呢?因为Set是无序的
两种方式:
第一种方式:Collections.sort();其中Collections是集合的工具类,提供了很多便于我们对集合操作的方法。
void sort(List<T> list):对给定集合元素进行自然排序。(数字从小到大)
该方法排序给定集合时对集合元素有一个要求,就是元素必须实现Comparable接口,否则编译不通过。
那,Comparable接口又是什么?
实现该接口必须实现抽象方法:int compareTo(T t);
若当前对象大于给定对象,那么返回值应为>0的整数。
若当前对象小于给定对象,那么返回值应为<0的整数
若当前对象等于给定对象,那么返回值应为0
package colletion1;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Random;
public class CollectionsSort {
public static void main(String[] args) {
/*
* java.util.Collections这个工具类提供了若干静态方法:如:
* Set<T> synchronizedSortedSet:返回支持该集合的线程同步集合(线程安全)
* List<T> synchronizedList(List<T> list:返回由指定列表支持的线程同步列表(线程安全)
*/
Random rm = new Random();
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
list.add(rm.nextInt(100));
}
/*
* sort方法对集合元素进行自然排序(从小到大)
*/
Collections.sort(list);
for (Integer integer : list) {
System.out.print(" "+integer);// 3 11 19 38 44 56 67 80 90 98
}
System.out.println("====排序自定义===");
/*
* 如果要排序自定义类型元素,那么自定义类型要实现Comparable接口,重写compareTo()
*/
List<Point> list2 = new ArrayList<Point>();
list2.add(new Point(3, 5));
list2.add(new Point(2, 5));
//排序前
for (Point point : list2) {
System.out.print(" "+point);
}//Point [x=3, y=5] Point [x=2, y=5]
Collections.sort(list2);
//排序后
for (Point point : list2) {
System.out.print(" "+point);
}//Point [x=2, y=5] Point [x=3, y=5]
}
}
class Point implements Comparable<Point>{
private int x;
private int y;
public Point(int x, int y) {
super();
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public void setY(int y) {
this.y = y;
}
@Override
public String toString() {
return "Point [x=" + x + ", y=" + y + "]";
}
@Override
public int compareTo(Point o) {
/*
* 两个点比较大小的方式,点到原点的距离长的大
*/
int thisLen = this.x*this.x+this.y*this.y;
int oLen = o.x*o.x+o.y*o.y;
return thisLen-oLen;
}
}
但是这种方式,一旦实现了Comparable接口,比较逻辑就已经确定,如果希望在排序的操作
中临时指定比较规则,则可以使用第二种方式。
第二种方式:实现Comparator接口,重写 int compare(T o1,T o2);
该方法的返回值要求:
若o1>o2:返回值应>0;
若o1<o2,返回值应<0;
若o1=o2,返回值应为0;
public class CollectionsSort2 {
public static void main(String[] args) {
List<Cell> cells = new ArrayList<Cell>();
cells.add(new Cell(3,2));
cells.add(new Cell(5,1));
cells.add(new Cell(2,3));
//自定义排序规则,现在我要按col值的大小顺序要排序
Collections.sort(cells, new Comparator<Cell>() {
@Override
public int compare(Cell o1, Cell o2) {
return o1.getCol()-o2.getCol();
}
});
System.out.println(cells);
//[Cell [col=2, row=3], Cell [col=3, row=2], Cell [col=5, row=1]]
//我们还可以用lambda表达式来实现,按照Cell的row从大到小排序
Comparator<Cell> com = (o1,o2)->{return o2.getRow()-o1.getRow();};
Collections.sort(cells, com);
System.out.println(cells);//[Cell [col=2, row=3], Cell [col=3, row=2], Cell [col=5, row=1]]
}
}
class Cell{
private int col;
private int row;
public void setCol(int col) {
this.col = col;
}
public void setRow(int row) {
this.row = row;
}
public int getCol() {
return col;
}
public int getRow() {
return row;
}
@Override
public String toString() {
return "Cell [col=" + col + ", row=" + row + "]";
}
public Cell(int col, int row) {
this.col = col;
this.row = row;
}
}
现在我们来Collection的另一个接口:Queue
Queue: 队列,Collection的另一个子接口,故也可以用迭代器遍历元素,可以将队列看成特殊的线性表,队列限制了对线性表的访问方式:只能从线性表的一端添加(offer)元素,从另一端取出(poll)元素
JDK提供了Queue接口,并同时使得LinkedList实现了该接口。因为Queue会经常进行添加和删除的操作,而LinkedList在这方面效率较高。
主要方法:
boolean offer(E e):将一个对象添加至队尾,添加成功返回true
E poll();从队首删除并返回一个元素
E peek():返回队首的元素(但并不删除)
Deque:双端队列,Queue的子接口,可从队列的两端分别入队(offer)和出队(poll),LinkedList也实现了该接口。
如果将Deque限制为只能从一端入队和出队,则可实现("栈"Stack)的数据结构,对于栈而言,入栈称为push,出栈称为pop。栈遵循先进后出的原则
主要方法:
offerFirst():向队首添加
offerLast():向队尾添加
offer():默认向队尾添加
pop():默认从首 取出元素
/**
* 双端队列
* @author tarena
*/
public class DequeTest {
public static void main(String[] args) {
Deque<String> deque = new LinkedList<String>();
deque.offer("一");
deque.offerFirst("二");
deque.offerLast("三");
deque.offer("四");
deque.offer("五");
System.out.println(deque);//[二, 一, 三, 四, 五]
//取出元素 peek()取出队首元素,不在原队列中删除
System.out.println("取出:"+deque.peek()+",以后变为"+deque);
//poll()默认从队首取出元素,要在原队列中删除
System.out.println("取出:"+deque.poll()+",以后变为"+deque);//取出:二,以后变为[一, 三, 四, 五]
System.out.println("从队尾取出:"+deque.pollLast()+",以后变为:"+deque);//从队尾取出:五,以后变为:[一, 三, 四]
}
}
Stack: 栈,如果双端队列只开放一端进行进出操作,那么就是栈的数据结构,比如:子弹上膛,最先上的那颗子弹最后一颗发射出来。LinkedList也实现了Stack
又比如:电脑磁盘的后退功能,ctrl+z。先倒退的是你最后操作的那一步。
主要方法:
push():入栈操作
pop():出栈操作
public class StackDemo {
public static void main(String[] args) {
LinkedList<String> stack = new LinkedList<String>();
stack.push("一");
stack.push("二");
stack.push("三");
System.out.println(stack);//[三, 二, 一]
System.out.println("取出:"+stack.pop()+",原栈为:"+stack);//取出:三,原栈为:[二, 一]
}
}
集合小结: