泛型
我们在创建一个集合后,因为集合中可以存储任何类型的数据。如果要对这个集合的元素使用。那么拿出这个集合的元素时,Java会选择使用Object类来收这个对象。那么我们在调用方法时,要进行向下转型的操作,这会给我们带来很多的不变,但是Java之后为我们提供了泛型这一机制。我们在定义集合时加入泛型,那么在取出元素时,那就会拿我们定义好的泛型来收,这样就解决了这一问题。
概述:
泛型是JDK1.5之后,引入的一种机制。泛型是把类型明确的工作推迟到创建对象,或者调用方法的时候才去明确类型的机制。
泛型可以被当做参数被传递。
泛型格式:
<数据类型> 这里的数据类型只能是引用类型。
案例:
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList<Integer> integers = new ArrayList<>();
integers.add(10);
integers.add(20);
integers.add(30);
Integer integer = integers.get(1);
String string = integer.toString();
System.out.println(string);
}
}
注意:如果在定义集合时明确了泛型的类型,那么就只能在这个集合中加入泛型这个类型的数据,如果加入别的类型,系统就会报错。
泛型的好处:
- 泛型把运行时期的问题提前到了编译期
- 避免了强制类型转换
- 优化了程序设计
我们可以把泛型定义在类、方法或者接口上。那么被定义的类称为泛型类,方法我们称为泛型方法,接口称为泛型接口。
我们也可以在定义泛型时可以不直接定义明确的类型,可以任意写一个大写字母。这种就是未知类型,可以将明确类型的工作继续推迟,但是如果一旦要创建对象或调用方法,那么就必须明确泛型。如果不明确泛型的类型,泛型就没有意义。
泛型通配符
Collection<?> col5 = new ArrayList<?>
- <?>
任意类型,就是未知类型,没有明确,就可以是Object或者是任意的Java类
- ? extends E:
向下限定,等号右边定义的泛型只能是左边定义泛型类型的本身,或者是它的子类。如果一旦类型大于左边定义泛型的类型,那么就会报错 - ? super E:
向上限定,等号右边定义的泛型只能是左边定义泛型类型的本身或者是它的父类。一旦类型小于左边泛型,就会报错
集合
集合就是Collection接口,数组只能存储一种类型的数据,而集合可以存储不同类型的数据。数组在定义之后,长度就不能被改变。而集合的长度是可变的,一旦往集合中加入元素,系统就会为集合多加一个容量。
图示:
Collection中的方法
- boolean add(Object obj)
向集合中添加一个元素 - boolean addAll(Collection c)
向一个集合中添加另一个集合中所有的元素 - void clear()
清空该集合的元素 - boolean remove(Object o)
删除集合中的指定元素 - boolean removeAll(Collection c)
删除一个集合中与另一个集合的交集中所有的元素 - boolean contains(Object o)
判断集合中是否包含指定元素 - boolean containsAll(Collection c)
判断集合中是否包含另一个集合中的所有元素 - boolean isEmpty()
判断集合是不是一个空集合 - int size()
返回集合的长度 - boolean retainAll(Collection c)
获取两个集合的交集元素
集合的两种遍历方法
第一种是调用toArray()方法,先将集合转化为数组,然后再遍历这个数据即可。
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList<Integer> integers = new ArrayList<Integer>();
integers.add(10);
integers.add(20);
integers.add(30);
integers.add(40);
integers.add(50);
Object[] objects = integers.toArray();
for (int i = 0; i < objects.length; i++) {
System.out.println(objects[i]);
}
}
}
这样遍历,有一个不足的地方,调用toArray()方法转换为数组时,Java会拿Object类来收,如果调用元素的特有方法的时候,还需要向下转型。
第二种是使用迭代器遍历
import java.util.ArrayList;
import java.util.Iterator;
public class Demo {
public static void main(String[] args) {
ArrayList<Integer> integers = new ArrayList<Integer>();
integers.add(10);
integers.add(20);
integers.add(30);
integers.add(40);
integers.add(50);
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
在使用迭代器遍历集合时,不能对集合中的元素进行增删操作,因为在遍历数据前,迭代器已经预先知道了集合的长度,如果在遍历期间增删元素,那么迭代器就会产生错误,所以如果进行增删操作,那么系统就是出现并发修改错误。如果一定要操作,那么我们就使用Arraylist集合中特有的迭代器遍历数组,并且有迭代器特有的增删元素的方法进行操作。
List集合
List集合的特点:
List集合中的元素有序,且每一个元素都有一个索引。
List集合特有的方法:
- void add(int index,E element)
在指定索引处添加元素 - E remove(int index)
删除指定索引处的元素 - E get(int index)
获取指定索引处的元素 - E set(int index,E element)
替换指定索引处的元素
List集合三个子类的特点
- ArrayList
底层结构是数组,查询快,增删慢
线程不安全,效率高 - LinkedList
底层结构是数组,查询快,增删慢
线程安全,效率低 - Vector
底层结构是链表,查询慢,增删快
线程不安全,效率高
所以我们在选择这三个集合时,先要考虑我们要考虑效率,安全和速度的问题,通过问题的需求,来选择合适的集合。
Set集合
Set集合的特点:
Set集合中的元素不能重复,元素是无序的。
那么如何保证元素不重复,我们以HashSet举例。
HashSet
HashSet特点
元素的存取顺序死无序的,元素不能重复
HashSet集合底层的数据结构的哈希表,也就是元素为链表数组,在添加元素时,HashSet首先会调用HashCode()方法,这个方法会返回一个值,这个值用来判断这个元素放在数组中的哪一个位置并存放。如果添加的下一个元素的返回值与上一个元素相同,那么它会以链表的形式,连接到上一个元素下面。equals()方法用来去除重复的元素。先用HashCode()方法来确定元素存放的位置,之后会调用equals()方法会比较这个元素和它存放位置的链表上的每一个元素。如果相等,那么这个元素就不会被加入到这个集合中。
所以HashSet集合用来保证元素的唯一性是靠重写HashCode()和equals()方法来保证的,如果不重写两个方法,那么这个唯一性无法保证。
合理的重新HashCode()方法有两个优点:一个是用来决定元素在数组中的存放位置,第二个是减少碰撞的次数。
碰撞是指调用equals()方法的次数,一旦确定的元素的存放位置,那么就会调用equals()方法和这个位置链表上所有的元素进行比较,用来去除重复的元素,如果重写不合理,那么会导致元素大都竖向存放,而不是横向存放,竖向存放是因为调用HashCode()返回值基本都一致,那么元素位置都是不断的加入在链表上,那么每添加一个新的元素,这个比较的次数就会大大增加,反而降低了我们程序的效率,所以,我们要使元素横向存放,使链表越来越短,这样比较的次数就会越来越低,程序的效率就会大大的提高。
案例:用HashSet集合存放自定义类型对象:
import java.util.HashSet;
public class Demo {
public static void main(String[] args) {
Student s1 = new Student("张三", 20);
Student s2 = new Student("张三", 20);
Student s3 = new Student("李四", 21);
Student s4 = new Student("李四", 20);
Student s5 = new Student("王五", 25);
Student s6 = new Student("赵六", 17);
HashSet<Student> hashSet = new HashSet<>();
hashSet.add(s1);
hashSet.add(s2);
hashSet.add(s3);
hashSet.add(s4);
hashSet.add(s5);
for (Student student : hashSet) {
System.out.println(student.getName()+"==="+student.getAge());
}
}
}
其结果为:
张三=20
李四=20
李四=21
王五=25
LinkedHashSet
特点:
元素有序,并且唯一。 这里的元素有序是指元素的存取顺序有序。
和HashSet集合一样,HashCode()方法和equals()方法用来保证元素的唯一性。
TreeSet
特点:
元素唯一,并且可以对元素进行排序。
图示:
排序有两种:自然排序和比较器排序。
-
自然排序
TreeSet的构造方法有两种,自然排序选的是空参构造,元素必须要满足实现Comparable接口,否则无法进行排序。
实现接口后要重写CompateTo()方法,当然方法是要人为来填写,根据此方法的返回值的 正 负 0 来选则元素在此集合的位置,如果是0那么就表示此元素在集合中已经有,就不会重复存储。 -
比较器排序
使用比较器,是在创建TreeSet集合时选择有参构造,传入一个比较器Comparator。实际上就是传入一个匿名内部类,在该匿名内部类里面重写compare()方法。方法本身还是需要人为书写,也是根据此方法的返回值来判断元素的存放位置和去除重复的元素。