JAVA基础(23)-泛型机制-集合

目录

泛型机制

集合概念

Collection接口

一、List:列表

   常用方法

   List的三个实现类:

   Iterator:迭代器接口

二、接口Queue 

   常用方法

三、Set接口

   Hash算法机制

   重写HashCode方法

   Set集合的遍历

   Set集合的元素

   Set接口派生的子类    


泛型机制

      (1)概念
              jdk1.5版本开始使用的新特性,本质是进行"参数化类型",在类,接口,方法的定义上都可以使用,用来指定数据类型名的。
      (2)集合在定义时,可以用泛型机制来指定元素的类型,这样编译器在编译期间就可以进行检查元素类型是否匹配,避免了程序在运行时出现过多的错误  
      (3)集合框架中的所有类型(接口,抽象类,实现类)都是用了泛型机制    
      (4)泛型机制的参数只能传引用类型。

/**
  * 定义类型时,使用泛型机制,
  * 泛型机制的本质是参数化类型
  * (PS:与参数化对象/数据进行对比,与方法的形参比较)
 */
public class MyContainer<M> {
	Object[] values;
	public MyContainer() {
		values = new Object[16];
	}
	/**定义方法时,使用泛型机制*/
	public void add(M m) {
		if((size()+1)>values.length) {
			values = Arrays.copyOf(values, values.length+16);
		}
		for(int i=0;i<values.length;i++) {
			if(values[i]==null) {
				values[i] = m;
				break;
			}
		}
	}
	public M remove(Object obj) {
		M m = null;
		for(int i=0;i<size();i++) {
			if(values[i].equals(obj)) {
				//存储要被移除的对象
				m = (M)values[i];
				System.arraycopy(values, i+1, values, i, size()-i-1);
				values[size()-1]=null;
				break;
			}
		}
		return m;
	}
}

            

集合概念


       用于存储多个对象的单一对象(容器)。存储的数据叫元素。(元素:必须是引用数据类型的数据,不能是基本数据类型的数据。)(集合用于模拟现实生活中的存储容器,因此集合类型,不单单是一种。有很多类型,所以设计成各种存储的数据结构。这些类型统称为集合框架)       

       JDK1.5新特性:
                在1.5以前,容器可以存储Object的任何子类型,但是在对单个元素进行操作时,比如调用元素的方法等。我们必需知道元素的类型,因此在编程中增加了大量代码,来进行强制转换,增加了开发难度。因此1.5开始支持了一个新特性,叫泛型机制。用来规定容器中存储的元素的类型。此机制可以在编译期间就进行判断元素的类型。

       集合与数组的区别:
               数组:可以存储基本数据类型,也可以存储引用数据类型
               集合:只能存储引用数据类型   

Collection接口


    是集合框架的顶级父接口,用来定义常用的抽象方法。子类需要实现相应的方法逻辑。
    
   常用方法:
    boolean add(E e)    将对象e添加到集合中
    int size()     返回集合元素的个数
    boolean addAll(Collection c)    将集合c里的元素添加到此集合中
    void clear()    清空集合元素
    boolean contains(Object obj)    用于判断集合中是否存在与obj相同的元素
    boolean containsAll(Collection c)    用于判断此集合中是否包含集合c中的所有元素
    boolean isEmpty()    用于判断集合元素是否为空
    boolean remove(Object o)    用于移除集合中的某一元素
    boolean removeAll(Collection c)    用于移除此集合中与c集合中共有的元素
    boolean retainAll(Collection c)    用于保留此集合中与c中共有元素 
子接口:List与Set,Queue

	public static void main(String[] args) {
		/* 集合c1中存储  java,c,c++,python*/
		Collection<String> c1 = 
				new ArrayList<String>();
		c1.add("java");
		c1.add("c");
		c1.add("c++");
		c1.add("python");
		System.out.println(c1);
		/* 集合c2中存储 c++,python,php,c#*/
		Collection<String> c2 = 
				new ArrayList<String>();
		c2.add("c++");
		c2.add("python");
		c2.add("php");
		c2.add("c#");
		System.out.println(c2);
		/*从c1中移除c2中的共有元素*/
//		c1.removeAll(c2);
//		System.out.println(c1);
//		System.out.println(c2);
		/*保留c1中与c2中的共有元素*/
		c1.retainAll(c2);
		System.out.println(c1);
		
		/*想使用数组元素时,必须强制转换
		 * 相对来说麻烦
		 * */
		Object[] obj = c2.toArray();
		System.out.println(((String)obj[1]).length());
		/*
		 *为了避免强制转换这一步操作
		 *调用重载方法,传一个元素类型的数组对象即可 
		 */
		String[] arr = c2.toArray(new String[0]);
		System.out.println(arr[1].length());

	}


一、List:列表


    此接口对应的实现类的特点是:有序的,可以重复的。

    有序:存储时与添加的顺序相关。有对应的索引/下标标记位置。从0开始
    重复:存储的元素可以是同一个,也可以是对象内容相同不同对象根据元素的equals方法进行判断
  

   常用方法

     List是Collection的子接口,所以继承了父接口的所有方法
     void add(int index,E element)    将某一元素,插入到此集合的下标index处。
     E  get(int index)    返回指定下标上的元素 

     E set(int index,E newElement)    使用新元素newElement替换下标index上的元素,返回原元素。
     boolean remove(int index)    移除此集合中下标为index上的元素   
     List<E> subList(int fromIndex,int endIndex)
           截取此集合中的一部分,即截取子集,从fromIndex到endIndex
           包前不包后 
           PS:此方法在堆中产生了一个内部类SubList集合对象。此集合对象引用的是父集的一部分,修改子集,会影响父集         
     int lastIndexOf(Object obj)    返回此集合指定元素obj最后一次出现的下标。找不到返回-1.  
     int indexOf(Object obj)    返回指定元素第一次出现的位置。如果没有,返回-1.        

public static void main(String[] args) {
		List<String> list  = new ArrayList<String>();
		list.add("java");
		list.add("C++");
		list.add("C#");
		list.add("php");
		list.add("C#");
		System.out.println(list);
		//将元素python插入到下标2上
		list.add(2, "python");
		System.out.println(list);
		/*获取集合list中的第二个元素*/
		String ele = list.get(1);
		System.out.println(ele.length());
		int index = list.indexOf("C#");
		System.out.println("index:"+index);
	}

   数组与集合之间的转换
           1、集合转数组
                     Object[ ] toArray()
                     E[ ]  toArray(E[] e)   E[]是要转化成的数组类型,列 String[] str = a.toArray(new String[0])
           2、数组转集合
                     List Arrays.asList(数组参数);
           注意:数组转成的集合,不能进行增删操作,否则会出现运行时异常.可以进行替换操作,但是会数组变量有影响。
                       如果想要成功进行增删操作,可以将元素,存入新的集合中。

 

   List的三个实现类:


           (1)ArrayList  列表
                    底层是基于动态数组的数据结构。是有存放顺序的。           
           (2)LinkedList  链表
                   底层是基于双链表的数据结构。每一个存储单元,都涉及到其他两个引用。

        优缺点: 在执行get()/set()时,ArrayList的效率高,LinkedList需要移动指针,效率低
                      在增加/删除操作时,LinkedList效率高,ArrayList效率低(需要扩容,移动元素)。
        ps:当然,在元素的数量大的情况下,区别才明显。         

           (3)Vector:是一个比较古老的集合类型,线程安全,但是效率特别低。虽然安全,也不建议使用。

   Iterator:迭代器接口


    (1)迭代器的作用,用来遍历集合元素。是一个接口。Collection接口提供一个方法 Iterator iterator()
    (2)Collection的实现类使用内部类定义了迭代器子类。
    (3)迭代器提供了统一的方法,用于遍历集合元素。
    常用方法:
     boolean hasNext()
                  判断集合中是否有下一个元素
     E next()
                  取出集合中的下一个元素   
     void remove()        
                 在使用迭代器对集合进行遍历时,不能使用集合的移除方法,移除集合的元素。必须使用迭代器自己提供的移除才行。

练习代码

public static void main(String[] args) {
		/*
		 * 创建一个集合list,用于存储坐标系第2象限的
		 * 整数点    5个
	    *
		* 使用迭代器进行遍历,查看是否有点(-3,2)。
		* 如果有,请从集合中删除
		* */
		List<Point> list = new ArrayList<Point>();
		for(int i=0;i<5;i++) {
			int m = (int)(Math.random()*4);
			int n = (int)(Math.random()*4);
			list.add(new Point(-m,n));
		}
		System.out.println(list);
		
		Iterator<Point> it = list.iterator();
		Point p1 = new Point(-3,2);
		//要想用迭代器查看集合中是否有点(-3,2),需要重写Point的equals方法
		while(it.hasNext()) {   
			Point e= it.next();
			if(p1.equals(e)) {
				it.remove();
			}
		}
		System.out.println(list);
	}


    
    增强for循环-foreach循环
     for(元素类型 变量名:要遍历的集合或者数组){
           
     }

          与经典for循环的区别:
                 (1)增强for循环中无法使用下标。
                 (2)经典for循环中可以使用下标。跟下标有关的逻辑,随便写。

练习代码

public static void main(String[] args) {
		int[] nums = {10,9,8,1,2};
		//foreach循环写法
		for(int num:nums) {
			System.out.println(num);
		}
		//创建一个集合,存储 "1","3","5","7","9"
		List<String> list = new ArrayList<String>();
		for(int i=1;i<=5;i++) {
			list.add(2*i-1+"");
		}
		for(String e:list) {
			System.out.println(e);
		}
	}

二、接口Queue 


            Queue也是Collection的子接口,是一种数据结构,队列。
                 队列:通常都是一端进(offer),另一端出(poll)。
            进出原则:FIFO 先进先出
          
            因为队列要经常进行增删操作,所以使用Linkedlist(链表)实现了Queue接口.  


   常用方法


               boolean offer(E e)    元素从队尾进入队列。
               E poll()    从队首移除元素,返回被移除的元素。当队列没有元素时返回null.
               E peek()    查看队首元素,不移除。队列中没有元素时,返回null.        
               注意:  为了避免移除队列的队首时出现null,我们最好先查看队首是不是null.
                 
           Deque:是一个Queue的子接口,实现的是双端队列的数据结构。
                  双端队列:两端都可以进,也都可以出。   
               boolean offerFirst(E e);
               boolean offerLast(E e)
               E pollFirst();
               E pollLast();
               E peekFirst();
               E peekLast()
   
            栈的数据结构:先进后出:FILO
                    我们可以将双端队列的一端进行禁止操作。另一端进或出,即Stack 
                
               void push(E e)    将元素 e推进栈中
               E pop()    将栈中的最顶端的元素,移除。     

public static void main(String[] args) {
		/*Person人类,有name,age*/
		Deque<Person> stack = new LinkedList<Person>();
	    /*进栈*/
		stack.push(new Person("小A",23));
	    stack.push(new Person("小B",23));
	    /* toString() 按照出栈的顺序进行打印的*/
	    System.out.println(stack);
	    stack.push(new Person("小C",24));
	    System.out.println(stack);
	    /*出栈:*/
	    Person p1 = stack.pop();
	    System.out.println(p1);
	    System.out.println(stack);
	}

三、Set接口


        特点1: 无序,存储的元素与添加顺序无关
        特点2: 不可重复(使用元素的equals方法来判定是否重复)
        特点3: 能存储null元素,只能存储一次。


   Hash算法机制


                 Set集合在添加或查看元素时,当集合中的元素过多时,就是进行多次的比较,效率变低。
                 为了提高效率,在设计元素类型时,提供hash算法,用于返回对象的一个 哈希值(int值)。并在集合所占的内存中开辟很多小的区域,用于存储一定范围的返回值的对象。当我们想添加元素或检索元素,先获取此元素的哈希值,然后去相应区域中查找遍历,
                 --如果在这个区域没有找到对象,说明集合中可以添加这个对象。
                 --如果有,然后查看两个对象的equals的返回值
                 --如果为true,  不能添加  
                 --如果为false, 可以添加,添加至对应的链表结构中(尽可能的避免发生)

   重写HashCode方法

               Object是引用类型的父类,提供了hashCode()方法以及equals()方法因此我们在定义类型时,一般都重写hashCode和equals方法。

               equals方法我们用来判断集合中的元素是否重复
               hashCode方法我们在使用Set集合时,必须要重写,因为我们采用的hash算法计算Set集合元素的存储位置。
               重写规则:尽可能的让所有的成员变量都参与运算,尽可能的避免出现hash值碰撞
        注意,重写的必要性:
              (1)如果重写了equals(), 有必要重写hashCode方法
              (2)如果equals()返回true, hashCode返回值有必要相同
              (3)如果equals()返回false,hashCode返回值不一定相同,如果返回值不同,可以提高检索的效率    
           反过来说:
              (1)hashCode值相同,equals方法可能不同
              (2)hashCode值不同,equals方法一定不同      

 

   Set集合的遍历

           因为Set集合是无序的,无下标可言,因此不能使用经典for循环。我们可以使用迭代器原理。
                 (1) 调用集合的iterator()获取迭代器
                 (2) 使用foreach循环

public static void main(String[] args) {
		/*创建五个方块,存储在Set集合中*/
		Cell c1 = new Cell(1,1);
		Cell c2 = new Cell(1,2);
		Cell c3 = new Cell(1,3);
		Cell c4 = new Cell(1,4);
		Cell c5 = new Cell(1,5);
		Set<Cell> set = new HashSet<Cell>();
		set.add(c1);
		set.add(c2);
		set.add(c3);
		set.add(c4);
		set.add(c5);
		System.out.println(set);
		//set.add(new Cell(1,5)); 存储不进来
		
		/*遍历Set集合*/
		Iterator<Cell> it = set.iterator();
		while(it.hasNext()) {
			Cell e = it.next();
			System.out.println(e);
		}
		/*使用foreach循环*/
		for(Cell c:set) {
			System.out.println(c);
		}
	}

   Set集合的元素

           不能轻易修改参与hash值算法的成员变量。否则容易引起内存溢出。
           原因:成员变量修改后,会出现新的hash值,但是存储位置还在原hash值的位置上。因此操作时,找不到具体的存储位置。  
               

public static void main(String[] args) {
		Set<Cell> set = new HashSet<Cell>();
		Cell c1 = new Cell(1,1);
		Cell c2 = new Cell(1,2);
		Cell c3 = new Cell(1,3);
		Cell c4 = new Cell(1,4);
		Cell c5 = new Cell(1,5);
		set.add(c1);
		set.add(c2);
		set.add(c3);
		set.add(c4);
		set.add(c5);
		System.out.println(set);
		/*修改c2的成员变量 改为  1,10*/
		c2.setCol(10);
		System.out.println(set);
		/*从集合中删除c2
		 * 逻辑:修改后,在删除操作时,使用的是新的hash值,
		 * 去此hash值对应的位置上查找元素,没有找到元素,
		 * 所以,删不掉。
		 * 
		 * */
		set.remove(c2);
		System.out.println(set);
		/*如果想删除元素,改回原来的成员变量值*/
		c2.setCol(2);
		set.remove(c2);
		System.out.println(set);
	}

   Set接口派生的子类    

  
              HashSet:通过实现hash算法的一种数据结构,无序,不重复。增加删除时效率高  
              LinkedHashSet:通过实现hash算法的一种数据结构,但是通过链表来维持顺序。顺序与添加顺序一致。
              TreeSet: 是SortedSet子接口的实现类,使用二叉树的数据结构维护元素的顺序

/**
 * Set集合:不可重复
 */
public class TestSet01 {
	public static void main(String[] args) {
		//Point坐标点 (x,y)
		Set<Point> ps = new HashSet<Point>();
		ps.add(new Point(2,3));
		ps.add(new Point(4,5));
		ps.add(new Point(2,2));
		System.out.println(ps);
		ps.add(new Point(2,3));
		System.out.println(ps);
		
		ps.add(new Point(3,2));
		System.out.println(ps);
	}
}

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值