Java基础课的中下基础课04

2023-12-14 10:56:18

?目录

二十三、集合相关

23.1 集合

(1)集合的分支

23.2 List有序可重复集合

(1)ArrayList类

(2)泛型

(3)ArrayList常用方法

(4)Vector类

(5)Stack类(栈)

(6)Queue/ kju? /类

(7)LinkedList类

23.3 Set无序无重复集合

(1)HashSet类

(2)Set集合常用方法

(3)HashSet无重复的原则

(4)TreeSet类

(5)TreeSet无重复规则是如何实现的?

(6)Set集合总结

23.4 Map集合

(1)Map介绍

(2)HashMap

(3)如何选用集合和数组?

(4)登录小流程来体验如何选用集合

(5)TreeMap

(6)设计学生做选择题(考试机)

二十四、错误与异常

24.1 异常

(1)异常的分支体系:

①、运行时异常(非检查异常)

②、编译时异常(检查异常)

(2)添加处理异常的手段:

①、try{ }catch(){ }[finally{ }]

②、throws抛出

(3)自定义异常


二十三、集合相关

23.1 集合

1、变量----容器:存储一个元素

2、数组----容器:一组具有某种特性的数据存在在一起,存储一组元素(数据类型一致),长度固定

3、我们自己封装了一个Box(ArrayBox、LinkedBox)----容器:存储一组元素,长度可变

4、集合--容器:集合是指具有某种特定性质的具体或抽象的对象汇总而成的集体与数组类似,集合的长度存储之后还能改变,集合用来存储一组元素,可以理解为集合是我们封装的Box,只不过比我们写的更加优秀,更多的方法

5、集合的分支:Collection、Map、List、Set都是接口,需要通过子类来完成操作(所有的集合都在Java.util)

(1)集合的分支

1、Collection:存储的都是value(value有可能是基本类型或引用类型)

2、Map:存储的是以Key-value(键值对)的形式存在的,(Key无序无重复,Value无序可重复)

3、List:有序可重复

4、Set:无序无重复

(1)序:顺序,添加进去的元素 和 取得元素的顺序一致(指的不是集合自己的顺序)

(2)重复:两个对象元素一致

23.2 List有序可重复集合

1、List集合的子类:(1)ArrayList(2)LinkedList(3)Vector

2、ArrayList 和 Vector区别:

(1)相同:底层都是数组

(2)不同:类似StringBuffer 和 String Builder的不同,但是相反(早--线程同步安全效率低;晚---线程非同步不安全效率高)

ArrayList是早版本、Vector是晚版本(早--线程非同步不安全效率高;晚---线程同步安全效率低)

(3)特点:Vector底层也是利用(动态)数组的形式存储;Vector是线程同步的(synchronized),安全性高,效率低

(1)ArrayList类

1、ArrayList------底层就是数组

(1)所属包java.util

(2)有序:顺序,添加进去的元素 和 取得元素的顺序一致(指的不是集合自己的顺序)

(3)重复:两个对象元素一致

(4)ArrayList特点适合遍历查询,不适合插入删除,按照1.5倍扩容

(6)创建对象:无参构造方法 和 带默认空间的构造方法 和 带collection参数的构造方法ArrayList(cllection c){}(意思是它可以将collection家族的任何一个构建成一个ArrayList)

(6)常用方法---小容器

add();get();remove();size()个数;能添取删,由于ArrayList底层是一个Object[],所以什么类型都可以存进去,取出来的时候是多态的效果,需要自己造型才可以,显得用来了非常麻烦,所以在JDK1.5版本以后提出了一个概念泛型

ArrayList objects = new ArrayList();
objects.add("abc");
String value = (String) objects.get(0);//很麻烦,需要自己在进行造型才能赋值使用
value.trim();

(2)泛型

1、泛型:用来规定数据类型的,定义的时候用一个符合代替某种类型,在使用的时候用具体的数据类型,将定义的那个符合替换掉

如:ArrayList< T >------------->ArrayList< String >

2、泛型可以使用在哪里?

(1)类定义的时候描述某种数据类型,所以集合的使用就是这样

//ArrayList是个集合,这个集合里面只能存String
ArrayList<String> list = new ArrayList<String>();

(2)泛型接口:与泛型类的使用基本一致,子类实现接口时必须添加泛型

public interface Person<X> {
 ? ?public X value;
}
public class Son<X> implements Person<X>{
 ? ?
}

3、泛型方法:方法调用时传参数在方法描述的时候加泛型,注意方法的泛型与类无关,带有泛型的方法可以不放在带有泛型的类中

4、高级泛型:用于规范边界的 extends、super

(3)ArrayList常用方法

 ? ? ? 
/*
 ? ? ? ? ? ?ArrayList objects = new ArrayList();
 ? ? ? ? ? ?objects.add("abc");
 ? ? ? ? ? ?String value = (String) objects.get(0);
 ? ? ? ? ? ?value.trim();
 ? ? ? ?*/
?
 ? ? ? ?//ArrayList(有序可重复)是个集合,这个集合里面只能存String
 ? ? ? ?ArrayList<String> list = new ArrayList<String>();
 ? ? ? ?list.add("a");
 ? ? ? ?list.add("b");
 ? ? ? ?list.add("c");
 ? ? ? ?list.add("d");
 ? ? ? ?System.out.println(list.get(0));//a
 ? ? ? ?System.out.println(list.get(3));//d
// ? ?  System.out.println(list.get(4));//没有索引4,所以异常IndexOutOfBoundsException
 ? ? ? ?System.out.println(list.size());//4  有效元素个数
?
 ? ? ? ?for (int i = 0; i < list.size(); i++) {
 ? ? ? ? ? ?String value = ?list.get(i);
 ? ? ? ? ? ?System.out.print(value + "\t");//a b c d
 ? ? ?  }
System.out.println("==================================================================================================");
?
 ? ? ? ?System.out.println(list);//list底层重写了toString方法,原本Object是返回包名.类名@ hashCode
System.out.println("==================================================================================================");
?
 ? ? ? ?/*
 ? ? ? ? ? ?集合元素全部删除,通过for循环是否能把所以元素删除?不能
 ? ? ? ? ? ?i=0时,size=4,list=abcd
 ? ? ? ? ? ?删除后为:bcd
 ? ? ? ? ? ?i=1时,size=3,list=bcd
 ? ? ? ? ? ?删除后为:bd
 ? ? ? ? ? ?i=2时,size=2
 ? ? ? ? ? ?remove(int index);删除索引为index的位置元素,所以删不干净
 ? ? ? ? ? ?remove(i);  和 ? remove(0) ? 都删不干净
 ? ? ? ? ? ?因为你的size一直在变化,所以删不干净
 ? ? ? ? ? ?for (int i = 0; i < list.size(); i++) {
 ? ? ? ? ? ? ? ?list.remove(i);
 ? ? ? ? ? ?}
 ? ? ? ?*/
        //这样可以删除干净,给一个固定值
 ? ? ? ?int size = list.size();
 ? ? ? ?for (int i = 0; i < size; i++) {
 ? ? ? ? ? ?list.remove(i);
 ? ? ?  }
 ? ? ? ?System.out.println(list);

(1)boolean = add(E e); 将指定的元素追加到此列表的末尾

(2)void = add(int index, E e); 在此列表中的指定位置插入指定的元素。

(3)boolean = addAll(Collection c); 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。

(4)boolean = addAll(int index , Collection c); 将指定集合中的所有元素插入到此列表中,从指定的位置开始。

//泛型不能使用基本类型————如果想要使用基本类型,需要使用其对应的包装类
ArrayList<Integer> list1 = new ArrayList<Integer>();
list1.add(10);
list1.add(20);
list1.add(30);
list1.add(40);
ArrayList<Integer> list2 = new ArrayList<Integer>();
list2.add(300);
list2.add(100);
//我想要将list2集合中的所有元素添加到list1中
list1.addAll(list2);
System.out.println(list1);//[10,20,30,40,300,100]
/*
 ? ?list1.addAll(Collection<? extends E> list2)
 ? ?将list2集合中的全部元素存入list1集合中,这个?问号,问的就是你传进来的是不是我的子类
 ?如:
 ? ?如果list1存在泛型Integer,list2存在泛型String,那么就不对,类型至少是一致的
*/
//将list2里面的内容插入到索引为2号的list1中
list1.addAll(2,list2);//[10,20,300,100,30,40]

(5)void = clear();将集合内的全部元素清除

(6)boolean = contains(Object);找寻某一个给定的元素是否在集合中拥有

boolean b = list1.contains(100);//list1中是否存在100元素
System.out.println(b);//true

(7)void = ensureCapacity(int minCapacity);如果需要,增加此 ArrayList实例的容量,以确保它可以至少保存最小容量参数指定的元素数。

(8)E = get(int index); 返回此列表中指定位置的元素(返回值是一个咱们自己规定的类型)

(9)int = indexOf(); 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。

(10)int = lastIndexOf(); 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。

(11)boolean = isEmpty();判断集合是否为空,如果size的个数为0就是空

(12)Iterator = iterator();返回值是它自己,迭代器,JDK1.5版本之后,以正确的顺序返回该列表中的元素的迭代器。

(13)boolean = remove(int index);删除指定索引位置的元素

(14)boolean = remove(Object obj);删除指定元素,就是我不知道这个元素在哪,不知道索引;

//这两个出现重载,那么remove(1)是删除的位置?还是1这个元素?
list.remove(1);//删除的是位置
list.remove(new Integer(1));//删除的是1这元素

(15)boolean = removeAll(Collection c);差集 从此列表中删除指定集合中包含的所有元素。(Collection 集合的意思)

(16)boolean = retainAll(Collection c);交集 仅保留此列表中包含在指定集合中的元素。

(17)E = set(int index , E value); 用指定的元素替换此列表中指定位置的元素。

ArrayList<Integer> list3 = new ArrayList<>();
 ? ? ? ?ArrayList<Integer> list4 = new ArrayList<>();
 ? ? ? ?list3.add(10); ? ? ? ? ?list4.add(10);
 ? ? ? ?list3.add(20); ? ? ? ? ?list4.add(200);
 ? ? ? ?list3.add(30);
?
 ? ? ? ?//list3 = [10,20,30] ? ? ?  list4 = [10,200] ?  最终:list3 = [10,20,30,10,200]
 ? ? ? ?list3.addAll(list4);//并集,将list2中的元素合并给list1
 ? ? ? ?//list3 = [10,20,30,10,200] list4 = [10,200] ?  最终:list3 = [20,30]
 ? ? ? ?list3.removeAll(list4);//差集,在list1中将list2与list1相同的元素及其list2中的所有元素都删除
 ? ? ? ?//list3 = [20,30] ? ? ? list4 = [10,200] ?  最终:list3 = [200]
 ? ? ? ?list3.retainAll(list4); //返回list3和list4中相同的元素
?
System.out.println("==================================================================================================");
?
 ? ? ? ?ArrayList<Integer> list5 = new ArrayList<>();
 ? ? ? ?ArrayList<Integer> list6 = new ArrayList<>();
 ? ? ? ?list5.add(10); ? ? ? ? ?list6.add(10);
 ? ? ? ?list5.add(20); ? ? ? ? ?list6.add(200);
 ? ? ? ?list5.add(30);
 ? ? ? ?//set修改,它也是会保留原本的元素
 ? ? ? ?int value = list5.set(1,10);
 ? ? ? ?System.out.println(list5);//list5 = [10,10,30]
 ? ? ? ?System.out.println(value);//20

(18)List = subList(int begin , int end);返回此列表中指定的 begin (包括)和 end之间的独占视图。

ArrayList<Integer> list5 = new ArrayList<>();
list5.add(10);
list5.add(20);
list5.add(30);
list5.add(40);
 ? ? ? ?List<Integer> newList = list5.subList(1,3);
 ? ? ? ?System.out.println(newList);//[20,30]

(19)Object[] = toArray();集合变成数组 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。

(20)T[] = toArray( T [] );看起来是个参数,实则是为了让它装满的。以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。

ArrayList<Integer> list6 = new ArrayList<>();
list6.add(10);
list6.add(20);
list6.add(30);
list6.add(40);
//实现准备好一个空的数组,这个数组其实是用来接收返回值的(接收list6这个集合中的每一个元素)
Integer[] x = new Integer[list6.size()];//这个数组里面没有元素,目的是为了拿这个数组装东西
//我想要同学手里的椅子,我很懒,不想动手,这时我就提前准备好一个空教室,让同学们把椅子装到这个空教室中
//所以做返回结果不一定用返回值,可以用引用类型参数,我给你一个空教室,你把这个教师塞满
//把list6中的元素挨个装进这个空数组x中
list6.toArray(x);
System.out.println(x.length);//4

(21)void = trimTosize();变成有效元素个数那么长; 修改这个 ArrayList实例的容量是列表的当前大小。

(4)Vector类

1、Vector

(1)java.util包

(2)是ArrayList集合的早期版本1.0版本(StringBuffer早期1.0版本、StringBuilder是后期JDK1.5版本)

(3)特点:Vector底层也是利用(动态)数组的形式存储;Vector是线程同步的(synchronized),安全性高,效率低

(4)扩容方式与ArrayList不同,Vector是扩容2倍,可以通过构造方式创建对象时修改这一机制

(5)有三个构造参数,无参 ,有参,带collection(集合)参数的构造方法

//无参,默认容量20,空间不够自动增加20个
Vector<String> vector = new Vector<String>();
for (int i = 0; i < 20; i++) {
    vector.add("a");
    //有效元素个数,底层真实数组容量
    System.out.println(vector.size() + "----------" + vector.capacity());
}
//有参,自己定义创建100个空间
Vector<String> vector2 = new Vector<String>(100);
//默认刚开始就创建4个空间,如果不够用每次就扩大4个空间,4个4个的增加空间
Vector<String> vector3 = new Vector<String>(4,4);

(5)Stack类(栈)

1、Stack类:先进后出

(1)java.util包

(2)继承Vector类,是Vector的子类

(3)构造方法只有一个无参数的

(4)除了继承Vector类的方法为还有几个特殊方法

2、stack中的常用方法

(1)E = push(E e);将某一个元素压入栈顶,压栈

(2)E = pop();将某一个元素从栈顶取出并删除,出栈

(3)E = peek();查看栈顶的一个元素,不删除只是看

(4)boolean = empty();判断栈内元素是否为空

(5)int = search();查找给定的元素在栈中的位置

Stack<String> stack = new Stack<>();
stack.push("a");//将a压入      a
stack.push("b");//将b压入      b————a
stack.push("c");//将c压入      c————b————a
//此时c是栈顶
System.out.println(stack);//[a,b,c] 最右边为栈顶
System.out.println(stack.pop());//c 将栈顶元素取出删除
System.out.println(stack);//[a,b]   最右边为栈顶
System.out.println(stack.peek());//b将栈顶元素取出不删除
System.out.println(stack);//[a,b]
System.out.println(stack.search("b"));//2,返回元素的第几个位置(不是所索引)

(6)Queue/ kju? /类

1、Queue

(1)java.util包

(2)通过无参数构造方式创建对象

2、一般方法

(1)boolean = add(E e); 将指定的元素插入到此队列中,如果可以立即执行此操作,而不会违反容量限制, true在成功后返回 IllegalStateException如果当前没有可用空间,则抛出IllegalStateException。

(2)E = element()————get();检索,但不删除,这个队列的头。取得是栈底的第一个元素,也就是你压入得第一个元素,不删除

(3)E = remove(); 检索并删除此队列的头。

(4)bollean = offer(E e);如果在不违反容量限制的情况下立即执行,则将指定的元素插入到此队列中。 相当于add,但是这个方法不会抛异常,出现错误就变为空

(5)E = peek();检索但不删除此队列的头,如果此队列为空,则返回 null 。相当于element方法

(6)E = poll;检索并删除此队列的头,如果此队列为空,则返回 null 。相当于remove()

(7)LinkedList类

1、LinkedList:他是List的子类,也是Queue的子类

(1)java.util包,自己封装过(LinkedBox,内部类Node<T>对象(节点 prev item next))

(2)底层使用双向链表的数据结构形式来存储,适合于插入或删除,不适合遍历查询

(3)构建对象:无参数构造对象,带参数构造方法LinkedList(Collection <? extends E> c)

(4)常用方法

增删改查:add();remove();ser();get();size();------- offer();poll();peek();

(5)手册中提供的其他常用方法:

addAll(); addFirst();( 相当于add(0 , value) ); addLast(); clear();contains(); element();getFirst();

getLast();indexOf();lastIndexOf();等.....

23.3 Set无序无重复集合

(1)HashSet类

(1)java.util包

(2)HashSet的底层是HashMap(数组+链表的形式,也可以叫散列表或临接连表)

(3)无序:我们使用集合存放元素的顺序,集合内取出来的顺序不一致,集合本身是有自己的算法排布顺序,hash算法,只不过这个顺序咱们自己找不到规律

HashSet<String> set = new HashSet<>();
    set.add("a");
    set.add("b");
    set.add("A");
    set.add("C");
    set.add("c");
    set.add("B");
    set.add("d");
System.out.println(set);//[a,a,b,B,C,c,d]每一次顺序都不一样,无序,Hashset有自己排序算法

(4)利用无参数构造方法和有参数构造方法创建对象

(2)Set集合常用方法

boolean = add( value ) ;addAll( collection c );retainAll( );removeAll( );boolean = remove( Object );注意:HashSet是没有修改方法;

没有循环遍历的方法,但是可以使用增强for进行集合遍历(在JDK1.5版本以后的),在1.5之前是用iterator()进行的

(1)iterator();获取一个迭代器,增强for循环的底层就是迭代器

工人做事没有顺序,贴完商标就往传送带上送,传送带就是个HashSet集合;

(2)hasNext();判断有没有元素

(3)next();取元素

HashSet<String> set = new HashSet<>();
 ? ? ? ?set.add("a");
 ? ? ? ?set.add("b");
 ? ? ? ?set.add("A");
 ? ? ? ?set.add("C");
 ? ? ? ?set.add("c");
 ? ? ? ?set.add("B");
 ? ? ? ?set.add("d");
 ? ? ? ?//获取一个迭代器对象,通过set集合获取
 ? ? ? ?Iterator<String> it = set.iterator();//iterator接口 多态的效果
 ? ? ? ?//判断下一个位置是否有元素
 ? ? ? ?if (it.hasNext()) {
 ? ? ? ? ? ?String value = it.next();
 ? ? ? ? ? ?System.out.println(value);//a
 ? ? ?  }
 ? ? ? ?//将所有元素取出来,用while如果有就取,因为for循环你都不知次数
 ? ? ? ?while (it.hasNext()) {
 ? ? ? ? ? ?String value = it.next();
 ? ? ? ? ? ?System.out.println(value);//a A b B C c d
 ? ? ?  }

(3)HashSet无重复的原则

public class Person {
 ? ?private String name;
?
 ? ?public Person(String name) {
 ? ? ? ?this.name = name;
 ?  }
}
public class TestHashSet {
 ? ?public static void main(String[] args) {
 ? ? ? ?//什么是无重复?名字一样就叫重复嘛?不是的!每次new一个地址都不一样
 ? ? ? ?HashSet<Person> people = new HashSet<>();
 ? ? ? ?people.add(new Person("曹宇希"));
 ? ? ? ?people.add(new Person("曹宇希"));
 ? ? ? ?people.add(new Person("曹宇希"));
 ? ? ? ?people.add(new Person("曹宇希"));
 ? ? ? ?System.out.println(people.size());//4
 ? ? ? ?//到底什么是无重复?String就是1个,可以看出String比较的是名字,Person比较的是地址
 ? ? ? ?HashSet<String> str = new HashSet<>();
 ? ? ? ?str.add(new String("曹宇希"));
 ? ? ? ?str.add(new String("曹宇希"));
 ? ? ? ?str.add(new String("曹宇希"));
 ? ? ? ?str.add(new String("曹宇希"));
 ? ? ? ?System.out.println(str.size());//1
 ?  }
}

(6)首先通过String类型和Person类型存储,可以猜测出?有可能它底层无重复原则是通过利用equals方法进行比较的;如果我们想让Person对象的name一致,认为是同一个对象,那么我们可以重写equals方法

//在Person中重写equals方法,然后在执行TestHashSet中的代码,发现还是没有出现无重复的效果,继续猜测?
//重写equals方法,为了将Person放入set集合中,去掉重复
public boolean equals(Object obj) {
 ? ?if (this == obj) {//如果传进来的对象和当前对象地址一样,就说明重复了
 ? ? ? ?return true;
 ?  }
 ? ?if (obj instanceof Person) {//如果不满足就看看传进来的obj属不属于Person这个类型
 ? ? ? ?//如果属于(属于的化可能是子类)就将obj这个传进来的转为Person类型
 ? ? ? ?Person anotherPerson = (Person)obj;
 ? ? ? ?//this 是一个对象,anotherPerson也是有一个对象,比较对象中的name属性
 ? ? ? ?if (this.name.equals(anotherPerson.name)) {//不是递归,这个equals是String的方法,不是重写的equals方法
 ? ? ? ? ? ?return true;
 ? ? ?  }
 ?  }
 ? ?return false;
}

(7)但是发现我们重写equals方法还是没有产生无重复的效果,证明可能原则不知equals一个方法这么简单!!!

(8)我们想想以前的知识点,往往重写equals方法就会伴随这重写hasheCode方法,所以我们再次猜测?

还有另一个规则同时起着作用:hashCode方法,所以我们还需要重写hashCode()

//在Person中重写hashCode方法,然后在执行TestHashSet中的代码,就成功了无重复效果
//重写hashCode方法
public int hashCode() {
    //两个Person对象的name属性一致,我需要让hashCode返回值一样
    return this.name.hashCode();//这个hashCode是name的
}
package testhashset;

public class Person {
    private String name;
    private int testNum;//记录人是谁,来分辨剩下的一个,是第一次存储的还是最后一次存储的?

    public Person(String name, int testNum) {
        this.name = name;
        this.testNum = testNum;
    }

    public int getTestNum() {
        return this.testNum;
    }

    //重写equals方法,为了将Person放入set集合中,去掉重复
    public boolean equals(Object obj) {
        if (this == obj) {//如果传进来的对象和当前对象地址一样,就说明重复了
            return true;
        }
        if (obj instanceof Person) {//如果不满足就看看传进来的obj属不属于Person
            //先把obj还原回Person类型,因为你传进来的obj有可能不是Person,是Person的子类也可能,需要造型
            Person anotherPerson = (Person)obj;
            //this 是一个对象,anotherPerson也是有一个对象,比较对象中的name属性
            if (this.name.equals(anotherPerson.name)) {//不是递归,这个equals是String的方法,不是重写的equals方法
                return true;
            }
        }
        return false;
    }

    //重写hashCode方法
    public int hashCode() {
        //两个Person对象的name属性一致,我需要让hashCode返回值一样
        return this.name.hashCode();//这个hashCode是name的,是String类型的
    }

}
public class TestHashSet {
    public static void main(String[] args) {
        HashSet<Person> people = new HashSet<>();
        people.add(new Person("曹宇希",1));
        people.add(new Person("曹宇希",2));
        people.add(new Person("曹宇希",3));
        people.add(new Person("曹宇希",4));
        System.out.println(people.size());//1
    }
}

(9)五个Person对象只剩下一个,那么问题来了,这剩下的一个,是第一次存储的那个,还是最后一次存储的那个?

(10)Set集合是发现重复的元素,就拒绝存入,所以存储的是第一个元素

public class TestHashSet {
    public static void main(String[] args) {
        HashSet<Person> people = new HashSet<>();
        people.add(new Person("曹宇希",1));
        people.add(new Person("曹宇希",2));
        people.add(new Person("曹宇希",3));
        people.add(new Person("曹宇希",4));
        System.out.println(people.size());//1
        //利用getTestNum查看一下是第几个元素
        System.out.println(people.iterator().next().getTestNum());//1
    }
}

(11)发现这种方式太麻烦了,还需要用迭代器访问;能不能直接输出一个Person呢?为了输出打印方便重写toString(可以不重写toString,这里重写就是为了方便打印)

package testhashset;

import java.util.HashSet;
import java.util.Iterator;

public class TestHashSet {
    public static void main(String[] args) {
        HashSet<String> set = new HashSet<>();
        set.add("a");
        set.add("b");
        set.add("A");
        set.add("C");
        set.add("c");
        set.add("B");
        set.add("d");
        HashSet<Person> people = new HashSet<>();
        people.add(new Person("曹宇希",1));
        people.add(new Person("曹宇希",2));
        people.add(new Person("曹宇希",3));
        people.add(new Person("曹宇希",4));
        System.out.println("people的集合:" + people.size());//1
        //发现这种方式太麻烦了,还需要用迭代器访问
        System.out.println(people.iterator().next().getTestNum());//1
        //能不能直接输出一个Person呢?为了输出打印方便重写toString,如果不重写就会输出的是Person地址
        System.out.println(people);//[{曹宇希,1}]

    }
}
package testhashset;

public class Person {
    private String name;
    private int testNum;//记录人是谁,来分别剩下的一个,是第一次存储的还是最后一次存储的?

    public Person(String name, int testNum) {
        this.name = name;
        this.testNum = testNum;
    }

    public int getTestNum() {
        return this.testNum;
    }

    //重写equals方法,为了将Person放入set集合中,去掉重复
    public boolean equals(Object obj) {
        if (this == obj) {//如果传进来的对象和当前对象地址一样,就说明重复了
            return true;
        }
        if (obj instanceof Person) {//如果不满足就看看传进来的obj属不属于Person
            //先把obj还原回Person类型,因为你传进来的obj有可能不是Person,是Person的子类也可能,需要造型
            Person anotherPerson = (Person)obj;
            //this 是一个对象,anotherPerson也是有一个对象,比较对象中的name属性
            if (this.name.equals(anotherPerson.name)) {//不是递归,这个equals是String的方法,不是重写的equals方法
                return true;
            }
        }
        return false;
    }

    //重写hashCode方法
    public int hashCode() {
        //两个Person对象的name属性一致,我需要让hashCode返回值一样
        return this.name.hashCode();//这个hashCode是name的
    }

    //重写toString方法,让对象打印输出的时候,不需要在.get方法了,而是直接输出对象的属性
    // ,而不是hashCode码了
    public String toString() {
       /* 这种方法不是很好
        return "{" + this.name + ", " + this.testNum + "}";*/
        StringBuilder builder = new StringBuilder("{");
        builder.append(this.name);
        builder.append(", ");
        builder.append(this.testNum);
        builder.append("}");
        /*return builder.toString(); 下面的也可以*/
        return new String(builder);
    }
}

(4)TreeSet类

1、TreeSet

(1)无序无重复,java.util

(2)TreeSet的底层是TreeMap(二叉树的形状,也是利用Node(left item right) item是数据、left左节点、right右节点)

(3)创建对象------无参数构造方法,带Collection(集合)构造方法

(4)基本常用方法:add(); iterator(); 没有修改....等

(5)TreeSet无重复规则是如何实现的?

1、treeSet集合本身有顺序,它的无序指的是,我们自己存的顺序和它取的顺序不一样,但是它本身是可以按照unicode字典的顺序排布

public class TestTreeSet {
 ? ?public static void main(String[] args) {
 ? ? ? ?TreeSet<String> strings = new TreeSet<>();
 ? ? ? ?strings.add("a");
 ? ? ? ?strings.add("c");
 ? ? ? ?strings.add("A");
 ? ? ? ?strings.add("D");
 ? ? ? ?strings.add("d");
 ? ? ? ?strings.add("b");
 ? ? ? ?strings.add("A");
 ? ? ? ?strings.add("B");
 ? ? ? ?strings.add("C");
 ? ? ? ?strings.add("D");
 ? ? ? ?System.out.println(strings.size());//6  set家族若有相同的对象,拒绝存入
 ? ? ? ?System.out.println(strings);//[A,B,C,D,a,b,c,d]  集合本身有自己的排布顺序
 ?  }
}

2、那我们猜测?有可能它底层的顺序是按照compareTo的形式排序的(按照字母的自然顺序排序Unicode)

3、那我们来看一下是否出现无重复,TreeSet这么写不但不能输出还会抛异常,ClassCastException----->造型异常,实际它底层是TreeMap,是它给造的型

package testhashset;
?
import java.util.TreeSet;
public class TestTreeSet {
 ? ?public static void main(String[] args) {
 ? ? ? ?TreeSet<String> strings = new TreeSet<String>();
 ? ? ? ?strings.add(new String("曹宇希"));
 ? ? ? ?strings.add(new String("曹宇希"));
 ? ? ? ?strings.add(new String("曹宇希"));
 ? ? ? ?strings.add(new String("曹宇希"));
 ? ? ? ?System.out.println(strings.size());//1  set家族若有相同的对象,拒绝存入
?
 ? ? ? ?TreeSet<Person> people = new TreeSet<Person>();
 ? ? ? ?people.add(new Person("曹宇希",1));
 ? ? ? ?people.add(new Person("曹宇希",2));
 ? ? ? ?people.add(new Person("曹宇希",3));
 ? ? ? ?people.add(new Person("曹宇希",4));
 ? ? ? ?//TreeSet这么写不但不能输出还会抛异常
 ? ? ? ?System.out.println(people.size());//1
 ? ? ? ?//ClassCastException----->造型异常,实际它底层是TreeMap,是它给造的型
?
 ?  }
}

4、所以,如果想要把自己写的类型 比如 Person对象存入TreeSet集合里,不能随意的存储

5、TreeSet不像HashSet,因为hashSet它的equals和hashCode方法默认继承Object,谁都可以存

6、所以我们需要让自己写的类先实现Comparable接口,就是你这个对象先是可比较的才能放进TreeSet这个集合里,否则放不进去

7、如果想让Person对象存入TreeSet集合内,必须实现Comparable接口,然后重写这个compareTo方法

重写后,然后咱们再去执行代码就不会异常了,想要的无重复效果就出来了
package testhashset;
?
public class Person implements Comparable<Person>{
 ? ?private String name;
 ? ?private int testNum;//记录人是谁,来分别剩下的一个,是第一次存储的还是最后一次存储的?
 ? ?
 ? ?public Person(String name, int testNum) {
 ? ? ? ?this.name = name;
 ? ? ? ?this.testNum = testNum;
 ?  }
 ? ?public int getTestNum() {
 ? ? ? ?return this.testNum;
 ?  }
 ? ?
 ? ?//如果想让Person对象存入TreeSet集合内,必须实现接口,然后重写这个compareTo方法
 ? ?@Override
 ? ?public int compareTo(Person o) {
 ? ? ? ?//当前对象name和另一个对象name的compareTo结果,compareTo是String类型的,是name的compareTo
 ? ? ? ?return this.name.compareTo(o.name);
 ?  }
?
}
package testhashset;

import java.util.TreeSet;

public class TestTreeSet {
    public static void main(String[] args) {

        TreeSet<Person> people = new TreeSet<Person>();
        people.add(new Person("曹宇希",1));
        people.add(new Person("曹宇希",2));
        people.add(new Person("曹宇希",3));
        people.add(new Person("曹宇希",4));
        System.out.println(people.size());//1
        System.out.println(people);//[{曹宇希,1}]
    }
}

8、我们也可以规定一下排序的顺序,可以才compareTo中进行修改,如:让年龄大的排在后面,就要添加age属性

package testhashset;

import java.util.TreeSet;

public class TestTreeSet {
    public static void main(String[] args) {

        TreeSet<Person> people = new TreeSet<Person>();
        people.add(new Person("曹宇希",1));
        people.add(new Person("曹宇希",2));
        people.add(new Person("曹宇希",3));
        people.add(new Person("曹宇希",4));
        System.out.println(people.size());//1
        System.out.println(people);//[{曹宇希,1}]
    }
}

这里重写toString为了方便查看

package testhashset;

public class Person implements Comparable<Person>{
    private String name;
    private int testNum;//记录人是谁,来分别剩下的一个,是第一次存储的还是最后一次存储的?
    private int age;

    public Person(String name,int age, int testNum) {
        this.name = name;
        this.testNum = testNum;
        this.age = age;
    }

    //重写toString方法,让对象打印输出的时候,不需要在.get方法了,而是直接输出对象的属性
    // ,而不是hashCode码了
    public String toString() {
       /* 这种方法不是很好
        return "{" + this.name + ", " + this.testNum + "}";*/
        StringBuilder builder = new StringBuilder("{");
        builder.append(this.name);
        builder.append(", ");
        builder.append(this.testNum);
        builder.append("}");
        /*return builder.toString(); 下面的也可以*/
        return new String(builder);
    }
    
    public int compareTo(Person o) {
        //当前对象name和另一个对象name的compareTo结果,compareTo是String类型的,是name的compareTo
        int value = this.name.compareTo(o.name);
        if (value != 0) {//如果他们两个的名字不一样,说明不是一个对象
            return value;//输出名字
        }
        //这里自己规定了一个排序:正数排到后面去,正数说明年龄大,当前年龄大就拍到后面去
        return this.age - o.age;
    }
}
package testhashset;
?
import java.util.TreeSet;
?
public class TestTreeSet {
 ? ?public static void main(String[] args) {
?
 ? ? ? ?TreeSet<Person> people = new TreeSet<Person>();
 ? ? ? ?people.add(new Person("曹宇希",12,1));
 ? ? ? ?people.add(new Person("曹宇希",34,2));
 ? ? ? ?people.add(new Person("曹宇希",56,3));
 ? ? ? ?people.add(new Person("曹宇希",23,4));
 ? ? ? ?
 ? ? ? ?System.out.println(people.size());//4
 ? ? ? ?System.out.println(people);//[{曹宇希, 1}, {曹宇希, 4}, {曹宇希, 2}, {曹宇希, 3}]
?
 ?  }
}

(6)Set集合总结

1、HashSet无序无重复集合

2、Set集合主要具体的实现类:HashSet、TreeSet

3、特点:无序无重复

4、无序:添加的顺序和获取的顺序不一致(不是集合本身是否有效),Tree是自然有序(按照字典的索引号,字母的自然顺序Unicode码)

5、无重复:添加的元素不能一致(如果出现重复元素,只存储第一个,后面的就不在存入了)

6、set集合家族的基本使用:增add(E e);删remove(E e);没有修改;查iterator迭代器:hasNext();next();获取有序元素个数size();

1. HashSet无重复特性:

(1)HashSet 无重复原则有两个方法同时起作用 equals()、hashCode();

(2)默认比较的是两个对象的地址,若第二个对象地址与之前的一致,不再存入,如果想要改变比较的规则,可以重写上述两个方法

(3)HashSet(HashMap----->数据存储结构 散列表(数组+双向链表))

2. TreeSet无重复特性:

(1)TreeSet 无重复原则有一个方法起作用 compareTo

(2)上述这个方法不是每一个对象都有的,如想要将某一个对象存入TreeSet合集中,需要让对象所属的类实现接口Comparable,实现接口后将compareTo方法重写,返回值int 负数靠前排布,正数排列靠后

(3)HashSet(TreeMap----->数据存储结构 二叉树)

HashSet无序无重复集合

(1)需要重写equals、hashCode方法才可以实现无重复,

(2)HashSet的无序:我们存入的元素顺序和我们取出来的不一致,它有自己的hash算法排序,但是咱们找不到规律;

(3)底层是个HashMap

TreeSet无序无重复集合

(1)需要重写compareTo方法才能实现无重复,

(2)TreeSet无序:当我们存入元素时,它底层会给进行排序(就是compareTo按照字母unicode码在底层给你排序好),当取出来时就按照它底层排序好的取;

(3)底层是个TreeMap

23.4 Map集合

(1)Map介绍

1、Map----映射----通过某一个key可以直接定位到一个value值,存储方式以键值对存储的(key-value),key无序无重复,value无序可重复

2、key无序:无序还是一样,指的是存入顺序与取得顺序不一致

3、key无重复:指的是元素不能一致

4、Map的基本使用:HashMap、TreeMap、Properties(读取文件中的信息)

(2)HashMap

1、java.util包

2、构造方法创建对象:无参数、参默认容量的、带map参数的构造方法

3、特点:(数组 + 链表)底层散列表形式存储,key无序无重复、value无序可重复(在找寻某一个唯一元素的时候建议使用map,更适合查找唯一元素)

4、基本方法:

(1)增put(key , value);存放一组映射关系(key-value)将指定的值与此映射中的指定键相关联。

①key存储的顺序与取得顺序不同,不同的key可以存储相同的value

②key若有相同的,则将原有的value覆盖,而不是拒绝存入(跟Set集合正好相反)

//创建一个HashMap对象
HashMap<Integer, String> map = new HashMap<Integer, String>();
//将一些key-value的映射关系存入集合
map.put(1,"a");//key不同 value同
map.put(4,"a");
map.put(2,"b");//key同 value不同
map.put(2,"bc");
System.out.println(map);// {1=a, 2=bc, 4=a}

(2)删V = remove(object key);从该地图中删除指定键的映射(如果存在)。remove(object key,object value);

map.remove(1);//把key为1的删除
System.out.println(map);//{2=bc, 4=a}

(3)改可以利用put进行覆盖put( key , value1);put( key , value2);

也提供了修改的方法boolean = replace(key , new Value);只有当目标映射到某个值时,才能替换指定键的条目。

(4)查V = get(key);如果key不存在就返回null,存在就返回对应的value; 返回到指定键所映射的值,或 null如果此映射包含该键的映射。

(5)int = size();获取有序元素

(6)void = clear()将Map集合中元素清空

(7)boolean = containsKey();是否包含建。boolean = containsValue();是否包含值

(8)V = getOrDefault( key , defaultValue);如果Key存在就返回对应的value,如果没有就返回一个默认值(默认值可以自己定义)

map.getOrDefault(10,"1000");//如果没有就返回自己定义的默认值1000

(9)boolean = isEmpty();判断集合是否为空

(10)void = putAll(map);首先满足你填进来的泛型得满足我本身的泛型或者是子集才可以添加

(11)V = putIfAbsent(key ,value) ;如果key不存在才向集合内添加value,如果存在就不添加

map.putIfAbsent(2,"sss");//如果key=2的建没有就添加这个字符串,如果有就不添加

5、遍历map集合?需要有循序和次数才能遍历,遍历比较麻烦

(1)首先获取到所有的key(利用keySet方法,然后返回一个Set父类集合)

(2)遍历key(由于返回的是Set,所以要想遍历Set就要通过迭代器)

(3)通过key获取value(迭代器有hasNext和next方法)

 //创建一个HashMap对象
 ? ? ? ?HashMap<Integer, String> map = new HashMap<>();
 ? ? ? ?//将一些key-value的映射关系存入集合
 ? ? ? ?map.put(1,"a");//key不同 value同
 ? ? ? ?map.put(4,"a");
 ? ? ? ?map.put(2,"b");//key同 value不同
 ? ? ? ?map.put(3,"bc");
        map.put(5,"bc");
 ? ? ? ?//获取map集合的全部key,返回值是Set集合是个父类
 ? ? ? ?Set<Integer> keys = map.keySet();
 ? ? ? ?//Set集合怎么遍历?通过迭代器遍历keys
 ? ? ? ?Iterator<Integer> it = keys.iterator();
 ? ? ? ?while (it.hasNext()) {
 ? ? ? ? ? ?Integer key = it.next();
 ? ? ? ? ? ?String value = map.get(key);
 ? ? ? ? ? ?System.out.println(key + "--->" + value);//1--->a 2--->b 3--->bc 4--->a 5--->bc
 ? ? ?  }

6、HashMap底层的数据结构存储:

总:HashMap的存储是以数组+ 链表的形式存储,首先不同的对象可能产生相同code码,但是不同code码应该对应不同对象,然后这个Code码经过Map自己的hash算法得到一个hash值,然后把key-value包装成一个对象Entry存起来(这个Entry跟Node差不多)(这个Entry对象中存储着key、value、next是下一个对象的地址),所以数组用于存储hash值,当不同的对象产生相同的hash值时就在当前数组位置的后面串一个链,这个链就连接着对象,而这个对象的hash值和数组中的hash值一样

7、两种方式进行遍历Map集合:

//获取map集合的全部key,返回值是Set集合是个父类
Set<Integer> keys = map.keySet();
//Set集合怎么遍历?通过迭代器遍历keys
Iterator<Integer> it = keys.iterator();
while (it.hasNext()) {
 ? ?Integer key = it.next();
 ? ?String value = map.get(key);
 ? ?System.out.println(key + "--->" + value);
}
//获取集合中全部的entry对象
Set<Map.Entry<Integer, String>> entries = map.entrySet();
//Set集合遍历
Iterator<Map.Entry<Integer, String>> its = entries.iterator();
while (its.hasNext()) {
 ? ?Map.Entry<Integer, String> entry = its.next();//Entry中有:key value next(这个暂时不用,我不找下一个人,我想找的是Entry对象中的值)
 ? ?Integer key = entry.getKey();
 ? ?String value = entry.getValue();
 ? ?System.out.println(key + "--->" + value);
}

(3)如何选用集合和数组?

1、想要存储一组元素

(1)数组,如果存储的元素以后长度不变用数组;(比如星期,一周永远是7天,长度固定不变)

(2)集合,如果长度以后不确定用集合

2、如果发现长度以后不确定--->集合

(1)List:list家族有序的,存储有顺序用这个

ArrayList(Vector) 更适合遍历查询

LinkedList 更适合插入删除

Stack 先进后出

(2)Set:Set家族无重复,存储元素希望自动去掉重复元素用这个

Hash 它的性能更高,但是排序乱

Tree 希望存进去的元素自动去重复,同时还能自动排序

(3)Map:map家族k-v,想要查找某一个元素的时候,通过唯一的k快速找寻v用这个

Hash 它的性能更高

Tree 希望存进去的元素key自动排序

(4)登录小流程来体验如何选用集合

1、数组:实现登录认证

//方法:用来登录认证--数组
private String[] userBox = new String[]{"yuxi","xixi","sad"};
private int[] passwordBox = new int[]{123,66,888};
?
//设计方法
public String loingForArray(String name, String password) {
 ? ?for (int i = 0; i < userBox.length; i++) {
 ? ? ? ?if (userBox[i].equals(name)) {
 ? ? ? ? ? ?if (passwordBox[i] == Integer.parseInt(password)){//你传进来的password是String
 ? ? ? ? ? ? ? ?return "登录成功";
 ? ? ? ? ?  }
 ? ? ? ? ? ?break;
 ? ? ?  }
 ?  }
 ? ?return "用户或密码错误";
}

2、ArrayList:实现登录认证

//方法:用来登录认证--ArrayList
//new完之后也没有我们想要的值
private ArrayList<String> userBox = new ArrayList<String>();
//可以用块进行初始化,添加个人信息
{
 ? ?userBox.add("yuxi-123");
 ? ?userBox.add("xx-123");
 ? ?userBox.add("asd-123");
}
public String loginForList(String name, String password) {
 ? ?for (int i = 0; i < userBox.size(); i++) {
 ? ? ? ?//userBox.get(i)得到的是一个字符串
 ? ? ? ?String[] value = userBox.get(i).split("-");//返回值是数组了是一个人的信息 v[0]人名 v[1]密码
 ? ? ? ?if (value[0].equals(name)) {
 ? ? ? ? ? ?if (value[i].equals(password)) {
 ? ? ? ? ? ? ? ?return "登录成功";
 ? ? ? ? ?  }
 ? ? ? ? ? ?break;
 ? ? ?  }
 ?  }
 ? ?return "用户名或密码错误";
}

3、Set:实现登录认证

//方法:用来用户认证--Set
private HashSet<String> userBox = new HashSet<String>();
{
 ? ?userBox.add("yuxi-123");
 ? ?userBox.add("xx-123");
 ? ?userBox.add("asd-123");
}
public String loginForSet(String name,String password) {
 ? ?//Set集合遍历用迭代器Iterator
 ? ?Iterator<String> it = userBox.iterator();
 ? ?while (it.hasNext()) {
 ? ? ? ?String[] value = it.next().split("-");//取出来的是一个人的信息,是个String串
 ? ? ? ?if (value[0].equals(name)) {
 ? ? ? ? ? ?if (value[1].equals(password)) {
 ? ? ? ? ? ? ? ?return "登录成功";
 ? ? ? ? ?  }
 ? ? ? ? ? ?break;
 ? ? ?  }
 ?  }
 ? ?return "用户名或密码错误";
}

4、Map:实现登录认证

//方法:用来登录认证--Map ?  name(唯一) pass
//key是唯一的可以通过人名直接就去找密码
private HashMap<String,Integer> userBox = new HashMap<>();
{
 ? ?userBox.put("yuxi", 123);
 ? ?userBox.put("xx", 345);
 ? ?userBox.put("ss", 123);
}
public String loginForMap(String name, String password) {
 ? ?Integer realPassword = userBox.get(name);//如果key存在返回对应的value,不在就返回null
 ? ?if (realPassword != null) { //证明人名存在
 ? ? ? ?//如果人名存在,那么我们就可以直接取找密码了
 ? ? ? ?if (realPassword.equals(Integer.parseInt(password))) {
 ? ? ? ? ? ?return "登录成功";
 ? ? ?  }
 ?  }
 ? ?return "用户名或密码错误";
}

优化后:

//简化
public String loginForMap(String name, String password) {
 ? ?Integer realPassword = userBox.get(name);//如果key存在返回对应的value,不在就返回null
 ? ?if (realPassword != null && realPassword.equals(Integer.parseInt(password))) { //证明人名存在
 ? ? ? ?return "登录成功";
 ?  }
 ? ?return "用户名或密码错误";
}

我们可以体会到每一个不同集合的特点

(5)TreeMap

1、java.util包

2、构建对象:无参数、带map参数

3、自然有序,按照Unicode码自然有序

4、底层数据结构的存储

(6)设计学生做选择题(考试机)

1、题目做成选择题

(1)以下哪个选项不是Java基本类型?

A、short B、bollean C、String D、char

请您输入认为正确的选项

2、学生考试之前需要先登录认证

3、设计一个学生 考试机 老师关系

4、考试机存储好多题目-----题库10道

5、考试机随机生成试卷的方法5道

6、学生利用生成的试卷开始-----------答案5个选项 A D B C A

7、老师负责批改卷子 最终成绩

8、分析:

(1)考试的题目如何存储?

自己去描述一个类---------一个题目类型,有两个属性----题干和真实的答案

(2)有几个实体类(3个)类的关系?

1.考试机:

相当于一个题库,存储好多Question类型对象(包含关系,一个类当作另一个类的属性存储);

那么题库能增加题进行扩容相当于是个集合,那么集合选用HashSet或ArrayList?假如你出了10个题又来了一个老师还要出10个题,以免防止题出现重复,所以要选用Set无重复集合;

考试机还有有一个功能就是随机抽取题目的功能,定义一个方法-----随机抽题目然后变为试卷,在给学生使用,所以返回值就是个试卷

2.学生:

学生要使用试卷才能做题(依赖关系,一个方法中出来另一个类),方法--参数试卷,返回一个学生最终的选项答案

3.老师:

方法---老师批卷子要拿学生的试卷答案和正确试卷答案进行比对,参数学生最终的选项答案 和 一个真实的试卷,返回学生成绩

(3)具体添加每一个类中的成员描述(涉及到如何选择容器来存储的问题)

(4)具体的执行验证

public class Question {
 ? ?private String title;//题目题干
 ? ?private String answer;//真实答案
?
 ? ?public Question(){}
 ? ?public Question(String title, String answer) {
 ? ? ? ?this.title = title;
 ? ? ? ?this.answer = answer;
 ?  }
 ? ?//学生要看题干
 ? ?public String getTitle() {
 ? ? ? ?return title;
 ?  }
 ? ?//老师要看答案
 ? ?public String getAnswer() {
 ? ? ? ?return answer;
 ?  }
 ? ?//HashSet集合遵循原则;需要重写equals和hashCode方法
 ? ?//将默认比较题目对象的地址规则,改成比较题干一致就是同一道题
 ? ?public boolean equals(Object obj) {
 ? ? ? ?//如果当前这个对象与传进来的对象的地址一致,说明同一个对象,直接返回true
 ? ? ? ?if (this == obj){
 ? ? ? ? ? ?return true;
 ? ? ?  }
 ? ? ? ?//如果传进来的对象属于Question这个类型就比较
 ? ? ? ?if (obj instanceof Question) {
 ? ? ? ? ? ?Question anotherQuestion = (Question)obj;//传进来的对象和Question是一个类型但是不是一个地址,需要造型
 ? ? ? ? ? ?//this.title 按照?截取 与 anotherQuestion.title?截取之前的部分比较
 ? ? ? ? ? ?if (this.title.equals(anotherQuestion.title)) {//比较全部题干
 ? ? ? ? ? ? ? ?return true;
 ? ? ? ? ?  }
 ? ? ?  }
 ? ? ? ?return false;//如果不属于这个类型就false
 ?  }
 ? ?public int hashCode() {
 ? ? ? ?return this.title.hashCode();
 ?  }
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;
?
public class ExamMachine {
 ? ?//属性---题库,要存储好多个Question类型的对象,每一个对象就是一道题目,把题放在集合中,这个题是个对象
 ? ?//Set集合,因为不重复,如果重复就覆盖,如果题库进行扩充,产生重复题目可以自动去掉
 ? ?//我们将题存在HashSet集合,那么它不会自动去掉重复元素,还要重写equals和hashCode方法,才能遵循
 ? ?//所以我们要在Question中重写规则,将默认比较题目对象的地址改为比较题干,题干一致就是同一道题
 ? ?private HashSet<Question> questionBank = new HashSet<Question>();
?
 ? ?//属性---记录学生账号和密码
 ? ?private HashMap<String, String> userBox = new HashMap<String,String>();//登录就不用循环了
 ? ?//利用块来初始化学生账户和密码,学生登录的时候都是学生自己的事
 ?  {
 ? ? ? ?userBox.put("cao","123");
 ? ? ? ?userBox.put("hh","123");
 ? ? ? ?userBox.put("cxixi","123");
 ?  }
?
 ? ?//利用块来初始化hashSet集合内的题目对象
 ?  {
 ? ? ? ?questionBank.add(new Question("如下哪个选项是Java基本数据类型?\n\tA.String\n\tB.Integer\n\tC.boolean\n\tD.Math","C"));
 ? ? ? ?questionBank.add(new Question("如下哪个选项不是Java基本数据类型?\n\tA.String\n\tB.int\n\tC.boolean\n\tD.double","A"));
 ? ? ? ?questionBank.add(new Question("如下哪个选项是Java引用数据类型?\n\tA.String\n\tB.int\n\tC.char\n\tD.char","A"));
 ? ? ? ?questionBank.add(new Question("如下哪个选项不是Java引用数据类型?\n\tA.String\n\tB.int\n\tC.char\n\tD.Double","C"));
 ? ? ? ?questionBank.add(new Question("如下哪个选项是java.util包中的类?\n\tA.String\n\tB.Integer\n\tC.Scanner\n\tD.Math","C"));
 ? ? ? ?questionBank.add(new Question("如下哪个选项不是java.util包中的类?\n\tA.Date\n\tB.Integer\n\tC.Calendar\n\tD.Random","C"));
 ? ? ? ?questionBank.add(new Question("如下选项不是java.util包中的类?\n\tA.Date\n\tB.Integer\n\tC.Calendar\n\tD.Random","C"));
 ? ? ? ?questionBank.add(new Question("如下选项不是java.util包中的类?\n\tA.Date\n\tB.Integer\n\tC.Calendar\n\tD.Random","C"));
 ?  }
?
 ? ?//设计一个方法:随机生成试卷,那么这个试卷有没有参数?参数如果确定每张试卷的题是固定的就不用参数了(要是每张卷子的题不固定就需要参数)
 ? ?//返回值得是个卷子,试卷应该什么样子啊?试卷应该和题库一样,就是比题库的题少,所以试卷应该也是个集合,这个集合里面的泛型应该是Question
 ? ?//那这个试卷的集合用哪个好?大家知道这个试卷最后应该是给学生用,那么就要遍历一个一个答题,那么遍历好用的集合就是ArrayList,所以这个试卷
 ? ?//就是ArrayList<Question>
 ? ?public ArrayList<Question> getPaper() {
 ? ? ? ?System.out.println("考试机正在随机的生成试卷,请耐心等待...");
 ? ? ? ?try{
 ? ? ? ? ? ?Thread.sleep(3000);//睡3秒等3秒
 ? ? ?  } catch (Exception e) {
 ? ? ? ? ? ?e.printStackTrace();
 ? ? ?  }
 ? ? ? ?//首先创建一个试卷用于存放随机抽出来的题,然后每张试卷抽出来的题都不能完全一样,在随机抽取试卷的时候,试卷的题目应该是不重复的
 ? ? ? ?//试卷中的题可以用Set集合存,存完之后再把Set集合变为ArrayList集合
 ? ? ? ?HashSet<Question> paper = new HashSet<Question>();//这些题就能组合成一个试卷,然后在想办法变成ArrayList
?
 ? ? ? ?//存完之后,这个试卷随机抽,抽的题应该是在题库中找,但是你想要去questionBank题库中找题的话,通常是通过索引找,
 ? ? ? ?//,但是这个题库是Set集合,是没有索引的,所以要把题库在这里转化为ArrayList
 ? ? ? ?ArrayList<Question> questionBank = new ArrayList<Question>(this.questionBank);//把当前题库转为有序的
?
 ? ? ? ?//一直随机抽题,抽到这个题够数为止
 ? ? ? ?while (paper.size() != 5) {//够5题就不抽了
 ? ? ? ? ? ?//题库有序了就可以抽题,产生个序号就行了,也就是索引号
 ? ? ? ? ? ?//随机抽题,Random().nextInt()给出一个范围,而这个范围应该是题库的长度
 ? ? ? ? ? ?int index = new Random().nextInt(this.questionBank.size());//[0~10)
 ? ? ? ? ? ?//抽到这个索引号之后,然后我们可以去当前函数的这个题库中找题,就得到一个题目
 ? ? ? ? ? ?Question question = questionBank.get(index);
 ? ? ? ? ? ?//题目有了之后,放到这个当前的试卷中就可以了
 ? ? ? ? ? ?//将随机抽的题放到试卷里
 ? ? ? ? ? ?paper.add(question);
 ? ? ?  }
 ? ? ? ?//paper是HashSet类型的,如果要把这个试卷返回需要为ArrayList类型,那么我们就可以把Set集合构建成ArrayList集合
 ? ? ? ?return new ArrayList<Question>(paper);
 ?  }
?
 ? ?//方法:用于学登录认证,因为学生要在考试机上登录成功才可以进行考试答题
 ? ?public String login(String username, String password) {
 ? ? ? ?//获取Map集合中的密码与输入的密码的进行比较
 ? ? ? ?String realPassword = this.userBox.get(username);//返回对应中的key的value
 ? ? ? ?if (realPassword != null && realPassword.equals(password)) {
 ? ? ? ? ? ?return "登录成功";
 ? ? ?  }
 ? ? ? ?return "用户名或密码错误";
 ?  }
?
}
import java.util.ArrayList;
import java.util.Scanner;

public class TestMain {
    public static void main(String[] args){
        /*
        String question = "1、如下哪个选项不是Java基本数据类型?\n\tA.String\n\tB.int\n\tB.char";
        System.out.println(question);
        String answer = "A";
        */
        /*
            题干 答案 两个字符串,需要存储到一起,变成一个完整的题目,那怎么存储呢?
            ①String[2] {"title", "answer"}
            ②String     "title-answer"
            ③ArrayList<String[]>
            ④HashMap<title, answer>;Map是通过key找value,找到这个value之后key就没有了
            ,get(key)得到value;如:get(1)得到A,key就没有了,所以你得到一个答案但是你不知
            哪个题的
            上面的4种方法都不好,但能存储
            Java是面向对象的编程思想------我能把每一道题当作每一个对象嘛?那么题干和答案就是属性
        */
        /*
        Question question = new Question("1、如下哪个选项不是Java基本数据类型?\n\tA.String\n\tB.int\n\tC.char\n\tD.Double", "A");
        String value = question.getTitle();
        System.out.println(value);
        */

        //先创建考试机,用于获取试卷
        ExamMachine machine = new ExamMachine();//调用构造器是,有一个块默认执行,存入题
        //创建学对象用来考试
        Scanner scanner = new Scanner(System.in);
        System.out.println("欢迎进入考试系统\n请输入人名");
        String username = scanner.nextLine();
        System.out.println("请输入密码");
        String password = scanner.nextLine();
        //然后创建学生对象
        Student student = new Student(username,password);
        //判断输入的是否正确
        String result = machine.login(student.getUsername(), student.getPassword());
        if (result.equals("登录成功")) {
            System.out.println("登录成功\n" + student.getUsername() + "同学开始考试啦!请认真作答");
            //获得试卷后返回变为ArrayList集合
            //考试机随机抽取一套试卷,这个试卷是个集合,集合中每一个元素都是一道题
            ArrayList<Question> paper = machine.getPaper();
            //学生考试,然后返回出学生的选项答案
            String[] answers = student.exam(paper);
            Teacher teacher = new Teacher();
            int score = teacher.checkPaper(paper, answers);
            System.out.println(student.getUsername() + "同学最终成绩:" + score);
        } else {
            System.out.println(result + ";即将退出系统");
        }


    }
}
import java.util.ArrayList;
import java.util.Scanner;

public class Student {
    private String username;
    private String password;

    public Student(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public String getPassword() {
        return password;
    }

    //方法:学生需要考试,学生需要使用试卷,参数一套试卷,返回值学生做答得所有的选项
    //"A" "B" "C" "D"五个选项存哪,用数组好一点,因为你答完题最终得答案是不能改动
    //的,固定的String[]
    public String[] exam(ArrayList<Question> paper) {
        //先用数组存答案,有几个题就给数组开辟多少空间的长度
        String[] answers = new String[paper.size()];
        //用于学生作答
        Scanner scanner = new Scanner(System.in);
        //学生答题一个一个答
        for (int i = 0; i < paper.size(); i++) {
            //我们要得到这个题库中的题给学生作答
            Question question = paper.get(i);
            //但是Question中有题目和正确的答案,我们不能都拿因为正确答案不能给学生看,我们只获取题干给学生看
            System.out.println( i+1 + "、" + question.getTitle());
            System.out.println("请输入你认为正确的选项?");
            answers[i] = scanner.nextLine();//接收学生输入的选项,存入数组中
        }
        return answers;
    }


}
import java.util.ArrayList;

public class Teacher {
    //负责批卷子,参数 学生作答所有选项,真实的试卷,跟学生随机的那套一样,因为要进行批改,返回值返回一个成绩
    public int checkPaper(ArrayList<Question> paper, String[] answers) {
        System.out.println("老师正在批改卷子!");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int score = 0;
        for (int i = 0; i < paper.size(); i++) {
            Question question = paper.get(i);
            //有可能学生输入的选项答案有大写还有小写所以equalsIgnoreCase方法
            if (question.getAnswer().equalsIgnoreCase(answers[i])) {
                score = 100/paper.size();//答对一题就加一题的分  100/paper.size()如果以后扩题用这个好
            }
        }
        return score;
    }
}

二十四、错误与异常

24.1 异常

1、异常/错误:

(1)程序运行过程中,可能会发生一些不被期望的效果,肯定会阻止我们的程序按照指令去执行,这种不被预期出现的效果,肯定需要抛出来告诉我们。

(2)异常和错误是不能随意被抛出的,需要定义或实现一些指定的规则才可以进行抛出。

(3)在Java中有一个定义好的规范Throwable类(可以抛出的),这个类实例化了一个序列化接口

2、Throwable有两个子类:Error错误、Exception异常

3、Error错误:通常是一些物理性的,如:jvm虚拟机本身出现的问题,或者是咱们给出的程序或指令执行不了,或者是内存产生的一些问题,这些问题都是咱们程序指令处理不了的。StackOverflowError栈内存错误、OutOfMemoryError堆内存错误

4、Exception异常:我们主要关注异常,因为错误是我们自己不能解决的。异常通常是一种认为规定的不正常现象,通常是给定的程序指令产生了一些不符合规范的事情。

我们主要研究Exception异常,它下面还有很多子类

(1)异常的分支体系:

①、运行时异常(非检查异常)

1、Error 和 RuntimeException都算作运行时异常

2、javac编译的时候,不会提示和发现的(在程序编写时不要求必须做处理),如果我们愿意可以添加处理手段,比如:try 和 throws

3、要求大家出现这样异常的时候,知道怎么产生及如何修改

(1)InputMisMatchException 输入不匹配 int value = scanner.nextInt();你输入abc就会产生此异常

(2)NumberFormatException 数字格式化 int value = Integer.parseInt("abc");

(3)NegativeArraySizeException 数组长度负数 int[] array = new int[-2];

(4)ArrayIndexOutOfBoundsException 数组索引越界 int[] array = {1,2,3} ;array[10];

(5)NullPointerException 空指针异常 int[] [] array = new int[3] []; array0 = 10;相当于3个小数组,但是这个3个小数组没有长度;

Person p = null;p.getName();都会产生空指针异常

(6)ArithmeticException 数字异常 ;如10/0 整数不允许除以0,Infinity小数除以0会产生无穷

(7)ClassCastException 造型异常; Person p = new Teacher(); Student s = (Student)p;就会产生

(8)StringIndexOutOfBoundsException 字符串越界 String str = "asd";char value = str.charAt(10); 打印value就会产生

(9)IndexOutOfBoundsException 集合越界 只有List家族 ArrayList<String> list = new ArrayList<String>() ;list.add("a"); list.get(20);

(10)IllegalArgumebtException 非法参数异常 ArrayList<String> list = new ArrayList<String>(-1) ;

②、编译时异常(检查异常)

1、除了Error和RuntimeException以外其他的异常

2、javac编译的时候,强制要求我们必须为这样的异常做处理(Try 或 throws),因为这样的异常在程序运行过程中极有可能产生问题的,异常产生后后续的所有执行就停止了

(1)InterruptException;如:try{ Thread.sleep(5000); } catch(Exception e){ }

(2)添加处理异常的手段:

处理异常不是,异常消失了,处理异常指的是,处理掉异常之后,后续的代码不会因为此异常而终止执行

两种手段:

①、try{ }catch(){ }[finally{ }]

1、try不能单独的出现,后面必须添加catch或finally,catch有一组()目的是为了捕获某一种异常,catch可以有多个

 ? ? ? ?
try{
 ? ? ? ? ? ?System.out.println("try开始");
 ? ? ? ? ? ?String str = null;
 ? ? ? ? ? ?str.length();
 ? ? ? ? ? ?str.charAt(10);
 ? ? ? ? ? ?System.out.println("try完毕");
 ? ? ?  } catch (NullPointerException e) {//这里只能捕获空指针,其他异常不能捕获
 ? ? ? ? ? ?System.out.println("捕获到了空指针异常");
 ? ? ?  } catch (StringIndexOutOfBoundsException e) {
 ? ? ? ? ? ?System.out.println("捕获到了字符串越界");
 ? ? ?  } finally {
 ? ? ? ? ? ?System.out.println("我是finally必须执行");
 ? ? ?  }

捕获的异常之间没有任何的继承关系,捕获的异常需要从小到大进行捕获

2、finally不是必须存在的,若存在finally结构,则必须执行

//就是用try去尝试一下String str = null;这行代码是否出现异常,如果没异常就直接执行,有异常就捕获了
try{
 ? ?System.out.println("try开始");
 ? ?String str = null;
 ? ?str.length();
 ? ?str.charAt(10);
 ? ?System.out.println("try完毕");
} catch (NullPointerException e) {//这里只能捕获空指针,其他异常不能捕获
 ? ?System.out.println("捕获到了空指针异常");
} catch (Exception e) {
 ? ?System.out.println("捕获到了其他的异常");
} finally {
 ? ?System.out.println("我是finally必须执行");
}
?

3、如果处理异常放在方法内部,可能还有一些问题:

(1)如果在方法内部含有返回值,不管返回值return关键字在哪里,finally一定会执行完毕,返回值的具体结果,看情况如果测试的代码没有问题就执行最后的retun,如果有问题就返回try中的

public class Test {
 ? ?public static void main(String[] args) {
?
 ? ? ? ?Test test = new Test();
        test.testException();
 ?  }
 ? ?public String testException() {
 ?     ?//无论return放在哪,都会执行finally,try catch finally是个整体,必须把这个整体执行完
 ? ?    try{
 ? ? ? ?    System.out.println("try开始");
 ? ? ? ?    String str = null;
 ? ? ?     ?System.out.println("try完毕");
 ?     ? ?  return "try中的返回值";
 ?      }catch (Exception e) {
 ? ? ? ?    //e.printStackTrace();//打印输出异常的名字
 ? ? ? ?    System.out.println("捕获到了异常");
 ?      } finally {
 ? ? ?     ?System.out.println("我是finally必须执行");
 ?      }
 ?     ?return "最终的返回值";
    }
}
?
最后输出:
 ? ?try开始 
 ? ?try完毕
 ? ?finally执行完
 ? ?try中的返回值

final 和 finally 和 finalize的区别:完全没关系

1、final:

(1)特征修饰符,修饰变量、属性、方法、类;

(2)修饰变量、基本类型的时候,值不能改变,修饰引用类型的时候,地址不能改变(如果变量没有初值,给一次机会赋值);

(3)修饰属性,特点与修饰变量类型(要求必须给属性赋初始值,否则编译报错);

(4)修饰方法,不能被子类重写

(5)修饰类,不能被其他的子类继承

2、finnally:

(1)处理异常手段的一部分try{ }catch(){ }后面的一部分,这个部分可有可无,如果有只能含有一份,且必须执行

3、finalize:

(1)Object类中的一个protected方法,对象没有任何引用指向的时候---会被GC回收,当对象回收的时候,默认调用finalize方法,若想看回收效果,需要重写此方法

②、throws抛出

1、异常只能在方法上抛出,属性是不能处理异常的,方法可以抛出不止一个异常,抛出的异常与多个catch类似,要么没关系,要么先抛出小异常

(3)自定义异常

1、自己描述一个异常的类

2、让我们自己的类继承,如果继承RuntimeException---》运行时异常(不需要必须添加处理手段)

如果继承时Exception----》编译时异常(必须添加处理手段)

3、需要创建一个当前自定义异常类的对象,通过throws关键字,主动产生异常

4、当我们设计描述的方法(事情),发现在之前没有相关的异常能描述我的问题,这个时候才会利用自定义异常来描述

public class Test {
 ? ?public static void main(String[] args) {
?
 ? ? ? ?Test test1 = new Test();
 ? ? ? ?try {
 ? ? ? ? ? ?test1.testMyException();
 ? ? ?  } catch (Exception e) {
 ? ? ? ? ? ?e.printStackTrace();
 ? ? ?  }
?
 ?  }
 ? ?//设计方法:测试自定义异常的使用
 ? ?public void testMyException() {
 ? ? ? ?System.out.println("测试自定义异常的方法执行了");
 ? ? ? ?if (3 > 2) { //若满足某个条件
 ? ? ? ? ? throw new MyException("说明一下异常的具体问题");
 ? ? ?  }
 ?  }
}
package testthrowable;
?
public class MyException extends RuntimeException{
 ? ?public MyException(String msg) {
 ? ? ? ?super(msg);
 ?  }
}

文章来源:https://blog.csdn.net/weixin_55415300/article/details/134863658
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。