面向对象
封装
对象代表什么,就得封装对应数据,并提供数据对应行为
例子1:人画圆
对象:圆、人
则画圆的方法应该写在圆的类中(画圆会对应到圆的半径等数据)
public class Circle {
double radius;
public void draw(){
System.out.println("根据半径" + radius + "画一个圆");
}
}
例子2:人关门
对象:人、门
关门的方法应该写在门里面
public class Door {
public void close(){
System.out.println("关门");
}
}
继承
- 子类可以继承父类成员变量,但是不能直接访问私有成员变量
- 子类不能继承父类构造方法
- 子类可以继承父类非私有成员方法,不能继承私有成员方法
成员方法继承:虚方法表,从父到子
方法重写:覆盖虚方法表
- 重写方法的名称、形参列表必须与父类中一致
- 子类重写父类方法时,访问权限必须大于等于父类(空<protected<public)
- 子类重写父类方法时,返回值类型必须小于等于父类
- 只有添加到虚方法表的方法才能被重写
多态
同类型的对象表现出的不同形态
- 有继承关系
- 有父类引用指向子类对象
- 有方法重写
调用成员变量: 编译看左边, 运行看左边
调用成员方法: 编译看左边, 运行看右边
多态的优势:
- 在多态形式下,右边对象可以实现解耦合,便于拓展和维护
- 定义方法的时候,使用父类型作为参数,可以接收所有子类对象,体现多态的拓展性与便利
多态的弊端:
- 不能调用子类特有方法(编译看左边),解决方案:强转回子类
抽象类与抽象方法
抽象方法必须被子类强制重写,抽象方法所在的类就叫做抽象类
- 抽象类不能被实例化,可以创建多态对象
- 抽象类中不一定有抽象方法,有抽象方法的一定是抽象类
- 可以有构造方法:当创建子类对象时,给属性进行赋值
- 抽象类的子类要么重写抽象类中的所有抽象方法,要么是抽象类
接口
给多个但不是全部类定义规则,对行为的抽象
- 接口不能实例化
- 接口的子类(实现类)要么重写接口中所有抽象方法,要么是抽象类
- 接口和类可以单实现,也可以多实现
- 实现类还可以在继承一个类的同时实现多个接口
接口成员特点
- 成员变量
- 只能是常量
- 默认修饰符:public static final
- 构造方法
- 没有
- 成员方法
- JDK7以前:
- 只能定义抽象方法,默认修饰符public abstract
- JDK8:
- 可以定义有方法体的默认方法,需要用default修饰
- 作用:解决接口升级问题
- 默认方法不强制重写,但是如果被重写需要去掉default关键字
- 如果实现了多个接口,多个接口中存在相同名字的默认方法,子类就必须对该方法进行重写
- 可以定义静态方法
- JDK9:
- 接口中可以定义私有方法
- 提取重复代码
- JDK7以前:
多个接口方法重名,只需要重写一次重名方法
接口和接口是继承关系,可以单继承也可以多继承
内部类
内部类表示的事物是外部类的一部分
内部类访问特点
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
内部类分类:
- 成员内部类
- 静态内部类
- 局部内部类
- 定义在方法内,类似局部变量
- 外界无法直接使用,需要在方法内创建对象并使用
- 可以直接访问外部类的成员,也可以访问方法内的局部变量
- 匿名内部类
- 隐藏了名字的内部类,可以写在成员位置,也可以写在局部位置
- 使用场景:方法的参数是接口或类,且实现类只需要使用一次
字符串
字符串拼接底层原理
拼接的时候无变量,全是字符串:触发字符串的优化机制,在编译时就已经是最终的结果
编译前的java文件:
public class Test {
public static void main(String[] args) {
String s = 'a' + 'b' + 'c';
System.out.println(s);
}
}
编译后的class文件:
public class Test {
public static void main(String[] args) {
//直接赋值,会复用串池
String s = "abc";
System.out.println(s);
}
}
含变量的字符串拼接:
JDK8之前编译时采用StringBuilder进行拼接
编译前的java文件
public class Test {
public static void main(String[] args) {
String s1 = "a";
String s2 = s1 + "b";
String s3 = s2 + "c";
System.out.println(s3);
}
}
编译后的class文件
public class Test {
public static void main(String[] args) {
String s1 = "a";
String s2 = new StringBuilder().append(s1).append("b").toString();
String s3 = new StringBuilder().append(s2).append("c").toString();
System.out.println(s3);
}
}
toString方法会创建一个new String
JDK8含变量字符串拼接:预估字符串长度创建数组,再将数组转换成字符串
结论:字符串变量拼接不要直接使用+,会在底层创造多个对象,浪费性能和时间,尽量使用StringBuilder
StringBuilder源码分析
- 默认创建一个长度为16的字节数组
- 添加的内容长度小于16,直接存
- 添加的内容大于16会扩容(原来的容量*2+2)
- 如果扩容之后还不够,以实际长度为准
修饰符
final
- final修饰方法:表明该方法是最终方法,不能被重写
- final修饰类:表明该类是最终类,不能被继承
- final修饰变量:叫做常量,只能被赋值一次
final修饰基本数据类型:记录的值能不发生改变
final修饰引用数据类型:记录的地址值不能发生改变,内部的属性值还是可以改变的
权限修饰符
(private < 缺省 < protected < public)
修饰符 | 同一个类中 | 同一个包中其他类 | 不同包下的子类 | 不同包下的无关类 |
---|---|---|---|---|
private | √ | |||
空着不写/缺省 | √ | √ | ||
protected | √ | √ | √ | |
public | √ | √ | √ | √ |
实际开发中一般只用private和public
- 成员变量私有
- 方法公开
- 如果方法中的代码是抽取其他方法中共性代码,这个方法一般也私有
权限修饰符一般只能修饰成员变量,不能修饰局部变量
代码块
构造代码块
- 写在成员位置的代码块
- 可以把多个构造方法中重复的代码抽取出来
- 创建本类对象时先执行构造代码块再执行构造方法
public class Student() {
private String name;
private int age;
{
System.out.println("开始创建对象");
}
public Student() {
System.out.println("空参构造");
}
public Student(String name, int age) {
System.out.println("带参构造");
this.name = name;
this.age = age;
}
}
抽取多个构造中重复代码的其他方法
public class Student() {
private String name;
private int age;
public Student() {
this(null, 0);
}
public Student(String name, int age) {
System.out.println("开始创建对象");
this.name = name;
this.age = age;
}
}
也可以将重复代码写成一个方法在两个构造方法中分别调用
静态代码块
格式:static{}
特点:通过static关键字修饰,随着类的加载而加载,并且自动触发、只执行一次
应用场景:初始化数据
常用API
Math类
方法名 | 说明 |
---|---|
abs | 获取参数绝对值 |
ceil | 向上取整 |
floor | 向下取整 |
round | 四舍五入 |
max | 获取较大值 |
pow | 次幂计算 |
random | 随机数 |
System类
方法名 | 说明 |
---|---|
exit | 终止当前运行的jvm |
currentTimeMillis | 返回当前系统的时间毫秒值形式 |
arraycopy | 数组拷贝 |
Runtime类
表示当前虚拟机运行环境,非静态,需要先获取对象
方法名 | 说明 |
---|---|
getRuntime | 创建对象 |
exit | 停止虚拟机 |
availableProcessors | 获得CPU线程数 |
maxMemory | JVM能从系统中获取总内存的大小(单位Byte) |
totalMemory | JVM已经从系统获取总内存的大小(单位Byte) |
freeMemory | JVM剩余内存大小(单位Byte) |
exec | 运行cmd命令 |
Object类
方法名 | 说明 |
---|---|
toString | 返回对象的字符串形式 |
equals | 比较两个对象是否相等 |
clone | 对象克隆(浅克隆) |
- 浅克隆:克隆的对象中new的引用数据类型指向相同的地址值
- 深克隆:克隆的对象中new的引用数据类型指向不同地址值(创建新的),非new的引用数据类型(直接赋值的字符串)复用(串池)
Objects类
方法 | 说明 |
---|---|
equals | 先做非空判断,比较两个对象 |
isNull | 判断对象是否为null |
nonNull | 判断对象是否为null,与isNull结果相反 |
BigInteger类
构造方法 | 说明 |
---|---|
BigInteger(int num, Random rnd) | 获取随机大整数,范围[0 ~ 2的num次方-1] |
BigInteger(String val) | 通过传递字符串获取指定大整数 |
BigInteger(String val, int radix) | 获取指定进制的大整数 |
对象一旦创建里面的数据就不能发生改变,进行计算后会新产生一个BigInteger对象记录计算结果的值
静态创建方法内部优化:提前把-16 ~ 16先创建好BigInteger整数,多次获取不会创建新的
import java.math.BigInteger;
public class Test {
public static void main(String[] args) {
BigInteger bd1 = BigInteger.valueOf(16);
BigInteger bd2 = BigInteger.valueOf(16);
System.out.println(bd1 == bd2); //true
}
}
方法 | 说明 |
---|---|
static BigInteger valueOf(long val) | 静态方法创建对象 |
add | 加 |
subtract | 减 |
multiply | 乘 |
divide | 除,获取商 |
divideAndRemainder | 除,获取商和余数 |
equals | 比较是否相同(内部属性值) |
pow | 次幂 |
max/min | 较大/较小 |
intValue | 转为int类型,超出范围有误 |
BigDecimal类
构造方法 | 说明 |
---|---|
BigDecimal(String val) | 通过传递字符串获取指定小数 |
静态创建方法内部优化:提前把0 ~ 10的整数先创建好对象,多次获取不会创建新的
方法 | 说明 |
---|---|
static BigDecimal valueOf(double val) | 静态方法获取对象 |
add | 加 |
subtract | 减 |
multiply | 乘 |
divide(BigDecimal val) | 除 |
divide(BigDecimal val, 精确位数, 舍入模式) | 除 |
正则表达式
String.match(String regex)
字符类 | 只匹配一个字符 |
---|---|
[abc] | 只能是a,b或c |
[^abc] | 除了a,b,c之外的任何字符 |
[a-zA-Z] | a到z A到Z,包括 |
[a-z[A-Z]] | 同上 |
[a-z&&[def]] | z-a和def的交集 |
[a-z&&[^bc]] | a-z和非bc的交集 |
[a-z&&[^m-p]] | a-z和非m-p的交集 |
预定义字符 | 只匹配一个字符 |
---|---|
. | 任意字符 |
\d | 一个数字 |
\D | 非数字 |
\s | 一个空白字符:[\t\n\x0B\f\r] |
\S | 非空白字符 |
\w | 英文、数字、下划线 |
\W | 一个非单词字符 |
注:在java代码中使用预定义字符需要\,前一个\代表转义 |
数量词 | X代表任意字符 |
---|---|
X? | X, 一次或零次 |
X* | X, 零次或多次 |
X+ | X, 至少一次 |
X | X, 正好n次 |
X | X, 至少n次 |
X | X, 至少n次但不超过m次 |
特殊结构 | (捕获组和非捕获组) |
---|---|
(? |
捕获X作为一个捕获组(名称为name) |
(?:X) | 捕获X作为一个非捕获组 |
(?idmsuxU-idmsuxU) | 打开(idmsuxU)或关闭(-idmsuxU)i d m s u x U模式 |
(?idmsux-idmsux:X) | 捕获X作为一个非捕获组,打开或关闭i d m s u x模式 |
(?=X) | 检查是否满足X条件,最终结果不包含X |
(?!X) | 检查是否不满足X条件,最终结果不包含X |
(?<=X) | 检查当前位置之前是否满足X条件,最终结果不包含X |
(?<!X) | 检查当前位置之前是否不满足X条件,最终结果不包含X |
(?>X) | 独立非捕获组,防止回溯 |
flags i d m s u x U
标志 | 描述 |
---|---|
i | 忽略大小写 |
d | UNIX行模式:仅\n被视为结束符(影响^和$的匹配) |
m | 多行模式:^和$匹配每一行的开始和结束,而不仅仅是整个字符串 |
s | 单行模式:.匹配包括换行符在内的所有字符 |
u | Unicode感知:启用Unicode匹配规则 |
x | 拓展模式:忽略正则表达式中的空白和注释,用于提高可读性 |
U | 默认懒惰匹配:将量词(如*、+)默认设为懒惰模式 |
包装类
包装类:基本数据类型的引用类型
Integer获取对象(JDK5以前)valueOf()方法:预先缓存数组-128 ~ 127,超出范围会创建新的对象
public class Test {
public static void main(String[] args) {
Integer i1 = Integer.valueOf(127);
Integer i2 = Integer.valueOf(127);
System.out.println(i1 == i2); //true
Integer i3 = Integer.valueOf(128);
Integer i4 = Integer.valueOf(128);
System.out.println(i3 == i4); //false
}
}
JDK5以后:自动装箱和自动拆箱
- 自动装箱:基本数据类型 -> 包装类对象 (自动调用valueOf())
- 自动拆箱:包装类对象 -> 基本数据类型 (自动调用intValue())
键盘录入tips:使用nextLine()方法进行键盘录入,再使用parse方法进行转换(char无parse方法),可以防止遇到空格和制表符停止的情况
集合
单列集合:
- list: 有序(存取顺序)、可重复、有索引
- set: 无序(存取顺序)、不可重复、无索引
ArrayList底层原理,源码分析
ArrayList<Integer> list = new ArrayList<>();
//假设list1为已经存有10个元素的ArrayList
list.addall(list1);
list.add(100);
ArrayList空参构造 初始化elementData为空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//elementData: Arraylist中的Object[]数组
transient Object[] elementData;
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA: 空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
addall方法
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray(); //将集合参数c转换为数组a
modCount++; //modify count
int numNew = a.length;
if (numNew == 0)
return false;
Object[] elementData;
final int s;
if (numNew > (elementData = this.elementData).length - (s = size)) //size 为当前数组长度,同时表示新元素的起始位置
elementData = grow(s + numNew); //如果数组容量不够,则扩容
System.arraycopy(a, 0, elementData, s, numNew); //数组容量足够,直接将数组a复制到elementData中
size = s + numNew;
return true;
}
grow方法扩容
private Object[] grow(int minCapacity) {
int oldCapacity = elementData.length;
if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //原数组不为空
int newCapacity = ArraysSupport.newLength(oldCapacity,
minCapacity - oldCapacity, /* minimum growth 需要新增的最小增长量*/
oldCapacity >> 1 /* preferred growth 位运算右移1为,相当于1/2,即原数组的一半*/);
return elementData = Arrays.copyOf(elementData, newCapacity);
} else { //原数组为空
return elementData = new Object[Math.max(DEFAULT_CAPACITY, minCapacity)]; //创建一个新数组,长度为DEFAULT_CAPACITY和minCapacity的最大值,DEFAULT_CAPACITY为10
}
}
public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
// preconditions not checked because of inlining
// assert oldLength >= 0
// assert minGrowth > 0
int prefLength = oldLength + Math.max(minGrowth, prefGrowth); //最小增长量超过原数组长度一般,则返回原数组长度+最小增长量;否则返回原数组长度的1.5倍
if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
return prefLength;
} else {
// put code cold in a separate method
return hugeLength(oldLength, minGrowth);
}
}
总结:
- ArrayList空参构造创建的初始化数组为空
- 第一次添加元素时,如果添加数量小于10个,则新创建的数组长度为10,否则长度为添加个数,将元素拷贝到新数组中
- 扩容时,默认扩容量为原数组的一半,最小增长量如果超过原数组的一半,则扩容量为最小增长量