集合、泛形、枚举、注解、反射
一.集合
1. 集合与数组之间的区别
- 数组的长度是固定的,集合的长度是可变的
- 数组中存储的是同一类型的元素,集合中村粗的数据可以是不同类型的
- 数组中可以存放基本类型数据或者对象,集合中只能存放对象
- 数组是由JVM中现有的类型 +[] 组合而成的,除了一个length属性,还有从Object中继承过来的方法之外,数组对象就调用不到其它属性和方法了
- 集合是由Java 中的java.util 包里面所提供的接口和实现类组成的,这里面定义并实现了很多方法,可以使用集合对象直接调用这些方法从而操作集合存放的数据
2.集合的框架体系
public class Collection_ {
public static void main(String[] args) {
//集合主要分为两种 (单列集合和双列集合)
//Collection 接口有两个重要的子接口 List 和 Set 他们实现的子类都是单列集合
//举例如下:
ArrayList arrayList = new ArrayList();
arrayList.add("hpj");
arrayList.add("hpjj");
//Map 接口实现的子类 是双列集合 都是 键值对组合 key 和 value
HashMap hashMap = new HashMap();
hashMap.put("1","h");// "1" 是键 "h"是值
hashMap.put("2","p");
hashMap.put("3","j");
hashMap.put("4","hpj");
}
}
3.Collection接口和常用方法
collection
实现子类可以存放多个元素,所有的对象或者是Object的子类都可以存放其中- 有些
collection
的实现类,可以存放重复的元素,有些不可以 Collection
的实现类,有些事有序的比如List
存放的顺序和取出的顺序是一致的,有些不是有序的比如Set
接口实现的子类存放的顺序和取出的顺序并不是完全一样的Collection
接口没有直接的实现子类,是通过它的子接口Set
和List
来实现的
- 常用方法 举例如下
public class Collection_FangFa { public static void main(String[] args) { ArrayList arrayList = new ArrayList(); ArrayList list = arrayList; // add 添加单个元素 list.add("昨天"); list.add("今天"); list.add("明天"); System.out.println("List :"+list); // remove 删除指定元素 list.remove(0);//删除第一个元素 list.remove("今天");//指定删除某个元素 System.out.println("list.remove后:"+list); //contains 查找元素是否存在 if(list.contains("明天!")){ System.out.println("明天!存在"); }else{ System.out.println("不存在"); } //size 获取元素个数 System.out.println(list.size()); //isEmpty 判断是否为空 list.remove(0); if(list.isEmpty()){ list.add("昨天"); list.add("今天"); list.add("明天"); System.out.println(list); } //clear 清空 list.clear(); System.out.println("list clear后:"+list); //addAll 添加多个元素 ArrayList list1 = new ArrayList(); list1.add("前天"); list1.add("大前天"); list.addAll(list1); System.out.println("list: "+list); //containsAll 查找多个元素是否存在 System.out.println("list1中的元素在list中是否存在:"+list.containsAll(list1)); //removeAll 删除多个元素 list.removeAll(list1); System.out.println(list); } } --------------------------------------------------------------------------------------- 输出结果如下: List :[昨天, 今天, 明天] list.remove后:[明天] 不存在 1 [昨天, 今天, 明天] list clear后:[] list: [前天, 大前天] list1中的元素在list中是否存在:true []
4. Collection接口遍历元素方式-使用Iterator(迭代器)
- Iterator对象称为迭代器,主要用于遍历Collection集合中的元素
- 所有实现了 Collection接口的集合类都有一个iterator()方法,用以返回一个实现了Iterator接口的对象,既可以返回一个迭代器
- Iterator仅用于遍历集合,Iterator本身并不存放对象
- 迭代器的执行原理:
Iterator iterator = list.iterator();
//得到一个集合的迭代器hasNext()
判断是否还有下一个元素-
while(iterator.hasNext()){ //next() 作用:下移 将下移以后集合位置上的元素返回 System.out.println(iterator.next()); }
- 在调用
iterator.next()
方法之前必须调用iterator.hasNext()
进行检测。若不调用,且下一条无效的话,直接调用iterator.next
会抛出NoSuchElementExeption异常。
5.Collection接口遍历元素方式-使用增强for循环
- 增强for循环,可以代替
iterator
迭代器,特点:增强for循环就是简化版的iterator
,本质是一样的。只能用于遍历集合或者数组。 - 基本语法如下:
for(元素类型 元素名 :集合名或者数组名字){ 访问元素; }
6.List接口
- List集合类中元素有序(既添加顺序和取出顺序一致)且可重复
- List集合中的每个元素都有其对应的顺序索引
- List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
- List接口的实现类有:
ArrayList
:java.util.ArrayList
是最常用的一种List
类型集合,ArrayList
类中使用数组来实现数据的存储,所以它的特点是就是: 增删慢,查找快。 在日常的开发中,查询数据也是用的最多的功能,所以ArrayList
是最常用的集合。但是,如果项目中对性能要求较高,并且在集合中大量的数据做增删操作,那么ArrayList
就不太适 合了。LinkedList
:java.util.LinkedList
存储数据采用的数据结构是链表,所以它的特点是:增删快,查找慢。它的特点刚好和ArrayList
相反,所以在代码中,需要对集合中的元素做 大量 的增删操作的时候,可以选择使用LinkedList
Vector
内部也是采用了数组来存储数据,但是Vector
中的方法大多数都是线程安全的方法,所以在 多线并发访问的环境中,可以使用Vector
来保证集合中元据操作的安全。查看Vector
中方法的定义,可以看到多大数方法都使用了synchronized
关键字,来给当前方法加 锁。
- 如何选择
ArrayList
和LinkedList
- 如果我们改查的操作多,就选择
ArrayList
- 如果我们增删的操作多,就选择
LinkedList
- 一般来说,在程序中,百分之八十到九十都是查询,因此大部分情况都会选择
ArrayList
- 在一个项目中,根据业务灵活选择。
- 如果我们改查的操作多,就选择
7.set接口
- 无序(添加和取出顺序不一致,没有索引)
- 不允许重复元素所以最多包含一个 null
set
接口的常用方法和Collection
接口一样- 遍历方式也同
Collection
一样但是不能使用索引的方式来获取 - 代码实操如下:
import java.util.HashSet; import java.util.Iterator; import java.util.Set; public class Set_ { public static void main(String[] args) { Set set = new HashSet(); set.add("hpj"); set.add("hpjj"); set.add("xiaohou"); set.add("hpj");// 由输出结果可以看出添加重复数据并没有用 set.add("h"); set.add(null);//只可以添加一个 null值 set.add(null); System.out.println("set="+set); //输出结果set=[null, hpj, hpjj, h, xiaohou] //从输出结果看出 排练方式是无序的 添加顺序和取出数据的顺序不一致 for (int i = 0; i<10;i++){ System.out.println("set"+i+"="+set); //循环输出十次后 输出十次的结果同上,也就是说虽然取出的顺序和添加的顺序不一致但他是固定的, // 不会因为每取出一次顺序就变一次 } //遍历方式 //1.迭代器遍历 System.out.println("=====使用迭代器遍历====="); Iterator iterator = set.iterator(); while (iterator.hasNext()) { Object next = iterator.next(); System.out.println("迭代器遍历后:"+next); } //2.增强for循环 System.out.println("=====使用增强for循环====="); for (Object o :set) { System.out.println("增强for循环: "+ o); } } } ==================================================================== 输出结果如下: set=[null, hpj, hpjj, h, xiaohou] set0=[null, hpj, hpjj, h, xiaohou] set1=[null, hpj, hpjj, h, xiaohou] set2=[null, hpj, hpjj, h, xiaohou] set3=[null, hpj, hpjj, h, xiaohou] set4=[null, hpj, hpjj, h, xiaohou] set5=[null, hpj, hpjj, h, xiaohou] set6=[null, hpj, hpjj, h, xiaohou] set7=[null, hpj, hpjj, h, xiaohou] set8=[null, hpj, hpjj, h, xiaohou] set9=[null, hpj, hpjj, h, xiaohou] =====使用迭代器遍历===== 迭代器遍历后:null 迭代器遍历后:hpj 迭代器遍历后:hpjj 迭代器遍历后:h 迭代器遍历后:xiaohou =====使用增强for循环===== 增强for循环: null 增强for循环: hpj 增强for循环: hpjj 增强for循环: h 增强for循环: xiaohou
7.1 HashSet
- HashSet 代码实操分析:
import java.util.HashSet; public class HashSet_ { public static void main(String[] args) { //HashSet底层实现就是HashMap HashSet hashSet = new HashSet(); //从源码可以看出 执行add 后返回的是一个boolean值 //所以如果添加成功返回的是 true 失败 false System.out.println(hashSet.add(1));//t System.out.println(hashSet.add(2));//t System.out.println(hashSet.add(2));//f System.out.println(hashSet.add(3));//t System.out.println(hashSet.add(4));//t hashSet = new HashSet(); //此时hashSet为空 System.out.println(hashSet); hashSet.add("hpj");//加入成功 hashSet.add("hpj");//因为是相同数据,所以加入失败 hashSet.add(new hpj("hpj1")); hashSet.add(new hpj("hpj1"));//此时两个数据都添加成功 System.out.println(hashSet); hashSet.add(new String("123"));//加入成功 hashSet.add(new String("123"));//加入失败 System.out.println(hashSet); } } class hpj { private String name; public hpj(String name) { this.name = name; } @Override public String toString() { return "hpj{" + "name='" + name + '\'' + '}'; } } =============================================================== 输出结果如下: true true false true true [] [hpj{name='hpj1'}, hpj, hpj{name='hpj1'}] [hpj{name='hpj1'}, 123, hpj, hpj{name='hpj1'}]
- HashSet底层结构分析,代码模拟:
public class HashSet_Diceng { //HashSet底层机制说明 //HashSet底层是 HashMap ,HashMap底层是(数组+链表+红黑树) public static void main(String[] args) { //模拟HashSet的底层 数组加链表结构 //创建一个数组 Node[] table = new Node[16]; //创建节点 Node hpj = new Node("hpj",null); table[2] = hpj; Node hpj1 = new Node("hpj1",null); hpj.next=hpj1;//将hpj1挂在到 hpj Node hpj2 = new Node("hpj2",null); hpj1.next=hpj2; System.out.println(table); } } class Node {//结点 存储数据,可以指向下一个结点从而形成列表 Object item;//存放数据 Node next;//指向下一个节点 public Node(Object item, Node next) { this.item = item; this.next = next; } }
- HashSet 添加元素底层如何实现(详细代码解读后续更新)
- 添加一个元素时,先得到它的hash值,然后转换成索引值
- 找到存储数据表
table
看这个索引的位置是否已经存放有元素,如果没有就直接加入,如果有调用equals方法比较,如果相同就放弃添加,如果不同则添加到最后 - 在
jdk8
中如果一条链表的元素的个数等于8时,并且table的的大小大于等于默认的64时就会进行树化(红黑树)
- 通过上面结论可知如果我们添加自定义对象元素的时候我们可以通过重写
equals方法
和hashcode方法
来实现对相同自定义对象元素的去重工作,代码实例如下:import java.util.HashSet; import java.util.Objects; public class HashSet_xiangtong { public static void main(String[] args) { HashSet set = new HashSet(); set.add("1"); set.add("2"); set.add("1"); set.add(new A(1,2)); set.add(new A(3,4)); set.add(new A(1,2)); System.out.println(set); } } class A{ private int h; private int age; public A(int h, int age) { this.h = h; this.age = age; } @Override public String toString() { return "Hpj{" + "h=" + h + ", age=" + age + '}'; } public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; A a = (A) o; return h == a.h && age == a.age; } @Override public int hashCode() { return Objects.hash(h, age); } }
7.2 TreeSet
-
TreeSet可以将我们存进去的数据进行排序:自然排序和比较器排序,直接代码实操:
- 自然排序
import java.util.TreeSet; public class TreeSet_ { public static void main(String[] args) { //自然排序 //1.TreeSet可以排序的集合,所以我们往其中添加的元素要能区分大小 TreeSet treeSet = new TreeSet(); //2.当我们直接添加自定义对象的时候我们是无法排序的,因为Student类中没有没有实现Comparable接口,所以我们要去实现比较接口->3 treeSet.add(new Student(15, "h")); treeSet.add(new Student(16, "h")); treeSet.add(new Student(14, "j")); treeSet.add(new Student(19, "p")); for (Object o : treeSet) { System.out.println(o); } } } //3.让我们的Student类实现比较接口->4 class Student implements Comparable<Student> { private int age; private String name; Student(int age, String name) { this.age = age; this.name = name; } @Override public String toString() { return "Student{" + "age=" + age + ", name='" + name + '\'' + '}'; } @Override //4.实现接口的compareTo方法,先按照年龄排序,再按名字排序 public int compareTo(Student o) { //升序 就是第一个减去第二个 ,降序就是相反 int r = this.age - o.age; if (r != 0) { return r; } //再按照名字排序 升序 return this.name.compareTo(o.name); } }
- 比较器排序
- 假设现在Student类已经写好了,但是没有实现Comparable接口,同时我们又不愿意直接修改Student类的代码,那么在这种情况下,或者是当我们无法更改源码实现接口的时候 我们就能用到比较器排序
import java.util.Comparator;
import java.util.TreeSet;
public class TreeSet_bjq {
public static void main(String[] args) {
//比较器排序
//自定义一个比较器对象,匿名内部类
Comparator c = new Comparator<Student1>() {//要重写接口的所有抽象方法
//另一个equals方法 匿名内部类默认继承了object类 的equals方法
@Override
public int compare(Student1 o1, Student1 o2) {
//先名字升序 后 年龄降序
int r = o1.getName().compareTo(o2.getName());
if (r != 0) {
return r;
}
return o2.getAge()- o1.getAge();
}
};
TreeSet treeSet = new TreeSet(c);
treeSet.add(new Student1(15, "h"));
treeSet.add(new Student1(16, "h"));
treeSet.add(new Student1(14, "j"));
treeSet.add(new Student1(19, "p"));
for (Object k : treeSet) {
System.out.println(k);
}
}
}
class Student1 {
private int age;
private String name;
Student1(int age, String name) {
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
=====================================================
Student{age=16, name='h'}
Student{age=15, name='h'}
Student{age=14, name='j'}
Student{age=19, name='p'}
8.Map接口
8.1Map实现类的特点
import java.util.HashMap;
import java.util.Map;
public class Map_ {
public static void main(String[] args) {
//Map用于保存具有映射关系的数据 Key Value
Map map = new HashMap();
map.put("1", "h");
map.put("2", "p");
map.put("3", "j");
//Map中的key值如果相同就会重写这个key值下的value对应的值
map.put("1","hh");
//Map中的value可以重复
map.put("4","hh");
//Map中的key值可以为null,value也可以为null,注意key为null只能有一个,value可以有多个
map.put(null,null);
map.put("5",null);
//常用String类作为Map的key,别的类型也可以
map.put(1,2);
//Map和Value是一对一的关系也就是指定的key能找到对应的value
System.out.println(map.get(1));
System.out.println(map);
}
}
====================================================
输出结果如下:
2
{null=null, 1=hh, 1=2, 2=p, 3=j, 4=hh, 5=null}
9.开发中如何选择集合实现类
- 先判断存储数据的类型(是一组对象【单列】还是一组键值对【双列】)
- 如果是一组对象【单列】:
Collection
接口- 允许重复:
List
- 增删多:
LinkedList
[底层维护了一个双向列表] - 改查多:
ArrayList
[底层维护Object类型的可变数组]
- 增删多:
- 不允许重复:
Set
- 无序 :
HashSet
[底层是HashMap,维护了一个hash表(数组+链表+红黑树)] - 有序:
TreeSet
- 插入和取出数据一致:
LinkedHashSet
维护数组+双向列表
- 无序 :
- 允许重复:
- 一组键值对:
Map
- 键无序:
HashMap
- 键排序:
TreeMap
- 键插入和取出顺序一致:
LinkedHashMap
- 读取文件:
Properties
- 键无序:
二.泛型
1.泛型简介
- 泛型又称参数化类型,解决数据类型的安全性问题
- 在类声明或实例化时只要指定好需要的具体类型即可
- 泛型的作用是:可以在声明时通过一个表示类中某个属性的类型,或者是某个方法的返回值类型,或者是参数类型
- 泛型的声明:
-
interface 接口 <T>{} 和 class 类<K,V>{} //其中 T K V不代表值,而是表示引用类型,任意字母都可以
- 在给泛型指定具体类型后,可以传入该类型或者其子类类型
- 如果我们 不写<> 默认给它的是
< Object >
- 泛型不具备继承性
<?>
支持任意泛型类型<? extends A>
支持A类以及A类的子类,规定了泛型的上限<? super A>
支持A类以及A类的父类,不限于直接父类,规定了泛型的下限
三.枚举
1.概述
- 枚举(enumeration 简写 enum),是一组常量的集合,枚举属于一种特殊的类里面只包含一组有限的特定的对象,他和普通类一样可以使用构造器、定义成员变量和方法,也能实现一个或多个接口,但是枚举类不能继承其它的类
2.枚举的两种实现方式
- 自定义类实现枚举
- 使用
emum
关键字实现枚举
3.自定义类实现枚举
- 不需要提供
set
方法 因为枚举对象值通常为只读。 - 对枚举对象/属性使用
final static
共同修饰,实现底层优化 - 枚举对象名通常使用全部大写,常量的命名规范
- 枚举对象根据需要,也可以有多个属性
- 应用案例如下:
public class Test1 { public static void main(String[] args) { System.out.println(Enum_.N); } } class Enum_ { private String name ; public final static Enum_ N= new Enum_("name"); private Enum_(String name) { this.name = name; } public String getName() { return name; } @Override public String toString() { return "Enum_{" + "name='" + name + '\'' + '}'; } }
自定义类实现枚举 - 小结
- 构造器私有化
- 本类内部创建一组对象
- 对外暴露对象(通过对对象添加
public final static
修饰符)- 可以提供 get方法 不要提供set方法
4.enum
关键字实现枚举
- 使用
enum
代替class
-
public final static Enum_ N= new Enum_("name"); /*直接使用*/ N("name"); //解读 常量名(实参列表)如果不值一个常量,则用逗号 隔开最后一个写完分号结尾
- 如果通过
enum
实现枚举则要求将定义的常量对象写在最前面 - 如果我们使用的无参构造器,创建常量对象的时候可以省略 ()。
- 代码展示:
public class Test2 { public static void main(String[] args) { System.out.println(Enum2.AB); // System.out.println(Enum2.CD); // System.out.println(Enum2.EF); } } enum Enum2{ AB("hpj","23"),CD,EF;//写在最前 调用无参构造器可以省略() private String name; private String age; Enum2() { System.out.println("无参构造器"); } Enum2(String name, String age) { this.name = name; this.age = age; } public String getName() { return name; } public String getAge() { return age; } @Override public String toString() { return "Enum2{" + "name='" + name + '\'' + ", age='" + age + '\'' + '}'; } }
5.enum
常用方法
- 说明:使用关键字
enum
时,会隐式继承Enum
类,这样我们就可以用Enum
类相关的方法enum Enum_3 { CHUNTIAN("春天"), XIATIAN("夏天"), QIUTIAN("秋天"), DONGTIAN("冬天"); private String name; private Enum_3(String name) { this.name = name; } public String getName() { return name; } @Override public String toString() { return "Enum_3{" + "name='" + name + '\'' + '}'; } } public class Test3 { public static void main(String[] args) { Enum_3 chuntian = Enum_3.CHUNTIAN; //得到当前枚举常量的名称 System.out.println("name()方法的使用 输出结果如下:"); System.out.println(chuntian.name()); //ordinal() 得到当前枚举的次序或者编号 从 0开始 // CHUNTIAN("春天")是0 以此类推,XIATIAN("夏天"),QIUTIAN("秋天"),DONGTIAN("冬天"); System.out.println("ordinal()方法的使用 输出结果如下:"); System.out.println(chuntian.ordinal()); // 从反编译可以看出 values 方法 返回的的是 Enum_3 [] // values 方法 是返回含有定义的所有枚举对象 System.out.println("values()方法的使用 输出结果如下:"); Enum_3[] values = Enum_3.values(); for (Enum_3 enum_3 : values) { //增强for循环原理 : 依次从values数组中取出数据 赋给 enum_3 如果取出完毕则推出 for循环 System.out.println(enum_3); } // valuesOf 方法是将字符串转换成枚举对象,要求字符串必须为已有的常量名,否则报异常 // 执行的流程为 // 1.根据你输入 的 "CHUNTIAN" 去 Enum_3的枚举对象中去查找 // 2.如果找到了,就返回 如果没有找到就报错 System.out.println("valueOf()方法的使用 输出结果如下:"); Enum_3 chuntian1 =Enum_3.valueOf("CHUNTIAN"); System.out.println("chuntian1: "+ chuntian1); System.out.println("chuntian:"+chuntian); //比较两个枚举常量的编号 用前者的编号减去后着的编号 System.out.println("compareTo()方法的使用 输出结果如下:"); System.out.println(Enum_3.CHUNTIAN.compareTo(Enum_3.DONGTIAN)); } } ----------------------------------------------------------------------------------------------------------------- 输出结果如下: name()方法的使用 输出结果如下: CHUNTIAN ordinal()方法的使用 输出结果如下: 0 values()方法的使用 输出结果如下: Enum_3{name='春天'} Enum_3{name='夏天'} Enum_3{name='秋天'} Enum_3{name='冬天'} valueOf()方法的使用 输出结果如下: chuntian1: Enum_3{name='春天'} chuntian:Enum_3{name='春天'} compareTo()方法的使用 输出结果如下: -3
6.enum
实现接口
- 使用
enum
关键字后,就不能继承其它类,因为enum
会隐式继承Enum类 而java又是单继承机制 - 枚举类和普通类一样可以实现接口
7.枚举使用细节和注意事项
- 枚举类型中的第一行代码,要求一定是指定枚举对象的个数和名字,同时最后面加分号,在这行代码下,才可以定义枚举类型的属性和方法
- 枚举中的构造器,只能使用
private
修饰,或者不写修饰符,默认就是private
,同时还可以构造器重载 - 在枚举中,定义对象的时候,就已经默认使用了无参构造器
- 枚举类型中可以定义抽象方法,但是需要再定义每一个枚举对象的时候,就将抽象方法实现,因为枚举类型是
final
修饰的类,不可能有子类型来实现这个抽象方法,所以就必须是自己对象去实现这个抽象方法。
如上图所示 直接定义抽象方法后 会报错此时我们要在枚举对象中对抽象方法进行实现package com.java.enum_; enum Student1 { BOY() { public String run() { return " 男生跑步"; } }, GIRL() { public String run() { return "女生跑步"; } }; private String sex; public abstract String run(); } public class Test5 { public static void main(String[] args) { for (Student1 s : Student1.values()) { System.out.println(s.run()); } } }
- 在项目中只要一个类型的对象个数和名称能固定下来,就可以考虑使用枚举