1.集合泛型枚举
1.1为什么使用集合框架
场景1:存储一个班学员信息,假定一个班容纳20名学员
解决办法:一维数组
场景2:如何存储每天的新闻信息?每天的新闻总数不确定,太少浪费空间,太多空间不足
解决办法:如果并不知道程序运行时会需要多少对象,或者需要更复杂方式存储对象——可以使用Java集合框架
1.2Java集合框架包含的内容
1.2.1集合框架
Java集合框架提供了一套性能优良、使用方便的接口和类,它们位于java.util包中
1.2.2Collection
Collection接口存储一组不唯一,无序的对象
1.2.3List 有序不唯一
定义:List 接口存储一组不唯一,有序(插入顺序)的对象
1.2.4Set 无序,唯一
定义:Set 接口存储一组唯一,无序的对象,唯一:是指equals相同
使用:遍历set
- 获取迭代器
获取Iterator :Collection 接口的iterate()方法
Iterator的方法
boolean hasNext(): 判断是否存在另一个可访问的元素
Object next(): 返回要访问的下一个元素
- 增强for循环遍历
1.2.5Map:键值对
定义:Map接口存储一组键值对象,提供key到value的映射
Key:具有唯一性,且无序
-
如果再次放入某个key相同的值,那么value覆盖原来的值
-
key可以是null:但是null值也是一个唯一存在的key
Value:可以重复,可以是任意值
获取所有key和所有map
遍历Map:
方法1:通过迭代器Iterator实现遍历
方法2:增强型for循环
常用方法:
方法名 | 说明 |
---|---|
Object put(Object key, Object val) | 以键-值对的方式进行存储 |
Object get(Object key) | 根据键返回相关联的值,如果不存在指定的键,返回null |
Object remove(Object key) | 删除由指定的键映射的键-值对 |
int size() | 返回元素个数 |
Set keySet() | 返回键的集合 |
Collection values() | 返回值的集合 |
boolean containsKey(Object key) | 如果存在由指定的键映射的键-值对,返回true |
1.2.6 List的实现类
1.2.6.1ArrayList
定义:ArrayList实现了长度可变的数组,在内存中分配连续的空间。遍历元素和随机访问元素的效率比较高
常用方法:
方法名 | 说明 |
---|---|
boolean add(Object o) | 在列表的末尾顺序添加元素,起始索引位置从0开始 |
void add(int index,Object o) | 在指定的索引位置添加元素。索引位置必须介于0和列表中元素个数之间 |
int size() | 返回列表中的元素个数 |
Object get(int index) | 返回指定索引位置处的元素。取出的元素是Object类型,使用前需要进行强制类型转换 |
boolean contains(Object o) | 判断列表中是否存在指定元素 |
boolean remove(Object o) | 从列表中删除元素 |
Object remove(int index) | 从列表中删除指定位置元素,起始索引位置从0开始 |
代码演练:
public static void main(String[] args) {
List l = new ArrayList();
// l.add("a");
l.add("b");
l.add("c");
List l2 = new ArrayList();
l2.add("a");
l2.add("a");
//1.判断是否存在某个元素:返回true的前提是equals判单相等
System.out.println(l.contains("a"));
System.out.println(l.containsAll(l2));//只要元素包含
//2.添加
l.addAll(l2);
l.addAll(l2);
l.addAll(l2);
System.out.println(Arrays.toString(l.toArray()));
//3.获取元素的游标
//or -1 if there is no such index.
//从左到右第一次遇到的那个
System.out.println(l.indexOf("a"));
//从右到左第一次遇到的那个
System.out.println(l.lastIndexOf("c"));
//截取:从fromIndex开始,到toIndex游标之前一位
List l3 = l.subList(0, 2);
System.out.println(Arrays.toString(l3.toArray()));
}
1.2.6.2LinkedList
定义:LinkedList采用双向链表存储方式。插入、删除元素时效率比较高
常用方法:
方法名 | 说明 |
---|---|
void addFirst(Object o) | 在列表的首部添加元素 |
void addLast(Object o) | 在列表的末尾添加元素 |
Object getFirst() | 返回列表中的第一个元素 |
Object getLast() | 返回列表中的最后一个元素 |
Object removeFirst() | 删除并返回列表中的第一个元素 |
Object removeLast() | 删除并返回列表中的最后一个元素 |
使用:因为该实现类,实现了很多List接口中没有的方法,所以通常使用该类本身来声明
代码演练:
public static void main(String[] args) {
LinkedList list = new LinkedList();
list.addFirst("");
list.addLast("");
list.getFirst();
list.getLast();
list.removeFirst();
list.removeLast();
}
1.2.6.3 LinkedList VS ArrayList
ArrayList:不适合频繁增减元素, 擅长随机访问
LinkedList:适合于增减元素操作,不擅长随机访问
1.2.7Set接口的实现类
1.2.7.1HashSet
Set set=new HashSet();
String s1=new String("java");
String s2=s1;
String s3=new String("JAVA");
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println(set.size());
HashSet使用的是HashMap的Key
代码演练:
public static void main(String[] args) {
//无序且唯一
//基本数据类型/引用数据类型
Set s = new HashSet();
s.add("a");
s.add("a");
s.add("a");
s.add("b");
s.add("c");
s.add("d");
//1.获取迭代器
Iterator is = s.iterator();
while(is.hasNext())//判断是否有下一个:如果有则返回true,没有则返回false
{
Object obj = is.next();
System.out.println(obj);
}
//2.增强for循环来遍历
for(Object obj : s){
System.out.println(obj);
}
}
1.2.8 Map的实现类
1.2.8.1HashMap
1.2.8.1.1代码演练
public static void main(String[] args) {
Map m = new HashMap();
//1.添加元素
m.put("k1", "v1");
m.put("k1", "value1");
System.out.println(m.get("k1"));
m.put(null, null);
m.put(null, "nullStr");
System.out.println(m.get(null));
//2.获取所有key
Set keys = m.keySet();
System.out.println(Arrays.toString(keys.toArray()));
//3.获取所有value
Collection cons = m.values();
System.out.println(Arrays.toString(cons.toArray()));
}
1.2.9Hash,set和map(理解)
① hashMap
-
Entry:是一个单向链表
-
TreeEntry:包含了hash的双向链
-
红黑树
② Hash:
-
hash可以重写
-
native默认情况下,hash是由本地调用提供的,比如调用本地C。
-
集合中的hashcode是被****重写过****的
public class Test5 {
public static void main(String[] args) {
System.out.println("a".hashCode());
System.out.println(Objects.hash(1));//基本数据类型的hash
System.out.println(Objects.hash(1));//基本数据类型的hash
System.out.println(Objects.hash(1));//基本数据类型的hash
//相同对象的hash,必然一样
A a1 = new A();
A a2 = a1;
System.out.println(a1.hashCode());
System.out.println(a2.hashCode());
//不同对象的hash,不一样
System.out.println(Objects.hash(new A()));
System.out.println(Objects.hash(new A()));
//set的元素一致,hash就一致
Set s1 = new HashSet();
s1.add("a");
s1.add("b");
s1.add("c");
Set s2 = new HashSet();
s2.add("a");
s2.add("b");
s2.add("c");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
//Map的key 和 value相同,那么他们hash也一样
Map m1 = new HashMap();
m1.put("k1", "value1");
m1.put("k2", "value2");
m1.put("k3", "value3");
Map m2 = new HashMap();
m2.put("k1", "value1");
m2.put("k2", "value2");
m2.put("k3", "value3");
System.out.println(m1.hashCode());
System.out.println(m2.hashCode());
//LinkedList
LinkedList l1 = new LinkedList();
l1.add("a");
l1.add("b");
l1.add("c");
LinkedList l2 = new LinkedList();
l2.add("a");
l2.add("b");
l2.add("c");
System.out.println(l1.hashCode());
System.out.println(l2.hashCode());
//ArrayList
List la1 = new ArrayList();
la1.add("a");
la1.add("b");
la1.add("c");
List la2 = new ArrayList();
la2.add("a");
la2.add("b");
la2.add("c");
System.out.println(la1.hashCode());
System.out.println(la2.hashCode());
}
}
class A{}
1.2.10泛型
1.2.10.1应用场景
如何解决以下强制类型转换时容易出现的异常问题
List的get(int index)方法获取元素
Map的get(Object key)方法获取元素
Iterator的next()方法获取元素
通过泛型
JDK5.0使用泛型改写了集合框架中的所有接口和类
目的:统一集合当中的元素
1.2.10.2定义
将对象的类型作为参数,指定到其他类或者方法上,从而保证类型转换的安全性和稳定性
本质是参数化类型
1.2.10.3泛型集合
泛型集合可以约束集合内的元素类型
典型泛型集合ArrayList、HashMap<K,V>
、<K,V>表示该泛型集合中的元素类型
泛型集合中的数据不再转换为Object
1.2.10.4泛型类
泛型类用于类的定义中,被称为泛型类。通过泛型可以完成对一组类的操作对外开放相同的接口。最典型的就是各种容器类,如:List、Set、Map
(1)泛型标识:标识可以随便写,通常是一个大写字母,需要具备一定的可读性
(2)比如:T(type),I(item),K(key),V(value)
(3)如果在泛型类当中没有指定泛型,那么该类型,默认为Object
(4)Public void test(T t){}:这种不是泛型方法!!!!!!
(5)通常来说,我们不会对已泛型方式传入的数据做什么操作,因为使用泛型的前提是,我不知道用户会提供什么类型给我,所以,不会去操作他
(6) :泛型形参
(7) :泛型实参
1.2.10.5泛型接口
泛型接口与泛型类的定义及使用基本相同,泛型接口常被用在各种类的生产器中
(1) 泛型接口是定义在接口上
(2) 泛型类和泛型接口的泛型,在实现或者继承的情况下,重写或者实现的方法,泛型的参数和返回值需要保持一致
(3) 泛型接口的泛型实参必须指定,而实现类,可以不必指定,如果不指定,则沿用其接口的泛型类型
//定义一个泛型接口
public interface Generator<T> {
//接口方法
public T next();
}
public interface List <E>{
public void add(E e);
public void set(int i,E e);
public E get(int i);
public E remove(int i);
public int size();
public boolean isEmpty();
public E[] toArray();
}
1.2.10.6泛型通配符
我们都知道Integer是Number的子类,那么阅读以下代码,思考会出现什么结果
public static void main(String[] args) {
test(new ArrayList<Number>());
}
static void test (List<?> is){}
(1) ?:代表任意泛型
(2) ?:是泛型实参,不是泛型形参,可以看成是所有类型的父类型
(3) 可以指定边界,规定某一批类型可以作为泛型,既可以规定泛型形参,也可以规定泛型实参
1.2.10.7泛型边界
是在调用方法的时候指明泛型的具体类型
? super B ?用于指定类型实参的边界
T extends Object T用于指定类型形参的边界
public class Test1{
public static void main(String[] args) {
Demo1<String> d1 = new Demo1<>();
d1.put("你好,范特西");
System.out.println(d1.get());
}
}
//这是一个泛型类
class Demo1<T extends Object>{
//这个不是泛型方法!!!!!!!!
private Object obj;
public void put(T t){
obj = t;
}
public T get(){
return (T) obj;
}
}
public class Test2{
public static void main(String[] args) {
Demo1<A> d1 = new Demo1<A>();
Demo2 d2 = new Demo2();
d2.test(d1);
}
}
//这是一个泛型类
class Demo2{
public void test(Demo1<? super B> demo1){
}
}
class A{}
class B extends A{}
class C extends A{}
1.2.10.8泛型方法
是在调用方法的时候指明泛型的具体类型
public class Test3{
public static void main(String[] args) {
DemoUtil util = new DemoUtil();
}
}
class DemoUtil<T , E>{
//这是一个泛型方法
public <K , V , I> void test(K k , V v , I i , T t ,E e){
}
}
(1) 如果一个类当中,不必多个方法公用同一个类型,某个方法的泛型是独立在当前方法当中的,这时,建议使用泛型方法,不影响到其他方法的类型
(2) 在方法的修饰符和返回值之间,申明泛型,该泛型,只作用于当前方法
1.2.10.4使用
① 对于基本数据类型:泛型只能使用包装类来定义基本数据类型
1.2.10.5代码演练
public static void main(String[] args) {
List<String> l = new ArrayList<String>();
l.add("ybb1");
l.add("ybb2");
l.add("ybb3");
l.add("ybb4");
for(String str : l){
System.out.println(str);
}
Set<Double> s = new HashSet<Double>();
Map<String,Object> m = new HashMap<String,Object>();
}
public class ArrayList<E> implements List<E>{
//希望通过该实现类,来完成一个可变长度的数组
private Object[] objs = new Object[0];//这是一个长度初始为0的object数组
private int length = 0;//是当前list的长度
@Override
public void add(E e) {
// 通过复制数组,来完成对数组的扩容
objs = Arrays.copyOf(objs, ++length);
objs[length - 1] = e;
}
@SuppressWarnings("unchecked")
@Override
public E get(int i) {//取游标位置的值
//1.先判断取值范围是否合理
if(i < 0 || i > (length - 1)){
throw new RuntimeException("游标越界异常:"+i);
}
return (E) this.objs[i];
}
@Override
public E remove(int i) {
//1.先判断取值范围是否合理
if(i < 0 || i > (length - 1)){
throw new RuntimeException("游标越界异常:"+i);
}
Object removeObj = this.objs[i];//要删除的旧值
for(int j = i + 1 ; j < length ; j ++){
this.objs[j-1] = this.objs[j];
}
objs = Arrays.copyOf(objs, --length);
return (E) removeObj;
}
@Override
public int size() {
return this.length;
}
@Override
public boolean isEmpty() {
return this.length == 0;
}
@Override
public E[] toArray() {//将list变为数组
// 复制一个数组,不能直接给原数组,直接给原数组那么就意味者,获取到原的地方,可以任意修改集合当中的数据,造成数据隐患
return (E[]) Arrays.copyOf(objs, length);
}
@Override
public void set(int i, E e) {
//1.先判断取值范围是否合理
if(i < 0 || i > (length - 1)){
throw new RuntimeException("游标越界异常:"+i);
}
this.objs[i] = e;
}
}
1.2.11枚举
1.2.11.1应用背景
问题:
如果我需要定义一个类型,例如:性别
int sex = 0;//0:女/1:男
以上定义方式有什么问题?
如何解决
1.2.11.2定义
枚举是一种特殊的数据类型,之所以特殊是因为它既是一种类(class)类型却又比类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁,安全性以及便捷性。
(1) 一些散列值:如果存在数据的安全风险,那么在使用的过程中,该数据可以定义为枚举来避免风险数据对安全性的影响
(2)构造是针对所有的枚举的元素,每一个枚举的元素都相当于一个类。
(3)构造必须私有,调用构造的方法是通过调用类型
(4) 枚举中的类型是单例,有几个类型创建几个单例(问题之一)
(5) 枚举可以重写方法
(6) 枚举可以实现接口
(7) 有可能会大量产生单例,但是这个问题并不是枚举最致命的问题
(8) 在网络当中,WEB开发,枚举和很多主流的数据结构不兼容
1.2.11.3创建枚举
创建一个枚举类型,需要使用关键字enum
public enum TESTENUM {
}
1.2.11.4定义一个枚举,并使用其完成赋值
public enum TESTENUM {
F1,
F2,
F3
}
TESTENUM te = TESTENUM.F1
1.2.11.5在switch中使用枚举
TESTENUM te = TESTENUM.F1
switch(te){
case F1:
break;
case F2:
break;
}
1.2.11.6在类当中使用枚举作为属性
private TESTENUM te;
public TESTENUM getTe(){
return te;
}
public void setTe(TESTENUM te){
this.te = te;
}
1.2.11.7枚举实现接口
public enum MyDay implements DayInterface
1.2.11.8接口组织枚举
public interface A {
enum E1 implements A{
T1,T2,T3
}
enum E2 implements A{
I1,I2,I3
}
}
public static void main(String[] args) {
A.E1 e1 = A.E1.T1;
A.E1 e2 = A.E1.T2;
}
1.2.11.9枚举优缺点
优点:
可以有效的管理离散值
标识符的缺点:
代码可读性差,易用性差。
类型不安全。
耦合性高,扩展性差。
缺点:
通用性差,在web工程中和其他一些不支持枚举的第三方交互时,用枚举不如用xml或properties或JSON
需要实例化,会大量消耗资源,得不偿失
注意:相较于枚举的优点,其缺点更加明显,所以不要盲目跟风,理性看待新技术,旧的未必不好,新的未必有用,一切从实践出发!
1.2.11.7综合代码演练
public interface DefInterface {
public void test();
}
public class Person {
private int sex;
private Sexs sexs;
public int getSex() {
return sex;
}
public void setSex(int sex) {
this.sex = sex;
}
public Sexs getSexs() {
return sexs;
}
public void setSexs(Sexs sexs) {
this.sexs = sexs;
}
}
public interface Sex {
public int MAN = 1;
public int WOMAN = 0;
}
public enum Sexs implements DefInterface{
MAN(1){
public String toString(){
return "sex:男";
}
//在枚举中的方法重写
@Override
public void test() {
// TODO Auto-generated method stub
}
},WOMAN(0){
public String toString(){
return "sex:女";
}
@Override
public void test() {
// TODO Auto-generated method stub
}
},MID(2){
public String toString(){
return "sex:脑补";
}
@Override
public void test() {
// TODO Auto-generated method stub
}
};
private int sex;
private Sexs(int sex){
this.sex = sex;
}
//在枚举中创建方法(和构造方法)
private Sexs(){
System.out.println("构造被调用了");
}
public int getSexValue(){
return this.sex;
}
public String toString(){
return "sex:"+this.sex;
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
// Person p = new Person();
// p.setSex(Sex.MAN);
// p.setSex(2);//这是潜在的风险
//
// p.setSexs(Sexs.MAN);
// Sexs s = Sexs.MAN;
// switch(s){
// case MAN:
// break;
// case WOMAN:
// break;
// }
Sexs s1 = Sexs.MID;//第一次是调用了2次
System.out.println(s1.getSexValue());
System.out.println(s1);
}