Java面试准备之反射、代理和异常

hresh 547 0

Java面试准备之反射、代理和异常

什么是反射?反射机制的应用场景有哪些?

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

在 Java 环境中运行时,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法。

Java 反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类。
  • 在运行时构造任意一个类的对象。
  • 在运行时判断任意一个类所具有的成员变量和方法。
  • 在运行时调用任意一个对象的方法。

反射机制优缺点:

  • 优点: 运行期类型的判断,动态加载类,提高代码灵活度。
  • 缺点: 性能瓶颈:反射相当于一系列解释操作,通知 JVM 要做的事情,性能比直接的 java 代码要慢很多。

反射的应用场景:

反射是框架设计的灵魂。在我们平时的项目开发过程中,基本上很少会直接使用到反射机制,但这不能说明反射机制没有用,实际上有很多设计、开发都与反射机制有关,例如模块化的开发,通过反射去调用对应的字节码;动态代理设计模式也采用了反射机制,还有我们日常使用的 Spring/Hibernate 等框架也大量使用到了反射机制。

举例:

  • 在使用 JDBC 连接数据库时使用 Class.forName()通过反射加载数据库的驱动程序;
  • Spring 框架也用到很多反射机制,最经典的就是 xml 的配置模式。Spring 通过 XML 配置模式装载 Bean 的过程:1) 将程序内所有 XML 或 Properties 配置文件加载到内存中;
    2)Java 类里面解析 xml 或 properties 里面的内容,得到对应实体类的字节码字符串以及相关的属性信息; 3)使用反射机制,根据这个字符串获得某个类的 Class 实例; 4)动态配置实例的属性

推荐阅读:

怎么实现反射?

Class.forName 通过带有包名的类名称获取到对应的 Class。

Class clz = Class.forName("com.chenshuyi.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
  • 获取类的 Class 对象实例
Class clz = Class.forName("com.zhenai.api.Apple");
  • 根据 Class 对象实例获取 Constructor 对象
Constructor appleConstructor = clz.getConstructor();
  • 使用 Constructor 对象的 newInstance 方法获取反射类对象
Object appleObj = appleConstructor.newInstance();

而如果要调用某一个方法,则需要经过下面的步骤:

  • 获取方法的 Method 对象
Method setPriceMethod = clz.getMethod("setPrice", int.class);
  • 利用 invoke 方法调用方法
setPriceMethod.invoke(appleObj, 14);

参考文献:大白话说Java反射:入门、使用、原理

序列化

序列化类别

文本序列化和二进制序列化

文本序列化

常用的文本序列化方式:XML、JSON等。文本序列化协议最大的优势在于可读性强和跨语言强。缺点也是显而易见的,效率低。

二进制序列化

  • Protocol Buffers

  • Thrift

  • Java序列化

    可读性差,但高效。

对原生序列化来讲,其他三方框架提供的序列化协议要快很多,所以我们做RPC技术选型的时候,序列化协议这块一定要摒弃原生序列化,去选择一款自己熟悉的序列化协议来传输IO流。

关于序列化协议介绍推荐阅读:[几种流行的序列化协议比较](

什么是 java 序列化?什么情况下需要序列化?

为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。简单来说就是将 Java 对象转换成字节流的过程。

什么情况下需要序列化:

  • 当你想把内存中的对象状态保存到一个文件中或者数据库中的时候;
  • 当你想用套接字在网络上传送对象的时候;
  • 当你想通过 RMI 传输对象的时候;

序列化的实现:类实现 Serializable 接口,这个接口没有需要实现的方法。实现 Serializable 接口是为了告诉 jvm 这个类的对象可以被序列化。

注意事项:

  • 某个类可以被序列化,则其子类也可以被序列化
  • 声明为 static 和 transient 的成员变量,不能被序列化。static 成员变量是描述类级别的属性,transient 表示临时数据
  • 反序列化读取序列化对象的顺序要保持一致

推荐阅读:https://www.cnblogs.com/yangjian-java/p/7813623.html

Java 序列化中如果有些字段不想进⾏序列化,怎么办?

声明为 static 和 transient 的成员变量,不能被序列化。static 成员变量是描述类级别的属性,transient 表示临时数据。transient 只能修饰变量,不能修饰类和方法。

代理模式分类

代理机制通过代理类创建时间的不同分为了静态代理和动态代理:

  • 静态代理:代理类在编译阶段生成,程序运行前就已经存在,那么这种代理方式被称为静态代理,这种情况下的代理类通常都是我们在 Java 代码中定义的。
  • 动态代理:代理类在程序运行时创建,也就是说,这种情况下,代理类并不是在 Java 代码中定义的,而是在运行时根据我们在 Java 代码中的“提示”动态生成的。

目前,静态代理主要有 AspectJ 静态代理、JDK 静态代理技术 ,而动态代理有 JDK 动态代理、Cglib 动态代理技术,而 Spring AOP 是整合使用了 JDK 动态代理和 Cglib 动态代理两种技术。

动态代理是什么?有哪些应用?

动态代理

当想要给实现了某个接口的类中的方法,加一些额外的处理。比如说加日志,加事务等。可以给这个类创建一个代理,故名思议就是创建一个新的类,这个类不仅包含原来类方法的功能,而且还在原来的基础上添加了额外处理的新类。这个代理类并不是定义好的,是动态生成的。具有解耦意义,灵活,扩展性强。

动态代理的应用:Spring的 AOP 功能模块就是采用动态代理的机制来实现切面编程,加事务,加权限,加日志。

怎么实现动态代理?

首先必须定义一个接口,还要有一个 InvocationHandler (将实现接口的类的对象传递给它)处理类。再有一个工具类 Proxy(习惯性将其称为代理类,因为调用他的 newInstance()可以产生代理对象,其实他只是一个产生代理对象的工具类)。利用到 InvocationHandler,拼接代理类源码,将其编译生成代理类的二进制码,利用加载器加载,并将其实例化产生代理对象,最后返回。

推荐阅读:代理模式

创建对象的方式

  • 通过 new 关键字。这是最常用的一种方式,通过 new 关键字调用类的有参或无参构造方法来创建对象。比如 Object obj = new Object();

  • 通过 Class 类的 newInstance() 方法。这种默认是调用类的无参构造方法创建对象。比如

    Person p2 = (Person) Class.forName("com.ys.test.Person").newInstance();

  • 通过 Constructor 类的 newInstance 方法。这和第二种方法类时,都是通过反射来实现。通过 java.lang.relect.Constructor 类的 newInstance() 方法指定某个构造器来创建对象。

    Person p3 = (Person) Person.class.getConstructors()[0].newInstance();

    实际上第二种方法利用 Class 的 newInstance() 方法创建对象,其内部调用还是 Constructor 的 newInstance() 方法。

  • 利用 Clone 方法。Clone 是 Object 类中的一个方法,通过 对象A.clone() 方法会创建一个内容和对象 A 一模一样的对象 B,clone 克隆,顾名思义就是创建一个一模一样的对象出来。

    Person p4 = (Person) p3.clone();

  • 反序列化。序列化是把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点(在网络上传输)。而反序列化则是把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。

深拷贝vs 浅拷贝

浅拷贝:对基本数据类型进行值传递,对引用数据类型进行引用传递(即两份引用指向同一个对象)。关于对象的浅拷贝,必须让类实现 Cloneable 接口,并且覆写 clone 方法 。

深拷贝:对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容。

关于对象的深拷贝,创建一个新对象,然后将当前对象的非静态字段复制到该新对象,无论该字段是值类型的还是引用类型,都复制独立的一份。当你修改其中一个对象的任何内容时,都不会影响另一个对象的内容。

深拷贝的实现方式:

1、让每个引用类型属性内部都重写clone() 方法

2、在类中的 clone 方法中使用 new 关键词创建引用类型属性

3、序列化。注意每个需要序列化的类都要实现 Serializable 接口,如果有某个属性不需要序列化,可以将其声明为 transient,即将其排除在克隆属性之外。

关于序列化的实现代码:

//深度拷贝
public static Object deepClone(Object o) throws Exception{
    // 序列化
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(bos);

    oos.writeObject(o);
    oos.flush();

    // 反序列化
    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);

    return ois.readObject();
}

推荐阅读:Java的深拷贝和浅拷贝

Java中的两种异常类型是什么?他们有什么区别?

  • Throwable 是所有异常的根,java.lang.Throwable
  • Error 是错误,java.lang.Error
  • Exception 是异常,java.lang.Exception

Exception 又包含了运行时异常(RuntimeException, 又叫非检查异常)和非运行时异常(又叫检查异常)

  • Error 是程序无法处理了, 比如 OutOfMemoryError 等, 这些异常发生时, java虚拟机一般会终止线程 .

  • 运行时异常都是 RuntimeException 类及其子类,如 NullPointerException、IndexOutOfBoundsException 等, 这些异常是不检查的异常, 是在程序运行的时候可能会发生的, 所以程序可以捕捉, 也可以不捕捉. 这些错误一般是由程序的逻辑错误引起的, 程序应该从逻辑角度去尽量避免.

  • 检查异常是运行时异常以外的异常, 也是 Exception 及其子类, 这些异常从程序的角度来说是必须经过捕捉检查处理的, 否则不能通过编译. 如 IOException、SQLException 等

Java中Exception和Error有什么区别?

Error类一般是指与虚拟机相关的问题,如系统崩溃,虚拟机错误,内存空间不足,方法调用栈溢出等。对于这类错误导致的应用程序中断,仅靠程序本身无法恢复和和预防,遇到这样的错误,建议让程序终止。

Exception类表示程序可以处理的异常,可以捕获且可能恢复。遇到这类异常,应该尽可能处理异常,使程序恢复运行,而不应该随意终止异常。

Throwable 类常用方法

  • public string getMessage():返回异常发生时的简要描述
  • public string toString():返回异常发生时的详细信息
  • public string getLocalizedMessage():返回异常对象的本地化信息。使用 Throwable 的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同
  • public void printStackTrace():在控制台上打印 Throwable 对象封装的异常信息

异常处理完成以后,Exception对象会发生什么变化?

某个 Exception 异常被处理后,该对象不再被引用,gc 将其标记,在下一个回收过程中被回收。

throw 和 throws 的区别?

1、Throw用于方法内部,Throws用于方法声明上

2、Throw后跟异常对象,Throws后跟异常类型

3、Throw后只能跟一个异常对象,Throws后可以一次声明多种异常类型

try-catch-finally

  • try块: 用于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块,则必须跟一个 finally 块。
  • catch块: 用于处理 try 捕获到的异常。
  • finally 块: 无论是否捕获或处理异常,finally 块里的语句都会被执行。当在 try 块或 catch 块中遇到 return 语句时,finally 语句块将在方法返回之前被执行。

在以下 3 种特殊情况下,finally 块不会被执行:

  1. tryfinally块中用了 System.exit(int)退出程序。但是,如果 System.exit(int) 在异常语句之后,finally 还是会被执行
  2. 程序所在的线程死亡。
  3. 关闭 CPU。

final、finally、finalize 有什么区别?

final 可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展,final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。

finally 则是 Java 保证重点代码一定要被执行的一种机制。我们可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接、保证 unlock 锁等动作。

finalize 是基础类 java.lang.Object 的一个方法,该方法在Object类中声明:
protected void finalize() throws Throwable { }它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated。

final学习链接:https://www.cnblogs.com/dolphin0520/p/3736238.html

https://www.cnblogs.com/ktao/p/8586966.html

https://www.cnblogs.com/jpcflyer/p/10739217.html

try-catch-finally 中,如果 catch 中 return 了,finally 还会执行吗?

public class TryStudy {

    public static int test(String str){
        try {
            return str.charAt(0) - '0';
        }catch (Exception e){
            return 1;
        }finally {
            return 2;
        }
    }

    public static int test2(String str){
        try {
            return str.charAt(0) - '0';
        }catch (Exception e){
            return 1;
        }finally {
            System.out.println("finally....");
        }
    }
    public static void main(String[] args) {
        //当finally里有return时,返回finally里return的结果,撤销之前的return语句
        System.out.println(test(null)+","+test("3"));
        System.out.println("****************");
        //当try里无异常,且有返回值时,先执行finally再返回值;当try有异常,catch里有返回,先执行finally在return
        System.out.println(test2(null)+","+test2("3"));
    }
}
//执行结果为:
2,2
****************
finally....
finally....
1,3

常见的异常类有哪些?

  • NullPointerException:当应用程序试图访问空对象时,则抛出该异常。
  • SQLException:提供关于数据库访问错误或其他错误信息的异常。
  • IndexOutOfBoundsException:指示某排序索引(例如对数组、字符串或向量的排序)超出范围时抛出。
  • NumberFormatException:当应用程序试图将字符串转换成一种数值类型,但该字符串不能转换为适当格式时,抛出该异常。
  • FileNotFoundException:当试图打开指定路径名表示的文件失败时,抛出此异常。
  • IOException:当发生某种I/O异常时,抛出此异常。此类是失败或中断的I/O操作生成的异常的通用类。
  • ClassCastException:当试图将对象强制转换为不是实例的子类时,抛出该异常。
  • ArrayStoreException:试图将错误类型的对象存储到一个对象数组时抛出的异常。
  • IllegalArgumentException:抛出的异常表明向方法传递了一个不合法或不正确的参数。
  • ArithmeticException:当出现异常的运算条件时,抛出此异常。例如,一个整数“除以零”时,抛出此类的一个实例。
  • NegativeArraySizeException:如果应用程序试图创建大小为负的数组,则抛出该异常。
  • NoSuchMethodException:无法找到某一特定方法时,抛出该异常。
  • SecurityException:由安全管理器抛出的异常,指示存在安全侵犯。
  • UnsupportedOperationException:当不支持请求的操作时,抛出该异常。
  • RuntimeExceptionRuntimeException:是那些可能在Java虚拟机正常运行期间抛出的异常的超类。

发表评论 取消回复
表情 图片 链接 代码

分享