JAVA常见面试题总结(四)反射和对象拷贝

1、什么是反射?

反射是指程序可以在运行过程中访问、检测、修改自身状态或行为的一种能力。

Java 中反射可以获取任意一个类的所有属性、所有方法,并可以任意调用属性和方法。

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

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

2、反射的常用操作。

提供一个被操作的类:

public class Student {
    private String name;
    private Integer age;
    public Student() {}
    public Student(String name) {
        this.name = name;
    }
    private Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
}
  1. 通过反射创建对象

    • 方法1:通过类对象调用 newInstance() 方法。

      //获取类对象
      Class<Student> clz = Student.class;
      Student student = clz.newInstance();
      
    • 方法2:通过类对象的 getConstructor() 或 getDeclaredConstructor() 方法获得构造器(Constructor)对象并调用其 newInstance() 方法创建对象。

      //获取类对象
      Class<Student> clz = Student.class;
      //通过获取无参构造方器创建对象
      Constructor<Student> constructor1 = clz.getConstructor();
      Student student1 = constructor1.newInstance();
      //通过获取有参构造器创建对象
      Constructor<Student> constructor2 = clz.getConstructor(String.class);
      Student student2 = constructor2.newInstance("张三");
      //通过获取私有构造器创建对象
      Constructor<Student> constructor3 = clz.getDeclaredConstructor(String.class, Integer.class);
      //设置为可以访问
      constructor3.setAccessible(true);
      Student student3 = constructor3.newInstance("李四", 18);
      
  2. 如何通过反射获取和设置对象私有字段的值?

    通过类对象的 getDeclaredField() 方法获取对象的私有属性,然后再通过字段对象的 setAccessible(true) 将其设置为可以访问,最后就可以通过字段对象的 get/set 方法获取和设置字段的值了。

    //获取类对象
    Class<Student> clz = Student.class;
    //通过反射创建实例
    Student student = clz.newInstance();
    //获取Student类的name字段对象
    Field nameField = clz.getDeclaredField("name");
    //设置可以访问
    nameField.setAccessible(true);
    //设置字段的值
    nameField.set(student, "王五");
    //获取字段的值
    String wName = (String) nameField.get(student);
    
  3. 如何通过反射调用对象的方法?

    通过类对象的 getMethod() 方法获取对象的方法对象,然后通过方法对象的 invoke() 方法调用此方法。

    如果是私有方法,需要使用类对象的 getDeclaredMethod() 方法获取方法对象,然后设置为可访问 setAccessible(true) ,最后通过 invoke() 方法调用。

    //获取类对象
    Class<Student> clz = Student.class;
    //通过反射创建实例
    Student student = clz.newInstance();
    //获取名为“setName”,只有一个参数且参数类型为String的方法
    Method setNameMethod = clz.getMethod("setName", String.class);
    //调用student对象的setName方法
    setNameMethod.invoke(student, "张三");
    //获取名为“getName”,没有入参的方法
    Method getNameMethod = clz.getMethod("getName");
    //调用student对象大的getName方法并获取返回值
    String name = (String) getNameMethod.invoke(student);
    

3、Java 中获取 Class 对象有哪些方式,区别是什么?

  • 方式一:Object.class
  • 方式二:Class.forName(“java.lang.Object”)
  • 方式三:new Object().getClass()

区别:

方式一获取 Class 对象的时候不会调用类里面的任何代码块;

方式二获取 Class 对象的时候会调用类中的静态代码块;

方式三获取 Class 对象的时候会调用类中的静态代码块、匿名代码块、构造方法。

演示:

package com.test.javademo.reflect;

/**
 * @author ZhengNC
 * @date 2020/6/10 15:58
 */
public class Person {

    static {
        System.out.println("Person: 静态代码块");
    }

    {
        System.out.println("Person: 匿名代码块");
    }

    public Person(){
        System.out.println("Person:构造方法");
    }
}
public class TestPerson {

    public static void main(String[] args) throws ClassNotFoundException {
//        Class clazz = Person.class;
//        Class clazz = Class.forName("com.test.javademo.reflect.Person");
        Class clazz = new Person().getClass();
    }
}

第一行运行结果未输出任何内容。

第二行运行输出:

Person: 静态代码块

第三行运行输出:

Person: 静态代码块
Person: 匿名代码块
Person:构造方法

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

动态代理:

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

动态代理的应用:Spring的AOP、加事务、加日志、校验权限。

5、JDK 怎么实现动态代理?

首先必须定义一个接口,还要 InvocationHandler 处理类。再使用 Proxy 工具类创建代理对象。

/**
 * @author ZhengNC
 * @date 2020/6/10 16:17
 */
public interface Eat {

    void eatA();

    void eatB(String name);

    String eatC();
}
/**
 * @author ZhengNC
 * @date 2020/6/10 16:18
 */
public class EatImpl implements Eat {
    @Override
    public void eatA() {
        System.out.println("eat A......");
    }

    @Override
    public void eatB(String name) {
        System.out.println("eat B : " + name);
    }

    @Override
    public String eatC() {
        System.out.println("eat C......");
        return "eat C";
    }
}
/**
 * @author ZhengNC
 * @date 2020/6/10 16:30
 */
public class MyInvocation<T> implements InvocationHandler {

    private T impl;

    public MyInvocation(T impl){
        this.impl = impl;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(proxy.getClass().getName()+"."
                +method.getName()+"("
                + (args == null ? "" : Arrays.toString(args))
                +")");

        Object result = method.invoke(impl, args);

        System.out.println("result:"+result);
        return result;
    }

    /**
     * 根据实现类创建代理类
     * @param implClass 实现类的类对象
     * @param <T>
     * @return 代理类
     */
    public static<T> T createObj(Class implClass){
        MyInvocation invocation = null;
        try {
            invocation = new MyInvocation(implClass.newInstance());
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        T t = (T) Proxy.newProxyInstance(implClass.getClassLoader(), implClass.getInterfaces(), invocation);
        return t;
    }
}
/**
 * @author ZhengNC
 * @date 2020/6/10 16:44
 */
public class Test {

    public static void main(String[] args) {
        //创建代理类
        Eat eat = MyInvocation.createObj(EatImpl.class);
        eat.eatA();
        eat.eatB("苹果");
        String eatC = eat.eatC();
        System.out.println(eatC);
    }
}

运行结果:

com.sun.proxy.$Proxy0.eatA()
eat A......
result:null
com.sun.proxy.$Proxy0.eatB([苹果])
eat B : 苹果
result:null
com.sun.proxy.$Proxy0.eatC()
eat C......
result:eat C
eat C

6、什么是 Java 序列化?什么情况下需要序列化?

序列化就是将内存中对象的状态信息保存成可传输或存储的形式的过程。

什么情况下需要序列化?

  1. 要把内存中的对象状态保存到一个文件中或数据库中的时候
  2. 要用套接字在网络上传输对象的时候
  3. RMI 远程方法调用传输对象的时候。

7、为什么使用克隆?

想要对一个对象进行处理,同时又要保留原有的数据进行接下来的操作,这个时候就需要克隆了,Java 中克隆针对的是类的实例。

8、如何实现克隆?

实现克隆有两种方式:

  • 实现 Cloneable 接口并重写 Object 类中的 clone() 方法。
  • 实现 Serializable 接口,通过对象的序列化和反序列化实现克隆,这种方式可以实现真正的深度克隆。

序列化方式代码实现:

/**
 * @author ZhengNC
 * @date 2020/6/10 19:27
 */
public class People implements Serializable {

    //姓名
    private String name;
    //年龄
    private Integer age;
    //汽车
    private Car car;

    public People() {
    }

    public People(String name, Integer age, Car car) {
        this.name = name;
        this.age = age;
        this.car = car;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public Car getCar() {
        return car;
    }

    public void setCar(Car car) {
        this.car = car;
    }

    @Override
    public String toString() {
        return "People{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", car=" + car +
                '}';
    }
}
/**
 * @author ZhengNC
 * @date 2020/6/10 19:28
 */
public class Car implements Serializable {

    //车名
    private String name;
    //最高时速
    private Double maxSpeed;

    public Car() {
    }

    public Car(String name, Double maxSpeed) {
        this.name = name;
        this.maxSpeed = maxSpeed;
    }

    public String getName() {
        return name;
    }

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

    public Double getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(Double maxSpeed) {
        this.maxSpeed = maxSpeed;
    }

    @Override
    public String toString() {
        return "Car{" +
                "name='" + name + '\'' +
                ", maxSpeed=" + maxSpeed +
                '}';
    }
}
/**
 * @author ZhengNC
 * @date 2020/6/10 19:32
 */
public class CloneUtil {

    public static <T extends Serializable> T clone(T obj) throws Exception{
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        return (T) ois.readObject();
    }
}

测试类:

/**
 * @author ZhengNC
 * @date 2020/6/10 19:32
 */
public class Test {

    public static void main(String[] args) throws Exception {
        Car car = new Car("小白", 150D);
        People love = new People("一生所爱", 18, car);
        People my = CloneUtil.clone(love);
        my.setName("生生世世");
        my.getCar().setMaxSpeed(140D);
        System.out.println(love);
        System.out.println(my);
    }
}

测试结果:

People{name='一生所爱', age=18, car=Car{name='小白', maxSpeed=150.0}}
People{name='生生世世', age=18, car=Car{name='小白', maxSpeed=140.0}}

9、深拷贝和浅拷贝的区别是什么?

  • 浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中一个对象的的任意值,另一个对象也会跟着变化。
  • 深拷贝是将对象及值全部拷贝过来,相当于重新在内存中申请了一块内存存放拷贝过来的对象,修改其中一个的值时另一个对象的值不会跟着改变。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值