【Java面试】第二天

在这里插入图片描述

🌟个人主页:时间会证明一切.



Java中创建对象哪几种方式

使用new关键字

这是我们最常见的也是最简单的创建对象的方式,通过这种方式我们还可以调用任意的构造函数(无参的和有参的)。
User user = new User();

使用反射机制

运用反射手段,调用Java.lang.Class或者java.lang.reflect.Constructor类的newInstance()实例方法。
1 使用Class类的newInstance方法可以使用Class类的newInstance方法创建对象。这个newInstance方法调用无参的构造函数创建对象。

User user = (User)Class.forName("xxx.xxx.User").newInstance(); 
User user = User.class.newInstance();

2 使用Constructor类的newInstance方法
和Class类的newInstance方法很像, java.lang.reflect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。

Constructor  constructor = User.class.getConstructor();
User user = constructor.newInstance();

这两种newInstance方法就是大家所说的反射。事实上Class的newInstance方法内部调用Constructor的newInstance方法。

使用clone方法

无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。

要使用clone方法,我们需要先实现Cloneable接口并实现其定义的clone方法。如果只实现了Cloneable接口,并没有重写clone方法的话,会默认使用Object类中的clone方法,这是一个native的方法。

public class CloneTest implements Cloneable{
    private String name; 
    private int 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;
    }
    public CloneTest(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public static void main(String[] args) {
        try {
            CloneTest cloneTest = new CloneTest("wangql",18);
            CloneTest copyClone = (CloneTest) cloneTest.clone();
            System.out.println("newclone:"+cloneTest.getName());
            System.out.println("copyClone:"+copyClone.getName());
        } catch (CloneNotSupportedException e) {
        	e.printStackTrace();
        }
    }
}

使用反序列化

当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。其实反序列化也是基于反射实现的。

public static void main(String[] args) {
    //Initializes The Object
    User1 user = new User1();
    user.setName("aaron");
    user.setAge(23);
    System.out.println(user);

    //Write Obj to File
    ObjectOutputStream oos = null;
    try {
        oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
        oos.writeObject(user);
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        IOUtils.closeQuietly(oos);
    }

    //Read Obj from File
    File file = new File("tempFile");
    ObjectInputStream ois = null;
    try {
        ois = new ObjectInputStream(new FileInputStream(file));
        User1 newUser = (User1) ois.readObject();
        System.out.println(newUser);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        IOUtils.closeQuietly(ois);
        try {
            FileUtils.forceDelete(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

使用方法句柄

通过使用方法句柄,可以间接地调用构造函数来创建对象

public static void main(String[] args) throws Throwable {
    // 定义构造函数的方法句柄类型为void类型,无参数
    MethodType constructorType = MethodType.methodType(void.class);

    // 获取构造函数的方法句柄
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle constructorHandle = lookup.findConstructor(User.class, constructorType);

    // 使用方法句柄调用构造函数创建对象
    User obj = (User) constructorHandle.invoke();
}

使用了MethodHandles.lookup().findConstructor()方法获取构造函数的方法句柄,然后通过invoke()方法调用构造函数来创建对象。

使用Unsafe分配内存

在Java中,可以使用sun.misc.Unsafe类来进行直接的内存操作,包括内存分配和对象实例化。然而,需要注意的是,sun.misc.Unsafe类是Java的内部API,它并不属于Java标准库的一部分,也不建议直接在生产环境中使用。

public static void main(String[] args) throws Exception {

    Field field = Unsafe.class.getDeclaredField("theUnsafe");
    field.setAccessible(true);
    Unsafe unsafe = field.get(null);

    // 获取User类的字段偏移量
    long nameOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("name"));
    long ageOffset = unsafe.objectFieldOffset(User.class.getDeclaredField("age"));

    // 使用allocateInstance方法创建对象,不会调用构造函数
    User user = (User) unsafe.allocateInstance(User.class);

    // 使用putObject方法设置字段的值
    unsafe.putObject(user, nameOffset, "aaron");
    unsafe.putInt(user, ageOffset, 30);
}


这种方式有以下几个缺点:

  1. 不可移植性:Unsafe类的行为在不同的Java版本和不同的JVM实现中可能会有差异,因此代码在不同的环境下可能会出现不可移植的问题。
  2. 安全性问题:Unsafe类的功能是非常强大和危险的,可以绕过Java的安全机制,可能会导致内存泄漏、非法访问、数据损坏等安全问题。
  3. 不符合面向对象的原则:Java是一门面向对象的语言,鼓励使用构造函数和工厂方法来创建对象,以确保对象的正确初始化和维护对象的不变性。

什么是泛型?有什么好处?

Java泛型(generics) 是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。

泛型的好处有两个:

  1. 方便:可以提高代码的复用性。以List接口为例,我们可以将String、Integer等类型放入List中,如不用泛型,存放String类型要写一个List接口,存放Integer要写另外一个List接口,泛型可以很好的解决这个问题
  2. 安全:在泛型出之前,通过Object实现的类型转换需要在运行时检查,如果类型转换出错,程序直接GG,可能会带来毁灭性打击。而泛型的作用就是在编译时做类型检查,这无疑增加程序的安全性

什么是反射机制?为什么反射慢?

反射机制指的是程序在运行时能够获取自身的信息。在java中,只要给定类的名字,那么就可以通过反射机制来获得类的所有属性和方法。

Java的反射可以:

  1. 在运行时判断任意一个对象所属的类。
  2. 在运行时判断任意一个类所具有的成员变量和方法。
  3. 在运行时任意调用一个对象的方法
  4. 在运行时构造任意一个类的对象
Object obj = // ... 任意对象;
Class<?> clazz = obj.getClass();

// 获取成员变量
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    System.out.println("成员变量: " + field.getName());
}

// 获取方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    System.out.println("方法: " + method.getName());
}


Method method = clazz.getDeclaredMethod("methodName", // 方法参数类型...);
method.setAccessible(true); // 如果方法是私有的
method.invoke(obj, // 方法参数...);


// 默认构造函数
Object obj = clazz.newInstance();

// 或者使用特定的构造函数
Constructor<?> constructor = clazz.getConstructor(// 参数类型...);
Object obj = constructor.newInstance(// 构造函数参数...);

反射的好处就是可以提升程序的灵活性和扩展性,比较容易在运行期干很多事情。但是他带来的问题更多,主要由以下几个:

1、代码可读性低及可维护性
2、反射代码执行的性能低
3、反射破坏了封装性

所以,我们应该在业务代码中应该尽量避免使用反射。但是,作为一个合格的Java开发,也要能读懂中间件、框架中的反射代码。在有些场景下,要知道可以使用反射解决部分问题。

那么,反射为什么慢呢?主要由以下几个原因:

1、由于反射涉及动态解析的类型,因此不能执行某些Java虚拟机优化,如JIT优化。

2、在使用反射时,参数需要包装(boxing)成Object[] 类型,但是真正方法执行的时候,又需要再拆包(unboxing)成真正的类型,这些动作不仅消耗时间,而且过程中也会产生很多对象,对象一多就容易导致GC,GC也会导致应用变慢。

3、反射调用方法时会从方法数组中遍历查找,并且会检查可见性。这些动作都是耗时的。

4、不仅方法的可见性要做检查,参数也需要做很多额外的检查。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值