Java SE 学习笔记-Day 09
1 注解基本概念
- 注释(comment):给人看的
- 注解(Annotation):不是程序本身,可以对程序做出解释,给程序看的,同时可以被其他程序读取。
- 以"@注释名"存在的,如@Override
- 注解可以附加在package,class,method,filed等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制实现对这些元数据的访问
2 内置注解
@Override:重写的注解
@Deprecated:不推荐使用,但是可以使用,或许存在更好的方式
@SuppressWarnings:用来抑制编译时的警告信息。这个注释需要添加一个已经定义好了的参数。
元注解:负责注解其他注解的注解
- @Target: 用于描述被注解的注解的使用范围
- @Retention: 表示需要在什么级别保存该注释信息,描述注解的生命周期
- source < class <runtime,一般都使用runtime,让运行时,注解也存在
- @Documented: 说明该注解包含在javadoc中
- @Inherited: 说明子类可以继承父类中的该注解
自定义注解:使用@interface自定义一个注解,其将自动继承java.lang.annotation.Annotation接口
-
注解参数定义格式:
参数类型 + 参数名(); -
可以使用
default指定一个默认值,当有默认值时,使用注解可以不用显示传参,没有时,一定要给定参数 -
只有一个参数时,参数命名为value,就可以在使用时,不用写
value =,直接写实参即可
@Explain(a = 3)
@Another("可以省略写value =")
public class MyAnnotation {
}
// 定义一个注解
// Target 表示我们的注解可以用在哪些范围,通过枚举类型
@Target(value = {ElementType.TYPE,ElementType.METHOD})
// Retention 表示注解的生命周期,一般使用runtime,可以被反射读取
@Retention(value = RetentionPolicy.RUNTIME)
// Documented 表示注解可以被生成javadoc
@Documented
// Inherited 表示注解可以被子类继承
@Inherited
@interface Explain{
String name() default "";
int a();
String[] mans() default {"Bob","Peter"};
}
@Target(value = {ElementType.TYPE,ElementType.METHOD})
@Retention(value = RetentionPolicy.RUNTIME)
@interface Another{
String value();
}
3 反射概述
动态语言:在运行过程中也能改变程序结构,例如新的函数、对象、数据类型
静态语言:程序编译之后不可改变,如C,C++,Java
Java不是动态语言,是“准动态语言”。由于有了反射机制Java程序可以获得类似动态语言的特性。
Class类
Java中的每一个类在加载后都存在一个且只有一个Class类型的对象,一个Class对象包含了某些特定的结构,如属性,方法,构造器,父类等信息。
创建Class的方式
Person person = new Student();
// 方法一:通过对象获得
Class c1 = person.getClass();
// 方法二:通过Class.forname(),传入包名+类名获得
Class c2 = Class.forName("reflection.Student");
// 方法三:通过类名.class获得
Class c3 = Student.class;
// 方法四:通过基本内置类型的包装类的TYPE属性获得
Class c4 = Integer.TYPE;
// 方法五:获得父类类型
Class c5 = c1.getSuperclass();
Java内存模型
方法区是一个特殊的堆
注意:这里的class是指类型信息,不是Class类对象,Class类对象在堆中。
类型信息是一个java类的描述信息(class mate),classloader加载一个类时从class文件中提取出来并存储在方法区。它包括以下信息:
- 类型的完整有效名,类型的修饰符(public,abstract, final的某个子集),类型直接接口的一个有序列表及继承的父类。
类型名称在java类文件和jvm中都以完整有效名出现。在java源代码中,完整有效名由类的所属包名称加一个”.”,再加上类名组成。例如,类Object的所属包为java.lang,那它的完整名称为java.lang.Object,但在类文件里,所有的”.”都被斜杠“/”代替,就成为java/lang/Object。完整有效名在方法区中的表示根据不同的实现而不同。
-
类型的常量池( constant pool):常量池里面包含了实际的常量,也含有一些方法、类的符号引用
-
域(Field)信息
-
方法(Method)信息
-
除了常量外的所有静态(static)变量
-
classloader的引用
Classloader加载一个类并把类型信息保存到方法区后,会创建一个Class对象,存放在堆区的,不是方法区
类的加载与ClassLoader的理解
我们的实例对象是通过 Class对象 来创建的, Class 对象被创建在堆中。
如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
1)Loading(载入)
JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的
java.lang.Class对象()。2)Verification(验证)
JVM 会在该阶段对二进制字节流进行校验,只有符合 JVM 字节码规范的才能被 JVM 正确执行。该阶段是保证 JVM 安全的重要屏障,下面是一些主要的检查。
- 确保二进制字节流格式符合预期(比如说是否以
cafe bene开头)。- 是否所有方法都遵守访问控制关键字的限定。
- 方法调用的参数个数和类型是否正确。
- 确保变量在使用之前被正确初始化了。wwww
- 检查变量是否被赋予恰当类型的值。w
3)Preparation(准备)
JVM 会在该阶段对类变量(也称为静态变量,
static关键字修饰的)分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等)。也就是说,假如有这样一段代码:
public String chenmo = "沉默"; public static String wanger = "王二"; public static final String cmower = "沉默王二";chenmo 不会被分配内存,而 wanger 会;但 wanger 的初始值不是“王二”而是
null。需要注意的是,
static final修饰的变量被称作为常量,和类变量不同。常量一旦赋值就不会改变了,所以 cmower 在准备阶段的值为“沉默王二”而不是null。4)Resolution(解析)
该阶段将常量池中的符号引用转化为直接引用。
what?符号引用,直接引用?
符号引用以一组符号(任何形式的字面量,只要在使用时能够无歧义的定位到目标即可)来描述所引用的目标。
在编译时,Java 类并不知道所引用的类的实际地址,因此只能使用符号引用来代替。比如
com.Wanger类引用了com.Chenmo类,编译时 Wanger 类并不知道 Chenmo 类的实际内存地址,因此只能使用符号com.Chenmo。直接引用通过对符号引用进行解析,找到引用的实际内存地址。
5)Initialization(初始化)
到了此阶段,才真正开始执行类中定义的Java程序代码。用于执行该类的静态初始器和静态初始块,如果该类有父类的话,则优先对其父类进行初始化。
什么时候会发生类初始化
public class TestClass {
static {
System.out.println("main类被加载");
}
// 虚拟机启动,就会加载main方法所在类
public static void main(String[] args) {
System.out.println("进入main函数");
// 当访问类变量时,所在类初始化
System.out.println(Hello.m);
}
}
class Hello{
// 类变量(static)的赋值顺序会影响最后的值,在类加载的初始化阶段,clint会将这些静态赋值语句按顺序抽出。
static {
System.out.println("加载子类的static代码块");
m = 32;
}
static int m = 20;
}
执行结果:
而常量(static final)在常量池中,所以使用它不会触发类加载。
public class TestClass {
static {
System.out.println("main类被加载");
}
public static void main(String[] args) {
System.out.println("进入main函数");
System.out.println(Hello.m);
}
}
class Hello{
static {
System.out.println("加载子类的static代码块");
}
final static int m = 20;
}
运行结果:
使用类声明数组也不会加载,如下。因为数组声明,此时只是声明了一个引用变量(存放对象的地址)的数组,没有使用到类。
public class TestClass {
static {
System.out.println("main类被加载");
}
public static void main(String[] args) {
System.out.println("进入main函数");
Hello [] array = new Hello[10];
}
}
class Hello{
static {
System.out.println("加载子类的static代码块");
}
}
类加载器
Java平台核心库在 rt.jar 中
JVM中提供了三层的ClassLoader:
Bootstrap classLoader:主要负责加载核心的类库(java.lang.*等),构造ExtClassLoader和APPClassLoader。
ExtClassLoader:主要负责加载jre/lib/ext目录下的一些扩展的jar。
AppClassLoader:主要负责加载应用程序的主函数类
双亲委派机制
首先,检查是否已经被系统类加载器(AppClassLoader)加载过, 存在父加载器,递归的交由父加载器, 判断是否加载过;若加载就不再加载,若一直没有加载,从引导类加载器开始,逐级向下判断是否可以加载,若可以加载,就加载,若一直到最底层都没有加载器可加载,将跑出ClassNotFoundException。
为什么要设计这种机制?
这种机制的作用:
- 防止类重复加载
- 保护程序安全,防止核心的API类被篡改,自己定义一个
java.lang.String不能被加载
4 动态创建对象
通过Class对象来使用它的构造器来创建实例对象,并可以操纵方法与属性等。
// 获得一个Class对象
Class c = Class.forName("reflection.Person");
// 通过Class对象构造一个实例
Person person = (Person) c.newInstance(); // 调用了Person的无参构造器
System.out.println(person);
//先通过Class对象的getDeclaredConstructor()返回一个构造器,再通过构造器来创建一个对象
Constructor constructor = c.getDeclaredConstructor(String.class, String.class); //指定参数列表匹配构造器
Person p2 = (Person) constructor.newInstance("lpf", "man");
System.out.println(p2.getName());
// 通过Class对象获取该类的一个方法
Method setName = c.getDeclaredMethod("setName", String.class);
// 通过方法的invoke,指定执行这个方法的对象与传入方法的参数
setName.invoke(p2,"lpf");
System.out.println(p2.getName());
// 通过Class对象获取该类的一个属性
Field name = c.getDeclaredField("name");
// 通过设置访问检查是否开启,可以访问类的私有属性,方法也是同理
name.setAccessible(true); // 传入true时,关闭访问权限检查。默认是开启权限检查的,所以要显式使用
name.set(p2,"属性");
- 通过反射调用方法的效率比正常调用方法慢得多,但关闭访问权限检查,能提高一些。
5 反射操作泛型
public class Generic {
public void test1(Map<String, Integer> student, List<String> course){
}
public Map<String, Integer> test2(){
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
// 1.获取包含泛型参数的方法
Method method = Generic.class.getMethod("test1", Map.class, List.class);
// 2.使用Type类型的数组接受所有泛型参数
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type ParameterTypes : genericParameterTypes) {
System.out.println(ParameterTypes);
// 3.对每个泛型参数判断是否为参数化类型
if (ParameterTypes instanceof ParameterizedType)
{
// 4.类型转换为参数化类型后,返回所有当作参数的类型
Type [] actualtype = ((ParameterizedType) ParameterTypes).getActualTypeArguments();
for (Type type :
actualtype) {
System.out.println(type);
}
}
}
Method test2 = Generic.class.getMethod("test2", null);
// 获取方法的返回值泛型类型
Type genericReturnType = test2.getGenericReturnType();
System.out.println(genericReturnType);
if (genericReturnType instanceof ParameterizedType)
{
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
6 反射操作注解
ORM(Object Relationship Mapping) 对象关系映射
通过反射可以读取类的所有信息,包括注解,而在注解中可以对类、属性、方法进行给定参数描述,通过反射机制读出注解的参数信息,可以交由其他框架处理或生成数据库。
@MyAtonotion(name = "a class",length = "1222")
public class Testall {
@FiledAnnotation(name = "a word for description")
private int m;
public static void main(String[] args) throws NoSuchFieldException {
MyAtonotion annotation = Testall.class.getAnnotation(MyAtonotion.class);
System.out.println(annotation.name());
System.out.println(annotation.length());
Field m = Testall.class.getDeclaredField("m");
FiledAnnotation filedAnnotation = m.getAnnotation(FiledAnnotation.class);
System.out.println(filedAnnotation.name());
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAtonotion{
String name();
String length();
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FiledAnnotation{
String name();
}
运行结果:

浙公网安备 33010602011771号