Java 内存分析
jvm把class文件加载到内存,并对数据进行校验、解析和初始化,最终形成jvm可以直接使用的java类型的过程。
加载:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
链接:将java类的二进制代码合并到jvm的运行状态之中的过程,链接过程又分为3个过程:
(1)、验证:确保加载的类信息符合jvm规范,没有安全方面的问题
(2)、准备:正式为类变量(static变量)分配内存并设置类变量初始值的阶段, 这些内存都将在方法区中进行分配
(3)、解析:虚拟机常量池内的符号引用替换为直接引用的过程。(比如String s ="aaa",转化为 s的地址指向“aaa”的地址)
初始化:初始化阶段是执行类构造器
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先初始化其父类的初始化
虚拟机会保证一个类的构造器方法在多线程环境中被正确加锁和同步
当访问一个java类的静态域时,只有真正声明这个静态变量的类才会被初始化。
代码解析:
public class Demo {
public static void main(String[] args) {
A a = new A();
System.out.println(a.m);
}
}
class A{
static {
System.out.println("A 类的静态代码块初始化");
m = 300;
}
static int m = 100;
public A(){
System.out.println("A 类的无参构造器初始化");
}
}
输出结果:
Java类的生命周期
类初始化
类的主动引用(一定会发生类的初始化)
当虚拟机启动,先初始化main方法所在的类
new 一个类的对象
调用类的静态成员(除了 final 常量)和静态方法
使用 java.lang.reflect 包的方法对类进行反射调用
当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
类的被动引用(不会发生类的初始化)
当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化
通过数组定义类引用,不会触发此类的初始化
引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)
代码解析:
public class Demo {
static {
System.out.println("main被加载");
}
public static void main(String[] args) throws ClassNotFoundException {
//主动引用
//Son son = new Son();
//反射
//Class.forName("com.tx.reflection.Son");
//不会产生类的引用的方法
//System.out.println(Son.f);
//数组
//Son[] sons = new Son[5];
//常量
System.out.println(Son.m);
}
}
class Father{
static int f = 2;
static {
System.out.println("父类被加载");
}
}
class Son extends Father{
static {
System.out.println("子类被加载");
s = 3;
}
static int s = 1;
static final int m = 4;
}
类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区中的运行时数据结构,在堆中生成一个代表这个类的java.lang.Class对象,作为方法区类数据的访问入口。
类缓存:标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过 JVM 垃圾回收机制可以回收这些 Class 对象
JVM 规范定义了如下类型的类的加载器:
引导类加载器(Bootstrap)
用 本地代码实现的类加载器,它负责将
扩展类加载器(Extension)
由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的,它负责将
系统类记载器(System)
也称应用类加载器,由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的,负责将用户类路径java -classpath 或 -D java.class.path 所指的目录下的类与 jar 包加载到内存中。开发者可以直接使用系统类加载器,是最常用的加载器
Bootstrap ClassLoader :最顶层的加载类,主要加载核心类库,也就是我们环境变量下面 %JRE_HOME%\lib 下的rt.jar、resources.jar、charsets.jar和class等。另外需要注意的是可以通过启动jvm时指定-Xbootclasspath和路径来改变Bootstrap ClassLoader的加载目录。比如java -Xbootclasspath/a:path被指定的文件追加到默认的bootstrap路径中。我们可以打开我的电脑,在上面的目录下查看,看看这些jar包是不是存在于这个目录
Extention ClassLoader :扩展的类加载器,加载目录%JRE_HOME%\lib\ext目录下的jar包和class文件。还可以加载-D java.ext.dirs选项指定的目录。
Appclass Loader:也称为SystemAppClass。 加载当前应用的classpath的所有类。
这三种类加载器的加载顺序:Bootstrap ClassLoader > Extention ClassLoader > Appclass Loader
层次关系:
public static void main(String[] args) throws ClassNotFoundException {
//获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);//sun.misc.Launcher$AppClassLoader@14dad5dc
//获取系统类加载器的父类加载器 --> 扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);//sun.misc.Launcher$ExtClassLoader@28d93b30
//获取扩展类加载器的父类加载器 --> 根加载器(C/C++)
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);//null
//测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("com.tx.reflection.ReflectionDemo").getClassLoader();
System.out.println(classLoader);//sun.misc.Launcher$AppClassLoader@14dad5dc
//测试 JDK 内置的类是谁加载的
classLoader = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader);//null
//如何获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
/*
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\charsets.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\deploy.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\access-bridge-64.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\cldrdata.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\dnsns.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jaccess.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\jfxrt.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\localedata.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\nashorn.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunec.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunjce_provider.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunmscapi.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\sunpkcs11.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\ext\zipfs.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\javaws.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\jce.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfr.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\jfxswt.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\jsse.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\management-agent.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\plugin.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\resources.jar;
* C:\Program Files\Java\jdk1.8.0_45\jre\lib\rt.jar;
* F:\ideaProject\study\javaTest\out\production\javaTest;
* C:\IDEA\ideaIU-2019.3.5.win\lib\idea_rt.jar
*/
}
但是,如果站在 Java 虚拟机的角度来讲,只存在两种不同的类加载器:
启动类加载器:它使用C++实现(这里仅限于Hotspot,也就是JDK1.5之后默认的虚拟机,有很多其他的虚拟机是用Java语言实现的),是虚拟机自身的一部分
所有其他的类加载器:这些类加载器都由Java语言实现,独立于虚拟机之外,并且全部继承自抽象类java.lang.ClassLoader,这些类加载器需要由启动类加载器加载到内存中之后才能去加载其他的类。
类加载器加载Class大致要经过如下8个步骤:
检测此Class是否载入过,即在缓冲区中是否有此Class,如果有直接进入第8步,否则进入第2步
如果没有父类加载器,则要么Parent是根类加载器,要么本身就是根类加载器,则跳到第4步,如果父类加载器存在,则进入第3步
请求使用父类加载器去载入目标类,如果载入成功则跳至第8步,否则接着执行第5步
请求使用根类加载器去载入目标类,如果载入成功则跳至第8步,否则跳至第7步
当前类加载器尝试寻找Class文件,如果找到则执行第6步,如果找不到则执行第7步
从文件中载入Class,成功后跳至第8步
抛出ClassNotFountException异常
返回对应的java.lang.Class对象
双亲委派原则
依据三类加载器(分层级的,最底层是应用程序加载器,上面一层是扩展类加载器,最顶层的是根加载器)结构建立的,具体的工作过程:
如果一个加载器收到了类加载的请求,加载器并不会直接进行加载,而是把这个请求委派个父类加载器来完成,只有父类加载器无法完成的时候,才到子类加载器中加载。另外需要明确一点:这里虽然说是“父子”关系,但是实际上并不是继承关系,而是通过组合方式实现的。在获取类加载器的时候,可以通过 getParent 方法来获取对应加载器的父类加载器。Application ClassLoader的父类加载器是Extension ClassLoader;Extension ClassLoader的父类加载器是BootStrap ClassLoader。但是如果我们通过Extension ClassLoader的getParent方法获取父类加载器的时候,得到的会是null,这是因为根加载器是C++实现的,它是本地语言实现的。
好处:
就是Java类随着加载器的不同,有了优先级划分,同时对于Java程序的安全性和稳定性有了保障。如果没有双亲委派,随便一个类都可以指定类加载器进行加载,那如果用户自定义了一个Object类,指定根加载器加载,这就破坏了Java内部的继承结构,Java内部,所有的类都是直接或间接继承自Object,这样出现了多个Object类,Java体系内部,一些很基础的行为就无法保证了(例如:hash,toString,equals等等这些行为)。这样系统内部就会一片混乱。而有了双亲委派,就能够保证越基础的类,由越高级的类加载器去完成,例如:用户尝试编写一个与rt.jar类库中某个类重名的类,可以发现,虽然可以编译,但是永远不会被加载运行,因为在到根加载器验证的时候就无法通过。
双亲委派模型在 Java 的 ClassLoader.java 的源码中就可以看到,可以查看该类中的 loadClass 方法:
protected Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,验证类是不是已经被加载过了
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果父类加载器中没有找到该类,就抛出ClassNotFoundException
}
if (c == null) {
//此时仍然没有找到,就调用本身的findClass方法
long t1 = System.nanoTime();
c = findClass(name);
//下面这些步骤就是定义类加载器,并且记录状态的,暂时无视它
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
可以看到
先根据findLoadedClass确定是不是已经加载过
如果没有,就委派父类加载器进行查找加载
如果父类加载器没有加载成功,就抛出ClassNotFoundException,并且调用本加载器的findClass方法进行加载
静态语言和动态语言
动态语言:是一类在运行时可以改变其结构的语言,例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构,主要动态语言有 Object-C、C#、JavaScript、PHP、Python 等
静态语言:与动态语言相对应,运行时结构并不可变的语言,如 Java 、C、C++。而 Java 不是动态语言,但可以称为“准动态语言”。即Java 有一定的动态性,可以利用反射机制获得类似动态语言的特性,让编程时更加灵活。
Java Reflection
Reflection(反射)是 Java 被视为动态语言的关键,反射机制允许程序在执行期借助 Reflection API 取得任何类的信息,并能直接操作任意对象的内部属性及方法。
Class c = Class.forName("java.lang.String")
加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象地称为反射,通俗点讲, 通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。
正常方式:引入需要的“包类”名称 ——> 通过 new 实例 ——> 取得实例化对象
反射方式:实例化对象 ——> getClass() ——> 得到完整的“包类”名称
Class 类
在 Object 类中定义了一下的方法,此方法将被所有子类继承
public final Class getClass()
此方法的返回值的类型是一个 Class 类,此类是 Java 反射的源头。
通过反射方式可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,jre 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void[])的有关信息。
Class 本身也是一个类
Class 对象只能由系统建立对象
一个加载的类在 JVM 中只会有一个 Class 实例
一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件
每个类的实例都会记得自己是由哪个 Class 实例所生成
通过 Class 可以完整地得到一个类中的所有被加载的结构
Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的 Class 对象
Java 反射机制提供的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注释
生成动态代理
... ...
反射的优缺点
优点:使用反射,我们就可以在运行时获得类的各种内容,进行反编译,对于Java这种先编译再运行的语言,能够让我们很方便的创建灵活的代码,这些代码可以在运行时装配,无需在组件之间进行源代码的链接,更加容易实现面向对象。实现动态创建对象和编译,体现出很大的灵活性
缺点:反射会消耗一定的系统资源,因此,如果不需要动态
地创建一个对象,那么就不需要用反射;反射调用方法时可以忽略权限检查,因此可能会破坏封装性而导致安全问题
主要API
java.lang.Class:代表一个类
java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量
java.lang.reflect.Constructor:代表类的构造器
... ...
Class类常用类
修饰符 | 返回值类型 | 方法(形参) | 说明 |
---|---|---|---|
static | Class | forName(String className) | 返回与带有给定字符串名的类或接口相关联的 Class 对象。 |
static | Class | forName(String name, boolean initialize, ClassLoader loader) | 使用给定的类加载器,返回与带 有给定字符串名的类或接口相关 联的 Class 对象 |
Object | newInstance() | 调用缺省的构造器函数,返回 Class 对象的一个实例 | |
String | getName() | 返回此 Class 对象所表示的实体类(类,接口,数组类或 void )的名称 | |
Class | getSuperClass() | 返回当前 Class 对象的父类的 Class 对象 | |
Class[] | getInterfaces() | 获取当前 Class 对象的接口 | |
ClassLoader | getClassLoader() | 返回该类的类加载器。 | |
Method | getDeclaredMethod(String name, Class... parameterTypes) | 返回一个 Method 对象,该对象 反映此 Class 对象所表示的类或 接口的指定已声明方法 | |
Method[] | getDeclaredMethods() | 返回 Method 对象的一个数组, 这些对象反映此 Class 对象表示 的类或接口声明的所有方法,包 括公共、保护、默认(包)访问 和私有方法,但不包括继承的方 法。 | |
Method | getMethod(String name, Class... parameterTypes) | 返回一个 Method 对象,它反映 此 Class 对象所表示的类或接口 的指定公共成员方法。 | |
Method[] | getMethods() | 返回一个包含某些 Method 对象 的数组,这些对象反映此 Class 对象所表示的类或接口(包括那 些由该类或接口声明的以及从超 类和超接口继承的那些的类或接 口)的公共 member 方法。 | |
Field | getDeclaredField(String name) | 返回一个 Field 对象,该对象反映 此 Class 对象所表示的类或接口 的指定已声明字段。 | |
Field[] | getDeclaredFields() | 返回 Field 对象的一个数组,这些 对象反映此 Class 对象所表示的 类或接口所声明的所有字段 | |
Field | getField(String name) | 返回一个 Field 对象,它反映此Class 对象所表示的类或接口的 指定公共成员字段。 | |
Field[] | getFields() | 返回一个包含某些 Field 对象的数 组,这些对象反映此 Class 对象 所表示的类或接口的所有可访问 公共字段。 | |
Constructor | getConstructor(Class... parameterTypes) | 返回一个 Constructor 对象,它反 映此 Class 对象所表示的类的指 定公共构造方法。 | |
Constructor[] | getConstructors() | 返回一个包含某些 Constructor 对 象的数组,这些对象反映此 Class 对象所表示的类的所有公 共构造方法 | |
Constructor | getDeclaredConstructor(Class... parameterTypes) | 返回一个 Constructor 对象,该对 象反映此 Class 对象所表示的类 或接口的指定构造方法。 | |
Constructor[] | getDeclaredConstructors() | 返回 Constructor 对象的一个数 组,这些对象反映此 Class 对象 表示的类声明的所有构造方法 |
获取 Class 类的实例
a)、若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序性能最高
Class c = Object.class;
b)、已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象
Class c = Object.getClass();
c)、已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取,可能抛出 ClassNotFoundException
Class c = Class.forName("java.lang.Object");
d)、内置基本数据类型可以直接用 类名.TYPE
Class c = Integer.TYPE;
获取类的运行时结构
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.tx.po.User");
//获得类的名字
System.out.println(c1.getName());//获得包名 + 类名
System.out.println(c1.getSimpleName());//获得类名
System.out.println("=================================================");
//获得类的属性
Field[] fields = c1.getFields();//只能找到 public 属性
for (Field field : fields) {
System.out.println("getFields:" + field);
}
fields = c1.getDeclaredFields();//找到全部属性
for (Field field : fields) {
System.out.println("getDeclaredFields:" + field);
}
//获得指定属性的值
Field name = c1.getDeclaredField("name");
System.out.println(name);
System.out.println("=================================================");
//获得类的方法
Method[] methods = c1.getMethods();//获得本类及其父类的全部 public 方法
for (Method method : methods) {
System.out.println("getMethods:" + method);
}
methods = c1.getDeclaredMethods();//获得本类的所有方法
for (Method method : methods) {
System.out.println("getDeclaredMethods:" + method);
}
//获得指定的方法
Method getName = c1.getMethod("getName", null);
System.out.println(getName);
Method setName = c1.getMethod("setName", String.class);
System.out.println(setName);
System.out.println("=================================================");
//获得指定构造器
Constructor[] constructors = c1.getConstructors();//public
for (Constructor constructor : constructors) {
System.out.println("getConstructors:" + constructor);
}
constructors = c1.getDeclaredConstructors();//all
for (Constructor constructor : constructors) {
System.out.println("getDeclaredConstructors:" + constructor);
}
//获得指定的构造器
Constructor declaredConstructor = c1.getDeclaredConstructor(Integer.class,String.class);
System.out.println(declaredConstructor);
}
动态创建对象执行方法
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//获得 Class 对象
Class c1 = Class.forName("com.tx.po.User");
//构造对象
// User user = (User) c1.newInstance();//本质上调用无参构造器
// System.out.println(user);
//通过构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(Integer.class, String.class);
User u = (User) constructor.newInstance(1, "huang");
System.out.println(u);
//通过反射调用指定的方法
User u2 = (User) c1.newInstance();
//通过反射获取一个方法
Method setName = c1.getDeclaredMethod("setName", String.class);
setName.invoke(u2,"bing");
System.out.println(u2.getName());
//通过反射操作字段
User u3 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
//不能直接操作私有属性,需要关闭程序的安全检测
name.setAccessible(true);
name.set(u3,"fu");
System.out.println(u3.getName());
}
在调用指定方法/属性时,若原方法/属性声明为 private ,则需要在调用此 invoke() 方法前,显式调用方法对象的 setAccessible(true) 方法,才可以访问声明为 private 的方法/属性
setAccessible()
Method 和 Field、Constructor 对象都有 setAccessible() 方法,作用是启动和禁用访问安全检查的开关
参数值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查
提高反射效率。如果代码中必须用反射,而该句代码需要频繁的被调用,则设置为 true
使得原本无法访问的私有成员也可以访问
参数值为 false 则指示反射的对象应该实施 Java 语言访问检查
获取泛型信息
Java 采用泛型擦除的机制来引入泛型,Java 中的泛型仅仅是给编译器 javac 使用的,确保数据的安全性和免去强制类型转换问题,但是,一旦编译完成,所有和泛型有关的类型全部擦除
为了通过反射操作这些类型,Java 新增了 ParameterizedType、GenericArrayType、TypeVariable 和 WildcardType 几种类型来代表不能被归一到 Class 类中的类型但是又和原始类型齐名的类型
ParameterizedType:表示一种参数化类型,比如 Collection
GenericArrayType:表示一种元素类型,是参数化类型或者类型变量的数组类型
TypeVariable :是各种类型变量的公共父接口
WildcardType :代表一种通配符类型表达式
示例:
public class ReflectionDemo {
public void test01(Map map, List list){
System.out.println("test01");
}
public Map test02(){
System.out.println("test02");
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Method method = ReflectionDemo.class.getMethod("test01", Map.class, List.class);
Type[] genericParameterTypes = method.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println("#" + genericParameterType);
if (genericParameterType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
method = ReflectionDemo.class.getMethod("test02",null);
Type genericReturnType = method.getGenericReturnType();
if (genericReturnType instanceof ParameterizedType){
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
}
获取注解信息
getAnnotations
getAnnotation
public class ReflectionDemo {
public static void main(String[] args) throws ClassNotFoundException {
Class c = Class.forName("com.tx.reflection.Temp");
//通过反射获得注解
Annotation[] annotations = c.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解的value
InTemp inTemp = (InTemp)c.getAnnotation(InTemp.class);
String value = inTemp.value();
System.out.println(value);
}
}
@InTemp(value = "temp")
class Temp{
private int id;
private String name;
private long length;
public Temp() {
}
public Temp(int id, String name, long length) {
this.id = id;
this.name = name;
this.length = length;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public long getLength() {
return length;
}
public void setLength(long length) {
this.length = length;
}
@Override
public String toString() {
return "Temp{" +
"id=" + id +
", name='" + name + '\'' +
", length=" + length +
'}';
}
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface InTemp{
String value();
}