重载与重写的区别
重载
重载发生在同一个类中,方法名相同,参数类型,个数,顺序不同返回值和访问修饰符可以不同
重写
重写发生在子类中,方法名,参数列表必须相同,返回值范围小于等于父类,抛出异常范围小于等于父类,访问修饰符大于等于父类,如果父类方法修饰符为private则不可以重写该方法
String,StringBuffer,StringBuilder的区别,String为什么不可变
可变性
String类中使用final关键字字符数组保存字符串,private final char value[],所以string对象是不可变的,而StringBuffer和StringBuilder都继承AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串,但是没用final关键字修饰,所以这两种对象都是可变的
AbstractStringBuilder.java
1 | abstract class AbstractStringBuilder implements Appendable, CharSequence { |
线程安全性
String中的对象是不可变的,也可以理解为常量,线程安全
AbstractStringBuilder是StringBuilder和StringBuffer的公共父类,定义了一些字符串的基本操作,如expandCapacity、append、insert、indexof等公共方法,StringBuffer对方法加了同步锁或对调用的方法加入了同步锁,所以线程是安全的而StringBuilder并没有对方法加同步锁,所以线程是不安全的
性能
每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象
StingBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder比StringBuffer性能高10%·15%左右,但要冒多线程下不安全的风险
总结
操作量少的数据String
单线程操作字符串缓冲区下操作大量数据StringBuilder
多线程操作字符串缓冲区下操作大量数据StringBuffer
自动装箱与拆箱
装箱:将基本类型用他们对应的引用类型包装起来
拆箱:将包装类型转换为基本类型
注:基本类型与引用类型用==判断相等时注意空指针异常(https://gpdstudy.club/posts/integer-bug/#more)
1 | int a = 1; |
==与equals
==
它的作用是判断两个对象的地址是否相等,即判断两个对象是不是同一个对象基本类型比值,引用数据类型比较的是内存地址
equals()
它的也作用是判断两个对象是否相等,但他一般有两种情况
类没有重写equals方法通过equals()比较该类型的两个对象时,等价于==比较
类重写了equals()方法,一般,我们重写equals方法来比较两个对象的内容相等相等返回true
举个例子
1 | public class test{ |
String中的equals()方法是被重写过的,因为Object的equals方法是比较对象的内存地址而String中的equals()方法比较的是对象的内容
当创建String类型的对象时,虚拟机会在常量池中查找有没有已存的值和要创建的值相等,如果有就把他给当前对象引用,如果没有就在常量池中重新创建一个String对象
final关键字
final关键字主要用在三个地方:变量,方法,类
- 对于一个final变量,如果是基本数据的类型的变量,则其数值一旦初始化后不能在更改.如果是引用类型的变量,则在其初始化后便不能再让其指向另一个对象
- 当用final修饰一个类时,表示这个类不能被继承。final类中的所有成员方法都会被隐式指定为final方法
- 用final修饰的方法原因有两个一是因为把方法锁定,以防任何继承类修改它的含义二是因为效率早期java实现版本中,会将final方法转为内嵌调用,但如果方法过于庞大,可能看不到内嵌调用带来的性能提升(现在java版本已不需要final方法进行优化)类中所有private方法都隐式指定为final
Object类中的常见方法总结
Object是所有类的父类,主要提供了11个方法
native方法,用于返回当前运行时对象的Class对象,使用了final关键字,不允许重写
1
public final native Class<?> getClass();
native方法,用于返回对象的哈希码,主要使用在哈希表中(HshMap)
1
public native int hashCode();
用于比较两个对象的地址是否相同,string重写改为了比较两个对象的内容
1
2
3public boolean equals(Object obj) {
return (this == obj);
}native方法,用于创建并返回当前对象的一份拷贝,一般情况下,对于任何对象x,表达式x.clone()!=x为true,而x.clone().getClass()==x.getClass()为true。Object本身没有实现Cloneable接口,所以不重写clone方法并调用的话会发生CloneNotSupportedException异常
1
protected native Object clone() throws CloneNotSupportedException;
返回类的名字@实例的哈希码的16进制的字符串(建议Object子类都重写这个方法)
1
2
3public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念),如果有多个线程在等待只会随机唤醒一个
1
public final native void notify();
native方法,并且不能重写。与notify方法一样,区别在于会唤醒在此对象监视器上等待的所有线程,而不是其中一个
1
public final native void notifyAll();
native方法,并且不能重写。暂停线程的执行,注:sleep方法没有释放锁,而wait释放了锁,timeout是等待时间
1
public final native void wait(long timeout) throws InterruptedException;
与上个方法一样,只是多了nanos参数,这个参数表示额外时间(以毫秒为单位,0-999999),所以超时时间还要加上nanos毫秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {
timeout++;
}
wait(timeout);
}和之前两个wait方法一样,只不过该方法一直等待,没有超时
1
2
3public final void wait() throws InterruptedException {
wait(0);
}实例被垃圾回收器回收时触发的操作
1
protected void finalize() throws Throwable { }
java中的异常处理
在java中,所有的异常都有一个共同的父类java.lang.Throwable,Throwable有两个子类Exception(异常)和Error(错误)两者都是异常处理的重要子类,各自都包含大量子类
Error(错误)
**Error(错误)是程序无法处理的错误,表示运行程序中较严重的问题。大多数错误与代码编写者执行的操作无关,而表示代码运行时JVM出现的问题
- java虚拟机运行错误(Virtual MachineError)
- 类定义错误(NoClassDefFoundError)
- ……
Exception(异常)
Exception(异常)是程序本身可以处理的异常Exception有一个重要的子类RuntimeException。RuntimeExceptioon由java虚拟机抛出
- NullPointerException,要访问的变量没有任何对象引用时,抛出该异常
- ArithmeticException,算术运算异常,整数除0时抛出
- ArrayIndexOutOfBoundsException,下标越界异常
注:异常和错误的区别在于异常能被程序本身处理,而错误不可以
Throwable类常用方法
getMessage():返回异常发生时的详细信息
toString():返回异常发生时的简要描述
getLocalizedMessage():返回异常对象的本地化信息。使用Throwable子类重写这个方法可以声称本地化信息,如果子类没有重写该方法,则返回与getMessage()结果相同
printStackTrace():在控制台打印Throwable对象封装的异常信息
异常处理
try块:用于捕捉异常,其后可接零或多个catch块,如果没有catch块则必须跟一个finally块
catch块:用于处理try捕捉到的异常
finally块:无论是否捕捉到异常都会执行当在try或catch中遇到return语句时,finally语句在返回前执行
finally不会执行的条件
finally中发生了异常
在finally前用System.exit()退出程序
程序所在线程死亡
关闭CPU
获取键盘输入
通过Scanner
1 | Scanner input = new Scanner(System.in); |
通过BufferedReader
1 | BufferedReader input =new BufferedReader(new InputStreamReader(System.in)); |
接口与抽象类的区别
- 接口方法默认public,所有方法在接口中不能有实现在java8开始接口方法可以有默认实现抽象类可以有非抽象方法
- 接口中的实例变量默认是final类型的而抽象类不一定
- 一个类可以实现多个接口,但只能实现一个抽象类
- 接口不能被new实例化,但可以声明,但是必须引用一个实现该接口的对象,从设计层来说,抽象是对类的抽象,是一种模板设计,接口是行为的接口,是一种行为规范
jdk8中,接口也可以定义静态方法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现了两个接口,接口定义了一样的默认方法,必须重写,不然会报错(https://github.com/Snailclimb/JavaGuide/issues/146/ )
java集合
ArrayList与LinkedList异同
- 线程安全
ArrayList和LinkedList都是不同步的,线程不保证安全
- 底层数据结构
ArrayList底层是Object数组,LinkedList底层使用的是双向链表数据结构(1.6之前是循环链表,1.7取消了循环)
- 插入和删除是否受元素位置的影响
ArrayList采用数组存储,所以插入和删除元素的时间复杂度受元素的位置影响,比如:执行add(E e)
方法的时候,ArrayList会默认将指定元素追加到末尾,这种情况下时间复杂度为O(1),但如果是在指定位置i插入和删除元素add(int index E e)
时时间复杂度为O(n-i)。因为在执行此方法时集合中的第i个元素个i之后(n-i)个元素都要执行向后/前位移一位的操作LinkedList采用链表存储,所以插入删除元素时间复杂度不受元素位置影响,都是近似O(1),而数组为近似O(n)
- 是否支持快速随机访问
LinkedList不支持高效的随机元素访问,而ArrayList支持get(int index)
- 内存空间占用
ArrayList空间浪费在list列表结尾会预留一定的容量空间,而LinkedList空间花费在每一个元素,每个元素消耗的空间都比ArrayList更多,因为LinkendList要存放直接后继和直接前驱以及数据
- list的遍历
实现了RandomAccess接口的list,优先使用for循环,其次foreach
未实现RandomAccess接口的list,优先选择iterator遍历(foreach底层也是有iterator实现的)大size的数据不要使用普通for循环
ArrayList和Vector
Vector类所有方法都是同步的。可以由两个线程安全的访问一个Vector对象,但一个线程访问Vector的话代码要在同步操作上耗费大量的时间
ArrayList不是同步的,在不需要保证线程安全的时候建议使用