注解(Annotation)
举个例子就是你重写方法时候上面哪个东西----> (@Override)
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Chicken(i));
System.out.println("生产了" + i + "只鸡");
}
}
}
我们看一下注解源码
这里我们可以知道注解定义的格式是
public @interface 注解名 {
}
方便我们下面自定义注解。格式上面那两个注解是元注解,后面会讲到
内置注解
3个常见的内置注解
当你的代码有警告时,可以使用@SuppressWarnings()来去除警告
元注解(meta-annotation)
作用:注解其他的注解
@Target()
需要传一个参数,打开源码我们可以看到能传进去的参数有很多
补充:
1.TYPE表示可以使用在类上,接口上...(自己看注释),METHOD表示可以使用在方法上
2.写的格式(value可省略)
@Target(value = {ElementType.TYPE})
@Target(value = {ElementType.TYPE,ElementType.ANNOTATION_TYPE})
@Retention()
同样需要参数
作用范围大小:RUNTIME>CLASS>SOURCE
我们自定义注解一般都写RUNTIME
写的格式和上面一样就不多写了,只不过要把ElementType换成@Retention()对应的RetentionPolicy
补充:自己敲的时候不用写ElementType,RetentionPolicy,直接敲参数就行了
@Documented
不需要参数,作用上面有解释
@Inherited
不需要参数,作用上面有解释
自定义注解
格式:
@interface 注解名 {
}
定义了参数不写参数就会报错,跟元注解一样,但可以设定默认值
public class Test01 {
@MyAnnotation(name = "xiaoMing")
public void test(){
}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation{
//注解的参数 : 参数类型 + 参数名 + ();
String name();
int id() default -1;
String[] school() default {"五邑大学"};
}
如果只有一个参数,建议使用value命名,因为使用value命名,且只有一个参数的时候,显示赋值的时候可以不写value = xxx(参数),直接写参数名就好了,这也解释了上面元注解为什么不用写value的原因
public class Test01 {
@MyAnnotation2("xiaoHong")
public void test(){
}
}
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation2{
String value();
}
反射(Reflectoin)
静态语言和动态语言
Java Reflection
反射的优点和缺点
反射主要API
这里的方法是Class,C大写,我们定义一个类的时候是class,c是小写的
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
//通过反射获取类的Class对象
//要抛出异常,因为程序不知道返回什么类
Class c1 = Class.forName("com.nomad.java.User");
System.out.println(c1);
}
}
//实体类
class User{
private String name;
private int id;
private int age;
public User() {
}
public User(String name, int id, int age) {
this.name = name;
this.id = id;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
", age=" + age +
'}';
}
}
可以发现有很多方法
重点:一个类在内存中只有一个Class对象,一个类被加载后,类的整个结构都会被封装在Class对象中
他已经帮你new好这个对象了,并且只有一个对象,所以你自己无论创建多少个引用都是指向同一个对象
Class c2 = Class.forName("com.nomad.java.User");
Class c3 = Class.forName("com.nomad.java.User");
Class c4 = Class.forName("com.nomad.java.User");
System.out.println(c2.hashCode());
System.out.println(c3.hashCode());
System.out.println(c4.hashCode());
发现打印的hashCode结果都一样,证明是同一个对象
补充:
1.自动匹配变量快捷键:alt+enter
Class类
正常是写一个类,然后new一个对象(从上往下)
反射就是通过对象来获得你这个类的信息(从下往上)
Class类的常用方法
获取Class类实例的几种方法
自己写代码练习一下
public class Test03 {
public static void main(String[] args) throws ClassNotFoundException {
//父类引用指向子类对象
Person person = new Student();
System.out.println(person.name);
//方法1,已知具体的类,通过类名.class
Class c1 = Student.class;
System.out.println(c1);
//方法2,已知某个类的实例,通过实例.getClass()
Class c2 = person.getClass();
System.out.println(c2);
//方法3,通过forName方法,但你得知道包名
Class c3 = Class.forName("com.nomad.java.Student");
System.out.println(c3);
//方法4,基本内置类型的包装类都有一个TYPE属性
Class c4 = Integer.TYPE;
System.out.println(c4);
//补充,获得父类类型
Class c5 = c1.getSuperclass();//c2,c3都可以,证明了只要是指向子类的Class对象都可以通过.getSuperclass()来获得父类类型
System.out.println(c5);
}
}
class Person{
public String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
class Student extends Person{
public Student() {
this.name = "学生";
}
}
class Teacher extends Person{
public Teacher() {
this.name = "老师";
}
}
补充:重写toString方法-->在你直接打印对象名时候有用,方便查看属性
可以有Class对象的类型
public class Test04 {
public static void main(String[] args) {
//通过类名.class来查看是否有Class对象
Class c1 = Object.class;//类
Class c2 = Comparable.class;//接口
Class c3 = String[].class;//一维数组
Class c4 = int[][].class;//二维
Class c5 = Override.class;//注解
Class c6 = ElementType.class;//枚举
Class c7 = Integer.class;//基本数据类型
Class c8 = void.class;//void
Class c9 = Class.class;//Class
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
}
}
运行结果
class java.lang.Object
interface java.lang.Comparable
class [Ljava.lang.String;
class [[I
interface java.lang.Override
class java.lang.annotation.ElementType
class java.lang.Integer
void
class java.lang.Class
类加载的过程
加载,链接,初始化
注意:
1.链接的准备过程,此时会给静态变量赋默认值(int类型 = 0,String类型 = null等等)
2.初始化时会合并类中所有类变量的赋值动作和静态代码块的语句,就像这样
3.静态代码块和静态代码执行取决于编写顺序
类加载时内存图
类的初始化
public class Test05 {
static {
System.out.println("main加载");
}
public static void main(String[] args) {
//1.主动引用
Son son = new Son();
//2.反射
Class.forName("com.nomad.java.Son");
//3.不会初始化的操作
System.out.println(Son.b);
Son[] sons = new Son[10];
System.out.println(Son.n);
}
}
class Father{
static int b = 100;
static {
System.out.println("父类加载");
}
}
class Son extends Father{
static{
System.out.println("子类加载");
}
static int m = 100;
static final int n = 200;
}
1和2的执行结果(new对象和反射执行结果一样,但是只会执行一次,因为这些信息只会加载一次)
main加载
父类加载
子类加载
3.的执行结果
System.out.println(Son.b);---->main加载,父类加载,100
Son[] sons = new Son[10];---->main加载
System.out.println(Son.n);---->main加载,200
总结:一般是new对象和反射时才会初始化
类加载器(加载那一步)
类加载器的类型
左边那个东西叫做双亲委派机制
双亲委派机制,是防止同名包、类与 jdk 中的相冲突,实际上加载类的时候,先通知 appLoader,看 appLoader 是否已经缓存,没有的话,appLoader 又委派给他的父类加载器(extLoader)询问,看他是不是能已经缓存加载,没有的话,extLoader 又委派他的父类加载器(bootstrapLoader)询问,BootstrapLoader看是不是自己已缓存或者能加载的,有就加载,没有再返回 extLoader,extLoader 能加载就加载,不能的话再返回给 appLoader 加载,再返回的路中,谁能加载,加载的同时也加缓存里。正是由于不停的找自己父级,所以才有 Parents 加载机制,翻译过来叫 双亲委派机制。
获取运行时类的结构
首先得创建你要获得的类的Class对象
Class c1 = Class.forName("com.nomad.java.User");
1.获得类的名字
//获得包名 + 类名
c1.getName()
//获得类名
c1.getSimpleName();
2.获得类的属性
//获得全部的公共属性(public)
c1.getFields();
//获得全部属性
c1.getDeclaredFields();
//获得指定的公共(public)属性
c1.getField("name");
//获得任意指定属性
c1.getDeclaredField("name");
3.获得类的方法
//获得本类以及父类的所有公共方法(public)
c1.getMethods();
//获得本类全部方法
c1.getDeclaredMethods();
//获得指定方法,如果有方法有参数就要填参数类型.class,没有就填null
c1.getMethod("getName", null);
c1.getMethod("setName", String.class);
4.获得类的构造器
//获得公共构造器
c1.getConstructors();
//获得全部构造器
c1.getDeclaredConstructors();
//获得指定构造器,要哪个构造器就传对应的参数进去
c1.getDeclaredConstructor(String.class, int.class, int.class);
还有其他的,比如获得注解啊啥的,就不写了
Class对象的具体应用
invoke
setAccessible
上代码
public class Test07 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//通过反射动态创建对象
Class c1 = Class.forName("com.nomad.java.User");
//一开始是这样的--->Object o = c1.newInstance();(通过alt + 回车的方式补充匹配变量的话)
//因为我们知道是什么类型的,所以强转就好了
//用这个方法有要求1.有无参构造器2.访问权限要够
User user = (User) c1.newInstance();//本质上是调用了类的无参构造器
System.out.println(user);
//不需要无参构造器的方法--->自己调用一个构造器就好了
//通过构造器创建对象
Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
//同样需要强转
User user1 = (User) constructor.newInstance("xiaoHong", 18, 100);
System.out.println(user1);
//通过反射调用方法
User user2 = (User) c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
//invoke:激活,传入的参数(对象,"方法的参数")
setName.invoke(user2, "xiaoMing");//调用方法的语句
System.out.println(user2);
//通过反射调用属性
User user3 = (User) c1.newInstance();
Field name = c1.getDeclaredField("name");
//这里直接运行会报错,因为不能直接设置私有的属性和方法,通过setAccessible方法就可以(设置为true)
name.setAccessible(true);
name.set(user3, "xiaoHua");
System.out.println(user3);
}
}
反射方式调用方法的性能分析
通过不停的调用同一个方法,看运行的时间
public class Test08 {
//普通方式调用
public static void test01(){
User user = new User();
//获取当前时间
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
user.getName();
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式需要" + (endTime - startTime) + "ms");
}
//反射方式调用
public static void test02() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
//获取当前时间
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式需要" + (endTime - startTime) + "ms");
}
//关闭检测的反射方式调用
public static void test03() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
User user = new User();
Class c1 = user.getClass();
Method getName = c1.getDeclaredMethod("getName", null);
getName.setAccessible(true);
//获取当前时间
long startTime = System.currentTimeMillis();
for (int i = 0; i < 1000000000; i++) {
getName.invoke(user, null);
}
long endTime = System.currentTimeMillis();
System.out.println("普通方式需要" + (endTime - startTime) + "ms");
}
public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
test01();
test02();
test03();
}
}
执行结果
普通方式需要3ms
反射方式需要2634ms
关闭检测的反射方式需要1020ms
结论:关闭检测可使得效率变高
通过反射操作泛型
先跳过了
通过反射操作注解
练习ORM
Java中的类和数据库中的表的映射
public class Test09 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.nomad.java.Student2");
//通过反射获得注解(这里是连注解以及注解的值一起获取了)
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
//获得注解value的值
//Annotation annotation = c1.getAnnotation(TableMad.class);
//一开始是上面那样,强转一下就好了
TableMad tableMad = (TableMad) c1.getAnnotation(TableMad.class);
//通过对象.value可以获取value的值
String value = tableMad.value();
System.out.println(value);
//获得指定的注解
//先获得指定的属性
Field name = c1.getDeclaredField("id");
//获得该指定属性的注解
FieldMad annotation = name.getAnnotation(FieldMad.class);
System.out.println(annotation.columnName());
System.out.println(annotation.type());
System.out.println(annotation.length());
}
}
@TableMad("db_Student")//数据库表名
class Student2{
@FieldMad(columnName = "db_id", type = "int", length = 10)
private int id;
@FieldMad(columnName = "db_age", type = "int", length = 10)
private int age;
@FieldMad(columnName = "db_name", type = "varchar", length = 3)
private String name;
public Student2() {
}
public Student2(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
//类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TableMad{
//数据库表名
String value();
}
//属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface FieldMad{
//数据库列名
String columnName();
//数据库类型
String type();
//数据库长度
int length();
}
执行结果
@com.nomad.java.TableMad("db_Student")
db_Student
db_id
int
10
补充:增强for快捷键
数组名.for
看一天终于看完了,迷迷糊糊的,有空多复习几遍,感觉就是学了很多方法,但是方法具体实现原理不知道
自己总结的一点心得:
不同类型的引用也能调用相同的方法,因为那个引用的类重写了相同的方法