• java创建对象的五种方式


    一、使用new关键字

    如 User user=new User();
    执行这条语句,jvm做了什么?

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

    二、使用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();
    
            }
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    输出:

    newclone:酸辣汤
    
    copyClone:酸辣汤
    
    • 1
    • 2
    • 3

    注意:

    1.clone是Object中的方法,Cloneable是一个标识接口,它表明这个类的对象是可以拷贝的。如果没有实现Cloneable接口却调用了clone()函数将抛出异常
    
    2.Object.clone()未做同步处理,线程不安全
    
    3.clone()有深拷贝和浅拷贝两种方式
    
    • 1
    • 2
    • 3
    • 4
    • 5

    三、使用反序列化

    序列化是干什么的?
    简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自 己的各种各样的方法来保存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; 
    
        } 
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    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(); 
    
      } 
    
      } 
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    运行结果:

    name:zhangsan age:30 height:170  //todo
    
    name:lisi age:35 height:175 
    
    name:wangwu age:28 height:178
    
    • 1
    • 2
    • 3
    • 4
    • 5

    android中的场景
    1.组件间(如activity间)的对象传递 (实现Parcelable或Serializable接口)
    2.使用 Binder进行进程间的通讯传递的对象必须实现Parcelable接口

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

    四、使用反射

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

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

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

    • 类.class,如Person.class
    • 对象.getClass()
    • Class.forName(“类全路径”)

    2.通过反射创建类对象的实例对象

    在拿到类对象的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 +
    
                    '}';
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    使用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);
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行结果:

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

    注意 :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);
    
            }
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行结果:

    public com.eft.reflect.Person()
    
    public com.eft.reflect.Person(java.lang.String,int)
    
    • 1
    • 2
    • 3

    获取指定的构造方法
    通过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);
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    运行结果:

    public com.eft.reflect.Person()
    
    public com.eft.reflect.Person(java.lang.String,int)
    
    • 1
    • 2
    • 3

    通过构造方法创建对象
    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); 
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行结果:

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

    五、使用Unsafe

    sun.misc.Unsafe中提供allocateInstance方法,仅通过Class对象就可以创建此类的实例对象,而且不需要调用其构造函数、初始化代码、JVM安全检查等。它抑制修饰符检测,也就是即使构造器是private修饰的也能通过此方法实例化,只需提类对象即可创建相应的对象。由于这种特性,allocateInstance在java.lang.invoke、Objenesis(提供绕过类构造器的对象生成方式)、Gson(反序列化时用到)中都有相应的应用。
    直接看例子

    package cn.eft.llj.unsafe;
    import java.lang.reflect.Field;
    
    import sun.misc.Unsafe;
    public class Demo9 {
    
        static Unsafe unsafe;
    
        static {
            //获取Unsafe对象
            try {
                Field field = Unsafe.class.getDeclaredField("theUnsafe");
                field.setAccessible(true);
                unsafe = (Unsafe) field.get(null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        static class C1 {
            private String name;
    
            private C1() {
                System.out.println("C1 default constructor!");
            }
    
            private C1(String name) {
                this.name = name;
                System.out.println("C1 有参 constructor!");
            }
            
            public void test(){
                System.out.println("执行了test方法");
            }
        }
    
        public static void main(String[] args) throws InstantiationException {
            C1 c= (C1) unsafe.allocateInstance(C1.class);
            System.out.println(c);
            c.test();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    输出结果:

    cn.eft.llj.unsafe.Demo9$C1@6bc7c054
    
    执行了test方法
    
    • 1
    • 2
    • 3
  • 相关阅读:
    【数据结构】线性表的链式存储结构
    codeforces 828 E1
    JavaScript基础: 异步
    学习笔记02-iview组件使用
    非零基础自学Java (老师:韩顺平) 第13章 常用类 13.5 StringBuffer类
    为什么在vue2中改变数据视图不会更新,带你阅读源码
    搜维尔科技:【周刊】适用于虚拟现实VR中的OptiTrack
    基于Python和mysql开发的今天吃什么微信小程序(源码+数据库+程序配置说明书+程序使用说明书)
    Linux挂载Windows端NFS服务(实现板端Linux与PC互传文件)
    建模杂谈系列155 从一段程序讨论通用的任务执行方法
  • 原文地址:https://blog.csdn.net/qq_41512902/article/details/126242060