目录
13.hashCode()有什么用?为什么重写equals方法必须重写hashcode方法?
13.String、StringBuilder、StringBuffer的区别?
16.Checked Exception 和 Unchecked Exception有什么区别?
1.iterator的三个方法:
1.hasNext():没有指针下移操作,只是判断下一个元素是否存在;
2.next():指针下移,并返回指针所指向的元素;
3.remove():删除当前指针所指向的元素,一般与next()配合使用;
1、当创建完成指向某个集合或者容器的Iterator对象时,这时的指针其实指向的是第一个元素的上方,即指向空。
2、当调用hasNext方法的时候,只是判断下一个元素的有无,并不移动指针。
3、当调用next方法的时候,向下移动指针,并且返回指针指向的元素,如果指针指向的内存中没有元素,会报异常。
4、remove方法删除的元素是指针指向的元素。如果当前指针指向的内存中没有元素,那么会抛出异常。
2.add(),offer(),poll()
1.addLast()和add():两者都将元素链接到队列或链表的尾部,区别是后者会返回true;
2.add()和offer():在链表中二者的使用是一致的,在队列中,因为有容器大小的限制,add()方法如果因为容量限制添加失败,会抛出IllegalStateException异常,而offer()方法则直接返回false表示添加失败,不会抛出异常;
3.offerLast()和add()效果一样;
4.pollFirst与removeFirst():前者删除并返回队首元素,若队列为空,返回null;后者也是删除并返回队首元素,但是若队列为空,会抛出异常;
总结:需要链接元素到队尾时,优先使用offer(),查看元素优先使用peek(),删除元素优先使用poll();
3.Java和C++的区别?
- java不提供指针来直接访问内存,程序内存更加安全;
- java的类是单继承的,C++支持多继承,但是java的接口是可以实现多个的;
- java有自动内存管理垃圾回收机制,不需要程序员手动释放无用内存;
- C++同时支持方法重载和操作符重载,但是java只支持方法重载;
4.Java包装类型的缓存机制?
Java基本数据类型的包装类型大部分都用到了缓存机制来提升性能。
Byte、Short、Integer、Long这四种包装类型默认创建了[-128,127]的相应类型的缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean直接返回True或False;所以在涉及到包装类型的比较时,推荐使用equals方法;
5.为什么浮点数在运算时会有精度丢失的风险?怎么解决?
计算机在表示一个数时,宽度是有限的,无限循环小数存储在计算机时,只能被截断,就会发生精度丢失的现象。
解决:BigDecimal可以实现对浮点数的运算,并且不会丢失精度。
在使用BigDecimal时,为了防止精度丢失,推荐使用BigDecimal(String val)构造方法或者BigDecimal.valueOf(double val)静态方法来创建对象。不要只用BigDecimal(double val),存在精度损失风险。
BigDecimal的加法用add方法,减法用subtract方法,乘法用multiply方法,除法用divide方法,其中除法用三参数的版本,指定保留小数的位数和四舍五入的规则。
BigDecimal的大小比较用compareTo方法,a.compareTo(b)返回1代表a>b,返回0表示a=b,返回-1表示a<b;为什么不使用equals方法是因为equals不光比较值的大小,还比较精度,例如1.0和1.00会返回false;
6.成员变量和局部变量的区别?
- 语法形式:从语法形式上看,成员变量属于类,局部变量是在代码块或者方法中定义的变量或者是方法的参数;成员变量可以被private、public、static等修饰符修饰,而局部变量不行;但是,二者都能被final修饰;
- 存储方式:从变量在内存中的存储方式上看,如果成员变量被static修饰,它是属于类的,未被static修饰,它是属于实例的;而对象存在于堆内存中,成员变量存在于栈内存中;
- 生存时间:从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而产生,而局部变量随着方法的调用而自动产生,随着方法调用的结束自动消亡;
- 默认值:从变量是否有默认值看,成员变量如果没有被赋初始值,则自动会以类型的默认值赋值(一种情况例外,被final修饰的成员变量必须显式地赋值),而局部变量不会自动赋值;
7.静态方法为什么不能调用非静态成员?
- 静态方法是属于类的,在类加载的时候就会分配内存,可以通过类名直接访问。而非静态成员属于类的实例,只有在对象实例化之后才存在,需要通过类的实例对象去访问。
- 在类的非静态成员不存在的时候静态方法就已经存在了,此时调用在内存中还不存在的非静态成员,属于非法操作;
8.面向对象三大特征?
- 封装:封装是指把一个对象的内部信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息;但是可以提供一些可以被外界访问的方法来操作属性;(可以联想空调和遥控器的例子);
- 继承:不同类型的对象,相互之间经常有一定数量的相同点;继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或者新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,可以提高代码的重用,程序的可维护性,节省大量创建新类的时间,提高我们的开发效率。
- 多态:表示一个对象具有多种状态,具体表现为父类的引用指向子类的实例。
多态的特点:
1.对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
2.引用类型发出的方法到底调用的是哪个类中的方法,必须在程序运行期间才能确定;
3.多态不能调用“只在子类存在而在父类中不存在”的方法;
4.如果子类重写了父类的方法,真正执行的是子类中重写后的方法, 否则执行的是父类 中的方法。
9.抽象类和接口的共同点与区别?
共同点:
1.都不能被实例化;
2.都可以包含抽象方法;
3.都可以有默认实现的方法(Java8开始可以利用default关键字在接口中定义默认方法)
区别:
1.接口主要用于对类的行为进行约束,实现了某个接口就有了相应的行为。抽象类主要用于代码复用,强调的是所属关系;
2.一个类只能继承一个类,但可以实现多个接口;
3.接口中的成员变量只能是public static final类型的,不能被修改且必须有初值,而抽象类的成员变量默认default,可在子类中被重新定义,也可被重新赋值;
10.深拷贝和浅拷贝的区别?什么是引用拷贝?
浅拷贝:浅拷贝会在堆创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象;
深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象;
引用拷贝:两个不同的引用指向同一个对象;
11.Object类中的常见方法?
/**
* native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
*/
public final native Class<?> getClass()
/**
* native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
*/
public native int hashCode()
/**
* 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
*/
public boolean equals(Object obj)
/**
* native 方法,用于创建并返回当前对象的一份拷贝。
*/
protected native Object clone() throws CloneNotSupportedException
/**
* 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
*/
public String toString()
/**
* native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
*/
public final native void notify()
/**
* native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
*/
public final native void notifyAll()
/**
* native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
*/
public final native void wait(long timeout) throws InterruptedException
/**
* 多了 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。。
*/
public final void wait(long timeout, int nanos) throws InterruptedException
/**
* 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
*/
public final void wait() throws InterruptedException
/**
* 实例被垃圾回收器回收的时候触发的操作
*/
protected void finalize() throws Throwable { }
12.==和equals的区别?
对于基本数据类型来说,==比较的是值
对于引用数据类型来说,==比较的是对象的内存地址
equals方法只能用来判断两个对象是否相等,不能用于判断基本数据类型的变量。equals方法存在于Object类中,而Object类是所有类的直接或间接父类,因此所有的类都有equals方法;
13.hashCode()有什么用?为什么重写equals方法必须重写hashcode方法?
hashCode的作用是获取对象的hash码,也称为散列码,这个哈希码的作用是确定对象在哈希表中的索引位置。
当我们把对象加入HashSet时,会首先计算对象的哈希码来判断对象加入的位置,同时也会与已经加入的对象的哈希码进行比较,如果没有相同的HashCode,HashSet会假设对象没有重复出现。但如果发现了有HashCode相同的对象,这时会调用equals方法来检查HashCode相同的对象是否真的相同,如果二者相同,就不会让其加入HashSet,如果不同的话,就会重新散列到其他位置,这样就大大减少了equals的次数,相应提高了执行的速度。
总结:如果两个对象的哈希值相等,它们并不一定相等(哈希碰撞)
如果两个对象的哈希值相等且equals方法返回true,那两个对象真的相等
如果两个对象的哈希值不相等,可以认为两个对象就是不相等的
如果只重写equals方法,那么在判断hashcode时,两个相等对象的hashcode可能也不相等,导致重复进入HashSet集合中
13.String、StringBuilder、StringBuffer的区别?
1.可变性:
String是不可变的,而StringBuilder和StringBuffer都是可变的,二者都继承自AbstractStringBuilder类,在这个类中底层也是使用字符数组来存储字符串,不过没有使用private和final修饰,而且该类还提供了很多修改字符串的方法如append;
2.线程安全性
String中的对象是不可变的,常量,是线程安全的。AbstractStringBuilder是StringBuilder和StringBuffer的公共父类,定义了很多修改字符串的方法。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法加同步锁,所以是非线程安全的。
3.性能
每次对String类型进行改变的时候,都会生成一个新的String对象,然后将指针指向新的String对象。StringBuffer每次会对StringBuffer对象本身进行操作,而不是生成新的对象并更改引用。相同情况下,使用StringBuilder比使用StringBuffer能提高10%-15%左右的性能,但是要冒线程不安全的风险。
14.String为什么是不可变的?如何实现一个不可变类?
string本质是一个char数组。(private final char value[ ]),注意JDK9开始改成了byte[ ]数组,节省内存空间,提高内存利用率;
1.保存字符串的数组被final修饰且为私有的,并且String类没有提供/暴露修改这个数组的方法;
2.String类被final修饰导致其不能被继承,进而避免了子类破坏String的不可变性;
3.当然也还是会有修改的需求,比如replace方法,这时候需要返回一个新的对象作为结果;
4.实现不可变类的方法可以参考上面的String类的设计;
15.Exception和Error的区别?
在Java中,所有的异常都有一个共同的祖先,java.lang包中的Throwable类。而Exception和Error是它的两个子类。
Exception:程序本身可以处理的异常,可以通过catch来捕获,Exception又可以分成CheckedException(受检查异常,必须处理)和Unchecked Exception(不受检查异常,可以不处理)。
Error:Error属于程序无法处理的任务,例如Java虚拟机运行错误,虚拟机内存不够错误,类定义错误等。这些异常发生时,Java虚拟机一般会选择线程终止。
16.Checked Exception 和 Unchecked Exception有什么区别?
Checked Exception即受检查异常,在编译过程中,如果受检查异常没有被catch或者throws关键字处理的话,就没办法通过编译。
除了RuntimeException及其子类以外,其他的Exception类及其子类全部属于受检查异常。常见的受检查异常有:IO相关的异常、ClassNotFoundException、SQLException
Unchecked Exception:即不受检查异常,在编译过程中,即使不处理也能正常通过编译。RuntimeException及其子类都统称为不受检查异常,常见的有:
NullPointerException(空指针错误)
IllegalArgumentException(参数错误,比如方法入参类型错误)
NumberFormatException(字符串转换为数字格式错误,是上面类的子类)
ArrayIndexOutofBoundException(数组越界错误)
17.try-catch-finally如何使用?
try块:用于捕获异常,其后可以接零个或多个catch块,如果没有catch块,则必须跟一个finally块;
catch块:用于处理try块捕获的异常;
finally块:无论是否捕获或者处理异常,finally块里的语句都会被执行。当在try块或者catch块中遇到return语句时,finally语句块将在方法返回之前被执行;
注意:不要在finally块中使用return,当try语句和finally语句中都有return语句时,try语句块中的return语句会被忽略。这是因为try语句中的return返回值会先被暂存在一个本地变量里,当执行到finally语句中的return之后,这个本地变量的值就变成了finally语句中的return返回值。
18.finally中的代码一定会执行吗?
不一定,在某些情况下,finally中的代码不会被执行。比如finally之前虚拟机被终止运行的话,finally中的代码无法执行,另外还有两种特殊情况:
1.程序所在的线程死亡;
2.关闭cpu;
19.什么是序列化?什么是反序列化?
序列化:将数据结构或对象转换为二进制字节流的过程;
反序列化:将在序列化过程中所生成的二进制字节流转换为数据结构或者对象的过程;
序列化和反序列化的应用常见:
1.对象在进行网络传输(比如远程方法调用RPC的时候)之前需要先被序列化,接收到序列化的对象之后需要在进行反序列化;
2.将对象存储到文件之前需要进行序列化,将对象从文件中读取出来需要进行反序列化;
3.将对象存储到数据库(如Redis)之前需要用到序列化,将对象从缓存数据库中读取出来需要反序列化;
4.将对象存储到内存之前需要进行序列化,从内存中读取出来之后要进行反序列化;
20.JDK动态代理和Cglib动态代理?
JDK动态代理是基于接口的,所以要求代理类一定是有定义接口的,CGLIB基于ASM字节码生成工具,通过继承的方式来实现代理类,所以要注意final方法;
1.JDK动态代理机制中,InvocationHandler接口和Proxy类是核心。
Proxy类中使用频率最高的方法是:newProxyInstance(),这个方法主要用来生成一个代理对象。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
......
}
这个方法一共有三个参数:
1.loader:类加载器,用于加载代理对象
2.interfaces:被代理类实现的一些接口
3.h:实现了InvocationHandler接口的对象
要实现动态代理的话,还必须要实现InvocationHandler来自定义处理逻辑。当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用。
public interface InvocationHandler {
/**
* 当你使用代理对象调用方法的时候实际会调用到这个方法
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoke()方法有三个参数:
1.proxy:动态生成的代理类;
2.method:与代理类对象调用的方法相对应;
3.args:当前method方法的参数;
也就是说,通过Proxy类的newInstance()创建的代理对象在调用方法的时候,实际会调用到实现InvocationHandler接口的类的invoke()方法。可以在invoke()方法中自定义逻辑处理,比如在方法执行前后做什么事情。
JDK动态代理类的使用步骤:
1.定义一个接口及其实现类;
2.自定义InvocationHandler并重写invoke方法,在invoke方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
3.通过Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)方法创建代理对象;
2.CGLIB动态代理机制
JDK动态代理有一个最致命的问题是其只能代理实现了接口的类。
为了解决这个问题,我们可以用CGLIB动态代理机制来避免。
在CGLIB动态代理机制中MethodInterceptor接口和Enhancer类是核心。
需要自定义MethodInterceptor并重写intecepte方法,intercept用于拦截增强被代理类的方法。
public interface MethodInterceptor
extends Callback{
// 拦截被代理类中的方法
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,MethodProxy proxy) throws Throwable;
}
1.obj:被代理的对象(需要增强的对象)
2.method:被拦截的方法(需要增强的方法)
3.args:方法入参
4.proxy:用于调用原始方法
可以通过Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是MethodInterceptor中的intercept方法。
使用步骤:
1.定义一个类;
2.自定义MethodInterceptor并重写intercept方法,intercept用于拦截增强被代理类的方法,和JDK动态代理中的invoke方法类似;
3.通过Enhancer类的create()来创建代理类;
21.什么是Java的多态特性?
多态需要具备三个条件:1.继承:子类继承父类或实现接口;2.重写:子类覆盖父类的方法;
3.父类声明子类:使用父类引用来声明子类对象;平常使用的函数重载、继承、泛型等都是多态的具体体现;
22.Java中的参数传递是按值还是按引用?
Java只有按值传递,不论是基本类型还是引用类型;传递的值在栈中,直接拷贝一份值传递,改变的形参不会对实参造成影响;传递的值在栈中存放的是地址(引用),先根据栈中的地址找到在堆上的值,然后把地址拷贝一份(拷贝的地址是一个值),此时形参和实参指向堆上同一个地址,形参的修改导致了实参的改变;
23.