java创建对象的几种方式
java的对象创建方式有四种,如图所示:
使用new关键字
java创建对象最常用的方法是使用new关键字,使用new关键字可以创建一个对象实例,在创建的过程中可以调用无参构造方法或者是有参构造方法。(如果类中实现了有参构造方法,那么建议重写无参构造方法)
实例代码:
public class Demo {
private int a;
public Demo(int a){
this.a = a;
}
public static void main(String[] args){
Demo demo = new Demo(123);
System.out.println(demo.a);
System.out.println(demo.toString());
}
}
结果:
通过反射机制
使用Class类的newInstance方法
使用Class类的newInstance()
方法是使用反射创建对象最常用的方法之一,使用这个方法调用的是类的公有的无参数构造器
,即创建对象的这个类要包含有公有无参构造方法
,否则无法成功调用,运行报错。
实例代码1:
public class Demo {
public void test(){
int a = 100;
a ++;
System.out.println("a的值为: " + a);
}
public static void main(String[] args) throws Exception {
Demo demo = Demo.class.newInstance();
demo.test();
System.out.println(demo.toString());
}
}
结果:
实例代码2:
public class Demo {
public Demo(int a){
}
public void test(){
int a = 100;
a ++;
System.out.println("a的值为: " + a);
}
public static void main(String[] args) throws Exception {
Demo demo = Demo.class.newInstance();
demo.test();
System.out.println(demo.toString());
}
}
结果:
使用Construct类的newInstance方法
Construct类中包含有一个newInstance方法,这个方法与Class类的newInstance方法很相似,但是Construct类的newInstance方法可以调用类的有参构造方法(可以不是无参数的构造方法)或者是私有的构造方法。
实例代码:
import java.lang.reflect.Constructor;
class Test{
private String name;
private Test() {
System.out.println("无参数构造方法");
}
public Test(String name) {
System.out.println("有参数构造方法");
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Test{" +
"name='" + name + '\'' +
'}';
}
}
public class Demo {
public static void main(String[] args) throws Exception {
// 可以获取到public的或者是private的构造方法
Constructor<?>[] constructors = Test.class.getDeclaredConstructors();
// 只返回public的构造方法
Constructor<?>[] constructors1 = Test.class.getConstructors();
// 打印验证
for (Constructor temp: constructors) {
System.out.println(temp);
}
System.out.println();
for (Constructor temp1: constructors1) {
System.out.println(temp1);
}
System.out.println();
Constructor<?> noAccess = constructors[0];
Constructor<?> canAccess = constructors[1];
// 设置私有可以访问
noAccess.setAccessible(true);
// 私有无参构造
Object object = noAccess.newInstance();
Object object1 = canAccess.newInstance("小明");
System.out.println();
System.out.println(object.toString());
System.out.println(object1.toString());
}
}
结果:
使用Clone方法
如果要使用Clone方法,那就需要实现Cloneable接口并且需要重写clone()方法,调用这个对象的clone方法,将传入的对象内容全部拷贝,然后java虚拟机会给程序创建一个新的对象,在这个过程中,程序不会调用拷贝类的任何构造函数。
实例代码1:
class Test implements Cloneable{
private String name;
private Test() {
System.out.println("无参数构造方法");
}
public Test(String name) {
System.out.println("有参数构造方法");
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// 重写clone方法
public Test clone() throws CloneNotSupportedException {
return (Test)super.clone();
}
}
public class Demo {
public static void main(String[] args) throws Exception {
Test test = new Test("123");
Test test1 = test.clone();
System.out.println(test.toString());
System.out.println(test1.toString());
// false
System.out.println(test == test1);
// false
System.out.println(test.equals(test1));
}
}
结果:
调用clone方法创建出来的对象是一个全新的对象,调用 ==
或者 equals()
方法判断都可以看出。但是假如说Test
类里面存在有一个内部类,那么调用clone
方法创建出来的对象,外部类对象是一个全新的对象,内部类的对象引用指向的是内存中的同一块区域,也就是说内部类调用clone方法达到的效果是创建了一个新的对象引用,并没有发生内存分配等操作。
实例代码2:
class Sky{
private String type;
public Sky() {
}
public Sky(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
class Test implements Cloneable{
private String name;
private Sky sky;
private Test() {
System.out.println("无参数构造方法");
}
public Test(String name, Sky sky) {
this.name = name;
this.sky = sky;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Sky getSky() {
return sky;
}
public void setSky(Sky sky) {
this.sky = sky;
}
// 重写clone方法
public Test clone() throws CloneNotSupportedException {
return (Test)super.clone();
}
}
public class Demo {
public static void main(String[] args) throws Exception {
Sky sky = new Sky("蓝蓝的");
Test test = new Test("小明", sky);
Test test1 = test.clone();
System.out.println("测试Test外部类");
System.out.println(test);
System.out.println(test1);
System.out.println("测试内部类");
System.out.println(test.getSky());
System.out.println(test1.getSky());
System.out.println("测试 == 比较");
System.out.println(test == test1);
System.out.println(test.getSky() == test1.getSky());
System.out.println("测试equals方法");
System.out.println(test.equals(test1));
System.out.println(test.getSky().equals(test1.getSky()));
}
}
结果:
使用序列化和反序列化
什么情况下使用序列化和反序列化?
1、如果想要把内存中的对象状态保存到一个文件或者是数据库中的时候。
2、如果想使用套接字在网络上传输类对象的时候。
3、如果想通过RMI传输对象的时候。
序列化: 将一个类对象通过流的方式存储到文件中,注意:这个对象要重写Serializable 接口才能被序列化。另外实现序列化需要注意三点:
1、如果没有实现Serializable接口,会出现NotSerializableException
2、要求对象中的所有属性也都是可以序列化
3、如果某个属性不想序列化,可以在属性上加transient
关键字
反序列化: 将字节内容还原出来,转换成java类对象
序列化可以实现一个类的持久化,将一个类进行编码存储在本地或者可以通过网络传输。通过反序列化获取一个对象,类需要实现Serializable
接口,当使用反序列化时,java虚拟机会创建一个新的对象,这个过程中,java虚拟机不会调用任何的构造函数。
序列化代码工具类:
public class SerializationUtils {
public static void writeObject(Serializable serializable, String name){
try{
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(name));
out.writeObject(serializable);
out.close();
}catch (Exception e){
System.out.println("序列化出现异常!");
e.printStackTrace();
}
}
public static Object readObject(String name){
Object object = null;
try {
ObjectInputStream input = new ObjectInputStream(new FileInputStream(name));
object = input.readObject();
input.close();
}catch (Exception e){
System.out.println("反序列化出现异常!");
e.printStackTrace();
}
return object;
}
}
测试类:
import java.io.Serializable;
class Test implements Serializable {
private String name;
public Test() {
}
public Test(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Demo{
public static void main(String[] args) throws Exception {
Test test = new Test("小华");
// 路径名自定义。
SerializationUtils.writeObject(test, "路径名\\test.txt");
Test test1 = (Test) SerializationUtils.readObject("路径名\\test.txt");
System.out.println(test.getName());
System.out.println(test1.getName());
System.out.println("测试输出:");
System.out.println(test);
System.out.println(test1);
System.out.println("测试 == 比较");
System.out.println(test == test1);
System.out.println("测试equals方法");
System.out.println(test.equals(test1));
}
}
结果:
获取Class对象
使用反射机制可以动态获取类对象信息,Class类对象可以看作是一个工具,通过这个工具可以获取类的方法和属性,然后获取到的信息就可以提供给运行的程序。
直接获取
如果一开始就知道需要加载的是一个具体的类,那么可以直接通过以下这个方法获取类对象信息。
实例代码:
public class Demo {
public Demo(){
}
public Demo(int a){
}
public void test(){
int a = 100;
a ++;
System.out.println("a的值为: " + a);
}
public static void main(String[] args) throws Exception {
Class classz = Demo.class;
Demo demo = (Demo) classz.newInstance();
demo.test();
}
}
结果:
通过Class.forname()获取
一般来说,获取Class类对象是不知道具体类的,这个时候可以通过遍历包下面的类来获取Class对象,通过这个方式获取到的Class对象不会初始化类。
实例代码:
import java.io.Serializable;
import java.lang.reflect.Method;
class Test implements Serializable {
private String name;
public Test() {
System.out.println("无参数构造方法");
}
public Test(String name) {
System.out.println("有参数构造方法");
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class Demo{
public static void main(String[] args) throws Exception {
Class classz = Class.forName("Concurrent.Test");
Method[] methods = classz.getDeclaredMethods();
for (java.lang.reflect.Method method: methods) {
System.out.println(method);
}
}
}
结果:
通过instance.getClass()获取
通过这个方法获取到Class类对象需要先初始化类实例。
Test test = new Test();
Class classz = test.getClass();
通过类加载器获取
通过类加载器获取到Class类对象,需要传入类路径获取。
Class clazz = ClassLoader.loadClass(“Concurrent.Test”);
通过这个方法获取到的Class类对象并不会进行初始化,这就说明静态代码块和静态对象这些都不会执行。
java的引用
在java对象的使用中,一共存在有四种引用中,每一种引用的作用和范围都不同,这其中牵涉到了垃圾回收,持久化等等。
强引用
这是最普遍的一种引用方式,比如说String str= “abcd”
,变量str就是对字符串“abcd”
的一种强引用,只要强引用存在,那么垃圾回收器就不会回收这个对象。
软引用
这种引用用于描述还有使用价值但是非必须的对象,如果内存足够,那就不会回收这个对象,如果内存不足够,则回收。一般用于实现内存敏感的高速缓存,软引用可以和引用队列ReferenceQueue
一起使用。如果软引用的对象被垃圾回收,java虚拟机就会把这个软引用加入到与之关联的引用对列中。
弱引用
弱引用的用法和软引用大致相同,它们二者的区别:只是具有弱引用的对象具有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管内存是否足够,垃圾回收器都会回收这个对象。
虚引用
字面上理解的意思就是形同虚设,它和其他的几种引用都不太一样,虚引用不会决定对象的生命周期,如果一个对象只是具有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用必须要和引用队列ReferenceQueue
联合在一起使用,当垃圾回收器准备回收一个对象的时候,如果发现它还具有虚引用,那么在对象被回收之前就会把这个虚引用添加到与之对应的引用对列中。