黑马程序员——java高新(枚举、内省、注解、类加载器)

时间:2023-02-17 18:34:38

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------



第十八章:java高新(枚举、内省、注解、类加载器)


本章概述:
第一部分:枚举
第二部分:内省
第三部分:注解
第四部分:类加载器








第一部分:枚举


枚举:

关键字: enum

枚举的特点和作用:    
枚举是一个特殊的类,其中的每一个元素都是该类的一个对象或者该类对象的子类。
枚举就是要让某个类型的变量的取值只能为若干固定值之中的一个。
枚举可以让编译器在编译时控制源程序中填写的非法值,普通变量的方式在开发阶段无法实现这一目标。

注意 : 枚举的实例不能用new创建,枚举不能继承其他类,也不能被其他类继承。


用普通类模拟枚举的功能。

    1、构造方法私有化。
    2、枚举元素引用常量化。
    3、枚举类方法抽象化,元素对象匿名子类化,复写父类抽象方法。



代码示例:

//张老师的交通灯枚举示例
public class EnumDemo {
public static void main(String[] args) {
//展示枚举元素的方法
System.out.println(TrafficLamp.RED.nextLamp());
System.out.println(TrafficLamp.RED.nextLamp().getTime());
}
}

enum TrafficLamp{
/*
* 枚举的元素使用的是枚举的子类对象
* 后面跟上大括号复写父类的抽象方法
* 圆括号表示的是构造时的参数
*/
RED(30){
@Override
public TrafficLamp nextLamp() {
return YELLOW;
}
},
YELLOW(40){
@Override
public TrafficLamp nextLamp() {
return GREEN;
}
},
GREEN(45){
@Override
public TrafficLamp nextLamp() {
return RED;
}
};

// 定义枚举类的抽象方法、属性、构造方法设置属性的方法
public abstract TrafficLamp nextLamp();
private int time;
private TrafficLamp(int time){
this.time = time;
}
public int getTime() {
return time;
}
public void setTime(int time) {
this.time = time;
}
}







第二部分:内省



内省是java编程中的一种操作思想,即通过一系列操作实现对javabean类属性、方法等的缺省处理访问,缺省访问就是忽略该类的一些具体细节,将注意力主要集中在符合javaBean规则的内容中。

javaBean类是一种特殊的java类,主要用于传递数据信息,这种java类主要用来访问类中的私有字段,并且类中的方法要符合某些命名规则。

例如:一个类中有一些私有字段,该类为这些私有字段提供了公有的getter/setter方法(又叫存储器方法),那么就可以把这些类当做javaBean类来操作。

javaBean的对象通常叫做值对象(Value Object)

javaBean的属性是根据getter/setter方法来确定的,因为这些被当作javaBean的类中的字段是私有修饰的,所以无法对这些字段进行直接的访问,根本就不知道这些类中的字段的具体命名,只能由公有的操作这些字段的方法getter/setter方法来推断出类中的字段名,所以javaBean中的字段名称跟类中本身的名称没有相关性

一个符合JavaBean特点的类可以当作普通类一样使用,但把它当JavaBean用肯定需要带来一些额外的好处,我们才会去了解和应用JavaBean


好处:

(1)JDK中提供了对JavaBean进行操作的一些API,这套API就称为内省。如果要访问私有的x,用内省这套api操作JavaBean比用普通类的方式更方便。
(2)在Java EE开发中,经常要使用到JavaBean。很多环境就要求按JavaBean方式进行操作。


JDK内省类库:

PropertyDescriptor类:

PropertyDescriptor类表示JavaBean类通过存储器导出一个属性。
主要方法:
1. getPropertyType(),获得属性的Class对象;
2. getReadMethod(),获得用于读取属性值的方法;
    getWriteMethod(),获得用于写入属性值的方法;
3. hashCode(),获取对象的哈希值;
4. setReadMethod(Method readMethod),设置用于读取属性值的方法;
5. setWriteMethod(Method writeMethod),设置用于写入属性值的方法。

Introspector类:
将JavaBean中的属性封装起来进行操作。在程序把一个类当做JavaBean来看,就是调用Introspector.getBeanInfo()方法,得到的BeanInfo对象封装了把这个类当做JavaBean看的结果信息,即属性的信息。
getPropertyDescriptors(),获得属性的描述,可以采用遍历BeanInfo的方法,来查找、设置类的属性。


张老师的代码示例:

package com.itheima;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class ReflectDemo {
public static void main(String[] args) throws Exception {

Reflectpoint pt1 = new Reflectpoint(3, 5);

//使用内省的方式,对符合javaBean的类进行属性访问
//其实该javaBean类中并没有名字为“X”的属性,但在getter/setter方法中我们写的是getX/setX,它就会将对应的属性当成“x”来操作
String propertyName = "x";
PropertyDescriptor pd = new PropertyDescriptor(propertyName, pt1.getClass()); //通过存储器方法导出类中的一个属性
Method readMethod = pd.getReadMethod(); //获取该属性的获取属性方法
Object retVal = readMethod.invoke(pt1); //调用该方法取出属性值
System. out.println(retVal); //结果:3

Method writeMethod = pd.getWriteMethod(); //获取设置属性方法
Object value = 7;
writeMethod.invoke(pt1,value);
System. out.println(pt1.getX()); //结果:7
}
}

//符合javaBean的类,类中有两个带存储器方法的私有类
class Reflectpoint {

private int a ;
private int b ;

Reflectpoint(int a, int b) {

this.a = a;
this.b = b;
}

public int getX() {
return a ;
}
public void setX(int a) {
this.a = a;
}
public int getY() {
return b ;
}
public void setY(int b) {
this.b = b;
}
}








第三部分:注解


定义:
注解是java 的一种类型,它与类、接口、枚举同属一个结构层次,它们都称作为java 的类型(TYPE)。注解可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。它的作用非常的多,如:进行编译检查、生成说明文档、代码分析等。


JDK提供的几个基本注解
1.@SuppressWarnings
该注解的作用是阻止编译器发出某些警告信息。
它可以有以下参数:
deprecation:过时的类或方法警告。
unchecked:执行了未检查的转换时警告。
fallthrough:当Switch 程序块直接通往下一种情况而没有Break 时的警告。
path:在类路径、源文件路径等中有不存在的路径时的警告。
serial:当在可序列化的类上缺少serialVersionUID 定义时的警告。
finally:任何finally 子句不能完成时的警告。
all:关于以上所有情况的警告。
2. @Deprecated
该注解的作用是标记某个过时的类或方法。
3. @Override
该注解用在方法前面,用来标识该方法是重写父类的某个方法。
元注解(修饰注解的注解)
1. @Retention
定义在一个注解类的前面,用来说明该注解的生命周期。
它有以下参数:
RetentionPolicy.SOURCE:指定注解只保留在源文件当中。
RetentionPolicy.CLASS:指定注解只保留到class 文件中。
RetentionPolicy.RUNTIME:指定注解可以保留在程序运行期间。
2. @Target
定义在一个注解类的前面,用来说明该注解可以被声明在哪些元素前。

它有以下参数:

ElementType.PACKAGE:指定注解只能声明在包名前。

ElementType.TYPE:指定注解只能被声明在类前面。
ElementType.FIELD:指定注解只能被声明在类的字段前。
ElementType.METHOD:指定注解只能被声明在类的方法前。
ElementType.PARAMETER:指定注解只能被声明在方法的参数前面。
ElementType.CONSTRUCTOR:指定注解只能声明在类的构造方法前。
ElementType.LOCAL_VARIABLE:指定注解只能声明在局部变量前。
ElementType.ANNOTATION_TYPE:指定注解只能声明在注解类型前。
注解的生命周期
注解可以有三种生命周期,注解的默认生命周期是保留到CLASS 文件,也可以由@Retetion 元注解指定它的生命周期。
(1) java 源文件
当在一个注解类前定义了一个@Retetion(RetentionPolicy.SOURCE)的注解,那么说明该注解只保留在一个源文件当中,当编译器将源文件编译成class 文件时,它不会将源文件中定义的注解保留在class 文件中。
(2) class 文件中
当在一个注解类前定义了一个@Retetion(RetentionPolicy.CLASS)的注解,那么说明该注解只保留在一个class 文件当中,当加载class 文件到内存时,虚拟机会将注解去掉,从而在程序中不能访问。
(3)程序运行期间当在一个注解类前定义了一个@Retetion(RetentionPolicy.RUNTIME)的注解,那么说明该注解在程序运行期间都会存在内存当中。此时,我们可以通过反射来获得 定义在某个类上的所有注解。
注解的定义
一个简单的注解定义:
public @interface Annotation01 {
//定义公共的final静态属性
.....
//定以公共的抽象方法
......
注解可以有哪些成员
注解和接口相似,它只能定义final 静态属性和公共抽象方法。
注解的使用
注解的使用分为三个过程。
定义注解-->声明注解-->得到注解
(1) 定义注解(参照上面的注解定义)
(2) 声明注解
 在哪些元素上声明注解
如果定义注解时没有指定@Target 元注解来限制它的使用范围,那么该注解可以使用在ElementType 枚举指定的任何一个元素前。否则,只能声明在@Target 元注解指定的元素前。
一般形式:
@注解名()
对注解的方法的返回值进行赋值
对于注解中定义的每一个没有默认返回值的方法,在声明注解时必须对它的每一个方法的返回值进行赋值。
一般形式:
@注解名(方法名=方法返回值,、、、、、、)
如果方法返回的是一个数组时,那么将方法返回值写在{}符号里
@注解名(方法名={返回值1,返回值2,、、、、、、},、、、、、、、)
对于只含有value 方法的注解,在声明注解时可以只写返回值。
(3)得到注解
对于生命周期为运行期间的注解,都可以通过反射获得该元素上的注解实例。
声明在一个类中的注解
可以通过该类Class 对象的getAnnotation 或getAnnotations 方法获得。
声明在一个字段中的注解
通过Field 对象的getAnnotation 或getAnnotations 方法获得
声明在一个方法中的注解
通过Method 对象的getAnnotation 或getAnnotations 方法获得







第四部分:类加载器


类加载器:就是加载类的工具。


类名.class.getClassLoader().getClass().getname();获取类加载器名称。

ClassLoader的作用:
ClassLoader是用来加载Class文件到JVM,以供程序使用的。我们知道,java程序可以动态加载类定义,而这个动态加载的机制就是通过ClassLoader来实现的,所以可想而知ClassLoader的重要性如何。

ClassLoader的基本加载流程:
类加载器也是java类,因为其他是java类的类加载器背身也要被类加载其加载。显然必须有第一个类加载器不是java类,这正是bootstrap ClassLoader;所以其他类加载器都是用bootstrap ClassLoader加载的。bootstrap ClassLoader(启动类加载器)是JVM实现的一部分,这个ClassLoader在JVM运行的时候加载java核心的API以满足java程序的最基本需求,其中就包括用户定义的ClassLoader,这里所谓的用户定义是指通过java程序实现的ClassLoader,一个是ExtClassLoader,这个ClassLoader是用来加载java的扩展API的,也就是/lib/ext中的类,一个是AppClassLoader,这个ClassLoader是用来加载用户机器上CLASSPATH设置目录中的Class的,通常在没有指定ClassLoader的情况下,程序员自定义的类就由该ClassLoader进行加载。
当运行一个程序的时候,JVM启动,运行bootstrapClassLoader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,最后AppClassLoader加载CLASSPATH目录下定义的Class,这就是一个程序最基本的加载流程。
类加载器是类的加载工具,主要有三种类加载器 一种是bootstrap ClassLoader 一种是ExtClassLoader  一种是AppClassLoader 

ClassLoader加载的方式:
ClassLoader使用双亲委托模式进行类加载。
当Java虚拟机要加载一个类时,到底派出哪个类加载器去加载呢?
首先当前线程的类加载器去加载线程中的第一个类。如果类A中引用了类B,Java虚拟机将使用加载类A的类装载器来加载类B。 还可以直接调用ClassLoader.loadClass()方法来指定某个类加载器去加载某个类。
每一个自定义ClassLoader都必须继承ClassLoader这个抽象类,而每一个ClassLoader都会有一个parent ClassLoader,ClassLoader这个抽象类中有一个getParent()方法,这个方法用来返回当前ClassLoader的parent,注意,这个parent不是指被继承的类,而是在实例化该ClassLoader时指定的一个ClassLoader,如果这个parent为null,那么就默认该ClassLoader的parent是bootstrap ClassLoader,这个parent有什么用呢?

假设我们自定义了一个ClientClassLoader,我们使用这个自定义的ClassLoader加载java.lang.String,事实上java.lang.String这个类并不会被这个ClientClassLoader加载,而是由bootstrap ClassLoader进行加载,为什么会这样?实际上这就是双亲委托模式的原因,因为在任何一个自定义ClassLoader加载一个类之前,它都会先委托它的父类ClassLoader进行加载,只有当父类ClassLoader无法加载成功后,才会由自己加载,在上面这个例子里,因为java.lang.String是属于java核心API的一个类,所以当使用ClientClassLoader加载它的时候,该ClassLoader会先委托它的父类ClassLoader进行加载,上面讲过,当ClassLoader的parent为null时,ClassLoader的parent就是Bootstrap ClassLoader,所以在ClassLoader的最顶层就是Bootstrap ClassLoader,因此最终委托到Bootstrap ClassLoader的时候,BootstrapClassLoader就会返回String的Class。







本章总结

1、枚举可以看作特殊的类,它只允许创建固定数量的实例,并且可以在编译时控制源程序中填写的非法值,为只需要固定数量实例的应用场景提供了更简单安全的的定义方式和操作方式

2、内省是对类中私有属性的缺省操作方式,可以为javaBean类访问私有属性提供一种更方便的操作方式,内省结合了反射技术

3、注解是一种类型,主要用于程序代码的说明、检查和分析;类加载器是用于加载程序的类的类,了解类加载机制有助于对程序运行的流程分析




------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------