Java 创建对象的四种方式
一、使用new创建对象
**使用new关键字创建对象应该是最常见的一种方式,但我们应该知道,使用new创建对象会增加耦合度。无论使用什么框架,都要减少new的使用以降低耦合度。 **
package jwx;
/**
* @ Author :jwx
* @ Date :16:45 2020/5/25
* @ Description:测试
* @ Modified By:
* @Version:1.0
*/
public class Hello {
public void say(){
System.out.printf("Hello World!");
}
}
import jwx.Hello;
/**
* @ Author :jwx
* @ Date :16:42 2020/5/25
* @ Description:主类
* @ Modified By:
* @Version:1.0
*/
public class Main {
public static void main(String[] args) {
Hello hello = new Hello();
hello.say();
}
}
运行结果放出来,这应该每位java选手都会的操作,下面来的不常用的
二、使用反射的机制创建对象
.使用反射机制创建对象的步骤如下:
1、先声明你要创建的对象的类全称;使用Class类的静态方法forName(String.className)加载这个类的字节码(注意,加载字节码不等于实例化对象) ,返回 一个Class对象,这个对象代表的是一个字节码文件。
2、调用这个类对象(字节码)的方法newInstance()方法(注意:这里的这个newInstance方法默认调用默认的构造方法即调用无参的构造方法, 一旦构造方法有参数时,此方法行不通,需要使用构造方法的对象的相关方法来 实例化)实例化类,返回的是Object类型
3、强制转换Object成你所需类型
Hello类不变,修改Main方法下面代码
package jwx;
/**
* @ Author :jwx
* @ Date :16:56 2020/5/25
* @ Description:反射机制
* @ Modified By:
* @Version:1.0
*/
public class RefelectDemo {
public static void main(String[] args) {
try
{
Class heroClass = Class.forName("jwx.Hello");
Hello h =(Hello) heroClass.newInstance();
h.say();
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
catch (InstantiationException e)
{
e.printStackTrace();
}
}
}
使用Constructor类的newInstance方法
package jwx;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @ Author :jwx
* @ Date :16:58 2020/5/25
* @ Description:
* @ Modified By:
* @Version:1.0
*/
public class ConsturctorDemo {
public static void main(String[] args) {
try
{
//获取类对象
Class heroClass = Class.forName("jwx.Hello");
//获取构造器
Constructor constructor = heroClass.getConstructor();
Hello h =(Hello) constructor.newInstance();
h.say();
}
catch (NoSuchMethodException e)
{
e.printStackTrace();
}
catch (InvocationTargetException e)
{
e.printStackTrace();
}
catch (IllegalAccessException e)
{
e.printStackTrace();
}
catch (InstantiationException e)
{
e.printStackTrace();
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
三、采用clone
clone时,需要已经有一个分配了内存的源对象,创建新对象时,首先应该分配一个和源对象一样大的内存空间。要调用clone方法需要实现Cloneable接口
下面我们具体看看Clone方法
Cloneable接口其实就是一个标记接口,只有实现这个接口后,然后在类中重写Object中的clone方法,然后通过类调用clone方法才能克隆成功,如果不实现这个接口,则会抛出CloneNotSupportedException(克隆不被支持)异常。Object中clone方法:
这个方法有一个native关键字,没有方法体,但是他与abstract关键字不同,他的方法内容在jvm内存中右具体的实现,java是通过jvm判断是否实现了Cloneable接口。
**clone是首先分配内存,这里分配的内存与调用clone方法对象的内存相同,然后将源对象中各个变量的值,填充到新的对象中,填充完成后,clone方法返回一个新的地址,这个新地址的对象与源对象相同,只是地址不同。 **
克隆分为深度克隆和浅度克隆
下面通过例子来看
创建Dog对象
package jwx;
import jdk.nashorn.internal.objects.annotations.Constructor;
import jdk.nashorn.internal.objects.annotations.Getter;
/**
* @ Author :jwx
* @ Date :17:06 2020/5/25
* @ Description:
* @ Modified By:
* @Version:1.0
*/
public class Dog {
private String name; //姓名
private int age; //年龄
private StringBuffer sex; //性别
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", sex=" + sex +
'}';
}
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 StringBuffer getSex() {
return sex;
}
public void setSex(StringBuffer sex) {
this.sex = sex;
}
}
然后我们再创建Person对象
package jwx;
/**
* @ Author :jwx
* @ Date :17:09 2020/5/25
* @ Description:
* @ Modified By:
* @Version:1.0
*/
public class Person implements Cloneable {
private String name; //姓名
private int age; //年龄
private Dog dog; //宠物狗
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 Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", dog=" + dog +
'}';
}
@Override
public Person clone() throws CloneNotSupportedException {
Person person = null;
try {
person = (Person) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return person;
}
}
写个测试类来康康
package jwx;
/**
* @ Author :jwx
* @ Date :17:11 2020/5/25
* @ Description:克隆测试
* @ Modified By:
* @Version:1.0
*/
public class ColneDemo {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1=new Person();
person1.setAge(20);
person1.setName("黑哥");
Dog dog1 = new Dog();
dog1.setAge(5);
dog1.setName("小花");
dog1.setSex(new StringBuffer("男"));
person1.setDog(dog1);
System.out.println("person1: "+person1+" person1的hashcode:"+person1.hashCode()+" person1中dog1的hashcode:"+person1.getDog().hashCode());
Person person2 = person1.clone(); //调用重写的clone方法,clone出一个新的Person ---person2
System.out.println("person2: "+person2+" person2的hashcode:"+person2.hashCode()+" person2中dog1的hashcode:"+person2.getDog().hashCode());
}
}
测试结果:
可以看出person1与person2的hashcode不同,也就是说clone方法并不是把person1的引用赋予person2,而是在堆中重新开辟了一块空间,将person1复制过去,将新的地址返回给person2。
但是person中dog的hashcode与perosn中dog的hashcode相同,也就是这两个指向了同一个对象,修改person2中的dog会造成person中dog数据的改变。但是修改person2中的基本数据类型与Stirng类型时,不会造成person1中数据的改变.基本数据类型例如int,在clone的时候会重新开辟一个四个字节的大小的空间,将其赋值。而String则由于String变量的唯一性,如果在person2中改变了String类型的值,则会生成一个新的String对象,对之前的没有影响。这就是浅度克隆。
如何实现深度clone? 这里介绍第一种方法,另一种方法是用流的拷贝,我把它放到文章最后面
首先需要让dog重写clone方法,实现cloneable接口
@Override
protected Dog clone() throws CloneNotSupportedException {
// TODO Auto-generated method stub
return (Dog)super.clone();
}
然后,在Person的clone方法中将Person中的dog对象手动clone一下。
@Override
public Person clone() throws CloneNotSupportedException {
Person person = null;
person = (Person) super.clone();
person.dog = dog.clone();
return person;
}
执行结果:
这里可以看到两个dog的hashcode已经不同了,说明这已经是两个对象了
这就是深度克隆,哈哈,就是这么easy!
四、采用序列化机制
使用序列化时,要实现实现Serializable接口,将一个对象序列化到磁盘上,而采用反序列化可以将磁盘上的对象信息转化到内存中。
package jwx;
import java.io.Serializable;
/**
* @ Author :jwx
* @ Date :16:45 2020/5/25
* @ Description:测试
* @ Modified By:
* @Version:1.0
*/
//实现Serializable接口
public class Hello implements Serializable {
public void say(){
System.out.printf("Hello World!");
}
}
下面来看测试主函数
package jwx;
/**
* @ Author :jwx
* @ Date :8:04 2020/5/26
* @ Description:
* @ Modified By:
* @Version:1.0
*/
import java.io.*;
public class Serialize
{
public static void main(String[] args)
{
Hello h = new Hello();
//准备一个文件用于存储该对象的信息
File f = new File("hello.obj");
try(FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream oos = new ObjectOutputStream(fos);
FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois = new ObjectInputStream(fis)
)
{
//序列化对象,写入到磁盘中
oos.writeObject(h);
//反序列化对象
Hello newHello = (Hello)ois.readObject();
//测试方法
newHello.say();
}
catch (FileNotFoundException e)
{
e.printStackTrace();
}
catch (IOException e)
{
e.printStackTrace();
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
运行结果:输出Hello World!
最后再总结一下:
-
1.使用new创建对象
使用new关键字创建对象应该是最常见的一种方式,但我们应该知道,使用new创建对象会增加耦合度。无论使用什么框架,都要减少new的使用以降低耦合度。 -
2.使用反射的机制创建对象
使用Class类的newInstance方法 -
3.采用clone
clone时,需要已经有一个分配了内存的源对象,创建新对象时,首先应该分配一个和源对象一样大的内存空间。要调用clone方法需要实现Cloneable接口 -
4.采用序列化机制
使用序列化时,要实现实现Serializable接口,将一个对象序列化到磁盘上,而采用反序列化可以将磁盘上的对象信息转化到内存中。哈哈,现在创建对象,不会只用new了吧,我个人比较推荐反射机制创建对象