先看一张图:
从上图可以清晰的看到:List和Set两个接口最的区别就是List可以存放重复的元素,而Set里面的元素不可以重复。List和Set这两个接口继承自Collection接口,collection接口有一个add()方法,Collection又继承自Iterable,而iterable里面的iterator()方法返回的是Iterator<T>接口。
由以上分析可知,Collection接口里面有add()和iterator()方法,那么它的子接口也有这两个方法.
与Collection相比,List接口有两个方法:
public E get(int indext);//根据索引取得数据
public void set(int index,E element);//根据索引修改数据
下面展示使用get()方法取出列表中的数据:
import java.util.ArrayList;
import java.util.List;
public class TestDemo{
public static void main(String args[]) {
List<String> list=new ArrayList<>();
list.add("yao");
list.add("liang");
list.add("yong");
for(int i=0;i<list.size();i++) {
//使用get()方法取出列表中的数据
System.out.println(list.get(i));
}
}
}
在这个Demo中,get是List中才有的方法,如果实现的Collection接口,那么就不能使用get方法了。(这个可以自己写Demo实验)当然了,我们也可以不适用get输出,也可以使用toArray()方法把集合变成数组进行输出,但是要进行向下转型,对我们来说向下转型是不安全的操作,应该尽量避免。如下面例子:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TestDemo{
public static void main(String args[]) {
List<String> list=new ArrayList<>();
list.add("yao");
list.add("liang");
list.add("yong");
Object obj[]=list.toArray();//返回的是Object对象,有可能造成向下转型
System.out.println(Arrays.toString(obj));
}
}
从理论上讲,集合中的remove()和contains()方法需要equals()方法的支持。所以要在简单java类中重写equals()方法。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Person {
private String name;
private int age;
public Person(String name,int age) {
this.name=name;
this.age=age;
}
public String get() {
return this.name+this.age;
}
}
public class TestDemo{
public static void main(String args[]) {
List<Person> list=new ArrayList<>();
list.add(new Person("张三",20));
list.add(new Person("李四",40));
list.remove(new Person("张三",20));
for(int i=0;i<list.size();i++) {
System.out.println(list.get(i).get());
}
}
}
以上这个程序没有重写equals()方法,所以即使调用了remove()方法,依然不能删除对象,这是为什么呢?
说到这里就插入一个知识点:equals()方法和==之间的区别:
对于==比较来说:
1,基本数据类型比较的就是值是否相等。
2,引用数据类型比较的就是它们堆内存地址是否相等。(除非是同一个new出的对象,否则返回的都是false,因为每次new都会开辟新的堆内存空间)
对于equals()方法来说,它的源码是这样的:
public boolean equals(Object obj) {
//this - s1
//obj - s2
return (this == obj);
}
从而可以看出:默认情况下equals()方法比较的仍然是堆内存地址是否相等。(栈内存保存的是对象名称,堆内存保存的是对象的内容),在一些类库当中这个方法被重写了,如String、Integer、Date。在这些类当中equals有其自身的实现(一般都是用来比较对象的成员变量值是否相同),而不再是比较类在堆内存中的存放地址了。 所以说,对于复合数据类型之间进行equals比较,在没有覆写equals方法的情况下,他们之间的比较还是内存中的存放位置的地址值,跟双等号(==)的结果相同;如果被复写,按照复写的要求来。
如下是我定义的一个类使用equals()来进行比较:
class Student{
private String name;
private int age;
public Student (String name,int age) {
this.name=name;
this.age=age;
}
}
public class TestDemo{
public static void main(String args[]) {
Student stu1=new Student("张三",12);
Student stu1=new Student("张三",12);
System.out.println(stu1==stu2);
System.out.println(stu1.equals(stu2));
}
}
输出结果:false,false。
但是当我们复写equals()之后,
class Student{
private String name;
private int age;
public Student (String name,int age) {
this.name=name;
this.age=age;
}
public boolean equals(Object obj) {
//为了提高效率:如果两个内存地址相等,那么一定是指向同一个对内存中的对象,
//就无需比较两个对象的属性值(自己跟自己比,没啥意义嘛)
if(this==obj) {
return true;
}
//为了提供程序的健壮性
//我先判断一下,obj是不是学生的一个对象,如果是,再做向下转型,如果不是,直接返回false。
if(!(obj instanceof Student)) {
return false;
}
Student stu=(Student)obj;
return this.name.equals(stu.name) && this.age == stu.age;//判断两个对象的属性值是否相等
}
}
public class TestDemo{
public static void main(String args[]) {
Student stu1=new Student("张三",12);
Student stu2=new Student("张三",12);
System.out.println(stu1==stu2);
System.out.println(stu1.equals(stu2));
}
}
返回结果false,true。
由此我们也就得出:基本数据类型如:int,long,char,,,比较相等一般不会用到equals()方法,用==就可以了。对于引用数据类型,比较相等就会用到equals(),因为使用==来比较只是比较的对象的堆内存地址,默认的equals()方法虽然比较的是堆内存地址,但是可以在我们定义的类中复写这个方法,按照我们的要求来比较。
1,基本数据类型:已经
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Person {
private String name;
private Integer age;
//Integer和int的区别就是Integer有空,而int没有空,Integer还可以有equals操作
public Person(String name,Integer age) {
this.name=name;
this.age=age;
}
public String get() {
return this.name+this.age;
}
public boolean equals(Object obj) {
if(this==obj) {
return true;
}
if(obj==null) {
return false;
}
if(!(obj instanceof Person)) {
return false;
}
Person per=(Person)obj;//向下转型,这里可以
return this.name.equals(per.name) && this.age.equals(per.age);
}
}
public class TestDemo{
public static void main(String args[]) {
List<Person> list=new ArrayList<>();
list.add(new Person("张三",20));
list.add(new Person("李四",40));
list.remove(new Person("张三",20));
for(int i=0;i<list.size();i++) {
System.out.println(list.get(i).get());
}
}
}
Vector
下面介绍Vector类,Vector子类是从JDK1.0就开始提出的,而ArrayList从JDK1.2才提出。把以上代码中的ArrayList全部换成Vector也可以得到同样的输出,但是两者还是有点区别的,区别就在于ArrayList是异步的,线程不安全的,而Vector是同步的,属于线程安全的操作。在实际操作中,一般都是一个线程对应一个列表。
LinkedList
LinkedList 是 Java 集合中比较常用的数据结构,与 ArrayList 一样,实现了 List 接口,只不过 ArrayList 是基于数组实现的,而 LinkedList 是基于链表实现的。所以 LinkedList 插入和删除方面要优于 ArrayList,而随机访问上则 ArrayList 性能更好。
1,
在查找和删除某元素时,区分该元素为 null和不为 null 两种情况来处理,LinkedList 中允许元素为 null。
3、基于链表实现不存在扩容问题。
4、查找时先判断该节点位于前半部分还是后半部分,加快了速度
5、因为基于链表,所以插入删除极快,查找比较慢。
6、实现了栈和队列的相关方法,所以可作为栈,队列,双端队列来用
Set
Set接口跟List接口还有一个区别就是Set接口没有对Colletion接口进行扩充,跟Collection中的方法一模一样的。Set有两个重要的子类就是HashSet和TreeSet;
javaHashSet与TreeSet的区别:https://blog.csdn.net/coding_1994/article/details/80553554
javaHashSet与TreeSet的区别:https://blog.51cto.com/13579086/2069837
HashSet和TreeSet两者的区别在于:HashSet是无序保存,而TreeSet是有序保存。
Hash算法初步解释:Hash算法就相当于某一天你去公园看演出,观众台有很多作为,你去的时候还有好多座位,你就随便选了一个,这跟你拿着电影票选座位是不一样的,电影院是你自己去对号入座。TreeSet是使用升序的方式来排列的。TreeSet需要Compareable的接口支持,所有属性必须参与比较。正是因为TreeSet使用起来太麻烦,所以一般不使用。
TreeSet在判断段重复元素的时候使用的是Compaeable的支持,而HashCode在判断重复元素元素的时候使用的是Object类里面的两个方法:
public int hashCode()//Hash码
public boolean equals(Object obj)
在java中对象的比较分为两步:第一步通过对象的唯一编码找到对象的信息。第二部,当编码匹配之后再调用equals()方法进行内容比较。只有这两部一样的话才能确定是一样的。所以,以后开发中,如果要标识对象的唯一性,一定要得到hashCode()和equales()两个方法的支持。但是eclipse已经可以自动生成了,不需要我们自己去写。
在很多时候使用Set集合并不是为了排序,而是让其进行重复元素的过滤。重复元素又需要依靠hashCode()和equals()支持,多以不是必须的时候,在使用Set接口的时候尽量使用系统提供的类实现。例如String ,Integer。
原则:保存自定义对象一般使用List接口,保存系统类信息的时候使用Set接口可以消除掉重复。
集合输出使用iterator和foreach,还有两种。
Map的主要功能是为了查找数据,而Collection主要是为了进行输出,所以这两者有着本子的区别。当然,Map集合也是可以输出的,Collection也是可以查找到,但是两者的重点是不一样的,
下面是Map集合的主要方法:
public V put(K key,V value)---向集合之中添加数据
public V get(Object key)---根据key取得对应的value,如果没有就返回null
public Set<k> keySet()---取得所有的key的信息,key不能重复
public Collection<V> values()---取得所有的values,不关注内容是否重复
public Set<Map.Entry<K,V>> entrySet()---将Map集合变成Set集合
Map主要的子类有:HashMap,HashTable,TreeMap其中,TreeMap主要是用来排序才能使用到。其余两个的区别请看下面:
HashMap和HashTable在存储自定义对象的时候也需要覆写Object对象总的hashCode和equales这两个方法。因为要确定对象的唯一性。但是知道这没什么用,因为在实际开发过程中,Map中的key主要就是String,Integer这两种类型,这两个都是系统类,所以系统就帮我们覆写好了这两个方法,不用我们操心了。
与HashTable相比,在数据量小的时候HashMap是按照链表的模式进行存储的,当数据量变大的时候,为了快速将进行查找,(因为链表查找的时间复杂度是N,而二叉树查找的是按复杂度是N的对数),那么会将这个链表变成红黑树,使用哈希码作为数据定位,来进行保存。HashMap还有一个特征就是它的所有的方法都是线程不安全的,都是线程异步的,也就意味着他是线程不安全的,虽然在高并发的时候考虑到访问速度,但是他是线程不安全的。(在这里线程安全是指多个线程可以同时操作HashTable,前提是必须等待上一个线程操作完成之后下一个线程才能操作,这样既保证了线程安全,而HashMap每次只能保证一个线程来访问集合,如果访问这个集合的线程比较多的的话,就可能出现线程不安全问题。)
其次就是HashMap可以接受null的键和值,而HashTable就不行
接下来就介绍一个结合了HashTable的安全性和HashMap的高性能的一个东西,这个东西就叫做ConcurrentHash,在使用ConcurrentHashMap的时候既可以保证多个线程更新数据的同步,又可以保证很高效的查询速度,这就是ConcurrentHashMap。