Java知识整理——反射、枚举、注解、泛型

本文详细介绍了Java中的反射机制,包括类的加载、初始化时机以及类加载器。接着讲解了反射的应用,如获取构造方法、成员变量和方法。此外,还探讨了枚举、注解的使用,以及代理模式和泛型的概念和优势。通过这些核心概念,有助于深入理解Java的动态性和类型安全特性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(1)、类的加载

当程序要执行某个类的时候,如果这个类还没有被加载到内存中,系统会通过加载、链接、初始化三步来实现对这个类的初始化。

加载: 加载就是把class文件读入到内存中,并对应的创建一个Class对象,任何类被使用的时候系统都会建立一个Class对象 链接: 验证 内部结构是否是正确的,并且和其他类协调一致 准备 负责为类的静态成员分配内存空间,并设置默认初始化值 解析 把类的二进制数据中的符号引用替换为直接引用 初始化: 执行静态代码块给静态变量初始化

(2)、类的初始化时机

创建类的实例 访问类的静态变量,或者在给静态变量赋值 调用类的静态方法 使用反射方式来创建某个类或者接口对应的Class对象 初始化某个类的子类 使用java命令运行某个主类

(3)、类加载器

负责把.class文件加载到内存中,并生成对应的Class对象。

类加载器的组成:

根类加载器(引导类加载器、 Bootstrap ClassLoader):负责java核心类的加载。String、System、double……等,在jdk中的jre中的lib中的rt.jar。

扩展类加载器(Extension ClassLoader):负责jre的扩展目录中jar包的加载,在jdk中的jre中的lib中的ext目录下

系统类加载器(System ClassLoader):负责在jvm启动时加载来自java命令的class文件。

(4)反射

就是通过class文件对象,去使用该文件中的成员变量、成员方法和构造方法。先要得到这个class文件对象,其实就是要得到Class(class是Class的实例)类的对象。

获取class文件对象的方式:Object类中的getClass()方法;数据类型的静态属性class;Class类中的静态方法public static Class forName(String className)

开发用第三种,因为不是具体的类名,这样我们就可以把这个字符串写到配置文件中去。

构造方法

public Constructor<?>[] getConstructors()获取所有共有的构造方法

public Constructor<?>[] getDeclaredConstructors()获取所有的构造方法

创建对象

public T newInstance()

获取单个的构造方法

public Constructor<T> getConstructor(Class<?>... parameterTypes),获取单个公共的构造方法对象,参数表示的是:你要获取的构造方法的构造参数个数以及参数的数据类型的class字节码文件对象

public Constructor<T> getDeclaredConstructor(类<?>... parameterTypes),获取私有构造方法对象

public void setAccessible(boolean flag),将此对象的accessible标志设置为指示的布尔值。 true的值表示反射对象应该在使用时抑制Java语言访问检查,false的值表示反映的对象应该强制执行Java语言访问检查。

Constructor con = c.getConstructor(),返回的是无参共有构造方法对象

public T newInstance(Object... initargs),使用由此Constructor对象表示的构造函数,使用指定的初始化参数创建和初始化构造函数的声明类的新实例。

Constructor con = c.getConstructor(String.class,int.class,String.class),获取带参构造对象 
Object obj = con.newInstance("AA",18,"帅哥"),通过带参构造方法对象创建对象

Constructor con = c.getDeclaredConstructor(),获取私有构造

con.setAccessible(true),取消java语言访问检查,没有则会报异常。

java.lang.IllegalAccessException:非法的存取异常

获取所有成员对象

public Field[] getDeclaredFields(),包括私有

public Field[] getFields(),公有

获取单个成员对象的

public Field getDeclaredField(String name),包括私有

public Field getField(String name),公有

设置成员值

public void set(Object obj,Object value),将指定对象参数上的此Field对象表示的字段设置为指定的新值

Constructor con = c.getConstructor();//通过无参构造方法创建对象并赋值
Object obj = con.newInstance();
System.out.println(obj);
Field nameField = c.getDeclaredField("name");//给指定对象上的Field对象表示的字段属性设置指定的值
nameField.setAccessible(true);
nameField.set(obj, "AAA");//给obj对象的nameField属性赋值。
Student s = (Student)obj;
System.out.println(s.getName());

获取所有方法对象的方法

public 方法[] getDeclaredMethods()获取所有的自己的方法

public 方法[] getMethods()获取所有共有的方法包括父类的公共方法

获取单个方法对象的方法

public 方法 getMethod(String name,类<?>... parameterTypes)

public 方法 getDeclaredMethod(String name,类<?>... parameterTypes)

【注:返回的是方法对象】

Method中的方法:public Object invoke(Object obj,Object... args),2个参数,第一个表示要执行这个方法的对象,第二个参数是调用这个方法时需要传递的实际参数,返回值Object代表的是方法执行完毕后的返回值的类型

Method m1 = c.getMethod("show");获取该类单个无参无返回值公共类型的成员方法,无参的成员方法,只需写成员方法的名称       
Method m2 = c.getMethod("method", String.class); 获取该类单个有参有返回值公共类型的成员方法

 代码

public class Demo22 {

    public static void main(String[]args) throws Exception{
        Class<Student> student = (Class<Student>) Class.forName("Student");
        Constructor<Student> constructor = student.getDeclaredConstructor();
        Student o = constructor.newInstance();
        o.setName("笑话");
        o.setAge(18);
        Method toString = student.getMethod("toString");
        System.out.println(toString.invoke(o));
    }
}
class Student{
    private String name;
    public int age;

    public Student() {

    }

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age &&
                Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

(5)、代理模式

在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。

在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。

意图:为其他对象提供一种代理以控制对这个对象的访问。

主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。

何时使用:想在访问一个类时做一些控制。

如何解决:增加中间层。

关键代码:实现与被代理类组合。

应用实例: 1、Windows 里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop。

优点: 1、职责清晰。 2、高扩展性。 3、智能化。

缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。

 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。

实现:

1)创建一个接口。
public interface Image {
   void display();
}
2)创建实现接口的实体类。
public class RealImage implements Image {
   private String fileName;
   public RealImage(String fileName){
      this.fileName = fileName;
      loadFromDisk(fileName);
   }
 
   @Override
   public void display() {
      System.out.println("Displaying " + fileName);
   }
 
   private void loadFromDisk(String fileName){
      System.out.println("Loading " + fileName);
   }
}
//===
public class ProxyImage implements Image{
   private RealImage realImage;
   private String fileName;
 
   public ProxyImage(String fileName){
      this.fileName = fileName;
   }
 
   @Override
   public void display() {
      if(realImage == null){
         realImage = new RealImage(fileName);
      }
      realImage.display();
   }
}
3)当被请求时,使用 ProxyImage 来获取 RealImage 类的对象。
public class ProxyPatternDemo {
   
   public static void main(String[] args) {
      Image image = new ProxyImage("test_10mb.jpg");
 
      // 图像将从磁盘加载
      image.display(); 
      System.out.println("");
      // 图像不需要从磁盘加载
      image.display();  
   }
}
4)执行程序,输出结果
Loading test_10mb.jpg
Displaying test_10mb.jpg

Displaying test_10mb.jpg

(6)、动态代理

代理模式是经常用到的设计模式,代理模式是给指定对象提供代理对象。由代理对象来控制具体对象的引用。

静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。

动态代理:在程序运行时,运用反射机制动态创建而成。

在java中java.lang.reflect包下提供了一个类Proxy和一个接口InvocationHandler通过这个类和这个接口就可以生成动态的代理对象。

Proxy类中方法可以创建动态代理对象

public static Object newProxyInstance(ClassLoader loader,类<?>[] interfaces, InvocationHandler h)

ClassLoader对象:定义由哪个ClassLoader对象来对生成的代理对象进行加载

Interface对象数组:将要给需要代理的对象提供什么接口,如果提供一组接口,那么这个代理对象就实现了该接口,这样就可以调用接口中的方法了。

InvocationHandler对象:表示的是当这个动态代理对象在调用方法时,会关联到哪一个InvocationHandler对象上。

每一个动态代理类都必须实现InvocationHandler接口,每个代理类的实例都要关联到一个Handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发由InvocationHandler这个接口的invoke方法来进行调用。

最终会调用InvocationHandler的方法

Object invoke(Object proxy,方法 method,Object[] args)

proxy:表示动态代理的对象 method:表示要执行的方法 args:表示调用方法时需要传递的参数

Proxy.newProxyInstance:

创建的代理对象是在jvm运行时动态生成的一个对象。

命名方式:以$Proxy开头,最后的数字表示对象的编号  $Proxy0

interface Subject {
    void action();
}

//被代理类
class RealSubject implements Subject {
    public void action() {
        System.out.println("这是被代理类!");
    }
}

class MyInvocationHandler implements InvocationHandler {
    Object obj;//实现了接口的被代理类的对象的声明

    public Object blind(Object obj) {//给被代理类的对象实例化,返回一个代理类对象
        this.obj = obj;
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
    }

    //每当代理类调用被重写的方法的时候就会转为对invoke方法的调用
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object invoke = method.invoke(obj, args);//返回值相当于methon方法的返回值
        System.out.println("哈哈!");
        return invoke;
    }
}

class DemoTeat {
    public static void main(String[] args) {
    //1.创建被代理类的对象
        RealSubject real = new RealSubject();
    //2.创建一个实现了InvocationHandler接口类的对象
        MyInvocationHandler handler = new MyInvocationHandler();
    //3.调用blind方法动态返回一个同样实现了real所在类实现的接口Subject的代理类的对象
        Object obj = handler.blind(real);
        Subject sub = (Subject) obj;//此时的sub就是代理类的对象
        sub.action();//转到对InvocationHandler接口的实现类的invoke方法的调用
    }
}

(7)、枚举类

JDK1.5之前需要自定义枚举类,JDK 1.5 新增的enum关键字用于定义枚举类,若枚举只有一个成员, 则可以作为一种单例模式的实现方式

枚举类:类的对象是有限个的,确定的。私有化类的构造器,保证不能在类的外部创建其对象

在类的内部创建枚举类的实例。声明为:public static final

若类有属性,那么属性声明为:private final此属性在构造器中赋值

public static class EnumSeason {
    //1.提供类的属性,声明为private final
    private final String seasonName;
    private final String seasonDesc;

    //2.声明为final的属性,在构造器中初始化
    private EnumSeason(String seasonName, String seasonDesc) {
        super();
        this.seasonName = seasonName;
        this.seasonDesc = seasonDesc;
    }

    //3.通过公共的方法来调用属性
    public String getSeasonName() {
        return seasonName;
    }

    public String getSeasonDesc() {
        return seasonDesc;
    }

    //4.创建枚举类的对象
    public static final EnumSeason SPRING = new EnumSeason("spring", "穿暖花开");
    public static final EnumSeason SUMMER = new EnumSeason("summer", "县日炎炎");

    public String toString() {
        return "EnumSeason [seasonName=" + seasonName + ", seasonDesc=" + seasonDesc + "]";
    }
}

使用enum关键字定义枚举类

必须在枚举类的第一行声明枚举类对象。

枚举类和普通类的区别:

使用enum定义的枚举类默认继承了java.lang.Enum 类 枚举类的构造器只能使用private访问控制符 枚举类的所有实例必须在枚举类中显式列出(,分隔;结尾),列出的实例系统会自动添加 public static final 修饰

JDK 1.5 中可以在switch表达式中使用enum定义的枚举类的对象作为表达式,case子句可以直接使用枚举值的名字, 无需添加枚举类作为限定

枚举类似类,一个枚举可以拥有成员变量,成员方法,构造方法

enum Type{
    A,B,C,D;
} 

创建enum时,编译器会自动为我们生成一个继承自java.lang.Enum的类,我们上面的enum可以简单看作:

class Type extends Enum{
    public static final Type A;
    public static final Type B;
    ...
}

可以把Type看作一个类,而把A,B,C,D看作类的Type的实例。

构建实例的过程不是我们做的,一个enum的构造方法限制是private的,也就是不允许我们调用。

在enum中,我们可以定义类和实例的变量以及方法。

enum Type{
    A,B,C,D;
    static int value;
    public static int getValue() {
        return value;
    }
    String type;
    public String getType() {
        return type;
    }
}

把Type看做一个类,那么enum中静态的域和方法,都可以视作类方法。和我们调用普通的静态方法一样,这里调用类方法也是通过 Type.getValue()即可调用。

enum Type{
A{
    public String getType() {
        return "I will not tell you";
    }
},B,C,D;
static int value;
public static int getValue() {
    return value;
}
String type;
public String getType() {
    return type;
 }
}
//还可以添加抽象方法在enum中,强制ABCD都实现各自的处理逻辑

常用的方法

values()示例

public static void main(String[] args) {
    EnumSeason[] values = EnumSeason.values();
    for (EnumSeason value : values) {
        System.out.println(value.seasonName + value.seasonDesc);
    }
}

valueOf(String name)示例

EnumSeason[] values = EnumSeason.values();
for (EnumSeason value : values) {
    System.out.println(value.seasonName + value.seasonDesc);
}
EnumSeason spring = EnumSeason.valueOf("SPRING");
System.out.println(spring);//EnumSeason [seasonName=spring, seasonDesc=穿暖花开]

(8)、注解Annotation

从JDK 5.0开始,Java增加了对元数据(MetaData)的支持,也就是Annotation(注解)

Annotation其实就是代码里的特殊标记,这些标记可以在编译,类加载,运行时被读取, 并执行相应的处理。通过使用 Annotation, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息

Annotation可以像修饰符一样被使用,可用于修饰包,类,构造器,方法,成员变量,参数, 局部变量的声明,这些信息被保存在 Annotation 的“name=value”对中

Annotation 能被用来为程序元素(类, 方法, 成员变量等)设置元数据

自定义 Annotation

定义新的 Annotation 类型使用 @interface 关键字

Annotation的成员变量在 Annotation 定义中以无参数方法的形式来声明,其方法名和返回值定义了该成员的名字和类型

可以在定义Annotation的成员变量时为其指定初始值,指定成员变量的初始值可使用default关键字

public @interface MyAnnotation{
       String name() default “atguigu";
}

没有成员定义的Annotation称为标记;包含成员变量的Annotation称为元数据Annotation

JDK提供的常用的三个注解

@Override: 限定重写父类方法, 该注释只能用于方法

@Deprecated: 用于表示某个程序元素(类, 方法等)已过时

@SuppressWarnings: 抑制编译器警告

元注解:可以对已有的注解进行解释说明。

Retention:SOURCE、CLASS、RUNTIME

Target: Documented:javadoc

Inherited

1)@Retention:只能用于修饰一个Annotation定义,用于指定该Annotation可以保留多长时间

@Rentention包含一个RetentionPolicy类型的成员变量,使用@Rentention时必须为该value成员变量指定值:

RetentionPolicy.SOURCE:编译器直接丢弃这种策略的注释

RetentionPolicy.CLASS:编译器将把注释记录在class文件中。当运行Java程序时,JVM不会保留注解。这是默认值

RetentionPolicy.RUNTIME:编译器将把注释记录在class文件中。当运行Java程序时,JVM 会保留注释。程序可以通过反射获取该注释

public enum RetentionPolicy{//源码
    SOURCE,
    CLASS,
    RUNTIME
}

2)@Target:用于修饰Annotation定义,用于指定被修饰的Annotation能用于修饰哪些程序元素

@Target也包含一个名为value的成员变量

3)@Documented:用于指定被该元Annotation修饰的Annotation类将被javadoc工具提取成文档

定义为Documented的注解必须设置Retention值为RUNTIME。

4)@Inherited:被它修饰的Annotation将具有继承性,如果某个类使用了被@Inherited修饰的Annotation,则其子类将自动具有该注解

实际应用中,使用较少

 // 适用类、接口(包括注解类型)或枚举  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.TYPE)  
public @interface ClassInfo {  
    String value();  
}  
    
// 适用field属性,也包括enum常量  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.FIELD)  
public @interface FieldInfo {  
    int[] value();  
}  
// 适用方法  
@Retention(RetentionPolicy.RUNTIME)  
@Target(ElementType.METHOD)  
public @interface MethodInfo {  
    String name() default "long";  
    String data();  
    int age() default 27;  
}

(9)、泛型

从jdk5之后出现的,泛型是一种特殊的类型,它把指定类型的工作推迟到客户端代码声明并实例化类或方法的时候进行。也叫做参数化类型,可以把类型当作参数一样传递,在传递前不明确,但是在使用的时候就明确了

格式

<数据类型>

这里的数据类型只能是引用类型

好处

把运行时期的问题提前到了编译期

避免了强制转换

优化了程序,没有了黄色警告线

泛型在哪些地方可以使用:看API,如果类,接口,抽象类后面有<E>就说明可以使用泛型,用的最多的是在集合中

为什么要有泛型

早期Object类型可以接收任意类型(任意对象),但是在实际使用的过程中会有类型转换问题,也就存在隐患,所以java就提供了泛型来解决这个问题

向上转型是没有问题的,但是向下转型的时候就隐含了类型转换问题,这样的程序不安全,jdk5之后提供了泛型,提高程序的安全性

泛型高级(通配符)

泛型通配符<?>:任意类型,如果没有明确,那么就是Object以及任意的java类

?extends E:向下限定,E及其子类

?super E:向上限定,E及其父类

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值