第十五章、反射机制
一、概述
Java中的反射机制是指在运行时检查、获取和操作类、对象、方法和属性的能力。反射机制允许程序在运行时获取类的信息、调用方法和访问属性,而无需在编译时明确知道这些信息。
二、反射机制的作用
通过java语言中的反射机制可以操作字节码文件(.class文件),有点类似于黑客,可以读和修改字节码文件,可以操作代码片段
使用Java的反射机制,可以实现以下功能:
-
获取类的信息:反射机制允许在运行时获取类的名称、父类、接口、方法、属性和构造函数等信息。通过反射,可以动态地加载和使用类,而无需在编译时将类名硬编码到代码中。
-
创建对象:通过反射,可以在运行时实例化类的对象,包括私有构造函数。这使得可以在运行时根据需要动态创建对象。
-
调用方法:反射机制可以在运行时动态调用对象的方法,包括公共方法、私有方法和静态方法。通过反射,可以动态地调用方法,无论这些方法是在编译时还是运行时创建的。
-
访问和修改属性:反射机制允许在运行时访问和修改对象的属性,包括私有属性。这样可以实现动态地获取和设置对象的属性值。
三、反射机制的相关类在java.lang.reflect包下
要使用反射机制,可以使用Java的java.lang.reflect
包中的类,如Class
、Method
和Field
等。这些类提供了获取类信息、调用方法和访问属性的方法。
四、反射机制相关的重要的类
- java.lang.Class.java:代表整个字节码,代表一个类型,代表整个类
- java.lang.reflect.Method.java:代表字节码中的方法字节码,代表类中的方法
- java.lang.reflect.Constructor.java:代表字节码中的构造方法字节码,代表类中的构造方法
- java.lang.reflect.Field.java:代表字节码中的属性字节码,代表类中的成员变量(实例 + 静态)
//整体叫做java.lang.class
public class User {
//这个叫做java.lang.reflect.Field(属性)
private int no;
//这个叫做java.lang.reflect.Constructor(构造方法)
public User(){
}
public User(int no){
this.no = no;
}
//这个叫做java.lang.reflect.Method.java(方法)
public void setNo(int no){
this.no = no;
}
public int getNo(){
return no;
}
}
第十六章、通用方式获取文件绝对路径
一、概述
通用路径表示法:即使代码换位置了,仍然是可用的
注意:使用通用方式的前提,这个文件必须在类路径下
放在src下的都是类路径
src是类的根路径
该方法不能获取java源文件的路径,因为真正的类路径下是没有.java 文件的,有.java文件编译后的.class文件,其他文件都可以使用这种方法获取绝对路径
二、getResource("写相对路径,但是该路径从src开始").getPath():获取绝对路径
package com.javase.反射机制;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.net.URLDecoder;
/**
* 研究一下文件路径问题
* 获取文件的绝对路径,以下这种方式是通用的,但是前提是:文件需要在类路径下,才能使用这种方式
*/
public class AboutPath {
public static void main(String[] args) throws Exception {
/**
* 这种方式的路径缺点是:移植性差,在IDEA中默认的当前路径是project项目根路径
* 这个代码假设离开了IDEA,换到了其他位置,可能当前路径就不是project的根路径了,此时这个路径就无效了
*/
// FileInputStream in = new FileInputStream("java_advanced\\classinfo.properties");
// byte[] bytes = new byte[4];
// int readCount;
// while ((readCount = in.read(bytes)) != -1){
// System.out.print(new String(bytes,0,readCount));
// }
// in.close();
/**
* 接下来说一种比较通用的路径,即使代码换位置了,这样编写仍然是通用的
* 通用路径表示法:即使代码换位置了,仍然是可用的
* 注意:使用通用方式的前提,这个文件必须在类路径下
* 放在src下的都是类路径
* src是类的根路径
*/
String path = Thread.currentThread().getContextClassLoader().
getResource("classinfo1.properties").getPath();
//采用以上的代码可以拿到一个文件的绝对路径
//这个绝对路径是通用的,在不同的操作系统中有不同的路径
System.out.println(path);
///D:/JavaSE/JavaSE_demo1/out/production/java_advanced/classinfo1.properties
/**
* 解释:
* Thread.currentThread():获取当前线程对象
* getContextClassLoader():是线程对象的方法,可以获取到当前线程的类加载器对象
* getResource(): 类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源,并获取资源对象
* getPath():资源对象的方法,获取绝对路径
*/
//获取db.properties文件的绝对路径():从类的根路径出发作为起点开始
String path2 = Thread.currentThread().getContextClassLoader()
.getResource("com/javase/反射机制/bean/db.properties").getPath();
// 解码路径中的中文字符
String decodedPath = URLDecoder.decode(path2, "UTF-8");
System.out.println(decodedPath);
}
}
三、getResourceAsStream():直接根据类路径下文件名(相对路径)以流的形式返回
package com.javase.反射机制;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URLDecoder;
import java.util.Properties;
public class IoPropertiesTest {
public static void main(String[] args) throws Exception {
// FileInputStream in = null;
//获取一个文件的绝对路径
// String path = Thread.currentThread().getContextClassLoader()
// .getResource("com/javase/反射机制/bean/db.properties").getPath();
// String decodePath = URLDecoder.decode(path,"UTF-8");
// in = new FileInputStream(decodePath);
//直接以流的形式返回
InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("com/javase/反射机制/bean/db.properties");
Properties properties = new Properties();
properties.load(in);
in.close();
//通过key获取value
String className = properties.getProperty("className");
System.out.println(className);
}
}
四、ResourceBundle类
1、概述
- java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
- 但是属性配置文件xxx.properties必须在类路径下
- 资源绑定器只能绑定xxx.properties文件。并且这个文件必须在类路径下,文件扩展名必须是properties,并且在写路径的时候,路径后面的扩展名不能写
2、案例
package com.javase.反射机制.类加载器对象常用方法;
import java.util.ResourceBundle;
/**
* java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容
* 但是属性配置文件xxx.properties必须在类路径下
*/
public class ResourceBundleTest {
public static void main(String[] args) {
//资源绑定器只能绑定xxx.properties文件。并且这个文件必须在类路径下,文件扩展名必须是properties,
// 并且在写路径的时候,路径后面的扩展名不能写
// ResourceBundle bundle = ResourceBundle.getBundle("com/javase/反射机制/bean/db");
ResourceBundle bundle = ResourceBundle.getBundle("classinfo1");
String className = bundle.getString("className");
System.out.println(className); //com.javase.反射机制.bean.User
}
}
第十七章、java.lang.Class
一、概述
java.lang.Class
类是Java反射机制的核心类之一。它提供了对Java类的运行时信息的访问和操作,通过java.lang.Class
类,可以实现对类的运行时信息的动态访问和操作。它在Java的反射机制中扮演着重要的角色,使得程序可以在运行时动态地加载和操作类。它提供了许多方法来获取类的信息、创建对象、调用方法和访问字段,使得编写灵活且可扩展的代码成为可能。
二、获取class字节码文件对象的三种方式
只是Class类型的字节码文件对象,一个对象,虽然可以代表该类的型,但是和类不一样,不能调用该类中的静态方法和属性
要操作一个类的字节码,需要首先获取到这个类的字节码
1、static Class<?> forName(String className):只要该方法能让类加载并获取Class类型的字节码文件对象
方法签名:
public static Class<?> forName(String className) throws ClassNotFoundException
参数:
className
:要加载的类的完全限定名(包名+类名)从src开始的相对路径类全名,不能加java后缀。
返回值:
Class
对象:表示被加载的类。
异常:
ClassNotFoundException
:如果找不到指定的类。
工作原理:
Class.forName()
方法通过传递类的完全限定名来加载类,并返回对应的Class
对象。- 当调用
Class.forName()
方法时,它会尝试加载并初始化指定的类。如果成功找到类并加载,则返回对应的Class
对象;如果找不到类,则抛出ClassNotFoundException
异常。
Class.forName()的方法的执行会导致类加载,如果只希望静态代码执行的话可采用这种方式
如果只希望一个静态的静态代码块执行,其他代码一律不执行:
可以使用:
Class.forName("完整类名");
这个方法的执行会导致类加载,类加载时,静态代码块执行
package com.javase.反射机制;
/**
* 研究一下:Class.forName()这个方法发生了什么
*/
public class ReflectTest04 {
public static void main(String[] args) {
// MyClass myClass = new MyClass();
try {
Class.forName("com.javase.反射机制.MyClass");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class MyClass{
//静态代码块在类加载是执行,并且只执行一次
static {
System.out.println("MyClass类的静态代码块执行了!");
}
}
类加载和初始化:
Class.forName()
方法会触发类的加载和初始化过程。类加载器会搜索类路径,查找指定的类文件,并将其字节码加载到内存中。然后,Java虚拟机会对类进行初始化,执行静态初始化块和静态变量的初始化。
默认类加载器:
- 如果没有指定类加载器,默认情况下,
Class.forName()
方法会使用调用该方法的类的类加载器(即当前上下文类加载器)来加载指定的类。
用途:
Class.forName()
方法常用于动态加载和实例化类,通过反射进行操作。它允许在运行时根据类名动态加载类,而不需要在编译时将类名硬编码到代码中。
需要注意的是,
Class.forName()
方法要求提供完全限定的类名,并且该类在类路径中可见。如果要加载的类不在类路径中,或者没有提供正确的完全限定名,将会抛出ClassNotFoundException
异常。
以下是一个简单的示例,演示了如何使用Class.forName()
方法来加载并获取类的Class
对象:
/**
* Class.forName()
* 1、静态方法
* 2、方法的参数是一个字符串
* 3、字符串需要的是个完整类名
* 4、完成类名必须带有包名,java.lang包也不能省略
*/
try {
//c1是Class类型对象,代表String.class字节码文件,或者说代表String类型
Class<?> c1 = Class.forName("java.lang.String");
System.out.println(c1.toString()); //class java.lang.String
System.out.println(c1.getName()); //java.lang.String
//c2代表Date类型
Class<?> c2 = Class.forName("java.util.Date");
System.out.println(c2.toString()); //class java.util.Date
System.out.println(c2.getName()); //java.util.Date
//c3代表Integer类型
Class<?> c3 = Class.forName("java.lang.Integer");
System.out.println(c3.toString()); //class java.lang.Integer
System.out.println(c3.getName()); //java.lang.Integer
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
在上面的例子中,Class.forName()
方法用于加载并获取名为com.example.MyClass
的类的Class
对象。如果成功加载类,则打印类的名称;如果找不到类,则打印异常堆栈跟踪。
Class.forName()
方法在动态加载类、插件系统和数据库驱动程序加载等场景中非常有用,它提供了在运行时动态加载和使用类的能力。
2、public final native Class<?> getClass()
在Java中,每个对象都继承自java.lang.Object
类。Object
类提供了一个getClass()
方法,用于获取对象所属的类的Class
对象。
方法签名:
public final Class<?> getClass()
返回值:
Class
对象:表示对象所属的类。
用途:
getClass()
方法用于获取对象的运行时类型,即对象所属的类。- 通过
getClass()
方法可以获取到对象所属类的Class
对象,从而可以获取类的各种信息,如类名、父类、接口、方法、字段等。
示例:
//java中任何一个对象都有一个方法:getClass()
String s = "jzq";
Class<?> x = s.getClass();
Class<?> x1 = Class.forName("java.lang.String");
System.out.println(x == x1); //true(==判断的是对象的内存地址)
Date nowTime = new Date();
Class<?> y = nowTime.getClass();
Class<?> y1 = Class.forName("java.util.Date");
System.out.println(y == y1); //true y和y1两个引用中保存的内存地址都是一样的,都指向方法区内存中字节码文件对象
需要注意的是,getClass()
方法返回的是对象所属类的Class
对象,而不是对象本身。如果对象是null
,则调用getClass()
方法会抛出NullPointerException
异常。
getClass()
方法在Java中广泛用于实现对象的运行时类型检查、动态分派和反射等操作。它提供了一种在运行时获取对象所属类信息的简便方式。
内存图分析:
3、使用类型都有的class属性
使用类的class属性获取字节码文件:每个类都有一个
class
属性,可以通过该属性直接获取对应的字节码文件
在Java中,每个类都有一个名为class
的属性。这个属性是java.lang.Class
类型的静态属性,表示该类的Class
对象。以下是对类的class
属性的详细解释:
访问方式:
- 类的
class
属性是一个静态属性,可以通过类名直接访问,例如MyClass.class
。
类型:
class
属性的类型是java.lang.Class
,它是Java反射机制的核心类之一。
返回值:
Class
对象:表示类的元数据信息,包括类的名称、修饰符、父类、接口、方法、字段等。
用途:
class
属性用于获取类的Class
对象,通过这个对象可以获取类的各种信息和进行反射操作。- 通过
class
属性,可以在编译时获取类的元数据信息,而无需实例化对象。
示例:
//java中的任何一种类型,包括基本数据类型,都有.class属性
Class<?> l = int.class; //l代表int类型
System.out.println(l); //int
System.out.println(l.getName());//int
Class<?> m = String.class; //m代表String类型
Class<?> m1 = "jzq".getClass();
Class<?> m2 = Class.forName("java.lang.String");
System.out.println(m == m1); //true
System.out.println(m == m2); //true
System.out.println(m1 == m2); //true
需要注意的是,类的class
属性在编译时就确定了,它是类加载器加载类时创建的。对于每个类而言,它的class
属性在整个应用程序运行期间保持不变。
类的class
属性常用于编写通用的代码,实现动态加载和实例化类,以及进行反射操作。它允许在编译时通过类名直接访问类的元数据信息,而无需在运行时通过对象调用getClass()
方法来获取类的Class
对象。
三、创建实例对象
1、使用newInstance()方法
newInstance()
方法是Class
类的一个实例方法,用于通过默认构造函数创建类的实例对象。- 该方法会调用类的默认构造函数来创建对象,并返回一个新的实例。
- 请注意,该方法要求类具有公共的无参构造函数,否则会抛出
InstantiationException
异常
package com.javase.反射机制.bean;
public class User {
public User() {
System.out.println("无参构造方法执行");
}
}
package com.javase.反射机制;
import com.javase.反射机制.bean.User;
/**
* 获取到Class,能干什么?
* 通过Class的newInstance()方法来实例化对象
* 注意:newInstance()方法内部实际上调用了无参数构造方法,必须保证无参构造方法存在才可以
*/
public class ReflectTest02 {
public static void main(String[] args) {
//不使用反射机制创建对象
User a = new User();
System.out.println(a);
//使用反射机制创建对象
//通过反射机制,获取Class对象,通过Class对象来实例化对象、
try {
//强制使用泛型
//Class<User> user = (Class<User>) Class.forName("com.javase.反射机制.bean.User"); //user代表User类型
Class<?> user = Class.forName("com.javase.反射机制.bean.User"); //user代表User类型
//newInstance()这个方法会调用User这个类的无参数构造方法,完成对象的创建
/**
* 重点:newInstance()方法调用的是无参构造,必须保证无参构造是存在的
*/
Object obj = user.newInstance();
// if(obj instanceof User){
// User aa = (User) obj;
// }
System.out.println(obj); //com.javase.反射机制.bean.User@27d6c5e0
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
四、验证反射机制的灵活性
java代码只写一遍,在不改变代码的情况下,可以做到不同对象的实例化,非常灵活(符合OCP原则:对扩展开放,对修改关闭)
后期要学习的是高级框架,在工作过程中,都是使用高级框架
包括:ssh,ssm
Spring框架
这些高级框架底层实现原理,都是采用了反射机制,所以反射机制还是重要的
学习反射机制,有利于理解框架底层的源代码
classinfo.properties
className1=com.javase.\u53cd\u5c04\u673a\u5236.bean.User
className=java.util.Date
package com.javase.反射机制;
import com.javase.反射机制.bean.User;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Properties;
/**
* 验证反射机制的灵活性
*/
public class ReflectTest03 {
public static void main(String[] args) {
/**
* 这种方式代码就写死了,只能创建一个User类型的对象
*/
// User user = new User();
/**
* 以下代码是灵活的,代码不需要修改,可以修改配置文件,配置文件修改之后,可以创建出不同的实例对象
*/
FileInputStream in = null;
// InputStreamReader reader = null;
Properties properties = null;
try {
//通过IO流读取classinfo.properties文件
in = new FileInputStream("java_advanced\\classinfo.properties");
// reader = new InputStreamReader(in, StandardCharsets.UTF_8);
//创建属性类对象
properties = new Properties();
//加载
properties.load(in);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// if (reader != null) {
// try {
// reader.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
}
//通过key获取value
String className = properties.getProperty("className1");
// System.out.println(className);
//通过反射机制实例化对象
try {
Class<?> c = Class.forName(className);
Object obj = c.newInstance();
if(obj instanceof Date){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
System.out.println(simpleDateFormat.format(obj));
}else {
System.out.println(obj);
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
五、常用实例方法
package com.javase.反射机制.bean;
/**
* 反射属性Field
*/
public class Student {
//Field翻译为字段,其实就是属性,成员变量
//4个Field,分别采用了不同的访问权限控制符
public int no; //整个成员变量是Field类型对象
protected int age; //整个成员变量是Field类型对象
boolean sex; //整个成员变量是Field类型对象
private String name; //整个成员变量是Field类型对象
//为了验证修饰符列表,新加的
public static final double MATH_PI = 2.1415926; //整个成员变量是Field类型对象
}
1、String getName():获取完整类名
//获取整个类
Class<?> studentClass = Class.forName("com.javase.反射机制.bean.Student");
//获取完整类名
String className = studentClass.getName();
System.out.println(className); //com.javase.反射机制.bean.Student
2、String getSimpleName():获取简单类名
//获取简单类名
String simpleName = studentClass.getSimpleName();
System.out.println(simpleName); //Student
3、Field[ ] getFields():获取类中所有的public修饰的Field类型属性对象
//获取整个类
Class<?> studentClass = Class.forName("com.javase.反射机制.bean.Student");
//获取完整类名
String className = studentClass.getName();
System.out.println(className); //com.javase.反射机制.bean.Student
//获取简单类名
String simpleName = studentClass.getSimpleName();
System.out.println(simpleName); //Student
//获取类中的所有的public修饰的Field
Field[] fields = studentClass.getFields();
System.out.println(fields.length); //1
//取出这个Field
System.out.println(fields[0]); //public int com.javase.反射机制.bean.Student.no
//取出这个Field的名字
String fieldName = fields[0].getName();
System.out.println(fieldName); //no
4、Field[ ] getDeclaredFields():获取类中所有的Field类型属性对象
getDeclaredFields()
方法是 Java 反射机制中Class
类的方法之一,用于获取类中声明的所有字段(成员变量),包括公共、私有、保护和默认访问权限的字段
5、Field getDeclaredField(String name):获取类中指定名称的Field类型属性对象
用于获取类中声明的指定名称的字段(成员变量),包括公共、私有、保护和默认访问权限的字段
6、int getModifiers():获取类的修饰符,返回一个 int
值,可以使用 Modifier
类的toString()方法解析修饰符(因为修饰符可能有多个)
主要用于获取类的修饰符
7、Method[ ] getDeclaredMethods():获取类中所有的Method类型方法对象
用于获取类中声明的所有的方法,包括实例,静态,公共、私有、保护和默认访问权限的方法
8、Method getDeclaredMethod(String name, Class<?>... parameterTypes):根据方法名和形参列表获取指定的Method类型方法对象
用于获取类中声明的指定方法名和形参列表的方法,包括实例,静态,公共、私有、保护和默认访问权限的方法
9、Constructor<?>[] getConstructors():获取类中所有的构造方法
主要用于获取类中所有的构造方法
10、Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取类中指定的构造方法
主要用于获取类中指定的构造方法
11、Class<? super T> getSuperclass():获取一个类的父类
主要用于获取一个类的superClass
12、Class<?>[] getInterfaces():获取一个类所有实现的接口
注意:一个类可实现多个接口,返回Class数组
六、获取类的父类和所有实现的接口
package com.javase.反射机制.获取父类和父接口;
/**
* 重点:获取一个类的父类,已经实现了哪些接口
*/
public class ReflectTest13 {
public static void main(String[] args) throws Exception{
//String举例
Class<?> className = Class.forName("java.lang.String");
//获取String的父类
Class<?> superClass = className.getSuperclass();
System.out.println(superClass.getName()); //java.lang.Object
//获取String类实现的所有接口(注意:一个类可实现多个接口)
Class<?>[] classes = className.getInterfaces();
for (Class<?> interfaceName:
classes) {
System.out.println(interfaceName.getName());
/**
* java.io.Serializable
* java.lang.Comparable
* java.lang.CharSequence
* java.lang.constant.Constable
* java.lang.constant.ConstantDesc
*/
}
}
}
第十八章、java.lang.reflect.Field
一、常用实例方法(获取Field)
1、String getName():获取Field类型属性对象的名称(成员变量名)
主要用于获取属性/成员变量的变量名
2、Class<?> getType():获取Field类型属性对象的类型
获取字段的类型,返回一个
Class
对象。
3、int getModifiers():获取字段的修饰符,返回一个 int
值,可以使用 Modifier
类的toString()方法解析修饰符(因为修饰符可能有多个)
主要用于获取属性的修饰符
//获取所有的Field
Field[] fields1 = studentClass.getDeclaredFields();
System.out.println(fields1.length); //4
//遍历
for (Field field:
fields1) {
//获取属性的名字
System.out.println(field.getName());
/**
* no
* age
* sex
* name
*/
//获取属性的类型
Class<?> fieldType = field.getType();
// System.out.println(fieldType.getName());
//也可以使用简单类名
System.out.println(fieldType.getSimpleName());
/**
* no
* int
* =============
* age
* int
* =============
* sex
* boolean
* =============
* name
* String
* =============
*/
//获取属性的修饰符列表
int i = field.getModifiers(); //返回的修饰符是一个数字,每一个数字是修饰符的代号
System.out.println(i);
//将代号数字转换成字符串
String modifierString = Modifier.toString(i);
System.out.println(modifierString);
System.out.println("=============");
/**
* no
* int
* 1
* public
* =============
* age
* int
* 4
* protected
* =============
* sex
* boolean
* 0
*
* =============
* name
* String
* 2
* private
* =============
* MATH_PI
* double
* 25
* public static final
* =============
*/
}
4、void set(Object obj, Object value):为指定对象的该字段设置新的值,如果字段是静态的,则传入 null
给对象的属性赋值
5、Object get(Object obj):获取指定对象中该字段的值,如果字段是静态的,则传入 null
获取对象属性的值
6、setAccessible(boolean flag)
:设置字段的可访问性
将其设置为
true
可以绕过访问权限检查,直接访问私有字段。
7、isAccessible()
:判断字段是否可访问,即是否可以直接访问私有字段
返回true可直接访问,false不能直接访问
二、反编译Field
package com.javase.反射机制.反射属性;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
/**
* 通过反射机制,反编译一个类的属性Field
*/
public class ReflectTest06 {
public static void main(String[] args) throws Exception {
getPropertyInfo("java.util.Date");
}
public static void getPropertyInfo(String fullClassName) throws ClassNotFoundException {
//创建字符串缓存对象,为了频繁拼接字符串
StringBuilder s = new StringBuilder();
//获取Class
Class<?> ClassName = Class.forName(fullClassName);
s.append(Modifier.toString(ClassName.getModifiers()) + " class " + ClassName.getSimpleName() + " {" + "\n");
Field[] fields = ClassName.getDeclaredFields();
for (Field field:
fields) {
s.append("\t");
String modifierName = Modifier.toString(field.getModifiers());
s.append(modifierName);
//缺省修饰符返回值是 长度为0的 “” 空字符串,并不是为null
if(!(modifierName.equals(""))){
s.append(" ");
}
s.append(field.getType().getSimpleName());
s.append(" ");
s.append(field.getName() + ";");
s.append("\n");
}
s.append("}");
System.out.println(s);
// System.out.println(Arrays.toString(fields));
}
}
三、操作对象的属性
反射机制让代码复杂了,但是为了灵活,这也是值得的
反射机制的缺点:打破封装,可能会给不法分子留下机会
package com.javase.反射机制.反射属性;
import com.javase.反射机制.bean.Student;
import java.lang.reflect.Field;
/**
* 必须掌握:
* 如何通过反射机制访问一个java对象的属性
* 给属性赋值 set
* 获取属性的值 get
*/
public class ReflectTest07 {
public static void main(String[] args) throws Exception {
//不使用反射机制访问对象的属性
Student student = new Student();
//给属性赋值
student.no = 111; //三要素:给student对象的no属性赋值111
//要素1:对象student
//要素2:no属性
//要素3:111
//读取属性的值
System.out.println(student.no); //两个要素:student对象和no属性
//使用反射机制访问对象的属性
Class<?> className = Class.forName("com.javase.反射机制.bean.Student");
//创建Student对象
Object studentObj = className.newInstance(); //底层调用无参数构造方法
//获取no属性(根据属性的名称来读取Field)
Field noField = className.getDeclaredField("no");
//给studentObj(Student对象)的no属性赋值
/**
* 虽然使用了反射机制,但是三要素还是缺一不可
* 要素1:studentObj对象
* 要素2:no属性,noField对象
* 要素3:888这个值
* 反射机制让代码复杂了,但是为了灵活,这也是值得的
*/
noField.set(studentObj,888); //给obj对象的no属性赋值888
//获取对象的值
/**
* 两个要素:
* 要素1:studentObj对象
* 要素2:no属性
*/
System.out.println(noField.get(studentObj));
//访问私有的属性
Field nameField = className.getDeclaredField("name");
//打破封装
//反射机制的缺点:打破封装,可能会给不法分子留下机会
//这样设置以后,在外部可以直接访问private属性
System.out.println(nameField.isAccessible()); //false
nameField.setAccessible(true);
//给name属性赋值
nameField.set(studentObj,"jzq");
//获取name属性的值
System.out.println(nameField.get(studentObj));
}
}
第十九章、可变长度参数
一、概述
在Java中,可变长度参数(Varargs)是一种特殊的参数形式,允许方法接受可变数量的参数。使用可变长度参数,方法可以接受零个或多个参数,并将它们视为一个数组。以下是可变长度参数的详细解释:
二、语法
- 可变长度参数的语法使用三个点号(
...
)来表示,放置在方法参数声明的类型后面。 - 例如:
public void myMethod(String... values) { ... }
三、特点
- 可变长度参数只能用于方法的最后一个参数。
- 方法形参中只能声明一个可变长度参数。
- 可变长度参数在方法内部被视为一个数组类型。
四、用法
- 调用可变长度参数的方法时,可以传递零个或多个参数,甚至可以传递一个数组。
- 在方法内部,可以像操作数组一样访问和处理可变长度参数。
五、案例
package com.javase.反射机制.Method;
public class ArgsTest02 {
public static void main(String[] args) {
MyClass obj = new MyClass();
obj.printValues("Hello", "World");
obj.printValues("Java", "is", "awesome");
obj.printValues(); // 不传递参数,可变长度参数为空数组
}
}
class MyClass {
public void printValues(String... values) {
for (String value : values) {
System.out.println(value);
}
}
}
在上面的示例中,printValues()
方法使用可变长度参数来接收任意数量的字符串参数,并在方法内部使用增强的 for
循环遍历并打印这些参数。
可变长度参数为开发者提供了一种方便的方式来处理不确定数量的参数。它可以简化方法的调用和使用,使代码更灵活和易读。在Java标准库中,像 System.out.printf()
和 Arrays.asList()
这样的方法就使用了可变长度参数。
需要注意的是,如果方法同时具有其他参数,可变长度参数必须放在最后一个参数的位置。此外,可变长度参数在方法内部被处理为数组,因此可以使用数组相关的方法和语法对其进行操作。
package com.javase.反射机制.Method;
/**
* 可变长度参数
* int... args 这就是可变长度参数
* 语法是:类型... 形参名(注意:一定是三个点)
*
* 1、可变长度参数要求的参数个数为:0~n个
* 2、可变长度参数在参数列表中必须在最后一个位置上,并且可变长度参数只能有一个
* 3、可变长度参数可以当做一个数组
*/
public class ArgsTest01 {
public static void main(String[] args) {
m();
m(10);
m(10,20);
//编译报错
// m("jzq");
m2(100);
m2(200,"abc");
m2(300,"abc","def");
m2(400,"abc","def","xyx");
m3("abc","def","xyx");
//只能传一个数组
String[] strings = {"a","b","c"};
m3(strings);
//直接传1个数组
m3(new String[]{"我","是","贾","卓","群"}); //没必要
m3("我","是","贾","卓","群");
}
public static void m(int... args){
System.out.println("m方法执行了");
}
/*public static void m2(String... args,int... args1){
}*/
//可变长度参数只能出现在最后面,只能有一个
public static void m2(int i , String... args){}
public static void m3(String... args){
//args有length属性,说明args是一个数组
//可以将args进行遍历
for (int i = 0; i < args.length; i++) {
System.out.println(args[i]);
}
}
}
第二十章、java.lang.reflect.Method
package com.javase.反射机制.bean;
/**
* 用户业务类
java中有方法重载机制,overload
* 区分方法是通过方法名和参数列表来区分
*/
public class UserService {
/**
* 登录方法
* @param userName 用户名
* @param password 密码
* @return true表示登录成功,false表示登录失败
*/
public boolean login(String userName,String password){
if("admin".equals(userName) && "123".equals(password)) return true;
return false;
}
/**
* 退出系统的方法
*/
public static void logout(){
System.out.println("系统已经安全退出!");
}
}
一、常用实例方法(获取Method)
1、String getName():获取Method类型方法对象的名称
主要用于获取方法名
2、Class<?> getReturnType():获取Method类型方法对象的返回值类型
获取方法返回值的类型,返回一个
Class
对象。
3、int getModifiers():获取方法的修饰符,返回一个 int
值,可以使用 Modifier
类的toString()方法解析修饰符(因为修饰符可能有多个)
主要用于获取方法的修饰符
4、Class<?>[] getParameterTypes():获取方法的参数类型
主要用于获取方法的参数类型,返回Class类型数组
5、Object invoke(Object obj, Object... args):调用对象的方法
符合调用方法的4要素,对象,方法名,实参列表,返回值
package com.javase.反射机制.Method;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 了解内容:
* 反射Method
*/
public class ReflectMethodTest01 {
public static void main(String[] args)throws Exception {
//获取整个类
Class<?> className = Class.forName("com.javase.反射机制.bean.UserService");
//获取类中所有的方法Method(包括私有的)
Method[] methods = className.getDeclaredMethods();
//System.out.println(methods.length); //2
//遍历Method[]
for (Method method:
methods) {
// System.out.println(method);
//获取方法的修饰符列表
System.out.println(Modifier.toString(method.getModifiers()));
//获取方法的返回值类型
System.out.println(method.getReturnType().getSimpleName());
//获取方法名
System.out.println(method.getName());
//获取方法的参数类型(一个方法可能有多个参数)
Class<?>[] parameterTypes = method.getParameterTypes();
for (Class<?> parameterType:
parameterTypes) {
System.out.println(parameterType.getSimpleName());
}
System.out.println("============");
}
}
}
二、反编译Method
package com.javase.反射机制.Method;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
/**
* 反编译类中的方法
*/
public class ReflectMethodTest02 {
public static void main(String[] args) throws Exception {
getMethodInfo("java.lang.Object");
}
public static void getMethodInfo(String name) throws Exception {
StringBuilder s = new StringBuilder();
//获取类
Class<?> className = Class.forName(name);
s.append(Modifier.toString(className.getModifiers()) + " class " + className.getSimpleName() + " {\n");
Method[] methods = className.getDeclaredMethods();
for (Method method :
methods) {
s.append("\t");
s.append(Modifier.toString(method.getModifiers()));
s.append(" ");
s.append(method.getReturnType().getSimpleName());
s.append(" ");
s.append(method.getName());
s.append("(");
for (Class<?> parameterType :
method.getParameterTypes()) {
s.append(parameterType.getSimpleName());
s.append(" ");
}
if (method.getParameterTypes().length > 0) {
s.deleteCharAt(s.length() - 1);
}
s.append(");\n");
}
s.append("}");
System.out.println(s);
}
}
三、反射机制调用对象的方法
反射机制,让代码很具有通用性,可变化的内容都是写在配置文件中,将来修改配置文件之后,创建的对象就不一样了,调用的方法也就不同了,但是java代码不需要做任何的改动,这就是反射机制的魅力
package com.javase.反射机制.Method;
import com.javase.反射机制.bean.UserService;
import java.lang.reflect.Method;
/**
* 重点:必须掌握,通过反射机制如何调用一个对象的方法
*/
public class ReflectMethodTest03 {
public static void main(String[] args) throws Exception {
//不使用反射机制,调用方法
UserService userService = new UserService();
//调用方法的四要素
//要素1:对象,要素2:方法名,要素3:实参列表,要素4:返回值
boolean result = userService.login("admin", "123");
System.out.println(result ? "登录成功" : "登录失败"); //登录成功
userService.logout(); //系统已经安全退出!
//使用反射机制,调用方法
Class<?> className = Class.forName("com.javase.反射机制.bean.UserService");
//创建对象
Object userServiceObj = className.newInstance();
//获取Method
Method loginMethod = className.getDeclaredMethod("login", Class.forName("java.lang.String"), String.class);
//调用方法
//同样反射机制调用方法也需4要素
/**
* 四要素:
* 要素1:方法名,loginMethod
* 要素2:对象,userServiceObj
* 要素3:实参列表,"admin","123"
* 要素4:返回值,retValue
*/
Object retValue = loginMethod.invoke(userServiceObj,"admin","123");
System.out.println((Boolean) retValue ? "登录成功" : "登录失败"); //登录成功
System.out.println(retValue); //true
}
}
第二十一章、java.lang.reflect.Constructor
package com.javase.反射机制.bean;
public class Vip {
int no;
String name;
String birth;
boolean sex;
public Vip() {
}
public Vip(int no) {
this.no = no;
}
public Vip(int no, String name) {
this.no = no;
this.name = name;
}
public Vip(int no, String name, String birth) {
this.no = no;
this.name = name;
this.birth = birth;
}
public Vip(int no, String name, String birth, boolean sex) {
this.no = no;
this.name = name;
this.birth = birth;
this.sex = sex;
}
@Override
public String toString() {
return "Vip{" +
"no=" + no +
", name='" + name + '\'' +
", birth='" + birth + '\'' +
", sex=" + sex +
'}';
}
}
一、常用实例方法
1、int getModifiers():获取构造方法的修饰符,返回一个 int
值,可以使用 Modifier
类的toString()方法解析修饰符(因为修饰符可能有多个)
主要用于获取属性的修饰符
2、Class<?>[] getParameterTypes():获取构造方法的参数类型
主要用于获取方法的参数类型,返回Class类型数组
3、T newInstance(Object ... initargs):调用构造方法来实例化对象
主要用于调用构造方法来new对象
package com.javase.反射机制.Constructor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
/**
* 反编译一个类的Constructor构造方法
*/
public class ReflectConstructorTest01 {
public static void main(String[] args) throws Exception{
StringBuilder s = new StringBuilder();
Class<?> vipClass = Class.forName("com.javase.反射机制.bean.Vip");
s.append(Modifier.toString(vipClass.getModifiers()) + " class " + vipClass.getSimpleName() + "{\n");
Constructor<?>[] constructors = vipClass.getConstructors();
for (Constructor<?> constructor:
constructors) {
s.append("\t");
s.append(Modifier.toString(constructor.getModifiers()));
s.append(" ");
s.append(vipClass.getSimpleName());
s.append(" ");
s.append("(");
//拼接参数
for (Class<?> parameterType :
constructor.getParameterTypes()) {
s.append(parameterType.getSimpleName() + ",");
}
if(constructor.getParameterTypes().length > 0) {
//删除最后下标位置上的字符
s.deleteCharAt(s.length() - 1);
}
s.append("){ }\n");
}
s.append("}");
System.out.println(s);
}
}
二、反射机制创建对象
package com.javase.反射机制.Constructor;
import com.javase.反射机制.bean.Vip;
import java.lang.reflect.Constructor;
/**
* 通过反射机制调用构造方法来实例化对象
*/
public class ReflectConstructorTest02 {
public static void main(String[] args) throws Exception {
//不使用反射机制创建对象
Vip vip = new Vip();
Vip vip1 = new Vip(1);
//使用放射机制创建对象
Class<?> c = Class.forName("com.javase.反射机制.bean.Vip");
//调用无参构造方法
Object obj = c.newInstance();
System.out.println(obj);//Vip{no=0, name='null', birth='null', sex=false}
//调用有参构造
//第一步:先获取到这个有参的构造方法
Constructor<?> con = c.getDeclaredConstructor(int.class, String.class);
//第二步:调用构造方法new对象
Object newObj = con.newInstance(110, "jzq");
System.out.println(newObj); //Vip{no=110, name='jzq', birth='null', sex=false}
//调用无参构造方法
Constructor<?> con1 = c.getDeclaredConstructor();
Object newObj1 = con1.newInstance();
System.out.println(newObj1); //Vip{no=0, name='null', birth='null', sex=false}
}
}
第二十二章、注解Annotation
一、概述
注解,又叫做注释类型,英文单词是Annotation
注解Annotation是一种引用数据类型。编译之后也是生成xxx.class文件
在 Java 中,注解(Annotation)是一种元数据(metadata)机制,它允许我们在代码中添加特定的标记或信息,以提供给编译器、解释器或其他工具使用。注解可以用于类、方法、字段、参数等各种程序元素上,用于描述这些元素的特性、行为或约束
二、定义注解
注解使用 @interface
关键字定义,类似于接口的定义方式。注解可以包含成员变量(称为元素)和默认值,元素可以是基本类型、字符串、枚举、Class 对象、注解类型或这些类型的数组。
语法格式:
[修饰符列表] @interface 注解类名名 {
}
package com.javase.注解Annotation;
public @interface MyAnnotation {
}
三、应用注解
1、语法格式
使用
@注解名称
的形式进行应用
2、使用位置
- 类
- 接口
- 方法
- 成员变量(字段,属性),局部变量
- 方法形参
- 注解类型
可以将注解应用于类、方法、字段、参数等程序元素上,注解还可以出现在注解类型上。
3、举例说明
package com.javase.注解Annotation;
/**
* 默认情况下,注解可以出现在任意位置
*/
@MyAnnotation
public class AnnotationTest01 {
@MyAnnotation
private int no;
@MyAnnotation
public AnnotationTest01(){
}
@MyAnnotation
public static void m1(){
@MyAnnotation
int i = 100;
}
@MyAnnotation
public void m2(@MyAnnotation String name,@MyAnnotation int age){
}
}
@MyAnnotation
interface MyInterface{
}
@MyAnnotation
enum Season {
SPRING,SUMMER,AUTUMN,WINTER
}
package com.javase.注解Annotation;
//注解修饰注解
@MyAnnotation
public @interface OtherAnnotation {
}
四、元注解 Meta-annotation
只能用于修饰注解,不能修饰其他
在 Java 中,元注解(Meta-annotation)是用于定义其他注解的注解。Java 提供了几个内置的元注解,用于在创建自定义注解时指定注解的行为和属性。下面是 Java 中常见的元注解的详细解释:
1、@Target(java.lang.annotation.Target)
1.1、源码:
//Target源码
public @interface Target {
//属性
ElementType[] value();
}
public enum ElementType {
TYPE,
FIELD,
METHOD,
PARAMETER,
LOCAL_VARIABLE,
PACKAGE,
TYPE_PARAMETER,
TYPE_USE,
MODULE,
RECORD_COMPONENT;
}
1.2、参数详解
指定注解的适用目标,即注解可以应用于哪些程序元素。它接受一个 ElementType
的数组作为参数,包括以下取值:
ElementType.TYPE
:类、接口、枚举。ElementType.FIELD
:字段。ElementType.METHOD
:方法。ElementType.PARAMETER
:方法参数。ElementType.CONSTRUCTOR
:构造函数。ElementType.LOCAL_VARIABLE
:局部变量。ElementType.ANNOTATION_TYPE
:注解。ElementType.PACKAGE
:包。
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface MyAnnotation {
// ...
}
这是一个元注解,用来标注“注解类型”的“注解”
这个Target注解用来标注“被注解的注解”可以出现在哪些位置上
例如:
@Target(ElementType.METHOD):表示“被标注的注解”只能出现在方法上
1.3、默认行为
默认的
@Target
: 如果注解没有使用@Target
进行修饰,默认情况下,注解可以应用于任何元素,即可以在类、方法、字段、参数等多种元素上使用。
2、@Retention(java.lang.annotation.Retention)
2.1、源码:
//元注解
public @interface Retention {
//属性
RetentionPolicy value();
}
public enum RetentionPolicy {
SOURCE,
CLASS,
RUNTIME
}
2.2、参数详解
指定注解的保留策略,即注解在什么级别可用。它接受一个 RetentionPolicy
参数,包括以下取值:
RetentionPolicy.SOURCE
:注解仅保留在源代码中,编译时丢弃。RetentionPolicy.CLASS
:注解保留在编译后的字节码文件中,运行时不可获取(默认值)。RetentionPolicy.RUNTIME
:注解保留在编译后的字节码文件中,并在运行时可通过反射获取。
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
// ...
}
2.3、默认行为
默认的
@Retention
: 如果注解没有使用@Retention
进行修饰,默认情况下,注解的保留策略是RetentionPolicy.CLASS
。这意味着注解将在编译时被保留,并存储在生成的字节码文件中,但在运行时不可访问。这是最常见的保留策略。
简而言之,如果注解没有显式地使用 @Target
和 @Retention
进行修饰,则该注解可以应用于任何元素,并且在编译时会被保留在生成的字节码中。这是 Java 中大多数自定义注解的默认行为。
3、案例
package com.javase.注解Annotation.annotation5;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//只允许该注解标注类,和方法
@Target({ElementType.TYPE,ElementType.METHOD})
//这个注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
@MyAnnotation //可以出现在类上
class MyAnnotationTest{
//@MyAnnotation //报错:该注解不能出现在属性上
int i;
//@MyAnnotation //不能出现在构造方法上
public MyAnnotationTest(){}
@MyAnnotation //可以出现在方法上
public static void doSome(){
//@MyAnnotation //不能出现在局部变量上
int a;
}
}
五、JDK内置的常用注解
1、java.lang.Deprecated
在 Java 中,@Deprecated
是一个内置注解,用于标记方法、类、字段或接口已经过时(deprecated)。它用于指示开发者不再推荐使用被标记的元素,因为它可能存在替代方案、存在安全风险或不再符合最新的设计或实现标准。
用Deprecated注释的程序元素,不鼓励程序员使用这样的元素,通过是因为它是过时的,或者有更好的选择
1.1、源码
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
public @interface Deprecated {
/**
* Returns the version in which the annotated element became deprecated.
* The version string is in the same format and namespace as the value of
* the {@code @since} javadoc tag. The default value is the empty
* string.
*
* @return the version string
* @since 9
*/
String since() default "";
/**
* Indicates whether the annotated element is subject to removal in a
* future version. The default value is {@code false}.
*
* @return whether the element is subject to removal
* @since 9
*/
boolean forRemoval() default false;
}
1.2、使用方式
@Deprecated
注解可以应用于类、方法、字段或接口声明之前。
@Deprecated
public class MyClass {
@Deprecated
public void deprecatedMethod() {
// method implementation
}
@Deprecated
private String deprecatedField;
}
1.3、目的
通过标记元素为过时,@Deprecated
注解提醒开发者不要在新代码中使用被标记的元素,以避免潜在的问题和错误。它充当了一种文档和警告的角色,让开发者了解被标记元素的状态和建议。
1.4、替代方案
通常,被标记为过时的元素会有一个建议的替代方案,开发者应该使用替代方案来取代被标记的元素。替代方案可以在注释中提供,或者通过文档或其他方式进行说明。
1.5、使用注意事项
- 被
@Deprecated
注解标记的元素仍然可以正常使用,不会影响其功能。 - 开发者可以选择继续使用被标记的元素,但应该意识到它们存在过时的风险,并考虑将其替换为建议的替代方案。
- 在使用过时的元素时,编译器会发出警告,提醒开发者使用了过时的代码。
使用 @Deprecated
注解可以帮助开发者识别和避免使用过时的元素,以维护代码的健康和可维护性。这样做可以鼓励开发者使用最新的、更好的解决方案,并促进代码库的升级和改进。
package com.javase.注解Annotation;
/**
* @Deprecated
* 这个注解标注的元素已过时
* 这个注解主要是向其他程序员传达一个信息,告知已过时,有更好的解决方案
*
* @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, MODULE, PARAMETER, TYPE})
* 表示Deprecated注解可以出现在:
* 构造方法上
* 字段上
* 局部变量上
* 方法上
* 包上
* 模块上
* 方法形参上
* 类,接口,枚举上
*/
//表示这个类已过时
@Deprecated
public class AnnotationTest03 {
public static void main(String[] args){
AnnotationTest03.doSome();
}
@Deprecated
public static void doSome(){
System.out.println("do something");
}
@Deprecated
public void doOther() {
System.out.println("do Other");
}
}
class Test{
public static void main(String[] args) {
AnnotationTest03 at = new AnnotationTest03();
at.doOther();
}
Class<?> name;
{
try {
name = Class.forName("java.util.Date");
name.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、java.lang.Override
在 Java 中,@Override
是一个内置注解,用于标记方法覆盖(Override)了父类中的方法。它提供了一种机制,让编译器在编译时检查该方法是否正确地覆盖了父类的方法,以避免潜在的错误。
表示一个方法声明打算重写父类中的另一个方法声明
2.1、源码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
2.2、使用方式
@Override
注解应用于子类中的方法,用于指示该方法是对父类方法的重写。它必须放在方法声明之前。
@Override
public void methodName() {
// method implementation
}
2.3、检查重写
当使用 @Override
注解时,编译器会在编译时检查该方法是否正确地覆盖了父类中的方法。如果存在以下情况之一,编译器将发出错误:
- 父类中不存在对应的方法。
- 方法签名(包括方法名称、参数列表和返回类型)与父类中的方法不匹配。
- 父类中的方法被标记为
final
或private
,无法被重写。 - 方法重写时的异常类型与父类方法不兼容。
2.4、安全性
使用 @Override
注解可以提高代码的安全性和可读性。它帮助开发人员明确表示他们意图重写一个方法,并在代码被修改时,能够及时发现潜在的错误。如果不小心使用了错误的方法签名,编译器将会给出错误提示。
2.5、注意事项
@Override
注解只能应用于方法,不能应用于其他程序元素,如字段或类。@Override
注解是可选的,但推荐在重写父类方法时使用,以提高代码的可维护性和可读性。- 在使用
@Override
注解时,方法签名必须与父类中被重写的方法完全匹配,包括方法名称、参数列表和返回类型。 - 当覆盖接口中的方法时,使用
@Override
注解是可选的,但推荐使用,以增加代码的清晰度。
使用 @Override
注解可以帮助开发人员更轻松地检测和修复方法覆盖的错误,从而提高代码的质量和可维护性
package com.javase.注解Annotation;
/**
* 关于java.lang包下的Override注解
*
* 标识性注解,给编译器做参考的
* 编译器看到这个方法上有这个注解的时候,编译器会自动检查该方法是否重写了父类的方法
* 如果没有重写,报错
*
* 这个注解只是在编译阶段起作用,和运行阶段无关
*
* @Override这个注解只能注解方法
* @Override这个注解是给编译器参考的,和运行阶段没有关系
* 凡是java中的方法带有这个注解的,编译器都会进行编译检查,如果这个方法不是重写父类的方法,编译器报错
*/
public class AnnotationTest02 {
@Override
public String toString() {
return "AnnotationTest02{}";
}
public static void main(String[] args) {
}
}
六、注解中的属性
在 Java 中,注解是一种元数据,用于为类、方法、字段等元素添加额外的信息。注解可以用来标记代码,也可以在运行时获取并处理这些标记,以实现特定的功能。注解的属性是注解的一部分,它们用于传递更多的信息和配置选项。
1、定义属性
在自定义注解中,你可以定义属性。属性的定义类似于接口中的方法声明,不同之处在于属性可以有默认值,并且在使用注解时可以通过属性赋值来传递信息。定义属性的语法如下:
如果一个注解中有属性,则必须给属性赋值(除非给该属性赋default默认值 ),属性名 = 属性值(多个属性之间用逗号隔开)
package com.javase.注解Annotation.annotation2;
public @interface MyAnnotation1 {
/**
*我们通常在注解当中可以定义属性,以下这个是MyAnnotation中的name属性
* 看着像接口中的方法声明,但是实际上我们称之为属性name
*/
String name();
//颜色属性
String color();
//年龄属性
int age() default 25; //属性指定默认值
}
class MyAnnotationTest1{
//报错原因:如果一个注解中有属性,则必须给属性赋值
/*@MyAnnotation1()
public void doSome(){
}*/
// @MyAnnotation1(属性名 = 属性值,属性名 = 属性值,属性名 = 属性值)
//指定name属性的值就好了
@MyAnnotation1(name = "jzq",color = "red")
public void doSome(){
}
}
2、属性默认值default
定义属性默认值后可不为属性传值,则使用默认值,传值后则使用传入的属性值
在定义注解属性时,可以为属性指定默认值。当使用注解时,如果没有为属性提供值,则将使用默认值。示例中的 default
关键字用于指定属性的默认值。
3、有且仅有一个value属性,可省略属性名value
package com.javase.注解Annotation.annotation2;
/**
* 如果一个注解的属性名是value,并且只有一个属性的话,该属性名可以省略
*/
public @interface MyAnnotation2 {
//指定一个value属性
String value();
// String email();
}
class MyAnnotationTest2{
//报错原因:没有指定属性值
/*@MyAnnotation2()
public void doSome(){
}*/
@MyAnnotation2(value = "jzq")
public void doSome(){
}
@MyAnnotation2("haha")
public void doOther(){
}
}
有且仅有一个,但是属性名不是value,也不能省略
package com.javase.注解Annotation.annotation2;
public @interface OtherAnnotation2{
String name();
}
//报错了,因为属性名是name,不能省略
//@OtherAnnotation2("haha")
class Test{
//正确的
@OtherAnnotation2(name = "jzq")
public void doSome(){
}
}
4、注解中属性的可选类型
属性的类型可以是:
byte, short, int, long, float, double, boolean, char, String, class, 枚举类型
以及以上每一中类型的数组的形式
package com.javase.注解Annotation.annotation2;
/**
* 注解当中的属性可以是哪一种类型
* byte, short, int, long, float, double, boolean, char, String, class, 枚举类型
*
* 以及以上每一中类型的数组的形式
*/
public @interface MyAnnotation3 {
byte value1();
short value2();
int value3();
long value4();
float value5();
double value6();
boolean value7();
char value8();
String value9();
Season value10();
Class<?> value11();
}
5、属性为数组类型
如果数组中只有一个元素,大括号可省略
package com.javase.注解Annotation.annotation2;
public @interface OtherAnnotation3 {
//年龄属性
int age();
//邮箱属性,数组
String[] email();
//季节数组,Season是枚举类型
Season[] seasonArray() default {};
}
class OtherAnnotationTest3{
//数组是大括号
@OtherAnnotation3(age = 27,email = {"123","456"},seasonArray = Season.WINTER)
public void doSome(){
}
//如果数组中只有一个元素,大括号可以省略
@OtherAnnotation3(age = 27,email = "123",seasonArray = {Season.SPRING,Season.SUMMER})
public void doOther(){
}
}
七、使用reflect反射机制获取注解
1、Class对象的实例方法
1.1、boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
判断注解是否在类上面
1.2、<A extends Annotation> A getAnnotation(Class<A> annotationClass)
获取注解对象
2、Method对象的实例方法
2.1、boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
判断注解是否在类上面
2.2、<A extends Annotation> A getAnnotation(Class<A> annotationClass)
获取注解对象
3、注解对象常用实例方法
3.1、属性名():获取该属性的值
属性名是value,就是value()方法
属性名是name,就是name()方法
返回类型为该属性声明时的类型
package com.javase.注解Annotation.annotation5;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//只允许该注解标注类,和方法
@Target({ElementType.TYPE, ElementType.METHOD})
//这个注解可以被反射
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
String value() default "西安雁塔区";
}
@MyAnnotation("甘肃庆阳") //可以出现在类上
class MyAnnotationTest {
//@MyAnnotation //报错:该注解不能出现在属性上
int i;
//@MyAnnotation //不能出现在构造方法上
public MyAnnotationTest() {
}
@MyAnnotation //可以出现在方法上
public static void doSome() {
//@MyAnnotation //不能出现在局部变量上
int a;
}
}
package com.javase.注解Annotation.annotation5;
public class ReflectAnnotationTest {
public static void main(String[] args) throws Exception{
//获取类
Class<?> name = Class.forName("com.javase.注解Annotation.annotation5.MyAnnotationTest");
//判断类上面是否有@MyAnnotation
System.out.println(name.isAnnotationPresent(MyAnnotation.class)); //true
if(name.isAnnotationPresent(MyAnnotation.class)){
//获取该注解对象
MyAnnotation myAnnotation = name.getAnnotation(MyAnnotation.class);
System.out.println("类上面的注解对象: " + myAnnotation); //@com.javase.注解Annotation.annotation5.MyAnnotation("\u897f\u5b89\u96c1\u5854\u533a")
//获取注解对象的属性
String value = myAnnotation.value();
System.out.println(value); //西安雁塔区 甘肃庆阳
}
Class<?> stringClass = Class.forName("java.lang.String");
System.out.println(stringClass.isAnnotationPresent(MyAnnotation.class)); //false
}
}
4、反射机制获取属性值
package com.javase.注解Annotation.annotation6;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
//username属性
String username();
//password属性
String password();
}
class MyAnnotationTest {
@MyAnnotation(username = "admin",password = "123")
public void doSome(){
}
public static void main(String[] args) throws Exception{
//获取MyAnnotation中的doSome()方法的注解信息
//获取类
Class<?> c = Class.forName("com.javase.注解Annotation.annotation6.MyAnnotationTest");
//获取方法
Method doSomeMethod = c.getDeclaredMethod("doSome");
if(doSomeMethod.isAnnotationPresent(MyAnnotation.class)){
MyAnnotation myAnnotation = doSomeMethod.getAnnotation(MyAnnotation.class);
System.out.println(myAnnotation.username()); //admin
System.out.println(myAnnotation.password()); //123
}
}
}
八、注解在实际开发中作用
需求:
假设有这样一个注解,叫做:@Id
这个注解只能出现在类上面,当这个类上面有这个注解的时候,要求这个类中必须有一个int类型的id属性,如果没有这个属性,就报异常,如果有则正常执行
package com.javase.注解Annotation.注解在实际开发中的作用;
/**
* 自定义运行时异常
*/
public class HasNotIdPropertyException extends RuntimeException{
public HasNotIdPropertyException(){
super();
}
public HasNotIdPropertyException(String message){
super(message);
}
}
package com.javase.注解Annotation.注解在实际开发中的作用;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
//表示该注解可以被反射机制读取
@Retention(RetentionPolicy.RUNTIME)
//表示该注解只能出现在类上面
@Target(ElementType.TYPE)
public @interface Id {
// int id();
}
@Id
class User {
int id;
// String id;
String username;
String password;
}
class Test{
public static void main(String[] args) {
//获取类
Class<?> c = null;
try {
c = Class.forName("com.javase.注解Annotation.注解在实际开发中的作用.User");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
boolean isOk = false;
//判断类上是否存在@Id注解
if(c.isAnnotationPresent(Id.class)){
//当一个类上有@Id注解的时候,要求类中必须存在int id属性
//如果没有id属性,则报异常
//获取id属性
Field[] fields = c.getDeclaredFields();
for (Field field:
fields) {
if("id".equals(field.getName()) && "int".equals(field.getType().getSimpleName())){
//表示这个类是合法的类,有@Id注解,则这个类中必须有int类型的id属性
isOk = true; //表示合法
break;
}
}
if(!isOk){
throw new HasNotIdPropertyException("被@Id注解标注的类必须有int id属性");
}
}
}
}