JavaImprove--Lesson01--枚举类,泛型
一.枚举
认识枚举类
枚举是一种特殊的类
枚举的格式:
修饰符 enmu 枚举类名{
名称1,名称2;
其它成员
}
//枚举类 public enum A { //枚举类的第一列必须是罗列枚举对象的名称 X,Y,Z; private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }
枚举类的构造器是私有的,也就是说,我们不能通过外部类来使用new关键字实例化枚举类,但是可以通过:枚举类 . 对象 ;拿取内部的对象
如拿取对象X
public static void main(String[] args) { A a=A.X; System.out.println(a); }
枚举类有以下特点
- 枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象
- 枚举类的构造器都是私有的(无论是否显式表达,都是私有的),因此枚举类不能对外创建对象
- 枚举类都是最终类,不可以被继承
- 枚举类中,从第二行开始,可以定义类的其它各种成员
- 编译器为枚举类新增了几个方法,并且枚举类都是继承:Java.lang.Enum类的,从Enum类中也会继承到一些方法
枚举类的一些方法
拿取全部对象:
//拿取到全部对象 A[] values = A.values(); for (A value : values) { System.out.println(value); }
通过方法拿对象
//通过方法拿对象,传入对象名 A z = A.valueOf("Z"); System.out.println(z);
抽象枚举
抽象枚举指的是在枚举类中,写一些抽象方法,在抽象枚举类中的对象都需要实现这个方法
//抽象枚举 public enum B { X(){ @Override public void fun() { System.out.println("对象X实现抽象方法"); } },Y("msf"){ @Override public void fun() { System.out.println(getName()+"对象Y实现抽象方法"); } }; public static void X() { } //抽象方法 public abstract void fun(); private String name; //私有的,无修饰符,且不可更改 B() { } B(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
抽象枚举的操作:
//拿取抽象对象,使用实现的方法方法 B x= B.X; System.out.println(x); x.fun();
枚举的应用场景
一般用于数据标注和分类,用于解决原来区分参数时,传入变量值是不可控的情况
如,我们一些APP在做认证的时候,需要分别男,女然后分别推送相应的信息,那么传输的关键字肯定是性别
我们试着写如下代码,查看有什么问题?
public static void main(String[] args) { check(1); } public static void check(int sex){ switch (sex){ //女 case 0: System.out.println("此用户为女,展示一些化妆品,包包~"); break; //男 case 1: System.out.println("此用户为男,展示一些游戏,美女信息~"); break; } }
首先,这样写识别身份是肯定没有问题的,如果开发者或用户都选择从正常的角度去进入,那么没有问题
但是,往往这些事情都是不可控,在调用check()方法的时候,传递参数完全是不可控的,也就是,如果我传递的是-1,也能运行只是身份识别不了,违背了程序的初衷,
有些人解决的方式是利用常量来解决的,也就是限定为传入的参数为常量
//专门构造一个存放常量的类 public class staticVar { //男 public static final int Boy=1; //女 public static final int Girl=0; }
然后传递参数的时候就是传递一个常量:
public static void main(String[] args) { check(staticVar.Boy); } public static void check(int sex){ switch (sex){ //女 case staticVar.Girl: System.out.println("此用户为女,展示一些化妆品,包包~"); break; //男 case staticVar.Boy: System.out.println("此用户为男,展示一些游戏,美女信息~"); break; } }
当我们能构思到常量的时候,我们就离枚举类很近了,
回忆一下枚举类的第一列是什么?------枚举类的第一行只能罗列一些名称,这些名称都是常量,并且每个常量记住的都是枚举类的一个对象
这不是匹配上了嘛,所以继续改造,编写一个枚举类,用来存储这些标记
public enum StaticEnum { Boy,Girl; }
传递枚举类对象作为区分男,女的标志
public static void main(String[] args) { check(StaticEnum.Girl); } public static void check(StaticEnum sex){ switch (sex){ //女 case Girl: System.out.println("此用户为女,展示一些化妆品,包包~"); break; //男 case Boy: System.out.println("此用户为男,展示一些游戏,美女信息~"); break; } }
使用枚举类作为数据标记分类的优点在于,用户或后面实现的程序员都只能传递此枚举类的参数
此枚举类的对象又被我们限定好了,且枚举类没有公共构造器,不能自己构造,参数就被限定为开发者设定的那几组内部了
总结:
但是要知道静态变量也是可以使用的,那么枚举类和静态变量做数据标注和分类的区别在哪呢?
- 一般游戏搭建里使用枚举类比较多,因为它的参数就是那些固定参数,如,上,下,左,右键,鼠标事件等固定的案件
- 在项目开发里,使用静态变量就比较多了,他需要从初始变量拿取值的,这些值有可能还是字符串,所以是有类型的,和枚举类的特点不符
二.泛型
认识泛型
在定义方法,接口,类的时候,同时声明了一个或多个类的变量(如:E),称为泛型方法,泛型接口,泛型类,它们统称为泛型
泛型的一般格式:
public class ArrayList<E>{
......
}
泛型的作用:泛型提供了在编译操作的时候所提供的数据类型,并自动检测的能力,这样可以避免强制类型的转换,及其可能出现的异常
泛型的本质:把具体的数据类型作为参数传递给类变量
在使用Java自定义的一些类的时候,它们的数据类型大多都是泛型,所以在进行值传递是都是Object类型,返回的值也是Object,在存放数据的时候可以将任意一种对象放入,但是去数据就很麻烦了
第一,有可能你根本不知道这个Object中到底存放的是那些数据
第二,即使你知道它是那个对象的类型,但是要进行操作时,必须要强转成它原本的类型
所以,我们可以使用将数据类型作为参数传递给类变量的方式来控制泛型
如下,使用Java自带的ArrayList类来做测试:
public class Main2 { public static void main(String[] args) { //直接new的ArrayList ArrayList list1 = new ArrayList<>(); list1.add("java"); list1.add("mysql"); list1.add(new Animal()); } } class Animal{ }
在为进行泛型约束的时候,默认是什么都可以往里面装的,
在拿取的时候也是拿取的Object类型
如下,我们在取里面的值的时候:
for (int i = 0; i < list1.size(); i++) { String o = (String) list1.get(i); System.out.println(o); }
在拿取对象的时候报了一个错误,它的意思就是Animal不是String类型,肯定不是啊,他们属于两个不同的类对象,如果强转成Animal,又会报错String听不是Animal类型
发生这种事情的根本原因就是传递在添加数据的时候设定的两个数据类型都可以,所以我们要尽量避免这种情况发生
现在我们使用将类型作为参数传递给类,约束泛型:
ArrayList<String> list2 = new ArrayList<>(); list2.add("java"); list2.add("mysql");
将数据类型约束成了字符串,再来添加一个对象试试:
我们可以看到报了一个错误,这个错误的意思是需要将这个对象转为String类型才能添加
这样就可以约束到泛型的参数类型了
自定义泛型类
//泛型类<E> E默认表示Object,在实例化的时候通过参数传递进来 public class MyArrayList<E> { //存放数据的Object数组 private Object[] o = new Object[20]; //计数器,记录数据存放到哪里了 private int size=0; //添加数据的方法 public void add(E e){ o[size++]=e; } //获取数据的方法 public E get(int index){ return (E)o[index]; } //拿取当前位置长度 public int getSize() { return size; } }
此时的E默认是Object,当我们在构造泛型的时候传递一个数据类型给类接收到后,就会将E 替换为你传递进来的类型
如:
//自定义的ArrayList MyArrayList<String> list3 = new MyArrayList<>();
也就是将E替换为String,我们的自定义泛型类就会做出如下改变:
public class MyArrayList<String> { //存放数据的Object数组 private Object[] o = new Object[20]; //计数器,记录数据存放到哪里了 private int size=0; //添加数据的方法 public void add(String e){ o[size++]=e; } //获取数据的方法 public String get(int index){ return (String)o[index]; } //拿取当前位置长度 public int getSize() { return size; } }
如上,E都被替换成为了String类型
这就是对泛型类型的约束
测试自定义泛型类:
//自定义的ArrayList MyArrayList<String> list3 = new MyArrayList<>(); list3.add("msf"); list3.add("maming"); for (int i = 0; i < list3.getSize(); i++) { System.out.println(list3.get(i)); }
多泛型类定义:
//多泛型类 public class MyArrayList2 <E,T>{ public void add(E e,T t){ } public E getE(String name){ return null; } public T getT(String name){ return null; } }
实例化:
//自定一ArrayList2 MyArrayList2<String, Animal> list4 = new MyArrayList2<>();
泛型类型的继承定义:
//泛型E是继承Animal类的 public class MyArrayList3<E extends Animal> { public void add(E e){ } public E getE(String name){ return null; } }
这种类的泛型类型只能是Animal类本身或继承Animal类的类
如:
MyArrayList3<Animal> list5 = new MyArrayList3<>();
错误情况,如其为String类时:
如上,当不为Animal类或其继承类时,会报错,且不能运行程序
泛型接口
泛型接口的定义和泛型类的定义方式是差不多的,只是用途不一样,因为类和接口的适用范围本来就是不一样的
在实现泛型接口的类中,也可以携带数据类型进行实现接口,这样接口中泛型T都会被替换成被传递的参数类型
//泛型接口 public interface MyInterface<T>{ //默认Object void add(T e); T get(String name); }
我们再来看看实现类将数据类型传入后实现:
实现类一:
//实现类的操作数据都是String public class MyStringImp implements MyInterface<String>{ //变化:T -》String @Override public void add(String e) { } //变化:T -》String @Override public String get(String name) { return null; } }
实现类二:
//实现类的操作数据类型都是Integer public class MyIntImp implements MyInterface<Integer>{ //变化:T -> Integer @Override public void add(Integer e) { } //变化:T -> Integer @Override public Integer get(String name) { return 0; } }
泛型方法
定义格式
修饰符 <类变量,类型变量.....> 返回值类型 方法名(形参列表){
//语句
}
此类方法和泛型类是一样的使用方式,都是用于解决参数类型不知道的情况下做的适配,
泛型方法的泛型参数都是自定义的,这是一个基本点,在泛型类中也有泛型变量,但是泛型类中的方法都不是泛型方法,因为泛型变量是泛型类定义的,而不是方法自定义的
//泛型方法 public static <T> void test(T t){ } //不是泛型方法 public E get(int index){ return E; }
泛型方法示例:
public static void main(String[] args) { //ArrayList ArrayList<String> list = new ArrayList<>(); list.add("java"); list.add("mysql"); //调用方法 String rs = test(list); System.out.println(rs); } //使用自定义泛型变量 public static <T> T test(ArrayList<T> arr){ return arr.get(1); }
我们在使用已经定义好的泛型的时候可以 不用自定义泛型,而是使用通配符?代替
通配符 ?可以代替一切类型
如我们熟知的ArrayList<>就是一个典型的泛型对象,我们可以使用自定义方法,让自定义泛型变量作为参数传入ArrayList<>中,也可以使用通配符?
自定义方法:
//使用自定义泛型变量 public <T> T test(ArrayList<T> arr){ return arr.get(1); }
ArrayList<>对象本身就是定义好的泛型对象,使用的时候没必要在自定义一个泛型变量做适配,所以推荐使用通配符,自定义泛型是基于没有写好的泛型对象的基础上,可以自定义一个来解决参数类型不确定的情况
通配符解决:
//使用通配符 public <T> T test(ArrayList<?> arr){ return arr.get(1); }
对已经定义好的泛型对象,ArrayList<?>使用通配符是最常用的
泛型的上下限
泛型变量出现就是为了解决,未知的数据类型,但是它也有一个缺点,所谓得于斯者,毁于斯,正因为,他包含很多种数据类型可以通配
我们在使用泛型方法的时候,就不能限制不是我们需要的参数类型进入方法计算,如我们泛型方法不能处理字符型的数据,但是还是传入了字符数据,泛型方法就不能解决它
所以需要用到泛型的上下限来解决
泛型的上限,E extend Animal ,即只能有Animal对象或者它的子类进行传入方法,其它的参数类型是不能传入此泛型方法的,相当于Animal就是此方法的上限
public static <T extends Animal> T test(T t){ return t; }
相同的还有泛型的下限,E super Animal,即只能有Animal对象或者它的父类传入方法,其它的类型是传入不了此方法的
public static <T> void test(ArrayList<? super Animal> t){ }
注意点
- 泛型是工作在编译阶段的,一旦程序编译成class文件,class文件中就不存在泛型了,这就是泛型擦除
//.java文件 public static void main(String[] args) { //ArrayList ArrayList<String> list = new ArrayList<>(); list.add("java"); list.add("mysql"); //调用方法 String rs = test(list); System.out.println(rs); } //.class文件 public static void main(String[] args) { ArrayList<String> list = new ArrayList(); list.add("java"); list.add("mysql"); String rs = (String)test(list); System.out.println(rs); }
可以看到.class文件中没有了泛型,都是用的强转来进行数据操作的
- 泛型不支持基本数据类型,只支持对象类型(引用类型)
如上,
对于基本数据类型int,编译器则提示需要转为int的封装类型Interger