1.简介Java由SunMicrosystems发明并于1995年发布,是世界上使用最广泛的编程语言之一。Java是一种通用编程语言。由于其强大的库、运行时、简单的语法、平台独立性(一次编写,随处运行-WORA)和很棒的社区,它吸引了许多开发人员。在本系列文章中,我们将介绍一些高级Java概念,我们假设您已经具备一些Java语言的基础知识。本系列文章并不是完整的参考,而是将您的Java技能提升到一个新水平的详细指南。在本系列文章中,您将看到一些代码片段,其中将使用java7的语法和java8的语法。二、实例构造(InstanceConstruction)Java是一种面向对象的编程语言,因此创建新的实例(对象)可能是它最重要的概念之一。构造函数在新类实例中起着非常核心的作用,Java为构造函数的定义提供了很多解决方案。2.1隐式(implicitly)构造函数Java允许类的定义不带任何构造函数,但这并不意味着这样的构造函数不存在。例如,让我们看看下面的类。包com.javacodegeeks.advanced.construction;publicclassNoConstructor{}这个类没有构造函数,但是Java编译器会隐式(implicitly)生成一个构造函数,当使用new关键字创建一个新的类实例时会调用它。finalNoConstructornoConstructorInstance=newNoConstructor();2.2无参数构造函数无参数构造函数是显式执行Java编译器工作的最简单方法。包com.javacodegeeks.advanced.construction;publicclassNoArgConstructor{publicNoArgConstructor(){//此处为构造函数主体}}当使用new关键字创建此类的新实例时将调用此构造函数。最终NoArgConstructornoArgConstructor=newNoArgConstructor();2.3带参数的构造函数带参数的构造函数是一种非常有趣和有用的参数化创建类实例的方法。下面的类定义了一个带有两个参数的构造函数。包com.javacodegeeks.advanced.construction;publicclassConstructorWithArguments{publicConstructorWithArguments(finalStringarg1,finalStringarg2){//这里是构造函数体}}这种情况下,使用new关键字创建类实例时,必须提供两个构造参数。finalConstructorWithArgumentsconstructorWithArguments=newConstructorWithArguments("arg1","arg2");有趣的是,使用this关键字,构造函数可以相互调用。这种连接构造函数的方式在减少代码重复方面是一种非常好的做法,基本上这样做允许一个类只有一个初始化入口点。继续前面的示例,我们添加一个只有一个参数的构造函数。publicConstructorWithArguments(finalStringarg1){this(arg1,null);}2.4初始化块(InitializationBlocks)Java还提供了另一种使用初始化块来实现初始化逻辑的方法。此功能很少使用,但值得了解它的存在。包com.javacodegeeks.advanced.construction;publicclassInitializationBlock{{//初始化代码在这里}}在某些情况下,初始化块可以弥补匿名无参数构造函数的缺点。一些特殊的类可能有很多初始化块,它们将按照它们在代码中定义的顺序被调用,例如:packagecom.javacodegeeks.advanced.construction;publicclassInitializationBlocks{{//初始化代码在这里}{//初始化代码在这里}}初始化块不是构造函数的替代品,它们可以独立于构造函数存在。但最重要的是,初始化块是在调用任何构造函数之前执行的。包com.javacodegeeks.advanced.construction;publicclassInitializationBlockAndConstructor{{//初始化代码在这里}publicInitializationBlockAndConstructor(){}}2.5构造保证(Constructionguarante)Java提供了一些开发者依赖的初始化保证,未初始化的实例和类参数被自动初始化为其默认值。让我们使用以下示例确认这些默认值。包com.javacodegeeks.advanced.construction;publicclassInitializationWithDefaults{privatebooleanbooleanMember;私有字节byteMember;私人短短会员;私有int成员;私人长长成员;私有字符charMember;私人浮动浮动成员;私人双双会员;私有对象引用成员;publicInitializationWithDefaults(){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(newchar[]{charMember},0));System.out.println("floatMember="+floatMember);系统输出.println("doubleMember="+doubleMember);System.out.println("referenceMember="+referenceMember);使用new关键字实例化后:finalInitializationWithDefaultsinitializationWithDefaults=newInitializationWithDefaults();会在控制台输出结果如下:booleanMember=falsebyteMember=0shortMember=0intMember=0longMember=0charMember=0floatMember=0.0doubleMember=0.0referenceMember=null2.6可见性构造函数受Java可见性规则约束,可以有访问控制修饰符来确定其他Class是否可以调用特定的构造函数2.7垃圾回收(Garbagecollection)Java(尤其是JVM)使用了自动垃圾回收机制。简而言之,当创建新对象时,JVM会自动为这些新创建的对象分配内存。然后,当这些对象没有引用时,将它们销毁,并回收它们占用的内存。Java垃圾回收是分代的,基于这样的假设(generationalassumption),即大多数对象在很小的时候就不可访问(没有任何引用并且在创建后不久就被安全地销毁)。大多数开发人员过去认为Java中的对象创建速度很慢,应尽可能避免实例化新对象。实际上,这是不正确的:在Java中创建对象非常便宜和快速。即便如此,也没有必要创建长寿命对象,因为创建太多长寿命对象最终可能会填满老年代空间并触发stop-the-world垃圾收集,这会更加昂贵。2.8Finalizers到目前为止,我们已经讨论了构造函数和对象初始化,但实际上还没有提到任何关于对象销毁的内容。这是因为Java使用垃圾收集器来管理对象的生命周期,而垃圾收集器的职责就是销毁无用的对象,回收这些对象占用的内存。然而,在Java中有一个特殊的特性叫做Finalizers,它有点类似于析构函数,但它在执行资源清理时解决了不同的意图。终结器(Finalizers)被认为是为了解决一些危险的特性(比如导致无数副作用的问题和性能问题)。一般来说,它们不是必需的,应该避免(极少数情况除外,主要与本地对象有关)。Java7语言引入了一个更好的替代方法,称为try-with-resources和AutoCloseable接口,它允许像这样干净地编写代码:初始化(静态初始化)到目前为止,我们已经讨论了构造函数和对象初始化。但是Java也支持类级别的初始化构造,我们称之为静态初始化(Staticinitialization)。静态初始化(Staticinitialization)有点类似于初始化块,只是需要加上static关键字。请注意,每个类加载只执行一次静态初始化。例如:包com.javacodegeeks.advanced.construction;publicclassStaticInitializationBlock{static{//这里是静态初始化代码}}与初始化块类似,你可以在类定义中包含任意数量的初始化块,它们会根据类代码出现的顺序依次执行,例如:包com.javacodegeeks.advanced.construction;publicclassStaticInitializationBlocks{static{//这里是静态初始化代码}static{//这里是静态初始化代码}}因为静态初始化(Staticinitialization)块可以从多个并行线程中触发(最先类加载发生),Javaruntime在线程安全的前提下保证只会执行一次。4.构造模式(ConstructionPatterns)在过去的几年里,Java社区中出现了很多简单易懂、应用广泛的构造模式。我们将介绍几个比较常用的:单例、助手、工厂、依赖注入——也就是所谓的控制反转。4.1单例(Singleton)单例模式是软件开发者社区中最古老也是最具争议的模式之一。基本上,它的主要思想是确保在任何时候只创建一个类的一个实例。这个想法就是这么简单,但是单例模式引发了很多关于如何正确实现它的讨论,尤其是线程安全。以下是Singleton模式的本机版本示例:packagecom.javacodegeeks.advanced.construction.patterns;publicclassNaiveSingleton{privateNaiveSingleton(){}publicstaticNaiveSingletongetInstance(){if(instance==null){instance=newNaiveSingleton();}返回实例;}}这段代码至少有一个问题是,如果多个线程同时调用它,那么这个类可以创建多个实例。设计合适的单例模式的方法之一是使用类的静态最终属性。class.package的最终属性com.javacodegeeks.advanced.construction.patterns;publicclassEagerSingleton{privateEagerSingleton(){}publicstaticEagerSingletongetInstance(){;return}instance如果不想浪费资源,希望单例对象在真正需要的时候懒惰地创建,这就需要显式同步(explicitsynchronization),在多线程环境下可能会导致并发性降低(详情关于并发的内容会在后续的文章中讨论)。包com.javacodegeeks.advanced.construction.patterns;公共类LazySingleton{私有静态LazySingleton实例;privateLazySingleton(){}publicstaticsynchronizedLazySingletongetInstance(){if(instance==null){instance=newLazySingleton(}Singletonreturninstance;}}如今,单例模式在大多数情况下都不是一个好的选择,主要是因为单例模式会使代码难以测试。依赖注入模式使得单例模式变得不必要。4.2Utility/Helper类Utility或helper类是许多开发人员使用的相当流行的模式。基本上,它代表的是非-可实例化类(构造函数定义为private),只能选择将方法定义为final(后面会介绍如何定义类)或static。例如;packagecom.javacodegeeks.advanced.construction.patterns;publicfinalclassHelperClass{privateHelperClass(){}publicstaticvoidhelperMethod1(){//这里是方法体}publicstaticvoidhelperMethod2(){//这里是方法体}}从开发者的角度来看,助手类通常起到了容器的作用,里面包含了很多其他地方找不到但需要被其他类共享和使用的不相关的方法。在许多情况下应该避免这种设计决策:总是有另一种方法来重用所需的功能,保持代码干净和清晰。4.3工厂模式(Factory)工厂模式被证明是软件开发人员手中非常有用的技术。因此,Java有多种工厂模式,从工厂方法到抽象工厂。工厂模式最简单的例子是返回特定类的新实例的静态方法(工厂方法)。例如:packagecom.javacodegeeks.advanced.construction.patterns;publicclassBook{privateBook(finalStringtitle){}publicstaticBooknewBook(finalStringtitle){returnnewBook(title);}}有人可能会争辩说,引入一个newBook工厂方法并没有多大意义,但使用这种模式通常会使代码更具可读性。工厂模式的另一种变体涉及接口或抽象类(抽象工厂)。例如,让我们定义一个工厂接口:publicinterfaceBookFactory{BooknewBook();}根据图书馆的类型,完成了几种不同的实现:publicclassLibraryimplementsBookFactory{@OverridepublicBooknewBook(){returnnewPaperBook();}}公共类KindleLibrary实现BookFactory{@OverridepublicBooknewBook(){returnnewKindleBook();既然Book-specific类隐藏在BookFactory接口的实现之后,BookFactory仍然提供了一种创建书籍的通用方法。4.4依赖注入(DependencyInjection)依赖注入(inversionofcontrol)被类设计者认为是一个很好的实践:如果某些类的实例依赖于其他类的实例,则应该构造依赖实例(比如通过Setters——setters,或者策略——策略模式等)提供给依赖实例,而不是依赖实例创建它们自己。考虑这种情况:packagecom.javacodegeeks.advanced.construction.patterns;importjava.text.DateFormat;importjava.util.Date;publicclassDependentpublicStringformat(finalDatedate){returnformat.format(date);}}ClassDependent需要DateFormat的一个实例,它仅在构建时通过调用DateFormat.getDateInstance()创建。最好的设计方案应该是通过构造函数参数的形式来完成同样的事情。packagecom.javacodegeeks.advanced.construction.patterns;importjava.text.DateFormat;importjava.util.Date;publicclassDependent{privatefinalDateFormat格式;publicDependant(finalDateFormatformat){this.format=format;}publicStringformat(finalDatedate){returnformat.format(date);}}如果实现了这个方案,类的所有依赖都是对外提供的,所以很容易修改类的日期格式和编写测试用例。在本系列的这一部分中,我们一直在研究类及其实例构造和初始化技术,涵盖了几种广泛使用的模式。在接下来的部分中,我们将分析Object类及其著名方法的用法:equals、hashCode、toString和clone。
