类与对象
1. 访问器方法
只访问对象而不修改对象的方法有时称为"访问器方法",而修改对象的方法称为"更改器方法"
2. 构造器
2.1 构造器与类同名
2.2 每个类可以有一个以上的构造器
2.3 构造器可以有0个、1个或者多个参数
2.4 构造器没有返回值
2.5 构造器总是伴随new操作符一起调用
3. 类方法的隐式参数和显式参数
隐式参数:当前类对象,也就是this
显式参数:方法括号中的参数
4. 方法参数使用情况
4.1 一个方法不能修改一个基本数据类型的参数(即数值型或者布尔值)
4.2 一个方法可以改变要给对象参数的状态
4.3 一个方法不能让对象参数引用一个新的对象(想通过传入对象来改变对象的引用是不可的!)
5. 重载
有相同的方法名字,但不同参数,就叫"重载"(overloading)
编译器会挑选出具体执行哪个方法,它通过用各个方法给出的参数类型与特定方法调用所使用的值类型进行匹配来挑选出相应的方法。若匹配不到参数,就会报编译错误,这个过程被称为"重载解析"(overloading resolution)
6. 域初始化
6.1 默认域(若在构造器中没有显式的给域赋值初始,那么会自动赋值为默认值,比如int为0,double为0.0,布尔为false,对象引用为null)
6.2 无参数构造器
(若一个类没有写构造器,则系统会提供一个无参数构造器)
(若提供了一个构造器含参数,则在构造对象时必须传入参数否则报错)
(若想在构造对象时不传入参数,则需要重载一个无参数的构造器)
6.3 显式域初始化
6.3.1 在类定义中,直接将值赋值给域,比如
class Test{
private int d = 12;
}
6.3.2 初始不一定是常量值,也可以调用方法对域进行初始化
class Test{
private int id = getId();
}
6.4 同类调用其他构造器
可以使用this来调用其他构造器,这样就可以抽出一个公共构造器,然后再按需调用其他构造器。
7. 初始化块(初始化数据域方式)
7.1 在构造器中设置值
7.2 在声明定义中赋值
7.3 初始化块
在一个类的声明中,可以包含多个代码块,只要构造类的对象,这些块就会被执行。如:
class Test{
private static int nextId;
private int id;
// 初始化块
{
id = nextId;
nextId++;
}
// 针对静态域进行静态代码块初始值
static
{
Random x = new Random();
nextId = x.nextInt(100);
}
}
继承
1. 关键字
使用"extends"表明正在构造新类派生于一个已存在得类。
已存在的类称为"超类(superclass)"、"基类(base class)"、"父类(parent class)"
而新类称为"子类(subclass)"、"派生类(derived class)"、"孩子类(child class)"
2. super
2.1 当我们在子类中想调用父类中的方法时,需要使用super.xxx()来调用。(但是不能认为跟this是引用类似的概念,因为super不是一个对象的引用,不能将super赋值给另一个对象变量,"它只是一个指示编译器调用父类的特殊关键字")
2.2 也可以使用super来调用超类的构造器(但必须出现在第一行)
类比this关键字:
2.3 引用隐式参数
2.4 调用该类的其他构造器
类比super关键字:
2.5 调用超类的方法
2.6 调用超类的构造器
3. 继承层次
由一个公共超类派生出来的所有类的集合被称为"继承层次(inheritance hierarchy)";从某个特定的类到其祖先的路径被称为该类的"继承链(inheritance chain)"
Java不支持多继承
4. 多态
在Java中,对象变量是"多态的",一个XXX变量既可以引用一个XXX类对象,也可以引用一个XXX类的任何一个"子类"的对象
5. 阻止继承:final类和方法
final类:阻止人们利用某个类定义子类,可以使用final关键字,则不允许扩展的类被称为final类。如:
public final class Test{}
定义为"final类"后,该类中的所有方法自动成为final方法,但不包括域。
final方法:类中的特定方法也可以被声明为final,这样之后,子类就不能覆盖这个方法。
6. 抽象类
6.1 当层次比较高的类,如祖先类,我们只将其作为派生其他类的基类,而不作为想使用的特定的实例类时,我们可以将其定义为"抽象类",当我们提供的一个公共方法,并不知道其到底会有什么操作时,可以将其定义为"抽象方法",当包含一个或者多个抽象方法的类本身必须被声明为抽象,使用关键字"abstruct"。
6.2 抽象类同样可以包含具体的数据和具体的方法,建议将通用的域和方法(不管是否是抽象)放在超类(不管是否为抽象类)中。
6.3 抽象类是不能被实例化的。
6.4 抽象类中的抽象方法没有方法体。
6.5 抽象类的子类必须给出父类中的抽象方法的具体实现,除非该类也是抽象类。
7. 可见性的4个访问修饰符
7.1 仅对本类可见 - "private"
7.2 对所有类可见 - "public"
7.3 对本包和所有子类可见 - "protected"
7.4 对本包可见 - "默认",不需要修饰符
8. Object(所有类的超类)
Java中每个类都是扩展Object类来的;
若没有明确指出超类则,认为Object是这个类的超类;
Object类型的变量可以引用任何类型的对象;
Java中只有"基本类型"不是对象,其他均是扩展Object类
9. 自动装箱与拆箱
9.1 基本类型可以转换为对象,所有的基本类型都有一个与之对应的类。比如int基本类型对应Integer类等,这些类称为"包装器(wrapper)"
9.2 自动装箱就是Java自动将"原始类型值"转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做"装箱",反之将Integer对象转换成int类型值,这个过程叫做"拆箱"
9.3 "装箱":编译器将调用包装类对象的valueOf方法将原始类型值转换成对象
"拆箱":编译器调用包装类对象的intValue或者doubleValue方法将对象转换成原始类型值
9.4 关于缓存
Java中的8种基本类型种,除了double和float的自动装箱代码没有使用缓存,每次都是new新的对象,其他6种基本类型都使用了缓存策略
缓存是有一定范围的,如下图,值在这个范围内的数值都是返回缓存,超过后会重新创建对象,比如下面这个:
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
Long f = 128;
int e = 128;
a == b // true 范围内 返回同一个
c == d // false 超过127了 返回新对象 对象==比较的地址 即false
d == e // true 拆箱操作比较的是数值 即true
10. 参数数量可变
jdk1.5后推出了省略号...来接收任意数量的对象,js是在es6才推出叫做展开运算符,提到这里来对比下重载、重写、不定参的区别:
10.1 "重载"
首先作用范围,它是在"同一个类中",只要"形参列表不同"而方法名称相同,则称为方法的重载
10.1.1 同一个类中方法名相同
10.1.2 参数列表不同(至于其他部分,比如"返回值类型"、"修饰符"则与重载没有关系)
10.2 "不定参"
首先作用范围,它是定义一个"方法"时,在最后一个形参的类型后增加三个点(...),则表明该形参可以接受多个参数值,注意:获取到的可变参数变量是一个"数组",且不定形参只能处于形参列表的最后一个,且最多只能有个一不定参数。
10.3 "重写"
首先作用范围,它是体现在"子类中包含有父类同名方法的现象"就被称为重写。
10.3.1 方法名相同,形参列表相同
10.3.2 子类返回值类型应比父类方法返回值类型更小或者相等
10.3.3 子类方法声明抛出的异常类应比父类抛出的异常类更小或者相等
10.3.4 子类方法的访问权限应比父类方法的访问权限更大或者相等
10.3.5 覆盖的方法和被覆盖的要么都是类方法要么都是实例方法,不能一个是类方法一个是实例方法
接口
1. 接口特性:
1.1 接口不是类,而是对类的一组需求描述,这些类要遵从接口描述的统一格式进行定义
1.2 接口中的所有方法自动属于public,因此在接口声明方法时,不必提供关键字public,但是在实现接口时,实现类中必须将方法声明为public
1.3 接口不是类,不能使用new运算符实例化一个接口;
可以声明接口的变量,如:InterfaceX xxx,但接口变量必须引用实现了接口的类对象;
可以使用instance检查一个对象是否实现了某个特定的接口;
接口可以被其他接口进行扩展,如:
public Interface1 extends Interface2{};
接口中不能包含实例域或者静态方法,但却可以包含常量,且接口中的域自动被设为public static final;
Java中类只能继承一个父类,但是可以实现多个接口,如:class Test implements Interface1, Interface2{};
"抽象类与接口的区别:单说功能上,抽象类也能模拟接口,但是因为Java的单继承,使得一个类只能有一个父类,导致不能多样性,因此才出现接口的多实现来扩展功能"
2. 静态方法
在Java SE8中,允许在接口中增加静态方法,如:
public interface Path{
public static Path get(String first,String... more){
return FileSystems.getDefault().getPath(first,more);
}
}
3. 默认方法
可以为接口方法提供一个默认实现,必须用"default"修饰符标记这样一个方法。
4. 解决默认方法冲突
若在一个接口中将一个方法定义为默认方法,然后又在超类或者另一个接口中定义了同样的方法时:
4.1 "超类优先":若超类提供一个具体方法,同名且有相同参数类型的默认方法会被忽略。
4.2 "接口冲突":若一个超接口提供了一个默认方法,另一个接口提供了一个同名且参数类型(不论是否默认参数)相同的方法,必须覆盖这个方法来解决冲突。
lambda
1. 形式
参数,箭头(->)以及一个表达式;若逻辑无法放入一个表达式中,就可以像写方法一样,把这些代码放在{}中,并包含显式的return语句
类比js: "箭头函数"
2. 特性
若没有参数,仍要提供空括号,就像无参方法一样;
如果可以推导出一个lambda表达式的参数类型,则可以忽略其类型;
若方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至可以省略小括号;
若lambda表达式只在分支返回一个值,而在另外一些分支不返回值,则这是不合法的。
3. 方法引用
可以使用::操作复分割方法名与对象或类名:
object::instanceMethod
Class::staticMethod
Class::instanceMethod
super::instaceMethod
Timer t = new Timer(1000, event -> System.out.println(event)) 替换成:
Timer t = new Timer(1000, System.out::println)
(x,y) -> Math.pow(x,y) 替换成 Math::pow
(x,y) -> x.compareToIgnoreCase(y) 替换成 String::compareToIgnoreCase
4. 构造器引用
构造器引用与方法引用类似,只不过方法名为new,比如Person::new是Person构造器的一个引用。
5. 变量作用域
lambda表达式有3个部分:
5.1 一个代码块
5.2 参数
5.3 自由变量的值,这里指非参数而且不在代码中定义的变量
利用闭包特性,lambda表达式中可以捕获外围作用域中的变量的值,但是是有限制的:
5.1 只能引用值不会改变的变量必须是实际上的最终变量
5.2 lambda表达式同样不能有同名的局部变量
5.3 lambda表达式中的this关键字,是指向创建这个lambda表达式的方法的this参数
内部类
1. 定义
内部类(inner class)是定义在另一个类中的类,为什么使用,主要原因如下:
1.1 内部类方法可以访问该类定义所在的作用域中的数据,包括私有的数据
1.2 内部类可以对同一个包中的其他类隐藏起来
1.3 当想定义一个会第哦啊函数且不想编写大量代码时,使用匿名内部类比较便捷
异常
1. 所有的异常都是由Throwable继承下来的,下层分为两个分支:Error和Exception
1.1 Error
Error类层次结构描述了Java运行时系统的"内部错误"和"资源耗尽错误"
1.2 Exception
1.2.1 派生于"RuntimeException"(由程序错误导致的异常)
"错误的类型转换"
"数组访问越界"
"访问null指针"
1.2.2 其他异常(如I/O错误)
"试图在文件尾部后面读取数据"
"试图打开一个不存在的文件"
"试图根据给定的字符串查找Class对象,而这个字符串标识的类并不存在"
Java规范中将
非受查异常(unchecked): "Error类"和"RuntimeException类"
受查异常(checked): "其他异常"
2. 如何声明受查异常
若遇到了无法处理的情况,那么Java的方法可以抛出一个异常:不仅需要告诉编译器将返回什么值,"还要告诉编译器有可能发生什么错误",方法应该在其首部声明所有可能抛出的异常,如
public FileInputStream(String name) throws FileNotFoundException{
}
若存在多个受查异常类型,必须在方法首部列出所有异常类且用逗号隔开,如
public Image loadImage(String s) throws FileNotFoundException, EOFException{
}
3. 什么时候需要抛出异常?
在自己编写方法时,不必要将所有的可能抛出的异常进行声明
3.1 "调用一个抛出受查异常的方法,例如:FileInputStream构造器"
3.2 "程序运行过程中发生错误,并且利用throw语句抛出一个受查异常"
对于哪些可能被他人使用的Java方法,应该根据异常规范,在方法的首部声明这个方法可能抛出的异常。
3.3 "程序出现错误,例如:ArrayIndexOutOfBoundsException这样的非受查异常"
3.4 "Java虚拟机和运行时库出现的内部错误"
针对以上两者,3.3Java内部错误,我们对其没有任何控制能力,同样针对3.4,也不应该声明从RuntimeException继承的那些非受查异常
综上所诉:"一个方法必须声明所有可能抛出的受查异常,而非受查异常要么不可控制(Error),要么就应该避免发生(RuntimeException),若方法没有声明所有可能发生的受查异常,编译器就会发出一个错误消息"
4. 如何抛出异常
4.1 找到一个合适的异常类
4.2 创建这个类的一个对象
4.3 将对象抛出
5. 如何创建异常类
只需要定义一个派生于Exception的类,或者派生于Exception子类的类
习惯上,定义的类应该包含两个构造器,一个是默认的构造器;另一个是带有详细描述信息的构造器(超类Throwable的toString方法将会打印出这些详情,在调试中非常有用)
6. 如何捕获异常
若某个异常发生的时候没有在任何地方进行捕获,那么程序就会终止执行,并在控制台上打印出异常信息。
想要捕获一个异常,必须设置try/catch语句块,如:
try{
// 正常业务逻辑
}catch(ExceptionType e){ // 符合的异常类型
// 针对异常进行处理
}
6.1 若在try中抛出了一个catch中说明的异常类,则会跳过try语句块其余代码,将执行catch中的处理器代码。
6.2 若在方法中任何代码中抛出了一个catch中没有声明的异常类型,那么这个方法会立刻退出。
一个方法可以在方法内,进行try/catch捕获异常来由方法书写者来处理;也可以通过throws将异常传递给调用者,以便告知调用者这个方法可能会抛出异常。因此,我们需要仔细阅读下Java API文档,以便知道每个方法可能会抛出哪种异常,然后再决定是自己处理,还是添加到throws列表中。
6.3 捕获多个异常
在一个try语句块中可以捕获多个异常类型,并对不同类型的异常做出不同的处理。如:
try{
}catch(ExceptionType1 e1){
}catch(ExceptionType2 e2){
}catch(ExceptionType3 e3){
}
同时,异常对象e可能包含与异常本身有关的信息,想要获得对象的更多信息,可以试着使用e.getMessage()得到详细的错误信息(如果有的话)或者使用e.getClass().getName()得到异常对象的实际类型。
在Java7中,同一个catch中可以捕获多个异常类型,如:
try{
}catch(FileNotFoundException | UnknownHostException e){
}
6.4 再次抛出异常与异常链
在catch语句块中,可以再次抛出更详细的异常类型及信息,常用于子系统发生异常,可以往上层抛出详情的信息,避免丢失原始该有的异常信息;还有如果在一个方法中发生了一个受查异常,而不允许抛出它,此时使用包装,将其包装成一个运行时异常
6.5 finally
不论是否有异常被捕获,finally语句中的代码都被执行。
try{
}finally{
}
或者
try{
}catch(ExceptionType e){
}finally{
}
6.6 带资源的try语句
在我们跟资源相关书写代码的时候,比如读取文件,若使用上面的模式,我们可以catch到try里面的异常,然后在finally中关闭文件流,但是调用close的时候也会发生异常,因此就会在外面又套一层try/catch,这样代码就显得臃肿。
因此,Java7后,提出"带资源的try语句(try-with-resources)"
try(Resouce res = ...){
// work with res
}
这样当我们的try块退出时,就会自动的调用res.close(),还可以指定多个资源,如:
try (Scanner in = new Scanne「(new FileInputStream('7usr/share/dict/words"). "UTF-8"):
PrintWriter out = new Pri ntWriter("out.txt"))
{
while (in.hasNextO)
out.pri ntl n(i n.next().toUpperCaseO);
}
7. 分析堆栈轨迹元素 堆栈轨迹(stack trace)是一个方法调用过程的列表,它包含了程序执行过程中方法调用的特定位置。
7.1 可以调用Throwable类的"printStackTrace"(访问堆栈轨迹的文本描述信息)注意:"我们经常看现存代码上经常在处理异常的时候使用e.printStackTrace(),不能认为它只能在异常的时候才能调用,这个方法只是显示当前方法堆栈调用轨迹方便我们分析问题在哪儿,就跟浏览器打印出JS异常一样"
7.2 更灵活的方法就是"getStackTrace"方法,它会得到一个"StackTraceEelement",类含有能够获取文件名和当前执行的代码行号方法,且能获取类名和方法名的方法。
7.3 静态的Thread.getAllStackTrace(),它可以产生所有线程的堆栈轨迹
断言
1. 含义:
针对测试期间可以向代码中插入一些检查语句,来检查代码逻辑是否正确,否则抛出异常,当代码发布时,这些插入的检查语句将会被自动移走。
2. 形式
assert 条件;
assert 条件:表达式;
这两种形式都会对条件进行检测, 如果结果为 false, 则抛出一个 AssertionError 异常。
在第二种形式中,表达式将被传人 AssertionError 的构造器, 并转换成一个消息字符串。
3. 启用和禁用断言
默认情况下,断言被禁用,可以使用如下方式开启:
3.1 在运行程序时用-enableassertions或-ea选项
java -enableassertions XXX
3.2 指定某个类或者整个包中使用
java -ea:myClass -ea:com.company.mylib XXX
使用-disableassertions或者-da禁用某个类或者包
java -ea:... -da:MyClass XXX
注意:启用和禁用所有的断言的-ea和-da开关不能应用到哪些没有类加载器的"系统类",即需要使用-enablesystemassertions/-esa开关启用断言
4. Java处理错误机制及什么时候使用断言
4.1 抛出一个异常
4.2 日志
4.3 使用断言
4.1 断言失败是致命的,不可恢复的错误
4.2 断言检查只用于开发和测试阶段
泛型
1. 泛型好处
编写的代码可以被很多不同类型的对象所重用
2. 泛型类
一个泛型类就是具有一个或者多个类型变量的类;
引入类型变量T,用尖括号(<>)括起来,并放在类名的后面,且泛型类可以有多个类型变量。
public class Test<T,U>{
// ...
}
Java中,常常类型变量使用大写形式,且比较短,比如:使用变量E标识集合的元素类型,K和V分别表示表的关键字与值的类型,而T(或者U\S)表示“任意类型”
3. 泛型方法
泛型方法中的类型变量放在修饰符(如public static)的后面,方法返回类型的前面;泛型方法可以定义在普通类中,也可以定义在泛型类中,如:
public class Test{
public static <T> T getTest(T... args){
// ....
}
}
调用一个泛型方法时,在方法名前的尖括号中放入具体的类型,但实际大多数情况下,编译器有足够的信息能够推断出所调用的方法,如:
String test = Test.<String>getTest("test");
String test = Test.getTest("test");
4. 类型变量的限定
限定使用"extends"关键字,多个限定类型使用"&"分割,而多个类型变量使用","逗号分割,如:
public class Test{
public static <T extends Comparable & Serializable> T getTest(T... args){
// ...
}
}
5. 类型擦除
无论何时定义一个泛型类型,都自动提供了一个相应的"原始类型",原始类型的名字就是删去参数后的泛型类型名。擦除类型变量,并替换为限定类型(无限定的变量用Object)
6. 翻译泛型表达式
当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换
7. 翻译泛型方法
虚拟机中没有泛型,只有普通的类和方法;
所有的类型参数都用它们的限定类型替换;
桥方法被合成来保持多态;
为保持类型安全性,必要时插入强制类型转换。
8. 约束与局限性
不能用类型参数代替基本类型;
运行时类型查询只适用于原始类型;
不能创建参数化类型的数组;
不能实例化类型变量;
不能构造泛型数组;
不能在静态域或者静态方法中引用类型变量;
不能抛出或者捕获泛型类的实例;
可以消除对受查异常的检查
9. 通配符类型
使用"?"来表示
Test<? extends TestMore>
或者表示通配符的超类型限定:
? super TestMore
或者无限顶通配符:
Test<?>
`