记录《疯狂Java讲义精粹》划线内容及理解(第5章-面向对象(下))

第五章-面向对象(下)

 

 

 

 

JDK 1.5提供了自动装箱和自动拆箱功能,允许把基本类型值直接赋给对应的包装类引用变量,也允许把包装类对象直接赋给对应的基本类型变量。


基本数据类型和包装类的对应关系


关于使用字符串创建boolean

如果传入的字符串是"true",或是此字符串不同字母的大小写变化形式,如"True",都将创建true对应的Boolean对象;如果传入其他字符串,则会创建false对应的Boolean对象。


基本类型变量和包装类对象之间的转换关系,如下图


把字符串类型的值转换为基本类型的值有两种方式。

利用包装类提供的parseXxx(String s)静态方法(除了Character之外的所有包装类都提供了该方法。

利用包装类提供的Xxx(String s)构造器。String类提供了多个重载valueOf()方法,用于将基本类型变量转换成字符串,

这里有点难理解,给个例子


基本类型变量和字符串之间的转换关系


虽然包装类型的变量是引用数据类型,但包装类的实例可以与数值类型的值进行比较,这种比较是直接取出包装类实例所包装的数值来进行比较的。


两个包装类的实例进行比较的情况就比较复杂,因为包装类的实例实际上是引用类型,只有两个包装类引用指向同一个对象时才会返回true。


JDK 1.5以后支持所谓的自动装箱,自动装箱就是可以直接把一个基本类型值赋给一个包装类实例,在这种情况下可能会出现一些特别的情形。看如下代码(程序清单同上)。

上面程序让人比较费解 

这个很有意思,下面会详解


会出现2相等,但是128不相等的原因在于java的java.lang.Integer类的底层设计,这个底层创建了一个存储值的数组,值得范围在-128到127之间

−128~127之间的同一个整数自动装箱成Integer实例时,永远都是引用cache数组的同一个数组元素,所以它们全部相等;但每次把一个不在−128~127范围内的整数自动装箱成Integer实例时,系统总是重新创建一个Integer实例,所以出现程序中的运行结果。


Java为什么要对这些数据进行缓存呢?

答:缓存是一种非常优秀的设计模式,在Java、Java EE平台的很多地方都会通过缓存来提高系统的运行性能。简单地说,如果你需要一台电脑,那么你就去买了一台电脑。但你不可能一直使用这台电脑,你总会离开这台电脑——在你离开电脑的这段时间内,你如何做?你会不会立即把电脑扔掉?当然不会,你会把电脑放在房间里,等下次又需要电脑时直接开机使用,而不是再次去购买一台。假设电脑是内存中的对象,而你的房间是内存,如果房间足够大,则可以把所有曾经用过的各种东西都缓存起来,但这不可能,房间的空间是有限制的,因此有些东西你用过一次就扔掉了。你只会把一些购买成本大、需要频繁使用的东西保存下来。类似地,Java也把一些创建成本大、需要频繁使用的对象缓存起来,从而提高程序的运行性能。


compare(xxx val1,xxx val2)方法来比较两个基本类型值的大小,包括比较两个boolean类型值,两个boolean类型值进行比较时,true>false

更新2022.08.05

String,包装类,基本类型,相互转换

5.2处理对象

toString()方法是Object类里的一个实例方法,所有的Java类都是Object类的子类,因此所有的Java对象都具有toString()方法。


toString方法是一个非常特殊的方法,它是一个“自我描述”方法,该方法通常用于实现这样一个功能:当程序员直接打印该对象时,系统将会输出该对象的“自我描述”信息,用以告诉外界该对象具有的状态信息。

Object类提供的toString方法总是返回该对象实现类的“类名+@+hashCode”值,这个返回值并不能真正实现“自我描述”的功能,因此如果用户需要自定义类能实现“自我描述”的功能,就必须重写Object类的toString方法


当使用==来判断两个变量是否相等时,如果两个变量是基本类型变量,且都是数值类型(不一定要求数据类型严格相同),则只要两个变量的值相等,就将返回true。

但对于两个引用类型变量,它们必须指向同一个对象时,==判断才会返回true。

==不可用于比较类型上没有父子关系的两个对象

如果使用==比较没有继承关系的两个引用对象,将直接导致编译错误


对初学者而言,String还有一个非常容易迷惑的地方:"hello"直接量和newString("hello")有什么区别呢?

当Java程序直接使用形如"hello"的字符串直接量(包括可以在编译时就计算出来的字符串值)时,JVM将会使用常量池来管理这些字符串;

当使用new String("hello")时,JVM会先使用常量池来管理"hello"直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中。换句话说,new String("hello")一共产生了两个对象。


常量池(constant pool)专门用于管理在编译期被确定并被保存在已编译的.class文件中的一些数据。它包括了关于类、方法、接口中的常量,还包括字符串常量。


使用new String()创建的字符串对象是运行时创建出来的,它被保存在运行时内存区内(即堆内存),不会放入常量池中。

在程序中的直接量会放入常量池,使用new调用构造器创建的会放入堆内存,所以用==会返回false

但用equals会返回ture

因为equals只比较值,==比较地址(是否引用同一个对象)


String已经重写了Object的equals()方法,

String的equals()方法判断两个字符串相等的标准是:只要两个字符串所包含的字符序列相同,通过equals()比较将返回true,否则将返回false。

object的equals与==作用相同


通常而言,正确地重写equals方法应该满足下列条件。

自反性:对任意x,x.equals(x)一定返回true。

对称性:对任意x和y,如果y.equals(x)返回true,则x.equals(y)也返回true。

传递性:对任意x,y,z,如果x.equals(y)返回ture,y.equals(z)返回true,则x.equals(z)一定返回true。

一致性:对任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)多少次,返回的结果应该保持一致,要么一直是true,要么一直是false。

 对任何不是null的x,x.equals(null)一定返回false。

5.3 类成员

类成员有类Field、类方法、静态初始化块等三个成分,static关键字不能修饰构造器。static修饰的类成员属于整个类,不属于单个实例。

5.3.1 理解类成员

在Java类里只能包含Field、方法、构造器、初始化块、内部类(包括接口、枚举)5种成员,


当通过对象来访问类Field时,系统会在底层转换为通过该类来访问类Field。

虽然静态成员变量也可通过,对象名来访问,但是底层其实还是类在访问


对static关键字而言,有一条非常重要的规则:类成员(包括方法、初始化块、内部类和枚举类)不能访问实例成员(包括Field、方法、初始化块、内部类和枚举类)。

因为类成员是属于类的,类成员的作用域比实例成员的作用域更大,完全可能出现类成员已经初始化完成,但实例成员还不曾初始化的情况,如果允许类成员访问实例成员将会引起大量错误

5.3.2 单例(Singleton)类

如果一个类始终只能创建一个实例,则这个类被称为单例类。

5.4 final修饰符

final修饰的变量不可被改变,一旦获得了初始值,该final变量的值就不能被重新赋值。


final修饰的类Field、实例Field能指定初始值的地方如下。

类Field:必须在静态初始化块中或声明该Field时指定初始值。

实例Field:必须在非静态初始化块、声明该Field或构造器中指定初始值。

5.4.2 final局部变量

如果final修饰的局部变量在定义时没有指定默认值,则可以在后面代码中对该final变量赋初始值,但只能一次,不能重复赋值;

如果final修饰的局部变量在定义时已经指定默认值,则后面代码中不能再对该变量赋值。

5.4.3 final修饰基本类型变量和引用类型变量的区别

当使用final修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。

例如使用final修饰的引用变量指向一个数组,那么这个数组元素可变(排序,替换元素值),或者指向对象实例,那么实例的field值也是可变的,final只保证引用变量指向的地址值不变

5.4.4 可执行“宏替换”的final变量

对一个final变量来说,不管它是类Field、实例Field,还是局部变量,只要该变量满足3个条件,这个final变量就不再是一个变量,而是相当于一个直接量。

 使用final修饰符修饰;

 在定义该final变量时指定了初始值;

 该初始值可以在编译时就被确定下来。


final修饰符的一个重要用途就是定义“宏变量”。当定义final变量时就为该变量指定了初始值,而且该初始值可以在编译时就确定下来,那么这个final变量本质上就是一个“宏变量”,编译器会把程序中所有用到该变量的地方直接替换成该变量的值。

对于基本的算术表达式(参加运算的是两个直接量或宏变量),还有字符串链接操作(参与运算的两个值要求同上,字符与数值连接也可,这种隐式数据转换也可在编译时期确定下来),这些都可以在编译时期就确定下来,也就是满足宏变量的要求,但是一旦给变量赋初值时出现方法或变量(不是宏变量),那么在编译时期就无法确定这些值,


对于final实例变量而言,只有在定义该变量时指定初始值才会有“宏变量”的效果。

2022.08.02更新

5.4.5 final方法

对于一个private方法,因为它仅在当前类中可见,其子类无法访问该方法,

所以子类无法重写该方法——如果子类中定义一个与父类private方法有相同方法名、相同形参列表、相同返回值类型的方法,也不是方法重写,只是重新定义了一个新方法。

因此,即使使用final修饰一个private访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名、相同形参列表、相同返回值类型的方法。


final修饰的方法仅仅是不能被重写,并不是不能被重载

5.4.7 不可变类

不可变(immutable)类的意思是创建该类的实例后,该实例的Field是不可改变的。

Java提供的8个包装类和java.lang.String类都是不可变类,当创建它们的实例后,其实例的Field不可改变。

这也就是为什么被创建的字符串不能被修改的原因,因为string是不可变类,只能通过构造器赋值,但string没有提供set方法修改field,所以被创建的字符串不可变


如果需要创建自定义的不可变类,可遵守如下规则。

使用private和final修饰符来修饰该类的Field。

提供带参数构造器,用于根据传入参数来初始化类里的Field。

仅为该类的Field提供getter方法,不要为该类的Field提供setter方法,因为普通方法无法修改final修饰的Field。

如果有必要,重写Object类的hashCode和equals方法。equals方法以关键Field来作为判断两个对象是否相等的标准,除此之外,还应该保证两个用equals方法判断为相等的对象的hashCode也相等。


与不可变类对应的是可变类,可变类的含义是该类的实例Field是可变的。大部分时候所创建的类都是可变类,特别是JavaBean,因为总是为其Field提供了setter和getter方法。


如果需要设计一个不可变类,尤其要注意其引用类型Field,如果引用类型Field的类是可变的,就必须采取必要的措施来保护该Field所引用的对象不会被修改,这样才能创建真正的不可变类。

保护措施可以采取如下方案,在不变类的构造器中创建
一个引用类型的对象,传入该对象的初始化值,创建获取引
用变量field的新方法,返回在构造器中实例的field

5.5 抽象类

5.5.1 抽象方法和抽象类

抽象方法和抽象类必须使用abstract修饰符来定义,有抽象方法的类只能被定义成抽象类,抽象类里可以没有抽象方法。


abstract不能用于修饰Field,不能用于修饰局部变量,即没有抽象变量、没有抽象Field等说法;abstract也不能用于修饰构造器,没有抽象构造器,抽象类里定义的构造器只能是普通构造器。

使用abstract (抽象类)可以更好的使用java语言的多态,
使程序更加灵活,另外没有抽象成员变量和抽象构造器,
final和abstract不能同时使用(abstract需要 被继承,final不能被继承)


static和abstract不能同时修饰某个方法,即没有所谓的类抽象方法


abstract关键字修饰的方法必须被其子类重写才有意义,否则这个方法将永远不会有方法体,因此abstract方法不能定义为private访问权限,即private和abstract不能同时使用。

总结一下,final (不可被继承) static (属于类)和private (对子类隐藏)不能和abstract同时修饰一 个方法,
否则abstract修饰的方法将无意义


使用模板模式的一些简单规则。

抽象父类可以只定义需要使用的某些方法,把不能实现的部分抽象成抽象方法,留给其子类去实现。

父类中可能包含需要调用的其他系列方法的方法,这些被调方法既可以由父类实现,也可以由其子类实现。父类里提供的方法只是定义了一个通用算法,其实现也许并不完全由自身实现,而必须依赖于其子类的辅助。

2022.08.02更新 

5.6 更彻底的抽象:接口

接口(interface),接口里不能包含普通方法,接口里的所有方法都是抽象方法。

5.6.1 接口的概念

接口是从多个相似类中抽象出来的规范,接口不提供任何实现。接口体现的是规范和实现分离的设计哲学。

5.6.2 接口的定义

和类定义不同,定义接口不再使用class关键字,而是使用interface关键字。接口定义的基本语法如下:

[修饰符] interface接 口名extends
父接口1,父接口2...

{
零个到多个常量定义...
零个到多个抽象方法定义...
}


由于接口定义的是一种规范,因此接口里不能包含构造器和初始化块定义。接口里可以包含Field(只能是常量)、方法(只能是抽象实例方法)、内部类(包括内部接口、枚举)定义。

接口不同于任何别的类,主要区别有,

1-定 义时用interface
2-修饰符只有publice和空缺(包级权限)

        其实不止接口,基本上所有的类都只能有这两种修饰符(内部类除外,内部类具有全部的权限修饰符)
3-可以继承多个接口

        (也就是说,接口可以单继承,也可以多继承,这点对于单继承的java来说非常特殊)
4-接内不能定义构造器和初始化块


接口里定义的内部类、接口、枚举类默认都采用public static两个修饰符,不管定义时是否指定这两个修饰符,系统都会自动使用public static对它们进行修饰。

区别
5接口中的filed(成员变量)必须使用final和static修饰,且必须指定初始值
        权限修饰符修饰符只有public
6-方法必须用abstract修饰,不可用static
7-内部类,接口内的接口,枚举类,默认用public和static
这三条中,如果不显示指定这些必须使用的修饰符,系统会自动添加


一个Java源文件里最多只能有一个public接口,如果一个Java源文件里定义了一个public接口,则该源文件的主文件名必须与该接口名相同。

和类的相同点

5.6.4 使用接口

接口不能用于创建实例,但接口可以用于声明引用类型变量

接口的用途

1-被实现类实现

2-定义接口类型变量(必须被连接到实现类)


一个类可以实现一个或多个接口,继承使用extends关键字,实现则使用implements关键字


一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法,该类也必须定义成抽象类。


实现类实现接口里的方法时只能使用public访问权限。

5.6.5 接口和抽象类

接口和抽象类很像

接口(interface) 和抽象类(sabtract) 的相同点
1-都不能被实例化,且位于继承树顶端
2-都可包含普通方法,实现/继承者必须实现这些方法

                接口中的普通方法的权限修饰符,必须为default,不能省略

2022.08.02更新

 

5.6.6 面向接口编程

1.简单工厂模式

通过这种方式,我们把所有生成Output对象的逻辑集中在OutputFactory工厂类中管理

面向接口的编程的一种用法, 简单工厂模式,
A类需要使用B类的实例化对象,如果有大量A类引用B类,这时需要将所有A类中的B实例对象改为C
类的对象,修改时将非常困难,这时设订接口D类,让B.C类作为D的实现类,做F普通类,方法F-turn返回B或C类对象,将A的引改为使用F类的F-turn方法,
在这种情况下修改A对B的引用为C时,只需要修改F的F-turn方法即可,F类成为工厂类
令我激动的时,这正是李娜老师在讲spring工厂时的逻辑,spring使用同样的方法来管理程序中的bean


所谓设计模式,就是对经常出现的软件设计问题的成熟解决方案。


2.命令模式

方法不仅需要普通数据可以变化,甚至还有方法执行体也需要变化,

面向接口编程命令模式
当一个方法的形参无法确定具体处理行为(方法体)时,
该如何处理
有普通A类的A-one方法,形参int ms ,B.b
方法体b.B-test (ms)
定义接口B,中有B-test方法, 形参ms
定义B的实现类C重写B-test方法


定义B的实现类D重写B-test方法
在A的A-one方法形参内,传入C/D的实例化对象,以此
实现方法和方法体分离

5.7 内部类

5.7.1 非静态内部类

总结
1-内部类可以在任何地方定义,包括方法内部,称为局部内部类,

但局部内部类和匿名内部类不是外部类的成员(有待考证)

<br>
2-外部类只能有包级和公开两种权限,内部类可以有四种权限(私有,包级,子父类,公开)

<br>
3-在编译时内部类会编译为一个独立class文件,名字为
外部类名class$内部类名class.class

<br>
4-非静态内部类可直接访问外部类的私有成员,反过来却不行。关键在于内部类一定有包裹他的外部类,外部类却不一定有内部类

<br>
5-外部类field,内部类field,内部类局部field同名时访问顺序
内部局部(变量名)->内部field(this.变量名)->外部field(外部类名.this.变量名)
如果到外部还未找到该field将报错

<br>
如果想直接调用对应field可用括号内代码

<br>
6-外部静态成员不可引用内部非静态类

<br>
7-非静态内部类不可定义静态成员

5.7.2 静态内部类

总结
1-static可修饰内部类,对外部类不适用,原因在于static的作用是将成员变为类相关,而外部类的上层是包
2-静态内部类不可访问外部类的实例成员,静态内部类的实例成员同样不可访问外部类的实例成员,原因在于静态内部类只有外部类的引用,如果静态内部类访问外部类实例成员,却没有找到对应外部类的对象(因为静态内部类寄存在外部类中而不是对象中),将引发错误,逻辑有些像this
3-外部类不可直接访问静态内部类成员
4-接口内可定义内部类,但只能用public static修饰
5-延伸,接口内可定义内部接口,但只能用public static修饰,且意义不大,因为接口的作用就是公共规范,隐藏之后失去了原本意义


定义类的主要作用就是定义变量、创建实例和作为父类被继承。


(1)在外部类内部使用内部类

不要在外部静态成员中使用内部类非静态成员


(2)在外部类以外使用非静态内部类

1-注意权限,不能是private
2-创建对象   外部类名.内部类名     变量名=new  外部类构造器().new   内部类构造器()

一般没人这样做,了解即可

<br>
因为非静态内部类必须要有可寄存的对象,所以先要创建外部类对象

<br>
3-为非静态内部类创建子类,不管是子类还是非静态内部类本身,都需要保留一个对外部类的引用(因为需要外部类对象作为主调调起内部类的构造器,那怕方式不同)
子类可在构造器中使用                外部类名.super()
创建内部类对象时使用                外部类名.new  内部类名()
 


非静态内部类的构造器必须通过其外部类对象来调用。


调用非静态内部类的构造器时,必须存在一个外部类对象。


(3)在外部类以外使用静态内部类

在外部类之外使用静态内部类
1-权限问题
同非静态内部类一样

<br>
2-定义变量  外部类名.内部类名(与非静态内部类无区别)

<br>
3-定义实例对象   new  外部类名.内部类名()
和普通类相比只多加了外部类名,类似一层包关系,因为创建静态内部类对象与外部类对象无关(类相关)

<br>
4-外部类的子类是否可以重写内部类
不可以,因为外部类名被作为了内部类的类名前缀,这个命名空间是唯一不可被覆盖的,所以可以说内部类具有一定性质的fine属性


局部内部类的class文件总是遵循如下命名格式:Outer$NInner.class。 

5.7.5 匿名内部类

1-匿名内部类使用
new  父类构造器(实参列表)|实现接口()
        {
          //匿名内部类的类体部分
        }

<br>
2-匿名内部类不能是抽象类(java创建匿名内部类时会立刻创建一个该类的实例,如果是抽象类将报错)
匿名内部类不能显示定义构造器,但可用初始化块,

<br>
3-可用匿名内部类做接口的实现类,可用 new 接口名(){具体实现},这样的实现类跳过了implements关键字

<br>
4-匿名内部类使用父类实现时,实参列表就是对应父类的构造器形参,且跳过了extend关键字

<br>
5-匿名内部类如果想要使用外部局部变量,则这个局部变量必须使用final修饰,这是个很奇怪的点,一般程序很少用到,但值得探究

<br>

首先说明,这里是匿名内部类,访问同一个方法的局部变量,在java8中会自动被final修饰,且不能在匿名内部类中,或者在此类结束之后修改这个变量值

<br>
首先在jdk8中局部变量不用加final也可被访问,但底层还是会加(语法糖),且不能改变这个局部变量值否则报错,那为什么要加final呢,是为了保证数据的一致性,匿名内部类会接受底层传入的变量值,(地址-引用,值-基本),

<br>

如果不使用final,这个引用指向别的值,将会发生未知错误,同时就算被final修饰的引用对象变化,程序还处在可控中
具体参考https://blog.csdn.net/luzhensmart/article/details/103366682

<br>

匿名内部类的class文件名字格式为:                外部类名$数字.class


定义匿名内部类的格式如下:

new 父类构造器(实参列表)|实现接口(){

//匿名内部类的类体部分

}


如果匿名内部类需要访问外部类的局部变量,则必须使用final修饰符来修饰外部类的局部变量,否则系统将报错。

2022.08.05更新

扩展Lamdba表达式 

5.7.6 闭包(Closure)和回调、

闭包,能够调用其他类内部变量/方法的类,在java中非静态内部类
回调,使用非静态内部类调用外部类方法/变量的这一动作称为回调
这是一种编程手法,可以使程序更灵活
那么简单探讨一下为什么必须是非静态内部类,因为非静态内部类与被他寄存的外部类之间存在引用关系,而静态内部类没有,而这种关系就是闭包概念中的“保存创建他的作用域信息”,
在闭包之上实现回调可以实现类似,同一类中,同一方法名,同样参数的情况下,执行方法不同的效果,
有普通类A,有work无参方法输出一句话“AAAAAAAAA”
有接口B 内有方法work
可做一个A的继承类C,写方法work1输出“BBBBBBB”
之后做C的非静态内部类,让他实现接口B,实现接口B的work方法,方法内调用外部类的work1方法
这时C的同一方法名同一形参的方法work(一个普通方法,一个非静态内部类方法)有两种不同的方法体

对于同一方法同一形参不同处理的需求,其实还可以通过接口的第二种设计模式,命令模式,使方法体和方法分离来实现


回调就是某个方法一旦获得了内部类对象的引用后,就可以在合适的时候反过来去调用外部类实例的方法。所谓回调,就是允许客户类通过内部类引用来调用其外部类的方法,这是一种非常灵活的功能。

闭包是一个概念,回调是在闭包基础上的应用

5.8 枚举类

实例有限而且固定的类,在Java里被称为枚举类。

5.8.1 手动实现枚举类

枚举类:实例有限且固定的类,如行星,四季
手动实现枚举类
1-将构造器用private修饰
2-所有实例用公开静态固定(final)修饰
3-提供静态方法供别的程序获得枚举值
关于历史,曾经程序中使用公开静态固定成员变量来作为枚举量,但这样做有些弊端
1-意义不明
2-没有命名空间,可能与其他常见混淆
3-类型不安全,如使用整形常量,可用作运算

5.8.2 枚举类入门

在自定义的枚举类还可以给变量赋值,有一定意义,但java提供的枚举类只能声明变量(其实是常量),我觉得有点脱裤子放屁的感觉

 1-枚举类不同于一般类,他并不是object的子类,而是java.lang.enum类的子类,同时enum类还提供了一些方法, 

int compareTo(E o)用于同一枚举类枚举值的比较顺序,如该枚举值位于指定值后,返回正整数,如在之前,返回负整数,否则返回0 

string name()返回枚举实例中,某个枚举值的名称

int ordinal()返回枚举值在枚举类中的索引,从0开始

string tostrng()返回枚举值的名称,与name类似,推荐使用tostring,因为返回值更友好

string value()遍历枚举类的所有值 

public static<T extends Enum<T>>T valueOf(Class<T>enumType,String name):静态方法,用于返回指定枚举类中指定名称的枚举值 

2-枚举类不能被继承(默认使用final修饰) 

3-枚举类的构造器只能使用private修饰 

4-枚举类的实例(枚举值)必须在类体的第一行显示指出,且不能赋初值, 多个实例间用,隔开,系统会为这些实例默认添加public static final,并且你不能手动写这些修饰符

5-使用枚举类可以 类名.枚举值得方式,但在switch中例外,switch 语句里可直接使用枚举值做为判断条件


一个Java源文件中最多只能定义一个public访问权限的枚举类,且该Java源文件也必须和该枚举类的类名相同。


当switch控制表达式使用枚举类型时,后面case表达式中的值直接使用枚举值的名字,无须添加枚举类作为限定。

 5.8.3 枚举类的Field、方法和构造器

1-因为枚举类的构造器是private,所以创建对象要用Enum.valueOf(类名.class 枚举值)来生成枚举对象
2-用上面方式生成的对象还可以和枚举field绑定
如当给枚举field绑定值时,用switch加if强制要求每个枚举值只能绑定到某些变量,
3-在第二步这样绑定的变量和枚举值得变量值可能会发生改变,可将field设置为private final,这样在定义枚举值时需要显示定义枚举值绑定变量的值,枚举值(“值”)
并且还要做对应的构造方法


枚举类的实例只能是枚举值,而不是随意地通过new来创建枚举类对象。

5.8.4 实现接口的枚举类

1-枚举类也可以实现接口,和普通类实现接口一样
2-上面方式重写的接口方法,所有枚举值调用都是相同执行表现,如果想让不同枚举值调用同一方法有不同的执行表现(方法体),可用  枚举值(“field值”){具体重写接口方法内容}这种形式写,这种写法类似匿名内部类,
同时如有多个枚举值。那么多个都这样写违反了定义枚举类的规则,所有枚举值都必须写在第一行,是否会发生错误不可知

5.8.5 包含抽象方法的枚举类

前面说了枚举类默认使用final修饰,所以不能有子类,但如果枚举类包含抽象方法,此枚举类就能创建子类,但和普通子类有些区别,
假设有枚举类A,内有枚举值B.C,一个抽象方法D,则可以在枚举值后跟花括号实现抽象方法D,同时这个花括号代表的匿名内部类就是枚举类A的子类,
这个抽象方法D必须存在,有了D编译器会为枚举类A自动添加abstract关键字,但这个关键字不可手动添加

5.9 对象与垃圾回收

在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收

5.9.1 对象在内存中的状态

对象在内存中有三种状态
1-可达状态  此对象有一个以上的引用变量指向他
2-可恢复状态  没有任何引用指向此对象,但如果调用finalize函数重新使引用变量指向此对象,则变为可达状态
3-不可达状态  该对象与所有引用变量的引用都被切断,且调用了finalize,依然没能是该对象变为可达状态


当某个对象被其他类的类变量引用时,只有该类被销毁后,该对象才会进入可恢复状态;当某个对象被其他对象的实例变量引用时,只有当该对象被销毁后,该对象才会进入可恢复状态。

5.9.2 强制垃圾回收

强制系统垃圾回收有如下两个方法。

调用System类的gc()静态方法:System.gc()

调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()

5.9.3 finalize方法

你可以进行强制垃圾回收,有如下两个方法。
System.gc()
Runtime.getRuntime().gc()
但这两个方法也只是通知系统,可以进行垃圾回收了,并不一定会执行,具体的垃圾回收时机完全对程序透明
垃圾回收机制可以显示的指定。清理对象的方法,如不指定,则调用默认的object类的finalize方法,
finalize方法有些特点,
1-不要主动调用该方法,应将他交给垃圾回收机制
2-这个方法的调用具有非常大的不确定性(因为垃圾回收的实例不确定),不要依赖他,
3-当jvm执行finalize出现异常时,程序不会报错,不会暂停
4-该方法有可能复活对象
System和Runtime类里都提供了一个runFinalization方法,可以让程序强制调用对象的finalize方法


在垃圾回收机制回收某个对象所占用的内存之前,通常要求程序调用适当的方法来清理资源,在没有明确指定清理资源的情况下,Java提供了默认机制来清理该对象的资源,这个机制就是finalize()方法。


垃圾回收机制何时调用对象的finalize()方法是完全透明的,只有当程序认为需要更多的额外内存时,垃圾回收机制才会进行垃圾回收。

5.9.4 对象的软、弱和虚引用

java对象在内存中的四种状态
1-强引用   由普通方式构建的对象就是强引用,这种对象处于可达状态,永远不会被垃圾回收机制回收
2-软引用  由java.lang.ref.SoftReference
类实现,这种状态下的对象,在内存充足时不会被回收
3-弱引用  由java.lang.ref.PhantomReference类实现,不论内存是否充足,只要系统开始垃圾回收,这种类型的对象将被回收
4-虚引用  由java.lang.ref.WeakReference类实现,虚引用必须和引用队列配合使用,主要用于跟踪对象的垃圾回收状态,虚引用的对象在被释放之前,会将该引用添加到对应的引用队列中,所以可以通过查看引用队列来确定一个对象的状态(是否要被回收了),且虚引用不可被单独调用
软弱虚都包含一个get方法,用于获取他们所引用的对象
在程序中可以通过(软,弱,虚的类名)类名 变量名=new 类构造器(引用类型变量)的方式指定一个引用的内存状态
如果使用这种状态,就能非常明确的告诉垃圾回收机制该回收那些对象,这样做可以降低在对象生命周期内在内存的消耗
引用队列由java.lang.ref.ReferenceQueue类表示,它用于保存被回收后对象的引用。
因为无法确定垃圾回收机制何事回收,所以可能出现需要用对象时该对象已经被回收的情况,这种情况下有两种方式恢复对象,我将划线可靠的一种


引用队列由java.lang.ref.ReferenceQueue类表示,它用于保存被回收后对象的引用。


下面代码显示了另一种取出被引用对象的风格。

        //取出弱引用所引用的对象
        obj = wr.get();
        //如果取出的对象为null
        if (obj == null)
        {
          //重新创建一个新的对象,并使用强引用来引用它
          obj = recreateIt();
          //取出弱引用所引用的对象,将其赋给obj变量
          wr = new WeakReference(obj);
        }
        ...//操作obj对象
        //再次切断obj和对象之间的关联
        obj = null;


表5.2 Java修饰符适用范围总表

 初始化块,局部变量,一定是包访问控制级别,
strictfp,被他修饰的方法或类,接口,在编译和执行期间,将完全依照浮点规范IEEE-754来执行
native,被他修饰的方法可用c写,于是这个方法可以访问底层硬件,但包含方法的程序失去了平台移植性
还有一些修饰符是互斥的
1.公开(publix)-包级子父类(protect)-包级(缺省)-私有(private)
2.abstract-final
3.abstract-static
4.abstract-private

5.11 使用JAR文件

JAR文件的全称是Java Archive File,意思就是Java档案文件。通常JAR文件是一种压缩文件,与我们常见的ZIP压缩文件兼容,通常也被称为JAR包。JAR文件与ZIP文件的区别就是在JAR文件中默认包含了一个名为META-INF/MANIFEST.MF的清单文件,这个清单文件是在生成JAR文件时由系统自动创建的。

5.11.1 jar命令详解

打包jar包
jar cvf|cf  test.jar test
将当前目录下的test文件夹打包为test.jar文件,放在当前目录
cf不显示打包过程,cvf显示打包过程
添加自定义信息
jar cvfm test.jar a.txt test
将a.txt文件中的键值对添加到jar的META-INF/MANIFEST.MF文件中,
上面命令中cvfm的m表示将a.txt中自定义的内容,添加到MANIFEST.MF中
需要注意键值对的写法有些规则,
1-每行只能有一个键值对,且要顶格写
2-键值对的格式为    key: value  冒号后有个空格
3-整个文件开头不能有空行,但结束时以一个空行结束
且如果要手动建立一个MANIFEST.MF文件,则文件中必须包含Manifest-Version: 1.0
        Created-By: 1.7.0 (Oracle Corporation)
两行
解压jar包
jar xf test.jar
解压当前目录下的test.jar到当前目录,不显示过程

5.11.2 创建可执行的J A R包

制作可执行的jar包,要保证jar包内有程序入口,也就是main方法,然后在制作jar包时
jar cvfe test.jar Test *.class
cvfe中的e表示指定程序入口,为test*.class类

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值