JAVA 基础学习之容器

容器

1、泛型(Generics)

1.1、概述

      泛型是JDK1.5以后增加的,它可以帮助我们建立类型安全的集合。在使用了泛型的集合中,遍历时不必进行强制类型转换。JDK提供了支持泛型的编译器,将运行时的类型检查提前到了编译时执行,提高了代码可读性和安全性。泛型的本质就是“数据类型的参数化”

1.2、好处:

1、提高了代码可读性和安全性。

2、避免了类型强制转换的麻烦。

1.3、特性

      Java中的泛型,只在编译阶段有效。在编译过程中,正确检验泛型结果后,会将泛型的相关信息擦出,并且在对象进入和离开方法的边界处添加类型检查和类型转换的方法。也就是说,泛型信息不会进入到运行时阶段。

对此总结成一句话:泛型类型在逻辑上看成是多个不同的类型,实际上都是相同的基本类型。

1.4、泛型的使用

     泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

1.4.1、泛型类

      泛型类型用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如: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. 泛型的类型参数只能是类类型,不能是简单类型
  2. 不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

  if(ex_num instanceof Generic<Number>){ }

1.4.2、泛型接口

   泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中

//定义一个泛型接口

public interface Generator<T> {    

public T next();

}

  1、当实现泛型接口的类,未传入泛型实参时:

    未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中     即: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;}}

2、当实现泛型接口的类,传入泛型实参时:

    传入泛型实参时:

   定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口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;}}

1.4.3、泛型通配符

     类型通配符一般是使用?代替具体的类型实参,注意了,此处’?’是类型实参,而不是类型形参 。再直白点的意思就是,此处的?和Number、String、Integer一样都是一种实际的类型,是一种真实的类型。可以解决当具体类型不确定的时候,这个通配符就是 ?  ;那么可以用 ? 通配符来表未知类型

public static void printshow(Generic<?> gInteger) {      System.out.println(gInteger);     }

1.4.4、泛型方法

     泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 

泛型方法的基本介绍:

param: 传入的泛型参数  return T 返回值为T类型

  说明:

 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。

 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。

 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。

 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。

/**
     * 泛型方法的基本介绍
     * @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。 (不懂得先知道一下有这个东西的存在)

 

1.4.6、泛型数组

经过查看sun的说明文档,在java中是”不能创建一个确切的泛型类型的数组的。

也就是说下面的这个例子是不可以的:

List<String>[] ls = new ArrayList<String>[10];  

而使用通配符创建泛型数组是可以的,如下面这个例子:

List<?>[] ls = new ArrayList<?>[10];

这样也是可以的:

List<String>[] ls = new ArrayList[10];

    数组的类型不可以是类型变量,除非是采用通配符的方式,因为对于通配符的方式,最后取出数据是要做显式的类型转换的。(取出数据还是比较麻烦,还是尽量不要用泛型数组)

 

2、Collection接口

    Collection 表示一组对象,它是集中、收集的意思。Collection接口的两个子接口是List、Set接口。

由于List、Set是Collection的子接口,意味着所有List、Set的实现类都有上面的方法。

3、List 接口

   3.1、特点

      List是有序、可重复的容器。

     有序: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.3ArrayList特点和底层实现

  3.3.1、特点: 查询效率高,增删效率低,线程不安全

 3.3.2、底层实现

ArrayList底层是用数组实现的存储。

     我们可以看出ArrayList底层使用Object数组来存储元素数据。所有的方法,都围绕这个核心的Object数组来开展。

   怎么实现数组不受限呢?

本质就是定义一个大容量新的数组,将旧数组中的内容拷贝到新数组里面,来实现数组扩展

3.4LinkedList特点和底层实现(双向链表)

 3.4.1、特点:查询效率低,增删效率高,线程不安全

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.3.1、HashMap与HashTable的区别

  1. HashMap:线程不安全,效率高。允许key或value为null。
  2. HashTable:线程安全,效率低。不允许key或value为null。

4.4、HashMap 底层实现

     HashMap底层实现采用了哈希表。

4.4.1、数组和链表的特点

  1. 数组:占用连续空间。寻址容易,查询速度快。但是增删效率低。
  2. 链表:占用空间不连续。寻址困难,查询速度慢。但是增删效率高。

哈希表的优点:就是数组的优点+链表的优点

哈希表的本质就是“数组+链表”。

4.5、HashMap 基本结构

     哈希表的基本结构就是“数组+链表”。核心数组结构:“位桶数组”

   存储数据过程put(key,value)

  1. 先获取key对象的hashcode。
  2. 根据hashcode计算出hash值
  3. 根据对应的hash值来将Entry对象放到table数组中,如果对应的索引没有Entry对象,就直接存进数组中,如果对应索引位置已经有Entry对象,则将已有Entry对象的next指向本Entry对象,形成链表。

jdk1.8:将链表在大于8情况下变为红黑二叉树

这样又大大提高了查找的效率。

    TreeMap和HashMap实现了同样的接口Map,因此,用法对于调用者来说没有区别。HashMap效率高于TreeMap;在需要排序的Map时才选用TreeMap。

5、Set 接口

 5.1、特点:无序、唯一(不可重复)

 Set常用的实现类有:HashSet、TreeSet等,我们一般使用HashSet。

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 底层

   HashSet是采用哈希算法实现。

那为什么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);
		
	}
}

注意: TreeSet中不能放入null元素

 

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");
		}

2、Map取出元素的方法

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 接口

  1.   for 循环遍历
  2.  for-each 遍历(增强for循环)
  3. 使用迭代器遍历

8.2、Set 接口

  1.  for-each 遍历(增强for循环)
  2. 使用迭代器遍历

8.3、Map 接口

1、使用迭代器遍历(根据key获取value)

Set<String> ss=map.keySet(); 

Iterator<String> iterator = ss.iterator();

2、使用迭代器遍历(使用entrySet)

Set<Entry<String, String>> ss = map.entrySet(); 

Iterator<Entry<String, String>> iterator = ss.iterator();

3、可以通过set间接地使用for-each

  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值