容器
1、泛型(Generics)
1.1、概述
泛型是JDK1.5以后增加的,它可以帮助我们建立类型安全的集合。在使用了泛型的集合中,遍历时不必进行强制类型转换。JDK提供了支持泛型的编译器,将运行时的类型检查提前到了编译时执行,提高了代码可读性和安全性。泛型的本质就是“数据类型的参数化”
1.2、好处:
1.3、特性
Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。
对此总结成一句话:泛型类型在逻辑上看成是多个不同的类型,实际上都是相同的基本类型。
1.4、泛型的使用
泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map。
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
private 泛型标识 /*(成员变量类型)*/ var;
.....
}
public class TestOO {
public static void main(String[] args) {
System.out.println(new Test(1436788).getAge());
System.out.println(new Test("rwrwrw").getAge());
System.out.println(new Test(true).getAge());
}
}
class Test<T>{
private T age;
public Test(T age) {
this.age=age;
}
public T getAge() {
return age;
}
}
注意:
1.4.2、泛型接口
泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中
public interface Generator<T> {
public T next();
}
未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中 即:class FruitGenerator<T> implements Generator<T>{如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"(也就是说,对应的实现类定义成泛型类)
class AAA<T> implements Generator<T>{@Overridepublic T next() {// TODO Auto-generated method stubreturn null;}}
定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T> 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
class AAA implements Generator<String>{@Overridepublic String next() {// TODO Auto-generated method stubreturn null;}}
类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,是一种真实的类型。可以解决当具体类型不确定的时候,这个通配符就是 ? ;那么可以用 ? 通配符来表未知类型。
public static void printshow(Generic<?> gInteger) { System.out.println(gInteger); }
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
param: 传入的泛型参数 return T 返回值为T类型
1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
/**
* 泛型方法的基本介绍
* @param tClass 传入的泛型实参
* @return T 返回值为T类型
*/
public <T> T genericMethod(Class<T> tClass)
throws InstantiationException ,
IllegalAccessException{
T instance = tClass.newInstance();
return instance;
}
总结:判定该方法是否是泛型方法,看是否声明了 <T> 作为类型
1.4.5、泛型上下界
在泛型编程时,使用部分限定的形参时,<? super T>和<? extends T>的使用场景容易混淆,PECS原则可以帮助我们很好记住它们:生产者(Producer)使用extends,消费者(Consumer)使用super。
上边界:指的是该类的所有子类以及子类以下的类;(上子)
下边界:指的是该类的父类以及父类以上的类 ;(下父)
示例:
Number 的子类为 byte、double、float、int、long 和 short 的方法。
//上边界
public static void printshow(Generic<? extends Number> gInteger) {
System.out.println(gInteger);
}
//下边界
public static void printshow(Generic<? super Number> gInteger) {
System.out.println(gInteger);
}
总结:PECS原则:频繁往外读取内容的,适合用上界Extends。经常往里插入的,适合用下界Super。 (不懂得先知道一下有这个东西的存在)
经过查看sun的说明文档,在java中是”不能创建一个确切的泛型类型的数组”的。
数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。(取出数据还是比较麻烦,还是尽量不要用泛型数组)
2、Collection接口
Collection 表示一组对象,它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。
由于List、Set是Collection的子接口,意味着所有List、Set的实现类都有上面的方法。
3、List 接口
3.1、特点
有序:List中每个元素都有索引标记。可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素。
可重复:List允许加入重复的元素。更确切地讲,List通常允许满足 e1.equals(e2) 的元素重复加入容器。
List接口常用的实现类有3个:ArrayList、LinkedList和Vector。
3.2、ArrayList方法的使用
public class TestList {
public static void main(String[] args) {
List<Integer> list=new ArrayList<Integer>();
System.out.println(list.isEmpty()); // true,容器里面没有元素
list.add(1);//添加
list.add(2);//添加
List<Integer> list2=new ArrayList<Integer>();
list2.add(1);
list2.add(2);
list2.add(3);
System.out.println("list2是否包含list:"+list2.containsAll(list));
list.set(1, 3);//修改
list.get(1);//根据索引获取指定的元素
list.remove(1);//根据索引删除指定的元素
list.indexOf(2);
System.out.println(list.isEmpty()); // false,容器里面有元素
Object[] objs = list.toArray();
System.out.println("转化成Object数组:" + Arrays.toString(objs));
list.clear();//清空所有的元素
System.out.println(list);
}
}
3.3、ArrayList特点和底层实现
我们可以看出ArrayList底层使用Object数组来存储元素数据。所有的方法,都围绕这个核心的Object数组来开展。
本质就是定义一个大容量新的数组,将旧数组中的内容拷贝到新数组里面,来实现数组扩展
3.4、LinkedList特点和底层实现(双向链表)
public class TestLinkedList {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<String>();
//通过这两个方法就可以模拟内存堆栈的结构,
//从而对一些特殊场景业务进行处理,以下是一个简单实例:
list.push("栈帧1");
list.push("栈帧2");
list.push("栈帧3");
list.push("栈帧4");
//堆栈结构 push(), pop()
/*for (int i = 0, j = list.size(); i < j; i++) {
System.out.println("entry:"+list.pop());
System.out.println("size :"+list.size());
}*/
System.out.println("----------------");
// LinkedList类实现了Queue(队列)接口,
// 在Queue接口中提供了offer,peek和poll三个方法用于添加和获取元素:
/*boolean offer(E e)
将指定的元素插入此队列(如果立即可行且不会违反容量限制),
当使用有容量限制的队列时,此方法通常要优于 add(E),
后者可能无法插入元素,而只是抛出一个异常。
E peek()
获取但不移除此队列的头;如果此队列为空,则返回 null。
E poll()
获取并移除此队列的头,如果此队列为空,则返回 null。
*/
for (int i = 0, j = list.size(); i < j; i++) {
System.out.println("entry:" + list.peek()); //获取队列的头
System.out.println("size :" + list.size());
}
System.out.println("----------------");
for (int i = 0, j = list.size(); i < j; i++) {
System.out.println("entry:" + list.poll()); //获取并移除队列的头
System.out.println("size :" + list.size());
}
}
}
3.5、Vector向量
Vector底层是用数组实现的List,相关的方法都加了同步检查,因此“线程安全,效率低”。 比如,indexOf方法就增加了synchronized同步标记。
总结:由于在查询方法增加了同步标记,所以查询比arraylist慢。
4、Map 接口
4.1、特点:通过键和值一一对应的格式来储存的,简称“键值对”
4.2、概述
Map就是用来存储“键(key)-值(value) 对”的。 Map类中存储的“键值对”通过键来标识,所以“键对象”不能重复。
Map 接口的实现类有HashMap、TreeMap、HashTable、Properties等。
4.3、HashMap 和HashTable
HashMap采用哈希算法实现,是Map接口最常用的实现类。 由于底层采用了哈希表存储数据,我们要求键不能重复,如果发生重复,新的键值对会替换旧的键值对。 HashMap在查找、删除、修改方面都有非常高的效率。只不过HashTable的方法添加了synchronized关键字确保线程同步检查,效率较低。
public class TestHashMap {
public static void main(String[] args) {
Map<Integer, String> map=new HashMap<Integer,String>();
map.put(1, "大哥");
map.put(2, "二哥");
map.put(3, "三哥");
System.out.println(map.containsKey(2));
System.out.println(map.containsValue("大哥"));
System.out.println(map.toString());
map.put(1, "dnaf");//键重复了,则会替换旧的键值对
System.out.println(map.toString());
System.out.println("HashTable 使用-----");
Map<Integer, String> map2=new Hashtable<>();
map2.put(1, "小明");
map2.put(2, "小红");
map2.put(3, "小朱");
map2.put(1, "方法");//键重复了,则会替换旧的键值对
System.out.println(map2.toString());
}
}
4.4、HashMap 底层实现
4.5、HashMap 基本结构
哈希表的基本结构就是“数组+链表”。核心数组结构:“位桶数组”
TreeMap和HashMap实现了同样的接口Map,因此,用法对于调用者来说没有区别。HashMap效率高于TreeMap;在需要排序的Map时才选用TreeMap。
5、Set 接口
5.1、特点:无序、唯一(不可重复)
Set常用的实现类有:HashSet、TreeSet等,我们一般使用HashSet。
public class TestSet {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
set.add("A");
set.add("C");
set.add("B");
System.out.println(set);
set.add("C");//相同的元素不会被加入
System.out.println(set);
set.add(null);
System.out.println(set);
set.add(null);
System.out.println(set);
}
}
5.2、HashSet 底层
从图中我们发现里面有个map属性,这就是HashSet的核心秘密。
往set中加入元素时,本质就是把这个元素作为key加入到了内部的map中
由于map中key都是不可重复的,因此,Set天然具有“不可重复”的特性。
5.3、TreeSet的使用和底层
TreeSet底层实际是用TreeMap实现的,内部维持了一个简化版的TreeMap,通过key来存储Set的元素。 TreeSet内部需要对存储的元素进行排序。因此,我们对应的类需要实现Comparable接口。这样,才能根据compareTo()方法比较对象之间的大小,才能进行内部排序。
public class Test {
public static void main(String[] args) {
Set<User> set=new TreeSet<>();
String[] str= {"小乔","大乔","周瑜","亚瑟","后羿","诸葛","司马","庄周","猴子","韩信"};
for (int i = 0; i < 10; i++) {
set.add(new User((i+1),str[i],(int)(200+(Math.random()*5000))));
}
System.out.println(set.toString());
}
}
class User implements Comparable<User>{
int id;
String name;
int salay;
public User(int id, String name, int salay) {
super();
this.id = id;
this.name = name;
this.salay = salay;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String toString() {
return "id号 : " + this.id + " 名字 : " + this.name + " 工资 : "+this.salay+"\n";
}
@Override
public int compareTo(User o) {
// TODO Auto-generated method stub
//按什么排序就用什么字段 (升序就是大于的就改为1,小于的-1,倒序就反过来)
return (this.salay>o.salay) ? -1:(this.salay<o.salay ? 1: 0);
}
}
6、Iterator 迭代器
Iterator 主要用于遍历 Collection 集合中的元素,也有称为迭代器或迭代精灵。
1、list和set取出元素的方法一样的
List<String> list = new ArrayList<String>();
for (int i = 0; i < 5; i++) {
list.add("a" + i);
}
Iterator<String> iterator=list.iterator();
System.out.println(iterator.hasNext());
while (iterator.hasNext()) {
String string = (String) iterator.next();
System.out.println(string+"a");
}
Map<String, String> map = new HashMap<String, String>();
map.put("A", "杨过");
map.put("B", "杨洋");
//第一种:我们也可以通过map的keySet()、valueSet()获得key和value的集合,
//从而遍历它们。
Set<String> ss=map.keySet();
Iterator<String> iterator = ss.iterator();
System.out.println(iterator.hasNext());
while (iterator.hasNext()) {
String key = iterator.next();
System.out.println(key+"="+map.get(key));
}
//第二种
Set<Entry<String, String>> ss = map.entrySet();
Iterator<Entry<String, String>> iterator = ss.iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry =
(Map.Entry<String, String>) iterator.next();
}
/*三种:将map中的键取出放进set集合中,之后通过遍历set将键取出,
* 可以通过键从map取出对应的值,(键就类似一个索引)
*/
Set<String> ss=map.keySet();
for (Integer integer : ss) {
System.out.println(integer+"="+map.get(integer));
}
注意:每个迭代器只能遍历一次喔(也就说只能使用一次喔)
7、Collections 工具类的常用方法
类 java.util.Collections 提供了对Set、List、Map进行排序、填充、查找元素的辅助方法。
1. void sort(List) //对List容器内的元素排序,排序的规则是按照升序进行排序。
2. void shuffle(List) //对List容器内的元素进行随机排列。
3. void reverse(List) //对List容器内的元素进行逆续排列 。
4. void fill(List, Object) //用一个特定的对象重写整个List容器。
5. int binarySearch(List, Object)//对于顺序的List容器,采用折半查找的方法查找特定对象。
8、遍历集合的方法
8.1、List 接口
8.2、Set 接口
8.3、Map 接口
Set<String> ss=map.keySet(); Iterator<String> iterator = ss.iterator();
Set<Entry<String, String>> ss = map.entrySet(); Iterator<Entry<String, String>> iterator = ss.iterator();
3、可以通过set间接地使用for-each