Day15
一、枚举类型
枚举类型的引入
概念:枚举(enum)全称为enumeration,是JDK1.5中引入的新特性。
语法:
public enum Season{ //注意:枚举类的第一行必须声明对象 //spring;//底层实现 - public static final Season spring = new Season(); //spring();//底层实现 - public static final Season spring = new Season(); spring("春天","春雨绵绵"),//底层实现 - public static final Season spring = new Season("春天","春雨绵绵"); winter("冬天","白雪皑皑");
枚举的应用场景:
该类有固定的几个对象,就使用枚举组织起来 — 枚举是用来组织常量的。本质:枚举看上去像是一种新的数据类型,但本质上枚举是一种受限制的类,并且具有自己的办法。创建自己的enum类时,这个类继承自java.lang.Enum。
public abstract class Enum<E extends Enum <E>> implements Comparable<E>, Serializable{ ... }
特点:
1.枚举就是一个受限制的类(该类只能有固定的几个对象,不能让外界创建对象)
2.枚举不能有继承关系(显示继承)
3.自定义枚举类隐式继承Enum类 – public class Season extends Enum{}
4.枚举类的第一行必须声明对象
优势:
1.增强代码可读性
2.枚举型可直接与数据库交互
3.switch语句优势
4.将常量组织起来统一管理
5.取出equals两者判断,由于常量值地址唯一,可以直接用“==”进行对比
6.编译优势,枚举类编译时,没有把常量值编译到代码中,即使常量值发生改变,也不会影响引用常量的类。
枚举的switch优势
自己的代码:
package com.qf.enum03; public enum Season{ spring("春天","春雨绵绵"), summer("夏天","烈日炎炎"), autumn("秋天","硕果累累"), winter("冬天","白雪皑皑"); private String name; private String info; private Season() { } private Season(String name, String info) { this.name = name; this.info = info; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } @Override public String toString() { return name + " -- " + info; } }
switch类型处理枚举类型的底层源码:
//使用final修饰自定义枚举类很好的说明了自定义枚举类是不能被继承的 public final class Season extends Enum{ //该枚举类的对象 public static final Season spring; public static final Season summer; public static final Season autumn; public static final Season winter; //枚举类的属性 private String name; private String info; private static final Season[] ENUM$VALUES;//该枚举类的对象数组 static { // (枚举名,枚举编号,属性name,属性info) spring = new Season("spring", 0, "春天", "春雨绵绵"); summer = new Season("summer", 1, "夏天", "烈日炎炎"); autumn = new Season("autumn", 2, "秋天", "硕果累累"); winter = new Season("winter", 3, "冬天", "白雪皑皑"); ENUM$VALUES = (new Season[] {spring, summer, autumn, winter}); } //private Season(){} private Season(String s, int i){ super(s, i); } //private Season(String s, int i, String name, String info){ private Season(String s, int i, String name, String info){ super(s, i); this.name = name; this.info = info; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getInfo() { return info; } public void setInfo(String info) { this.info = info; } public String toString() { return (new StringBuilder(String.valueOf(name))).append(" -- ").append(info).toString(); } public static Season[] values(){ Season[] aseason = ENUM$VALUES;//[spring,summer,autumn,winter] int i = aseason.length;//4 Season[] aseason1 = new Season[i];//new Season[4]; //将aseason数组从下标为0的位置开始的元素复制到aseason1数组从下标为0的位置开始,赋值4个元素 System.arraycopy(aseason, 0, aseason1 , 0, i); //[spring,summer,autumn,winter] return aseason1; } //通过字符串获取枚举对象 public static Season valueOf(String s) { //利用Enum类的静态方法去实现 -- 反射 return (Season)Enum.valueOf(com/qf/enum03/Season, s); } }
从创建一个枚举类的源码中可以看到:
1.会创造一个ENUM$VALUES的数组,用于获取该枚举类的对象。
2.构造方法中会默认添加两个参数,分别是String s,int i,代表枚举名和枚举编号。
3.会创造一个values()方法,该方法会将ENUM$VALUES数组中的对象复制一遍到一个新创建的对象数组,目的是为了防止在外部改变原有的枚举对象。
4.创造一个valueOf方法,利用父类Enum的valueOf静态方法实现反射。
自己的主方法代码:
package com.qf.enum03; public class Test01 { /** * 知识点:枚举的switch优势 */ public static void main(String[] args) { switch (Season.spring) { case spring: System.out.println("aaa"); break; case summer: System.out.println("bbb"); break; case autumn: System.out.println("ccc"); break; case winter: System.out.println("ddd"); break; } } }
底层源码:
public class Test01{ //[1,2,3,4] private static int $SWITCH_TABLE$com$qf$enum03$Season[]; public static void main(String args[]){ //$SWITCH_TABLE$com$qf$enum03$Season() --> [1,2,3,4] -> ai[0] -> 1 switch ($SWITCH_TABLE$com$qf$enum03$Season()[Season.spring.ordinal()]){ case 1://$SWITCH_TABLE$com$qf$enum03$Season()[Season.spring.ordinal()] -> 1 System.out.println("aaa"); break; case 2://$SWITCH_TABLE$com$qf$enum03$Season()[Season.summer.ordinal()] -> 2 System.out.println("bbb"); break; case 3://$SWITCH_TABLE$com$qf$enum03$Season()[Season.autumn.ordinal()] -> 3 System.out.println("ccc"); break; case 4://$SWITCH_TABLE$com$qf$enum03$Season()[Season.winter.ordinal()] -> 4 System.out.println("ddd"); break; } } static int[] $SWITCH_TABLE$com$qf$enum03$Season(){ //int[] ai = new int[4]; -- [0,0,0,0] int ai[] = new int[Season.values().length]; try{ //ai[0] = 1; -- ai-> [1,0,0,0] ai[Season.spring.ordinal()] = 1; }catch (NoSuchFieldError ) { } try{ //ai[1] = 2; -- ai-> [1,2,0,0] ai[Season.summer.ordinal()] = 2; }catch (NoSuchFieldError ) { } try{ //ai[2] = 3; -- ai-> [1,2,3,0] ai[Season.autumn.ordinal()] = 3; }catch (NoSuchFieldError ) { } try{ //ai[3] = 4; -- ai-> [1,2,3,4] ai[Season.winter.ordinal()] = 4; }catch (NoSuchFieldError ) { } return $SWITCH_TABLE$com$qf$enum03$Season = ai; } }
1.会声明一个int数组变量 S W I T C H T A B L E SWITCH_TABLE SWITCHTABLEcom q f qf qfenum03$Season,这个数组变量用来存放枚举对象中元素的位置。
2.创建一个 S W I T C H T A B L E SWITCH_TABLE SWITCHTABLEcom q f qf qfenum03$Season()的静态方法,用于获得这个int类型的数组变量。
3.再进行switch语句,此时每个case判断的不是元素,而是用静态方法获取数组变量,再用枚举类型的ordinal()方法获取该元素在对象数组中的次序,即最终判断的是int类型的值,而不是String类型的值。
扩展知识点:switch如何判断String
判断的实际是字符串的哈希地址。
/** * 研究String的hash码如何计算出来: * * "abc" --> ['a','b','c'] --> [97,98,99] * * public int hashCode() { int h = hash;//h - 0 if (h == 0 && value.length > 0) { char val[] = value; //h - 96354 for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; } System.out.println("abc".hashCode());//96354 注意:两个不一样的字符串,hash值有可能相同 System.out.println("Aa".hashCode()); //2112 System.out.println("BB".hashCode()); //2112 */
写的代码:
switch ("Aa") {//2112 case "Aa"://2112 System.out.println("Aa"); break; case "BB"://2112 System.out.println("BB"); break; case "CC"://2144 System.out.println("CC"); break; }
底层源码:
String s; switch ((s = "Aa").hashCode())//2112 { default: break; case 2112: if (!s.equals("Aa")){ if (s.equals("BB")) System.out.println("BB"); } else{ System.out.println("Aa"); } break; case 2144: if (s.equals("CC")) System.out.println("CC"); break; }
由此,为应对不同的字符串有相同的哈希码情况,switch判断String类型是依据hash值 + equals()。
枚举的常用方法
//获取该枚举类所有的对象 Season[] values = Season.values(); //通过字符串获取到枚举类的对象 Season season1 = Season.valueOf("spring"); //或 Season season2 = Enum.valueOf(Season.class, "spring");
枚举案例 之 状态机
//信号灯 public enum Signal { RED, YELLOW, GREEN; }
public static void main(String[] args) { Scanner scan = new Scanner(System.in); System.out.println("请选择信号灯:RED, YELLOW, GREEN"); String str = scan.next(); Signal signal = Signal.valueOf(str); String trafficInstruct = getTrafficInstruct(signal); System.out.println(trafficInstruct); scan.close(); } public static String getTrafficInstruct(Signal signal) { String trafficInstruct = "信号灯故障"; switch (signal) { case RED: trafficInstruct = "红灯停"; break; case YELLOW: trafficInstruct = "黄灯请注意"; break; case GREEN: trafficInstruct = "绿灯行"; break; } return trafficInstruct; }
注意:
1.枚举类虽然不能创建对象,但是可以用.valueOf(str)方法把获取的字符串转换为对应的对象。
2.创造了一个getTrafficInstruct方法用来判断获取的枚举类并返回一个字符串来表示状态。
枚举案例 之 错误码
public enum AddCode { ERR01(-1,"添加失败 - 学生信息不合法"), ERR02(-2,"添加失败 - 没有该学生"), OK(1,"添加成功"); private int code; private String message; private AddCode(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Override public String toString() { return code + " -- " + message; } }
public class Test01 { /** * 知识点:枚举案例 之 错误码 */ public static void main(String[] args) { System.out.println(AddCode.ERR01); System.out.println(AddCode.ERR02); System.out.println(AddCode.OK); } }
枚举案例 之 组织枚举
应用场景:可以将一个一个的错误码使用类或接口组织在一起
注意:
使用类组织枚举,枚举默认使用static修饰
使用接口组织枚举,枚举默认使用public static修饰
经验:使用接口组织众多枚举public interface Code { enum AddCode { ERR01(-1,"添加失败 - 学生信息不合法"), ERR02(-2,"添加失败 - 有重复学生"), OK(1,"添加成功"); private int code; private String message; private AddCode(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Override public String toString() { return code + " -- " + message; } } enum RemoveCode { ERR01(-1,"删除失败 - 学生信息不合法"), ERR02(-2,"删除失败 - 没有该学生"), OK(1,"删除成功"); private int code; private String message; private RemoveCode(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Override public String toString() { return code + " -- " + message; } } enum UpdateCode { ERR01(-1,"修改失败 - 学生信息不合法"), ERR02(-2,"修改失败 - 没有该学生"), ERR03(-3,"修改失败 - 修改数据不合法"), ERR04(-4,"修改失败 - 目标班级上有学生"), ERR05(-5,"修改失败 - 目标学号上有学生"), OK(1,"修改成功"); private int code; private String message; private UpdateCode(int code, String message) { this.code = code; this.message = message; } public int getCode() { return code; } public void setCode(int code) { this.code = code; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } @Override public String toString() { return code + " -- " + message; } } }
public static void main(String[] args) { System.out.println(Code.AddCode.ERR01); }
注意:
1.组织枚举本质上就是创建一个接口或者类,然后在这个接口或类里面创建几个内部枚举类,分别表示不同情况下的错误码。
2.通常情况下使用接口,因为接口组织枚举在其他包内可以调用(public static),而类不行(static)。
枚举案例 之 策略枚举
需求:计算工资
按照员工类别:讲师、人力
讲师:基本工资 + 绩效 + 课时费*课时
人力:基本工资 + 绩效按照部门类别:Java(讲师)、Python(讲师)、HTML(讲师)、总经办(人力)、财务(人力)
//获取工资的枚举 public enum Salary { //按照部门创建工资对象 java(StaffType.teacher),python(StaffType.teacher),HTML(StaffType.teacher), GeneralManagerOffice(StaffType.humanResources),finance(StaffType.humanResources); private StaffType staffType; private Salary(StaffType staffType) { this.staffType = staffType; } public double getSalary(double basicSalary, double performance, double classFees, int classHours){ return staffType.calculateSalary(basicSalary, performance, classFees, classHours); } //员工类别的枚举 //public class StaffType{} enum StaffType{ //public class Salary$StaffType$1 extends StaffType{} //StaffType teacher = new Salary$StaffType$1(); //teacher是匿名内部类的对象 teacher { @Override public double calculateSalary(double basicSalary, double performance, double classFees, int classHours) { BigDecimal big1 = new BigDecimal(String.valueOf(basicSalary)); BigDecimal big2 = new BigDecimal(String.valueOf(performance)); BigDecimal big3 = new BigDecimal(String.valueOf(classFees)); BigDecimal big4 = new BigDecimal(String.valueOf(classHours)); double salary = big3.multiply(big4).add(big1).add(big2).doubleValue(); return salary; } }, //public class Salary$StaffType$2 extends StaffType{} //StaffType humanResources = new Salary$StaffType$2(); //humanResources是匿名内部类的对象 humanResources { @Override public double calculateSalary(double basicSalary, double performance, double classFees, int classHours) { BigDecimal big1 = new BigDecimal(String.valueOf(basicSalary)); BigDecimal big2 = new BigDecimal(String.valueOf(performance)); double salary = big1.add(big2).doubleValue(); return salary; } } ; public abstract double calculateSalary(double basicSalary, double performance, double classFees,int classHours); } }
public static void main(String[] args) { double salary1 = Salary.java.getSalary(1800, 200, 88, 20); System.out.println(salary1); double salary2 = Salary.GeneralManagerOffice.getSalary(50000, 130000, 0, 0); System.out.println(salary2); }
解读:
1.每个部门的枚举对象都有一个对应的属性,这个属性决定了这个对象属于哪一类(相当于通过制定另外一个枚举类来进行分支),因此还创建了一个StaffType的枚举类。
2.由于要通过指定StaffType类来作为部门类的属性,所以要创建一个有参构造,该有参构造中用的是StaffType类的对象,但是在定义枚举常量(本质上是定义匿名内部类创建的对象)时用StaffType.teacher体现了多态的思想(StaffType是创建teacher时创建的匿名内部类的父类)。
3.创造了一个getSalary方法,该方法调用了StaffType类的静态方法用于计算工资。
4.在StaffType类中,通过两个枚举,分别重写了StaffType类的静态方法用于计算不同类别的工资。
5.编写顺序:构建Salary类->构建StaffType类->在StaffType类中创建抽象方法->构建StaffType枚举常量并实现抽象方法->构建Salary枚举常量(用有参构造)->定义方法(利用StaffType类的方法)
二、初识集合
集合
含义
- 集合是Java API所提供的一系列类,可以用于动态存放多个对象 (集合只能存对象)
- 集合与数组的不同在于,集合是大小可变的序列,而且元素类型可以不受限定,只要是引用类型。(集合中不能放基本数据类型,但可以放基本数据类型的包装类)
- 集合类全部支持泛型,是一种数据安全的用法。
集合与数组的不同
数组:一旦初始化后长度不可变,元素类型受限定(String类型的数组只能装String的数据),数组可以存储基本数据类型
集合:长度可变的序列,元素类型不受限定(一个集合可以存储多个数据类型的元素),集合只能存储引用数据类型
Collection家族
List接口
特点:有序且可重复(因为List接口中添加了许多针对下标操作的方法)
实现类:
- ArrayList
- LinkedList
- Vector
- Stack
Set接口
特点:无序且不可重复
实现类:
- HashSet
- LinkedHashSet
- TreeSet
Map家族
实现类:
- HashMap
- LinkedHashMap
- Hashtable
- ConcurrentHashMap
- TreeMap
- Properties
泛型
含义:数据安全的做法
泛型限定:
?表示什么类型都可以
? extends A 表示元素必须是A类或A的子类
? super A 表示元素必须是A类或A的父类
迭代器
含义:遍历集合中的数据
分类:Iterator 和 ListIterator
Iterator 和 ListIterator 区别
Iterator :Collection接口下所有的实现类都可以获取的迭代器,可以在遍历时删除元素
ListIterator :List接口下所有的实现类可以获取的迭代器,可以在遍历时删除、替换、添加元素,也可以指定下标开始遍历,还可以倒叙遍历
比较器接口
作用:排序时使用
分类:
内置比较器:Comparable - compareTo()
外置比较器:Comparator - compare()
使用场景:
内置比较器:对象要想存入TreeSet、TreeMap中,对象所属的类必须要实现内置比较器
外置比较器:当内置比较的规则不满足现在的需求,但又不能改动内置比较器规则时
优先级别:外置比较器 > 内置比较器
注意
- Collection 与 Map的区别
Collection 存单个值,可以获取迭代器进行遍历
Map存两个值(Key-Value),不可以获取迭代器,不能遍历(Map可以间接遍历)
- 理解Set为什么是无序
无序:存入顺序和取出顺序不一致,无序不等于随机
- ArrayList 与 LinkedList的区别
使用上的区别:
LinkedList添加了
队列模式-先进先出(removeFirst())
栈模式-先进后出(removeLast())
效率上的区别:
ArrayList底层数据结构是一维数组
LinkedList底层数据结构是双向链表
添加 - 不扩容的情况:ArrayList快
添加 - 扩容的情况:LinkedList快
删除:LinkedList快
查询:ArrayList快
修改:ArrayList快
注意:工作中常用ArrayList,因为很多需求都需要使用查询功能,ArrayList查询更快
- 各种集合的应用场景
ArrayList:存数据,线程不安全
LinkedList:队列模式、栈模式,线程不安全
Vector:弃用,线程安全
Stack:弃用,线程安全
HashSet:去重+无序,线程不安全
LinkedHashSet:去重+有序,线程不安全
TreeSet:排序,线程不安全
HashMap:存key+value,key去重,无序,线程不安全
LinkedHashMap:存key+value,key去重,有序,线程不安全
Hashtable:弃用,存key+value,key去重,无序,线程安全,方法加锁-效率低
ConcurrentHashMap:存key+value,key去重,无序,线程安全,局部加锁、CAS-效率高
TreeMap:存key+value,针对于Key排序
Properties:配置文件
- 。。。
ArrayList
使用
ArrayList list = new ArrayList<>();
list.add(“abc”) - 添加数据
list.add(1,“xxx”) - 在指定下标上添加数据
list.addAll(newList); - 将指定集合添加到集合的末尾
list.remove(1); - 删除指定下标的元素
list.remove(“abc”) - 删除指定元素
list.removeAll(newList); - 将list中有newList的元素全部删除(删除交集)
list.retainAll(newList); - 将list中有newList的元素保留,删除其他元素(保留交集)
list.set(0, “小康”); - 在指定位置重置数据
String str = list.get(1); - 获取指定下标上的元素
int size = list.size(); - 获取元素的个数
list.clear(); - 清空集合里所有的元素
list.contains(“abc”) - 判断集合里是否包含指定元素,返回的是一个布尔值
list.containsAll(newList); - 判断集合里是否包含指定集合,返回的是一个布尔值
list.indexOf(“abc”); - 获取元素在集合中第一次出现的下标
list.lastIndexOf(“abc”) ;- 获取元素在集合中最后一次出现的下标
list.isEmpty(); - 判断集合里有没有元素,返回的是一个布尔值
List subList = list.subList(1,4) - 从开始(包含)下标处截取到结束(排他)下标处,返回新的集合
Object[] array1 = subList.toArray();-将集合转换为数组
String[] array2 = subList.toArray();- 将集合转换为指定类型的数组
遍历集合 – for循环
for(int i=0;i<list.size();i++){ System.out.println(list.get(i)); }
遍历集合–foreach
for(String element:list){ System.out.println(element); }
遍历集合–Iterator
Iterator<String> it = list.iterator() while(it.hasNext()){ String next = it.next(); System.out.println(next); }
遍历集合 – ListIterator
ListIterator<String> listIterator = list.listIterator() while(listIterator.hasNext()){ String next = listIterator.next(); System.out.println(next); }