创建对象的4种方式和RTTI和反射的区别

1. 使用new 关键字

使用 new 关键字创建对象,实际上是做了两个工作,一是在内存中开辟空间,二是初始化对象。但是new 关键字只能创建非抽象对象。

如 User user=new User();

执行这条语句,jvm做了什么?

1. 首先在方法区的常量池中查看是否有new 后面参数(也就是类名)的符号引用,并检查是否有类的加载信息也就是是否被加载解析和初始化过。如果已经加载过了就不在加载,否则执行类的加载全过程
2. 加载完类后,大致做了如下三件事:
    a、给实例分配内存
    b、调用构造函数,初始化成员字段
    c、user对象指向分配的内存空间
    注意:new操作不是原子操作,b和c的顺序可能会调换?

2. 使用反射创建对象

反射是对于任意一个正在运行的类,都能动态获取到他的属性和方法。反射创建对象分为两种方式,一是使用Class类的new Instance() 方法,二是使用Constructor类的new Instatnce() 方法。

两者区别在于:

Class.newInstance() 只能够调用无参的构造函数,即默认的构造函数;
Constructor.newInstance() 可以根据传入的参数,调用任意构造构造函数。

通过反射来创建类对象的实例,有两个步骤:

首先我们得拿到类对象的Class

如何获取? 有三种方式(反射章节会详细讲解)

.class,如Person.class
对象.getClass()
Class.forName("类全路径")
通过反射创建类对象的实例对象
在拿到类对象的Class后,就可以通过Java的反射机制来创建类对象的实例对象了,主要分为两种方式:

Class.newInstance()

调用类对象的构造方法

举个栗子:

首先准备一个Person的类:

public class Person {
    private String name;
    private int age;
    public Person() {
    }
    public Person(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 String toString() {

        return "Person{" +

                "name='" + name + '\'' +

                ", age=" + age +

                '}';
    }
}

使用Class.newInstance()创建对象:

public class ClassNewInstance {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException {

        Person person = Person.class.newInstance();

        person.setAge(18);

        person.setName("酸辣汤");

        System.out.println(person);

    }

}

运行结果:

Person{name='酸辣汤', age=18}

注意 :newInstance创建对象实例的时候会调用无参的构造函数,所以必需确保类中有无参数的可见的构造函数,否则将会抛出异常。

调用类对象的构造方法——Constructor
Constructor是Java反射机制中的构造函数对象,获取该对象的方法有以下几种:

Class.getConstructors():获取类对象的所有可见的构造函数

Class.getConstructor(Class… paramTypes):获取指定的构造函数

获取类对象所有的构造方法并遍历:

public class ConstructorInstance {

    public static void main(String[] args) {

        Class p = Person.class;

        for(Constructor constructor : p.getConstructors()){

            System.out.println(constructor);

        }

    }

}

运行结果:

public com.eft.reflect.Person()

public com.eft.reflect.Person(java.lang.String,int)

获取指定的构造方法

通过Class.getConstructor(Class… paramTypes)即可获取类对象指定的构造方法,其中paramTypes为参数类型的Class可变参数,当不传paramTypes时,获取的构造方法即为默认的构造方法。

public class ConstructorInstance {

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

        Class p = Person.class;

        Constructor constructor1 = p.getConstructor();//获取默认的构造方法

        Constructor constructor2 = p.getConstructor(String.class,int.class);//获取指定的构造方法

        System.out.println(constructor1);

        System.out.println(constructor2);

    }

}

运行结果:

public com.eft.reflect.Person()

public com.eft.reflect.Person(java.lang.String,int)

通过构造方法创建对象
Constructor对象中有一个方法newInstance(Object ... initargs),这里的initargs即为要传给构造方法的参数,如Person(String,int),通过其对应的Constructor实例,调用newInstance方法并传入相应的参数,即可通过Person(String,int)来创建类对象的实例对象。

测试代码如下:



public class ConstructorInstance {

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

        Class p = Person.class;

        Constructor constructor = p.getConstructor(String.class,int.class);

        Person person = (Person) constructor.newInstance("酸辣汤",18);

        System.out.println(person); 

    }

}

运行结果:

Person{name='酸辣汤', age=18}

3. 使用clone方法

要拷贝的对象需要实现Cloneable类,并重写clone()方法。

当我们调用一个对象的clone方法,jvm就会创建一个新的对象,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。因为Object 类的 clone 方法的 原理是从内存中(堆内存)以二进制流的方式进行拷贝,重新分配一个内存块,那构造函数没有被执行也是非常正常的了.

使用clone方法创建对象的实例:

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("酸辣汤",18);//todo

            CloneTest copyClone = (CloneTest) cloneTest.clone();

            System.out.println("newclone:"+cloneTest.getName());

            System.out.println("copyClone:"+copyClone.getName());

        } catch (CloneNotSupportedException e) {

            e.printStackTrace();

        }

    }

}

输出:

newclone:酸辣汤

copyClone:酸辣汤

4. 使用反序列化方式

当序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。为了反序列化一个对象,需要让类实现Serializable接口。然后在使用new ObjectInputStream().readObject() 来创建对象。

下面demo:

public class Order implements Cloneable, Serializable{
    private String code;
    private String name;

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return (Order)super.clone();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Order order = (Order) o;

        if (code != null ? !code.equals(order.code) : order.code != null) return false;
        return name != null ? name.equals(order.name) : order.name == null;
    }

    @Override
    public int hashCode() {
        int result = code != null ? code.hashCode() : 0;
        result = 31 * result + (name != null ? name.hashCode() : 0);
        return result;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

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

4种方式。

public static void main(String[] args) throws Exception {
    // 1、使用new关键值创建对象
    Order order1 = new Order();
    order1.setCode("111");
    order1.setName("订单1");
    System.out.println(order1);

    // 2、使用clone
    Order order2 = new Order();
    Object clone = order2.clone();
    System.out.println(clone);

    // 3、使用反射
    Class c = Class.forName("cn.qidd.other.Order");
    Object o = c.newInstance();
    System.out.println(o);

    // 4、反序列化
    // 先序列化
    Order order3 = new Order();
    ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("order.obj"));
    os.writeObject(order3);
    // 再反序列化
    ObjectInputStream is = new ObjectInputStream(new FileInputStream("order.obj"));
    Object o1 = is.readObject();
    System.out.println(o1);
}

序列化是干什么的?
简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自 己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。一句话概括:序列化是指将对象的状态信息转换为可以存储或传输的形式的过程。

java中要序列化的类必要实现Serializable接口

什么情况下需要序列化
a)当你想把的内存中的对象状态保存到一个文件中或者数据库中时候;

b)当你想用套接字在网络上传送对象的时候;

c)当你想通过RMI(远程方法调用)传输对象的时候;

使用反序列化创建对象实例:

1.对象要实现Serializable接口

import java.io.Serializable; 

public class Person implements Serializable { 

    int age; 

    int height; 

    String name; 

    public Person(String name, int age, int height){ 

        this.name = name; 

        this.age = age; 

        this.height = height; 

    } 

}

2、序列化与反序列化

import java.io.FileInputStream; 

import java.io.FileOutputStream; 

import java.io.IOException; 

import java.io.ObjectInputStream; 

import java.io.ObjectOutputStream; 

public class MyTestSer { 

/**

* Java对象的序列化与反序列化

*/ 

public static void main(String[] args) { 

  Person zhangsan = new Person("zhangsan", 30, 170); 

  Person lisi = new Person("lisi", 35, 175); 

  Person wangwu = new Person("wangwu", 28, 178); 

  try { 

      //需要一个文件输出流和对象输出流;文件输出流用于将字节输出到文件,对象输出流用于将对象输出为字节 

      ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("person.ser")); 

      out.writeObject(zhangsan); 

      out.writeObject(lisi); 

      out.writeObject(wangwu); 

  } catch (IOException e) { 

e.printStackTrace(); 

  } 

  try { 

      ObjectInputStream in = new ObjectInputStream(new FileInputStream("person.ser")); 

      Person one = (Person) in.readObject(); 

      Person two = (Person) in.readObject(); 

      Person three = (Person) in.readObject(); 

      System.out.println("name:"+one.name + " age:"+one.age + " height:"+one.height); 

      System.out.println("name:"+two.name + " age:"+two.age + " height:"+two.height); 

      System.out.println("name:"+three.name + " age:"+three.age + " height:"+three.height); 

  } catch (Exception e) { 

e.printStackTrace(); 

  } 

  } 

}

运行结果:

name:zhangsan age:30 height:170  //todo

name:lisi age:35 height:175 

name:wangwu age:28 height:178

android中的场景

1.组件间(如activity间)的对象传递 (实现Parcelable或Serializable接口)

2.使用 Binder进行进程间的通讯传递的对象必须实现Parcelable接口

Serializable
是java的序列化接口,使用简单但是开销比较大,序列化和反序列化都涉及到大量的I/O操作,效率相对较低。Parcelable是Android提供的序列化方法,更适用于Android平台,效率很高,但是使用起来比较麻烦.Parcelable主要用在内存序列化上,序列化存储设备或将序列化后的对象通过网络传输建议使用Serializable。

网上看到他们的区别是:如果不知道一个对象的准确类型,RTTI会帮助我们调查。但却有一个限制:类型必须是在编译期间已知的。而反射使我们能在运行期间探察一个类,RTTI和“反射”之间唯一的区别就是:对RTTI来说,编译器会在编译期打开和检查.class文件。但对“反射”来说,.class文件在编译期间是不可使用的,而是由运行时环境打开和检查 ,我们利用反射机制一般是使用java.lang.reflect包提供给我们的类和方法。

5.RTTI和反射的区别

但是在说RTTI的形式包括三种:
1.传统的类型转换。如“(Apple)Fruit”,由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个 ClassCastException异常。
2.通过Class对象来获取对象的类型。如
Class c = Class.forName(“Apple”);
Object o = c.newInstance();
3.通过关键字instanceof或Class.isInstance()方法来确定对象是否属于某个特定类型的实例

其中的第二种,Class c = Class.forName(“Apple”),难道不是运行时得到类型信息的么???

《Thinking in Java》第592页有这么一句话"The result produced by Class.forName() cannot be known at compile time,so therefore all the method signature information is being extracted at tun time",书上说forName这种形式是在运行时得到类型信息的,而且这部分内容是和reflection有关的。而题主说的第二点,我在原书上看见的内容如下:"2. The Class object representing the type of your object.The Class object can be queried for useful runtime information."和题主举得例子还是有一定差距的吧。

RRTI要求是编译期已知的,是因为其编译时要打开和检查class文件,也就是class.forName(“name”),name是必须存在在当前classpath路径的class文件的文件名。也就是在编译时,编译器必须知道所有通过RTTI来处理的类.而当.class文件在编译时不可用时,就必须依赖反射机制在运行时打开和检查class文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值