零 概述 本篇文章是读《Java编程逻辑》的笔记, 第1章 编程基础 为了操作数据便当,界说了数据类型和变量;数据核算的进程中还需求进行流程控制,所以有了控制句子;为了削减重复代码和分化杂乱操作,引入了函数和子程序的概念。
数据类型和变量 数据类型为了对数据归类,以便于了解和操作。Java的基础数据类型有4中大类型: 整数类型:有四种整型,byte/short/int/long,别离有不同的取值规模; 小数类型:两种float/double,有不同的取值规模和精度; 真假类型:boolean,表明真假。 根本类型也有对应的数组类型,数组表明固定长度同种数据类型的多条记载,在内存中接连寄存。
内存在程序看来便是一块有地址编号的接连空间,数据放到空间之后为了便当查找和操作,需求给方位起个姓名——用变量来表明这个概念。变量的变是指内存空间里边存的值是可变的。但 是变量的称号不变,这个称号是程序员心中的内存空间的意义——应该是不变的,假如你一定要个age变量里边放身高的值也是能够的。 小结:变量便是给数据起姓名,便当找不同的数据,值能够变可是含义不该该变。 赋值。赋值便是给变量对应的空间设一个值,留意给float赋值的时分加F或f,由于小数默许是double类型。 数组类型。数组的长度确认之后就不能变化了。数组有一个length特色,可是是只读特色。假如数组的初始值确认就不能再改长度,由于这样会让核算机无所适从。例如一下代码: int [] a= new int[3]{1,2,3} 数组和根本类型纷歧样,根本类型占一块内存空间,而数组需求两块:一块用于存储数组内容自身——榜首个值的内存空间,另一块存储内容方位。 数组的比较是判别两个变量指向的同一个数组,而不是判别元素内容是否相同。 逻辑运算符。 异或(^):两个相同为false,两个不同为真。 或(|)与短路或(||)、与和短路与(&&)的区别是短路的只要榜首个依据榜首个能够得出成果,就不会履行第二个。例如下面代码 int b=0; boolean flag= true|b++>0; int b=0; boolean flag= true||b++>0; 榜首段b最终成果为1,第二个最终成果为0。既第二个只要得出成果就不会求后边的。
switch的类型为byte,short,int,char,枚举和String,为什么没有float、double和long呢?这和switch的完成原理有关。switch的转化和详细的体系完成有关。假如分支比较小,或许会转 换为跳转句子,假如分支比较多,会转化为跳转表——跳转表高效的原因是其间的值有必要为整数——String经过hashCode办法转化为整数,且按巨细排序能够运用二分查找。假如值是接连的 或许比较密布,跳转表或许会优化为一个数组。跳转表的存储空间为32位,所以不能运用long类型,存储不下;没有float和double是由于存储的是整数。 函数的首要意图是削减重复代码和分化杂乱操作。界说函数仅仅界说了一段有着清晰功用的子程序,可是自身不会履行,需求调用。 界说函数时声明参数,实践上便是界说变量,仅仅这些变量的值是不知道的,调用函数时传递参数,实践上便是给函数中的变量赋值。 函数参数里边比较特别的是数组类型和可变长度。数组类型赋值的时分赋值的是数组存储自身内容自身的空间,所以修正会影响外面。可变参数实践上是变为了一个数组。例如下面两端代码 等同。 public static int max(int min,int ... a){ //anything } public static int max(int min,int [] a){ //anything } mac(0,new int[]{2,4,5}) 可变参数简化了代码书写。 函数调用 函数调用是经过栈来存储相关的数据——变量和下一条履行句子等,体系就调用者和函数怎样运用栈做了约定,回来值能够简单的认为是经过一个专门的回来值存储器存储的。从这个进程中 能够看出,函数的调用是有开支本钱的——分配额外的栈空间存储参数、局部变量以及回来地址,还需求进行出栈和入栈操作。所以函数巨细的划分需求衡量。
数组和方针的内存分配 数组和方针的内存由两块组成,一块寄存实践内容,一块寄存实践内容的地址。寄存实践内容的空间一般不分配在栈上,而是分配在堆上。寄存地址的空间分配在栈上。 第2章 了解数据背面的二进制 二进制的最高位表明符号,1为负数、0为正数。可是二进制的实践表明办法是补码表明法,例如byte类型的-1:1的原码是00000001,取反是11111110,然后加1便是11111111。 知道了最高位为符号位,那么就能够推算出为什么byte的最大值为127,最小值为-128了。8位2进制的模为256,正数和负数各占一半,00000000到01111111表明正数,10000000到11111111表 示负数。这个核算进程杂乱,可是为了简单的记规模区间的算法-2n-1~2n-1 -1。
关于这一点,网上大多数关于补码、反码的讨论都无法核算出-128,或许核算逻辑有问题,常见csdn的博客。本书作者也没有去说这一块,能够参考知乎文章,或许2.1.2 数的机器码表明, 这里从起源到核算都论说到了。 为什么核算机无法精确表明0.1?由于核算机只能表明2n的和,例如0.5=2-1、0.75=2-1+2-2。假如不是特别寻求精度,能够运用四舍五入等办法。假如寻求精度能够转化为正数,求完值之后 再转回小数。 为什么叫浮点数?这是由于在二进制表明中,表明那个小数点的时分,点不是固定的而是起浮的。在十进制中表明123.45,直接这么写便是固定的。假如运用1.2345E2那便是小数点向左起浮 了两位。二进制中表明小数也采用相似科学计数法,形如m*(2e),m称为尾数,e称为指数。在二进制中,单独表明尾数部分和指数部分,别的还有一个符号位表明正负。 简直一切的硬件和编程言语表明小数的二级制格局是相同的——IEEE754标准。一种是32位(Java的float),一种是64位(Java的double)。
32位格局中,1位表明符号,23位表明尾数,8位 表明指数。64位格局中,1位符号、53位尾数、11位指数。Java中想检查浮点数的详细二进制办法,能够运用如下代码: Integer.toBinaryString(Float.floatToIntBits(value)); Long.toBinaryString(Double.floatToIntBits(value)); char实质上是一个占用固定两个字节的无符号整数——Java采用UTF-16BE编码,这个正整数表明Unicode编号,用于表明那个Unicode编号对应的字符。关于更多编码文章,能够看java中一个 汉字占几个字符。 第3章 类的基础 类的特色和办法分为:类一切也叫静态,静态成员变量和静态办法;实例一切的变量和办法。 静态初始化代码块在类加载的时分履行,这是在任何方针创立之前,且只履行一次。 私有结构办法 结构办法能够是私有办法,运用场景或许有3种: 1)不能创立类的实例,类只能被静态拜访,如Math和Arrays类,他们的结构办法是私有的; 2)能创立类的实例,但只能被类的静态办法调用。一种常见的场景:类的方针有可是只能有一个,即单例。在这种场景中,方针是经过静态办法获取的,而静态办法调用结构办法创立一个 方针,假如方针现已创立过了,就重用这个方针——我在创立线程池的时分运用过。 3)仅仅用来被其他多个结构办法调用,用于较少重复代码。 类的承继 子类方针赋值给父类引证变量,这叫做向上转型。 一种类型的变量,能够引证多种实践类型方针叫做多态。声明时的类型成为静态类型,实践的类型为动态类型,在运转进程中调用对应动态类型的办法,称为动态绑定。 Shap[] shaps = new Shap[3]; shaps[0]=new Circle(); shaps[0]=new Line(); //anything shaps[0].draw(); 上面代码中Shop类型是shops静态类型,Circle、Line是动态类型,shaps[0].draw()是动态绑定。 那为什么需求有多态和动态绑定呢?由于创立方针的代码和操作方针的代码常常不在一同,操作方针的代码往往只知道方针是某种父类型,也往往需求知道它是某种父类型就能够了。 多台和动态绑定是核算机的一种重要思想办法,使得操作方针的程序不需求重视方针的实践类型,然后能够统一处理不同方针,但又能完成每个方针的特有行为。 每个类肯定有一个父类,且只能有一个父类。没有声明的默许为Object。 在new进程中,父类先进行初始化。假如没有经过super调用父类的结构办法,会主动调用父类默许办法,假如父类没有默许办法则会报错——编译过错。 父类结构办法中假如调用了可被重写的办法,则或许会呈现意想不到的的成果。例如 public class Base { public Base() { test(); } public void test() { } } public class Child extends Base { private int a = 123; private Integer b = 456; public Child() { } public void test() { System.out.println(a); System.out.println(b); } public static void main(String args[]) { Child c = new Child(); c.test(); } } Base调用了被子类重写的办法test(),输出成果为 0 null 123 456 由于Base调用了被子类重写的办法test(),在Child结构办法调用之前先调用了父类的办法,父类办法调用了被子类重写的test办法,可是这时分子类结构办法和变量赋值句子还没有被执 行,所以输出默许值0和null;后边子类实例化和赋值之后输出了实在的值。所以在父类的结构办法中,应该只调用private的办法。
承继中重命名和静态绑定 实例变量、静态变量、静态办法、private办法,都是静态绑定的——即拜访绑定到变量的静态类型。 private的变量:子类拜访子类的,父类拜访父类的;public变量和办法:在类内,拜访的是当时类的,可是子类能够经过super.清晰指定拜访父类的。在类外,则要看拜访类的静态类型 ——声明类型,静态类型是父类拜访父类的,静态类型是子类拜访子类的。 重载与重写 重载是改动办法签名里边的参数签名;重写是子类对父类办法的重写。 当有多个重名函数的时分,在决定要调用哪个函数的进程中,首先需求按照参数类型进行匹配,换句话说,寻找在一切重载版别中最匹配的,然后才看变量的动态类型进行动态绑定——当子 类重载了父类的办法,先判别参数类型然后再判别方针是子类仍是父类引证。 例如父类有sum(int a,int b)办法,子类有sum(int a,long b)办法,创立子类的方针c调用sum办法,履行c.sum(1,2),那么调用的是父类的sum(int a,int b)办法。
父子类型转化 子类型能够向上转型为父类型,可是父类型纷歧定能够转化为子类型。能不能转化首要取决于父类型的动态类型是不是这个子类或子类的子类型——常用的便是把Object转子类型。 protected是承继权限,当然还包含了包等级权限。 可见性重写 承继时可见性是不变或增大的,由于承继表达的是“is-a”的联络,即子类肯定是父类,所以不能降低或削减父类对外的行为。 防止被承继final 方针创立的进程 创立方针包含: 1)分配内存 2)对一切实例变量赋默许值 3)履行实例初始化代码 办法调用的进程 寻找要履行的实例办法的时分,是从方针的实践类型信息开端查找的,找不到的时分再查找父类类型的信息——从重写的作用上也能够看出来这一点。动态绑定也是相同。 这个进程,假如承继层次比较深,当要调用的办法坐落比较上层的父类,则调用的功率是比较低的,大多数体系采用一种虚办法表的办法来优化——便是在类加载的时分为每个类创立一个 表,记载该类的方针一切动态绑定的办法(包含父类的办法)及其地址,但一个办法只要一条记载,子类重写了父类办法就只保存子类的。 对变量的拜访是静态绑定的——不论是类变量仍是实例变量。 为什么承继是把双刃剑 1)承继破坏封装。封装是为了躲藏细节,可是承继许多时分却需求了解父类的完成,父类改动的时分要去重视子类的完成。子类重视父类的改动首要是父类可重写办法之间的互相调用,这 样子类重写的时分或许就破坏父类的办法。
2)承继没有反映is-a联络。承继原本规划用来反映is-a联络的,可是实践中规划彻底的is-a联络很难。例如鸟类作为父类,那么有个fly()办法,可是偏偏企鹅不会飞,你或许想企鹅不会飞 可是会游泳,那么fly()办法就完成为了游泳。原本是为了is-a联络规划的承继,可是Java没法确保这种联络。父类的办法和特色子类或许纷歧定都适用,子类完成或许和父类预期纷歧致。 怎样应对承继的双面性 1)防止运用承继 运用final关键字 运用组合而非承继——在类中界说要承继类的变量 运用接口 2)正确适用承继 基类他人写的——重写办法不要改动预期行为;阅读文档阐明,了解可重写办法完成机制,尤其是办法之间的依靠联络;在基类改动的情况下,阅读改动修正子类 咱们写基类,他人完成——运用反响真实的is-a联络,只将真实公共的部分放到基类中;对不期望承继的运用final修饰符;写文档,为他人供给阐明,辅导子类怎样编写 咱们写基类,写子类——能够统筹榜首和第二点,可是自己写恰当能够铺开一点。 2018-06-05更新 第五章 类的扩展 1.接口的实质 假如仅仅将方针看做归于某种数据类型,并按该类型进行操作,在一些情况并不能反响方针以及方针操作的实质。许多时分咱们更关心方针的才能。 接口便是用来表明才能的一种办法。 接口最重要的是降低了耦合,提高了灵敏性。 接口中的变量 接口中的变量是public static final的,即便不写也是这样的。相似于的接口中的办法,写不写都是public的。 接口的承继 接口能够承继,且能够有多个父接口。例如 public Interface extends IBase1,IBase2 Java 8 和Java 9对接口的增强 在Java 8 和Java 9 中都答应接口有静态办法和默许办法。可是Java 8 里边只答应public,Java 9取消了这种约束,提高了办法的复用才能。 笼统类和笼统办法 只有子类知道怎样完成的办法一般界说为笼统办法,例如接口里边的非默许和静态办法。 笼统类便是笼统的类,笼统相关于详细而言的,一般来说详细类具有直接对应的方针,而笼统类没有,它表达的是笼统的概念。 有笼统办法的类一定是笼统类,可是笼统类能够没有笼统办法。笼统类能够和详细类相同界说实例变量、详细办法等,可是他不能创立方针。 假如一个类承继了笼统类,有必要完成一切的笼统办法,除非他自己是笼统类。 为什么需求笼统类 关于笼统办法,假如不知道怎样完成为什么不界说一个空办法体?不让笼统类创立方针,看上去也仅仅增加了一个不必要的约束。 实践上笼统类和笼统办法都是Java的一个语法东西,经过强制措施来引导运用者正确运用它们,在编译阶段就发现问题。 无论编程和日常日子,每个人都或许会犯错,削减犯错不能只靠个人素质,还需求一些机制使得每个一般人都容易把事情做对。这便是笼统类规划的初衷。 笼统类和接口 笼统类和接口有相同的当地,也有不同的当地。接口不能界说实例变量,而笼统类能够。 笼统类和接口是合作不是代替联络,他们常常一同合作运用。接口供给才能,笼统类供给默许完成——完成全部或部分办法,一个几口常常有一个默许完成类。
例如: Collection接口和对应的AbstractCollection类 List接口和对应的AbstractList笼统类 Map接口和对应的AbstractMap笼统类 关于需求完成接口的详细类而言,有两个选择:完成接口需求重写一切办法;承继笼统类依据需求重写非笼统办法。假如详细类现已有父类,那么就只能完成接口。 2018-06-06 内部类的实质 一个类放在别的一个类的内部,这个类就叫做内部类。 内部类和包含它的外部类有比较亲近的联络,和其他类联络不大,界说在内部,能够完成对外部彻底躲藏,有更好的封装性,代码完成也往往更为简练。 内部类仅仅编译时的概念, 在编译的时分会被编译两个文件。 public class OuterClass { private String outerName; private int outerAge; public class InnerClass{ private String innerName; private int innerAge; } } 编译后外部类及其内部类会生成两个独立的class文件: OuterClass.class和OuterClass$InnerClass.class 内部类能够便当的运用外部类的私有变量,把内部类声明为private然后完成对外彻底躲藏,相关代码写在一同,写法更简练。 内部类分为: 静态内部类 静态内部类的运用场景许多,假如它与外部类联络亲近且不依靠于外部类实例,则能够考虑界说为静态内部类。 静态内部类除了在类内部,其它和界说一般类没有区别,它能够拜访外部类的静态变量、静态办法,可是不能拜访实例变量和办法——和一般类的静态办法相似,静态的只能拜访静态的。静 态类能够被外部拜访,运用“外部类.静态内部类”的办法。 成员内部类——与静态内部类比较,成员内部类没有static修饰符。除了静态变量和办法,成员内部类还能够直接拜访外部类的实例和办法。假如内部类和外部类联络亲近,需求拜访外部类 的实例变量或办法,则能够考虑界说成员内部类。 办法内部类——在一个办法里界说和运用。假如办法是实例办法,则除了静态变量和办法,内部类还能够直接拜访外部类的实例变量和办法。假如是静态办法,则只能拜访外部类的静态变量 和办法。办法内部类能够直接拜访办法的参数和办法中的变量,可是这个变量是final或许隐形final——不能被赋值,不然编译过错。 匿名内部类——规模最小,不能在外部运用。
匿名内部类没有单独的类界说,在创立方针的一起界说类。语法如下: new 父类(参数列表){ //匿名内部类完成部分 } new 父接口(){ //匿名内部类完成部分 } 匿名内部类只能被运用一次,用来创立一个方针,没有姓名没有结构办法,可是能够依据参数列表调用对应的父类结构办法。由于没有结构办法,所以无法承受参数。与办法内部类相同,匿 名内部类也能够拜访外部类的一切变量和办法。 匿名内部类也会被生成一个独立的类,可是命名是经过父类加数字编号,没有有意义的姓名。匿名内部类能够做的,办法内部类都能够。假如方针只会创立一次且不需求结构办法来承受参 数,这样能够运用匿名内部类,代码更为简练。 将程序分为坚持不变的主题框架,和针对详细情况的可变逻辑,经过回调的办法进行写作,是核算机程序的一种常用实践。匿名内部类是完成接口回调的一种简练办法。 总结:内部类实质上都会被转化为独立的类,是一种编译上的概念,在代码编译的时分会变为两个文件。但一般来说,它们能够完成更好的封装,代码完成上也更为简练。 2018-06-10 10 枚举的实质 在switch的句子内部(case句子)枚举值不能带枚举类型前缀。枚举类型也都有一个静态的values办法,回来一个包含一切枚举值的数组,次序与声明时的次序共同。 枚举的优点: 界说枚举的语法简练 枚举更为安全。取值规模要么为一个枚举类型,要么为null,不会超出可控。
枚举类型有许多便当办法。 枚举类型会被编译器编译为一个类,承继了java.lang.Enum。有name和ordinal两个实例变量,枚举类型的办法都是经过name和ordinal两个变量完成的。下面是Size枚举编译后的代码: public final class Size extends Enum{ public static final Size SMALL = new Size("SMALL",0); public static final Size MEDIUM = new Size("MEDIUM",1); public static final Size LARGE = new Size("LARGE",2); private static final Size VALUES = new Size[]{SMALL,MEDIUM,LARGE}; private Size(String name,String ordinal){ super(name,ordinal); } public static Size[] values(){ Size[] values = new Size[VALUES.length]; System.arraycopy(VALUES,0,values,0,VALUES.length); return values; } public static Size valueOf(String name){ return Enum.valueOf(Size.class,name); } } 经过编译后的类,能够发现: Size类是final的,不能够被承继。 Size有一个私有结构办法,所以不能够被外部调用创立新方针。 三个枚举值实践上是三个静态变量 values办法是编译器添加的,内部有一个valuse数组坚持一切枚举值 valuseOf办法是调用的父类办法。 在switch句子中,枚举值会被转化为其对应的ordinal值——switch的跳转表最多有32位空间,那么创立一个枚举类,其间类型有long种。这样是不是就会报错?不会,由于在结构枚举的时 候就报错了,等不到switch句子。枚举类型编译后的私有结构办法承受的ordinal参数为int。 2018-06-11 第六章 反常 反常栈信息就包含了从反常发作点到最上层调用者的信息,还包含行号。Java默许的反常处理机制是退出程序,反常发作点后的代码都不会履行。 throw关键字能够与return关键字进行对比。return代表了正常退出,throw代表了反常退出;return的回来方位是确认的,throw后履行哪行代码则常常是不确认的,有反常处理机制动态确 定——反常处理机制从当时函数开端查找看谁“捕获”了这个反常,当时海曙没有就检查上一层,直到主函数,假如主函数没有就运用默许反常机制,即输出反常栈信息并退出。
Throwable throwable是一切反常类的父类,有四个结构办法: public Throwable() public Throwable(String message) public Throwable(String message,Throwable cause) public Throwable(Throwable cause) Throwable类的两个首要参数是message和case。message表明反常信息,case表明触发该反常的其他反常。反常能够构成一个反常链,上层的反常由底层反常触发,case表明底层反常。 Throwable还有一个public 办法用于设置cause: Throwable initCause(Throwable cause) Throwable的某些子类没有带cause参数的结构办法,能够经过这个办法来设置,该办法最多只能调用一次。 Throwable有两个子类:Error和Exception。
SSError表明体系过错或资源耗尽,有Java体系自己运用,应用程序不该抛出和处理。 Exception表明应用程序过错,它有许多子类,能够经过集成Exception或其子类创立自己的反常类。 受检反常(checked)和未受检反常(unchecked) 受检反常和未受检反常的区别在于Java怎样处理这两种反常。受检反常Java会强制要求程序员进行处理,否则会发作编译过错,而对未收件反常则没有这个要求。例如RuntimeException是未 受检反常,Exception的其他子类则是受检反常。 Exception的子类中RuntimeException及其子类都是未受检反常,其他的子类或其子类都是受检反常。所以在创立自己的子类反常时,留意承继的父类。
finally finally的代码不论有无反常发作,都会履行,详细来说: 假如没有反常发作,在try代码履行完毕后履行 假如有反常发作且被catch,在catch内代码履行完毕后履行 假如有反常且没有被捕获,则在反常抛给上层之前履行 由于finally的这个特色,所以往往作为资源释放来运用。 finally的履行细节 假如在try或许catch句子内有return句子,则return句子在finally履行完毕后才履行,但finally不会改动回来值,例如: public static int test(){ int ret=0; try{ return ret; }finally{ ret=2; } } 由于在履行到try内的回来句子return前,会将回来值ret保存在一个暂时变量中,然后才履行finally句子,当finally句子履行完毕后,再回来暂时句子的值,所以finally修正的ret不会被 回来。 假如finally中也有return句子呢?try和catch句子里边的return会丢失,实践会回来finally中的回来值。finally中有return不只会覆盖try和catch里边的回来值,还会掩盖try和catch内 的反常,就像没有反常发作相同。例如: public static int test(){ int ret=0; try{ //ArithmeticException 分母为0 int a=5/0; return a; }finally{ return 2; } } 由于finally句子中有return句子,所以这个办法就会回来2,反常不会再往上层传递。假如finally中抛出了反常,则原反常也会被掩盖。 为防止混淆,应该防止在finally中运用return句子或许抛出反常,假如调用其他代码或许抛出反常,则应该捕获反常并进行处理。 try-with-resources java 7开端支撑一种新的语法,称之为try-with-resources,这种语法针对完成了java.lang.AutoCloseable的接口方针。在try中声明资源,Java 9取消了这种约束,可是有必要是final或许 事实上的final。
示例代码如下: try(AutoCloseable r = new FileInputStream("hello")){ //anything } throws 反常机制中,还有一种和throw很像的关键字throws,用于一个办法或许抛出反常,语法如下所示: public void test() throws AppException,SQLException{ } 关于未受检反常能够不用声明,可是关于受检反常有必要经过声明——换句话说,假如没有声明不能抛出。由于声明是告诉调用者,这些反常没有处理,需求处理。当然声明晰也能够不抛出异 常。 受检反常和未受检反常 受检反常有必要呈现在throws句子中,调用者有必要处理,Java编译器会强制这一点,而未受检反常则没有这个要求。 关于受检反常和未受检反常,一种遍及的说法是:未受检反常表明编程的逻辑过错,编程是应该防止这种过错,例如npe,当发作这种问题的时分,程序员应该考虑检测代码bug而不是处理这 种反常。受检反常表明代码没有问题,由于一些其他问题导致的,调用者应该进行恰当处理。 但实践中未受检反常也需求处理,由于Java程序往往运转在服务器中,不能由于一个逻辑过错就退出。 愈加有用的一种观点是:无论受检反常仍是未受检反常,无论是否呈现在throws声明中,应该在适宜的当地以恰当的办法处理,并且坚持同一个项意图共同性。 怎样运用反常 反常应该且仅用于反常情况。反常不能代替正常的条件判别,例如知道某个值或许未null,能够提早判别。真实呈现反常的时分应该抛出反常,而不是回来特别值——可是假如反常的处理结 果便是特别值,那就回来特别值。 处理反常的方针能够分为康复和陈述。康复是指经进程序主动处理问题,陈述的最终方针或许是用户、运维人员或许程序员。陈述的意图是为了康复。 反常处理的一般逻辑 假如自己知道怎样处理反常,就进行处理;假如能够经进程序主动处理,就主动处理;假如反常能够被自己处理,就不需求向上陈述。 2018-06-12 第七章 常用基础类 1.包装类 包装类便是只对根本类型的包装,例如Integer,Boolean等。 包装类与根本类型的转化代码结构是相似的,每种包装类都有一个静态办法valuseOf(),承受根本类型,回来引证类型,也都有一个xxxValue()回来根本类型。
主动装箱和拆箱 主动装箱和拆箱是编译器供给的才能,背面他会替换为对应的valuseOf()/xxxValue(),例如 Integer a =100; int b=a; 会被编译器替换为 Integer a = Integer.valueOf(100); int b=a.intValue(); 静态办法valueOf和new 在运用中是运用静态办法valueOf仍是new一个新方针?一般建议运用valueOf办法,new 每次都会创立一个新方针,而除了Float和Double外的其他包装类,都会缓存包装类方针,削减需求创 建方针的次数,节约空间提升功能。Java9开端,这些结构办法现已被标示为过期的,推荐运用静态的valuseOf办法。 包装类重写Object类的办法 1)重写equals办法 Object的equal办法是比较的存储地址,重写之后的比较的是根本类型的巨细。Float和Double运用了floatToIntBits(把float的二级制当作int二进制)和doubleToLongBits的办法比较大 小。 2)重写hashCode hashCode回来一个方针的哈希值。哈希值是一个int类型的数,由方针中一般不变的特色映射得来,用于快速对方针区别、分组等。一个方针的哈希值不能改动,相同方针的哈希值有必要一 样。不同方针的哈希值一般应不同,但这不是有必要的。例如学生方针能够用生日作为哈希值,不同学生生日一般不同,哈希值比较均匀,个别生日相同也没有联络。 hashCode和equals办法联络亲近,对两个方针假如equals办法回来true,那么hashCode有必要相同。反之不要求,equals回来false时,hashCode的值有必要为true。 hashCode的默许完成是把方针存储空间地址转化为整数,子类假如重写了equals办法,也有必要重写hashCode办法。之所以有这个规则,是由于Java api中很中类依靠这个行为,例如容器中的 一些类。 包装类都对hashCode进行了重写,其间Byte、Short、Integer、Character的hashCode为其的内部值;Boolean的为: public int hashCode(){ return value?1231:1237; } 选用两个质数,质数用于哈希比价好,不容易冲突。 Long是最高32位和最低32位的异或操作,Double为把二进制看做Long类型之后最高32位和最低32位的抑或操作。
Float是把二进制看做Integer的值。 包装类完成Comparable Comparable类只有一个办法compareTo,当但前方针与参数进行比较的时分,当小于、等于、大于参数时别离回来-1、0、1。 Number类 6种根本数据类型都有一个父类Number,Number是一个笼统类,他界说了如下办法: byte byteValue(); short shortValue(); int intValue(); long longValue(); float floatValue(); double doubleValue(); 不可变性 包装类是不可变类,即方针创立之后就无法修正了,经过以下办法强制完成: 一切包装类都是final的,不能被承继; 一切根本类型值都是私有的,且声明为final; 没有界说setter办法。 包装类的valueOf完成 以Integer为例,他的valuseOf办法如下: public static Integer valueOf(int i){ assert IntegerCache.high>=127; if(i>= IntegerCache.low && i<=IntegerCache.high) return IntegerCache.cache[i+(-IntegerCache.low)]; return Integer(i); } IntegerCache是一个私有静态内部类。其间IntegerCache有两个特色low和high,high能够经过配置指定,默许是127,low无法改动,默许为-128。 假如没有默许指定,那么IntegerCache会缓存-128~127之间的整数,由于包装类是不可变的,所以直接回来缓存值即可。
String String内部用一个字符数组表明字符串,实例变量界说为: private final char value[]; String有两个结构办法: public String (char value[]); public String (char value[],int offset,int count); String会依据参数新创立一个数组,并仿制内容,而不会直接用参数中的字符数组。 String不可变性 String类是不可变类,即方针一旦创立,就没有办法修正了。String也声明为了final,不能被承继,内部char数组也是final的,初始化就不能再变了。 字符串常量 在内存中,字符串常量放在一个同享的当地,叫做字符串常量池。它保存一切的常量字符串,每个常量只会保存一份,被一切同享者同享。当经过常量运用一个字符串的时分,运用的便是常 量池中的哪个对应的String类型的方针。例如 String name = "测验"; String name2="测验"; System.out.println(name==name2); 输出成果为true,由于name和name2都运用的是常量池里边的方针。可是经过new创立的就不是的了。
例如: String name = new String("测验"); String name2 = new String("测验"); System.out.println(name==name2); 输出成果便是false了。name和name2指向两个不同的String方针,其间两个方针里边的value值指向同一个char数组。 StringBuilder StringBuffer和StringBuilder都相同,可是StringBuffer是线程安全的。 StringBuilder内部的char数组不是final的,是可变的,默许长度为16。当长度不可的时分会按指数扩展,会在当时长度乘以2再加上2。所以假如能够预期字符串长度的情况下,应该在创立 StringBuilder的时分指定长度。 在不知道最终需求多长的情况下,指数扩展是一种常见的额战略,广泛应用于各种内存分配相关的核算机程序中。 String的+和+=运算符 String的+和+=运算符实践上编译器转化为StringBuilder,然后运用append。例如: String hello = "hello"; hello+=",world"; System.out.println(hello); 一般会被Java编译器转化为: StringBuilder hello = new StringBuilder("hello"); hello.append(",world"); System.out.println(hello.toString()); 已然Java会直接编译,为什么还要自己运用StringBuilder呢?由于编译器没那么智能,或许会创立多个StringBuilder。
第8章 泛型 “泛型”的字面意思便是广泛的类型。类、接口和办法代码能够应用于非常广泛的类型,代码与它们能够操作的数据类型不再绑定在一同,同一套代码能够用于多种数据类型。 泛型便是类型参数化,处理的数据类型不是固定的,而是能够作为参数传入。 Java的伪泛型 Java伪泛型是指Java把泛型分为两部分:编译器和虚拟机。Java的泛型在编译的时分会把泛型替换为Object,然后运用类型强制转化。在虚拟机履行的时分,不会感知到泛型,只知道一般的 类及代码。 泛型的优点 更好的安全性 更好的可读性 言语和程序规划的一个重要方针是将bug尽量消除在摇篮里,能消除在写代码的时分就不要留到运转的时分。经过泛型,编辑器、编译器会提早发现问题,不会在运转的时分发现。 强类型言语的优点。弱类型言语很难在编写的时分发现这种问题。 办法是否泛型和类是否泛型无关。办法的泛型声明如下: image.png 图片来自杨元博客 在回来值前面放置一个类型参数。 和泛型类不同,泛型办法调用时一般不需求特意指定类型参数的实践类型,Java编译器能够主动推断出来。 类型的参数约束 Java能够支撑约束类型参数的上界,经过extends关键字来表明,这个类能够是详细的类或许详细的接口,也能够是其他类型参数。 指定鸿沟后,类型擦除时就不会转化为Object了,而是会转化它的鸿沟类型。
1)上限为某个接口 例如约束类型为Comparable接口: public staticT max(T[] arr){ T max = arr[0]; for(int i=0;i0){ max = arr[i]; } } } 可是这么写编译器会报错,由于Comparable是一个泛型接口,它也需求一个泛型参数。所以应该为: public static T max(T[] arr) 是一种令人费解的语法办法,这种办法称为递归类型约束,能够这么解读:T是一种数据类型,有必要完成Comparable接口,且有必要能够与相同类型的元素进行比 较。 2)上界为其他类型参数 public class DynamicArray{ public void addAll(DynamicArrayc) { for (int i = 0; i < c.size; i++) { add(c.get(i)); } } } 可是这种写法有一些局限性,例如: DynamicArraynumbers = new DynamicArray<>(); DynamicArrayints = new DynamicArray<>(); ints.add(100); numbers.add(34); numbers.addAll(ints); Integer是Number的子类,这种运用办法感觉合情合理,可是为什么不可呢?看下面代码: DynamicArrayints = new DynamicArray<>(); DynamicArraynumbers = ints;//假设这行是合法的 numbers.add(new
Double(12.34)); ints中原本预备存储的是Integer,可是里边却存进去了Double,显然破坏了泛型的类型安全。 由于Integer是Number的子类,可是DynamicArray不是DynamicArray的子类。 假如想把Integer添加到Number地点容器中,这个问题能够经过类型约束来处理——把办法也变为泛型的。例如: publicvoid addAll(DynamicArrayc){ //完成代码 } E是DynamicArray的类型,T是addAll的类型,T的上界约束是E,这样就能够了。 泛型总结 泛型是核算机程序中一种重要的思想办法,他将数据接口和算法与数据类型相别离,使得同一套数据结构和算法能够应用于各种数据类型,并且能够确保类型安全,提高可读性。 更简练的参数类型约束 运用通配符,能够愈加简练。例如前面说到的将Integer方针添加到Number容器中的代码,能够写成: public void addAll(DynamicArray c){ //办法体 } 这个办法没有界说类型参数(办法有泛型办法变为了一般办法),c的类型为DynamicArray c,“?”表明通配符,""表明有约束通配符,匹配E或许E的子类,具 体什么类型是不知道的。
统一运用了extends关键词,和有什么区别: 1)用于界说类型参数,声明晰一个类型参数T,可放在泛型类界说的类名后边,泛型办法的回来值前面。 2)用于实例化类型参数,它用于实例化泛型变量中的类型参数,仅仅这个详细类型是不知道的,只知道他是E或许E的某个子类型。指明晰T是上界E或许E的子类; 中?是一种详细的类型参数。 除了前面说到的有约束通配符,还有一种无约束通配符,无约束通配符和运用类型参数是相同的。 尽管通配符办法更为简练,可是两种通配符都有一个约束——只能读,不能写——addAll办法里边也是读。 原因是这样的,通配符不像类型参数那样指定的是一种,而是一到多种,编译器彻底无法确认到底传入的是哪一种。这种情况不符合Java类型安全规范,例如下面代码: DynamicArray numbers = new DynamicArray<>(); numbers.add(new Double(23.0)); numbers.add(new Interge(21)); 类型参数和通配符的总结 1)通配符都能够用类型参数来代替,通配符能做的,类型参数都能做。
2)通配符办法上能够削减类型参数,办法上简单可读性好,所以能用通配符就用通配符 3)假如类型参数之间有依靠联络,或许回来值依靠类型参数,或许需求写操作,则只能用类型参数 4)通配符和类型参数往往合作运用,界说必要的类型参数,通配符表明依靠,并承受更广泛的数据类型。 超类型匹配符 有一种和正好相反,它的办法为,称之为超类型通配符,E表明某个父类型。有了这个,咱们能够更灵敏的写入了。 超类型匹配符无法运用类型参数代替。 通配符比较 前面介绍了三种通配符,、、,它们之间的联络是: 1)它们的意图都是是办法接口更为灵敏,能够承受更为广泛的类型。 2)用于灵敏写入或比较,使得方针能够写入父类型的容器,使得父类型的比较办法能够应用于子类方针,它不能被类型参数代替。 3)和用于灵敏读取,使得办法能够读取E或许E的子类容器方针,它能够被类型参数代替,可是通配符更为简练。
运用泛型类、办法和接口时的细节和局限性 1)根本类型不能用于实例化类型参数——由于编译时,会被替换为Object,所以不能运用根本类型。 2)运转时类型信息不适用于泛型——由于编译之后,泛型就不存在了 3)类型擦除或许会引发一些冲突——例如运用泛型重载办法,编译后都为Object。 界说泛型类、办法和接口时的细节和局限性 1)不能经过类型参数创立方针 例如 T elm = new T(); 假如答应,那么用户以为创立的是详细类型的方针,可是类型擦除后都为Object,所以为了防止误解Java直接禁止。 2)泛型类型参数不能用于静态变量和办法 由于静态变量和办法是归于的类的,且与类型参数无关。假如能够创立,那么每个实例都会有一份静态变量和办法。可是由于类型擦除,一切类都只有一份。 这里说的是类型参数不能用于静态变量和办法,不是说静态办法就不能有自己的类型参数,这个参数与泛型类的类型参数是没有联络的。 3)了解多个类型约束的语法——支撑多个上界,上界之间用&符号衔接。
例如 T extends Base & Comparable & Seriablizable Base为上界类,假如有上界类,类应该放在榜首个,类型擦除是,会用榜首个上界替换。 泛型与数组 不能创立泛型数组。 前面说到过,类型参数之间有承继联络的容器之间没有联络,例如DynamicArray方针不能赋值给DynamicArray变量。可是数组是能够的,例如 Integer[] ints = new Integer[10]; Number[] numbers = ints; Object[] objs = ints; 这种赋值联络Java是支撑的,数组是Java直接支撑的概念,所以他知道数组元素的实践类型,知道Object和Number是Integer的父类,所以能够的。尽管Java支撑这种操作,可是运用不当会 发作运转时反常,例如 objs[0]="hello"; 编译没有问题,可是运转时会抛出ArrayStoreException。由于Java知道实践类型是Integer,可是写入String就不可。 假如答应创立泛型数组,那么数据擦除之后,编译不会报错运转也不会报错。可是假如运用不当,代码的其他当地就会爆雷——把实践A类型作为B类型运用。并且这种非常隐蔽。所以Java为 了防止发作这种误解,就回绝创立泛型数组。 对泛型和数组的联络总结之后为: Java不支撑创立泛型数组 假如要寄存泛型方针,能够运用原始类型的数组,或许运用泛型容器 例如泛型类Pair,原始类型数组应该为: Pair[] = new Pair[]{new Pair("1元",7)}; 泛型容器内部运用Object数组,假如需求转化泛型容器为对应类型的数组,需求运用反射。