如何创建和销毁对象

本文是我们名为“ 高级Java ”的学院课程的一部分。

本课程旨在帮助您最有效地使用Java。 它讨论了高级主题,包括对象创建,并发,序列化,反射等。 它将指导您完成Java掌握的过程! 在这里查看

1.简介

根据TIOBE编程社区索引 ,Java编程语言起源于Sun Microsystems并于1995年发布,是世界上使用最广泛的编程语言之一。 Java是一种通用编程语言。 它对软件开发人员的吸引力主要是由于其强大的库和运行时,简单的语法,丰富的受支持平台集(一次编写,可在任何地方运行– WORA)和强大的社区。

在本教程中,我们将介绍高级Java概念,并假设我们的读者已经对该语言有所了解。 它绝不是完整的参考,而是将Java技能提升到更高水平的详细指南。

在此过程中,将有很多代码片段需要研究。 在有意义的地方,将使用Java 7语法和Java 8语法来呈现相同的示例。

2.实例构建

Java是面向对象的语言,因此,创建新的类实例(对象)可能是其最重要的概念。 构造函数在新类实例初始化中起着核心作用,而Java提供了一些定义它们的方法。

隐式(生成)构造函数

Java允许定义一个没有任何构造函数的类,但这并不意味着该类将没有任何构造函数。 例如,让我们考虑这个类:

package com.javacodegeeks.advanced.construction;

public class NoConstructor {
}

该类没有构造函数,但是Java编译器将隐式生成一个构造函数,并且可以使用new关键字创建新的类实例。

final NoConstructor noConstructorInstance = new NoConstructor();

无参数的构造函数

没有参数的构造函数(或no-arg构造函数)是显式完成Java编译器工作的最简单方法。

package com.javacodegeeks.advanced.construction;

public class NoArgConstructor {
    public NoArgConstructor() {
        // Constructor body here
    }
}

使用new关键字创建类的新实例后,将调用此构造函数。

final NoArgConstructor noArgConstructor = new NoArgConstructor();

带参数的构造函数

带参数的构造函数是参数化新类实例创建的最有趣,最有用的方法。 下面的示例定义一个带有两个参数的构造函数。

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);
}

初始化块

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() {
    }
}

施工保证

Java提供了开发人员可能依赖的某些初始化保证。 未初始化的实例和类(静态)变量将自动初始化为其默认值。

类型 默认值
布尔值
字节 0
0
整型 0
0升
烧焦 \ u0000
浮动 0.0分
0.0天
对象参考 空值

表格1

让我们确认使用以下类作为简单示例:

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关键字实例化后:

final InitializationWithDefaults initializationWithDefaults = new InitializationWithDefaults(),

以下输出将显示在控制台中:

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

能见度

构造函数服从Java可见性规则,并且可以具有访问控制修饰符,该修饰符确定其他类是否可以调用特定的构造函数。

修饰符 子类 其他所有人
上市 无障碍 无障碍 无障碍
受保护的 无障碍 无障碍 无法访问
<无修饰符> 无障碍 无法访问 无法访问
私人的 无法访问 无法访问 无法访问

表2

垃圾收集

Java(尤其是JVM)使用自动垃圾收集。 简而言之,无论何时创建新对象,都会自动为其分配内存。 因此,只要不再引用这些对象,它们就会被销毁并回收其内存。

Java垃圾回收是世代相传的,并且基于以下假设:大多数对象都死于年轻对象(它们在创建后不久就不再被引用,因此可以安全地销毁)。 大多数开发人员过去认为,Java中的对象创建速度很慢,应尽可能避免实例化新对象。 实际上,事实并非如此:用Java创建对象非常便宜且快速。 但是,昂贵的是不必要地创建了长期存在的对象,这些对象最终可能会填满旧的对象并导致世界各地的垃圾回收。

终结者

到目前为止,我们已经讨论了构造函数和对象初始化,但实际上并没有提及任何与之对应的东西:对象破坏。 这是因为Java使用垃圾回收来管理对象的生命周期,垃圾回收器负责破坏不必要的对象并回收内存。

但是,Java中有一个称为终结器的特殊功能,该终结器有点类似于析构函数,但具有执行资源清除的不同目的。 终结器被认为是一个危险的功能(这会导致许多副作用和性能问题)。 通常,它们不是必需的,应该避免使用(非常罕见的情况,大多数情况下与本机对象有关)。 终结器的更好替代方案Java 7语言结构引入的,称为try-with-resourcesAutoCloseable接口,该接口允许编写干净的代码,如下所示:

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

3.静态初始化

到目前为止,我们已经研究了类实例的构造和初始化。 但是Java还支持称为静态初始值设定项的类级初始化构造。 除了附加的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
    }
}

因为可以从多个并行线程触发静态初始化块(当第一次加载该类时),所以Java运行时保证它只能以线程安全的方式执行一次。

4.施工模式

多年来,Java社区中出现了一些易于理解且广泛适用的构造(或创建)模式。 我们将介绍其中最著名的:单例,助手,工厂和依赖注入(也称为控制反转)。

辛格尔顿

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;
    }
}

如果您不想浪费资源,并希望在真正需要时懒惰地创建单身人士,则需要显式同步,这有可能导致多线程环境中的并发性降低(有关Java并发性的更多详细信息,将在本文中讨论)。本教程的第9部分并发最佳实践 )。

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;
    }
}

如今,单例在大多数情况下都不被认为是一个不错的选择,主要是因为单例使代码难以测试。 依赖项注入模式的主导(请参见下面的“ 依赖项注入”部分)也使单身人士不必要。

实用程序/助手类

实用程序或帮助程序类是许多Java开发人员使用的非常流行的模式。 基本上,它代表了非实例化类(与构造函数声明为private ),可选声明为final (有关声明类的更多详细信息final将在本教程的第3部分提供, 如何设计类和接口 ),包含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
    }
}

从经验丰富的软件开发人员的角度来看,这样的助手通常成为各种不相关方法的容器,这些方法没有找到其他放置位置,但应以某种方式共享并由其他类使用。 在大多数情况下,应避免此类设计决策:始终可以找到另一种方法来重用所需的功能,同时保持代码简洁明了。

事实证明,工厂模式在软件开发人员手中是极其有用的技术。 因此,它具有Java的几种风格,从工厂方法抽象工厂 。 工厂模式的最简单示例是static方法,该方法返回特定类的新实例( factory方法 )。 例如:

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接口实现的后面,仍然提供了创建书籍的通用方法。

依赖注入

依赖注入(也称为控制反转)被认为是类设计者的一种好习惯:如果某个类实例依赖于其他类实例,则应通过构造函数(或setter,策略)为其提供(注入)那些依赖。等),但不是由实例本身创建的。 让我们考虑以下示例:

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 );
    }
}

在这种情况下,该类的所有依赖关系都是从外部提供的,因此更改日期格式和编写测试用例非常容易。

5.下载源代码

6.接下来

在本教程的这一部分中,我们研究了类和类实例的构造和初始化技术,以及涵盖几种广泛使用的模式的方法。 在下一部分中,我们将剖析Object类及其知名方法的用法: equalshashCodetoStringclone

翻译自: https://www.javacodegeeks.com/2015/09/how-to-create-and-destroy-objects.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值