Java枚举类、注解和反射
本文主要介绍的是枚举类,注解和反射。还有一些基础知识:static,基本数据类型,运算符优先级放在文中,以便查阅复习。
其中牵扯到泛型的部分,可参考本人的另一篇博客:(Collection, List, 泛型)JAVA 集合框架一
1. static 关键字
static
可以修饰的有:属性,方法,代码块,内部类。
1.1 static 修饰属性
按是否用static
修饰分为静态属性和非静态属性(实例变量)。
非静态属性(实例变量):当创建了类的多个对象,每个对象都独立拥有自己的非静态属性。当修改其中一个对象中的非静态属性时,不会改变其他对象中的非静态属性。
静态属性(静态变量):当创建了类的多个对象,多个对象共享同一个静态对象。通过任一个对象改变静态属性,所有对象的静态属性都会发生改变。
- 静态变量随着类的加载而加载。可通过
class.静态变量
进行调用。 - 静态变量的加载早于对象的创建,在创建对象的过程中,才实现实例属性的加载。
- 由于类只会加载一次,则静态变量在内存中也只会存在一份。存在方法区的静态域中。
1.2 static 修饰方法
静态方法:
- 随着类的加载而加载,可以直接通过
类.静态方法
调用 - 静态方法中,只能调用静态的方法或属性,因为它们生命周期相同;非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
注意点:
- 在静态的方法内,不能使用
this
和super
关键字,因为静态方法不需要实例化对象,而没有实例化对象this
就没有意义。一定要是同级的生命周期才能使用。
2. 枚举类
JDK5.0 新增enum
关键字
理解:
- 类的对象只有有限个、确定的,称此类为枚举类
- 当需要定义一组常量时,建议使用枚举类
2.1 基本使用
常用方法:
重点掌握:toString
,values
,valueOf
2.2 enum 实现接口
- 情况一:实现接口,在 enum 类中实现抽象方法
interface Info{
void show();
}
enum Season implements Info{
@Override
public void show() {
System.out.println("hello,world");
}
}
- 情况二:让枚举类的对象分别实现接口中的抽象方法
整体 demo 包含常用方法的使用:
public class EnumTest1 {
public static void main(String[] args) {
Season summer = Season.SUMMER;
System.out.println(summer); //SUMMER
System.out.println(Season.class.getSuperclass()); //class java.lang.Enum
System.out.println(summer.getSeasonFeel()); //hot
Season[] values = Season.values();
for(Object i : values){
System.out.println(i); //SPRING,SUMMER,AUTUMN,WINTER
}
Season winter = Season.valueOf("WINTER"); // 根据字符串返回相应的 enum,错误则出现异常
System.out.println(winter); //WINTER
winter.show();
}
}
interface Info{
void show();
}
enum Season implements Info{
SPRING("spring","warm"){
@Override
public void show() {
}
},
SUMMER(<span class="hljs-string">"summer"</span>,<span class="hljs-string">"hot"</span>){
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> {
}
},
AUTUMN(<span class="hljs-string">"autumn"</span>,<span class="hljs-string">"cool"</span>){
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> {
}
},
WINTER(<span class="hljs-string">"winter"</span>,<span class="hljs-string">"cold"</span>){
<span class="hljs-meta">@Override</span>
<span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">show</span><span class="hljs-params">()</span> {
}
};
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String SeasonName;
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String SeasonFeel;
Season(String seasonName, String seasonFeel) {
SeasonName = seasonName;
SeasonFeel = seasonFeel;
}
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getSeasonName</span><span class="hljs-params">()</span> {
<span class="hljs-keyword">return</span> SeasonName;
}
<span class="hljs-keyword">public</span> String <span class="hljs-title function_">getSeasonFeel</span><span class="hljs-params">()</span> {
<span class="hljs-keyword">return</span> SeasonFeel;
}
}
Thread
中线程状态利用的就是枚举类,可参考源码。
3. 注释和注解
注解(Annotation)是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取,并执行相应的处理。通过使用注解,程序员可以在不改变原有逻辑的情况下,在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或进行部署。JDK5.0 新增
在JavaSE
中注解的使用目的比较简单,比如标记过时功能,忽略警告等。在JavaEE
中占据了更重要的角色。后续博客将会继续JavaEE
的内容。
在一定程度上,框架 = 注解 + 反射 + 设计模式
3.1 类注释举例
/**
*/
类注释必须放在import
语句之后,类定义之前。
3.2 方法注解
除了通用标记之外,还可以使用下面的标记:
@param
变量描述,这个标记将对当前方法的参数部分添加一个条目。这个描述可以占据多行,并可以使用 HTML 标记,一个方法的所有变量描述需要放在一起。
@return
描述
@throws
类描述,表示这个方法有可能抛出异常。
3.3 通用注解
注意,一定要用 # 分隔类名和方法名,或类名和变量名。
3.4 自定义注解
//1
public @interface MyAnnotation {
String value();
}
@MyAnnotation(value = "hello") // 使用时
//2
public @interface MyAnnotation {
String value() default "hello";
}
@MyAnnotation // 默认值 "hello",可覆盖
- 注解声明为:@interface
- 内部定义成员,通常用
value
表示 - 可以指定成员的默认值,使用
default
定义 - 如果自定义注解里没有成员,表明是一个标识作用
具体用途在反射和框架中。自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
3.5 JDK 提供的 4 种元注解
元注解:用于修饰其他注解定义
例:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
JDK5.0 提供了 4 个标准的元注解:
Retention
, Target
, Documented
, Inherited
自定义注解通常都会使用以上两个元注解 Retention, Target。
3.6 JDK8 中注解的新特性
可重复注解
Repeatable()
@Repeatable(MyAnnotations.class)
public @interface MyAnnotation {
String value() default "hello";
}
public @interface MyAnnotations {
MyAnnotation[] value();
}
其中,MyAnnotation 和 MyAnnotations 需要保持 Retention 和 Target 以及 Inherited 一致,在这部分只需要注意,在以后用到时再重点讲解。
类型注解
4. 反射
4.1 反射的基本概念
反射机制提供的功能:
- 运行时判断任意一个对象所属的类
- 运行时构造任意一个类的对象
- 运行时判断任意一个类所具有的成员变量和方法
- 运行时获取泛型信息
- 运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
关于 java.lang.Class 类的理解:
- 类的加载过程:程序经过 javac.exe 命令后(编译),会生成一个或多个字节码文件(.class 文件)。接着使用 java.exe 命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到内存中,此过程成为类的加载。加载到内存中的类,称为运行时类,此运行时类,作为 Class 的一个实例。
- Class 的实例对应着一个运行时类
- 加载到内存中的运行时类,会缓存一定的时间。在此时间内,可以通过不同方式来获取 Class 的实例。
- Object 是所有类的父类,而 Class 是类的类。
三种 Class 实例的获取方式:
获取当前对象的类型对象,类型类是指代表一个类型的类,获取class
对象的方法有:1. 类名.class,所有的引用数据类型和基本数据类型都可以通过这个方式获取。2. 通过对象实例.getClass() 来获得class
对象。3. 调用 Class 的静态方法 Class.forName(String classPath)
例:
// 调用运行时类的属性, .class
Class<Person> clazz1 = Person.class;// 泛型可省略
System.out.println(clazz1); //class demo1.Person
// 通过运行时类的对象
Person p1 = new Person();
Class clazz2 = p1.getClass();
System.out.println(clazz2);
// 调用 Class 的静态方法 Class.forName(String classPath)
Class clazz3 = Class.forName("demo1.Person");
System.out.println(clazz3);
此外,还可以使用类的加载器ClassLoader
,不再详细赘述。
JDK8 中对于自定义类的ClassLoader
属于系统类加载器,此外还有扩展类加载器和引导类加载器。
简述下方的Person
类:内部成员变量为 public String name, private int age, 有公有构造器和私有构造器,有公有方法 show()和私有方法 show1(), 重写 toString。
基本应用 demo:
Class clazz = Person.class; //public age,private name
// 通过反射,创建 Person 类的对象
Constructor cons = clazz.getConstructor(String.class,int.class);
Object obj = cons.newInstance("Tom",12);
Person p = (Person) obj;
System.out.println(p.toString()); //Person{name='Tom', age=12} 调用了 Person 类中的 toString 方法
System.out.println(cons); //public demo1.Person(java.lang.String,int)
// 通过反射,调用对象指定的属性,方法
Field age = clazz.getDeclaredField("age");
age.set(p,10);
System.out.println(p.toString()); //Person{name='Tom', age=10}
Method show = clazz.getDeclaredMethod("show");
show.invoke(p); //sout("hello")
需要注意的是Class
对象并不能指明该类型的实例化,需要在Field
或Method
这种将实例放入参数中。
4.2 反射调用私有结构
通过反射,可以调用 Person 类的私有结构,如:私有构造器,私有方法,私有属性。
// 调用私有构造器
Constructor cons1 = clazz.getDeclaredConstructor(String.class);
cons1.setAccessible(true);
Person p1 = (Person)cons1.newInstance("tim");
System.out.println(p1); //Person{name='tim', age=0}
// 调用私有属性
Field name = clazz.getDeclaredField("name");
name.setAccessible(true);
name.set(p1,"Tim");
System.out.println(p1); //Person{name='Tim', age=0}
// 调用私有方法
Method show1 = clazz.getDeclaredMethod("show1",String.class);
show1.setAccessible(true);
show1.invoke(p1,"H"); //world H,相当于 p1.show1("H");
//.invoke 返回的是 object,强制转换
String str = (String)show1.invoke(p1,"H");
System.out.println(str); //H,p1.show1(str) 返回了 str。
这里和面向对象概念中的封装性可能有冲突,为什么要利用反射呢?
举个例子:反射具有动态性的特征。后台中,服务器的程序一直运行,假如从前端传来信息,后台就可以动态进行调用。动态过程中,可以利用反射进行应用。
4.3 反射的相关操作
通过反射创建运行时类的对象
Class<Person> clazz = Person.class;// 使用泛型声明后下方不用强制转换,Person 有 public 的空参构造器
Person obj = clazz.newInstance();//Person{name='null', age=0},内部其实调用了运行时类的空参构造器
System.out.println(obj);
获取运行时类的完整结构
Class clazz = Person.class;
// 获取属性结构
//.getFields() 获取当前运行时类及其父类中声明为 Public 访问权限的属性
Field[] fields = clazz.getFields(); //public int demo1.Person.id, public double demo1.Creature.weight,只有 public 属性
for(Field f : fields){
System.out.println(f);
}
//.getDeclaredFields() 获得当前运行类中声明的所有属性(不包含父类)
Field[] declaredfields = clazz.getDeclaredFields();// 省略为:name,age,id
for(Field f : declaredfields){
System.out.println(f);
}
Field[] declaredfields = clazz.getDeclaredFields();
for(Field f : declaredfields){
//1. 权限修饰符
int modifier = f.getModifiers(); // 修饰符以 int 表示,Modifier 类中有相关代码
System.out.println(Modifier.toString(modifier)); // 这样的话就可以正常显示 public,private 等了
// 数据类型
Class type = f.getType();
System.out.println(type.getName()); // 也可直接用 type
// 变量名
System.out.println(f.getName());
}
获得方法结构:
Class clazz = Person.class;
// 获取当前运行时类和父类的 Public 方法
Method[] methods = clazz.getMethods();
for(Method m : methods){
System.out.println(m);
}
// 获取当前运行时类声明的所有方法(不包含父类)
Method[] methods1 = clazz.getDeclaredMethods();
for(Method m : methods1){
System.out.println(m);
}
获取方法的内部结构:
Method[] methods = clazz.getDeclaredMethods();
for(Method m : methods){
// 获取方法声明的注解
Annotation[] anno = m.getAnnotations();
for(Annotation i : anno){
System.out.println(i);
}
// 得到每个方法的权限修饰符
System.out.println(Modifier.toString(m.getModifiers()));
// 返回值类型
System.out.println(m.getReturnType().getName());
// 方法名
System.out.println(m.getName());
// 形参列表
Class[] paras = m.getParameterTypes();
// 抛出的异常
Class[] exs = m.getExceptionTypes();
}
构造器等都类似,不再赘述。构造器的.getConstructors 和.getDeclaredConstructors 不能获取父类的结构,没有意义。
此外还可获取运行时类的父类和父类的泛型,运行时类的接口,包,注释等,代码比较机械,不再赘述。
4.4 调用运行时类的指定结构
调用运行时类的属性:
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
//.getField 只能获取 public,获取其他的话需要用.getDeclaredField()
// 此外,假如下方是获取所有属性,则需要继续扩展权限,调用 id.setAccessible(true);
Field id = clazz.getField("id");
// 设置当前属性的值
id.set(p,12);
int i = (int)id.get(p); // 返回 object,需要强转
System.out.println(i);
调用运行时类中指定的方法:
Class clazz = Person.class;
Person p = (Person) clazz.newInstance();
// 获取指定的某个方法
Method m = clazz.getDeclaredMethod("show1",String.class,String.class);
m.setAccessible(true);
// 注意: .getDeclaredMethod,.invoke 均需要两个参数
// 需要注意的是如果方法有多个参数,需要全部标出来
m.invoke(p,"hello","hi"); // 返回一个 Object,可强转方法的返回类型
// 调用静态方法
//private static void show1()
Method m = clazz.getDeclaredMethod("show1");
m.setAccessible(true);
m.invoke(Person.class);// 写 null 也可以,不影响
如果调用的运行时类的方法没有返回值,则返回null
调用运行时类中指定的构造器:
Class<Person> clazz = Person.class;
Constructor cons = clazz.getDeclaredConstructor(String.class);
cons.setAccessible(true);
Person p = (Person) cons.newInstance("hi"); //Person{name='hi', age=0}
System.out.println(p);
需要注意的是即使 Class 加上泛型声明,下方的 Constructor.newInstance 仍需强转
接下来是最近刷题时总结了一些基础知识,需要对这些数据保持敏感。
5. 基本数据类型
byte
:
- 1 个字节,8 位、有符号的,以二进制补码表示的整数;
- 最小值是 -128(-2^7);
- 最大值是 127(2^7-1);
- 默认值是 0;
- byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
- 例子:byte a = 100,byte b = -50。
short
:
- 2 个字节,16 位、有符号的以二进制补码表示的整数
- 最小值是 -32768(-2^15);
- 最大值是 32767(2^15 - 1);
- Short 数据类型也可以像 byte 那样节省空间。一个 short 变量是 int 型变量所占空间的二分之一;
- 默认值是 0;
- 例子:short s = 1000,short r = -20000。
int
:整数型,4 个字节 32 位,负数以补码形式存在,取值范围如下:
- 最小值是 -2,147,483,648(-2^31);
- 最大值是 2,147,483,647(2^31 - 1);
- 默认值为0
long
:
- 8 个字节, 64 位、有符号的以二进制补码表示的整数;
- 最小值是 -9,223,372,036,854,775,808(-2^63);
- 最大值是 9,223,372,036,854,775,807(2^63 -1);
- 这种类型主要使用在需要比较大整数的系统上;
- 默认值是 0L;
- 例子: long a = 100000L,Long b = -200000L。
"L" 理论上不分大小写,但是若写成 "l" 容易与数字 "1" 混淆,不容易分辩。所以最好大写。
float
:
- float 数据类型是单精度、32 位、符合 IEEE 754 标准的浮点数;
- float 在储存大型浮点数组的时候可节省内存空间;
- 默认值是 0.0f;
- 浮点数不能用来表示精确的值,如货币;
- 例子:float f1 = 234.5f。
double
:
- double 数据类型是双精度、64 位、符合 IEEE 754 标准的浮点数;
- 浮点数的默认类型为 double 类型;
- double 类型同样不能表示精确的值,如货币;
- 默认值是 0.0d;
boolean
:
- boolean 数据类型表示一位的信息;
- 只有两个取值:true 和 false;
- 这种类型只作为一种标志来记录 true/false 情况;
- 默认值是 false;
- 例子:boolean one = true。
char
:
- char 类型是一个单一的 16 位 Unicode 字符;
- 最小值是 \u0000(左方是 16 进制表示,十进制等效值为 0);
- 最大值是 \uffff(即为 65535);
- char 数据类型可以储存任何字符;
- 例子:char letter = 'A';。
6. 运算符优先级
__EOF__