一、枚举(Enum)
1.概述
枚举就是要让某个类型的变量的取值只能为若干个固定值中的一个,否则,编译器就会报错。枚举可以让编译器在编译时就可以控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。
<span style="font-size:18px;">/*
* 用普通类如何实现枚举功能,定义一个Weekday的类来模拟枚举功能。
1、私有的构造方法
2、每个元素分别用一个公有的静态成员变量表示
3、可以有若干公有方法或抽象方法。采用抽象方法定义nextDay就将大量的if.else语句转移成了一个个独立的类。
*/
package cn.itheima;
public abstract class WeekDay {
private WeekDay(){}
public final static WeekDay SUN=new WeekDay(){
public WeekDay nextDay(){
return MON;
}
};
public final static WeekDay MON=new WeekDay(){
public WeekDay nextDay(){
return SUN;
}
};
public abstract WeekDay nextDay();
public String toString(){
return this==SUN?"SUM":"MON";
}
}
</span>
2.枚举类的特点
1)通过关键字enum来定义枚举类。枚举类也是一种特殊形式的Java类。
2)枚举类中声明的每一个枚举值代表枚举类的一个实例对象。
3)与Java中的普通类一样,在声明枚举类时,也可以声明属性、方法和构造函数,但枚举类的构造函数必须是私
有的。
4)枚举类也可以实现接口,或者继承抽象类。
5)JDK5中扩展了switch语句,它除了可以接收int,byte,short,char外,还可以接收一个枚举类型。
6)若枚举类只有一个枚举值,则可以当作单例设计模式使用。
7)枚举类是一个不可被继承的final类,其中的元素都是类静态常量。
3.常用方法
构造器:
1)构造器只是在构造枚举值的时候被调用。
2)构造器只有私有private,绝不允许有public构造器。这样可以保证外部代码无法重新构造枚举类的实例。因为
枚举值是public static final的常量,但是枚举类的方法和数据域是可以被外部访问的。
3)构造器可以有多个,调用哪个即初始化相应的值。
非静态方法:(所有的枚举类都继承了Enum方法)
1)String toString() ;//返回枚举量的名称
2)int ordinal() ;//返回枚举值在枚举类中的顺序,按定义的顺序排
3)Class getClass() ;//获取对应的类名
4) String name();//返回此枚举常量的名称,在其枚举声明中对其进行声明。
静态方法:
1)valueOf(String e) ;//转为对应的枚举对象,即将字符串转为对象
2)values() ;//获取所有的枚举对象元素
<span style="font-size:18px;">//测试枚举的常用方法
@Test
public static void test2(){
//name(),返回名称
System.out.println(Grade.A.name());
//ordinal(),返回枚举值在枚举中所处位置
System.out.println(Grade.A.ordinal());
//valueOf().将字符串转为枚举值
String str = "A";
// Grade g = Grade.valueOf(Grade.class,str);
Grade g = Grade.valueOf(str);
System.out.println(g);
</span>
4.带抽象的枚举
<span style="font-size:18px;">// 如何定义枚举的构造函数、方法和字段,去封装更多的信息。
enum Grade { // class A 100-90优 B 89-80良 C 79-70一般 D 69-60差 E 59-0不及格
A("100-90"){
public String localValue(){
return "优";
}
}
, B("89-80"){
public String localValue(){
return "良";
}
}
, C("79-70"){
public String localValue(){
return "一般";
}
}
, D("69-60"){
public String localValue(){
return "差";
}
}
, E("59-0"){
public String localValue(){
return "不及格";
}
};// Object
private String value;// 封装每个对象对应的分数。
private Grade(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public abstract String localValue();//带抽象的枚举,每个枚举对象都要重写该方法
}
</span>
5.枚举总结
小结:
- 匿名内部类比较常用
- 类的方法返回的类型可以是本类的类型
- 类中可定义静态常量,常量的结果就是自己这个类型的实例对象
- 枚举只有一个成员时,就可以作为一种单例的实现方式。
注:
- 所有的枚举都继承自java.lang.Enum类。由于Java不支持多继承,所以枚举对象不能再继承其他类。
- switch语句支持int,char,enum类型,使用枚举,能让我们的代码可读性更强。
二、泛型(Generic)
1. 概述
没有使用泛型时,不管是什么类型的对象,都可以存入同一个集合中。使用泛型的集合,将存入集合的元素限定为特定的泛型类型,集合中只能存储同一类型的对象,确保只能将正确类型的对象存入集合,提供了类型安全;并且当从集合获取一个对象时,编译器可以知道这个对象的类型,不需要对对象进行强转,这样更方便。编译器编译带类型说明的集合时会擦除类型信息,使运行效率不受影响。
2. 泛型的好处
1)将运行时期的问题ClassCastException转到了编译时期,安全。
2)避免了强制转换的麻烦,方便。
例一:
<span style="font-size:18px;">import java.util.ArrayList;
import java.util.Iterator;
public class GenericDemo {
public static void main(String[] args) {
ArrayList<String> al = new ArrayList<String>();
al.add("abc");
al.add("hehe");
// al.add(4);//al.add(new Integer(4));//会报错。
Iterator<String> it = al.iterator();
while(it.hasNext()){
String str = it.next();
// String str = (String)it.next();//不用再强转。
System.out.println(str);
}
}
}
</span>
例二:使用泛型后,运行时期才报错的内容,在编译时期就能报错。 这样更安全。
<span style="font-size:18px;">import cn.itcast.bean.Student;
import cn.itcast.bean.Worker;
public class GenericDefineDemo3 {
public static void main(String[] args) {
//运行时报错
/*Tool tool = new Tool();
tool.setObject(new Worker());
Student stu = (Student)tool.getObject();*/
//编译时报错
Tool<Student> tool = new Tool<Student>();
tool.setObject(new Student());
Student stu = tool.getObject();
}
}
</span>
3. 什么时候用泛型?
当操作的引用类型不确定的时候,就使用<>,将要操作的引用数据类型传入即可。
<>就是一个用于接收具体引用数据类型的参数范围。在程序中,只要用到了带有<>的类或者接口,就要明确传入具体的引用数据类型。
泛型的格式为:Set<Integer>,意思是限定存入Set集合的元素类型为Integer类型。
泛型技术是给编译器使用的技术,用于编译时期,确保了类型的安全。
4. 泛型的擦除
运行时,会将泛型去掉,生成的class文件不带泛型信息,这就是泛型的擦除。
擦除泛型的原因:为了兼容运行的类加载器。如果保留类型信息,运行时class文件就不能加载到内存中,与类加载器不兼容。
5. 参数化的泛型类型
getClass()方法的返回值和原始类型完全一样。由于编译生成的字节码会去掉泛型的类型信息,只要跳过编译器,就可以往某个泛型集合中加入其他类型的数据,例如,用反射得到集合,再调用其add()方法即可。
代码示例:往泛型类型为Integer的集合中添加String类型的数据
ArrayList<Integer> al = newArrayList<Integer>();
al.getClass().getMethod("add",Object.class).invoke(al,你好");
6. 泛型的补偿
在运行时,通过获取元素的了类型进行转换,不用使用者强制转换。这就是泛型的补偿。
7. 泛型的通配符
通配符:?.通配符表示未知类型。使用通配符的好处就是可以不用明确传入的类型。
这样在使用泛型时,提高了扩展性。
注意:写泛型时,左右两边,泛型要一致。
8. 限定通配符
限定通配符对类型进行了限制。有两种限定通配符:
第一种:<? extends T>。它通过确保类型必须是T或者T的子类来设定类型的上界。
第二种:<? super T>。它通过确保类型必须是T或者T的父类来设定类型的下界。
9. 自定义泛型方法
泛型方法,在返回值前用<T>来定义。将传入的参数进行类型推断,返回参数的类型交集。
泛型的引用只能引用数据类型,基本数据类型不能用泛型。
10. 泛型类型
如果类的实例对象中的方法都要用到同一个泛型参数,即不同的方法引用的泛型类型要保持同一个实际类型时,这时候就要定义泛型类型。
注意:静态方法不能使用泛型。
例三
<span style="font-size:18px;">public class Tool<QQ>{
private QQ q;
public QQ getObject() {
return q;
}
public void setObject(QQ object) {
this.q = object;
}
/**
* 将泛型定义在方法上,称为泛型方法。会自动匹配数据类型。
* @param str
*/
public <w> void show(w str){
System.out.println("show:"+str);
}
public void print(QQ str){
System.out.println("print:"+str);
}
/**
* 当方法静态时,不能访问类上定义的泛型。
* 如果静态方法使用泛型,只能将泛型定义在方法上。
* 注意写法:定义泛型时,在返回值前面,在修饰符后面。
* @param obj
*/
public static <Y>void method(Y obj){
System.out.println("method:"+obj);
}
}
</span>
11. 泛型的重要知识点
1)参数化类型与原始类型的兼容
参数化类型可以引用一个原始类型的对象,编译报告警告。
Collection<String>c = new Vector();
原始类型可以引用一个参数化类型的对象。编译报告警告。
Collection c = newVector<String>();
2)参数化类型不考虑类型参数的继承关系。
Vector<String>v = new Vector<Object>();//报错
Vector<Object>v = new Vector<String>();//报错
3)创建数组实例时,数组元素不能使用参数化类型。
Vector<Integer>vector[] = new Vector<Integer>[10];//报错
运行时泛型数组的类型信息会被擦除,且在运行的过程中数组的类型有且仅有Object[],如果我们强制转换类型的话,虽然在编译的时候不会有异常产生,但是运行时会有ClassCastException抛出。
12. 泛型的一些面试题
1)泛型是如何工作的?什么类型擦除?
泛型是通过类型擦除来实现的,编译器在编译时擦除了所有类型相关的信息,所以在运行时不存在任何类型相关的信息。
例如List<String>在运行时仅用一个List来表示。这样做的目的,是确保能和jdk1.5以前的版本开发的二进制类库兼容。运行时无法访问到类型参数,因为编译器已经把泛型类型转换为原始类型。
2)什么是泛型中的限定通配符和非限定通配符 ?
限定通配符对类型进行了限制。有两种限定通配符,一种是<? extends T>它通过确保类型必须是T的子类来设定类型的上界;另一种是<? super T>它通过确保类型必须是T的父类来设定类型的下界。泛型类型必须用限定内的类型来进行初始化,否则会导致编译错误。
另一方面,<?>表示了非限定通配符,因为<?>可以用任意类型来替代。
3)List<? extends T>和List <? super T>之间有什么区别 ?
List<? extends T>可以接受任何继承自T的类型的List,而List<? super T>可以接受任何T的父类构成的List。
例如List<? extends Number>可以接受List<Integer>或List<Float>。
4)你可以把List<String>传递给一个接受List<Object>参数的方法吗?
不可以。因为List<Object>可以存储任何类型的对象包括String, Integer等等,而List<String>却只能用来存储String。一旦传递,编译会报错。
如List<Object> listObj = newArrayList<String>();//报错
但是,如果分开来写,就不会报错。
List list = new ArrayList<String>();
List<Object> listObj = list;
5)Array中可以用泛型吗?
6)如何阻止Java中的类型未检查的警告?
7)Java中List<Object>和原始类型List之间的区别?
原始类型和带参数类型<Object>之间的主要区别是:
在编译时编译器不会对原始类型进行类型安全检查,却会对带参数的类型进行检查。通过使用Object作为类型,可以告知编译器该方法可以接受任何类型的对象,比如String或Integer。
还有一点区别是,你可以把任何带参数的类型传递给原始类型List,但却不能把List<String>传递给接受List<Object>的方法,因为会产生变异错误。
三、注解
1.概述
注解相当于一种标记,在程序中加了注解就等于为程序打上了某种标记,没加,则没有某种标记。以后,java编译器、开发工具和其他应用程序就可以用反射来了解自己的类及各种元素上有无何种标记,有什么标记,就会做出相应的处理。标记可以加在包、类、字段、方法、方法参数,以及局部变量上等等。在java.lang包中提供了最基本的annotation,即注解。
格式:@注解类名()。如果有属性,则在括号中加上属性名(可省略)和属性值。
2.三种基本注解
1)@SuppressWarning(”deprecation“)--->压制警告
SupressWarning是告知编译器或开发工具等提示指定的编译器警告;
”deprecation”是告知具体的信息即方法已过时。
2)@Deprecated--->提示成员等已经过时,不再推荐使用。
源代码标记@Deprecated是在JDK1.5中作为内置的annotation引入的,用于表明类(class)、方法(method)、字段
(field)已经不再推荐使用,并且在以后的JDK版本中可能将其删除,编译器在默认情况下检测到有此标记的时候会
提示警告信息。
例如:假定之前的某个类升级了,其中的某个方法已经过时了,不能够将过时的方法删除,因为可能会影响到调用此类的这个方法的某些程序,这是就可以通过在方法上加这个注解。
3)@Override--->提示覆盖(父类方法)
加上此注解,,可对自己类中的方法判断是否是要覆盖的父类的方法,典型的例子即在集合中覆盖equals(Object obj)方法,其中的参数类型必须是Object,才能被覆盖,若不是,加上此注解就会提示警告。
3.注解类
格式:@interface名称{statement}
元注解(注解的注解)
一个注解有其生命周期(Retetion)和存放的位置(Taget),这就可以通过元注解说明。
1)Retetion:用于说明注解保留在哪个时期,加载定义的注解之上。
①一个注解的声明周期包含:
java源程序--(javac)-->class文件--(类加载器)-->内存中的字节码
第一、当再源程序上加了注解,javac将java源程序编译为class文件,可能会把源程序中的一些注解去掉,进行相应的处理操作,当我们拿到源程序的时候,就看不到这些注解了。
第二、假设javac把这些注解留在了源程序中(或者说留在了class文件中),当运行此class文件的时候,用类加载器将class文件调入内存中,此时有转换的过程,即把class文件中的注解是否保留下来也不一定。
注意:class文件中不是字节码,只有把class文件中的内部加载进内存,用类加载器加载处理后(进行完整的检查等处理),最终得到的二进制内容才是字节码。
②Reteton(枚举类)取值:
Retetion.Policy.SOURSE:java源文件时期,如@Overried和@SuppressWarning
Retetion.Policy.CLASS: class文件时期(默认阶段)
Retetion.Policy.RUNTIME:运行时期,如@Deprecated
2)Taget:用于说明注解存放在哪些成分上,默认值是任何元素
其值可设置为枚举类ElementType类中的任何一个,包括:包、字段、方法、方法参数、构造器、类等值。取值为:
PACKAGE(包声明)
FIELD(字段声明)
ANNOTATION_TYPE(注释类型声明)
CONSIRUCTOR(构造器声明)
METHOD(方法声明)
PARAMETER(参数声明)
TYPE(类、接口(包含注释类型)或枚举声明)
LOCAL_VARIABLE(局部变量声明)
注意:其中代表类的值是TYPE。因为class、enum、interface和@interface等都是属于Type的。不可用CLASS表示。
4、通过反射查看其它类中的注释:
过程:
第一、注解类:@interface A{}
第二、应用了“注释类”的类:@A class B{}
第三、对“应用注释类的类”进行反射操作的类:class{...},操作如下:
B.class.isAnnotionPresent(A.class);//判断是否存在此注解类
A a = B.class.getAnnotation(a.class);//存在的话则得到这个注释类的对象
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface ItcastAnnotation {}
@ItcastAnnotation()
public class AnnotionTest {
@SuppressWarnings("deprecation")//表示压制警告的注解
@ItcastAnnotation()
public static void main(String[] args) {
System.runFinalizersOnExit(true);
//反射方式查看注解
//检查类上是否有注解
if(AnnotionTest.class.isAnnotationPresent(ItcastAnnotation.class)){
//通过反射获取到注解
ItcastAnnotation annotation = AnnotionTest.class.getAnnotation(ItcastAnnotation.class);
System.out.println(annotation);
}
}
5.添加基本属性
1)属性:
一个注解相当于一个胸牌,但仅通过胸牌还不足以区别带胸牌的两个人,这时就需要给胸牌增加一个属性来区分,如颜色等。
2)定义格式:同接口中的方法一样:String color();
定义缺省格式:String value() default ”ignal”;
3)应用:直接在注解的括号中添加自身的属性,如:
@ItcastAnnotation(color=”red”)
这个和上面的@SuppressWarnings("deprecation")是一样的,其中的"deprecation"就是属性值
①当只有一个属性时,可直接传入属性值。如”red”
②当含有其他属性值的时候,如果那个属性值是缺省的(default),也可以直接传入这个属性值。
6.增加高级属性
1)可以为注解增加的高级属性的返回值类型有:
八种基本数据类型
String类型
Class类型
枚举类型
注解类型
前五种类型的数组
2)数组类型的属性:
定义:int[] arrayArr() default {1,2,3}; -->可不定义默认值
应用:@MyAnnotation(arrayArr={2,3,4}) -->可重新赋值
注:若数组属性中只有一个元素(或重新赋值为一个元素),这时属性值部分可省略大括号。
3)枚举类型的属性:
假设定义了一个枚举类TraffLamp,它是EnumTest的内部类,其值是交通灯的三色。
定义:EnumTest.TrafficLamp lamp();
应用:@MyAnnotation(lamp=EnumTestTrafficLamp.GREEN)
4)注解类型的属性:
假定有个注解类:MetaAnnotation,其中定义了一个属性:String value()
定义:MetaAnnotation annotation() default @MetaAnnotation(”xxx”);
应用:@MyAnnotation(annotation=@MetaAnnotation(”yyy”)) -->可重新赋值
可认为上面的@MetaAnnotation是MyAnnotation类的一个实例对象,同样可以认为上面的@MetaAnnotation是MetaAnnotation类的一个实例对象,调用:
MetaAnnotation ma = MyAnnotation.annotation();
System.out.println(ma.value());
5)Class类型的属性:
定义:Class cls();
应用:@MyAnnotation(cls=ItcastAnnotion.class)
注:这里的.class必须是已定义的类,或是已有的字节码对象
6)基本数据类型的属性(以int为例):
定义:int val() default 3; -->可不定义默认值
应用:@MyAnnotation(val=7) -->可重新赋值
7)注解的详细语法可通过查看java语言规范了解即java Language Specification
//自定义注解类
package cn.itcast.text2;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import cn.itcast.text1.EnumText;
//将定义的注解的生命周期设置在运行时期
@Retention(RetentionPolicy.RUNTIME)
//定义注解的放置位置
@Target({ElementType.TYPE,ElementType.METHOD})
//自定义注解
public @interface ItcastAnnotation {
//定义属性
String str();
int val() default 1;
int[] arr() default {2,3,4};
Class cls() default AnnotionTest.class;
EnumText.TrafficLamp lamp() default EnumText.TrafficLamp.YELLOW;
MetaAnnotation annotation() default @MetaAnnotation("sss");
}
//测试注解类,用反射查看其属性
package cn.itcast.text2;
import cn.itcast.text1.EnumText;
@ItcastAnnotation(annotation=@MetaAnnotation("anntation"),
Lamp=EnumText.TrafficLamp.RED,
arr=7,val=5,str="String",
cls=ItcastAnnotation.class)
public class AnnotionTest {
@SuppressWarnings("deprecation")//表示压制警告的注解
@ItcastAnnotation(str = "yyy")//有缺省值可不用写缺省部分
public static void main(String[] args) {
//反射方式查看注解
//检查类上是否有注解
if(AnnotionTest.class.isAnnotationPresent(ItcastAnnotation.class)){
//通过反射获取到注解
ItcastAnnotation annotation = AnnotionTest.class.getAnnotation(ItcastAnnotation.class);
//打印查看属性值
System.out.println(annotation);
System.out.println(annotation.str());
System.out.println(annotation.val());
System.out.println(annotation.arr().length);
System.out.println(annotation.cls().getName());
System.out.println(annotation.lamp().nextLamp());
System.out.println(annotation.annotation().value());
}
}
}
//定义枚举类,交通灯
package cn.itcast.text1;
public class EnumText {
public static void main(String[] args) {}
//定义交通灯
public enum TrafficLamp{
//定义3个元素,即此类的子类,覆写抽象方法
RED(30){
@Override
public TrafficLamp nextLamp() {return GREEN;}},
GREEN(45){
@Override
public TrafficLamp nextLamp() {return YELLOW;}},
YELLOW(5) {
@Override
public TrafficLamp nextLamp() {return RED;}};
private int time;
//构造方法
private TrafficLamp(int time){this.time = time;}
//抽象方法,转为下个灯
public abstract TrafficLamp nextLamp();
}
}