java核心卷1

it2024-11-02  7

第一章:java程序设计概述

白皮书术语

简单性:摒弃了c++难以理解的一些特性,可以说是c++的“纯净版”,另外一个方面的简单是小,解释器加上一些基础的类库,和对多线程的支持,大概需要175kb,这在当时来说已经是很大的成就了。面向对象:面向对象他是一种程序设计的技术,很显然java已经非常成熟的运用了这门技术,重点是对象,和对象的接口。制造一辆汽车,关心的是零件,而不是零件怎么制作的,java将c++的多继承转换为接口的方式更加容易让人理解,还有丰富的运行时自省的功能。分布式:java有丰富的例程库,用于处理http这样的协议,能够通过url访问打开网络上的对象,就像在本地打开一样健壮性:java采用的指针模型可以消除重写内存和数据损坏的可能,java编译器能够检测出其他语言在运行时才能检测的错误。安全性:一开始,java就设计成能够防范各种攻击,例如,运行时,堆栈溢出(蠕虫和病毒常用的手段),破坏自己进程之外的内存,未经授权读写文件。体系结构中立:生成与平台无关的字节码文件(经过编译的文件格式),可以通过特定的jvm在不同的平台生成不同的机器码,但是这种通过“虚拟机”生成代码的思路并不是新的,在之前的pascal,lisp,smalltalk早已采用这种技术,jvm有一个选项,就是将执行最为频繁的字节码序列翻译成机器码,这一过程叫做即时编译。可移植性:相对于c++或者c来说,其数据类型占用字节数是确定的,例如c的int可能是16位也可能是32位,但是java所有的平台都是32位高性能:java字节码可以(运行时)动态的翻译成运行这个应用的的特定cpu的机器码,java的即时编译技术已经非常成熟,甚至有时候超过了传统的编译器,还可以监控经常执行哪些代码并对他们加以优化,更为复杂的优化是使用内联(消除函数调用),即时编译知道哪些类已经被加载,基于已加载的类集,如果特定的函数不会被覆盖,就可以使用内联,必要时,还可以撤销优化。多线程:java是第一个支持并发编程的主流语言,而且在并发编程方面十分的出色 10.动态性:将一些代码添加到正在运行中的程序中,动态性是十分重要的,如在网上下载一段代码,然后再浏览器中打开这段代码,如果使用c或者c++是很难实现的

java applet

对于在internet上下载java字节码文件,并在本地运行(在网页中运行的java程序)称为applet,每次访问都能得到最新的applet,刚开始诞生的时候感觉会很流行,但是网景公司的浏览器和微软的ie浏览器因为使用的java版本不同,有些版本已经过时,难以将最新的Java版本开发的applet应用,随着后来adobe的falsh出场,和java的安全问题(就是访问一个applet,浏览器和java浏览器插件的限制变得越来越多),所以现在要想在浏览器中使用applet不仅要一定的水平而且要付出很大的努力,applet逐渐淘汰

java发展史

这张图片是不同的jdk版本出现的功能。

第二章:java环境

Java程序 Java图形程序 Javaapplet程序

第三章:java基本程序结构

一个简单的java程序

java从1.4开始,就强制要求主函数修饰符是publicjava没有为操作系统返回“退出代码”,如果main正常运行,程序的退出代码为0,如果想返回其他的代码需要调用Sytem.exit()方法

java注释

Java提供三种注释://,/* */这两种是单行和多行注释还有一种是/** */文档注释,可以用来自动生成文档,不能进行注释嵌套,一位只会和末尾的第一个 */进行匹配

数据类型

java提供了固定得数据范围,不像c++那样为了在特定平台得到更好得性能而采用不同范围,这样就会在移植平台的时候可能出现溢出的情况,而java提供的固定范围,可以很好的实现跨平台

java提供了八种基本数据类型

四种整数类型(int,short,long,byte):长整型后面可以添加一个L或者l,十六进制有一个前缀0x或者0X,八进制有0,二进制0b或者0B(从java7开始,可以用_下划线分割,这样更易读,编译器会将下划线去掉)

两种浮点型(float,double): float有一个F或者f的后缀,double有D或者d的后缀,因为float精度很小,大多数情况都是double,所以一个小数不加任何后缀的情况下是double类型的。(double还有十六进制的表示方式例如0.125=2^-3,就可以表示为0x1.0p-3,这里的1.0是十六进制数,-3是十进制但是基数是2,所以表示就是1* 2的-3次方,还有常规的aeb表示形式) 浮点型数据不适合表示四舍五入的情况,因为java采用二进制系统保存,不能精确的还原二进制数,所以会和预想的结果可能不太一样,最好使用BigDecimal类。

一种表示unicode编码的char: 可以表示为十六进制值,范围从\u0000到\uffff,\u这个转义字符还可以出现在引号之外,其他的转义字符则只能在引号中间 unicode和char char描述的是一个utf-16的代码单元(16位)

一种表示真值的Boolean 注意:java没有提供unsigned的修饰的类型 所有数值都遵守IEEE 754规范,Double.positive_infinity正无穷大,Double.negative_infinity负无穷大,Double.NaN不是数字

变量

java变量可以用字母和数字定义,但是必须由字母开头,java的数字和字母范围更加大,字母包括原来的字母还有_,$ ,以及本地语言的字母,在项目开发中因该避开使用$ ,因为这是留给一些工具产生的,或者编译器产生的

变量初始化

使用未初始化变量,编译器就会报错

常量

在java中,使用final代表常量 final修饰的常量只能被赋值一次, 而且不能修改,不然就会报错,习惯上把常量大写

运算符

除法运算/,当两边都是整数时,结果也会是整数,否则就会是浮点型(double)类型,整数除以0会报异常,浮点型除以0则会是正无穷大,或者是NaN 对于实现跨平台,想要实现浮点型的结果一致是有难度的,double是64位的存储一个数值,而 有些处理器是使用80位的寄存器,这些寄存器会进行中间结果的计算精度,这个结果会和64位的结果可能会不一样,所以java一开始是始终进行截断操作的,但是后来受到了反对,所以修改了,默认的话是不进行截断的,只有加了strictfp关键字的方法才会进行截断操作

数学函数和运算符

double Math.sqrt(double a)计算a的平方根 double Math.pow(double a,double b)计算a的b次方 Math.floorMod(int a,int b) 三角函数: Math.sin Math.cos Math.tan Math.atan Math.atan2

Math.log Math.log10 Math.exp Math.round(float) 四舍五入 常量π和E Math.PI Math.E 为确保在所有平台上都有相同的数值,可以使用StrictMath类

数值类型之间的转换

如果一种位数多的类型向位数少的类型转换就会造成精度的丢失,有大类型的都会向大类型的转换,如int+double,double大一些,就会向double转换 虚线表示精度会有丢失,实线表示不会造成精度丢失 当超过转换的目标的范围,就会高位截断,例如(byte)300会转换成44(低八位)

强制类型转换

除了上面的自动类型转换,还有强制的,有些时候需要将大类型转换为小类型就需要在类型前面(目标类型),就可以强制类型转换

结合赋值和运算符

相当于两个赋值操作,如果最终赋值类型不等于左边的就会进行强制类型转换,例如,int x; x+=1.4;等价于x = (int)(x+1.4)

自增自减运算符

跟C语言一样,提供自增自减的运算符,有前缀和后缀的区别 注意:在表达式中尽量不要使用++运算符,可读性不高

关系运算符和Boolean运算符

用于检测相等或不等,值为true或者false

逻辑运算符

&& || ! 前两个有短路的效果 !后面只能接boolean值

位运算符

& | ^ ~ 按位与,按位或,按位异或 << >> >>> 左移位,右移位,无符号右移位 左移位会在右边补0 右移位会在高位补符号位 无符号右移位会在高位补0

移位运算符会在达到位数范围后从头开始,例如1<<35,相当于1<<3,这里的1是整数类型,相当于35模32

由于>>不能确定是进行了算术运算(拓展符号位)还是进行了逻辑运算(补0)意味c++对负数产生的结果可能依赖具体的实现,但是java消除了这种不确定性

括号和优先级别

枚举类型

enum关键字 包括有限个命名的值

字符串

从概念上将,字符串就是unicode序列,java没有内置的字符串类型,但是基础类库提供了一个字符串的预定义类,每一个用双引号括起来的都是String的一个实例

子串

String实例提供了一个方法,substring(int a,int b)不包括b的位置,如果其中任何一个位置越界就会报错

拼接

String类提供了一个静态方法join可以将多个字符串拼接在一起,还可以直接字符串之间的分隔符

不可变字符串

String类没有提供修改字符串的方法,所以一旦一个字符串创建好了之后就不能改变,但是这是否会降低程序的性能,是也不完全是,因为不可变的话就可以让编译器提供共享。 可以理解为java的字符串是c语言里面的字符指针 char* a = “abc”,这样的,而不是char a[3] = “abc”,如果对字符串变量重新赋值的话,是重新创建一片内存空间,而原来没有使用的字符串将会被jvm回收。 小提示:c++的字符串是可以进行修改的,也会进行垃圾回收

检测字符串是否相等

使用String实例的equals方法,a.equals(b),这里的a可以直接用双引号换成字符串也可以 如果想要不区分大小可以使用equalsIgnoreCase方法 compareTo(String otherString) compareToIgonoreCase(String str) 上面两个方法返回值都是整形,0表示相等,1表示不相等

但是使用equals看起来清晰一些 注意:== 运算符只能比较两个位置是否一致,完全有可能将内容相同的多个字符串放置在不同的位置

解释:如果jvm始终将字符串共享,则用==比较就是相等的,但是事实上,使用 +或者substring等 产生的字符串是不共享的

空串和null串

空串是长度为0,内容为空的字符串,有自己的长度,内容 null串是一个Java对象,表示没有任何对象和该对象有关联

码点和代码单元

java字符串是由char值序列组成的,而char类型又是由utf-16编码表示unicode码点的代码单元 大多数的字符是一个代码单元就可以表示,辅助字符是两个代码单元 length()方法返回的是代码单元的数量 如果想要实际的码点数量则用codePointCount(int beginIndex,int endIndex)方法 charAt()返回的是指定代码单元 offsetByCodePoint(int index,ine codePointOffset)方法,返回从index,偏移codePointOffset后的码点的索引 codePointAt(int index)方法,返回指定码点的十进制Unicode编码

构建字符串

String StringBuffer StringBuilder 第一个String类是不可变类,每一次拼接都会产生大量的副本,占用内存降低效率 StringBuilder可以改变,线程不安全(不支持并发访问),效率高 StringBuffer可以改变,线程安全,效率低

可以看出StringBuilder线程不安全,造成数据的混乱,相反StringBuffer大多数方法都是synchronized修饰的,则不会造成混乱。

输入和输出

读取输入

Scanner类,指定构造器System.in则可以接受控制台的输入 next下一个单词 nextInt下一个整数 nextDouble下一个浮点型 如果输入的类型不一致就会报错 Console类,比Scanner安全,输入密码时显示不出,但是Scanner比较方便,大多数情况还是使用Scanner

格式化输出

System.out.printLn java还提供了和C语言一样的格式控制输出 printf() 这种通过格式输出控制的时间格式已经过时

文件的输入和输出

块作用域

就是用一对括号括起来的语句,(方法体)块作用域内可以嵌套定义块,但是不能像C语言那样定义重名的变量,这样会增加编程的出错率,所以不能重复定义

条件语句

if else

循环

while do while

确定循环

for 对于for循环,如果控制变量是浮点类型可能会造成死循环,如for(double i=1.02;i!=10;i+=0.1)由于浮点型不能精确的表示0.1,所以可能会跳过10造成死循环

switch语句

case的取值可以是 char byte short int 的常量表达式 枚举类型 从jdk7开始可以使用字符串

中断控制流程的语句

有break和continue,用法和C语言是一样的 区别在于java的提供了带标签的break和continue,可以用于跳出多重循环而不会像goto语句那样破坏程序结构,如果在多重的循环中跳出来,则会执行标签的下一条语句

大数

由于java采用的是二进制系统,从而不能精确的表示0.1之类的浮点数,所以会造成数据的误差,应当使用BigDecimal类进行操作 BigDecimal.valueOf(long a)返回一个转换后的BigDecimal BigDecimal.valueOf(long a,int t)返回a/(10^t)的BigDecimal 加减乘除 add(BigDecimal other) subtract(BigDecimal other) multiply(BigDecimal other) divide(BigDecimal other)如果是无限循环小数会报错,可以使用下面方法 divide(BigDecimal other,RoundingMode mode)RoundingMode.HALF_UP是四舍五入 compareTo(BigDecimal other)相等0,小于负数,大于正数

BigInteger也是用于整数的大数运算,不限制位数可以构造的时候传递字符串,方法和BigDecimal差不多

数组

存储同一数据类型的集合,如果只是声明,这就不是真正的数组 可以直接赋值,例如int[] a = {1,2}是一种简写new的写法 还有一种就是初始化同时赋值的写法int[] a = new int[]{1,2}这里注意不能直接写出数组的大小。 数组变量可以重新初始化或者将其他数组变量赋值给这个变量 有一种小技巧,数组是可以定义0长度的,但是和null又不一样,在结果刚好为空的时候这样很有用,不会造成空指针异常 int[] a = new int[0] int[] a = new int[]{} 两种方法都可以

访问数组元素

下标是从0开始的

创建一个数字数组时,所有元素初始化为0boolean数组,所有元素默认初始化为false创建对象数组的,所有元素初始化默认为null

for each循环

它定义了一个变量暂存集合中的每一个元素,不用担心数组下标越界的问题 Arrays.toString()方法提供了得到数组元素的字符串

数组拷贝

使用Arrays.copyOf(被拷贝的数组,新数组长度) 如果长度小于被拷贝的数组,则只拷贝指定的区域 如果大于原来的长度,则赋值是默认的(0,false,null) 注意:java数组和c++的数组很大不同,例如c++的int a[100]不同于java的int[] a = new int[100],而相当于int * a = new int[100],由于java没有指针运算,所以不能通过a+1得到下一个元素

命令行参数

类名后面的第一个参数是args[0]

数组排序

Arrays.sort() 使用了优化后的快排,效率很高

随机数

Math.random() 可以生成0~1之间的浮点数(包括0但是不包括1) 用生成的浮点数*n,就可以的到0~n-1范围内的数

Arrays工具类

binarySearch(a,v) 在数组a中查找v binarySearch(a,start,end,v) 在指定范围查找v(不包括end),吐过查找到了返回对应的下标,如果没有找到则返回一个负数,是负的a长度 fill(a,v) 将数组a的元素都填充为v equals(a,b) 如果两个数组大小相等且对应元素相等则为true

多维数组

deepToString()多维数组转换成字符串 foreach循环不能自动处理多维数组中的每一个元素,应该用一个多重foreach循环 定义的时候可以只指定行 int[][] a = new int[3][]; 指定了一个整形数组,里面有三个元素,元素又是一个数组,但是还没有分配。 这里的二维数组也不同于c++的数组 int** a = new int*[] 二级指针,一级指针则指向真正的数组

第四章:对象与类

面向对象程序设计概述

object-oriented programming OOP是当今主流的程序设计方式,取代了20世纪70年代“结构化”或者过程化编程技术 算法+数据结构=程序 在面向过程的程序设计方式中:算法即处理某一功能的过程,再对数据采用适当的存储结构,第一步是过程,第二步才是数据 而在面向对象的程序设计方式中:将数据排在第一,过程排在第二

总的来说面向对象将一个规模大的系统划分为完成特定功能的一些模块(对象)如果某一功能的数据发生错误,这将比在面向过程中笼统的大模块中查找容易的多。

类是构造对象的模板或者蓝图,可以将类想象成一个模具,将对象想象成小甜品,将材料放入模具制造甜品的过程称作创建类的实例 封装(encapsulation 也成数据隐藏):是处理对象的重要概念,将数据和方法封装到同一个包中,并对对象的使用者隐藏具体的实现方式,每一个类的实例都有一组特定的实例字段值,这些值的集合称为这个对象当前的状态,,只要调用这个对象的方法,他的状态就有可能会改变 封装的关键:只能通过对象的方法对对象数据进行操作,而不能在其他类中访问到当前类的实例字段,封装给对象带来了“黑盒”特征,这是提高重用性和可靠性的关键 在扩展一个已有的类时,新类具有父类的全部属性和方法,只需要在新类中提供适用于这个新类的新方法和数据字段(继承inheritance)

对象

要想使用OOP,就得了解对象的三个主要特征

对象的行为对象的状态对象的标识 1表示对象提供的方法,完成哪些操作 2表示对象的描述信息(属性),随着时间不同,状态也有可能发生改变,例如调用了某个对象方法 3表示同一状态的两个对象的区分方式,每个对象都有唯一的标识。

类之间的关系

依赖(uses-a):如果一个类的方法依赖于另一个类的对象,就叫做依赖关系,应当减少这种耦合关系,如果修改其中一个类可能会造成另一个类的bug聚合(has-a,有时也表示关联关系)一个对象里面包含若干另一个类的对象,表示A对象包含B的对象继承(is-a)表示一个更特殊的类和一个更一般类之间的关系,一般来说就是如果一个A扩展自B,A不但有B继承的方法,还有有一些额外的功能

UML符号

使用预定义类

并不是所有的类都由面向对象的特征,例如Math类,可以直接通过Math.random(),不用关系他是怎么实现的,这正是封装的关键,但是Math类之封装了功能,没有隐藏数据,因为没有数据

对象和对象变量

要想使用对象,就必须构造对象,并指定其初始状态,然后对对象应用方法 对象变量并没有实际的包含一个对象,而是引用了一个对象。 new操作符返回的是一个引用。 注意:所有的java对象都存储在堆中,当一个对象包含另外一个对象变量时,只是包含着另一个堆对象的指针

java类库的LocalDate类

Date类的实例有一个状态,就是特定的时间点 时间是距离一个固定时间点的毫秒数来表示的(纪元)UTC(Coordinated Universal time国际协调时间)时间1970年1月1日00:00:00 和GMT(Greenwich Mean Time格林尼治时间)一样,是实用的科学标准时间 但是Date类对于人类记录日期不是特别有用,Date的日期可以描述大多数地区的阳历,但是对于中国或者希伯来的描述就不一样了 类库的设计者决定将时间和时间点的命名分开,所以标准的java类库由两个类,一个是用于表示时间点的Date类,另外一个就是日历表示方法的LocalDate类。 localdate类应该使用工厂方法now()获取对象实例,这个实例会代表当前的日期

now过去当前日期的对象 of构造一个给定日期的对象 getyear getmonthvalue getdayofMonth 获得年月日 getdayOfweek获得当前日期是星期几返回一个DayOfWeek对象调用getvalue可以获得对应的整数值 plusDays minusDays 得到之后或者之前几天

更改器方法和访问器方法

可以改变对象装填的方法叫更改其方法 通过调用方法而不改变对象状态然后生成一个新对象返回,这个方法叫访问器方法

多个源文件的使用

如果一个A类中使用了另一个B类 当Javac 编译A类时,会自动编译B类,如果B类已有更新就会重新编译B类

隐式参数与显式参数

隐式参数是调用对象方法的那个对象,可以在方法中通过this来表示这个隐式参数 显式参数式方法括号后面接受的参数

封装的好处

封装易于检查出错误 注意:对于可变类的返回要注意,因为返回的是同一个对象,这样对对象进行修改之后就破坏了封装的完整性,不易于检查出错误,如果一定要返回可变类,应该对他进行克隆clone

基于类的访问权限

一个类的类方法可以对他的所有对象进行操作,而不仅限于隐式参数 (个人理解)对象只有属性,然而类方法是提取出来的,可以操作有的对象属性,当然私有属性是不能访问的。

final实例域

对于final修饰的变量,表示这个变量一旦给了他指向的目标,他就不会再指向别的内容了。

静态域和静态方法

静态域也叫类域

静态常量

使用final修饰的变量是不允许再对其进行赋值的 然而System类里面有一个setOut方法,可以将System.out流进行重新设置,这是因为setOut方法不是由java语言实现的,他是一个本地方法,本地方法可以绕过java语言的存取控制机制。

静态方法

静态方法不能访问实例对象,但是可以在实例对象中访问静态方法(但是通常不建议这么做)

工厂方法

静态方法还有另外一种用途 就是通过工厂方法来构造对象,返回不同的构造对象

main方法

main方法不对任何对象进行操作,在启动程序时还没有任何一个对象,而main方法就会执行并创建程序所需对象。

方法参数

按值调用 按引用调用

对象构造

重载

overlording 如果一个方法有相同的名字,不同的参数,便产生了重载 重载解析: 编译器挑选出具体执行哪一个方法,通过各个方法给出的参数列表和特定方法的参数进行对比,如果找不到对应的参数列表就会编译错误 签名: 提供方法名和参数列表,例如String类的 indexOf(int) indexOf(int,int) 这就是签名

默认域初始化

如果在构造器中没有对域进行初始化,就会赋值上默认值。这会影响程序的可读性。 如果没有提供构造方法,那么系统会提供一个空参的构造方法,将实例域都初始化为默认值,0,false,null 如果提供了一个带参数的构造方法,则无参的构造方法就不会自动创建,从而不能使用。

显式域初始化

在定义实例域的时候给予初始值(这会在构造器之前执行)

参数名

调用另一个构造器

调用当前类的另外构造器可以使用this(参数列表)来进行另外构造器的调用,不能进行构造器的递归调用 这样 对公共的构造器代码部分只用写一次即可

代码块

对象初始化块 用一对大括号包起来的 每当new一个对象时,都会自动调用这个代码块 构造器会在执行了这个代码块之后再执行 静态初始化块 静态代码块会在第一类加载时执行。

从图片中可以看出代码块的执行顺序 在java8之前是可以没有主方法就通过静态代码块打印输出一个Helloworld然后再输出没有主方法,在8及其之后程序就会先检查有无主方法了 java.util.Random 构造一个新的随机数生成器 random返回0-n-1的随机数

对象析构和finalize方法

在c++中有专门释放对象或者资源的析构器,但是由于java有垃圾回收期,所以就没有析构器。 如果使用了内存之外的资源,比如硬盘文件或者系统资源的另外一个句柄,这种情况下就需要回收了。 可以通过重写finalize方法,finalize方法会在清除对象之前进行调用,但是有弊端,你不知道什么时候回进行对象回收和清除,finalize方法很难知道什么时候调用。 有个System.runFinalizersOnExit(true)可以确保在jvm关闭时进行finalize的调用,但是这个静态方法不安全,可以使用替换的方法,Runtime.getRuntime().addShutdownHook(Thread a),这个方法可以在jvm关闭之前执行初始化但是未执行的线程a的线程体,从而确保资源的释放 从图片中可以看出System那个方法是已经弃用的,在运行的时候我发现有两次是没有执行的finalize方法的,所以可能官方定义的不安全可能在这里吧。

java允许使用包将类组织起来 所有的标准java包都处于java,javax包层次中 为了确保包的唯一性,sun公司建议将域名逆序书写,因为域名是独一无二的, 从编译器的角度来说java.util和java.util.jar包是没有任何关系的,这两个包都拥有独立的类集合

类的导入

一个类可以使用所属包下的所有类,以及其他包下面的公有类(public class如果在这个类文件写一个不是public修饰的class其他包就访问不到)

在包中定位是编译器的工作

import和#include还是不同的,c++不能看到其他文件的内部,除了#include引用进来的和自身文件,但是java不同,java可以看到所有文件的内部,只需要告诉他到哪里去看就行了,例如java.util.LocalDate,使用import的好处就是简洁,不用每次都写 在c++中类似的包机制是namespace,java的package和import类似c++的using和namespace

静态导入

可以通过package static导入其他类中的静态方法或者域

将类放到包中

只需要将包名放入源文件的开头,如果没有将包名放到开头,则这个类会被放到一个默认包中(default package),默认包是一个没有名字的包。 javac后面跟的是路径名加上具体需要编译的类名.java javac com/lzb/User.java 调用jvm加载类使用java命令后面跟具体的包名和类名 java com.lzb.User javac是对文件进行操作,所以使用的是文件分隔符 java是虚拟机对类进行操作,所用使用的是点运算符

注意:如果在文件开头导入了package指定包,而且没有依赖其他的类,即使不在指定的目录下也可以进行编译,但是使用java命令运行的时候jvm就会找不到具体的类,就会报错 可以看出是可以正常编译的,但是运行就会有问题

包作用域

public protected private 第一种可以被其他包中的类访问到 第二种只能被同一个包中的类访问到 第三种则只能在当前类中访问

类路径

类文件可以存储在文件系统之外,还可以存储在jar(java archive java归档)文件中,这样改善性能还可以节省空间。 为了能让jvm找到这些类,还需要配置类路径 点表示当前路径,windows中用分号进行分割。

jvm要找类文件,会先搜索jre/lib和jre/lib/ext目录下放的系统类文件,找不到的话就会从设置的classpath中搜索。

如果这个类依赖于别的类文件,编译器就会去定位,编译器定位比虚拟机定位会复杂很多,编译器会查找包含这个类的包,并查询所有的import指定,确定其中导入了被引用的类,例如查找User.java import java.util.* import com.lzb.* 他会首先去java.lang.User(默认包含这个包)然后去java.util.User,然后去com.lzb.User进行逐一查看,如果找到了两个以上的类,就会产生编译错误,因为类要是唯一的,多个的话会冲突,编译器的任务还不止这些,他还要去看原文件是否比编译好的文件新,如果新的话还要重新进行编译。

设置类路径

最好用-classpath或者-cp

提示:set命令用法

文档注释

注释的插入

javadoc从以下几个特性中抽取信息

包公共类和接口公有的受保护的构造器和方法共有的和受保护的域 在每个文档注释之后都会紧跟自由文本(可以是html修饰符),标记由@开头

类注释

放在import语句之后和类之前

方法注释

除了通用标记之外,还可以使用下面几个标记 @param 变量描述 @return 返回值描述 @throws 方法可能抛出的异常描述

域描述

只需要对公有域进行描述(静态变量)

通用标记

下面这些标记可以用再类文档注释 @author @version 这两个默认是省略的,javadoc -version -author才会出来 @since 从哪个版本开始的新特性 @deprecated 对类和方法或者变量添加不再使用的表示,给出取代的意见 @see可以用于类也可用于方法,添加一个超链接

包和概述注释

javadoc -d 生成目标位置 包名(可以多个,用空格分开) 创建package.html文件,会抽取body内的信息 p143

类设计技巧

一定要保证数据私有一定要对数据初始化,对象实例域会自动初始化为默认值,但是局部变量不会进行初始化(不能使用未初始化的局部变量)在一个类中不要过多的使用基本类型,应该用一个类封装这些数据。并不是所有的状态都需要访问器和修改器,例如员工一旦确定就应该禁用修改入职日期的修改器将职责过多的类进行分解类名和方法名要有意义,类名的习惯是一个名词,前面有修饰名词的形容词或者动名词(ing)优先使用不可变类,LocalDate及java.time下的其他类都是不可变类,如果多个线程修改一个可变类(并发更改),结果是未知的。

第五章:继承(inheritance)

反射:程序运行期间发现更多的类及其属性的能力 java的继承都是公有继承,而没有c++的私有继承和保护继承

类,超类,子类

已存在的类:超类,基类,父类 新类:子类,派生类,孩子类

定义子类

通过超类扩展子类的时候,需要指出子类和超类的不同,因此在设计类的时候,应该讲通用的方法放到超类里面,而讲具有特殊用途的方法放到子类中

覆盖方法

子类会继承父类的所有方法,和所有的域(包括初始化块),但是不能访问父类那些私有的域和方法,需要借助父类的方法才能进行访问。 如果子类和父类的那个方法相同,则不能通过方法名再调用,这样会递归调用这个重写的方法,直到程序崩溃,所以要使用关键字super调用父类的方法。 注意:super和this不是一个相同的概念,不能将super赋值给一个对象变量,他只是一个指示编译器调用超类方法的特殊关键字。 继承只能增加或者重写方法或者域不能将其删除。

子类构造器

由于子类不能访问父类的私有域,所以必须借用父类的构造器对其进行初始化,可以在子类构造器的第一句通过super(参数列表)的形式调用父类的构造器。

如果子类的构造器没有显示调用父类的构造器,系统会自动调用父类空参的构造器。如果父类中没有空参的构造器,且没有显示的调用其他的构造器,则编译器会报错。

一个对象变量可以指示多种实际类型的现象被称为多态(polymorphism),在运行时能够自动选择调用哪个方法的现象被称为动态绑定(dynamic binding)

在java中,不需要将方法声明为虚拟方法,动态绑定是默认的处理方式。如果不希望一个方法有虚拟特征,则加上final关键字即可

this和super回顾

this

引用隐式参数访问该类的其他构造器

super 3. 调用超类方法 4. 调用超类构造器。 注意:调用构造器的语句应该放在第一句

继承层次

由一个超类派生出的所有子类的集合叫做继承层次(inheritance hierarchy),从某个特定的类到其祖先的路径被称为继承链(inheritance chain)

多态

有一个用来判断是否应该设计为继承关系的简单规则就是is-a原则,表明每个子类对象也是超类的对象。 is-a的另一种表述规则就是置换原则,可以将子类元素赋值给父类变量 注意:java的子类数组可以赋值给父类数组,但是运行的时候数组只能兼容自己的类型,否则会有ArrayStoreException异常 在这里做个小笔记:java中允许的继承体系对于赋值的时候会产生向上转型和向下转型的情况,向上转型总是安全的,即将子类对象赋值给父类对象,但是在一些情况下向下转型就会产生错误,即将父类对象赋值给子类对象,例如两个没有关系的父子关系转换就会产生错误

你可以这样理解,A是经理,B是员工,如果你将经理赋给员工,显然经理能胜任员工的职位(大多数情况是这样),所以可以兼容,但是将员工赋值给经理,显然他干不来经理的活,所以不能兼容。

数组也是同样的道理,将一个没有关系的(也就是把经理位置给了一个员工)赋值给子类,显然不能正确转换,所以会报数组元素异常。

理解方法调用

例如现在调用x.flag(param)方法

编译器找到隐式参数x对应的方法flag,但是可能会有方法重名,会列出所有的flag方法(包括父类中获取到的public修饰的方法)编译器参看调用方法的参数类型,如果存在一个名称相同且类型相同的方法就会选择这个方法,这个过程叫做重载解析。由于类型可以自动转换,例如int可以自动转换成double,参数中传递的是int类型,但是方法只有double的,依旧可以解析,同理,如果传参是子类,方法只有父类,一样可以解析,反之则不行 如果发现解析后有多个方法与之匹配就会报错。

方法的名字和参数列表叫做方法的签名,如果子类中定义了和父类相同签名的方法,那么就会覆盖父类的方法,由于返回值类型不是签名的一部分,一定要确定返回值类型的兼容性,将重写的方法返回值类型定义为原来的子类型,这样的叫做可协变的返回值类型

如果是private,static,final方法修饰的方法或者构造器,编译器知道具体调用哪个方法,这样的调用方式称为静态绑定, 与此相反的 调用的方法依赖于隐式参数的实际类型,并且在运行时实现动态绑定

采用动态调用的方式,总会在对象中挑选最合适的那个方法,(总是在最低子类开始找,然后往上),这样会造成很大的时间开销,因此,jvm为每个类预先建立了一张方法表,列出所有方法的签名和实际调用的方法,在真正调用方法时仅查表就可以了。(如果使用super关键字,就会在父类表中搜索)

如果调用e.getSalary()

jvm首先会根据e的具体类型的方法表进行提取,可能是自身类也可能是子类的方法表。然后jvm对定义getSalary签名的类进行搜索,然后就知道调用哪一个方法。最后调用这个方法】

动态绑定有一个重要特性:就是不需要修改现存代码,就可以对程序进行扩展(多态)一般使用接口进行中间转换,将子模块作为参数,这样修改的时候只需要将旧的模块替换就可以了。

在重写一个方法的时候,子类方法不能低于父类方法的可见性(需要>=)因为private在子类中不可见,虽然被继承了,但是看不到,所以也没法重写。

阻止继承:final类和方法

如果希望一个类被禁止继承,就用final修饰他(他其中的方法都自动是final修饰的。但是域则不是final修饰可以进行多次修改),当然如果希望一个方法不能被重写,同样的可以使用final修饰它,这样他就不能被重写了。 为了解决多态性带来的系统开销,早起的java使用final来修饰,使得不能产生多态,现在即时编译器的产生,如果一个方法没有被重写并且很短且经常被调用,那么就可以进行优化,这个过程叫做内联(inlining),例如调用e.getA,就会被转换成e.a域,这会很大程度的改善性能,但是如果被重写,编译器就不知道会做何等复杂操作,所以覆盖的方法不能进行内联

强制类型转换

对于基本类型的自动转化和向上转型,java提供的强制类型转换可以向下转型,但是对于继承链中从下而下的转换,如果并无关系则会报ClassCaseException,这时可以用instanceof关键字进行检测,该关键字可以检测出某对象是不是后面类的实例(子类也行) 如果类型转换失败后,java不会产生一个null对象,而是抛出一个异常。

抽象类

抽象类是继承链中对顶层一些通用方法的抽象,使得他们有更好的通用性。如果一个类中包含一个或多个抽象方法,则这个类必须被声明为abstract 抽象类不能被实例化抽象类中不但可以定义抽象类,还可以有具体的方法和数据。抽象类的实现由子类来实现

受保护的域

在java中提供protected来描述受保护的方法或者域,他允许子类和同一个包下的类使用该域。但是这有一个弊端,如果这个类进行修改,那么他就要通知所有使用这个类的程序员进行改动,这违背了oop的设计思想,Object.clone就是这样的一个方法

访问修饰符

private–>仅对本类可见 public—>对所有类可见 protected–>对子类和同一个包的可见 default–>默认,仅对同一个包中的可见。

Object类

这是所有类的超类 使用Object类型可以引用任何类型的对象

在java中只有基本类型不是对象所有的数组类型,不管是对象数组还是基本类型类型数组都扩展了Object类。

equals方法

Object中的equals方法只会比较两个对象是否是同一个,这并没什么意义。一般进行重写比较状态是否相等。 为了防止隐式参数为空的情况,可以调用Objects.equals(object a,object b)方法,如果两个都是空返回true,否则调用String.equals

getClass方法返回一个对象所属的类

相等检测和继承

父类 instanceof 子类返回的是false,也就是说父类不是子类的实例。 如果equals比较的不是同一个类,则java规范要求equals方法具有以下特点

自反性:x.equals(x)返回true对称性:x.equals(y)和y.equals(x)应该返回相同结果传递性:x.equals(y)和y.equals(z)应该和x.equals(z)是相同结果一致性:对于反复测试x.equals(y)结果应该是一样的对于非空引用x,则x.equals(null)返回false

对于一个良好的equals方法应该是下面这样 6. 显示参数命名为otherObject 7. 检测this == otherObject(优化语句,这样比计较域付出的代价小很多) 8. 检测otherObject 是否为null(返回false) 9. 比较this和otherObject是否是同一个类(如果equals在每个子类中都有所改变就用getClass比较)getClass!=otherObject.getClass 返回false,如果在子类中都拥有统一的语义,那么久使用instanceof检测 10.将otherObject转化成other(强转)。 10. 比较域

注意:如果重写方法时,参数不一样,就不是重写,可以用@Override检测

hashCode方法

String类重写了hashCode方法 默认的hashcode方法返回的是对象存储的地址(native底层实现) 注意:如果重写了equals方法,那就必须重新定义hashcode方法,已便将对象插入到散列表中

Objects.hashCode方法提供安全的hashCode方法,如果参数为null则会返回0,否则就会调用hashCode方法 Objects.hash(…)可以组合多个散列值

如果存在数组类型的域,就可以使用Arrays.hashCode计算散列码,这个散列码是由数组元素的散列码组成的。

toString方法

使用getClass().getName()可以使得子类也能调用父类的toString方法。 只要让对象名加上一个字符串就能自动调用他的toString方法 Object类的toString方法是类名加上@和十六进制的散列码

java.lang.getClass() java.lang.getSuperClass()

泛型数组列表

ArrayList类似数组,但是在删除和添加的时候能够自动调整数组容量。

声明数组列表

将new 和列表结合起来,赋值给一个变量,编译器会检查这个变量,并且将类型放到<>里面。 数组列表中管理着一个对象引用数组,如果这个数组的空间用完了,数组列表会自动创建一个更多的数组,并将所有的对象从较小数组拷贝到大数组中 add(E obj)总是返回true,在末尾添加元素obj size返回数组列表元素个数 ensureCapacity(int capacity)确保数组列表在不重新分配内部存储数组的情况下有足够的容量存储给定数量的元素。 trimToSize()将列表存储容量消减到元素个数。

访问数组列表元素

使用get方法获取到第i个元素 set(i,ele) 用lel替换i位置

add(int i,E ele)后移元素,将i位置放为ele E remove(int index)删除指定位置元素,返回被删除元素

自动包装器和自动拆箱

可以将自动装箱和拆箱应用到算术运算符,他会先进行拆箱,然后就行计算,再进行装箱。 数值类的包装类都继承自Number类。 自动装箱规范要求boolean,byte,char<=127,介于-128到127之间的short和int被包装到固定的对象,如果用==比较他们一定会相等。 装箱和拆箱是编译器进行的操作,在编译时会将这些操作变成实际的方法。

使用反射在运行时分析对象

程序运行时,像查看一个对象字段是很容易的事。而利用反射机制可以查看再编译时还不知道的对象字段。 通过一个类获取到字段,再将指定的字段给get方法,将返回这个字段的值。 而使用set方法,则可以将指定对象的值给覆盖 如果是访问私有字段,会抛出一个非法访问异常。 解决方法是使用setAccessible方法(注释的内容)。 这个方法是AccessibleObject类中的一个方法,他是Field,Method,Constructor类的超类,这个特性是为调试,持久存储和类似机制提供的。如果不允许访问,就会抛出上述异常。 访问可以被模块系统,安全管理器拒绝,但是安全管理器并不常用。

使用反射编写泛型数组

数组在创建的时候会记住存的类型,当存储一个不兼容的内容就会报错。 可以使用反射包提供的Array类动态复制 Array.newInstance(类型,长度)返回一个Object类型,就可以进行一些其他的操作了,比如用System.copyOf进行复制。 这样就会动态创建一个10元素的String数组。 Object类型是可以指向数组的

Class.getCoponentType可以得到Class的类型.

这是创建了一个二维的数组。

反射,调用任意方法和构造器

java的设计者说过:方法指针是很危险的,所以没有c++中的函数指针,取而代之的是接口和lamda表达式 我们可以使用Method类中的Invoke方法执行指定的方法。 由于方法可以重载,所以Class.getMethod方法可以指定参数 得到了指定的方法Method后可以调用Method的Invoke方法执行,里面有两个参数,实际对象和参数。 若访问的是静态方法,第一个参数可以为null. 提示:由于Invode方法参数和返回值都是Object,编译器就不会检查那么严格,而且这比直接调用方法慢得多,设计者推荐使用接口或者lamda表达式。

继承的设计技巧

公共操作和字段放到超类中

第六章:接口,lambda表达式和内部类

接口

接口的概念

java中,接口不是类,而是对洗完符合这个接口的类的一组需求。 接口中声明的所有方法,都是public方法。 接口没有实例,所以没有实例字段 可以定义常量(自动是public static final),而定义的常量会被实现的子类所获得。

使用接口的原因:java是一种强类型语言,在调用方法时,编译器要检查这个方法是否存在,若实现了某个接口,那么久一定实现了这个方法,而不用担心这个方法调用出错。

在使用Arrays.sort(Object[] a)方法进行排序时,传入的类一定是实现了Compareble接口,不然会报错 若某个类实现了Cloneable就可以使用Object中的clone方法,为类对象创建一个准确副本。

接口与抽象类

区别:一个类只能继承一个抽象类,但是可以继承多个接口,在编程角度来说,接口弥补了java单继承的缺点,降低了程序的复杂性。

静态和私有方法

java8中的方法只能是共有的,不能是private,但是在8之后可以使用private,但是必须实现方法体。

默认方法

default关键字可以在接口中进行方法实现,但是子类会重写这个方法。子类不会强制实现这个方法。

若一个接口有默认方法,子类实现接口,那么调用子类对象.默认方法,将调用接口.默认方法

解决默认方法冲突

若两个接口提供了相同的默认方法,编译器会强制要求类实现这个默认方法,。。 若一个接口中有默认方法而另一个接口只是声明了方法,同样会强制实现。

如果不是实现两个接口,只是一个类一个接口,那么会采用类优先的原则。因为这可以和java7有更好的兼容性

接口与回调

callback. 当发生某一时间时候执行某一动作叫做回调 JOptionPane.showMessageDialog(Component,ActionListener) Timer(int Interval,ActionListener) Tookit.getDefaultToolkit.beep()

Comparator接口

String类的compareTo方法可以被Arrays.sort方法调用,但是当我们不想用String类写的compareTo按字典排序,而是希望按长度排序,我们就可以使用Arrays.sort的第二种形式sort(Object[],Comparator),这里Comparator是一个接口,需要我们自己实现compare方法,这样就可以按照长度进行排序了。

对象克隆

protected clone方法默认是进行浅拷贝(只是复制这个对象的基本类型,对于对象的引用只是拷贝过来其他对象的引用) 子类只能调用受保护的clone方法来克隆它自己的对象。 Cloneable接口是java提供的少数标记接口之一,也称为记号接口,标记接口不包含任何方法,唯一的作用是允许在类型查询中使用instaceof

最新回复(0)