集合
集合:是一个存储对象的容器。(因为java中万物皆对象)。
与数组的比较:
集合:变长容器,只能存储引用数据类型,可以存储不同类型的元素,但是为了方便对集合中的元素进行遍历等操作,一般存储同一类型。虽说集合只能存储引用数据类型,但是在存储基本数据类型时可以通过自动装箱将基本数据类型转换为基本数据类型包装类,这样从使用的角度讲,集合可以存储基本数据类型也可以存储引用数据类型。
数组:数组实际上也是一个集合。是定长容器,可以存储基本数据类型和引用数据类型(即所有数据类型都可以存),数组中的元素类型必须统一。
Java集合的分类:
单列集合:List:元素有序(即有下标),但是可以重复。
Set:元素无序,元素不可重复。
双列集合:Map:是键值对的形式。
Collection(无索引):
public interface Collection<E>extendsIterable<E>,是一个接口,不能实例化,所以采用接口多态方法,创建子类对象(已知接口实现类)。
Collection是所有单列集合(List、Set)的父类。Collection表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素(List),而另一些(Set)则不允许。一些collection 是有序的(List),而另一些则是无序的(Set)。JDK 不提供此接口的任何直接实现:它提供更具体的子接口(如 Set 和 List)实现。
方法:
boolean add(E e):添加元素。
boolean remove(Objecto):删除。Remove的时候自动调用equals方法,如果比较的元素所在的类没有重写equals方法,那么就是比较地址,如果重写了,就是比较内容。在删除元素时,jdk中包含的类已经自动重写了equals方法了,不需要我们重写,一般都是我们自己写的类需要重写equals方法。
boolean contains(Objecto):如果此 collection 包含指定的元素,则返回 true。调用元素的equals方法进行比较。
void clear():移除此 collection 中的所有元素,即清空。
int hashCode():返回集合的哈希码值,即十进制的地址值。
boolean isEmpty():判断集合是否为空。
int size():返回的是集合中的元素个数。
boolean addAll(Collection<?extends E> c):将指定 collection 中的所有元素都添加到此 collection 中。
boolean containsAll(Collection<?>c):如果当前集合包含了指定集合中的所有元素,返回true。
boolean removeAll(Collection<?>c):删除当前集合中包含了指定集合中元素的元素,即删除当前集合中与指定集合相同的元素。
boolean retainAll(Collection<?>c):保留当前集合和指定集合共有的元素,对当前集合做修改。
Object[] toArray():返回包含该集合中所有元素的数组。
代码示例:
packagelianxi;
importjava.util.ArrayList;
importjava.util.Collection;
public class Lianxi1 {
public static void main(String[] args) {
//创建集合对象
Collection c = new ArrayList();//多态创建
//创建元素
Person p1 = newPerson("jack",22);
Person p2 = newPerson("tom",21);
Person p3 = newPerson("bob",23);
//向集合中添加元素
c.add(p1); //自动将Person类型转为Object类型存储
c.add(p2);
c.add(p2);//List接口实现对象,元素是可以重复的。
c.add(p3);
c.add(100);//自动装箱,将基本数据类型转换为基本数据类型包装类对象。
c.add(true);//同上
c.add("abc");//String类型
System.out.println(c);//[Person[name=jack, age=22], Person [name=tom, age=21], Person [name=bob, age=23], 100,true, abc]
//删除元素
/*c.remove(p1);//删除成功
System.out.println(c);*///[Person[name=tom, age=21], Person [name=bob, age=23], 100, true, abc]删除成功
/*c.remove(newPerson("jack",22));//删除失败
与上一个删除相比,这次的删除是new了一个对象,虽然集合c中有jack,22这个对象,但是没有删除成功,说明在删除的时候,remove调用了equals方法,而所比较的元素Person对象没有重写equals方法,所以继承的object类的equals方法是比较的地址,在这里new的对象显然是一个新的地址,与集合中原有的p1的内容虽然相同,但地址不同,所以删除失败。而在上一个删除中,remove的元素是p1,与集合中的p1地址相同,所以删除成功。
System.out.println(c);//[Person[name=jack, age=22], Person [name=tom, age=21], Person [name=bob, age=23], 100,true, abc]
c.remove(newPerson("jack",22));//Person重写了equals方法之后
System.out.println(c);//[Person[name=tom, age=21], Person [name=bob, age=23], 100, true, abc]*/
c.remove(100);//100自动装箱,remove调用Integer类的equals方法(自动重写)
c.remove(true);//remove调用Boolean类的equals方法(自动重写)
c.remove("abc");//调用String类的equals方法
System.out.println(c);//[Person[name=jack, age=22], Person [name=tom, age=21], Person [name=bob, age=23]]
//contains方法
System.out.println(c.contains(p1));//true
System.out.println(c.contains(newPerson("jack",22)));//在Person没有重写equals方法时,返回的是false,重写equals方法后,返回的是true,说明contains比较的也是地址,调用equals方法。
//clear()
/*c.clear();
System.out.println(c);//[]集合为空*/
//hashCode()
System.out.println(c.hashCode());//104133902
//isEmpty()
System.out.println(c.isEmpty());//false
//size()
System.out.println(c.size());//3
//addAll(Collection<?extends E> c)
System.out.println(c);
Collection c1 = new ArrayList();
c1.add("java");
c1.add("vr");
c1.add("html");
c.addAll(c1);//将集合c1中的所有元素追加到集合c中
System.out.println(c);//[Person[name=jack, age=22], Person [name=tom, age=21], Person [name=tom, age=21],Person [name=bob, age=23], java, vr, html]
//containsAll()
System.out.println(c.containsAll(c1));//true
System.out.println(c1.containsAll(c));//false
//removeAll()
/*System.out.println(c.removeAll(c1));//true
System.out.println(c);//[Person[name=jack, age=22], Person [name=tom, age=21], Person [name=tom, age=21],Person [name=bob, age=23]]
System.out.println(c1.removeAll(c));//false
System.out.println(c1);*///[java, vr,html]
//retainAll()
c.retainAll(c1);
System.out.println(c);//[java, vr,html]
//将集合转为数组
Object [] o = c.toArray();
System.out.println(Arrays.toString(o));//[Person[name=jack, age=12], Person [name=rose, age=13], Person [name=tony, age=15]]
/*Object [] o1 = c.toArray();
for(int i = 0;i<o1.length;i++){
Object oo = o1[i];//多态对象
Person pp = (Person)oo;//强转为子类对象,调用子类特有的get方法
System.out.println(pp.getName());//jack rose tony
}*/
}
}
packagelianxi;
public class Person {
private String name;
private int age;
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 + "]";
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
}
/*@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;
}*/
}
涉及到的两个接口:
迭代器(Iterator):用于遍历集合。一个集合一个迭代器。在ArrayList中,含有一个私有化的内部类实现了Iterator接口,并重写了Iterator的方法。迭代器不能单独存在,有了集合,才会有基于该集合的迭代器。
Iterator<E>iterator():是一个接口,返回在此 collection 的元素上进行迭代的 Iterator(返回迭代器对象)。
publicinterface Iterator<E>:接口。
主要方法:
booleanhasNext(),如果后面还有元素,返回true。
Enext():返回迭代的下一个元素。
代码示例:
publicstatic void main(String [] args){
Collection c = new ArrayList();
Person p1 = newPerson("jack",22);
Person p2 = newPerson("tom",21);
Person p3 = newPerson("bob",23);
c.add(p1);
c.add(p2);
c.add(p3);
Iterator i = c.iterator();//通过集合的iterator方法创建该集合的迭代器对象
while(i.hasNext()){//while条件返回true,说明有写一个元素,则进入循环体输出下一个元素
System.out.println(i.next());
/* Person [name=jack, age=22]
Person [name=tom, age=21]
Person [name=bob, age=23]
*/
}
迭代器原理:
创建好集合之后,根据集合创建迭代器对象,迭代器中的每一个指针分别指向集合中的每一个元素。迭代器对象默认有一个指针,指针默认指向迭代器的前一行,当调用has.Next()方法时,指针并不移动,只是看一下有没有下一个元素,如果有,就调用next()方法,先移动指针,并返回下一个元素(指针移动后指向的元素),如果没有下一个元素,返回false,迭代结束。
比较性(Comparable):用于对元素进行自然排序。
List(有索引):
publicinterface List<E>extends Collection<E>,是Collection的子接口。是有序的collection。
常用的实现类:
ArrayList:底层数据结构是数组结构(连续的存储结构,一个挨一个),特点是增删慢(增的时候需要遍历将添加元素所在位置之后的每个元素覆盖掉后面的元素,删的时候要遍历把删除元素之后的每个元素向前覆盖),查询快(通过集合的地址根据要查询的元素的索引计算该元素的地址)。
LinkedList:底层是链表结构(不是连续的存储空间,每个元素存储了两部分内容,左边是上一个元素的索引,右边是下一个元素的索引。首元素的左边指针为空,末尾元素的右边指针为空),增删快(增删的时候只需要把上一个元素的右边指针指向修改后的下一个元素,把修改后的元素的左边指针指向上一个元素,右边的指针改为下一个元素的索引,下一个元素左边指针改为修改元素的索引),查询慢(查询某个元素需要先查到它前面的元素)。
ArrayList与LinkedList原理分析:
ArrayList:
public class ArrayList<E>
主要方法:
voidadd(int index, E element)在列表的指定位置插入指定元素。
Eremove(int index)移除列表中指定位置的元素。
Eget(int index)返回列表中指定位置的元素。
Eset(int index,E element)用指定元素替换列表中指定位置的元素。
代码示例:
publicstatic void main(String[] args) {
List l = new ArrayList();
l.add(100);
l.add(100);//List有序,可重复
l.add("abc");
l.add(true);
l.add(1, 200);//根据索引插入元素
l.remove(1);//根据索引删除元素
l.set(2, "www");//根据索引修改元素
System.out.println(l);
System.out.println(l.get(3));//根据索引获取元素
}
列表迭代器:
普通迭代器(Iterator)的缺点:在迭代的过程中不能修改集合长度,因为集合对应的迭代器是在生成迭代器时和集合中的元素一一对应的,如果在迭代的过程中修改了集合中的元素,那么迭代器与集合元素就不能一一对应了,会出现并发修改异常,所以普通迭代器在迭代过程中,不允许修改集合长度。
列表迭代器:public interfaceListIterator<E>extends Iterator<E>。允许程序员按任一方向遍历列表、迭代期间修改列表。
主要方法:
void add(E e)将指定的元素插入列表。
boolean hasPrevious():如果以逆向遍历列表,列表迭代器有多个元素,则返回 true。(即当前从集合末尾开始,往前查找元素,如果有,返回true)
Eprevious():返回列表中的前一个元素。
has.Next()和next()方法与普通迭代器用法相同。
代码示例:
List l = new ArrayList();//多态对象
l.add("jack");
l.add("tom");
l.add("rose");
//创建列表迭代器对象
ListIterator ll = l.listIterator();
while(ll.hasNext()){
String name = (String) ll.next();//将object类型强转为String类型
if(name.equals("tom")){
ll.add("lily");//调用迭代器的add方法,不是调用集合l的add方法
}
}
System.out.println(l);
//逆向迭代
while(ll.hasPrevious()){
System.out.println(ll.previous());//roselily tom jack
}
LinkedList:
publicclass LinkedList<E>
特有方法:
publicvoid addFirst(E e)将指定元素插入此列表的开头。
publicvoid addLast(E e)将指定元素添加到此列表的结尾。
publicE getFirst()返回此列表的第一个元素。
publicE getLast()返回此列表的最后一个元素。
代码示例:
LinkedList l = new LinkedList();//因为要用到的方法是LinkedList特有的方法,所以此处不再创建多态对象。在前面创建一些对象的时候用的比较多的是多态对象,因为我们用到的很多方法让传入的参数都是Object类型或者List类型。
l.add("java");
l.add("python");
l.add("html");
l.addFirst("vr");
l.addLast("c#");
System.out.println(l);//[vr, java, python, html, c#]
System.out.println(l.getFirst());//vr
System.out.println(l.getLast());//c#
Set:
publicinterface Set<E>extends Collection<E>,也是一个接口,无序(没有索引),不包含重复元素。
方法与collection方法一样。重要子类:HashSet,TreeSet,LinkedHashSet。
HashSet:
public class HashSet<E>。
必须重写hashCode方法(只能判断元素不同)和equals方法。
保证元素唯一性的原理:
hashSet对象在使用add方法添加元素时,默认调用hashCode方法和集合中已存在的元素比较,hashCode方法的返回值是对象的每个属性值的hashCode值之和,如果返回值不同,说明要添加的元素和集合中的元素不同,可以添加,如果返回值相同(属性值之和相同不能确定每个属性是否相同),还要调用重写的equals方法进行内容的比较,看每个属性是否相同,如果属性有不同的,就可以添加,如果每个属性也相同,就不添加。
为什么不直接用equals方法进行比较?
因为使用hashCode方法可以过滤掉一些不相同的元素,把返回值相同的元素再调用equals方法进行比较,提高了性能。
代码示例:
public static void main(String[] args) {
Sets = new HashSet();
Personp1 = new Person("jack",33);
Personp2 = new Person("tom",22);
//Person重写equals方法和hashCode方法
s.add(p1);
s.add(p2);//调用hashCode方法和集合中已有元素进行比较,得到的属性值之和不同,说明这两个对象肯定不一样,就可以添加成功
/*s.add(p1);//调用hashCode方法和集合中已有元素进行比较,得到的属性值之和与已经存在的p1属性值之和相同,进一步调用equals方法比较每个属性,发现每个属性也相同,就说明这个元素与集合中的元素相同,添加失败。
s.add(newPerson("jack",33));//调用hashCode方法和集合中已有元素进行比较,得到的属性值之和与已经存在的p1属性值之和相同,进一步调用equals方法比较每个属性,发现每个属性也相同,就说明这个元素与集合中的元素相同,添加失败。
System.out.println(s);*/
//如果Person没有重写hashCode方法和equals方法,那么add方法调用hashCode方法时,比较的是十进制地址值。
s.add(p1);//p1余集合中已经存在的p1地址相同,添加失败
s.add(newPerson("jack",33));//添加成功,因为new了一个新的对象,地址肯定和集合中元素的十进制地址不同。
System.out.println(s);
}
TreeSet:
public class TreeSet<E>。使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。元素必须具有可比较性。Person如何实现比较性?需要实现comparable接口,重写compareTo方法。重写之后默认返回值是0,只能添加一个元素。自定义排序规则。
保证元素唯一性原理:
如果compareTo方法的返回值是0,则元素相同,不添加,如果返回值非0,添加元素。
TreeSet两种常用构造方法:
public TreeSet()构造一个新的空 set,该 set 根据其元素的自然顺序进行排序。
public TreeSet(Comparator<? super E>comparator) 构造一个新的空 TreeSet,它根据指定比较器进行排序。
接口 Comparable<T>:
方法:intcompareTo(T o)比较此对象与指定对象的顺序。如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。底层实际上是二叉树,小于0放左边,大于0 放右,等于0说明相等,不添加。
无参构造方法实现接口:让元素具有比较性。在Person类中实现comparable接口,重写compareTo方法。
代码示例:
Set s = new TreeSet();
s.add(34);
s.add(22);
s.add(76);
s.add(19);
System.out.println(s);//[19, 22, 34, 76]对元素进行自然排序
Set s1 = new TreeSet();
s1.add("jack");
s1.add("rose");
s1.add("tom");
s1.add("bob");
System.out.println(s1);//[bob, jack,rose, tom]对字符串进行自然排序
Person p1 = newPerson("zhang",23);
Person p2 = newPerson("wang",12);
Person p3 = new Person("li",12);
Set s2 = new TreeSet();
s2.add(p1);
s2.add(p2);
s2.add(p3);
System.out.println(s2);//Exception in thread "main"java.lang.ClassCastException(类型强转异常,因为TreeSet元素必须具有可比较性,而Person没有可比较性,要想实现可比较性,需要实现comparable接口)
//Person实现comparable接口重写compareTo方法之后:[Person [name=li, age=12],Person [name=wang, age=12], Person [name=zhang, age=23]]
Person类:
public class Person implementsComparable{
.....
public int compareTo(Object o) {
//按照年龄排序
int result;
Person p = (Person)o;//传入的参数对象是Object类型,要获取Person对象的私有属性,需要强转。
result = this.getAge() - p.getAge();
if(result == 0){//年龄相同,按姓名排序
result =this.getName().compareTo(p.getName());//String类自己的compareTo方法
}
return result;
}
}
有参构造方法实现接口:让集合具有比较性。在创建集合时使用匿名内部类传入比较器实现接口。Person类不需要实现接口,也不用重写compareTo方法,直接在创建实现接口对象的匿名内部类中重写compare方法。
代码示例:
publicstatic void main(String[] args) {
TreeSet s = newTreeSet(newComparator(){//通过匿名内部类的方式创建实现接口的子类对象
publicint compare(Object o1, Object o2) {//重写compare方法
intresult;
Personp1 = (Person)o1;
Personp2 = (Person)o2;
result= p1.getAge() - p2.getAge();//按年龄排序
if(result== 0){
result= p1.getName().compareTo(p2.getName());//年龄相同,按姓名排序
}
returnresult;
}
});//绿色背景部分是匿名内部类
Person p1 = newPerson("jack",22);
Person p2 = newPerson("tom",12);
Person p3 = new Person("lily",12);
s.add(p1);
s.add(p2);
s.add(p3);
System.out.println(s);//[Person[name=lily, age=12], Person [name=tom, age=12], Person [name=jack, age=22]]
}
LinkedHashSet:
可预知迭代顺序的Set集合,底层是链表结构,按照add顺序进行迭代。HashSet的底层是散列的,无序的,迭代顺序与add的顺序无关。
保证元素唯一性的原理与HashSet相同。
代码示例:
HashSet s = new HashSet();//散列的
Personp1 = new Person("wang",22);
Personp2 = new Person("zhang",23);
Personp3 = new Person("liu",21);
s.add(p1);
s.add(p2);
s.add(p3);
System.out.println(s);//[Person[name=zhang, age=23], Person [name=liu, age=21], Person [name=wang, age=22]]顺序是散列的。
LinkedHashSet s1 = new LinkedHashSet();//可预知迭代顺序
s1.add(p1);
s1.add(p2);
s1.add(p3);
System.out.println(s1);//[Person[name=wang, age=22], Person [name=zhang, age=23], Person [name=liu, age=21]]按照add的顺序。
Set练习:
packagelianxi;
importjava.util.TreeSet;
importjava.util.Comparator;
//对多个字符串(不重复)按照长度排序(由短到长)
publicclass Set5 {
public static void main(String[] args) {
/*首先需要一个容器来存储多个字符串,可以考虑list、set,但是要求不重复,所以只能用set
* 需要进行排序,用到TreeSet里面的compareTo方法
*/
TreeSet s = new TreeSet();
s.add("http");
s.add("nba");
s.add("abc");
s.add("xi");
for(Object o:s){
System.out.println(o);//String中compareTo方法自然排序/*abc http nba xi*/
}
//要想按照字符串长度排序,而String类中已经定义好了compareTo方法,我们不能在String类中重写该方法,所以只能通过TreeSet有参构造方法传入比较器来重写compareTo方法
TreeSet s1 = new TreeSet(newComparator(){//通过匿名内部类实现comparator接口
public int compare(Object o1, Objecto2) {
int result;
String s1 = (String)o1;
String s2 = (String)o2;
result =s1.length()-s2.length();//先比较长度,长度相同再用String类的compareTo方法进行自然排序
returnresult==0?s1.compareTo(s2):result;//三目运算符是一个表达式,而不是一条语句,需要接受返回值
}
});
s1.add("http");
s1.add("nba");
s1.add("abc");
s1.add("xi");
for(Object o:s1){
System.out.println(o);//xi abc nbahttp
}
//不使用匿名内部类,通过有参构造器直接传入一个实现接口的对象new bijiaoqi(),红线提示点击生成bijiaoqi类(class),自动实现comparator接口。
TreeSet s2 = new TreeSet(new bijiaoqi());
s2.add("http");
s2.add("nba");
s2.add("abc");
s2.add("xi");
for(Object o:s2){
System.out.println(o);//xi abc nbahttp
}
}
}
packagelianxi;
importjava.util.Comparator;
publicclass bijiaoqi implements Comparator {
public int compare(Object o1, Object o2) {
int result;
String s1 = (String)o1;
String s2 = (String)o2;
result = s1.length()-s2.length();
return result==0?s1.compareTo(s2):result;
}
}