JAVA整理

Java成长之路(五)

Posted by MiaoMiaoMiao on January 7, 2021

Java成长之路五

初始化和清理

  • 垃圾收集器(Garbage Collector,GC)去自动回收不再被使用的对象所占的资源;

利用构造器保证初始化

  • 在Java中,类的设计者通过构造器保证每个对象的初始化;
  • 若类有构造器,Java会在用户使用对象之前(即对象刚创建完成)自动调用对象的构造器方法;
  • 构造器在其命名的方法;(1.任何命名都可能与勒种其他元素的命名冲突;2.编译器必须始终知道构造器的方法名称,从而调用它;)
  • Java中构造器名称和类名称相同(构造器的命名方法和类命名方法相同),在初始化过程中自动调用构造器的方法是有意义的;
  • 构造器保证了对那个在你使用之前进行了正确的初始化;
  • 无参数构造器被称为默认构造器(default);
  • 在Java中,对象的创建与初始化是统一的概念,二者不可分别;
  • 构造器没有返回值与void普通方法不同,void可返回一个空值,而构造器本身无任何返回值;

方法重载

  • 当创建一个对象时,会给此对象分配内存空间命名;
  • 方法是行为的命名,通过名字指代所有对象,属性和方法;
  • 构造器是方法重载的重要促使因素,因为构造器保持与类名相同;
  • 方法重载是必要的,它允许方法具有相同的方法名称,但接受的参数不同;

区分重载方法

  • 通过每个重载方法必须有独一无二的参数列表来区分方法重载;
  • 类名+方法重载;
  • 基本类型可以自动从较小的转型为较大的类型,结合重载使用会让人产生疑惑;
  • 如果传入的参数类型大于方法期望接受的期望类型,你必须首先做向下转换;如果你不做,编译器会报错;
  • 不能根据返回值类型区分重载的方法;
  • 若调用一个方法且忽略返回值,这叫做调用一个函数的副作用;

元参数构造器

  • 一个无参考构造器就是不接受参数的构造器,回来创建一个“默认”的对象;
  • 如果你创建类,类没有构造器,编译器会自动为你创建一个构造器。
  • 一旦你显式地定义了构造器(无论是有参还是无惨),编译器就不会自动为你创建无参构造器;
  • 有构造器系统就会使用用户创建的构造器;无构造器就会用默人的构造器;

this关键字

```java
class Banana{
    void peel(int i);
}

pubic static void main(String[] args){
    Banana a = new Banana();
    Banana b = new Banana();
    a.peel(1);
    b.peel(2);
}
```  - this 关键字只能在非静态方法内部使用,当你调用一个对象方法时,this生成一个对象引用,可对待其他一样对待这个引用;  - 如果你在该类方法,不要使用this,直接调用即可,this自动地应用于其他方法上;  - this关键字只用在一些必须显式当前对象的引用的特殊场合;

构造器中调用构造器

  • 在一个类中有多个构造器,有时你想在一个构造器中调用另一个构造器来避免重复代码,通过this关键字实现这种调用;
  • 通常说this,代表的就是”这个对象”或“当前对象”,它本身生成对当前对象的引用;但在一个构造器中,当你给this一个参数列表时,它通过最直接的方式显式地调用匹配参数列表的构造器;
  • 必须只能通过this调用一次构造器,另外必须首先调用构造器,否则编译器会报错;
  • 可以通过this.s表明为成员比阿娘的s;
  • 编译器不允许你在构造器之外的方法里调用构造器;

static的含义

  • static方法中不会存在this,你不能在静态方法中,调用非静态方法(反之亦可)
  • 静态方法是为类而创建的,不需要任何对象;
  • 一个类中的静态方法可以访问其他静态方法和静态属性;

垃圾回收器

  • Java中用垃圾回收器回收无用对象占用的内存;
  • 对象可能不被垃圾回收;垃圾回收不等于析构;
  • 在你不需要某个对象之前,如果必须执行某些动作,你得自己去做;
  • 如果程序执行结束,而垃圾回收器一直没有释放你创建的内存,则当程序退出时,那些资源全部交还给操作系统;

finalize()用途

  • 垃圾回收是与内存有关;
  • 使用垃圾回收的唯一原因就是为了回收程序不再使用内存;
  • 无论对象是如何创建的,垃圾回收器只会负责释放对象所占用的内存;
  • finalize()不会过多使用,会用于非Java方法分配存储空间,需求在finalize()方法里用本地方法调用它;
  • 无论是”垃圾回收”还是“终结”,都不保证一定会发生,如果JVM并未面临内存耗尽的情形,它可能不会浪费时间执行垃圾回收以恢复内存;

终结条件

  • 不能指望finalize(),必须创建其它的“清理”方法,并明确调用它们;
  • 所以看起来finalize()只对大部分程序员很难用到的一些晦涩内存清理里有用了。但是finalize() 还有一个有趣的用法,它不依赖于每次都要对finalize()进行调用,它就是对象的终结条件的验证;

垃圾回收器如何工作

  • 垃圾回收器很明显地提高了对象的创建速度,存储空间的释放影响了存储空间的分配;
  • Java的堆类似于传送带,毎分配一个对象,它就前进一格,对象存储空间的分配速度特别快,单并非完全像传送带一样工作,因为垃圾回收器的介入;
  • Java的“堆指针”只是简单地移动到尚未分配的区域;
  • 但在实际工作中,在薄记工作方面还有少量的开销,但比不上查找可用空间开销大;
  • 垃圾回收器工作时,一边回收内存,一边使堆中的对象紧凑排列,这样“堆”指针很容易移动到更靠近传送带的开始处,也避免了页面错误;
  • 垃圾回收器通过排列对象,实现了一种高速的,有无限空间可分配的堆模型;
  • 一种简单但速度很慢的垃圾回收机制叫做引用计数,每个对象中会有一个引用计数器;毎当有引用指向该对象时,引用计数加1;当引用离开作用域时或被置为null时,引用数减1;管理引用计数是一个开销不大但是在程序整个生命周期频繁发生的负担。垃圾回收器会遍历含有对全部对象的列表,当发现某个对象的引用计数为0时,就释放其占用的控件;但是引用计数模式经常会在计数为0时立即释放对象,如果对象之间存在循环引用,那么它们的引用都不为0,就会出现应该被回收单无法被回收的情况,定位循环引用所需的工作量极大;这种方法未被任意一种JVM实现;
  • 更快的策略:对于任意“活”的对象,一定能最终追溯到其存活在栈或静态存储区中的引用;
  • 从栈式静态存储区出发,遍历所有的引用,会发现所有“活”的对象,对于发现的每个引用,必须追踪它们引用的对象,然后该对象包含的所有引用,如此反复进行直到访问包“根源于栈或静态存储区的引用”所形成的整个网络,访问过的对象一定是“活”的;
  • 如何找到存活的对象,不同的JVM有不同的实现,有一种做法叫做停止-复制(stop-and-copy);先暂停程序的运行(不属于后台回收模式),然后将所有存活的对象从当前一个堆复制到另一个堆,没有复制的就是需要回收的。另外,当对象被复制到新堆时,它们是一个挨着一个紧凑的排列,然后就可以按照之前描述的那样直接分配新空间,当对象从一处复制到另一处所有指定它的引用都必须修正。位于栈或静态存储区的引用可以直接被修正,然后进行遍历找到这些其它对象的引用;
  • 这种复制回收器比较慢,原因是:其一得有两个堆,然后再这两个分离的堆之间来回折腾,得维护比实际需要多一格的空间,某些Java虚拟机对比问题的处理方式是按需从堆中分配几块较大的内存,复制动作发生在这些大块内存之间。
  • 其二在复制本身;一旦程序进入稳定状态后,可能只会产生少量的垃圾,尽管如此,复制回收器依然会从一处复制到另外一处,很浪费内存,为了解决这个问题,JVM会进行检查:要是没有新垃圾产生,就会转换到另一种模式(即“自适应”),这种模式称为“标记-清扫”(marke-and-sweap),标记-清扫方式速度相当慢,但你知道程序只会产生少量垃圾甚至不产生垃圾时,它的速度就很快了;
  • “标记-清扫”所依据的思路依然是从栈和静态存储区出发,遍历所有引用,但是每当找到一个存活对象,就给对象一个标记,并不回收它;只有当标记过程完成后,清理动作才开始,没有标记的对象将别释放,没有任何复制动作,“标记-清扫”后剩下的堆空间是不连续的,垃圾回收器要是希望得到连续的空间话,就需要重新整理剩下的对象;
  • “停止-复制”指的是垃圾回收动作不是在后台进行的,相反,垃圾回收动作发生的同时,程序就会暂停,同样”标记-清扫“也必须暂停程序;
  • 自适应的,分代的,停止-复制,标记-清扫式的垃圾回收器;
  • Java中的一些附加程序来提升速度,尤其是与加载器操作相关的,被称为”即时“(Just-In-Time,JIT)编译器的技术。这种技术可以把程序全部或部分翻译成本地机器码;
  • 这样不需要JVM来进行翻译,运行得更快,当要装载某个类(通常是创建该类的第一个对象,对编译器会找到.class文件),然后将该类的字节码装入内存,你可以让即时编译器编译所有代码。有两个缺点:1.这种加载动作贯穿整个生命周期内,累加起来需要花更多时间,而是会增加可执行代码的长度(字节码要比即时编译器展开后的本地机器码小很多),这会导致页面调度,从而一定降低程序运行速度;
  • 惰性评估,意味着即时编译器只有再必要的时间才编译代码,从未被执行的代码也就压根不会被JIT编译;
  • 代码毎执行一次就优化一些,执行次数越多,速度越快;——Java Hotspot;

成员初始化

  • Java 保证在所有变量使用前完成初始化;
  • 类的毎个基础类型数据成员都会有一个初始值;
  • 在类定义一个对象引用时,如果不讲其被初始化,那么引用被赋值为null;

### 执行初始化

  • 直接在定义的时候给变量赋初值;

构造器初始化

  • 使用构造器及你行初始化,为自己提供更大的灵活性;
  • 自动初始化会发生在构造器被调用之前;
      public class Counter{
      int i;
      Counter(){i=7;}
      }
    
  • 对于所有基本类型的引用,包括在定义时已明确指定初值的变量,这种情况都是成立的;

初始化的顺序

  • 在类中变量定义的顺序决定了它们初始化的顺序;
  • 即使变量定义散布在方法定义之间,它们也会在任何方法(包括构造器)被调用之前得到初始化;

静态数据的初始化

  • 无论创建多少对象,静态数据都是占用一份存储区域,static关键字不能用于局部变量,所有只能用作属性(字段,域);如果一个子弹是静态的基本类型,你没有初始化它,那么它会获得基本类型的标准初值;如果是对象引用,那么它的默认值初值就是null;
  • 初始化顺序:先是静态对象,然后是非静态对象;
  • 非静态代码块执行顺序:初始化->构造器

创建对象的过程,假设有个Dog类

  1. 即使没有显式地使用static关键字,构造器实际上也是静态方法,所以当首次创建Dog类型的对象或者是首次访问Dog类的静态方法或属性时,Java解释器必须在类路径中查找,以定位Dog.class. 2.当加载完Dog.class后,有关静态初始化的所有动作都会执行,因此静态初始化只会在首次加载class对象时初始化一次; 3.当用new Dog()创建对象时,首次会在堆上为Dog对象分配足够的存储空间; 4.分配的存储空间受限会被清零,即会将Dog对象中所有基本类型数据没有默认值;(0,null) 5.执行所有出现在字段定义处的初始化动作; 6.执行构造器;

数组初始化

  • 数组是相同类型的同一个标识符名称封装到一起的一个对象或基本类型数据序列;
  • 与[]来定义和使用的 int[] a1; int a1[];
  • 为了给数组创建相应的存储空间,必须写初始化表达式,对于数组初始化动作可以出现在代码的任何地方,也可以使用一种特殊的初始化表达式;
  • 在Java中可以将一个数组赋值给另一个数组 a2=a1;其实是复制了一个引用;
  • 所有的数组(无论是对象数组还是基本类型数组)都有一个固定成员length,不能对其修改

动态数组创建

  • 使用new来创建基本类型数组。new不能创建非数组之外的基本数据类型;
  • 数组最好再定义的时候进行初始化;
  • 如果你创建了一个基本类型的数组,那么你创建的是一个引用数组,以整型的包装类型Integer为例,它是一个类而非基本类型;

可变参数列表

  • 由于所有类都最后继承于Object类,你可以创建一个以Object数组为参数的方法;
  • @符号以及多个十六进制数组,就是打印类名和对象的地址;
  • 在单个参数列表中将类型混合在一起,自动装箱机制会把int类型的参数提升为Integer;
  • 可以在重载方法的一个版本上使用可变参数或者压根不用它;

枚举类型

  • 在创建enum时,编译器会自动添加一些有用的特性。它会创建toString()方法,以便你方便地显示某个enum实例的名称;编译器还可以创建ordinal()方法表示某个特定enum常量的声明顺序,static values()方法enum常量的声明顺序,生成这些常量值构成的数组;
  • switch 与 enum是绝佳的组合;

bye