1 Java概述
java特点:面向对象(oop),健壮性(强类型机制,异常处理,垃圾的自动收集),跨平台性(.class文件可以在多个平台下运行),解释性
java源文件基本组成部分是类
java应用程序执行入口是main()方法
java严格区分大小写
java每个语句以;结束
java括号成对出现,缺一不可
一个源文件中最多只能有一个public类,其他类个数不限,编译后每个类都会生成一个.class文件(java运行本质就是把.class加载到jvm运行)
也可以将main方法写在非public类中,指定运行非public类,这样入口就是非public的main方法
public类类名必须是文件名(如果有public类的话)
JDK(java开发工具包)= JRE(java运行环境) + java开发工具
JRE = JVM(java虚拟机) + java核心类库
环境变量path是为了在dos的任意目录使用java和javac命令
1.先创建Java_Home指向jdk安装的主目录
2.编辑path环境变量,增加%Java_Home%/bin
1.1 JDK安装出现问题
解决办法:在任务管理器里把这个序号的任务鲨了,然后点重试
1.2 运行常见问题(使用Sublime)
java文件(源文件)要先通过javac编译成.class文件(字节码文件)才能执行
运行出错可能是没有设置成GBK文件(文件中有中文)
文件名和公共类不匹配或写错文件名
缺少;
1.3 代码规范
1.类和方法的注释要用javadoc的方式写
2.非javadoc的注释是给代码维护者看的,告诉读者为什么这样写,如何修改,注意什么问题
3.使用tab缩进整体向右缩进,使用shift+tab整体向左缩进
4.运算符两边加一个空格
5.源文件要用utf-8格式
6.每行不要超过80个字符
7.代码编写次行风格(大括号换行)和行尾风格(行位打前括号,换号打后括号)(推荐行尾风格)
1.4 DOS命令
1.4.1 相对路径和绝对路径
相对路径:从当前目录开始定位,形成的一个路径(要去别的文件夹可能需要返回上一级目录,..\表示返回上一级)
绝对路径:从顶级目录开始定位,形成的路径
1.4.2 常用DOS命令(不常用)
1.dir 路径 查看当前目录内容
2.cd /D c: 将目录从d盘切换到c盘
3.cd 路径 切换目录(绝对路径或相对路径)
4.cd .. 切换到上一级
5.cd \ 切换到跟目录
6.tree 路径 查看目录下所有子级目录
7.cls 清屏
8.exit 退出dos
9.md 创建目录
10.del 删除目录
11.copy 拷贝文件
12.del 删除文件
13.echo 输入内容到文件
14.type 输出内容到文件
15.move 剪切
2 变量
变量三要素:类型+名称+值
不同类型变量占内存不同
变量必须先声明后使用
变量在同一个作用域内不能重名
2.1 数据类型
基本数据类型:数值型:
字节byte(1字节)范围:-128-127
短整型short(2字节)范围:-2^15-2^15-1
整型int(4字节)范围:-2^31-2^31-1
长整型long(8字节)范围:-2^63-2^63-1
浮点数:由符号位+指数位+尾数位组成
单精度浮点数float(4字节)
双精度浮点数double(8字节)
float型常量可以放入double中,反之会损失精度编译失败
0.123等价于.123(0可以省略)
5.12e2=5.12x10的平方=512.0(要保持double型)
5.12E-2=5.12/10的平方
字符型:字符char(2字节)存放单个字符,用''单引号括起来
布尔类型boolean(1字节)存放true或false(不能用0和非0代替)(不参与运算)()
引用数据类型:
类class
接口interface
数组([])
java整型常量默认为int型,声明long型常量要在常量后加l或L
java浮点数常量默认为double型,声明float型常量要在常量后加f或F
通常使用double型,精度更高
char本质是一个整数,代表unicode编码对应的字符,以二进制存储,可以参与运算
char可以用/转义字符将后面的字符变为特殊字符常量(换行,空格等)
bit是计算机中最小存储单位,byte是计算机中基本存储单元,1byte=8bit
AscⅡ码用一个字节表示,一共128个字符,总共能表示256个字符,但是只用了128个
Unicode码用两个字节表示字符,字母和汉字统一占两个字符(兼容AscⅡ码)
utf-8码大小可变,最大可以用六个字节表示,字母用一个字节,汉字用三个字节(相当于Unicode码的改进)
gbk码可以表示汉字,字母用1个字节,汉字用2个字节
gb2312码<gbk码
big5码繁体中文,台湾香港使用
当对运算结果是小数的值进行比较时要小心(8.1/3=2.699999997)应该以两个数的差值的绝对值在某个精度范围内判断
2.2 基本数据类型转换
char→int→long→float→double
byte→short→int→long→float→double
byte,short和char之间不会自动互相转换
赋值给byte时,首先判断是否在范围内,但是赋值变量给byte时,会直接判断变量的数据类型,不符合则编译失败
可以把精度小的数据类型自动赋给精度大的数据类型,反之会报错
byte,short和char之间运算,会先转换成int再计算(就算是同类型之间计算也会转成int)
多种数据类型混合运算时,系统首先把所有数据转换成容量最大的那种数据类型,然后再进行计算
2.3 强制类型转换
将容量大的数据类型转换为容量小的数据类型,要用强制转换符(),例如int i = (int)1.9,会直接舍去多出部分,精度损失(二进制截断)
强制类型转换符号只会对最近的操作数生效,可以用小括号提升优先级
char只能接受常量值,不能接收别的数据类型的形参,需要先强制类型转换才能赋值给char
2.4 String类型转换
使用+连接基本数据类型与String,即可连接成新的字符串
将String中的数据转换成基本数据类型,用xxx s = xxx.parsexxx(s1),第一个xxx为希望s表示的基本数据类型,第二个xxx为转换类型的英文,如整型Integer,双精度浮点数Double,第三个为数据类型简称,如Int,Double
String类型转换成char时,只取出字符串第一个字符
把String类型转换成基本数据类型时要保证格式能正确转换,如果转换不了会程序终止并抛出异常
2.5 字符串功能
字符串长度 str.length()
字符串拼接arr = arr1 + arr2;
或arr = arr1.concat(arr2);
调用第i个字符串元素arr.charAt(i)
3 运算符
3.1算数运算符
取余符号%:11%9=2,a%b=a-a/b*b
自增运算符++:++i表示先自增再赋值,i++表示先赋值再自增
除号/:如果计算的两个数都是整型,则最后结果也是整形,double i = 10 / 4,则i = 2.0,double i =
5 / 9 = 0.0
3.2 关系运算符
关系运算符运算结果为布尔类型,只有true和false两种,常用于if结构或循环结构中
3.3 逻辑运算符
短路与&&:如果第一个条件是false则不会判断第二个条件。
逻辑与&:无论第一个条件是否是false,都会判断第二个条件。
短路或Ⅱ:如果第一个条件是true则不会判断第二个条件。
逻辑或|:无论第一个条件是否是true,都会判断第二个条件。
赋值语句返回值为左边的变量赋值后的值,如x=false则返回false
3.4 赋值运算符
a += b等价a = a + b
赋值运算符左边只能是变量,右边可以是变量,表达式或常量,运算顺序从右到左
复合赋值运算符(+=,-=等等)会自动进行类型转换(强制转换成原有数据类型)
3.5 三元运算符
条件表达式?表达式1:表达式2;
如果条件表达式为true返回第一个表达式,为false返回第二个表达式
三元运算符可以转换成if-esle语句
三元运算符是一个整体,输出结果会按照精度最高的转换
3.6 标识符
java中对变量,方法和类等命名时使用的字符序列为标识符
定义规则:1.由26个英文字母大小写,0-9,_或$组成
2.数字不可以开头
3.不可以使用关键字和保留字
4.java中严格区分大小写,但不限制长度
5.标识符不能包含空格
命名规范:1.包名所有字母小写
2.类名,接口名由多个单词组成时所有单词首字母大写
3.变量名,方法名由多个单词组成时第一个字母小写,第二个单词开始每个单词首字母大写
4.常量名所有字母都大写,每个单词之间用下划线连接
3.7 进制
二进制以0b或0B开头
八进制以数字0开头
十六进制0-9以及A-F(10-15),a到f不分大小写,以0x或0X开头
十进制转二进制,一直除二,保留余数,最后倒着填写
二进制转八进制,每三位一组,转化成八进制
八进制转二进制,每位转成三位二进制数
3.8原码 反码 补码
1.二进制最高位是符号位,0为正数,1为负数
2.正数的原码,反码,补码都一样(三码合一)
3.负数的反码=原码符号位不变,其他位取反
4.负数的补码=反码+1,负数的反码=补码-1
5.0的反码补码都是0
6.java没有无符号数
7.计算机运算时用补码方式运算
8.看运算结果时要看原码
3.9 位运算
按位与&:两位全为1,结果为1,否则为0
按位或|:两位有一个为1,结果为1,否则为0
按位异或^:两位一个为0,一个为1,结果为1,否则为0
按位取反~:0变1,1变0
算术右移>>:低位溢出,符号位不变,并用符号位补溢出的高位,00000001>>2,01被顶出,变为全0
算术左移<<:符号位不变,低位补0
逻辑右移>>>:低位溢出,高位补0
运算步骤:
1.先把数字转成补码
2.位运算
3.转换回原码
4 程序控制结构
4.1 if选择
代码块只有一个语句可以不用括号
多重嵌套else if型,只要有一个代码块执行,后面的都不继续执行(一般不要超过三层,可读性差)
4.2 switch选择
switch中表达式表示一个值,当case中常量与表达式值匹配则执行相应代码块
如果case后面代码块没有break,则会自动执行下一个case的代码块,不会判断是否满足下一个case,最后一个case没有break则会执行default的代码块(穿透现象)
switch中表达式返回值只能是(byte,short,int,char,enum,String)
case中的值只能是常量,不能是变量
可以没有default
判断的值不多,且符合数据类型则用switch;区间判断或布尔类型判断用if
4.3 for循环
for循环四要素:循环变量初始化,循环条件,循环操作,循环变量迭代
循环条件是一个返回布尔值的表达式
for(;,循环条件,;)中初始化和变量迭代可以写在其他地方
4.4 while循环
while循环四要素:,循环条件,循环体,循环变量迭代
循环条件要返回一个布尔类型值
while先判断再执行
4.5 do while循环
先执行再判断
最后有个分号
4.6 break跳转控制语句
break用于终止某个语句块的执行,一般使用在switch或循环中,可以直接跳出最近的循环
4.7 continue跳转控制语句
continue用于结束本次循环,继续执行下一次循环
出现在多层嵌套循环语句时可以通过标签指名跳过哪一层循环
4.8 return跳转控制语句
return使用在方法,表示跳出所在方法,写在主方法会退出程序
5 数组、排序和查找
5.1 数组
数组用于存放多个同一类型的数据,数组是引用数据类型,数组型数据是对象
数组名.length用于取数组长度
数组初始化:
1. a[]=new 数据类型[数组大小]
2.数据类型 a[];
a=new 数据类型[数组大小];
3.数组类型[] a={1,2,3}表示生成double类型的数组,也可以写成数组类型 a[]
4.数组类型 a[]=new 数据类型[]{1,2,3};
数组创建后未赋值则默认0,false,null,/u0000
基本数据类型赋值是数值拷贝,n2=n1,当n2改变时n1不变,但数组赋值是地址,arr2=arr1
5.2 排序
冒泡排序:第一个与第二个数比较,把较大的数放后面,第二个与第三个数比较,把较大的数放后面,不断循环n-1次
5.3 二维数组
二维数组初始化:
1.int[][] a ={{0,0},{0,1}};(静态初始化)
2.int a[][];
a=new int[i][j];
3.int a[][]=new int[3][]
用循环分别给每个一位数组开空间a[i]=new int[i+1]
4.int a[][]=new int[i][j]
int a[][]和int[]a[]和int[][]a是等价的
二维数组每个元素是一个一位数组,即二维数组的列数就是元素个数
arr.length表示求二维数组列数
arr[i].length表示第i+1列的元素个数
访问第i个数组的第j个
arr[][]第一个里面存放的是后面每个一位数组的地址
6 面向对象编程入门
6.1 类与对象
创建类 class 类名{}
使用对象 类名 创建的对象名 = new 类名();
6.1.1 属性
属性=成员变量=field(字段)
属性可以是基本数据类型也可以是引用数据类型
属性前面可以加访问修饰符,后面和定义基本数据类型一样
如果新建对象p2=p1,则p2指向p1地址,p1变化p2
6.1.2 内存分配机制
栈:存放基本数据类型(局部变量)
堆:存放对象(包括数组)
方法区:常量池,类加载信息
6.1.3 创建对象流程
1.加载Person类信息
2.在堆中分配空间,默认初始化
3.把地址赋给p,p指向地址
6.2 方法
6.2.1 方法要点
void表示方法没有返回值,需要返回值则把void变成返回的数据类型,调用方法时相当于最后
[修饰符] 返回值类型 方法(int n),括号内表示形参列表
调用方法 创建的对象名.方法名();
调用方法时在栈中开辟独立空间运行,返回值后消除空间
一个方法只能有一个返回值,想返回多个结果可以用数组
方法定义时参数叫形参,使用时的参数叫实参
方法里不能再嵌套方法
同一个类中的方法可以互相直接调用
跨类的方法调用要通过对象名调用(在方法中创建对象调用方法)
在方法中修改基本数据类型,主方法中变量值不改变,修改引用数据类型则主方法变量值改变
6.2.2 编写方法思路:
1.方法返回类型
2.方法名
3.方法形参
4.方法体
6.2.3 方法重载
重载方法名必须相同,形参列表必须不同(参数类型或个数不同),返回值无限制
6.2.4 可变参数
可变参数:对于名称相同,功能相同,参数数目不同的方法可以封装成一个方法,(int... nums)表示可以接收多个int,也可以接收数组,nums作为数组使用
可变参数可以和普通类型参数类型一起放在形参列表,但是可变参数必须在最后
一个形参列表只能有一个可变参数
6.2.5 作用域
局部变量:在成员方法中定义的变量,必须赋值
全局变量:对象属性,作用域为整个类体,可以不赋值(有默认值)
属性和局部变量可以重名,使用时遵循就近原则
同一个作用域内变量不能重名
全局变量可以加修饰符,局部变量不能加修饰符
跨类访问属性方法:1.在方法中新建另一个类,访问属性
2.在方法中输入另一个类作为形参,访问属性
6.2.6 构造器
[修饰符]方法名(形参列表){
方法体;
}
使用方法:类名 对象名 = new 类名()
构造器用于初始化对象(不是创建对象),创建对象同时完成属性赋值(写在类内作为一个方法,但不能写void)
构造器没有返回值
方法名和类名必须一样
参数列表和方法规则一致
构造器的调用由系统完成,创建对象时自动完成对象初始化,给已有属性值赋值不需要在前面写数据类型,直接age=18即可
构造器也能重载,除了初始化对象以外不能进行调用
没有定义构造器则系统会生成一个默认参数构造器,如果定义了构造器会覆盖默认构造器,除非再单独定义一个同样的构造器
6.2.7 this
this关键字可以访问本类的属性,方法和构造器
访问属性this.属性名,不会输出同名局部变量
访问方法使用this.方法名(参数列表)
访问构造器使用this(参数列表),只能在构造器中使用(在一个构造器中访问另一个构造器,且必须在构造器函数体第一行)
this用于区分当前类的属性和局部变量
this只能在类中使用
7 面向对象编程中级版
7.1 IDEA
7.2 包
包用于区分和管理类(包括同名类),本质是创建不同文件夹
包命名规则:只能包含数字,字母,下划线,小圆点,但是不能用数字开头,不能是关键字或保留字
包命名规范:一般是小写字母+小圆点
如com.公司名.项目名.业务模块命
引入包的方式:1.import 包名.类名(引入一个类)
2.import 包名.*(引入所有类)
package用于声明当前类所在的包,要放在class最上面,一个类中最多只有一个package
import在package下面,class上面,可以有多个import
7.3 访问修饰符
修饰符可以修饰类中的属性,方法和类
只有默认的和public才能修饰类
7.4 封装
封装就是把抽象出的属性和方法封装在一起,数据被保护在内部,程序的其他部分只能通过方法进行操作
7.4.1封装的优势
1 隐藏实现细节
2 可以对数据进行验证,保证安全合理
7.4.2 封装实现步骤
1 private属性
2 提供public方法实现属性判断并赋值
3 提供public方法获取属性的值
7.4.3封装技巧
属性赋值如果有限制条件,可能会被构造器绕过,可以在构造器里写入限制条件
7.5 继承
当多个类存在相同的属性(变量)和方法时,可以从这些类中抽象出父类,在父类中定义这些相同的属性和方法,所有的子类不需要重新定义这些属性和方法,只需要通过extends.声明继承父类即可
7.5.1 继承的好处
提高代码的复用性,扩展性和维护性
7.5.2 继承的细节
1 子类不能直接访问私有的属性和方法,要通过父类提供公共的方法去访问
2 子类需要调用父类的构造器完成父类初始化,默认使用父类无参构造器,如果父类没有无参构造器则必须在子类的构造器中用super去指定使用父类哪个构造器完成父类初始化
语法:写在子类构造器里:super(参数);
3 super()和this()都只能放在构造器第一行,所以这两个方法不能共存在一个构造器
4 java中所有类都是object的子类
5 父类构造器的调用不限于直接父类,可以一直网上追溯到object类
6 子类最多只能继承一个父类
7 不能滥用继承,子类和父类必须有对应关系
8 子类调用属性和方法时,若子类中没有该属性和方法则查找直接父类中有无,若没有则逐层向上查找,一旦找到一个目标,即便不能访问也不会继续向上查找
7.5.3 super关键字
super代表父类的引用,用于访问父类的非私有属性,方法,构造器
用法:super.属性/方法/构造器
7.5.4 super细节
1 用super的好处,父类属性由父类初始化,子类属性由子类初始化
2 super.属性/方法会直接从父类查找,不会在本类内查找
7.5.5 super和this比较
7.6方法重写
7.6.1方法重写细节
1 子类方法的参数,方法名要和父类参数,方法名完全一样
2 子类方法的返回类型和父类方法返回类型一样,或是父类返回类型的子类,如string是object的子类,父类返回object,子类返回string
3 子类方法不能缩小父类方法的访问权限,如父类是public,子类不能是protected
7.6.2 方法重写与重载的区别
7.7 多态
方法或对象具有多种形态,是面向对象的第三大特征,多态是建立在封装和继承基础之上的
7.7.1 对象的多态
1 一个对象的编译类型和运行类型可以不一致
2 编译类型在定义对象时就确定了,不能改变
3 运行类型是可以变化的
4 编译类型看定义时看 = 左边,运行类型看 = 右边
5 属性不能重写,直接看编译类型的属性
6 instanceof比较操作符,比较一个类是否是另一个类或他的子类,返回布尔值,用法:引用名.instanceof 类名;(判断的是运行类型)
举例: Animal animal = new Dog();
animal为编译类型,dog是运行类型,animal可以指向别的对象,但animal不能变
animal = new Cat()
7.7.2 向上转型
向上转型本质是父类的引用指向了子类的对象
语法:父类类型 引用名 = new 子类类型();
编译类型看定义时看 = 左边,运行类型看 = 右边
可以调用父类的所有成员(遵守访问权限)
不能调用子类特有成员
运行时先从子类寻找有无指定方法,但是编译时不能直接用子类特有方法
7.7.3 向下转型
语法:子类类型 引用名 =(子类类型)父类引用;
只能强转父类引用,不能强转父类对象
要求父类的引用必须指向的是当前目标类型的对象(比如之前用animal指向了cat类,可以强转成cat,但是不能转成dog)
可以调用子类类型中的成员
7.7.4 动态绑定机制
当调用对象方法的时候,该方法会和该对象的内存地址/运行类型绑定
当调用对象属性时,没有动态绑定机制,哪里申明哪里使用
即当子类调用父类方法时,父类方法里调用子父类同名方法优先选择子类方法,但是父类方法里调用子父类同名属性时优先使用父类属性
7.7.5 ==和equals的对比
==是一个比较运算符
既可以判断基本类型,又可以判断引用类型
如果判断基本类型,判断值是否相等
如果判断引用类型,判断地址是否相等
equals是Object类中的方法,只能判断引用类型
默认判断地址是否相等,子类中往往重写该方法,用于判断内容是否相等
7.8 Object类
7.8.1 hashcode方法
1 该方法能提高具有哈希结构容器的效率
2 两个引用指向同一个对象哈希值一定一样
3 两个引用指向不同对象哈希值不一定不一样
4 哈希值通过地址计算来,但是不能等同于地址
5 需要时可以重写hashcode方法
重写时返回Objects.hash(单个或多个属性)即可
7.8.2 toString方法
默认返回全类名(包名+类名)+@+哈希值的十六进制
子类往往重写toString方法,用于返回对象的属性信息
输出对象时默认调用toString方法
7.8.3 finalize方法
1 当对象被回收时,系统自动调用该对象的finalize方法。子类可以重写该方法,做一些释放资源的操作
2 什么时候被回收:当某个对象没有任何引用时,则jvm就认为这个对象是一个垃圾对象,就会使用垃圾回收机制来销毁该对象,在销毁该对象前会先调用finalize方法
3 垃圾回收机制的调用,是由系统来决定,也可以通过System.gc(主动触发垃圾回收机制)
7.9 断点调试
在程序某一行设置一个断点,当程序运行到这里就会停止,可以一步一步往下调试,调试过程中可以看各个变量当前的值,出错的话调试到出错的代码行即显示错误
快捷键:F7跳入方法体内
F8逐行执行代码
shift + F8跳出
F9执行到下一个断点
8 面向对象编程高级版
8.1 类变量
也叫静态变量,静态属性,是该类所有对象共享的变量,任何对象访问该变量时获取到的是相同的值
语法:访问修饰符 static 数据类型 变量名;(推荐)
static 访问修饰符 数据类型 变量名;
访问修饰符修饰范围和普通属性一致,如果为private则该类对象也无法访问
访问类变量通过类.类变量名访问,也可以通过类的对象访问,推荐通过类.类变量名访问
实例变量不能通过类.类变量名访问
类变量的生命周期与类相同,类加载完成就可以访问类变量,无需实例化也可以访问
8.2 类方法
访问修饰符 static 数据返回类型 方法名(){}
类.方法名或对象.方法名进行访问,但要符合访问修饰符修饰范围
当方法不含对象成员时,建议设置为类方法提高开发效率,可以不创建实例对象直接调用方法
类方法与普通方法都是跟随类一起被加载,将结构信息存储在方法区
类方法无this参数,不能使用和对象有关的关键字,如this和super
普通方法隐含this参数
类方法只能访问静态变量或静态方法;普通方法既能访问普通变量和方法,又能访问静态变量和方法
8.3 main方法
jvm需要调用main方法,所以该方法必须为public
执行main方法时不必创建对象,所以方法必须为static
虚拟机不需要返回值,所以是void
该方法接收String类型的数组参数,该数组中保存java命令时传递给运行的类的参数
参数通过命令行java 执行程序 参数一 参数二 ...传入main程序
main方法中可以直接调用main方法所在类的静态方法或静态属性,但不能直接访问该类的非静态成员,必须创建该类的一个实例对象后才能通过对象访问类中的非静态成员
8.4 代码块
代码块是类的成员,但是没有方法名,没有返回,没有参数,只有方法体,并且不通过对象或类显式调用,而是加载类时隐式调用
代码块语法
修饰符 {代码};
修饰符可以不写,要写的话只能写为static,相应分为静态代码块和普通代码块
逻辑语句可以为任何语句
;可写也可以省略
代码块作用
代码块相当于另一种形式的构造器,可以做初始化的操作,无论使用哪个构造器进行实例化,都会走代码块中代码,优先级高于构造器
多个构造器有重复语句则可以抽取到代码块中,提高代码重用性
代码块使用细节
静态代码块只会执行一次,普通代码块在每次创建对象时都会执行,如果只是使用类中静态变量或方法,未创建对象的话,普通代码块不会执行
何时加载类?1.创建对象实例时;2.创建子类对象实例,父类也会先被加载;3.使用类的静态成员时
创建对象时,首先调用静态代码块和静态属性初始化,按照定义顺序调用;然后调用普通代码块和普通属性初始化;最后调用构造方法
构造器最前面隐藏了super()和普通代码块,静态的代码块和属性初始化在类加载时就执行完毕了,所以优先于普通代码块和构造器
有继承关系的执行顺序:1.父类静态资源;2.子类静态资源;3.父类普通代码块和普通属性初始化;4.父类构造器;5.子类普通代码块和普通属性初始化;6.子类构造器
静态代码块只能调用静态成员
8.5 单例模式
在整个软件系统中,一个类只能存在一个对象实例,并且该类只提供一个获取对象实例的方法
通常用于重量级对象
饿汉式
实现步骤:1.构造器私有化,防止new对象
2.类内部创建对象,要创建为static形式
3.提供静态公共getInstance方法
未用到对象,但是随着类的加载,对象已经被创建好了。可能造成资源浪费
懒汉式
在类中声明静态对象
在静态方法中new对象(如果对象为null则创建)并返回
饿汉式和懒汉式区别
饿汉式在类加载时创建对象;懒汉式在调用getInstance方法时创建对象
饿汉式线程安全,懒汉式存在安全问题,多个线程同时判断是否为空时,可能多个线程执行创建行为
饿汉式可能存在资源浪费,懒汉式不会
java中的Runtime就是经典的饿汉式单例模式
8.6 final关键字
可以修饰类,方法,属性和局部变量
使用场景:(写在访问修饰符后)
1.不希望类被继承时
2.不希望父类方法被子类重写时
3.不希望类的某个属性被修改时,此时该属性也叫常量,命名为全大写,字母间用下划线间隔,必须赋初值。普通属性可以在定义时,构造器中或代码块中赋初值。静态属性只能在定义时或代码块中赋初值
4.不希望某个局部变量被修改时,此时该变量被称为局部常量
final的方法仍能被继承,但不能被重写
final类不必再用final修饰方法
final不能修饰构造方法
final往往和static搭配使用效率更高,调用用时不会导致类加载,底层编译器做了优化处理
包装类和String类都是final类
8.7 抽象类
当父类方法需要声明但不确定实现方式时,可以声明为抽象方法,此时该类为抽象类。
抽象类的价值在于设计,由设计者设计好后子类继承并实现
抽象方法即没有实现的方法,没有方法体(不用加大括号)
抽象方法在访问修饰符后加abstract,抽象类在类前加abstract修饰,但abstract不能修饰属性
抽象类不能被实例化(new),一般会被继承并由子类实现,继承的子类如果不是抽象类则必须实现抽象方法
抽象类可以没有抽象方法,有抽象方法一定是抽象类
抽象方法不能使用private,static,final,因为这些关键字会限制重写
8.8 模版模式
提取出公共代码放置在抽象类中,非公共方法定义为空abstract方法,由子类继承去个性化重写abstract方法
8.9 接口
接口就是给出一些没有实现的方法,封装到一起,到某个类要使用的时候,再根据实际情况写出方法
接口语法
interface 接口名{
属性
方法
}
class 类名 implements 接口名{
自己的属性
自己的方法
必须实现接口的抽象方法
}
jdk7以前接口里方法没有方法体,都是抽象方法,jdk8以后接口类可以有静态方法,默认方法,即可以有方法的具体实现,但是默认方法需要有static或default关键字
接口使用细节
接口不能实例化
接口中的属性必须是public static final的,关键字可以省略,属性必须初始化
接口中的方法都是public方法,可以省略abstract和public关键字
普通类实现接口则必须将接口所有方法都实现,alt+enter快捷键实现
抽象类实现接口可以不实现接口方法、
一个类可以实现多个接口
访问接口属性通过接口名.属性名
接口不能继承其他类,但是能继承接口
接口的修饰符只能是public和默认,和类一样
实现接口和继承类的区别
继承的价值主要在于解决代码的复用性和可维护性
接口的价值主要在于设计好各种规范,让其他类去实现这些方法,更加灵活
接口在一定程度上实现代码解耦(通过接口规范性和动态绑定机制)
接口的多态性
方法的形参可以为接口,只要是实现了接口的实例对象都可以作为实参,通过对象调用方法传入实现了接口的实例对象
接口类型的变量可以指向实现类的实例对象
接口类型的数组可以存放多种不同的实现类 ,在遍历数组时调用某个类特殊方法需要instanceof判断对象运行类型,然后向下转型再调用
如果接口互相继承,实现了子接口则父接口也被实现了,实现父接口时会要求也实现子接口
如果继承和实现中存在同名属性,则super指定父类属性,接口名.属性指定接口属性
8.10 内部类
一个类内部完整嵌套了另一个类结构,被嵌套的类为内部类,嵌套其他类的为外部类,内部类可以访问私有属性,提现类之间的包含关系
内部类分为:局部内部类,匿名内部类;成员内部类,静态内部类
局部内部类
定义在外部类局部位置,方法或代码块中,并且有类名
可以访问外部类所有成员,包括私有的
不能添加访问修饰符,因为其地位相当于局部变量,但是可以使用final修饰
作用域仅在定义他的代码块或方法中
外部类在方法中可以实例化内部类对象,通过对象调用方法
外部其他类不能访问局部内部类
外部类和局部内部类成员重名时,采取就近原则,访问外部类成员可以使用外部类名.this.成员名
外部类名.this实际上是一个外部类对象,哪个对象调用执行该内部类,就指向哪个对象,通过对象调用外部类成员
匿名内部类
定义在外部类的局部位置,并且没有类名
匿名内部类既是类的定义,同时也是一个对象
语法:类名 对象名 = new 类名(){匿名对象属性以及方法}
实质上相当于匿名内部类继承了原有的类,编译类型是原有类,运行类型是匿名内部类的实例对象
本质上大括号内容相当于创建了对象并返回,所以后面可以加.属性/方法直接访问匿名内部类以及外部类中的对象和方法,包括私有成员
不能添加访问修饰符,因为其地位相当于一个局部变量
作用域仅在定义的代码块或方法中
外部其他类不能访问匿名内部类
外部类和内部类成员重名时,内部类访问的话遵循就近原则,想访问外部类成员,可以使用.this.访问
通常使用匿名内部类在调用需要用到接口的类时,通过现场创建匿名内部类实现接口返回对象,以此直接作为实参传递,简洁高效,省去了单独创建接口实现类的步骤
成员内部类
定义在外部类的成员位置,没有static修饰
可以直接访问外部类所有成员,包括私有成员
可以添加访问修饰符
作用域与其他成员一样,在外部类中创建内部类对象使用
外部其他类创建内部成员类实例,第一种方法:外部类名.内部类 类名=外部类实例.new 内部类名(),第二种方法,通过getInstance获取内部类实例,
外部类和内部类成员重名时,内部类访问的话遵循就近原则,想访问外部类成员,可以使用.this.访问
9 枚举和注解
10 异常
程序执行中不正常的情况称为异常,语法错误和逻辑错误不是异常
异常分为error(虚拟机无法解决的严重问题,如栈溢出和OOM,程序会崩溃)和Exception(因编程错误或偶然外在因素导致的 一般性问题,如空指针访问,读取不存在的文件,网络中断等)分为运行时异常和编译时异常
运行时异常编译器不要求强制处置,一般是指编程时的逻辑错误,可以不做处理,因为很普遍,默认throws
编译时异常是编译器要求必须处置的异常
10.1 运行时异常
NullPointerException空指针异常:当应用程序试图在需要对象的地方使用了null时抛出异常
ArithmeticException数学运算异常:出现异常运算条件时抛出异常
ArrayIndexOutOfBoundsException数组下标越界异常:数组索引为负或大于数组大小抛出异常
ClassCastException类型转换异常:试图将对象强制转换成不是实例的子类时抛出该异常
NumberFormatException数字格式不正确异常:当程序试图将字符串转换成一种数值类型,但字符串不能转换为适当格式时抛出异常
10.2 编译异常
SQLException操作数据库时,查询表异常
IOException操作文件时发生的异常
FileNotFoundException操作不存在的文件时发生的异常
ClassNotFoundException加载不存在的类发生异常
EOFException操作文件到文件末尾发生异常
IllegalArgumentException参数异常
10.3 异常处理
10.3.1 try-catch
try{
代码}catch(Exception e){
代码}finally{
代码}
捕获到异常时系统将异常封装成Exception对象e传给catch
不论是否发生异常,都要执行finally,通常在finally中写释放资源的代码,保证能正常结束
发生异常后则直接进入catch代码块,不会执行后续代码;没发生异常则不会执行catch代码
可以有多个catch语句,捕获不同的异常,要求父类异常在后,子类异常在前,只会匹配第一个符合的异常代码块
也可以使用try-finally,不捕获异常,程序会直接崩掉,但不论是否发生异常都会执行finally
10.3.2 throws
在类定义时加throws Exception{}会将异常返回给上一级处理(或继续抛出),当返回到jvm时则输出异常并终止程序
可以声明抛出异常列表,可以是方法中产生的异常类型或其父类,多个异常可以通过逗号分隔
不写则默认抛出异常
10.4 异常细节
子类重写父类方法时,抛出异常类型要么是和父类抛出的异常一致,要么是父类抛出的异常类型的子类型
如果异常被try-catch处理则无需抛出,否则不断向上一级抛出
如果调用抛出编译异常的方法,则必须对抛出的异常做处理或者继续抛出
10.5 自定义异常
自定义异常类名,继承Exception(编译异常)或RuntimeException(运行异常)
异常类参数需要是String类型,需要抛出异常的地方语法为throw new 异常类名(异常提示内容)
一般继承运行时异常,可以使用默认处理机制
10.6 throw和throws区别
throws表示异常处理的一种方式,在方法声明处使用,后面跟异常类型
throw表示手动生成异常对象关键字,位置在方法体中,后面跟异常对象
11 常用类
11.1 包装类
针对八种基本数据类型相应的引用类型,通过建立包装类,可以封装方法
前两个类继承Object,后六个类继承Object的子类Number,Number实现Serializable接口,其他子类实现Comparable接口
11.1.1 包装类和基本数据类型的转换
jdk5以前使用手动装箱和拆箱;jdk5以后使用自动装箱和拆箱。
装箱底层调用valueOf方法,比如Integer.valueOf(int类型的某个变量名)
拆箱底层调用intValue()方法获取内部数据
自动装箱:类名 对象名 = 变量名;即可自动装入包装类
自动拆箱: 变量名 = 对象名
11.1.2 包装类和String转换
包装类到字符串
1.包装类变量+字符串,传给String类型变量
2.包装类变量.toString()
3.String.valueOf(包装类变量)
字符串到包装类
4.Integer 对象名 = Integer.parseInt(int类型字符串)
5.Integer 对象名 = new Integer(字符串名)
11.1.3 常用方法
Integer.MIN_VALUE返回int类型的最小值/最大值
Character.isDigit(字符串)判断是不是数字
Character.isLetter(字符串)判断是不是字母
Character.isUpperCase(字符串)判断是不是大写
Character.isLowerCase(字符串)判断是不是小写
Character.isWhitespace(字符串)判断是不是空格
Character.toUpperCase(字符串)转为大写/Lower小写
11.1.4 valueOf源码
创建new包装类对象时指向不同对象地址
但是使用自动装箱时底层为valueOf方法,当传入的int值大于-127,小于128时不会新建对象,创建多个同样值的对象实际上是同一个,双等号判断为true
只有基本数据类型时比较值,只要有创建对象则比较地址不同
11.2 String
字符串字符使用Unicode编码。一个字符占两个字节
String继承Object类,实现了Serializable接口(串行化,可以在网络传输)和Comparable接口(可以相互比较),CharSequence接口
String类有final属性,不能被继承
字符串变量指向双引号包起来的字符串常量,实际内部使用私有final的value[]属性存储字符串内容
final实际效果为限制地址不可改变,不能指向其他地址,但是可以修改指向地址中的内容
11.2.1 String创建
Sting赋值可以直接赋值或通过构造器赋值
常用构造器new String(),new String(字符串),new String(字符串数组),new String(字符串数组,开始下标,计数序号)
构造过程:直接赋值:首先从常量池查看是否有要赋的值的数据空间,有的话直接指向,没有则创建并指向。最终变量指向的是常量池中的空间地址
构造器构造:先在堆中创建空间,里面维护了value属性,指向常量池中的赋的值的空间,如果常量池中没有则重新创建,有的话直接指向,最终指向的是堆中的空间地址
String.intern方法:查找常量池中有没有和字符串内容相同的字符串(equals比较),有的话返回池中字符串,没有则添加到池中并返回对象的引用
对象的字符串属性是直接指向常量池中对象,不同对象属性相同双等号判断为真
两个不同的new的String对象双等号判断对象地址,为假
11.2.2 String特性
String s1 = 字符串, s1= 新字符串,相当于创建了一个新的字符串对象并指向
s1 = a+b,会直接生成a+b对象并指向常量池
s1= s2 +s3,首先创建一个StringBuilder对象,然后执行append方法插入第一个字符串,再append追加第二个字符串,最后通过toString方法合在一起,最终生成新的String对象,指向常量池中字符串
字符串拼接则直接指向常量池,字符串变量拼接则生成新的对象指向堆中地址
当对象中属性为字符串时,调用属性首先指向堆中value,然后value指向常量池中字符串。如果直接用方法修改属性指向新的字符串常量,则会创建新的栈空间,通过栈指向常量池,当方法结束后栈销毁,再次访问,仍然会通过value寻找,仍然返回原来的字符串
11.2.3 String常用方法
equals区分大小写判断是否相等
equalsIgnoreCase忽略大小写判断是否相等
length字符串长度
indexOf获取字符在字符串中第一次出现的索引,从0开始
substring截取指定范围的子串,单个数字表示从该位置向后截取,两个数字表示截取区域
trim去前后空格
charAt获取某索引处的字符
toUpperCase转换大写
toLowerCase转换小写
concat在原有字符串后拼接字符串
replace(a,b)返回用b代替原有字符串中的a,对原有字符串a无影响
split(“,”)以,为标准分割字符串,使用/分割需要转义
toCharArray转换字符数组
compareTo比较两个字符串大小,前者大则返回正数,后者大返回负数,相等返回0。底层是当字符串长度一样时,每个字符作差,长度不一样时用长度作差
format格式化字符串,format(“%s,%d”,name,age)效果等同于(name + age),占位符由后面的变量替换
11.3 StringBuffer
StringBuffer直接父类是AbstractStringBuilder,父类中有char[]属性value,在堆中存放字符串内容,不带final修饰符
StringBuffer实现了Serializable(串行化)接口
StringBuffer是final类,不能继承
11.3.1 StringBuffer和String对比
String存储字符串常量,不能修改值,每次更新实际上是修改地址,放在常量池中
StringBuffer保存字符串变量,每次更新修改内容,放在堆中。当原有空间不够时创建新的空间将原有数据复制过来
11.3.2 StringBuffer构造器
无参构造器默认创建地址大小16字符的字符数组
可以创建时指定大小,或者直接放入字符串,放入字符串则在后面加16字符的空间,放入null则会导致空指针异常,因为会首先计算字符串长度,但是无法计算空指针长度
String对象转换为StringBuffer对象,可以通过构造器直接放入,或StringBuffer.append方法取出字符串值
StringBuffer对象转换为String对象,可以使用StringBuffer,toString()方法返回字符串,或者直接通过构造器传入StringBuffer
11.3.3 StringBuffer常用方法
append在后面添加字符串,加入null实质是添加了null四个字符
delete(a,b)删除字符串中从第a个到第b个字符,不包括第b个,从0开始计数
replace(a,b,字符串)把a到b之间的字符替换成目标,包括a不包括b
indexOf(字符串)返回字符串出现的位置
insert(a,字符串)在索引为a的位置插入字符串
length,返回长度
11.4 StringBuilder
可变字符序列,和StringBuffer兼容API,是其简易替换,但是不是线程安全,用于单个线程使用字符串缓冲区,效率更高,主要操作是append和insert
StringBuilder直接父类是AbstractStringBuilder,实现了Serializable(串行化,可以网络传输或保存到文件)接口
StringBuilder是final类,不能被继承,数据放在父类中char[]的value属性中,在堆中
11.4.1 String,StringBuffer,StringBuilder比较
StringBuffer,StringBuilder都是可变字符序列
String效率低,复用率高,将字符串存储在常量池中, 可以被共享
StringBuffer效率高,线程安全,方法中添加了synchronized修饰符
StringBuilder效率最高,线程不安全
String多次添加或修改字符串,会在常量池内存中留存大量副本,效率低,如果需要多次修改字符串则不用String
要大量修改且单线程使用StringBuilder
要大量修改且多线程使用StringBuffer
很少修改但被多个对象引用使用String
11.5 Math类
abs绝对值
pow(a,b)a的b次幂
ceil向上取整
floor向下取整
round四舍五入
sqrt开方
random返回0-1之间随机数,可以取0不能取1,可以加整数,乘以扩大倍数加强制类型转换,移动随机范围
max(a,b)取最大值
min(a,b)取最小值
11.6 Arrays类
toString返回数组的字符串形式,如果为null则返回null字符串,如果为空数组则返回[]字符串,其他情况首先输出[然后遍历数组转换为字符串逐个拼接,最后输出],每个元素用,分隔
sort排序,排序后会影响到实参,因为数组是引用类型。
sort是重载的,也可以传入接口Comparator实现定制排序,第一个元素传入数组,第二个元素new一个接口对象,后面接匿名内部类重写compare方法。方法底层使用binarySort排序,compare返回值大于0则倒序排,小于0则正序
可以定制排序方法,传入数组和接口,比较时调用接口的compare方法,根据返回值正负决定排序方法,接口具体实现方法可以在调用时定义,一般用两个元素作差
binarySearch(arr,a)二分查找a元素位置,要求必须排好序,不存在则返回-(本应该在的位置+1)
copyOf(arr,a)返回数组前a个元素的复制,a如果大于数组长度则在后面添加null,为负则返回异常
fill(arr,a)填充数组,将原来的元素都换成a
equals判断是否内容一致
asList将一组值转换成list,运行类型是util包中的Array的ArrayList静态内部类
12 集合
集合可以动态保存多个对象,提供了一系列操作对象的方法
Collection接口继承了Iterable接口,它有两个重要的子接口List和Set,都是单列集合
Map接口实现子类,是双列集合
集合主要是两组,单列集合和双列集合
12.1 Collection
12.1.1 常用方法
add添加单个元素
remove删除元素
contains查找元素是否存在
size获取元素个数
isEmpty判断是否为空
clear清空
addAll添加多个元素
containsAll查找多个元素是否存在
removeAll删除多个元素
12.1.2 Iterator迭代器
Iterator迭代器,主要用于遍历Collection集合中的元素
实现了Collection接口的集合都有该方法,用于返回一个迭代器
该迭代器仅用于遍历集合,不存放数据
hasNext判断是否有后续元素,返回boolean类型
next返回下一个元素,并将指向的位置下移
一般使用next前首先判断hasNext
12.1.3 增强for循环
for(元素类型 元素名:集合名或数组名){访问元素}
只能遍历集合或数组,底层仍然是迭代器Iterator
12.2 List
List集合中元素是有序的,添加的顺序和取出的顺序一致,并且可以重复
add添加元素
add(a,元素)在a位置处插入元素
addAll(a,元素集合)在a出插入元素集合
indexOf(元素)查找元素在集合中首次出现的位置
lastIndexOf(元素)查找元素在集合中最后一次出现的位置
remove(a)移出a位置的元素
set(a,元素)把a处的元素换成该元素
subList(a,b)取出a到b范围的子集合,含a不含b
List接口下的ArrayList和LinkedList,Vector都可以使用这些方法
12.3 ArrayList
可以放入多个null
ArrayList线程不安全
ArrayList中维护了一个Object类型的数组elementData
如果使用无参构造器创建ArrayList对象,则初始elementData容量为0,第一次添加,则扩容为10,后续每次扩容,容量提升1.5倍;如果指定大小构造器,则初始容量为指定大小,扩容时增大至1.5倍
12.3.1 ArrayList数组扩容(无参构造器)
1.创建空数组
2.进入add方法调用ensureCapacityInternal方法,首先判断添加数组是否为空,若为空则取默认大小和传入的最小大小的最大值,默认大小为10,确定好真正的最小大小
3.进入ensureCapacity方法,首先用modCount记录集合被修改的次数,防止并发。然后判断现有数组长度能否满足最小大小,若不满足则调用grow方法扩容
4.grow方法,新的大小为实际长度加上实际长度向右移位一位,即除以二向下取整。第一次进入实际长度为0,则需要修改新长度为最小大小。如果新长度超过最大数组长度则使用hugeCapacity方法扩容。然后将原有数组copyOf复制并放在新的扩容后的数组中
5.添加元素进数组
transient修饰符,表示后续该属性不会被序列化
在debug时,build设置中的debugger选项中enable alternative view for Collections classes选项会简化显示
12.3.2 指定大小构造器扩容
判断大小是否大于0,如果等于0则指定为默认大小,如果小于0则抛出异常,大于0则创建指定大小的elementData数组
进入add方法判断大小是否够用
12.4 Vector
Vertor底层是一个对象数组,protected Object[] elementData
Vertor线程同步,线程安全,操作方法带有Synchronized
始终单线程则用ArrayList,多线程则用Vector
12.4.1 Vector和ArrayList比较
Vector扩容时,新的数组大小为旧的数组大小加(capacityIncrement>0? capacityIncrement:旧数组大小)
capacityIncrement默认为0,所以每次都返回旧数组大小,即新数组大小为旧数组大小二倍
12.5 LinkedList
底层实现了双向链表和双端队列特点,可以添加任意元素,可以重复,也可以添加null,线程不安全,没有实现同步
LinkedList维护了一个双向链表,其中first和last属性分别指向首尾节点,添加和删除元素效率高
每个节点中维护了prev,next,item三个属性,prev指向前一个,next指向后一个
无参构造器创建LinkedList后,first和last指针为空
12.5.1 add方法源码
调用add方法,首先调用linkLast方法,用l节点存储尾节点,然后新建节点(l,新节点,null)(上一个节点,当前节点,下一个节点),将尾指针指向创建好的新节点。如果l为空,则first指针也指向新建的节点,l不为空则将l的下一个节点指向新节点,最后自增size和modCount
12.5.2 remove方法源码
调用remove方法,首先调用removeFirst方法,如果首元素为空则抛出异常,不为空则返回unlinkList方法,传入首节点
首先将下一个节点存储,然后清空首节点的值,next指针,然后让first指针指向存储好的下一个节点,如果下一个节点为空,则last指向null,否则下一个节点的prev指针为null,size自减,modCount自增
12.5.3 set方法修改
12.5.4 get方法获取
12.5.5 ArrayList和LinkedList比较
改查操作多用ArrayList,增删操作多用LinkedList
不考虑线程并发时选择ArrayList和LinkedList
12.6 Set
无序,添加和取出顺序不一致,取出顺序是固定的,没有索引,不允许重复元素,最多只能包含一个null
主要实现类有HashSet和TreeSet
12.6.1 Set常用方法
Set是Collection子接口,和Collection一致
12.6.2 Set遍历方式
可以使用Iterator迭代器和增强for,不能通过索引获取
12.7 HashSet
HashSet存放元素顺序取决于hash后,再确定索引结果
HashSet底层是HashMap,HashMap底层是数组+链表+红黑树
存放的是Node节点
添加元素时,首先通过hash得到索引值;查找索引位置是否有元素,没有则直接放入,有则equals对比是否相同,不同则插入链表最后;如果链表元素超过TREEIFY_THRESHOLD(默认为8),并且table大小≥MIN_TREEIFY_CAPACITY(默认64),则转换为红黑树
12.7.1 HashSet源码
HashSet构造器创建HashMap对象并返回
add方法:1.首先返回(map.put方法,传入要放入的元素e和PRESENT)==null的boolean值,如果为null则证明元素成功添加,PRESENT为空对象仅用作占位
2.put方法返回putVal(hash(key),key,value,false,true)
3.hash方法求出key的hashCode值,返回hashCode值按位异或hashCode值算数右移16位值,避免碰撞
4.putVal方法,首先判断table(HashMap中的属性,初始为空)是否为空,若为空则调用resize方法,并返回长度
4.1:resize扩容方法,判断原有的大小是否大于0,如果不大于0则将newCap设置为默认值16,Thr临界值为默认容量乘以默认负载因子0.75,新建节点表,初始容量为newCap,并将原有表复制进来,最终返回新表。如果原有大小大于0则容量翻倍,更新临界值。
5.计算哈希值n-1按位与hash,n为表长度,判断存储位置节点是否为空,若为空创建节点,hash为哈希值,key为节点值,value为PRESENT,next为下一个节点
5.1如果不为空则判断是否和存储位置节点的hash值一样,并且加入的key和存储好的key一致,或key内容通过equals比较相同,则不加入
5.2再判断是否是红黑树,如果是红黑树则通过红黑树方式putTreeVal比较
5.3循环比较链表中元素,如果没有相同的则插入元素到最后,发现相同元素则跳出循环
5.4添加元素到链表最后时检查是否达到阈值TREEIFY_THRESHOLD,且数组长度达到64,没达到表长度64则扩容表,达到则转化为红黑树
5.5如果元素重复则返回重复值,不返回null,插入失败
6.modCount++,判断添加后的size是否超过阈值threshold,超过则调用resize扩容
7.调用afterNodeInsertion方法(空方法),后续用来给子类调整链表结构(顺序链表)
4567均在putVal方法中
阈值通常是数组容量*0.75
12.8 LinkedHashSet
是HashSet子类,底层是LinkedHashMap,是数组加双向链表,使用链表维护元素次序,是元素看起来是以插入顺序保存的,同样不允许插入重复元素
双向链表按插入顺序前后连接
数组是HashMap$Node[],存放的是LinkedHashMap$Entry节点,通过静态内部类HashMap.Node 继承
12.9 Map
和Collection并列存在,存储具有映射关系的Key和Value
Map接口常用实现类HashMap,Hashtable,Properties
可以存储任意引用类型数据,是HashMap$Node的数组
key不能重复,但value可以重复,key和value都可以为null,但是key只能有一个null
重复添加key会替换原有value
常用String类作为key
key和value存放在实现了entry接口的HashMap$Node中,set和Collection实际上都是指向了HashMap$Node中的key和value
12.9.1 Map方法
put(key,value)放入
remove删除
get(key)获取
size大小
isEmpty判断是否为空
clear清空
containsKey(key)判断是否包含
12.9.2 HashMap存储机制
创建节点返回的是HashMap$Node对象
为了方便遍历,还会在HashMap中创建EntrySet集合,存放Entry类型元素,实际存放的还是Node节点(向上转型)
Entry中提供了获取key和value的方法
key和value一起被放在Collection中,但是key和value也可以单独放在Key和Value的set中
12.9.3 遍历Map方法
第一种方法:先用keySet方法取出所有key存放在set中,然后通过遍历set中的key取出value,
第二种方法:使用map自带的values方法取出所有value存入Collection中,然后遍历
第三种方法:先通过entrySet方法取出entrySet放入set中,然后遍历entrySet,每个都向下转型为Map.Entry类型,通过getValue方法获取value和key
12.10 HashMap
HashMap不保证映射顺序,没有实现同步
HashMap底层维护了Node类型的table
创建对象时,加载因子初始化为0.75
添加元素时,通过key的值得到在table的索引,然后判断索引处是否有元素,没有则添加,相等则替换value,不相等再判断是树还是链表,作出相应处理,容量不够则扩容
第一次添加容量为16,临界值12,后续扩容容量翻倍,临界值也翻倍
当链表元素超过8并且table大小超过64时进行树化
12.10.1 HashMap源码
构造器会初始化加载因子,创建Node数组
put方法:返回putVal(key的hash值,key,value,false,true)
hash方法:key为空则返回0,否则返回key的hashCode按位异或hashCode算术右移十六位
其他与HashSet几乎相同,除了key相同时替换value
12.11 Hashtable
存放键值对且都不能为null
用法和HashMap一样,但是线程安全
底层数组Hashtable$Entry初始化大小为11,临界值8
大于等于临界值时扩容,扩容时旧大小向左移一位再+1
12.11.1 HashMap和Hashtable对比
12.12 Properties
继承了Hashtable类并且实现了Map接口,使用键值对保存数据
可以用于从properties文件中加载到Properties对象并进行读取和修改
12.13 如何选择使用的集合
1.判断存储对象是单列还是双列,单列使用Collection,双列使用Map
Collection中,允许重复用List,不允许重复用Set
List中,增删多用LinkedList(双向链表),改查多用ArrayList(可变数组)
Set中,无序用HashSet(哈希表),有序有TreeSet
Map中,键无序用HashMap(哈希表),有序用TreeMap,插入和取出顺序一致
用LinkedHashMap,读取文件用Properties
12.14 TreeSet
使用无参构造器创建TreeSet时仍然是无序的,希望有序需要传入一个比较器对象赋给TreeSet底层的TreeMap
使用匿名内部类Comparator重写compare方法,返回比较方法,正/负值表示升序或降序,相等(为0)则不加入
12.15 TreeMap
使用无参构造器创建TreeMap时仍然是无序的,希望有序需要传入一个比较器对象赋给TreeMap
将key和value封装在Entry对象中,放入root
比较器比较相等则不插入
12.16 Collection工具类
是一个操作Set,List,Map等集合的工具类,提供了许多静态方法对集合元素进行排序查询和修改等操作
reverse(List)反转List中的元素
shuffle(List)对元素进行随机排序
sort(List)根据元素自然顺序进行升序排序
sort(List,Comparator)根据排序器顺序排序
swap(List,i,j)i和j处元素交换
Object max/min(Collection)返回自然顺序最大/小的元素
Object max(Collection,Comparator)返回排序器最大的元素
void copy(List dest,List src)将src内容复制到dest中
boolean replaceAll(List list,Object oldVal,Object newVal)
999 小tips
Java API文档中解释了java中包含的基础类及其使用方法
在控制台,输出tab键可以实现命令补全
担心定义的变量或者包名与关键字重复,就在后面加下划线
多行注释快捷键:Ctrl + /
复制整行粘贴至下一行:Ctrl+Shift+D
删除整行:Ctrl+Shift+K
终止运行:Ctrl
IDEA中src存放源码文件,out存放编译后的class文件
IDEA快捷键
删除当前行:Ctrl+Y
复制当前行到下一行:Ctrl+D
代码补全:alt+/
添加/取消注释:Ctrl+/
导入该行需要的类:先配置auto import,然后使用alt+回车
快速格式化代码(调整缩进和空格):Ctrl+alt+L
快速运行:shift+f10
快速新建属性的构造器:alt + insert(笔记本加fn)
查看类继承层级关系:Ctrl+H
定位方法:Ctrl+B
自动分配变量名:new 对象().var+回车
system.out模板:sout+回车
for循环模板:fori+回车
main主方法模板:main+回车
alt+enter快捷键实现抽象方法
try-catch快捷键:Ctrl + alt + t
通过while判断hasNext和获取next:itit+回车
增强for循环快捷键:I+回车