《Java编程思想》学习笔记(详细)
目录
01 对象导论
- 抽象过程:对象既有状态(属性),又有行为(方法),通常与具体实现的因此成为封装
- 每个对象都有一个接口(interface),决定接口的便是类型(class):
- 创建抽象数据类型是面向对象程序设计的基本概念之一
- 一个类实际上就是一个数据类型
- UML(Unified Modelling Language)
- 每个对象都提供服务:将对象作为服务提供者看待,程序向用户提供服务,通过调用其他对象提供的服务来实现,目标就是创建能够提供理想的服务的一系列对象
- 被隐藏的具体实现(访问控制):public(都可以访问),private(类内访问),protected(包内访问,包外子类可访问),默认包访问
- 复用具体实现:创建成员对象,进行任意组合
- 继承:
- 产生新类型:在子类中添加新方法或者重写父类方法,理想的继承是is-a的关系,新类型的继承是is-like-a的关系,新类与父类有相同的类型(新类可以向上转型为父类,父类却不可以向下转型为新类,如果本质上是一个新类,则可以向下转型,即使是这样,父类只知道子类中继承的原来的接口,对子类其他的东西一无所知),
- 强转类型时只能向上转,不是下面类型的向下转会出现ClassCastException
- 强转成父类对象后调用不到子类中新添加的方法属性等等,因为父类这个类型对子类类型一无所知,这时如果调用的是重写的方法,那么对象会根据自身的具体的实际类型恰当的执行代码,会调用子类的方法,这就是多态
- 伴随多态的可互换对象
- 子类对象当做父类对待时,那么对象调用方法时会根据自身的具体实际类型恰当的执行代码,Java默认是动态绑定(后期绑定,程序直到运行时才确定运行的代码),对于非OOP语言是前期绑定,编译时产生对具体函数名字的调用,运行时解析绝对地址执行
- 向上转型,向下转型
- 单根继承结构:除C++之外的所有OOP,所有的类都继承于单一的基类Object,可以确保所有的对象都属于同一类型,而C++无法保证(多继承),使得垃圾回收的实现容易很多,而垃圾回收也是Java相对于C++重要改进
- 容器
- 参数化类型:jdk1.5之前容器只能存储Object,因此必须向上转型,向上转型是安全的,但向下转型错误会导致ClassCastException,参数化类型是编译器自动定制存储对象的类型,是编译期间的作用,也叫泛型
- 对象的创建和生命周期:堆上动态创建,java采用动态内存分配,进行垃圾回收,C++追求执行速度,使用堆栈或静态区域实现,对象的存储空间和生命周期确定
- 异常处理
- 是与程序正常执行路径并行的,发生错误时执行的一条路径,不会干扰正常的执行代码
- 异常不能被忽略,保证一定会在某处得到处理,Java内置了异常处理,强制必须使用,否则编译出错
- 并发编程:并发,并行,线程,共享资源带来的隐患
- Java与Iinternet:客户端编程,服务端编程
- 总结:Java程序的两部分定义:表示问题空间的对象,发送给这些对象的用来表示在此空间内的行为的消息。Java中所做的全部工作就是定义类,创建类的对象,发送消息给这些对象(调用方法,获取属性(字段))
02 一切都是对象
尽管Java是基于C++的,但相比之下,Java是一种更纯粹的OOP语言(单根继承,垃圾回收)
- 用引用操纵对象
- Java中一切都被视为对象,操作的标识符实际上是对象的一个引用,引用可以单独存在,默认为null
- 使用new创建对象,存放在堆中,(寄存器,栈,堆,常量存储,非RAM持久化对象)
- 基本类型:不需要使用new,不是引用的自动变量,这个变量直接存储值
- boolean,char,byte,short,int,long,float,double,四类八种,都具有相应的包装类类型,可以在堆中创建一个非基本类型变量的对象来表示这个基本类型
- 高精度数字:包装类BigInteger,BigDecimal用于高精度计算,必须以方法调用的方式取代运算符使用,速度慢,以速度换取精度
- Java数组:会确保初始化,并且不能在范围外访问,是用数组上的少量内存开销及运行时的下标检查为代价的。创建一个数组对象时,实际上就创建了一个数组引用,默认为null
- 不需要销毁对象
- 作用域:由花括号决定,变量不可重复定义。Java对象不具备和基本类型一样的生命周期,但Java有垃圾回收器,用来监视new创建的所有对象
- 创建新的数据类型:类
- 关键字class
- 字段和方法:每个对象都有用来存储其字段的空间;使用句点调用字段和方法
- 成员默认值:变量作为类的成员会进行初始化,引用类型为null,boolean为false,其他数值为0,局部变量未初始化不可以使用,因为它有可能为任意值
- 方法、参数和返回值
- 方法的基本组成:方法名,参数,返回值和方法体。返回类型描述调用方法之后返回的值,参数列表给出要传给方法的参数类型和名称。方法名和参数唯一标识出某个方法,也称方法签名
- 参数列表:指定类型和名称,传递的实际上也是引用,对于基本类型是例外,但实际上传递的也是引用。return关键字返回正确类型的返回值或退出方法(返回类型为void)
- 构建一个Java程序
- 名字可见性:反过来使用自己的域名,小写。导入包使用import关键字,也可以同时导入多个,如import java.util.*; java.lang包会自动导入,其他的包必须声明import导入
- static关键字:声明静态方法或静态域(类方法,类数据,只有一份空间,非static字段对每个对象都有一份空间,静态方法是在不创建对象的情况下也能调用方法,如main()方法),不会与包含它的类的任何对象实例关联在一起,也即是未创建任何实例也可以调用,而非static方法和域必须用特定实例调用。静态域和方法一般使用类名引用,可以调用静态对象或方法
- 类名与文件名相同:必须存在某个类与文件名相同,且必须包含main()方法
public class ShowProperties {
public static void main(String[] args) {
System.getProperties().list(System.out); //获取系统属性,返回Properties,调用list
System.out.println(System.getProperty("user.name")); //使用key获取user.name
System.out.println(System.getProperty("java.library.path"));//获取java.library.path
}
}
- 注释和嵌入式文档
- 注释:单行注释//,多行注释/**/
- 注释文档:使用javadoc提取,以“/**”出现,三种类型的注释文档分别位于类,域,方法前
- 嵌入式HTML,使用一些标签格式化文档,如@since,@param
- 编码风格
- 类名大写,多个单词首字母大写,也称驼峰风格
- 方法,字段,对象名与类的风格相同,只是第一个字母小写
03 操作符
- Java操作符:几乎所有的操作符都只能操作基本类型,例外的“=”,“==”,“!=”能操作所有对象,“+”,“+=”支持String类
- 优先级:使用括号
- 赋值:基本类型是将值直接赋值给另外一个变量。而对对象赋值时,真正操作的是对象的引用,所以赋值是对引用的赋值,赋值之后两个变量都指向同一个对象,或者称同一个对象的不同引用。方法调用时也会有别名问题,会修改传入的对象
- 算术操作符:一元加号的作用:可以将较小类型的操作数提升为int
- 自动递增和递减
- 关系操作符:不适用于布尔类型,比较大小没意义。对于引用类型,==比较地址,比较内容使用equals,必须重写,否则使用Object的equals,默认比较地址
- 逻辑操作符:&&,||,!,只应用于布尔值,会短路
- 直接常量:0x表示十六进制,0表示八进制,L或l结尾表示long类型,F或f结尾表示float类型,D或d结尾表示double类型,Java默认int和double类型。指数计数e
- 按位操作符:&,|,^,~,表示按位与、或,异或,取反,只用于整数。含义与逻辑操作符类似,由于位是非常小的,所以按位操作符仅使用一个字符。将布尔类型看成一个单比特对待,除了按位取反也可以按“位”与、或、异或。按位操作符与逻辑操作符有相同的效果,但不会短路。对布尔值进行按位运算新增一个异或逻辑操作符
- 移位操作符:左移<<,低位补0,右移>>,正数补0,负数补1,无符号右移>>>,补0
- 三元操作符:boolean-exp ? value0 : value1,结果为true选value0,false选value1
- 字符串操作符+和+=,把所有连接的变量转换成字符串进行连接
- 类型转换:运算时,默认会将低精度的转化为高精度的类型。强制类型转换会造成精度损失
04 控制执行流程
- true,false,if-else
- while,do while,for,for中可以使用逗号操作符定义多个类型相同的变量,在外部定义变量时也可以
- foreach,增强for循环,用于迭代数组,容器,任何Iterable对象
- return,退出方法或返回值,break,continue
- goto,Java保留了goto关键字但未使用。标签,是后面有冒号的标识符,在使用break,continue时可以在其后面加上标签以结束或停止标签所指的循环,适用于所有循环,标签必须刚好在迭代之前。一般有循环嵌套时才使用标签
- switch,case,default,只适用于char,byte,short,int,String,enum类型,long,float,double都不行。没有break会继续执行后面的语句,内容都会执行,直到遇到break
05 初始化与清理
- 构造器:与类名相同,创建对象时会为对象分配空间并调用相应的构造器,确保使用之前已经初始化了。如果写的类没有构造器Java会自动创建一个无参构造器,如果定义了构造器,那么Java就不会提供任何构造器了
- 方法重载:根据参数类型列表区分。基本类型的重载:char会先自动提升为int,byte会先自动提升为short,int会先自动提升为float,long会先自动提升为float,float会先自动提升为double。
- 如果传入的数据类型(实际参数类型)小于方法中声明的形式参数类型,会自动提升。char会直接提升至int,其他的提升为存在的方法中最小的
- 如果传入的数据类型大于方法中声明的形式参数类型,需要强制类型转换
- this关键字:对象调用方法时,编译器自动把所操作对象的引用作为第一个参数传递个了方法,如果希望在方法内部获得对当前对象的引用,使用this关键字,表示对调用方法的那个对象的引用,只能在方法内部使用。
- 如果在方法内部调用同一个类的另一个方法,不必使用this,可以直接调用,当前方法的this引用会自动应用于同一类中的其他方法,因为是在同一个类中,也已经传入了这个对象的引用。当需要返回当前对象的引用时,通常使用return this,可以进行链式调用。为了将自身传递给外部方法时,必须使用this传递
- 构造器中使用构造器:使用this加参数列表,只能调用一次,且必须放在第一行
- static的含义:static就是没有this的方法,static方法内部不能调用非静态方法
- 清理:终结处理和垃圾回收
- 垃圾回收会首先调用finalize()方法,并在下一次垃圾回收时才会真正回收对象占用的内存。finalize()真正用途:在分配内存中有可能采用了类似C语言中的做法,而非Java中的通常做法,这种情况发生在本地方法下(目前只支持C和C++,而他们可以调用其他语言代码,实际上可以调用任何代码),在非Java代码中,也许会调用C的malloc()分配内存,而且除非调用了free()函数,否则空间无法释放,从而造成内存泄漏,free()函数是C和C++中的函数,所以需要在finalize()中用本地方法调用它
- 总而言之就是finalize()主要是用来使用本地方法清理非Java代码分配的空间
- 垃圾回收:引用计数,可达性分析,标记-清除算法,复制算法,标记-压缩算法
- 成员初始化:成员变量会默认初始化为标准0值,然后赋值,然后调用构造器
- 构造器初始化:类加载链接阶段的准备阶段会先默认初始化,初始化阶段会调用构造函数初始化,所以自动初始化是在构造器调用之前进行的,因此编译器不强制一定再某处初始化它们
- 静态数据的初始化:只有在必要时刻才进行,只有创建第一个对象或第一次访问静态数据时才会被初始化,之后静态对象不会被再次初始化。初始化顺序是先静态对象(如果未初始化过)再非静态对象。(构造器实际上也是静态方法,创建对象时都会调用),可以写成静态代码块
- 非静态实例初始化:使用大括号,在里面创建对象,跟不用好像没什么区别
- 数组初始化:是对数组的引用,有一个固有成员length,可以直接使用{}初始化,也可以在别处使用new初始化
- 可变参数列表:类型...,会解析成一个数组,包装类会自动拆箱装箱以匹配可变参数
- 枚举类型:enum关键字,只是为enum生成对应的类时产生某些编译器行为(每个enum生成一个对应的类,这个类只有一个对象,就是这个enum),可以很大程度上将enum当做类来处理。事实上,enum确实是类,具有自己的方法,它的基类是Enum,就像所有类的基类都是Object。enum有一个很实用的特性,可以在switch中使用
06 访问权限控制
权限修饰词,从大到小:public,protected,包访问权限,private
- 包:库单元,包内有一组类,它们在单一的名字空间下组织在一起
- 一个Java源文件,可以有一个public类,且必须与文件名相同,也可以有其他包访问权限的类(不可以是private和protected,内部类除外),它们主要是为主public类提供支持的,在包外看不见这些类。一个源文件内没有public类也可以,文件名随意命名
- 使用package关键字声明包,必须在文件注释以外的第一行。处在同一个目录下并且没有指定包的在同一个默认包内
- CLASSPATH必须指明jar文件,或.class所在的目录,添加.会查找当前目录
- 使用import导入类,import static导入静态方法或域
- Java访问权限修饰词:修饰属性和方法,主要涉及包外,包内,类内
- public,都可见
- protected,包内可见,包外继承可见
- 包访问权限,包内可见
- private,类内可见
- 接口和实现:访问权限控制是对实现的隐藏,把数据和方法包装进类中及具体实现的隐藏常共同被称为封装
- 类的访问权限:只能是public或包访问权限,内部类可以是protected或private
07 复用类(继承)
- 组合、继承、代理(创建一个private对象,再创建public方法调用private对象的方法)
- 初始化基类:当创建一个子类对象时,该对象包含了一个基类的子对象,该子对象与用基类创建的对象是一样的,二者的区别在于后者来自于外部,基类的子对象被包装在子类对象内部。子类在调用构造器之前必须完成基类的初始化(先初始化基类),如果没有默认构造器,必须使用super显式的调用基类的构造器
- 向上转型:新类是现有类的一种类型。新类向上转型后调用的方法是新类中的方法(因为本质上是新类的对象,动态绑定,只有在运行时才知道运行哪个方法)
- final关键字:
- final数据:编译时确定的常量,类加载中链接阶段已经确定了。static final定义的字段是一段不可改变的的内存空间。
- 空白final:final必须在定义或构造器中初始化,final基本类型,final引用
- final参数:final基本类型参数不可更改,final引用不可更改
- final方法:子类禁止重写,private方法都隐式的指定为了final
- final类:禁止继承,所以其中的方法都隐式的指定为final,但属性可以不是final
- final数据:编译时确定的常量,类加载中链接阶段已经确定了。static final定义的字段是一段不可改变的的内存空间。
-
初始化及类的加载:每个类只有在被初次使用时才会被加载,通常是创建第一个对象,当访问static域或static方法时也会被加载。每个类在加载时,加载、链接(验证、准备<静态变量初始化为标准0值>、解析<符号引用转化为直接引用>)、初始化(静态变量初始化,clinit()方法,按声明顺序初始化)。创建对象时,先确保类已经加载,再分配空间,初始化为标准0值,再赋值,再调用构造器,如果有父类先加载父类,调用构造器时也先调用父类构造器
08 多态
封装:通过合并特征和行为创建新的数据类型,将实现细节进行私有化把接口与实现分离
继承:允许将视为它自己本身的类型或者其基类型来处理
多态(动态绑定):方法调用允许一种类型表现出相似类型之间的区别,只要它们有同一基类
- 向上转型:子类向上转型为基类后,接口会变小,但不会比基类接口更窄(继承就有了基类所有的接口(方法,属性,所以说创建一个子类对象也会创建一个基类子对象))
- Java中处理static和final方法,其他的都是后期绑定,可以对基类进行扩展,其他的子类可以正常运行,同时也具有了扩展的功能。多态是将改变的事物与未变的事务分离开的技术
- 只有重写的方法才会有多态,否则还是会调用父类的方法,比如父类私有方法,子类重名的
- 子类与父类相同的属性不具有多态,任何域的访问操作由编译器解析,因此不是多态的
- 子类与父类相同的静态方法不具有多态,谁调是谁的,一般也是使用类调
- 构造器:也是静态方法(隐含的static)
- 构造器的调用顺序:有父类先调用父类,调用构造器之前先初始化成员变量,再执行构造器主体。在调用父类构造器之前,子类对象都是标准0值,没有父类时,先初始化变量,再执行构造器主体。也就是extend关键字先起作用
- 继承与清理:销毁的顺序应该与初始化的顺序相反(可能子对象依赖于基类对象),对于字段,与声明顺序相反;对于基类,应先清理子类,再清理基类
- 构造器内部的多态行为:子类重写父类的方法,父类构造器中又调用了这个方法,那么在创建子类对象的时候,父类的构造器中调用的是子类中动态绑定的方法,这时子类对象并没有完成初始化,成员是标准0值。所以编写构造器时有一条有效的准则:用尽可能简单的方法让对象进入正常状态,如果可以的话避免调用其他方法。唯一可以调用的是基类的final方法,因为它们不会被覆盖
- 协变返回类型:重写方法的返回值可以是基类的更具体的类型
- 用继承进行设计:
- 不能在运行期间决定继承不同的对象,因为在编译期间完全确定,但是可以将不同类型的对象向上转型为基类,调用方法换成不同的对象。通用的准则是:用继承表达行为间的差异,用字段表达状态上的变化
- 向下转型时不安全,会导致运行时异常ClassCastException
09 接口
接口和内部类为我们提供了一种将接口与实现分离的更加结构化的方法
- 包含抽象方法的类叫做抽象类,abstruct关键字
- interface关键字定义接口,表示所有实现了该接口的类看起来都像这样,允许创建者确定方法,参数列表,返回类型,但没有任何方法体,只提供形式不提供实现。接口被用来建立类与类之间的协议,它不仅是极度抽象的类,因为它允许人们通过创建一个能够被向上转型为多种基类的类型,来实现类似多重继承的特性(Java不支持多继承),将class替换成interface
- 接口可以包含域,隐式的是static和final的,使用接口直接调用,JDK5之后使用enum
- 接口中的方法默认都是public的
- 接口中可以由默认实现方法,使用default
- 完全解耦:策略设计模式,适配器模式
- Java的多重继承:
- 实现多个接口:接口中的默认方法与继承的同名方法会调用继承的方法。接口中不允许有相同的默认实现方法。接口中的方法必须实现,不管别的接口有没有默认实现,且会调用。继承和实现中不能通过返回值不同区别方法
- 接口也可以继承:扩展接口,当做类的继承使用
- 可以在任何现有类上添加新的接口,让方法接受接口类型,就可以实现让任何类对某个方法进行适配,因为实现了接口它就可以转型为这个接口类型,比如接收Readable接口类型的Scanner类
- 嵌套接口:
- 类中可以嵌套接口,可以是private,只能被内部类实现,且对象不能在类外部向上转型,在类外部获得的对象只能被其他能接受这种类型的方法所用
- 接口中也可以嵌套接口,嵌套接口不能是private,遵循所有的接口规则
- 实现接口时,不需要实现内嵌的接口
- 接口与工厂:工厂方法设计模式,创建一个类都可以替换为创建一个接口和一个工厂
10 内部类
允许把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性
- 如果从外部类的非静态方法之外的任意位置创建某个内部类的对象,指明这个对象的类型使用,OuterClassName.InnerClassName,静态方法不能创建内部类对象,也就是不能没有外部类的对象就有了内部类的对象,内部类的对象必须依托于外部类的对象
- 内部类自动拥有外部类所有成员的访问权,就像它也能拿到this,怎么做到的?因为创建内部类对象的时候需要先有一个外部类对象,这个内部类对象会拿到这个外部类对象的引用,通过这个引用就可以访问所有外部类成员
- 如果需要内部类返回外部类对象的引用,使用外部类名字.this,返回自己使用this,这个在编译期就知道
- 如果需要在外部创建某个外部类的对象,需要拿着这个外部类对象创建outerClassObject.new InnerClass(),如果这个内部类是静态内部类(嵌套类),则不需要使用外部类对象
- 在方法和作用域内的内部类:局部内部类
- 匿名子类对象(接口,抽象类),普通类也可以有匿名子类,把基类当做接口使用
- 嵌套类:不需要内部类对象与外部类对象有关系,可以将内部类声明为static。普通内部类的静态方法和属性只能放在外部层次上,嵌套类可以都有,嵌套类类似于一个static方法,普通内部类类似普通方法
- 接口内部类,默认是public和static的,甚至可以实现外部接口
- 多层内部类中可以任意访问外部类成员
- 内部类不可以有嵌套类
- 为什么需要内部类:
- 有效的实现多继承(如果是抽象的类或具体的类必须使用内部类才能实现多重继承)
- 内部类可以有多个实例,每个实例都有自己的状态信息,与外围对象信息相互独立
- 在单个外围类中,可以让多个内部类以不同方式实现同一个接口或继承一个类
- 创建内部类对象时并不依赖于外部类的创建
- 闭包与回调:对于父类中已有的接口中的实现,只能其子类中以内部类的形式单独实现接口,以避免覆盖父类中的与接口中同名的方法,通过回调形式返回内部类对象的引用
- 内部类与控制框架:控制框架中有很多使用内部类的例子,如应用程序框架
- 内部类的继承:
- 直接继承某个类的内部类时,需要在构造器中传入继承的内部类的外部类的对象引用x,使用x.super()初始化外部类对象
- 外部类的继承不会覆盖内部类,内部类是完全独立的实体,各自在自己的命名空间内,明确的继承是可以的
- 局部内部类:代码块内创建的类,也可以使用匿名内部类,区别:局部内部类可以提供已命名的构造器,或者需要重载的构造器,而匿名内部类只能实例初始化;此外,需要不止一个内部类对象时必须使用局部内部类
- 内部类标识符:每个类都会产生一个.class文件,包含所有创建该类型对象的全部信息,会产生一个Class对象,内部类使用外部类加$加内部类名表示,匿名内部类简单的使用$加一个数字表示
11 持有对象
- 泛型和类型安全的容器:使用泛型,可以在编译期间防止将错误类型的对象放入容器中,取出时也不再需要类型转换。编译之后是没有泛型的,是编译期间的检查
- 基本概念:Collection(List,Set,Queue),Map
- 添加一组元素:
- Arrays.asList()(显式类型参数说明Arrays.<Type>asList()),使用的是Arrays的内部类,解析数组时必须是对象数组,基本类型数组会解析成一个对象
- Collections.addAll()
- List:ArrayList:add(),get(),contains(),indexOf(),remove(),subList(),containsAll(),retainAll(),removeAll(),addAll(),clear(),toArray()
- LinkedList,具有比ArrayList多的接口
- 迭代器(也是一种设计模式):将遍历与底层结构分离,
- iterator(),next(),hasNext(),remove()
- listIteraotr(),可以双向移动,next(),previous(),nextIndex(),previousIndex(),hasNext(),hasPrevious()
- Collection和Iterator:Collection是所有描述序列容器的共性的根接口,java.util.AbstractCollection提供了默认实现,那么可以创建它的子类型,或者使用实现Collection接口的方式(都需要提供iterator(),size()方法)
- 所有的Collection都可以使用foreach语法,数组也可以,但是数组不能传递给Iterable接口,必须手动转换
- Set:TreeSet,HashSet,LinkedHashSet
- Map:HashMap,TreeMap,LinkHashMap
- Queue,LinkedList实现了Queue接口。PriorityQueue
- 新程序不应使用过时的Vector,Hashtable,Stack(继承自Vector,可以使用LinkedList实现)
12 通过异常处理错误
异常:当前环境无法获取必要的信息来解决问题,从当前环境中跳出,把问题交给上一级环境
- 所有标准异常类都有两个构造器,一般异常只需要知道类型就可以了
- throw能够抛出任意类型的Throwable对象,是异常的根类;
- e.printStackTrack()方法打印从调用到抛出的方法调用序列的反序列,最上面是抛出的地方,默认是标准错误流
- 处理异常:try-catch-finally,不处理就throws抛给上一级(调用者)处理
- catch里throw异常的时候打印原来的异常,调用fillInStackTrace()会将此作为新的异常发生地,也即是把原来的StackTrack给覆盖掉了
- Java标准异常,Throwable是任何可以作为异常抛出的类
- Error(编译时和系统错误,一般不关心,Java虚拟机报告系统错误)
- Exception是可以被抛出的基本类型,Java类库,方法及运行时故障都可能抛出,所以只需关心的基类型是Exception
- RuntimeException,不需要在方法处进行异常声明,程序退出之前调用printStackTrack()
- finally,会在跳至更高一级异常处理程序之前执行,在return之前也会执行
- finally里抛出了其他异常那么try里面没有被捕获的异常将会丢失
- finally里使用return将会丢失所有异常
- 异常的限制:
- 子类构造器中的异常可以throws多于父类构造器的异常,但必须包含父类构造器的异常说明,派生类构造器不能捕获基类构造器抛出的异常,其他方法不必须包含,但也不能有父类没有声明的异常
- 子类重写方法不可以throws父类没有声明的异常,可以不抛出异常,也即是小于等于父类的,异常限制对构造器不起作用,继承时,异常说明的接口变小了
- 派生类异常对象也可以有基类捕捉到
13 字符串
- 字符串String是不可变的
- 重载运算符+,+=,StringBuilder
- 重写toString时使用this会造成递归调用,应该使用super.this
- 格式化输出:System.out.printf(),类似C语言,使用占位符,等价于format()
- Formatter类:需要向构造器传递一些信息,告诉它将最终结果传递到哪里(比如System.out),有一个format()方法,等价于printf(),类型转换字符:\d\c\b\s\f\e\x\h\%
- String.format(),接收与Formatter.format()一样的参数,返回一个String,内部也是创建Formatter对象
- 正则表达式:以某种方式描述字符串,Java中\\表示插入一个正则表达式的\,其后面的字符具有特殊的意义
- |,表示或
- (),表示一个捕获组
- ?,表示可能有一个或多个,+,表示一个或多个
- \\w,表示一个单词字符,\\W表示一个非单词字符
- 字符类、逻辑操作符、边界匹配符、量词(贪婪、非贪婪、占有)
- Pattern类及matches()方法
- 扫描输入Scanner类:Scanner定界符;使用正在表达式扫描
14 类型信息
运行时类型信息可以在程序运行时发现和使用类型信息,RTTI:运行时类型检查,识别一个对象的类型,使用RTTI可以查询某个Shape所指向的对象的确切类型(子类)
- 每编写一个类都有一个Class对象,保存在.class文件中,Class对象表示对象类型的对象,所有Class对象都属于Class类,
- .forName()是Class类的一个静态方法,需要try,NotFoundClassException
- 也可以使用类.class获得Class对象的引用,更简单,更安全,编译时检查,不需要try,不会自动初始化该Class对象,比如static域(static final的编译期常量不需要进行初始化就可以读,static final引用需要初始化,如果不是final的,那么必须先进行链接<分配空间>和初始化),初始化实现了尽可能的惰性
- 可以通过泛型对Class对象的引用作限定,即使它表示的就是所指向对象的确切类型,通配符Class<?>优于平凡的Class,表示你就是选择了非具体的版本;
- extends,Class<? extends Xxx> Xxx及其子类型都可以赋值;
- super,Class<? super Xxx>,对于泛型化的Class引用调用getSuperClass(),必须使用Class<? super Xxx>,表示某个类的Class引用,是这个类的超类,使用它创建对象只会产生Object对象,是模糊的,而泛型化的Class引用创建对象时是具体的某个类的对象
- 类型转换前先做检查,RTTI在Java中的三种形式
- 强制类型转换
- 使用类对象(Class对象)
- instanceof
- instanceof与Class比较的等价性:instanceof保持类型的概念,表示的是是这个类或是这个类的子类,而使用Class对象的==或equals比较,则必须是这个确切的类型或者不是
- 反射获取运行时的类型信息,java.lang.reflect提供了对反射的支持,包含Field类、Method类、Constructor类(都实现了Member接口),这些类型的对象是JVM在运行时创建的,用来表示未知类里对于的成员
- 动态代理
15 泛型
- 泛型类、泛型接口、泛型方法(将泛型参数置于返回值之前)
- 类型推断只对赋值操作有效;方法中的显式类型说明,在点之后使用尖括号指明
- Java泛型使用擦除实现,参数类型在运行时只是作为参数占位符的标识符,在运行时都会被擦除为原生类型;
- 可以通过给定泛型的边界来让泛型对象进行调用;泛型类型只有在静态类型检查期间才出现,在此之后,所有的泛型类型都会被擦除,替换为它们的非泛型上界,普通的类型变量未指定边界时擦除为Object
- 在泛型中创建数组使用Array.newInstance(),不能使用new T[size],可以使用new Object[size]然后转型为(T[] new Object[size]),但实际运作的还是Object[],如果传入Class<T>那么就可以从擦除中恢复,获取确切的类型,否则都是Object[]
- 使用泛型创建实例(通过反射的方式),必须有默认的构造器
- 边界:可以在用于泛型的参数类型上设置限制条件,尽管强制规定了泛型可以应用的类型,但是潜在的更重要的效果是可以按照自己的边界类型来调用方法
- 通配符:
- 一旦使用<? extends Xxx>这种类型的向上转型,就丢失了向其中传递任何对象的能力
- 超类型通配符<? super Xxx>,或者<? super T>,逆变,可以安全的传递一个类型对象
- 无界通配符<?>
- 问题:
- 基本类型不能作为泛型参数,但可以使用包装类
- 一个类不能实现同一个泛型接口的两种变体
- 由于擦除的原因,重载方法将产生相同的类型签名
- 异常:catch语句不能捕获泛型类型的异常,因为编译器和运行时都必须知道异常的确切类型,泛型类也不能直接或间接的继承自Throwable(进一步阻止定义不能捕获的泛型异常)
16 数组
- 基本类型的数组存储基本类型数据的值,对象数组存储引用;聚焦初始化,动态初始化
- 数组中的每个向量都可以具有任意的长度(被称为粗糙数组)
- 不能创建泛型数组(数组必须知道所持有的确切类型),但可以创建泛型数组引用(可以进行编译器检查),然后对创建的非泛型数组进行转型
- 创建测试数据:Arrays.fill(),数据生成器
- Arrays实用功能
- equals(), deepEquals(), fill(), sort(), binarySearch(), toString(), hashCode(), Arrays.asList()
- 复制数组:System.arraycopy()
- 数组比较:equals, deepEquals()
- 元素比较:实现Comparable或Comparator接口
- 数组排序:Arrays.sort(),元素实现Comparable或Comparator接口
- 对已排序数组查找:Arrays.binarySearch()
17 容器深入研究
- Collections的一些静态方法:nCopies(), fill()
- Collection的功能方法,与Set相同,List有自己的新方法
- List的功能方法
- Set和存储顺序
- 队列,优先级队列,双向队列
- Map,HashMap,TreeMap,LinkedHashMap,WeakHashMap,ConcurrentHashMap,IdentityHashMap
- 持有引用:强引用,软引用,弱引用,虚引用
- Java 1.0/1.1容器:Vector, Enumeration. Hashtable, Stack, BitSet,
18 Java I/O系统
- File类
- 输入输出:
- InputStream, Reader
- OutputStream, Writer
- 标准IO
- nio
- 压缩
- 对象序列化,实现Serializable接口,transient关键字
- XML
- Preferences
19 枚举类型
- 功能方法:
- 普通方法:ordinal(), equals(), hashCode(), comparTo(), getDeclaringClass()
- 静态方法:
- Enum.valueOf(),根据给定名字返回对应实例
- Xxx.values(),返回实例数组,是编译器自动添加的static方法,向上转型后不可用,可以使用反射使用getEnumConstants()获取
- 添加新方法:枚举类可以有自己的描述,如果有方法,必须在最后一个枚举对象后加分号;只能在enum定义的内部使用构造器创建enum实例,一旦定义结束,编译器就不允许再使用构造器创建任何实例了
- 用在switch语句里:case里不必使用enum类型修饰一个enum实例
- 不可继承:编译器将enum标记为final类,所以无法继承enum,但可以实现接口
- 随机选取:
- 接口中定义枚举:接口中定义多个枚举类都实现这个接口,使其都具有这个接口类型
- 枚举中使用接口中定义的枚举:枚举中使用反射获取其他的枚举
- 枚举中定义接口,接口中再定义枚举
- EnumSet,元素来自一个enum,使用一个long值作为比特向量,说明一个二进制位是否存在时非常快速高效
- EnumMap,key来自一个enum
- 常量相关的方法:允许为enum实例编写方法,从而为每个enum实例赋予各自不同的行为。需要为enum定义一个或多个abstract方法,然后为每个实例实现该抽象方法
20 注解
注解,也被称为元数据
- 基本语法,类似接口的定义,@interface
- java.lang中的注解:@Override, @Deprecated, @SuppressWarnings
- 元注解:四种用于新注解的创建,注解其他的注解
- @Target,定义注解应用到什么地方,ElementType参数:构造器,域,局部变量,方法,包,参数,类/接口/注解/enum
- @Retention,定义注解在什么级别保存注解信息,RetentionType参数:源代码,类文件或运行时
- @Documented,将注解包含在Javadoc中
- @Inherited,允许子类继承父类的注解
- 注解处理器:如果没有用来读取注解的工具,那么注解不会比注释更有用
- 注解元素:所有基本类型,String,Class,enum,Annotation,以上类型数组;注解也可以作为元素的类型,也即是注解可以嵌套,在注解中使用注解
- 默认值限制:元素不能有不确定的值,要么有默认值,要么在使用时提供值
- 使用apt处理注解
- 基于注解的单元测试
21 并发
- 基本的线程机制
- 实现Runnable接口
- 使用Thread类
- 使用Executor,使用线程池的execute()提交
- 实现Callable接口,有返回值,使用线程池的submit()方法提交
- 休眠,Thread.sleep(),TimeUnit.MILLSECTIONS.sleep()
- 优先级,t.setPriority()
- 让步,Thread.yield(),表示可以让出CPU让其他线程执行
- 守护线程,t.setDaemon(True),它创建的线程都是守护线程
- 加入一个线程,t.join(),原来的线程挂起,直到这个线程执行完毕
- 共享资源
- synchronized同步方法,同步代码块,对象锁,类锁
- Lock对象
- volatile,保证可见性,有序性
- 原子类
- ThreadLocal类
- 终结任务
- 线程状态,新建,就绪,阻塞,死亡,挂起
- 线程之间的通信
- wait(),notify()/notifyAll()
- BlockingQueue
- 死锁,只要破坏其中一个就可以防止死锁,一般破坏第四个
- 互斥条件,任务使用的资源中至少有一个是不能共享的
- 至少有一个任务必须持有一个资源并等待另一个资源释放
- 资源不能被任务抢占
- 必须循环等待
- 新类库中的构件
- CountDownLatch
- CyclicBarrier
- DelayQueue
- PriorityBlockingQueue
- ScheduledExecutor
- Semaphore
- Exchanger
- 性能调优
- 乐观锁
本文来自博客园,作者:Bingmous,转载请注明原文链接:https://www.cnblogs.com/bingmous/p/15643695.html