一文详解Java如何创建和销毁对象

目录
  • 一、介绍
  • 二、实例构造(Instance Construction)
    • 2.1 隐式(implicitly)构造器
    • 2.2 无参构造器(Constructors without Arguments)
    • 2.3 有参构造器(Constructors with Arguments)
    • 2.4 初始化块(Initialization Blocks)
    • 2.5 构造保障(Construction guarantee)
    • 2.6 可见性(Visibility)
    • 2.7 垃圾回收(Garbage collection)
    • 2.8 终结器(Finalizers)
  • 3、静态初始化(Static initialization)
  • 4、构造模式(Construction Patterns)
    • 4.1 单例模式(Singleton)
    • 4.2 Utility/Helper类
    • 4.3 工厂模式(Factory)
    • 4.4 依赖注入(Dependency Injection)

一、介绍

Java由Sun Microsystems发明并在1995年发布,是世界上使用最广泛的编程语言之一。Java是一个通用编程语言。由于它拥有功能强大的库、运行时、简单的语法、平台无关(Write Once, Run Anywhere - WORA)以及令人敬畏的社区从而吸引了很多的开发者。

本系列文章我们我们将会覆盖一些高级的Java概念,我们假设你对Java语言已经有一些基础知识。本系列文章并不是一个完整的参考,而是一个将您的Java技能提升到下一个级别的详细指南。

本系列文章中将会看到一些代码片段,在这些代码片段里面将会使用java 7的语法以及java 8的语法。

二、实例构造(Instance Construction)

Java是面向对象的编程语言,所以新实例(objects)的创建可能是它最重要的概念之一。在新的类实例中构造器(Constructors)扮演了非常核心的角色,Java对于构造器(Constructors)的定义提供了很多方案。

2.1 隐式(implicitly)构造器

Java允许定义无任何构造器的类,但是这并不意味着此类没有构造器。比如说,让我们看一下下面这个类。

package com.javacodegeeks.advanced.construction;
public class NoConstructor {
}

此类没有构造器,但是Java编译器会隐式地(implicitly)生成一个构造器并且在使用new关键字创建新的类实例时会被调用。

final NoConstructor noConstructorInstance = new NoConstructor();

2.2 无参构造器(Constructors without Arguments)

无参构造器是显式执行Java编译器工作的最简单的方法。

package com.javacodegeeks.advanced.construction;
public class NoArgConstructor {
    public NoArgConstructor() {
        // Constructor body here
    }
}

在使用new关键字创建此类的新实例时会此构造器将会被调用。

final NoArgConstructor noArgConstructor = new NoArgConstructor();

2.3 有参构造器(Constructors with Arguments)

有参构造器是参数化创建类实例的一个非常有意思和有用的方法。下面这个类定义了一个具有两个参数的构造器。

package com.javacodegeeks.advanced.construction;
public class ConstructorWithArguments {
    public ConstructorWithArguments(final String arg1,final String arg2) {
        // Constructor body here
    }
}

在这种情况下,当使用new关键字创建类实例时,两个构造参数都必须提供。

final ConstructorWithArguments constructorWithArguments = new ConstructorWithArguments( "arg1", "arg2" );

非常有意思的是,使用this关键字,构造器之间可以相互调用。这种连接构造函数的方式在作为减少代码重复方面是一个非常好的实践,并且从跟本上说这样做可以让一个类只有一个初始化入口点。接上例,我们添加一个只有一个参数的构造器。

public ConstructorWithArguments(final String arg1) {
this(arg1, null);
}

2.4 初始化块(Initialization Blocks)

Java也提供了另外一种使用初始化块的方式实现初始化逻辑。这个特性很少使用但是非常有必要了解一下它的存在。

package com.javacodegeeks.advanced.construction;
    public class InitializationBlock {
    {
        // initialization code here
    }
}

在某些情况下,初始化块可以弥补匿名无参构造器的缺陷。有一些特殊的类可能会有很多个初始化块并且他们会依次按照他们在代码中定义的顺序被调用,比如:

package com.javacodegeeks.advanced.construction;
public class InitializationBlocks {
    {
        // initialization code here
    } {
        // initialization code here
    }
}

初始化块并不是替代构造器并且他们可以独立于构造器而存在。但是需要提及的最重要的一点就是初始化块会在任何构造器被调用之前被执行。

package com.javacodegeeks.advanced.construction;
public class InitializationBlockAndConstructor {
    {
        // initialization code here
    }
    public InitializationBlockAndConstructor() {
    }
}

2.5 构造保障(Construction guarantee)

Java提供了一些开发者所依赖的初始化保障,未初始化的实例和类参数会自动初始化为它们的默认值。

让我们使用下面的例子来确认一下这些默认值。

package com.javacodegeeks.advanced.construction;
public class InitializationWithDefaults {
    private boolean booleanMember;
    private byte byteMember;
    private short shortMember;
    private int intMember;
    private long longMember;
    private char charMember;
    private float floatMember;
    private double doubleMember;
    private Object referenceMember;

    public InitializationWithDefaults() {
        System.out.println( "booleanMember = " + booleanMember );
        System.out.println( "byteMember = " + byteMember );
        System.out.println( "shortMember = " + shortMember );
        System.out.println( "intMember = " + intMember );
        System.out.println( "longMember = " + longMember );
        System.out.println( "charMember = " +
        Character.codePointAt( new char[] { charMember }, 0 ) );
        System.out.println( "floatMember = " + floatMember );
        System.out.println( "doubleMember = " + doubleMember );
        System.out.println( "referenceMember = " + referenceMember );
    }
}

一旦使用new关键字实例化:

inal InitializationWithDefaults initializationWithDefaults = new InitializationWithDefaults();

将会在控制台输出如下结果:

booleanMember = false
byteMember = 0
shortMember = 0
intMember = 0
longMember = 0
charMember = 0
floatMember = 0.0
doubleMember = 0.0
referenceMember = null

2.6 可见性(Visibility)

构造器受Java可见性规则约束并且可以拥有访问控制修饰符来决定是否其他类可以调用特定的构造函数。

2.7 垃圾回收(Garbage collection)

Java(特别是JVM)使用自动垃圾回收机制。简而言之,当新对象被创建,JVM就会自动为这些新创建的对象分配内存。于是,当这些对象没有任何引用的时候,他们就会被销毁并且他们所占用的内存就会被回收。

Java垃圾回收是分代的,基于这种假设(分代假设)大多数的对象在很年轻的时候就已经不可达(在他们被创建之后的很短的时间内就没有任何引用并且被安全销毁)。大多数开发者曾经相信在Java中创建对象是很慢的并且应该尽可能地避免新对象的实例化。

实际上,这并不成立:在Java中创建对象的开销非常的小并且很快。虽然如此,但是没有必要创建生命周期比较长的对象,因为创建过多的长寿命对象最终可能会填满老年代空间从而引发stop-the-world的垃圾回收,这样的话开销就会比较大。

2.8 终结器(Finalizers)

到目前为止,我们已经谈到了构造函数和对象初始化,但实际上并没有提到任何关于对象销毁的内容。这是因为Java使用垃圾收集器去管理对象的生命周期,并且垃圾收集器的责任就是去销毁无用对象并回收这些对象占用的内存。

然而,在Java中有一个被称为终结器(Finalizers)的特殊特性,它有点类似于析构函数,但是在执行资源清理时它所解决的是不同的意图。终结器(Finalizers)是被考虑用来解决一些危险的特征(比如会导致无数的副作用和性能问题的问题)。

一般来说,他们是没有必要的,应该避免(除了非常罕见的情况下,主要是有关本地对象)。Java 7语言引入了一种名为try-with-resources的更好的替代方法和AutoCloseable接口,它允许像如下的方式这样干净的写代码:

try ( final InputStream in = Files.newInputStream( path ) ) {
    // code here
}

3、静态初始化(Static initialization)

到目前为止,,我们已经谈到了构造函数和对象初始化。但是Java也支持类级别的初始化构造,我们称之为静态初始化(Static initialization)。

静态初始化(Static initialization)有点类似于初始化块,除了需要添加static关键字之外。注意静态初始化在每次类加载的时候它只执行一次。比如:

package com.javacodegeeks.advanced.construction;
public class StaticInitializationBlock {
    static {
        // static initialization code here
    }
}

和初始化块类似,在类定义时你可以包含任意数量的初始化块,它们会根据在类代码中出现的顺序依次执行,比如:

package com.javacodegeeks.advanced.construction;
public class StaticInitializationBlocks {
    static {
        // static initialization code here
    }
    static {
        // static initialization code here
    }
}

因为静态初始化(Static initialization)块可以从多个并行线程中触发(第一次类加载发生),Java运行时保证在线程安全的前提下仅仅被执行一次。

4、构造模式(Construction Patterns)

过去这几年很多易于理解和广泛应用的构造模式在Java社区出现。我们将会介绍几个比较常用的:单例模式(singleton)、帮助器(helpers)、工厂模式(factory)、依赖注入(dependency injection )——大家熟知的控制反转(inversion of control)。

4.1 单例模式(Singleton)

单例模式是软件开发者社区中最老也是最具争议性的模式之一。基本来说,它的主要思想就是确保在任何时候类仅仅只有一个实例被创建。思想就是如此简单,然而单例模式引发了很多关于如何使之正确的讨论,特别是线程安全的讨论。下面是单例模式原生版本的例子:

package com.javacodegeeks.advanced.construction.patterns;
public class NaiveSingleton {
    private static NaiveSingleton instance;

    private NaiveSingleton() {
    }

    public static NaiveSingleton getInstance() {
        if( instance == null ) {
            instance = new NaiveSingleton();
        }
        return instance;
    }
}

这段代码至少有一个问题就是如果多个线程同时调用,那么此类就能够创建多个实例。设计合适的单例模式的方法之一是使用类的 static final属性。

final property of the class.
package com.javacodegeeks.advanced.construction.patterns;
public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {
    }

    public static EagerSingleton getInstance() {
        return instance;
    }
}

如果你不想浪费资源并且希望在单例对象真正需要的时候才被延迟创建的话,这就要求显示同步了(explicit synchronization),这就有可能导致多线程环境中的并发性降低(关于并发的详细内容我们将会在后续的文章中讨论)。

package com.javacodegeeks.advanced.construction.patterns;
public class LazySingleton {
private static LazySingleton instance;
    private LazySingleton() {
    }

    public static synchronized LazySingleton getInstance() {
        if( instance == null ) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

如今,在大多数的案例中单例模式并不被考虑作为一个很好的选择,主要是因为单例模式将会导致代码很难测试。依赖注入模式让单例模式变得没有必要。

4.2 Utility/Helper类

utility或者helper类是被许多开发者所使用的相当流行的一种模式。基本来说,它所代表的是无实例( non-instantiable)类(构造器被定义成private),仅仅可以选择将方法定义成final(后续会介绍如何定义类)或者static。比如;

package com.javacodegeeks.advanced.construction.patterns;
public final class HelperClass {
    private HelperClass() {
    }

    public static void helperMethod1() {
        // Method body here
    }

    public static void helperMethod2() {
        // Method body here
    }
}

站在开发者的角度,helpers类经常所扮演的是一个容器的角色,这个容器中放了很多在其他地方找不到但是其他类需要相互共享和使用的互相不相关的方法。这种设计决定了在很多情况下要避免使用:总能找到另一种重用所需功能的方式,保持代码的简洁和清晰。

4.3 工厂模式(Factory)

工厂模式被证明是软件开发人员手中非常有用的技术。因此,Java有几种风格工厂模式,从工厂方法到抽象工厂。工厂模式最简单的例子是返回特定类的新实例的静态方法(工厂方法)。例如:

package com.javacodegeeks.advanced.construction.patterns;
public class Book {
    private Book( final String title) {
    }
    public static Book newBook( final String title ) {
        return new Book( title );
    }
}

有人可能会争辩说,介绍newBook工厂方法并没有什么意义,但是使用这种模式通常会使代码更具可读性。工厂模式的另一个变化涉及接口或抽象类(抽象工厂)。例如,让我们定义一个工厂接口:

public interface BookFactory {
    Book newBook();
}

依赖库类型,完成几种不同的实现:

public class Library implements BookFactory {
    @Override
    public Book newBook() {
        return new PaperBook();
    }
}

public class KindleLibrary implements BookFactory {
@Override
    public Book newBook() {
        return new KindleBook();
    }
}

现在,Book的特定类被隐藏在BookFactory接口实现之后,BookFactory仍然提供创建book的通用方式。

4.4 依赖注入(Dependency Injection)

依赖注入(一说控制反转)被类设计者认为是一个很好的做法:如果某些类的实例依赖其他类的实例,被依赖的实例应该通过构造(比如通过设置器——setters,或者策略——strategies模式等)的思想提供给依赖的实例,而不是依赖的实例自行创建。看一下下面这种情况:

package com.javacodegeeks.advanced.construction.patterns;
import java.text.DateFormat;
import java.util.Date;
public class Dependant {
    private final DateFormat format = DateFormat.getDateInstance();

    public String format( final Date date ) {
        return format.format( date );
    }
}

类Dependant需要一个DateFormat的实例,并且它仅仅只是在构造时通过调用DateFormat.getDateInstance() 创建。最好的设计方案应该是通过构造器参数的形式去完成相同的事情。

package com.javacodegeeks.advanced.construction.patterns;
import java.text.DateFormat;
import java.util.Date;
public class Dependant {
    private final DateFormat format;

    public Dependant( final DateFormat format ) {
        this.format = format;
    }

    public String format( final Date date ) {
        return format.format( date );
    }
}

按这种方案实现的话,类的所有依赖都是通过外部提供,这样就很容易的修改date format和为类写测试用例。

在本系列文章的这一部分中,我们一直在研究类和类的实例构造以及初始化技术,涵盖了几种广泛使用的模式。在下一部分中,我们将分析Object类以及其熟知方法的用法:equals,hashCode,toString和clone。

以上就是一文详解Java如何创建和销毁对象的详细内容,更多关于Java创建 销毁对象的资料请关注我们其它相关文章!

(0)

相关推荐

  • Java中对象的销毁方法分析

    本文较为详细的分析了Java中对象的销毁方法.分享给大家供大家参考.具体分析如下: Java中的基本数据类型变量和对象的名称引用变量如定义在方法中,都为局部变量.但对象本身不一定是局部生命周期.如函数外存在其他对该对象的引用变量,则该对象的生命周期延伸至该其他引用变量所在的块. 如从被调用函数参数引用传值或返回值到主调用函数所在的对象类型变量中,则该对象都仍存在(但被调用函数的该对象的引用变量生命周期结束,因此引用变量是局部变量),此时对象突破了局部变量的局部生命期. Java对象销毁 Java

  • JAVA创建和销毁对象的方法

    创建对象的几种方式 构造器 静态工厂方法 通过Builder 静态工厂方法优点 有名称-调用更清晰 每次调用时不会创建一个新对象 可以返回原返回类型的任何子类型的对象 创建参数化类型实例的时候,使代码更简洁 静态工厂方法缺点 类如果不含共有的或受保护的构造器,就不能被子类化 与其他静态方法实际上没有任何区别 遇到多个构造器参数时要考虑用构建器 重叠构造器模式 但是,在有很多参数时,客户端代码难以编写且难以阅读. JavaBeans模式. 调用一个无参构造器来创建对象,调用 setter 方法来设

  • java教学笔记之对象的创建与销毁

    本课程的目标是帮你更有效的使用Java.其中讨论了一些高级主题,包括对象的创建.并发.序列化.反射以及其他高级特性.本课程将为你的精通Java的旅程提供指导. 1. 引言 在TIOBE 编程语言排名中,Sun 公司于1995年开发的Java语言是世界上使用最广泛的编程语言之一.作为一种通用编程语言,因为强大的工具包和运行时环境.简单的语法.丰富的平台支持(一次编写,到处运行)以及的异常活跃的社区支持,Java语言对软件开发工程师极具吸引力. 在这一系列的文章中,涵盖了Java相关的高级内容,因此

  • 你真的知道Java中对象的销毁吗

    在日常的开发中.我们都知道,Java的内存清理是通过垃圾回收器进行的,那么其是如何将没用的对象被被清理掉的呢? Java 语言的内存自动回收称为垃圾回收(Garbage Collection)机制,简称 GC.垃圾回收机制是指 JVM 用于释放那些不再使用的对象所占用的内存. Java对象在使用后需要清理. 对象清理是释放该对象所占用的内存. 在创建对象时,用户必须使用new操作符为对象分配内存. 清除对象后,系统会自动回收内存,不需要用户进行额外的处理. 这也是Java语言的一个特性,它使程序

  • Java中创建对象的6种方式

    目录 背景 创建对象的 6 种方式 方法1:new 一个对象 方法2:克隆一个对象 方法3:类派发一个对象(反射) 方法4:动态加载一个对象(反射) 方法5:构造一个对象(反射) 方法6:反序列化一个对象 总结 背景 本文教你创建对象的 6 种方式,从低端到高端,各种创建方式,总有一个适合你,没有对象的自己生成一个吧! 创建对象的 6 种方式 假设有个女朋友类: @Data @NoArgsConstructor @AllArgsConstructor class GirlFriend { pri

  • 一文详解Java如何创建和销毁对象

    目录 一.介绍 二.实例构造(Instance Construction) 2.1 隐式(implicitly)构造器 2.2 无参构造器(Constructors without Arguments) 2.3 有参构造器(Constructors with Arguments) 2.4 初始化块(Initialization Blocks) 2.5 构造保障(Construction guarantee) 2.6 可见性(Visibility) 2.7 垃圾回收(Garbage collect

  • 一文详解Java中的类加载机制

    目录 一.前言 二.类加载的时机 2.1 类加载过程 2.2 什么时候类初始化 2.3 被动引用不会初始化 三.类加载的过程 3.1 加载 3.2 验证 3.3 准备 3.4 解析 3.5 初始化 四.父类和子类初始化过程中的执行顺序 五.类加载器 5.1 类与类加载器 5.2 双亲委派模型 5.3 破坏双亲委派模型 六.Java模块化系统 一.前言 Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最 终形成可以被虚拟机直接使用的Java类型,这个过程

  • 一文详解Java线程的6种状态与生命周期

    目录 1.线程状态(生命周期) 2.操作线程状态 2.1.新创建状态(NEW) 2.2.可运行状态(RUNNABLE) 2.3.被阻塞状态(BLOCKED) 2.4.等待唤醒状态(WAITING) 2.5.计时等待状态(TIMED_WAITING) 2.6.终止(TERMINATED) 3.查看线程的6种状态 1.线程状态(生命周期) 一个线程在给定的时间点只能处于一种状态. 线程可以有如下6 种状态: New (新创建):未启动的线程: Runnable (可运行):可运行的线程,需要等待操作

  • 一文详解Java线程中的安全策略

    目录 一.不可变对象 二.线程封闭 三.线程不安全类与写法 四.线程安全-同步容器 1. ArrayList -> Vector, Stack 2. HashMap -> HashTable(Key, Value都不能为null) 3. Collections.synchronizedXXX(List.Set.Map) 五.线程安全-并发容器J.U.C 1. ArrayList -> CopyOnWriteArrayList 2.HashSet.TreeSet -> CopyOnW

  • 一文详解Java中Stream流的使用

    目录 简介 操作1:创建流 操作2:中间操作 筛选(过滤).去重 映射 排序 消费 操作3:终止操作 匹配.最值.个数 收集 规约 简介 说明 本文用实例介绍stream的使用. JDK8新增了Stream(流操作) 处理集合的数据,可执行查找.过滤和映射数据等操作. 使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询.可以使用 Stream API 来并行执行操作. 简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式. 特点 不是数据结构

  • 一文详解Java闭锁和栅栏的实现

    目录 题目描述 -闭锁 题目 解题思路 代码详解 题目二描述 :栅栏 题目 解题思路 代码详解 题目描述 -闭锁 题目 有3个线程,线程A和线程B并行执行,线程C需要和线程A和B执行完成后才能执行,使用闭锁CountDownLatch实现 解题思路 创建一个类 :Abc Test CountDownLatch初始化值为2 新增 A B线程,在线程里执行逻辑后,计算-1 countDownLatch.countDown() 新增C线程,等待A,B线程来完成 countDownLatch.await

  • 一文详解Java的饿汉和懒汉设计模式

    目录 饿汉设计模式 懒汉设计模式 饿汉和懒汉模式的区别 本文主要讲述java的饿汉和懒汉设计模式 饿汉和懒汉设计模式的目的:使得该类的对象,只能有一个,不允许其他类,创建该类的对象. 饿汉设计模式 示例代码如下: public class HungerModel { public static void main(String[] args) { // GirlFriend girlFriend1 = new GirlFriend("white", 34); // GirlFriend

  • 一文详解Java过滤器拦截器实例逐步掌握

    目录 一.过滤器与拦截器相同点 二.过滤器与拦截器区别 三.过滤器与拦截器的实现 四.过滤器与拦截器相关面试题 一.过滤器与拦截器相同点 1.拦截器与过滤器都是体现了AOP的思想,对方法实现增强,都可以拦截请求方法. 2.拦截器和过滤器都可以通过Order注解设定执行顺序 二.过滤器与拦截器区别 在Java Web开发中,过滤器(Filter)和拦截器(Interceptor)都是常见的用于在请求和响应之间进行处理的组件.它们的主要区别如下: 运行位置不同:过滤器是运行在Web服务器和Servl

  • 一文详解Java拦截器与过滤器的使用

    目录 流程图 拦截器vs过滤器 SpringMVC技术架构图 项目Demo 依赖 Interceptor拦截器 Filter过滤器 1.多Filter不指定过滤顺序 2.多Filter指定过滤顺序 流程图 拦截器vs过滤器 拦截器是SpringMVC的技术 过滤器的Servlet的技术 先过过滤器,过滤器过完才到DispatcherServlet: 拦截器归属于SpringMVC,只可能拦SpringMVC的东西: 拦截器说白了就是为了增强,可以在请求前进行增强,也可以在请求后进行增强,但是不一

  • 一文详解Java抽象类到底有多抽象

    目录 抽象类 1.引出抽象类 2.认识抽象类 接口 1.抽象类和接口的区别 2.认识接口 3.具体实现 USB接口 接口表示能力 抽象类 1.引出抽象类 向上转型带来的最大的好处就是参数统一化,使用共同的父类引用,就可以接收所有的子类实例. 多态非常依赖方法覆写,但是子类可以选择性的覆写父类的方法,若需要强制要求子类覆写方法,就会用到抽象类其实显示生活中就有很多的抽象类,这些类都是概念化的,没法具体到某个实例,但是能描述这一类对象的共同属性和方法,比如人类,就没法具体到某一类人,世界上有很多类人

随机推荐