Java基础

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

前言

没什么用的前言

Java 的特性

Java 的特性有如下这几点

  • 简单,Java 会让你的工作变得更加轻松,使你把关注点放在主要业务逻辑上,而不必关心指针、运算符重载、内存回收等与主要业务无关的功能。
  • 便携性,Java 是平台无关性的,这意味着在一个平台上编写的任何应用程序都可以轻松移植到另一个平台上。
  • 安全性, 编译后会将所有的代码转换为字节码,人类无法读取。它使开发无病毒,无篡改的系统/应用成为可能。
  • 动态性,它具有适应不断变化的环境的能力,它能够支持动态内存分配,从而减少了内存浪费,提高了应用程序的性能。
  • 分布式,Java 提供的功能有助于创建分布式应用。使用远程方法调用(RMI),程序可以通过网络调用另一个程序的方法并获取输出。您可以通过从互联网上的任何计算机上调用方法来访问文件。这是革命性的一个特点,对于当今的互联网来说太重要了。
  • 健壮性,Java 有强大的内存管理功能,在编译和运行时检查代码,它有助于消除错误。
  • 高性能,Java 黑科技就是字节码编程,Java 代码编译成的字节码可以轻松转换为本地机器代码。通过 JIT 即时编译器来实现高性能。
  • 解释性,Java 被编译成字节码,由 Java 运行时环境解释。
  • 多线程性,Java支持多个执行线程(也称为轻量级进程),包括一组同步原语。这使得使用线程编程更加容易,Java 通过管程模型来实现线程安全性。

什么是面向对象?面向对象和面向过程的区别?

  • 面向对象:把事物抽象出属性和行为,再通过实例化执行方法,来完成功能。有继承和多态。封装的是数据和功能
    • 优点:封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 维护、复用、扩展容易
    • 缺点:性能较低
  • 面向过程:以函数开发,来实现功能。没有继承和多态。只封装功能不封装数据
    • 优点:性能较高
    • 缺点:维护、复用、扩展难

面向对象的三大特性

  • 封装:封装是把数据和操作数据的方法绑定起来,对数据的访问只通过已定义的接口(api)。
    • 优点:减少耦合、自由修改类内部结构、对成员变量的控制更精确,隐藏信息实现细节
  • 继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类叫父类(超类、基类);得到继承信息的类被称为子类(派生类)。
  • 多态:同样的对象引用调用同样的方法却做了不同的事,多态分为编译时多态(方法重载)和运行时多态(方法重写)都提高了复用

方法重载和方法重写的区别

  • 方法重载
    • 实现的是编译时(前绑定、静态)的多态性
    • 发生在一个类的方法间。
    • 方法名相同,但是方法的参数类型、个数和顺序还有返回值有一项以上不同
  • 方法重写
    • 实现的是运行时(后绑定、动态)的多态性
    • 发生在子类继承父类的并重写父类的方法。
    • 方法名,参数的类型、个数、顺序还有返回值均与原方法均相同。但是方法的逻辑发生改变

JDK、JRE、JVM三者的关系

  • JDK是Java开发工具集(Java development kit),比如Java编译器(javac),它包含JRE

  • JRE是Java运行环境(Java runtime-Environment),包括JVM和一些标准的类函数库

  • JVM是Java虚拟机(Java virtual machine),它是运行Java程序的平台
    如果只是为了运行一下Java程序的话,只需要安装JRE就可以了

Java是否可以重写一个priate或者static方法

  • static不能,因为方法重写是基于运行时动态绑定的,而static方法是编译时静态绑定的
  • private也不能,因为static修饰的变量和方法只能在当前类种用,不能被继承

super和this关键字的作用

superthis
super(),子类构造方法中被隐藏的父类构造方法this(),类构造方法中代表无参构造方法
super.field,在子类中引用父类的实例变量this.field,类构造方法中,类对象变量
super.method(),在子类中引用父类的方法

如果父类和子类具有相同的字段,则使用 super 来指定为父类数据成员或字段。

构造方法有哪些特征

  1. 方法名与类名相同
  2. 没有返回值却不用void修饰
  3. 生成类的对象时自动执行,无需对象调用

new Name();//创建对象的过程中没有用到任何对象

无参构造器的作用

无参构造运行是需要往上一级一级找无参,甚至找到object.如果继承链中有一级没有无参,会发生错误。但只有无参,没有有参一定不会出错。在以后的Java开发中,若一个类要有带参构造方法,要记得先写无参构造方法。防止类追溯时找不到上级,报错

Java种创建对象的几种方式

  1. new关键字
  2. Class.newinstance
  3. Constructor.newinstance
  4. Clone方法
  5. 反序列化

抽象类和接口的区别

抽象类和接口都不能够实例化,但可以定义抽象类和接口类型的引用。一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该类仍然需要声明为抽象类

抽象类接口
可以有抽象方法和具体方法不能有具体方法
可以定义构造器(因为子类构造器要回溯)不能定义构造器
成员可以使用四种权限只能是public
可以定义成员变量只能是静态常量
抽象方法名和类名用abstract修饰类名用interface
只能有一个父类可以多继承

静态变量和实例变量的区别

静态变量(static)实例变量
属于类,也叫做类变量属于对象,也叫成员变量
内存中只有一个可以实现多个对象共享内存创建多少个对象就有多少个

final、finally、finalize的区别

  • final是修饰符

    • 修饰类,类不能派生子类
    • 修饰方法,方法不能在子类中被重写
    • 修饰变量,不能重新赋值

    final 修饰的对象,对象的引用不能被改变,但是对象内部的属性可以被修改。final 修饰的变量在某种程度上起到了不可变的效果,所以,可以用来保护只读数据,尤其是在并发编程中,因为明确的不能再为 final 变量进行赋值,有利于减少额外的同步开销。

  • finally:通常在捕获异常时使用,其内的语句无论异常是否发生都必须执行,除非JVM关闭。如果有return,也先执行完finally再return。JDK1.7 中,推荐使用 try-with-resources 优雅的关闭资源,它直接使用 try(){} 进行资源的关闭即可,就不用写 finally 关键字了。

  • finalize:Object类中定义的方法,这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize方法可以整理系统资源或者执行其他清理工作。这个方法我们一般不推荐使用,finalize 是和垃圾回收关联在一起的,在 Java 9 中,将 finalize 标记为deprecated(不推荐), 如果没有特别原因,不要实现 finalize 方法,也不要指望他来进行垃圾回收。

为什么重写equals,还要重写hashcode

equals 方法和 hashCode 都是 Object 中定义的方法,它们经常被一起重写

  • equals 方法:是用来比较对象大小是否相等的方法
  • hashcode 方法:是用来判断每个对象 hash 值的一种方法

hashcode不同的对象,equals一定是false。但hashcode相同不能判定equals的结果,所以要二次判定

equals是true的话,两个对象的hashcode一定相同

为了提高程序的效率才实现了hashcode方法,先进行hashcode的比较,如果不同,那没就不必在进行equals的比较了,这样就大大减少了equals比较的 次数。例如set集合的add方法,新加的元素不需要和每个元素equals,只需要匹配有没有一样的hash值

如果只重写 equals 方法而不重写 hashcode 方法,很可能会造成两个不同的对象,它们的 hashcode 也相等,造成冲突。比如

String str1 = "通话";
String str2 = "重地";//它们两个的 hashcode 相等,但是 equals 可不相等。

如果在 Java 运行期间对同一个对象调用 hashCode 方法后,无论调用多少次,都应该返回相同的 hashCode,但是在不同的 Java 程序中,执行 hashCode 方法返回的值可能不一致。

Comparator 和 Comparable 有什么不同

  • Comparable 更像是自然排序,

    对于一些普通的数据类型(比如 String, Integer, Double…),它们默认实现了Comparable 接口,实现了 compareTo 方法,我们可以直接使用。

  • Comparator 更像是定制排序(为自定义准备的)

    对于一些自定义类,它们可能在不同情况下需要实现不同的比较策略,我们可以新创建 Comparator 接口,然后使用特定的 Comparator 实现进行比较。

同时存在时采用 Comparator(定制排序)的规则进行比较。

String、StringBuilder 和 StringBuffer 有什么区别

  • String 特指的是 Java 中的字符串,String 类位于 java.lang 包下,String 类是由 final 修饰的,String 字符串一旦创建就不能被修改,任何对 String 进行修改的操作都相当于重新创建了一个字符串。String 字符串的底层使用 StringBuilder 来实现的

  • StringBuilder 位于 java.util 包下,StringBuilder 是一非线程安全的容器,StringBuilder 的 append 方法常用于字符串拼接,它的拼接效率要比 String 中 + 号的拼接效率高。StringBuilder 一般不用于并发环境

  • StringBuffer 位于 java.util 包下,StringBuffer 是一个线程安全的容器,多线程场景下一般使用 StringBuffer 用作字符串的拼接

StringBuilder 和 StringBuffer 都是继承于AbstractStringBuilder 类,AbstractStringBuilder 类实现了 StringBuffer 和 StringBuilder 的常规操作。

String s1 = new String(“abc”) 在内存中创建了几个对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3ACIgwfC-1682401909629)(C:\Users\Joshua.TV\Pictures\webPhoto_Typora\watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2thcnJ5MTMy,size_16,color_FFFFFF,t_70#pic_center.png)]

 String s = new String("abc");方式创建对象,在内存中创建了几个对象?
 解析:两个:一个是堆空间中new结构
 		  一个是char[]对应的常量池中的数据:"abc",如果常量池里已经存在就不创建了
 String s = "abc";方式创建对象,在内存中创建了几个对象?
 解析:一个.char[]对应的常量池中的数据:"abc"

byte的取值范围是多少,怎么计算出来的

byte 的取值范围是 -128 -> 127 之间,一共是 256 。一个 byte 类型在计算机中占据一个字节,那么就是 8 bit。一个字节八位。所以最大就是 2^7 = 0111 1111。

Java 中用补码来表示二进制数,补码的最高位是符号位,最高位用 0 表示正数,最高位 1 表示负数,正数的补码就是其本身,由于最高位是符号位,所以正数表示的就是 0111 1111 ,也就是 127。最大负数就是 1111 1111,这其中会涉及到两个 0 ,一个 +0 ,一个 -0 ,+0 归为正数,也就是 0 ,-0 归为负数,也就是 -128,所以 byte 的范围就是 -128 - 127。

计算对象的大小

  1. 计算空对象(object)的大小,用b表示byte,一个byte八位

    4b(保存引用对象的空间)+8b(保存对象的空间)+4b(int类型大小)+1b(布尔类型大小)=17b

    由于Java给对象分配内存时都是8b的整数倍,所以是24b

  2. 计算对象的大小

    • 计算指定对象本身在堆空间的大小

      long RamUsageEstimator.shallowSizeOf(Object obj)

    • 计算指定对象及其引用树上的所有对象的综合大小

      String RamUsageEstimator.humanSizeOf(Object obj)

Object 类中一般都有哪些方法

Object 类是所有对象的父类,它里面包含一些所有对象都能够使用的方法

  • hashCode():用于计算对象的哈希码
  • equals():用于对象之间比较值是否相等
  • toString(): 用于把对象转换成为字符串
  • clone(): 用于对象之间的拷贝
  • wait(): 用于实现对象之间的等待
  • notify(): 用于通知对象释放资源
  • notifyAll(): 用于通知所有对象释放资源
  • finalize(): 用于告知垃圾回收器进行垃圾回收
  • getClass(): 用于获得对象类

UTF-8 和 Unicode 的关系

每个国家都有自己独有的字符编码,所以Unicode 的发展旨在创建一个新的标准,用来映射当今使用的大多数语言中的字符,这些字符有一些不是必要的,但对于创建文本来说却是不可或缺的。Unicode 统一了所有字符的编码,是一个 Character Set,也就是字符集,字符集只是给所有的字符一个唯一编号,但是却没有规定如何存储,不同的字符其存储空间不一样,有的需要一个字节就能存储,有的则需要2、3、4个字节。

UTF-8 只是众多能够对文本字符进行解码的一种方式,它是一种变长的方式。UTF-8 代表 8 位一组表示 Unicode 字符的格式,使用 1 - 4 个字节来表示字符。

U+ 0000 ~ U+ 007F: 0XXXXXXX
U+ 0080 ~ U+ 07FF: 110XXXXX 10XXXXXX
U+ 0800 ~ U+ FFFF: 1110XXXX 10XXXXXX 10XXXXXX
U+10000 ~ U+1FFFF: 11110XXX 10XXXXXX 10XXXXXX 10XXXXXX

可以看到,UTF-8 通过开头的标志位位数实现了变长。对于单字节字符(只占用一个字节)实现了向下兼容 ASCII,并且能和 UTF-32 一样,包含 Unicode 中的所有字符,又能有效减少存储传输过程中占用的空间。

项目为 UTF-8 环境,char c = ‘中’,是否合法

可以,因为 Unicode 编码采用 2 个字节的编码,UTF-8 是 Unicode 的一种实现,它使用可变长度的字符集进行编码,char c = ‘中’ 是两个字节,所以能够存储。合法。

Java中的参数传递的是值还是引用

值传递,当一个对象实例作为一个参数被传递到方法中,参数的值就是对该对象的引用,对象的属性可以在被调用的过程中被改变,但对象的引用的改变是不会影响到调用者的。参看final变量赋值对象

描述一下值传递和引用传递的区别

简单理解的话就是

值传递是指在调用函数时将实际参数复制一份到函数中,所以函数对其传递过来的形式参数进行修改,将不会影响到实际参数

引用传递 是指在调用函数时将对象的地址直接传递到函数中,如果在对形式参数进行修改,将影响到实际参数(传递的对象)的值

如何实现对象的克隆

  • 实现Cloneable接口并重写Object类中的clone方法
  • 实现Serializable接口,标识对象的序列化和反序列化实现克隆(深度克隆

深度克隆和浅克隆的区别

  • 浅克隆:不会克隆原对象中的引用类型,只拷贝了引用类型的对象
  • 深克隆:在引用类型所在的类实现Cloneable接口,并使用public方法修饰符重写clone方法

什么是Java的序列化,如何实现,什么情况使用

  • 序列化:把对象的内容进行流化(对象转成字节码)。流化的对象可以进行读写操作(磁盘IO),也可以在网络传输(网络IO)。如果不进行序列化,在处理流问题的时候可能会数据乱码
  • 实现:让一个类实现Serializable(标识性接口),不用重写方法。

写对象(writeObject),用来保存序列化后的状态

读对象(readObject),用来保存反序列化后的状态

序列化既能保证对象的持久化,又能对对象深克隆

  • 使用场景:
    • 把内存中的对象状态保存到一个文件中或者数据库(比如保存到redis),磁盘IO
    • 用二进制在网络上传送对象的时候,网络IO
    • RMI传输对象的时候

Java的泛型是如何工作的,什么是类型擦除

泛型通过类型擦除来实现,编译器在编译时擦出了所有类型相关的信息,在运行时不存在类型相关的饿信息。比如List在运行时只是List。泛型的目的是为了规范开发者编码的一致性。

泛型中的限定通配符和非限定通配符

子类的限定通配符父类的限定通配符非限定通配符
<? extend T><? super T><?>
必须是T的子类必须是T的父类任意类型都可以

Java的反射是什么意思,有哪些应用场景,有什么优缺点

  • 反射机制
    • 核心:在程序运行时动态加载类并获取类的详细信息,以操作类或对象的属性和方法
    • 本质:JVM获得Class对象,通过Class对象进行反编译,从而获取对象的各种信息
  • Java属于先编译再运行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态加载某些类(如果不能动态加载的化就只能先停止所有程序的运行,添加 之前用不到没有被加载到JVM的类,然后启动程序,然后重新编译加载 类),这些类。通过反射,可以在运行时创建对象并调用其属性,不需要在编译器知道运行的对象是谁。
  • 反射机制提供的功能
    • 在运行时判断 对象所属的类
    • 在运行时构造 类的对象
    • 在运行时判断 类所具有的成员变量和方法
    • 在运行时调用 对象的方法

优点:提高了程序的灵活性和扩展性,降低耦合性。允许程序创建和控制任何类的对象,无需提前硬编码目标类。

缺点:1、反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码

2、反射会模糊程序内部逻辑

3、不安全,反射要求程序在一个没有安全限制的环境中运行

4、内部暴露,反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有属性和方法

调用反射的总体流程如下:

  • 准备阶段:编译器装载所有的类,将每个类的元信息保存至Class类对象中,每一个类对应一个Class对象
  • 获取Class对象:调用x.class/x.getClass()/Class.forName() 获取x的Class对象clz(这些方法的底层都是native方法,是在JVM底层编写好的,涉及到了JVM底层,就先不进行探究了)
  • 进行实际反射操作:通过clz对象获取Field/Method/Constructor(属性/方法/构造方法)和对象进行进一步操作

反射的基本原理,反射创建类实例的三种方式是什么

反射机制就是使 Java 程序在运行时具有自省(introspect) 的能力,通过反射我们可以直接操作类和对象,比如获取某个类的定义,获取类的属性和方法,构造方法等。

创建类实例(调用反射流程的第二步,获取class对象)的三种方式是

  • 对象实例.getClass();

  • 通过 Class.forName() 创建

  • 对象实例.newInstance() 方法创建

Java中的动态代理是什么,怎么实现动态代理,有哪些应用

  • 动态代理:在运行时,创建目标类,调用和扩展目标类的方法
  • 实现动态代理的方式(代理模式)
    • jdk动态代理,是由Java内部的反射机制来实现的,目标类基于统一的接口(InvocationHandler)
    • CGlib动态代理,底层是借助asm来实现,CGlib这种第三方类库实现的动态代理应用更加广泛,且效率上更有优势

动态代理是基于什么原理

代理一般分为静态代理和 动态代理,它们都是代理模式的一种应用

静态代理指的是在程序运行前已经编译好,程序知道由谁来执行代理方法。

而动态代理只有在程序运行期间才能确定,相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类中的方法。可以说动态代理是基于 反射 实现的。通过反射我们可以直接操作类或者对象,比如获取类的定义,获取声明的属性和方法,调用方法,在运行时可以修改类的定义。

动态代理是一种在运行时构建代理、动态处理方法调用的机制。动态代理的实现方式有很多,Java 提供的代理被称为 JDK 动态代理,JDK 动态代理是基于类的继承。

静态绑定和动态绑定的区别

jdk编写代码不在我们讨论的范围之内

Java 程序从源文件创建到程序运行要经过两大步骤:

1、编译时期,是由编译器将源文件编译成字节码的过程

2、运行时期,将字节码文件由Java虚拟机解释(cpu可执行的系统指令)执行

绑定

绑定就是一个方法的调用 与调用这个方法的类 连接在一起的 过程被称为绑定。

绑定主要分为两种:

静态绑定 和 动态绑定

绑定的其他叫法

静态绑定 == (运行)前期绑定 == 编译时绑定

动态绑定 == (运行)后期绑定 == 运行时绑定

为了方便区分:下面统一称呼为静态绑定和动态绑定

静态绑定

在程序运行前,也就是编译时期 JVM 就能够确定方法由谁调用,这种机制称为静态绑定

如果一个方法由 private、static、final 任意一个关键字所修饰,那么这个方法是静态绑定的

  • private:private 关键字是私有的意思,如果被 private 修饰的方法是无法由本类之外的其他类(包括子类)所调用的,也就是本类所特有的方法(私有方法和属性不可被继承,也就不能重写,不能体现多态),所以也就由编译器识别此方法是属于哪个类的

  • final:final 修饰的方法不能被重写(就不会多态),但是可以由子类进行调用,如果将方法声明为 final 可以有效的关闭动态绑定

  • static:static 修饰的方法比较特殊,不用通过 new 出某个类来调用,由类名.变量名直接调用该方法。new 很关键,可以认为是开启多态的导火索,而由类名.变量名直接调用的话,此时的类名是确定的,并不会产生多态,如下代码:

public class SuperClass {
    public static void sayHello(){      
        System.out.println("由 superClass 说你好");
    }
}
public class SubClass extends SuperClass{
    public static void sayHello(){
        System.out.println("由 SubClass 说你好");
    }
    public static void main(String[] args) {
        SuperClass.sayHello();
        SubClass.sayHello();
    }
}//SubClass 继承 SuperClass 后,是无法重写 sayHello 方法的,也就是说 sayHello() 方法是对子类隐藏的,但是你可以编写自己的 sayHello() 方法,也就是子类 SubClass 的sayHello() 方法,由此可见,方法由 static 关键词所修饰,也是编译时绑定
  • 构造方法也是静态绑定(构造方法在new时自动调用,不需要对象)

动态绑定

在运行时根据引用指向的对象 的类型进行绑定

发生动态绑定时,父类引用调用父类和子类的同名覆盖方法,执行的是子类里的方法。同一个方法的不同表现形式,也叫做多态

动态绑定的过程

动态绑定和静态绑定的特点

  • 静态绑定在编译时期触发,

    1、编译期触发,能够提早知道代码错误

    2、提高程序运行效率

  • 动态绑定在运行时触发

    1、使用动态绑定的前提条件能够提高代码的可用性,使代码更加灵活。(动态的都灵活)

    2、多态是设计模式的基础,能够降低耦合性。

内部类有哪些分类,分别解释一下

在 Java 中,将一个类的定义放在另外一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。

内部类的分类一般主要有四种

  • 静态内部类:就是定义在类内部的静态类,

    静态内部类可以访问外部类所有的静态变量,而不可访问外部类的非静态变量;

  • 成员内部类:就是定义在类内部,成员位置上的非静态类,就是成员内部类。

    成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。

  • 局部内部类:定义在方法中的内部类。

    定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义在静态方法中的局部类只能访问外部类的静态变量和方法。

  • 匿名内部类:就是没有名字的内部类

    匿名内部类必须继承一个抽象类或者实现一个接口。匿名内部类不能定义任何静态成员和静态方法。当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。

其他补充

什么是反射?

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用这个对象的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

## 反射的实现原理

java类加载:java类加载就是类加载器根据类的全限定名把.class的二进制字节码代表的静态存储结构转化为方法区的运行时数据结构,然后在内存中生成代表该类的Class对象,一个类有且只有一个Class对象每次生成Java对象实际上都是通过这个Class对象获取整个类的结构并生成相应的java对象。(接口和抽象类也会被加载为class对象)

所以如果能够在运行时拿到Class对象,就可以生成java对象并进行调用,这就是java反射的本质。

反射的使用

Java 中的 java.lang.reflect 包提供了反射功能。java.lang.reflect 包中的类都没有 public 构造方法

/*
        * 获取Class对象的3种方法:
        1、Class.forName("pacakge.className")
        2、Class.class
        3、javaObject.getClass()
        * */
        clazz = Class.forName("com.meituan.data.springbootdemo.User");
//        clazz = user.getClass();
//        clazz = User.class;
        System.out.println(clazz);
 
        /*
        * 判断是否为某个类的实例的2种方法:
        1、instanceof 关键字
        2、Class 对象的 isInstance 方法(它是一个 Native 方法)
        * */
        Boolean isInstance;
//        isInstance = user instanceof User;
        isInstance = clazz.isInstance(user);
        System.out.println("user is instance of User: "+isInstance);
 
        /*
        * 通过反射来创建实例对象的2种方法:
        1、Class 对象的 newInstance 方法。
        2、Constructor 对象的 newInstance 方法。
        * */
        User userNew;
//        userNew = (User) clazz.newInstance();
        Constructor constructor = clazz.getConstructor(String.class,Integer.class,String.class);
        userNew = (User) constructor.newInstance("ck",88,"234567");
        System.out.println(userNew);
 
        /*
        * 获取代理类的方法Method,并调用该方法(invoke)
        * */
        Method method = clazz.getMethod("getAge",null);
        System.out.println(method.invoke(user));
        
        /*
        * 获取代理类的构造器Constructor
        * */
        
        /*
         * 获取代理类的注解Annotation,并根据不同的注解进行不同的行为
         * */
        Annotation[] annotations = clazz.getAnnotations();
        for (Annotation annotation:annotations){
            System.out.println(annotation);
            if(annotation instanceof MyPrintAnnotation){
                System.out.println(user);
            }
            if(annotation instanceof MyAnnotation){
                System.out.println(((MyAnnotation) annotation).msg());
            }
        }

为什么需要反射?(反射的作用/应用场景)

反射的作用可以用一句话概括:反射赋予了jvm动态编译的能力。动态编译可以最大限度的体现Java的灵活性(多态)。

否则类的元信息只能通过静态编译的形式实现(在编译期确定类型,绑定对象),而不能实现动态编译(在运行期确定类型,绑定对象)。也就是说在编译以后,程序在运行时的行为就是固定的了,如果要在运行时改变程序的行为,就需要动态编译,在Java中就需要反射机制。

情景一:不得已而为之

有的类是我们在编写程序的时候无法使用new一个对象来实例化对象的。例如:

调用的是来自网络的二进制.class文件,而没有其.java代码;
注解 - 注解本身仅仅是起到标记作用,它需要利用反射机制,根据注解标记去调用注解解释器,执行行为。如果没有反射机制,注解并不比注释更有用。

情景二:动态加载(可以最大限度的体现Java的灵活性,并降低类的耦合性:多态)

有的类可以在用到时再动态加载到jvm中,这样可以减少jvm的启动时间,同时更重要的是可以动态的加载需要的对象(多态)。例如:

动态代理 - 在切面编程(AOP)中,需要拦截特定的方法,通常,会选择动态代理方式。这时,就需要反射技术来实现了。

情景三:避免将程序写死到代码里

因为java代码是先通过编译器将.java文件编译成.class的二进制字节码文件,因此如果我们使用new Person()来实例化对象person会出现的问题就是如果我们希望更换person的实例对象,就要在源代码种更改然后重新编译再运行,如果我们将person的实例对象类名等信息编写在配置文件中,利用反射的Class.forName(className)方法来实例化java对象(因为实例化java对象都是根据全限定名查找到jvm内存中的class对象,并根据class对象中的累信息实例化得到java对象,因此xml文件中只要包含了权限定类名就可以通过反射实例化java对象),那么我们就可以更改配置文件,无需重新编译。例如:

开发通用框架 - 反射最重要的用途就是开发各种通用框架。很多框架(比如 Spring)都是配置化的(比如通过 XML 文件配置 JavaBean、Filter 等),为了保证框架的通用性,它们可能需要根据配置文件加载不同的对象或类,调用不同的方法,这个时候就必须用到反射——运行时动态加载需要加载的对象。

总结

没什么好总结的

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Python JavaParser是一个用于解析Java代码的Python库。它帮助开发人员分析和操作Java代码,提取出代码中的结构和信息。 使用Python JavaParser,你可以实现以下功能: 1. 解析Java代码:将Java代码作为输入,使用JavaParser库解析代码并构建抽象语法树(AST)。 2. 遍历和操作AST:通过遍历AST,你可以访问和操作Java代码的各个部分,如类、方法、变量等。 3. 提取代码信息:你可以使用JavaParser提供的API来提取代码中的信息,如类名、方法名、变量名、注释等。 4. 修改和生成Java代码:你可以通过修改AST来修改Java代码,并将修改后的AST重新生成为Java代码。 Python JavaParser的安装可以通过pip命令进行: ``` pip install javalang ``` 以下是一个使用Python JavaParser解析Java代码的示例: ```python from javalang import parse # Java代码 java_code = ''' public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, World!"); } } ''' # 解析Java代码并构建AST tree = parse.parse(java_code) # 遍历AST并打印类名和方法名 for path, node in tree: if isinstance(node, parse.ClassDeclaration): print("Class name:", node.name) elif isinstance(node, parse.MethodDeclaration): print("Method name:", node.name) ``` 输出结果: ``` Class name: HelloWorld Method name: main ``` 这是一个简单的示例,你可以根据自己的需求使用Python JavaParser进行更复杂的操作和分析。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值