Java泛型,枚举,注解
1 泛型
1.1 什么是泛型
泛型:即参数化类型。在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型。允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时确定。
集合类在声明阶段不能确定这个容器是存储什么类的对象,在JDK1.5之前只能把元素的类型设计为Object,JDK1.5之后采用泛型类解决。这个时候除了元素的类型不确定其他部分是确定的,如元素如何保存,如何管理等,所以把元素的类型设计成一个参数,这个类型参数就是泛型。常见的List<E>,ArrayList<E>中的<E>就是泛型。
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
为什么要有泛型呢 ,解决集合中的两个问题:(1)元素存储的安全问题(添加元素时,检查添加元素的类型),比如商品标签不会搞混;(2)元素的类型转换,问题泛型可以起到对类的限定作用,获取数据元素时,就不需要类型强制转换 。
1.2 泛型的特性
泛型只有在编译阶段有效(在编译期正确检验泛型结果后,将泛型的相关信息擦除,并在对象进入和离开方法的边界处添加类型检查和类型装换方法),不会进入到运行时阶段
List<String> string1 = new ArrayList<String>();
List<Integer> integer1 = new ArrayList<Integer>();Class classString1 = string1.getClass();
Class classInteger1 = integer1.getClass();if(classString1.equals(classInteger1)){System.out.println("类型相同")
}
//输出结果为:类型相同
1.3 泛型的使用
泛型类:用于类的定义中
//此处T为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型,在实例化泛型类时,必须指定T的具体类型
public class Dao<T> {//name这个成员变量的类型为T,T的类型由外部指定private T name;//泛型构造方法形参的类型也为T,T的类型由外部指定public Dao(T name) {this.name = name;}//添加,方法的形参值类型为T,T的类型由外部指定public void add(T u) {//添加数据 }//查询多个public List<T> query() {//查询数据库return null;}//修改public boolean modificate(T u) {//修改操作return true;}
}
在使用泛型的时候传入泛型实参,根据传入的实参做相应的限制;如果不传入泛型的实参,在泛型类中使用泛型的方法或成员变量定义的类型可以为任意类型。
Dao d1 = new Dao("String");
Dao d2 = new Dao(9);
Dao d3 = new Dao(6.6);
注意:泛型的类型参数只能是类类型,不能是简单类型;不能对确切的泛型类型使用instanceof操作。
if(values instanceof List<T>) { //这是错误的,应该是(values instanceof List)List<T> list = (List<T>)values;return list;
}
泛型接口:泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中
public interface Genericity<T> {//添加void add(T u);//查询多个List<T> getUser();//修改boolean updateUser(T u);}
泛型方法:调用方法,决定参数返回值或类型。格式:[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称])
泛型类,是在实例化类的时候指明泛型的具体类型;泛型方法,是在调用方法的时候指明泛型的具体类型 。
public class Dao<T>{String T name;//定义泛型方法 //public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。//)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。public <T> E[] show(T[] e) {return e;}//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。public <E> void show_3(E t){System.out.println(t.toString());}public <S,E> E show3(S s,E e) {return e;}/** * 这才是一个真正的泛型方法。* 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T* 这个T可以出现在这个泛型方法的任意位置.* 泛型的数量也可以为任意多个 * 如:public <T,K> K showKeyName(Generic<T> container){* ...* }*///虽然在方法中使用了泛型,但是这并不是一个泛型方法。这只是类中一个普通的成员方法,只不过他的 返回值是在声明泛型类已经声明过的泛型。所以在这个方法中才可以继续使用 T 这个泛型。public T getName(){return Name;}//这个方法显然是有问题的,在编译器会给我们提示这样的错误信息"cannot reslove symbol E"因为在类的声明中并未声明泛型E,所以在使用E做形参和返回值类型时,编译器会无法识别。public E setName(E Name){this.Name= Name;}*/}//这也不是一个泛型方法,这就是一个普通的方法,只是使用了Dao<Number>这个泛型类做形参而已。public void showNameValue1(Dao<Number> obj){}//这也不是一个泛型方法,这也是一个普通的方法,只不过使用了泛型通配符?//同时这也印证了泛型通配符章节所描述的,?是一种类型实参,可以看做为Number等所有类的父类public void showKeyName2(Dao<?> obj){}/*** 这个方法是有问题的,编译器会为我们提示错误信息:"UnKnown class 'E' "* 虽然我们声明了<T>,也表明了这是一个可以处理泛型的类型的泛型方法。* 但是只声明了泛型类型T,并未声明泛型类型E,因此编译器并不知道该如何处理E这个类型。public <T> T showKeyName(Dao<E> container){...} */}
泛型方法与可变参数
public <T> void printMsg( T... args){for(T t : args){System.out.println("t is " + t);}
}
静态方法与泛型。如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。
public class StaticGenerator<T> {/*** 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。* 如:public static void show(T t){..},此时编译器会提示错误信息:"StaticGenerator cannot be refrenced from static context"*/public static <T> void show(T t){}
}
1.4 泛型通配符
Ingeter
是Number
的一个子类,但是List<Ingeter
>和List<Number
>是平行关系,List<Ingeter
>不能看做是List<Number
>的子类。因此通配符产生了。类型通配符一般是使用?代替具体的类型实参,而不是类型形参 。可以把?看成所有类型的父类。是一种真实的类型。
1.5 泛型继承关系
(1)List<?> 是List<A>,List<B>等类型的父类,List<A>和List<B>是平行关系
(2)List<?> 可以遍历,但不可以修改
(3)List<? extends Number> 可以小于或等于Number
(4)List<? super Number> 可以大于或等于Number
2 枚举
枚举的全称为 enumeration,用于创建有限个对象。创建枚举类型要使用 enum 关键字。枚举类型符合通用模式 Class Enum<E extends Enum<E>>
,而 E
表示枚举类型的名称。枚举类型的每一个值都将映射到 protected Enum(String name, int ordinal)
构造函数中,在这里,每个值的名称都被转换成一个字符串,并且序数设置表示了此设置被创建的顺序。
在什么情况下适合用枚举:在一个类的对象有限且固定的时候,如星期:Monday(星期一)、......、Sunday(星期天) 。当需要定义一组常量时,强烈建议使用枚举类
2.1 枚举类
枚举类的实现,JDK1.5之前需要自定义枚举类。JDK 1.5 新增的 enum 关键字用于定义枚举类 。
为什么不用静态常量代替枚举?(1)类型不安全,如定义一个季节的int类型的静态常量,开发者可以传入任何int类型的参数,如果是枚举类型的话就只能传入枚举类型的类中包含的对象;(2)没有命名空间,开发者要在命名的时候加个见名知意的前缀才知道这四个常量分别代表季节。
若枚举只有一个对象, 则可以作为一种单例模式的实现方式。枚举类对象的属性不应允许被改动, 所以应该使用 private final 修饰。枚举类的使用 private final 修饰的属性应该在构造器中为其赋值。若枚举类显式的定义了带参数的构造器, 则在列出枚举值时也必须对应的传入参数
(1)自定义枚举类
/*** 步骤:* 1 私有化类的构造器,保证不能在类的外部创建其对象* 2 在类的内部创建枚举类的实例。声明为:public static final* 3 对象如果有实例变量,应该声明为private final,并在构造器中初始化*/class TestEnum {private final String name;private TestEnum(String name) {this.name = name;}public static final TestEnum SPRING = new TestEnum("春天");public static final TestEnum SUMMER = new TestEnum("夏天");public static final TestEnum AUTUMN = new TestEnum("秋天");public static final TestEnum WINTER = new TestEnum("冬天");
}
(2)使用enum定义枚举类
注意:①使用 enum 定义的枚举类默认继承了 java.lang.Enum类,因此不能再 继承其他类;②枚举类的构造器只能使用 private 权限修饰符 ;③枚举类的所有实例必须在枚举类中显式列出(, 分隔 ; 结尾)。列出的 实例系统会自动添加 public static final 修饰;④必须在枚举类的第一行声明枚举类对象。
枚举类可以定义属性和方法,可以是静态的也可以是非静态的。例子如下
public enum TestEnum {SPRING("春季"),SUMMER("夏季"),FALL("秋季"),WINTER("冬季");private final String name; private TestEnum(String name){this.name = name;}public String getName() {return name;}
}
注意:(1)第一行写枚举类实例的时候,默认是调用了构造器的;(2)构造器需定义成私有的,就不能在在其他类中生成该对象;(3)枚举类应该要设计成不可变类,所以字段用private final修饰。
2.2 枚举实现接口
枚举实现接口与普通类是一样的,可以实现一个也可以实现多个,需要重写接口中的方法,如果没有完全实现,需要定义成抽象的,可以不需要显示添加abstract修饰,系统会默认加上。
public enum TestEnum implements Paly {SPRING("春季"),SUMMER("夏季"),FALL("秋季"),WINTER("冬季");private final String name; private TestEnum(String name){this.name = name;}public String getName() {return name;}@Overridepublic void setPaly() {System.out.println("实现接口");}
}
interface Paly { public void setPaly();
}
上述代码只是为了说明枚举的功能,重点在下面的代码。枚举的常量值本质就是枚举对象,那么我们可以采用匿名内部类。
public enum TestEnum implements Paly {SPRING(){@Overridepublic void setPaly() {System.out.println("春季踏青");} },SUMMER(){@Overridepublic void setPaly() {System.out.println("夏季游泳");} },FALL(){@Overridepublic void setPaly() {System.out.println("秋季收获");} },WINTER(){@Overridepublic void setPaly() {System.out.println("冬季堆雪人");} };
}
interface Paly { public void setPaly();
}
2.3 Switch语句中的枚举
Java5新增了enum关键字,同时扩展了switch,case表达式中直接写入枚举值,不需加入枚举类作为限定。
public class TestEnumSwitch {public void play(TestEnum t){switch(t){case SPRING:System.out.println("春");break;case SUMMER:System.out.println("夏");break;case FALL:System.out.println("秋");break;case WINTER:System.out.println("冬");break;}}
}
3 注解
从 JDK 5.0 开始, Java 增加了对元数据(MetaData) 的支持, 也就是Annotation(注解) 。
什么是注解:Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。简单的理解为:代码的一个特殊标记,可以在编译,加载,运行时读取,程序可以在不改变原有的代码的基础上可以在文件嵌入补充信息。
注解可以修饰:包,类,方法,构造器,参数,局部变量。这些信息被保存在 Annotation的“name=value” 对中。
3.1 注解的定义
注解通过 @interface
关键字进行定义,和 classs 和 interface 一样,注解也属于一种类型。自定义注解自动继承了java.lang.annotation.Annotation接口。
Annotation 的成员变量在 Annotation 定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class类型、enum类型、Annotation类型、以上所有类型的数组。
public @interface MyAnnotation {
}
注解的的使用方法是在类定义的地方的上面加上@MyAnnotation,如下
@MyAnnotation
public class Test {
}
注解的属性也叫做成员变量。注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。注解中属性可以有默认值,默认值需要用 default 关键值指定
@Target({TYPE, FIELD, METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {String name();int age() defalut 20;
}
随属性赋值的方式是在注解的括号内以 value="" 形式,多个属性之前用 ,隔开。如下
@MyAnnotation(name="aa",age=18)
public class Test {}
注意:如果一个注解内仅仅只有一个名字为 value 的属性时,应用这个注解时可以直接接属性值填写到括号内。要是没有任何属性的话可以把括号省略
public @interface MyAnnotation {String name();
}@MyAnnotation("cc")
public class Test{
}
------------------------------------public @interface MyAnnotation {
}@MyAnnotation
public class Test{
}
3.2 常见注解使用案例
(1)生成文档相关的注解
@author | 标明开发该类模块的作者,多个作者之间使用,分割 |
@version | 标明该类模块的版本 |
@see | 参考转向,也就是相关主题 |
@since | 从哪个版本开始增加的 |
@param | 对方法中某参数的说明,如果没有参数就不能写 |
@return | 对方法返回值的说明,如果方法的返回值类型是void就不能写 |
@exception | 对方法可能抛出的异常进行说明,如果方法没有用throws显式抛出的异常就不能写其中 |
@param @return 和 @exception 这三个标记都是只用于方法的。@param的格式要求:@param 形参名形参类型 形参说明 @return 的格式要求:@return 返回值类型返回值说明@exception的格式要求:@exception 异常类型异常说明@param和@exception可以并列多个
/*** @author zs* @version 1.0* @see String*/
public class DocAnnotation {/*** @param args String[] 命令行参数*/public static void main(String[] args) {}/**** @param name 名称* @return String*/public static String getName(String name){return "My name is "+name;}
}
(2)在编译时进行格式检查(JDK内置的三个基本注解)
@Override | 限定重写父类方法, 该注解只能用于方法 |
@Deprecated | 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择 |
@SuppressWarnings | 抑制编译器警告 |
public class AnnotationTest {public static void main(String[] args) {@SuppressWarnings("unused")String str = "zs";}@Deprecatedpublic void deprecatedMethod(){System.out.println("已过时");}@Overridepublic String toString(){return "重写";}
}
(3)跟踪代码依赖性,实现替代配置文件功能
Servlet3.0提供了注解(annotation),使得不再需要在web.xml文件中进行Servlet的部署
@WebServlet("/login")
public class LoginServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.doGet(req, resp);}@Overrideprotected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {super.doPost(req, resp);}
}
代替
<servlet><servlet-name>LoginServlet</servlet-name><servlet-class>com.servlet.LoginServlet</servlet-class>
</servlet><servlet-mapping><servlet-name>LoginServlet</servlet-name><url-pattern>LoginServlet</url-pattern>
</servlet-mapping>
3.3 元注解
元注解就是可以注解到注解上的注解,也可以理解为元注解是一种基本注解,但是它可以运用到其他注解上。
元注解有:@Target,@Retention,@Documented,@Inherited,@Repeatable 5种
(1)@Target:target是目标的意思,@Target指定注解可以运用的地方。他的取值如下:
例:@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) | |
ElementType.TYPE | 给一个类型进行注解,比如类、接口、枚举 |
ElementType.FIELD | 可以给属性进行注解 |
ElementType.METHOD | 可以给方法进行注解 |
ElementType.PARAMETER | 可以给一个方法内的参数进行注解 |
ElementType.CONSTRUCTOR | 可以给构造方法进行注解 |
ElementType.LOCAL_VARIABLE | 可以给局部变量进行注解 |
ElementType.ANNOTATION_TYPE | 可以给一个注解进行注解 |
ElementType.PACKAGE | 可以给一个包进行注解 |
(2)@Retention:retention的意思为保留,@Retention指定注解的存活时间,他的取值如下
例:@Retention(RetentionPolicy.SOURCE) | |
RetentionPolicy.SOURCE | 注解只在源码阶段保留,在编译器进行编译时它将被丢弃忽视。 |
RetentionPolicy.CLASS | 注解只被保留到编译进行的时候,它并不会被加载到 JVM 中。 |
RetentionPolicy.RUNTIME | 注解可以保留到程序运行的时候,它会被加载进入到 JVM 中,所以在程序运行时可以获取到它们。 |
(3)@Documented:他的作用是能够将注解中的元素包含到 Javadoc 中去。
(4)@Inherited:Inherited的意思为继承。他的作用是:如果一个超类被一个被@Inherited注解注解过的注解进行注解的话,如果子类没有被任何注解应用,子类就继承了超类的注解。
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Inherit {}@Inherit
public class A {}
public class B extends A {}//注解Inherit被@Inherited 修饰,之后类A被Inherit注解,类B继承A,类B也拥有Inherit这个注解。
(5)@Repeatable:repeatable是可重复的意思,jdk1.8才加进来的。在需要对同一种注解多次使用时,往往需要借助@Repeatable。比如人有多中身份把身份当成注解
//先声明一个Persons类用来包含所有的身份
@interface Persons {Person[] value();
}//@Repeatable 注解了 Person。而 @Repeatable 后面括号中的类相当于一个用来保存该注解内容的容器。
@Repeatable(Persons.class)
@interface Person{String role default "";
}//声明一个People类,给该类加上一些身份。
@Person(role="son")
@Person(role="father")
@Person(role="CEO")
public class People{}
3.3 java预置的注解
(1)@Deprecated:用来标记过时的元素。
(2)@Override:提示子类要复写父类中被 @Override 修饰的方法;业可以做检查,编译器可以给你验证@Override下面的方法名是否是你父类中所有的,如果没有则报错比如你如果没写@Override而你下面的方法名又写错了,这时你的编译器是可以通过的。
(3)@SuppressWarnings:阻止警告
(4)@SafeVarargs:参数安全类型注解。它的目的是提醒开发者不要用参数做一些不安全的操作,它的存在会阻止编译器产生 unchecked 这样的警告。
(5)@FunctionalInterface:函数式接口注解,jdk1.8新特性
3.4 注解的提取
注解通过反射获取。首先可以通过 Class 对象的 isAnnotationPresent() 方法判断它是否应用了某个注解;然后通过 getAnnotation() 方法来获取 Annotation 对象(返回指定类型的注解);或者是 getAnnotations() 方法(返回注解到这个元素上的所有注解);
3.5 自定义注解
public class TestPerson {public static void main(String[] args) {Test1.show(Test1.class);}
}
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
@interface Person{String name() default "aa"; //default "aa" 默认值 int age() default 18; //default 18 默认值
}
@Person(name="li",age=20)
class Test1{public static void show(Class c) {System.out.println(c.getName()); //Test1类的全路径Person p =(Person)c.getAnnotation(Person.class);System.out.println("name:"+p.name()+",age:"+p.age());}
}