黑马毕向东Java课程笔记(day15-6——15-13):集合类(集合框架)——泛型+集合对象排序与比较+JDK9对集合的优化+Debug

1、泛型概述级示例
  泛型的特点如下

泛型:JDK1.5版本以后出现新特性,用于解决安全问题,是一个类型安全机制。用于限定类型,避免出现ClassCastException类型转换异常!

好处
1.将运行时期出现问题ClassCastException,转移到了编译时期,方便于程序员解决问题,让运行时问题减少,安全。(eclipse中,编译时期可能出现的异常会标红)
2,避免了强制转换麻烦。

  注意,JDK1.7之后第二个<>内的泛型限定可以不用写
  泛型示例1

/*
练习:按照字符串长度排序。
字符串本身具备比较性。但是它的比较方式不是所需要的,这时就只能使用比较器。
(15-5,5.30:用匿名内部类的方法写,不过这样写代码太繁琐!)
*/
package pack;

import java.util.*;
class GenericDemo 
{
	public static void main(String[] args) 
	{
		//在集合定义的时候就指定数据类型:利用泛型
		ArrayList<String> al = new ArrayList<String>();
		al.add("abc01");//Object obj = new String("abc01");这里其实向上类型转换了!
		al.add("abc0991");
		al.add("abc014");
		
		//由于JDK1.5之后的自动装箱拆箱,因此集合可以添加基本数据类型,系统会自动装箱
		//al.add(4);//al.add(new Integer(4));
//添加后,出现:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String,类型转换异常
//这个异常编译没问题,运行的时候出错!存在安全隐患!(编译只检查代码格式语法是否正确,运行时才会设计内容是否正确)
//我们希望在编译的时候就能看见这个问题(15-6,4.30)。集合在初始化的时候没有明确类型,所以导致传入其他对象编译也不会报错!

//如果我们可以在定义集合的时候就明确里面的对象类型,那么这样传入错误的数据类型编译就会出错!(泛型)
		//这样在编译的时候就会报错,因为传入了错误类型的对象。
		
		Iterator<String> it = al.iterator();
		while(it.hasNext())
		{
			//我们想使用字符串,就必须将it中的元素强制装换为String:(String)it.next()
			String s = it.next();
			//我们将it.next()前面的强制转换去除,发现编译出错!
//因为虽然我们之前存储的是String类型,但是iterator将al传入的数据存放到it中,al在添加元素的时候会向上类型转换Object obj = new String();,必须强制向下转换才可以!
//如果用泛型确定it中只能存储String对象,如果我们往it里面传其他对象,那么编译就会报错!因为此时it只能存储String对象!
			//我们利用泛型将it中的对象强制设置为String类型,编译通过!
			System.out.println(s+":"+s.length());
		}
	}
}

  泛型的格式以及使用时期

泛型格式:通过<>来定义要操作的引用数据类型。

在使用java提供的对象时,什么时候写泛型呢?
通常在集合框架中很常见,只要见到<>就要定义泛型。(15-7,1.15)

其实<> 就是用来接收类型的。当使用集合时,将集合中要存储的数据类型作为参数传递到<>中即可。

  泛型示例2

/*
练习:按照字符串长度排序。
字符串本身具备比较性。但是它的比较方式不是所需要的,这时就只能使用比较器。
(15-5,5.30:用匿名内部类的方法写,不过这样写代码太繁琐!)
*/
package pack;

import java.util.*;
class GenericDemo 
{
	public static void main(String[] args) 
	{
		TreeSet<String> ts = new TreeSet<String>(new LenComparator());
		
		ts.add("abcd");
		ts.add("cc");
		ts.add("cba");
		ts.add("aaa");
		ts.add("z");
		ts.add("hahaha");
		
		Iterator<String> it = ts.iterator();
		while(it.hasNext())
		{
			String s = it.next();//以及明确it中是String对象,不需要强制向下转型
			System.out.println(s);
		}
	}
}

class LenComparator implements Comparator<String>//使用泛型,说明只要比较String类型元素,这是接口的泛型!
{
	//由于格式:compare(T o1, T o2) 
	//Comparator接受String类型,这里参数也要改为String
	public int compare(String o1,String o2)
	{
//我们发现这里接口 Comparator<T>,因此我们可以用泛型指定继承的Comparator的类型,
//那么便不需要在里面强转,因为Comparator在设置泛型后,只能接受String
//		String s1 = (String)o1;
//		String s2 = (String)o1;
		int num = new Integer(o2.length()).compareTo(new Integer(o1.length()));//如果想搞倒序,这里o2与o1转换即可!

		if(num==0)
			return o2.compareTo(o1);//这里o2与o1也要转换
		return num;
	}
}
//comparable也有<T>,也可以使用泛型,也可以避免强制转换。
//我们在写HashSet集合的时候,会重写接口 Collection<E> 中的 hashCode以及Object的equals方法,而equals方法中也有Object参数
//equals必须要写Object,equals复写的是Object,Object没有泛型!因此这里必须的做转换!而Collection有泛型,因此hashCode方法可以用泛型!而不需要写Object。

  到目前为止,我们想自定义一个对象需要做的事情(15-7,11.50)

2、泛型类
  我们想要在自己定义的类中使用泛型。见下面例子:

package pack;

import java.util.*;
class GenericDemo 
{
	public static void main(String[] args) 
	{
		Tool t = new Tool();
		
//		t.setWorker(new Worker());//设置Worker对象
//		t.getWorker();//获取Worker对象
		
		//当Tool设置为Object的时候
//		t.setObject(new Worker());//将Object设置为Worker,设置的时候Worker向上类型转换为Object
//		Worker w = (Worker)t.getObject();//获取的时候Object向下类型转换为Worker
		//上面没问题
		
		//假设不小心传入一个学生
//		t.setObject(new Student());
//		Worker w = (Worker)t.getObject();
		//我们发现编译也没问题,因为语法正确,运行就抛出类型转换异常!
		
		//那么有了泛型之后,我们用泛型来设定要操作的类
//		Utils<Worker> ut = new Utils<Worker>();
//		ut.setGeneric(new Worker());
//		Worker w = (Worker)ut.getGeneric();
		//编译运行正常
		
		//我们将强制装换去除,也很正常!因为我们已经用泛型指定Utils为Worker的工具类
//		Utils<Worker> ut = new Utils<Worker>();
//		ut.setGeneric(new Worker());
//		Worker w = ut.getGeneric();
		
		//如果传入Student,编译时出现类型转换异常!
//		Utils<Worker> ut = new Utils<Worker>();
//		ut.setGeneric(new Student());
//		Worker w = ut.getGeneric();
	}
}
//现在我们有一个Worker类
class Worker
{
	
}
//我们再创建一个使用Worker的工具类
/*
class Tool
{
	private Worker w;
	//设置Worker对象的方法
	public void setWorker(Worker w)
	{
		this.w = w;
	}
	//调用Worker对象的方法
	public Worker getWorker()
	{
		return w;
	}
}
*/
//如果我们现在多了一个学生类,又要设置一个Tool类,很麻烦!那么我们在Tool类中将对象的共性内容抽取出来,定义一个新的Tool
class Student
{}
//泛型前做法。
//我们不确定后续要用的是worker还是Student,那么干脆定义为Object
class Tool
{
	private Object obj;
	//设置Worker对象的方法
	public void setObject(Object obj)
	{
		this.obj = obj;
	}
	//调用Worker对象的方法
	public Object getObject()
	{
		return obj;
	}
}

//泛型后做法。
//干脆我们在定义工具类的时候就用泛型设置参数,然后在调用工具类的时候指定要使用的类型!
class Utils<Generic>
{
	private Generic ge;
	public void setGeneric(Generic ge)
	{
		this.ge = ge;
	}
	//调用Worker对象的方法
	public Generic getGeneric()
	{
		return ge;
	}
}

  说明什么时候定义泛型类?

当类中要操作的引用数据类型不确定的时候,早期定义Object来完成扩展,现在定义泛型来完成扩展。

3、泛型方法
  泛型除了定义在类上,还可以定义在方法上,如下例子

package pack;

import java.util.*;
class GenericDemo 
{
	public static void main(String[] args) 
	{
		//操作String,将String作为泛型的参数
//		Demo<String> d = new Demo<String>();
//		d.show("haha");
//		d.print("hehe");
		
		//如果我们不想打印字符串:
//		Demo<Integer> d = new Demo<Integer>();
//		d.show(new Integer(6));
//		d.print(9);
		//以前我们在定义函数的时候,必须明确操作类型,但是现在不需要
		//public void show(String s);public void print(Integer i);这样就很方便!!!
		
		//但是,有这样一种情况,如下:在同一个类中,show(Integer i),print(String s)
		//目前这样是不行的,因为一个类的泛型只能指定一种类型!而方法中需要用到多种类型。
//		Demo<Integer> d = new Demo<Integer>();
//		d.show(new Integer(6));
//		d.print("haha");//这里泛型设定为Integer,不能操作String
		
		Demo<Integer> d = new Demo<Integer>();
		d.show(new Integer(6));//这个方法的类参数必须与泛型类的类参数相同
		//下面2个方法所操作的类型可以随意定义,他们与泛型类无关,他们有自己的泛型(注意格式与普通调用格式相同)
		d.print("haha");
		d.method(new Integer(4));
		d.method("heihei");//由于方法是泛型,那么它也可以传入多种类型参数
	}
	
}
/*
//定义一个泛型类
class Demo<T>
{
	public void show(T t)//不知道要操作的类,就将方法参数定义为泛型
	{
		System.out.println("show:"+t);
	}
	public void print(T t)
	{
		System.out.println("show:"+t);
	}
}
*/
/*
泛型类定义的泛型,在整个类中有效。如果被方法使用,那么泛型类的对象明确要操作的具体类型后,所有要操作的类型就已经固定了。
为了让不同方法可以操作不同类型,而且类型还不确定。那么可以将泛型定义在方法上。
*/
class Demo<T>
{
	public void show(T t)//这个方法操作的类对象与泛型类所操作类对象相同
	{
		System.out.println("show:"+t);
	}
	//下面定义泛型方法,2个方法的泛型参数不一样,那么就可以对不同的方法设定不同的类
	public <Q> void print(Q q)//注意<Q>写在返回值之前
	{
		System.out.println("show:"+q);
	}
	
	//如果我们将静态方法设置为与泛型类相同的泛型方法,会报错,
//因为静态一开始就加载到内存中,而泛型只有等到创建对象的时候才会明确自己属于哪个对象,才会加载到内存
	/*
	特殊之处:静态方法不可以访问类上定义的泛型。
	如果静态方法操作的应用数据类型不确定,可以将泛型定义在方法上。
	因为静态方法会一开始就加载到内存,而方法泛型可以一开始就加载进去!
	*/
//	public static <T> void method(T t)
//	{
//		System.out.println("method:"+t);
//	}
	//将泛型定义到方法上
	public static <W> void method(W w)
	{
		System.out.println("method:"+w);
	}
}

4、泛型接口
  接口泛型最简单的实现方式(上面的Comparator接口就是这样实现的)

package pack;

import java.util.*;
class GenericDemo 
{
	public static void main(String[] args) 
	{
		interimpl i = new interimpl();
		i.show("haha");
	}
	
}
//定义一个接口,实现泛型T
interface inter<T>
{
	public abstract void show(T t);//定义一个抽象方法,参数为T类型的t变量
}
//再定义一个类实现inter接口,并确定接口泛型的具体类型
class interimpl implements inter<String>
{
	//重写show方法
	public void show(String s)
	{
		System.out.println(s);
	}
}

  泛型的传递:接口泛型——泛型类——调用者再实现

package pack;

import java.util.*;
class GenericDemo 
{
	public static void main(String[] args) 
	{
//		interimpl i = new interimpl();
//		i.show("haha");
		//调用者再指定调用的泛型类的类型
		interimpl<Integer> i = new interimpl<Integer>();
		i.show(4);
	}
	
}

//定义一个接口,实现泛型T
interface inter<T>
{
	public abstract void show(T t);//定义一个抽象方法,参数为T类型的t变量
}

/*
//再定义一个类实现inter接口,并确定接口泛型的具体类型
class interimpl implements inter<String>
{
	//重写show方法
	public void show(String s)
	{
		System.out.println(s);
	}
}
*/

//另一种情况,实现接口的类也不知道该操作哪一种类型。
//我们子类在实现的时候不指定inter的类型,并将子类设置为泛型类,也就是将泛型具体类的设置放到下一级的调用者
class interimpl<T> implements inter<T>
{
	public void show(T t)
	{
		System.out.println(t);
	}
}

5、泛型限定
  泛型限定的特点如下:(视频15-12,18.20开始)

? 通配符。也可以理解为占位符。
泛型的限定;
? extends E: 可以接收E类型或者E的子类型。上限。
? super E: 可以接收E类型或者E的父类型。下限

  泛型限定示例1

package pack;

import java.util.*;
class GenericDemo 
{
	public static void main(String[] args) 
	{//我们定义不同类型的2个泛型限定的ArrayList集合
		ArrayList<String> al = new ArrayList<String>();
		al.add("abc1");
		al.add("abc2");
		al.add("abc3");

		ArrayList<Integer> al1 = new ArrayList<Integer>();
		al1.add(4);
		al1.add(7);
		al1.add(1);

		/*为了提高代码复用性,我们将迭代部分封装成为一个方法
//如果要取出元素,本来我们需要定义2个迭代器,因为泛型的具体类不同,那么用来装al对象的迭代器对象的类型也不同
		Iterator<String> it = al.iterator();//将这个迭代器类型设置为String,限定迭代器只能传入String类型
		while(it.hasNext())
		{
			String s = it.next();//因为迭代器类型被限定,它装的都是String,这里不需要强制转换!
			System.out.println(s);
		}	
		Iterator<Integer> it1 = al1.iterator();//将这个迭代器类型设置为Integer,限定迭代器只能传入Integer类型
		while(it1.hasNext())
		{
			Integer s = it1.next();
			System.out.println(s);
		}
		*/
		
		//将al作为ArrayList对象存入:发现正确打印
//		printColl(al);
//		printColl(al1);//存入al1的话就会出错,因为左右2边不匹配
		
		//在使用了通配符“?”之后:发现成功运行,因为我们在传入参数的时候没有指定具体操作类型,而是等到调用才指定
		printColl(al);
		printColl(al1);
	}
	//创建一个打印集合的类,该类的参数为泛型限定的ArrayList对象
	//ArrayList<String> al:存入al1的话,相当于ArrayList<string> al=new ArrayList<Integer>();
//那么,当我们要操作的对象类型不确定的时候,我们可以用一个通配符“?”表示
	
/*	
需要注意这里的通配符“?”与前面泛型方法或者接口或者类所使用的“T”不同,如果要定义“T”,
那么必须将这个泛型传递下去,将printColl也设置为泛型方法(因为在创建printColl方法的时候我们也不确定操作的具体类型,因此必须将方法也设置为泛型,让具体实现的位置指定具体类型。而使用“?”则不需要传递泛型),这样我们在调用的时候再设置方法的具体操作类型也可以!
public static <T> void printColl(ArrayList<T> al)
	{
		Iterator<T> it = al.iterator();
		while(it.hasNext())
		{//如果定义一个具体的T类型,那么这里就可以创建t类型的对象,写成“?”就没办法这样
			T t = it.next();
			System.out.println(t);
		}	
	}
*/	
	public static void printColl(ArrayList<?> al)
	{
		Iterator<?> it = al.iterator();
		while(it.hasNext())
		{
//			String s = it.next();
//			System.out.println(s);
			//上面2句合并为一句
			System.out.println(it.next());
//it.next().length:想打印对象长度是无法实现的,因为length()是具体类型的方法,而我们现在不确定类型、
//也就是用泛型无法在这里使用类型特有的方法。it.next().toString()就可以,因为所有的类型都具备!
		}	
	}	
}

  示例2,承接上一个示例:

package pack;

import java.util.*;
class GenericDemo 
{
	public static void main(String[] args) 
	{//创建一个集合,用泛型限定其只能接受Person类
		ArrayList<Person> al = new ArrayList<Person>();
		al.add(new Person("abc1"));
		al.add(new Person("abc2"));
		al.add(new Person("abc3"));
		printColl(al);//将ArrayList对象存入调用:发现正常打印姓名
		
		//有了一个Student类,这个时候如果我们往里面存储一个学生,会发生编译失败!
//		ArrayList<Student> al1 = new ArrayList<Student>();
//		al1.add(new Student("abc1"));
//		al1.add(new Student("abc2"));
//		al1.add(new Student("abc3"));
//		printColl(al1);
		//编译失败,这里相当于ArrayList<Person> al1 = new ArrayList<Student>();,而printColl的参数限定为Person
//这样是不允许的,因为虽然Student是Person的子类,我们一开始设置ArrayList<Person>,说明集合中只能装Person,
//而我们后面再设置ArrayList<Student>,集合中只能装Student,后面如果我们想装入Person的其他子类也是做不到的!(视频15-12,11.30)
		//也就是说,泛型限定在确定具体限定类型时,左右两边必须一致!

		/*
解决方案:如果我们将printColl(ArrayList<Person> al)改为printColl(ArrayList<?> al),那么什么类型都可以打印
而我们只想打印Person和Person的子类,这个时候就要用到“泛型限定”——printColl(ArrayList<? extends Person> al)
		*/
		//这个时候我们再往里面存储Student就没问题!
		ArrayList<Student> al1 = new ArrayList<Student>();
		al1.add(new Student("abc--1"));
		al1.add(new Student("abc--2"));
		al1.add(new Student("abc--3"));
		printColl(al1);
	}	
	
	//为了遍历集合,我们定义一个类,往里面传递的参数是Person类型
	public static void printColl(ArrayList<? extends Person> al)
	{
		Iterator<? extends Person> it = al.iterator();
		while(it.hasNext())
		{
			System.out.println(it.next().getName());//获取姓名
		}	
	}	
}

//我们先创建一个Person类
class Person
{
	private String name;
	Person(String name)
	{
		this.name = name;
	}
	public String getName()
	{
		return name;
	}
}
//我们再创建一个学生类继承Peoson
class Student extends Person
{
	Student(String name)
	{
		super(name);
	}
}

  泛型,? super 子类 ,的示例(15-12,19分钟开始)——Comparable的例子不是很好,忽略!

class Comp implements Comparator<Person>//这里比较器首先限定的是Student类
{
	
	public int compare(Person s1, Person s2) 
	{
		//TreeSet接收Student父类Person的比较器,相当于
		//Person s1 = new Student("abc1");多态
		return s1.getName().compareTo(s2.getName());//只能调用子类重写父类的方法或者父类的方法,不能调用子类特有方法!
	}
	
}
/*
TreeSet(Comparator<? super E> comparator):
这里的意思是,TreeSet在接收比较器的时候,既可以接收E类型(Student),也可以接收E的父类型(Person)
那么我们将上面Comparator<Student>改为Comparator<Person>也是可以的,因为TreeSet添加的元素是Student类
那么根据TreeSet(Comparator<? super E> comparator),TreeSet接收的比较器也可以是Student的父类Person的比较器!!
而且利用Student父类Person的比较器,TreeSet一样可以对Student进行比较!

既既可以传Student类型比较器给TreeSet,也可以传Student的父类给TreeSet做比较器,因为他们都可以接受Student对象
*/
	TreeSet<Student> ts = new TreeSet<Student>(new Comp());
	ts.add(new Student("abc1"));
	ts.add(new Student("abc2"));
	ts.add(new Student("abc3"));

5、泛型限定2
  对于泛型,? super 子类 的更加详细的解析——这一部分直接看15-13

package pack;

import java.util.*;
class GenericDemo 
{
	public static void main(String[] args) 
	{
		
		TreeSet<Student> ts = new TreeSet<Student>(new PersonComp());//将Student的父类比较器传入
		ts.add(new Student("abc03"));
		ts.add(new Student("abc02"));
		ts.add(new Student("abc06"));
		ts.add(new Student("abc01"));
		
		Iterator<Student> it = ts.iterator();

		while(it.hasNext())
		{
			System.out.println(it.next().getName());
		}
		
		//---------------我们创建存储Worker的集合并遍历,这时我们还要再创建一个比较器
		TreeSet<Worker> ts1 = new TreeSet<Worker>(new PersonComp());//将Worker的父类比较器传入
		ts1.add(new Worker("abc---03"));
		ts1.add(new Worker("abc---02"));
		ts1.add(new Worker("abc---06"));
		ts1.add(new Worker("abc---01"));
		
		Iterator<Worker> it1 = ts1.iterator();

		while(it1.hasNext())
		{
			System.out.println(it1.next().getName());
		}
	}	
	
}
/*
//创建一个Student比较器的类
class StuComp implements Comparator<Student>
{
	@Override
	public int compare(Student s1,Student s2) {
		return s1.getName().compareTo(s2.getName());
	}	
}

//创建一个Worker比较器的类
class WorkerComp implements Comparator<Worker>
{
	@Override
	public int compare(Worker s1,Worker s2) {
		return s1.getName().compareTo(s2.getName());
	}	
}
*/
//我们前面多一个子类就要创建一个类的比较器类,很麻烦,
//由于TreeSet可以接受其元素的父类对象作为比较器,我们设置一个Person比较器
class PersonComp implements Comparator<Person>
{
	@Override
	public int compare(Person s1,Person s2) {
		return s1.getName().compareTo(s2.getName());
	}	
}

class Person
{
	private String name;
	Person(String name)
	{
		this.name = name;
	}
	public String getName()
	{
		return name;
	}
	public String toString()
	{
		return "person :"+name;
	}
}

class Student extends Person
{
	Student(String name)
	{
		super(name);
	}

}

class Worker extends Person
{
	Worker(String name)
	{
		super(name);
	}
}
/*
第一次结果:正常排序
abc01
abc02
abc03
abc06

第二次结果:创建一个Worker类继承Person,并将前面的部分注释:正常排序
abc---01
abc---02
abc---03
abc---06

第三次结果:用Person对象作为Student和Worker的比较器:2个均正常运行
abc01
abc02
abc03
abc06
abc---01
abc---02
abc---03
abc---06
 */

6、就业班补充——泛型
补充1:
  泛型的基本解析,见下图
在这里插入图片描述

补充2:
  方法的泛型定义的位置

定义含有泛型的方法:泛型定义在方法的修饰符和返回值类型之间

    格式:
        修饰符 <泛型> 返回值类型 方法名(参数列表(使用泛型)){
            方法体;
        }

    含有泛型的方法,在调用方法的时候确定泛型的数据类型
    传递什么类型的参数,泛型就是什么类型

补充3:
  泛型通配符与普通泛型使用的区别

package com.itheima.demo03.Generic;

import java.util.ArrayList;
import java.util.Iterator;

/*
    泛型的通配符:
        ?:代表任意的数据类型
    使用方式:
        不能在创建对象的时候使用
        只能作为方法的参数使用
 */
public class Demo05Generic {
    public static void main(String[] args) {
        ArrayList<Integer> list01 = new ArrayList<>();
        list01.add(1);
        list01.add(2);

        ArrayList<String> list02 = new ArrayList<>();
        list02.add("a");
        list02.add("b");

        printArray(list01);
        printArray(list02);

        //ArrayList<?> list03 = new ArrayList<?>();不能在创建对象的时候使用

        //如果我们使用泛型来解决上面的问题,这样也是可以做到的
        printArray1(list01);
        printArray1(list02);
    }

    /*
        定义一个方法,能遍历所有类型的ArrayList集合
        这时候我们不知道ArrayList集合使用什么数据类型,可以泛型的通配符?来接收数据类型
        注意:
            泛型没有继承概念的,因此不能在泛型中使用Object来替代所有类型
     */
    public static void printArray(ArrayList<?> list){
        //使用迭代器遍历集合
        Iterator<?> it = list.iterator();
        while(it.hasNext()){
            //it.next()方法,取出的元素是Object,可以接收任意的数据类型
            Object o = it.next();
            System.out.println(o);
        }
    }

    public static <E> void printArray1(ArrayList<E> list)
    {
        Iterator<E> iterator = list.iterator();
        while(iterator.hasNext())
        {
            E e = iterator.next();
            System.out.println(e);
        }

    }
}

补充4:
  斗地主案例

package lkj.demo1;

import java.util.ArrayList;
import java.util.Collections;

/*
    斗地主综合案例:
        1.准备牌
        2.洗牌
        3.发牌
        4.看牌
 */
public class DouDiZhu
{
    public static void main(String[] args)
    {
        //1.准备牌
        //定义一个存储54张牌的ArrayList集合,泛型使用String
        ArrayList<String> poker = new ArrayList<>();
        //定义2个数组,一个数组存储牌的花色,一个数组存储牌的序号
        String[] colors = {"♠","♥","♣","♦"};
        String[] numbers = {"2","A","K","Q","J","10","9","8","7","6","5","4","3"};
        //先把大王和小王存储到poker集合中
        poker.add("大王");
        poker.add("小王");

        //通过遍历将其他52张牌添加到poker集合中
        for(int i=0; i<colors.length ;i++)
        {
            for (int j = 0; j < numbers.length; j++)
            {
                poker.add(colors[i]+numbers[j]);
            }
        }

        /*
            2.洗牌
            使用集合的工具类Collections中的方法
            static void shuffle(List<?> list) 使用默认随机源对指定列表进行置换。
         */
        Collections.shuffle(poker);

        /*
            3.发牌
         */
        //定义4个集合,存储玩家的牌和底牌
        ArrayList<String> player01 = new ArrayList<>();
        ArrayList<String> player02 = new ArrayList<>();
        ArrayList<String> player03 = new ArrayList<>();
        ArrayList<String> dipai = new ArrayList<>();

        /*
            遍历poker集合,获取每一张牌
            使用poker集合的索引%3给3个玩家轮流发牌
            剩余3张牌给底牌
            注意:
                先判断底牌(i>=51),否则牌就发没了
         */
        for (int i = 0; i < poker.size(); i++)
        {
            String pai = poker.get(i);
            if(i>=51)
            {
                dipai.add(pai);//注意。集合不可以使用[]索引,相获取某个索引的值,必须使用get()方法
            }
            else if (i%3 == 0)
            {
                player01.add(pai);
            }
            else if (i%3 == 1)
            {
                player02.add(pai);
            }
            else if(i%3 == 2)
            {
                player03.add(pai);
            }
        }

        //4.看牌
        System.out.println("刘德华:"+player01);
        System.out.println("周润发:"+player02);
        System.out.println("周星驰:"+player03);
        System.out.println("底牌:"+dipai);
    }
}

7、就业班补充——数据结构(主要见就业班-java集合类-数据结构-视频解析与PDF文档解析)
补充1:
  数据存储的常见结构有:栈、队列、数组、链表、红黑树。(具体分析见就业班相应的md文档(PDF文档部分内容缺失)与视频解析)

  数组:查询快,增删慢

简单的说,采用该结构的集合,对元素的存取有如下的特点:

*  查找元素快:通过索引,可以快速访问指定位置的元素

*  增删元素慢
  指定索引位置增加元素:需要创建一个新数组,将指定新元素存储在指定索引位置,再把原数组元素根据索引,复制到新数组对应索引的位置。
  指定索引位置删除元素:需要创建一个新数组,把原数组元素根据索引,复制到新数组对应索引的位置,原数组中指定索引位置元素不复制到新数组中。

在这里插入图片描述

  链表:查询慢,增删快,分为数据域(1个)和指针域(2个)2个部分。

查找元素慢:想查找某个元素,需要通过连接的节点,依次向后查找指定元素

* 增删元素快:
  *  增加元素:只需要修改连接下个元素的地址即可。
  *  删除元素:只需要修改连接下个元素的地址即可。

在这里插入图片描述

  二叉树(binary tree):是每个结点不超过2的有序树(tree)。简单的理解,就是一种类似于我们生活中树的结构,只不过每个结点上都最多只能有两个子结点。
  二叉树是每个节点最多有两个子树的树结构。顶上的叫根结点,两边被称作“左子树”和“右子树”。
  我们要说的是二叉树的一种比较有意思的叫做红黑树,红黑树本身就是一颗二叉查找树,将节点插入后,该树仍然是一颗二叉查找树。也就意味着,树的键值仍然是有序的。

红黑树的约束:
1. 节点可以是红色的或者黑色的
2. 根节点是黑色的
3. 叶子节点(特指空节点)是黑色的
4. 每个红色节点的子节点都是黑色的
5. 任何一个节点到其每一个叶子节点的所有路径上黑色节点数相同

红黑树的特点:​速度特别快,趋近平衡树,查找叶子元素最小和最大的次数差不多于二倍,如果大于2倍则不是红黑树。

在这里插入图片描述
  哈希表的数据结构

**JDK1.8**之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

简单的来说,哈希表是由数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的,

在这里插入图片描述
在这里插入图片描述
  总而言之,JDK1.8引入红黑树大程度优化了HashMap的性能,那么对于我们来讲保证HashSet集合元素的唯一,其实就是根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

补充2:集合类比较的重要方法
  注意:对于List集合,用equals()方法来比较2个对象是否相同,而对于HashSet集合,用equals()与hashCode()方法比较2个对象是否相同。(比如List集合的add()与remove()方法底层都是使用equals()方法)。(List集合是按照元素存放到集合找那个的顺序进行排序的)
  对于HashSet集合与HashMap集合,它是无序的,自然也就没有排序的说法。但是他们是不重复的,底层也会通过Comparable接口的compareTo方法对存放的对象进行比较,重复的对象不会存放进来。
  对于TreeSet与TreeMap集合,他们的有序的,但是不是按照存放顺序排序,而是通过类实现Comparable接口,并通过接口的compareTo()方法对对象进行大小比较并排序。
  总而言之,List集合是按照存放顺序进行排序,TreeSet与TreeMap是通过Comparable接口进行排序,而HashMap与HashSet无序。而对于实现comparable接口的类,我们也可以通过compareTo()方法的返回值来比较2个对象的大小(当然也可以通过实现Comparator接口来实现)。
  当我们想对List进行重新排序的时候,使用Collections类的sort方法,而sort方法底层调用的就是Comparable接口的compareTo()方法。我们想使用sort方法对List集合中的某一类的对象进行排序,就必须先使得对象实现Comparable接口的compareTo方法,或者是在sort方法中使用Comparator对象,这样对象就具备比较性,从而可以重新排序。

  各类集合之间比较元素是否相同时底层所引用的方法

  • ArrayList、LinkedList:equals()方法;
  • HashSet、HashMap:equals()方法与hashCode()方法;
  • TreeSet、TreeMap:compareTo方法return 0。
      参考就业班——集合工具类Collections方法——2,3的解析。

补充3:JDK9对集合的优化
  Java 9添加了几种集合工厂方法,更方便创建少量元素的集合、map实例。新的List、Set、Map的静态工厂方法可以更方便地创建集合的不可变实例。

package com.itheima.demo04.JDK9;

import java.util.List;
import java.util.Map;
import java.util.Set;

/*
    JDK9的新特性:
        List接口,Set接口,Map接口:里边增加了一个静态的方法of,可以给集合一次性添加多个元素
        static <E> List<E> of​(E... elements)
        使用前提:
            当集合中存储的元素的个数已经确定了,不在改变时使用
     注意:
        1.of方法只适用于List接口,Set接口,Map接口,不适用于接接口的实现类
        2.of方法的返回值是一个不能改变的集合,集合不能再使用add,put方法添加元素,会抛出异常
        3.Set接口和Map接口在调用of方法的时候,不能有重复的元素,否则会抛出异常
 */
public class Demo01JDK9 {
    public static void main(String[] args) {
        List<String> list = List.of("a", "b", "a", "c", "d");
        System.out.println(list);//[a, b, a, c, d]
        //list.add("w");//UnsupportedOperationException:不支持操作异常

        //Set<String> set = Set.of("a", "b", "a", "c", "d");//IllegalArgumentException:非法参数异常,有重复的元素
        Set<String> set = Set.of("a", "b", "c", "d");
        System.out.println(set);
        //set.add("w");//UnsupportedOperationException:不支持操作异常

        //Map<String, Integer> map = Map.of("张三", 18, "李四", 19, "王五", 20,"张三",19);////IllegalArgumentException:非法参数异常,有重复的元素
        Map<String, Integer> map = Map.of("张三", 18, "李四", 19, "王五", 20);
        System.out.println(map);//{王五=20, 李四=19, 张三=18}
        //map.put("赵四",30);//UnsupportedOperationException:不支持操作异常
    }
}

补充4:Debug
  IDEA对程序的调试方法——见就业班-Map集合-Debug

package com.itheima.demo05.Debug;
/*
    Debug调试程序:
        可以让代码逐行执行,查看代码执行的过程,调试程序中出现的bug
    使用方式:
        在行号的右边,鼠标左键单击,添加断点(每个方法的第一行,哪里有bug添加到哪里)
        右键,选择Debug执行程序
        程序就会停留在添加的第一个断点处
    执行程序:
        f8:逐行执行程序
        f7:进入到方法中
        shift+f8:跳出方法
        f9:跳到下一个断点,如果没有下一个断点,那么就结束程序
        ctrl+f2:退出debug模式,停止程序
        Console:切换到控制台
 */
public class Demo01Debug {
    public static void main(String[] args) {
        int a = 10;
        int b = 20;
        int sum = a + b;
        System.out.println(sum);

        for (int i = 0; i <3 ; i++) {
            System.out.println(i);
        }

        print();

    }

    private static void print() {
        System.out.println("HelloWorld");
        System.out.println("HelloWorld");
        System.out.println("HelloWorld");
        System.out.println("HelloWorld");
        System.out.println("HelloWorld");
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值