本笔记来自 计算机程序的思维逻辑 系列文章
整数
不同类型的大小
类型 | 大小(bit) |
---|---|
byte | 8 |
short | 16 |
int | 32 |
long | 64 |
二进制表示
- 最左边一位是符号位,0 表示正数,1 表示负数
- 正数对应的负数由其补码表示,即取反再加 1
- 从负数二进制表示,依然是通过取反再加1得到二进制,继而求得十进制值
位运算
- 左移
<<
:高位舍弃,低位补 0 - 有符号右移
>>
:右边舍弃,左边补什么取决于原来高位是什么 - 无符号右移
>>>
:右边舍弃,左边补 0 - 按位与
&
:两边同时为 1 才为 1 - 按位或
|
:一边为 1 则为 1 - 按位取反
~
:1 为 0,0 为 1 - 按位异或
^
:相异为 1,相同为 0
浮点数
-
float
:32 位,1位表示符号,23位表示尾数,8位表示指数 -
double
:64 位,1位表示符号,52位表示尾数,11位表示指数
编码
-
ASCII
:美国信息互换标准代码,1个字节表示,128 个字符:0~127 -
ISO 8859-1
:西欧国家标准,128~159 为 控制字符,160~255 为 西欧字符 -
Windows-1252
:西欧国家标准,丰富并取代了ISO 8859-1
-
GB2312
:简体中文,2个字节表示,最高位都是1;约 7000 个汉字 -
GBK
:在GB2312
基础上增加 14000+ 个汉字,包括 繁体字 -
GB18030
:在GBK
基础上增加 55000+ 个汉字,包括少数民族字符和中日韩统一字符;使用 变长 编码,有的2个字节,有的4个字节 -
Big5
:针对繁体中文,两个字节表示,包括 13000+ 个繁体字 -
Unicode
:给 所有 字符分配了一个唯一的数字编号,范围110万-
UTF-8
:使用变长字节,1个到4个不等;小于128的,最高位为0,编码和ASCII
一样;其他的,第一个字节最高位有几个1,表示由几个字节组成,其他字节都以10开头 -
UTF-16
:使用变长字节,有的2个,有的4个 -
UTF-32
:4个字节表示-
UTF-32BE
:第一个字节是整数二进制的最高位,最后一个字节是整数二进制的最低位 (Big Endian) -
UTF-32LE
:与上相反
-
-
对于一个Unicode编号,具体怎么编码呢?首先将其看做整数,转化为二进制形式(去掉高位的0),然后将二进制位从右向左依次填入到对应的二进制格式x中,填完后,如果对应的二进制格式还有没填的x,则设为0。
条件的本质
if else
- 会转换为跳转指令被CPU识别
- 跳转有两种:条件跳转和无条件跳转
switch
- 和
if else
不一样,如果分支较多,可能会使用 跳转表 - 跳转表 是一个映射表,存储可能的值以及要跳转的地址
- 跳转表 的高效体现在 值必须是整数,且按大小排序
-
switch
的值可以为byte
short
int
char
enum
String
;跳转表值的存储空间为32位,放不下long
字符 char
- 本质是一个固定占用 两个字节 正整数
- 这个正整数对应于
Unicode
编号,用于表示对应的字符 - 由于固定占用两个字节,只能表示 65536 以内的字符,超出范围的字符,使用两个
char
表示
函数
- 函数中的参数和函数内定义的变量,都分配在栈中,这些变量只在函数被调用时才分配,调用结束就被释放;返回值存放在专门的返回值存储器中
- 对象的变量存储在栈中,内容存储在堆中
- 函数的每一次调用都需要分配额外的栈空间用于存储参数,局部变量以及返回地址,需要进行额外的入栈和出栈操作
继承和多态
类的继承
-
new
过程中,父类先进行初始化,可通过super
调用父类相应的构造方法,没有使用super
的话,调用父类的默认构造方法 - 子类变量和方法与父类重名的情况下,可通过
super
强制访问父类的变量和方法 - 父类没有不带参数的构造方法,则子类在构造方法中必须通过
super(…)
调用父类的带参数的构造方法
类的多态
子类对象可以赋值给父类引用变量,这叫多态,实际执行调用的是子类实现,这叫动态绑定
静态绑定
即访问绑定到变量的静态类型
- 静态绑定在程序编译阶段即可决定,而动态绑定则要到程序运行时
- 实例变量、静态变量、静态方法、私有方法,都是静态绑定
重载
当有多个重名方法时,在决定调用哪个方法的过程中,首先寻找参数类型最匹配的,然后才看变量的动态类型,进行动态绑定
类型转换
一个父类的变量,能不能转换为一个子类的变量,取决于这个父类变量的动态类型(即引用的对象类型)是不是这个子类或子类的子类
类的加载
分配内存保存类的信息,给类变量赋默认值,加载父类,设置父子关系,执行类初始化代码
虚方法表
类加载的时候,为每个类创建一个表,这个表包括该类的对象所有动态绑定的方法及地址,包括父类的方法,但一个方法只有一条记录,子类重写父类的方法后只保留子类的
避免使用继承
- 使用
final
关键字 - 优先使用 组合 而非继承
- 使用 接口
正确使用继承
- 重写方法不要改变预期的行为
- 理解可重写方法的实现机制
- 基类修改时,相应修改子类
接口
- 接口表示能力
- 降低耦合,提高灵活性
- 接口不能
new
,不能直接创建对象,只能通过类来创建对象;可以声明接口类型的变量,引用实现了该接口的类对象 - 接口中的变量都是
public static final
的 - 接口可以多继承
抽象类
- 表达一种抽象的概念
- 定义了抽象方法的类必须被声明为抽象类,但抽象类可以没有抽象方法
- 抽象类不能创建对象,但可以声明抽象类的变量,引用抽象类具体子类的对象,与接口类似
- 从语法看,抽象类不是必须的,但它能使程序更为清晰,减少误用
内部类
定义在类的内部,可以实现对外部隐藏,可以有更好的封装性,代码也更简洁
静态内部类
- 可以访问外部类的静态变量和静态方法,而不能访问实例变量和实例方法
- 在外部类内部,可以直接使用该内部类
成员内部类
- 不仅可以访问外部类的静态变量和静态方法,还可以访问实例变量和实例方法
- 在外部类内部,同样可以直接使用该内部类
- 在成员内部类中,不可用定义静态变量和静态方法
方法内部类
- 如果是静态方法,该内部类只能访问外部类的静态变量和静态方法
- 如果是实例方法,则还可以直接访问外部类的实例变量和实例方法
- 方法内部类可以访问方法中的参数和局部变量,是通过在构造方法中传递参数实现的
- 如果类只在某个方法中被使用,那么使用方法内部类,可以实现更好的封装
匿名内部类
- 在创建对象的时候定义类,与
new
关联 - 只能被使用一次,用来创建一个对象
- 因为没有构造方法,所以无法接收参数
- 可以访问外部类的所有变量和方法,可以访问方法中的
final
参数和局部变量
枚举
- 枚举变量的
toString()
方法返回其字面值,所有枚举类型都有一个name()
方法,返回值和toString()
一样 - 枚举变量可以用
equals
和==
比较,结果是一样的 - 枚举是有顺序的,可以比较大小;枚举类型都有一个
ordinal()
方法,表示枚举值在声明时的顺序,从 0 开始 - 枚举类型都实现了
Comparable
接口,可以通过compareTo()
方法与其他枚举值比较,实际就是比较ordinal
的大小 - 枚举类型都有一个静态的
valueOf(String)
方法,返回该字符串对应的枚举值 - 枚举类型都有一个静态的
values()
方法,返回一个包括所有枚举值的数组,顺序和声明时一样 - 一般不使用枚举的
ordinal
值,因为它会随着枚举值在定义中的位置变化而变化;通常增加一个实例变量来处理,好处是:该变量的值可以自定义,且不会改变 - 每个枚举值可以有关联的类定义体,枚举类型中声明抽象方法,枚举值实现该方法
异常
异常分类
所有异常都继承自 Throwable
Error
- VirtualMachineError
- OutOfMemoryError
- StackOverFlowError
Exception
- IOException
- SQLException
- RuntimeException
- NullPointerException
- IllegalStateException
- ClassCastException
- IllegalArgumentException
- NumberFormatException
- IndexOutOfBoundsException
- ArrayIndexOutOfBoundsException
- StringIndexOutOfBoundsException
方法
- 获取异常消息:
getMessage()
- 获取触发异常的异常:
getCause()
- 获取异常栈每一层的信息:
getStackTrace()
- 打印异常栈信息:
printStackTrace()
异常的捕获
-
catch
语句可以有多条,具体的异常子类应该放前面,因为抛出的异常类型如果是catch
中声明的异常的子类也算匹配,后面的catch
就不再执行了 - 在
catch
块内处理完后,可以重新抛出该异常或新异常 -
finally
,不管有无异常抛出,该代码块都会执行,常用于释放资源 - 如果在
try
或catch
内有return
语句,则return
语句在finally
语句执行结束后才执行,但finally
并不能改变其返回值 - 如果
finally
内也有return
语句,那么try
和catch
内的return
会丢失,实际返回finally
中的返回值;finally
内的return
语句还会掩盖try
和catch
内的异常;finally
中抛出的异常也会掩盖try
和catch
内的异常
所以,尽量避免在
finally
中使用return
语句或抛出异常
声明抛出的异常
- 使用
throws
,跟在方法后面,可以有多个异常,用,
隔开 - 对于
RuntimeException (unchecked exception)
,不要求使用throws
进行声明;但对于checked exception
,必须声明,没有声明则不能抛出,但可以声明而不抛出,留给子类去处理
包装类
装箱及拆箱
Java 1.5以后引入自动装箱拆箱技术
- 装箱:基本类型->包装类型
- 拆箱:包装类型->基本类型
装拆箱 方法
- 接收基本类型,返回包装类型:
valueOf()
- 返回对应的基本类型:
xxxValue()
equals
- 默认实现是比较地址
- 所有包装类都重写了该方法,实际比较用的是其包装的基本类型值
-
long
比较对应的longValue
-
Float
比较对应的floatToIntBits
,把二进制看作int
,再比较 -
Double
比较对应的doubleToLongBits
-
hashCode
- 返回一个对象的哈希值,
int
类型,由对象中一般不变的属性映射得来,用于快速对对象进行区分,分组 - 一个对象的哈希值不能变,相同对象的哈希值必须一样
- 如果
equals
方法返回true
,则hashCode
必须一样,反之不要求 - 子类重写
equals
方法时,必须重写hashCode
方法
Comparable
每个包装类都实现了Comparable
接口,用于比较
字符串 String
- 内部用一个 字符数组 表示字符串
-
String
是不可变类,声明为final
,内部字符数组也是final
的 - 很多看似修改的方法,都是创建新的
String
对象实现的 - 常量字符串在内存中,被放在一个共享的地方:字符串常量池
字符串构造器 StringBuilder
- 内部维护一个 可变的 字符数组和一个记录使用的字符个数的实例变量
- 字符数组默认长度为 16
- 长度扩展策略:
length * 2 + 2
- 指数扩展,减少内存分配的次数,也避免空间浪费
- 内部实现方法:
System.arraycopy(src, srcPos, dest, destPos, length)
数组操作工具类 Arrays
- 打印:
toString(objArray)
- 排序:
sort(objArray)
- 排序,自定义比较器:
sort(objArray, Comparator)
- 二分查找:
binarySearch(objArray, obj)
,找到返回index
,找不到返回-(插入点+1)
- 拷贝:
copyOf(objArray, newLength)
copyOfRange(objArray, from, to)
- 比较:
equals(a1, a2)
,数组长度相同,每个元素都相同,才返回true
- 填充:
fill(objArray, obj)
fill(objArray, from, to, obj)
时间和日期
纪元时
1970年1月1日0时0分0秒
Date
- 绝对时间,和年月日无关
- 内部维护一个
long
类型的值,毫秒值
Calendar
- 年历,抽象类
- 内部有一个表示时刻的实例变量
time
,还有一个整型数组fields
,表示日历中各个字段的值,数组长度为 17 - 内部字段,静态变量
YEAR
MONTH
DAY_OF_MONTH
DAY_OF_WEEK
HOUR_OF_DAY
MINUTE
SECOND
MILLISECOND
- 方法
-
add
,会自动调整其他字段的值 -
roll
,不影响时间范围更大的字段值
-
- 公历:
GregorianCalendar
DateFormat
- 格式化,抽象类
- 定义了4个静态变量,表示4种风格,默认是
MEDIUM
:SHORT
MEDIUM
LONG
FULL
-
SimpleDateFormat
,格式:-
yyyy
:年 -
MM
:月 -
dd
:日 -
HH
:时 -
mm
:分 -
ss
:秒 -
SSS
:毫秒 -
E
:星期几 -
a
:上下午
-
- 时区:
Timezone
- 国家和语言:
Locale
随机 Random
- 种子 种子决定了随机产生的序列,种子相同,产生的随机数序列就是相同的;指定种子就是为了实现可重复的随机
- 默认构造方法的种子是一个真正的随机数
泛型
Java有Java编译器和Java虚拟机,Java编译器将Java源代码编译成.class文件,虚拟机加载并运行.class文件
对于泛型类,Java编译器会将泛型代码转换为普通的非泛型代码,将类型参数T擦除,替换成Object,插入必要的强制类型转换
好处
安全性和可读性
通配符 ?
-
<? extend E>
:有限定通配符,匹配E或E的某个子类(只能读,不能写) -
<? super E>
:超类型通配符,表示E的父类(能写入) -
<?>
:无限定通配符(只能读,不能写)
比较
-
<T extend E>
:用于定义类型参数,它声明了一个类型参数T,可放在泛型类定义中类名后面,泛型方法返回值前面 -
<? extend E>
:用于实例化泛型变量中的类型参数
总结
-
<?>
和<? extend E>
用于实现更灵活的读取,可以用类型参数的形式替代,但通配符更为简洁 -
<? super E>
用于实现更为灵活的写入和比较,不能被类型参数形式替代
注意
- 基本类型不能实例化类型参数
- 一个泛型对象的
getClass()
方法的返回值与原始类型是相同的 - 不能通过类型参数创建对象
- 多个上界的类型参数,用&分隔,上界类写在第一位,上界接口写在后面
- 不能创建泛型数组,但可以使用原始类型来创建