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

被折叠的 条评论
为什么被折叠?



