【面试】javase
java基础
1.equals 与 == 区别
-
== 是java的一个关系运算符,是来检查两边的值是否相等。
- 如果两边是基本类型,则比较的是值是否相等
- 如果两边是引用对象,则比较该引用的地址值是否相等
public boolean equals(Object obj) { return (this == obj); }
-
equals 是java Object类中的一个方法,
- 如果我们没有重写 这个方法,底层还是比较这两个引用的地址值,通过这个来判断两个对象通常是没有意义的。
- 所以我们自己的类应该按照自己的逻辑判断两个对象是否相等,去重写equals,比如String的equals方法就是逐会比较字符。
2. final, finally, finalize 的区别
-
final是java的一个修饰符,可以修饰方法、变量、类等。被他修饰的方法不可以被重写;变量只可以被赋值一次就不可以改变;被他修饰的类是不可变类,不能被继承
-
finally是java的一个关键字,是异常处理的一部分,一般就try-catch-finally ,finally代码块中的语句是一定会被执行的。我们常常会在里面进行一些连接的关闭,确保资源的利用。
-
finalize 是Object 类中的一个方法,他是以protected修饰的一个空方法,也就是说要我们重写其中的逻辑。这里主要是关于垃圾回收的一些逻辑,当对象没有GC-Root引用时,就会去看看有没有必要执行finalize(), 如果finalize()被调用过 或者 子类没有覆盖 这个类,则就不会执行这个方法。
我们可以重写该方法让Gc-root能链到该对象从而达到对象的自救。所以这个方法我们程序员一般不主动调用。都由jvm来掌控。jdk9已经废弃了这个方法
3.重载和重写的区别
- 重载是一个方法的参数个数不同或者参数类型不同
- 重写是子类修改父类方法的逻辑,通过子类实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这是面向对象编程的多态性的一种表现。
区别
-
重写是需要继承,重载不用
-
重写的方法的访问权限必要大于等于父类方法,子类返回值类型应比父类返回值类型小或相等,只能比父类抛异常抛得更小或相等,不能坑爹hh
-
重载和返回值类型、权限修饰符没有任何关系,之和参数有关。
4. 两个对象的hashCode()相同,则 equals()是否也一定为 true?
-
两个对象的equals()相等,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。
-
两个对象的equals()不相等,那么对这两个对象中的任一对象上调用 hashCode 方法不要求一定生成不同的整数结果。但是,为不相等的对象生成不同整数结果可以提高哈希表的性能。
5.接口和抽象类区别
抽象类是从多个类抽象出来的,如果将这个类抽象得更具体,就可以提炼出更特殊得抽象类"接口",也就是接口抽象级别更高
-
接口中没有构造器,抽象类中有构造器,但是并不是来实例对象的,而是拿来子类调用的。
-
抽象类之中的方法都是抽象方法,都以隐式或显示public abstract修饰; 抽象类可以没有抽象方法
-
抽象类中可以有普通成员变量,接口中变量只能是静态常量
-
接口可以多继承,抽象类只能单继承。
6.BIO、NIO、AIO 有什么区别?
这几种都是java的IO模式,分别是同步非阻塞IO、异步非阻塞IO。
这三个的区别
- BIO是线程发起IO请求后,不管有没有IO操作,一个连接一个线程,从发起请求后线程就一直阻塞住,直到操作结束,如果这个连接不做事情就会导致资源的浪费。
- BIO是线程发起IO请求后,一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问。
- AIO:线程发起 IO 请求,立即返回;内存做好 IO 操作的准备之后,做 IO 操作,直到操作完成或者失败,通过调用注册的回调函数通知线程做 IO 操作完成或者失败。
BIO 是一个连接一个线程。,NIO 是一个请求一个线程。,AIO 是一个有效请求一个线程。
7.String,Stringbuffer,StringBuilder的区别
String
-
String是一个用finall修饰不可变类,一旦被创建就不可以被修改。
-
jdk1.8及以前String底层使用是char[],1.9开始使用byte[]。
首先byte是单字节,char是双字节。官方发现,绝大数的字符串都是英文和标点符号,所以只需要一个字节就可以存储(可使用Latin-1编码方案),如果使用char,所有的字符进来都是占双字节比较占用空间。
新版String会大幅降低内存占用,内存占用减少引发另一个好处:减少GC次数(这里也会提升效率)。某些情况会效率有所下降。总体来说优点远大于缺点。
-
String的每一次的改变都会使在常量池创建新的对象,而不是在原有的基础上去修改,所以如果有.频繁的字符串修改操作,用String不是一个好的选择。
-
当用String类拼接字符串时, 每次都会生成一个StringBuilder对象, 然后调用两次append()方法把字
符串拼接好, 最后通过StringBuilder的toString()方法new出一个新的字符串对象。
Stringbuffer,StringBuilder
- 和 String 类不同的是,StringBuffer 和 StringBuilder 类的对象能够被多次的修改,并且不产生新的未使用对象。
- StringBuffer 是线程安全的,StringBuilder 是线程不安全的,所以效率要比StringBuffer 低一点。
8.java有几种基本类型,分别占用空间大小
八大基本数据类型byte,short,int,long,float,double,boolean,char。分别对应 ==1 2 4 8 4 8 8 2== 字节
整形 : byte short int long;
浮点型 : float double
字符型 :char
布尔型 :boolean
9.Comparator 和Comparable区别
Comparable
- 而Comparable位于包 java.lang下。
- jdk源码是这样描述Comparable的
该接口对实现它的每个类的对象强加了总排序。 这种排序称为类的自然排序,类的compareTo方法称为其自然比较方法。
- Comparable 是一个对象本身就已经支持自比较所需要实现的接口(如 String、Integer 自己就可以完成比较大小操作,已经实现了Comparable接口) 自定义的类要在加入list容器中后能够排序,可以实现Comparable接口,在用Collections类的sort方法排序时,如果不指定Comparator,那么就以自然顺序排序, 这里的自然顺序就是实现Comparable接口设定的排序方式。
Comparator
- Comparator位于包java.util下
- Comparator 是一个专用的比较器,当这个对象不支持自比较或者自比较函数不能满足你的要求时,你可以写一个比较器来完成两个对象之间大小的比较。
- jdk中的描述
一个比较函数,它对某些对象集合进行总排序。 比较器可以传递给排序方法(例如Collections.sort或Arrays.sort )以允许精确控制排序顺序。 比较器还可用于控制某些数据结构(例如sorted sets或sorted maps )的顺序,或为没有natural ordering的对象集合提供natural ordering 。
10. String类能被继承吗,为什么。
不能,他是以final修饰的类,是不可以被继承 的。
为什么要这么设计?
- 效率性,String 类作为最常用的类之一,禁止被继承和重写,可以提高效率。
- 安全性,String 类中有很多调用底层的本地方法,调用了操作系统的 API,如果方法可以重写,可能被植入恶意代码,破坏程序。
11. 说说Java中多态的实现原理==*==
12. Java泛型和类型擦除==*==
类型擦除
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2);//true
ArrayList <Integer>
和ArrayList <String>
很容易被认为是不同的类型。但是这里输出结果是true,这是因为Java泛型是使用擦除实现的,不管是ArrayList<Integer>()
还是new ArrayList<String>()
,在编译生成的字节码中都不包含泛型中的类型参数,即都擦除成了ArrayList,也就是被擦除成“原生类型”,这就是泛型擦除。
13. int和Integer 有什么区别,还有Integer缓存的实现
-
int 是一个基本数据类型,Integer是int 的包装类
-
int 默认为0,Integer是一个引用类型,默认是null
-
Integer的缓存机制: Integer是对小数据(-128
127)是有缓存的,再jvm初始化的时候,数据-128127之间的数字便被缓存到了本地内存中,如果初始化-128~127之间的数字,会直接从内存中取出,不需要新建一个对象.
14. 说说反射的用途及实现原理,Java获取反射的三种方法==*==
15. 面向对象的特征
面向对象的三大特征:
- 封装
- 继承
- 多态
16. &和&&的区别
- 按位与, a&b 表示把a和b都转换成二进制数,再进行与的运算;
- &和&&都是逻辑运算符号,&&又叫短路运算符
- 逻辑与,a&& b ,a&b 都表示当且仅当两个操作数均为 true时,其结果才为 true,否则为false。
- 逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true,整个表达式的值才是true。但是,&&之所以称为短路运算,是因为如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。
17. Java中IO流分为几种?
- Java中的流分为两种:一种是字节流,另一种是字符流。
- IO流分别由四个抽象类来表示(两输入两输出):InputStream,OutputStream,Reader,Writer。
18. 讲讲类的实例化顺序,比如父类静态数据,构造函数,子类静态数据,构造函数。
- 父类的静态块
- 子类的静态块
- 父类的非静态块
- 父类的构造器
- 子类的非静态块
- 子类的构造器
19. Java创建对象有几种方式
- new
- 使用反射,使用Class.forName("").newInstance()创建对象。
- 调用对象的clone()方法。
- 运用反序列化手段,调用java.io.ObjectInputStream对象的readObject()方法.
- unsafe?
20. 守护线程是什么?用什么方法实现守护线程
在Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程)
- 守护线程是运行在后台的一种特殊线程
- 它会周期性的执行某种任务,或等待某些任务的发生并采取相应的措施
- java中的垃圾回收就是特殊的守护线程。
21.notify()和 notifyAll()有什么区别?
- notify是唤醒一个处于该对象wait的线程,而notifyAll是唤醒所有处于该对象wait的线程。
- 但是唤醒不等于就能执行了,需要得到锁对象才能有权利继续执行,而锁只有一把,所以多个线程被唤醒时需要争取该锁。
22.notify()和 notifyAll()有什么区别?
- notify是唤醒一个处于该对象wait的线程,而notifyAll是唤醒所有处于该对象wait的线程。
- 但是唤醒不等于就能执行了,需要得到锁对象才能有权利继续执行,而锁只有一把,所以多个线程被唤醒时需要争取该锁。
23.深拷贝和浅拷贝区别
浅拷贝
复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化。
深拷贝
将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变。
有两种方式实现深拷贝
- 实现Cloneable接口,并且重写Object类中的clone()方法,若不对clone()方法进行改写,则调用此方法得到的对象即为浅拷贝。
- 实现Serializable接口序列化
24.JDK 和 JRE 有什么区别?
-
JDK java开发工具包,包含了jre,同时还包含了编译java源码的编译器javac,还包含了很多java程序调试和分析的工具
-
JRE java运行时环境,包含了java虚拟机,java基础类库。是使用java语言编写的程序运行所需要的软件环境,是提供给想运行java程序的用户使用的。
25.什么是值传递和引用传递?
- java中,如果传递的是基本数据类型,都行值传递,每一个传递值过去的都相当于一个副本,改变该副本不会对原来的值产生影响
- 如果传递的是引用型变量来说,传递的就是该引用对象地址的一个副本,对引用对象进行修改会导致原对象的改变。
26.Java支持多继承么,为什么?
不可以。
多继承带来很多复杂性,容易在各种操作过程中产生问题,比如强制类型转换、构造函数链接等,而最重要的原因是,实际上需要多继承的场景非常少,所以为了保持事情的简单和直观,干脆不支持多继承。
27.构造器是否可被重写?
不可以
构造器是不能被继承的,因为每个类的类名都不相同,而构造器名称与类名相同,所以根本谈不上继承。 又由于构造器不能继承,所以就不能被重写。但是,在同一个类中,构造器是可以被重载的。
28.char型变量中能不能存贮一个中文汉字,为什么?
在Java中,char类型占2个字节,而且Java默认采用Unicode编码,一个Unicode码是16位,所以一个Unicode码占两个字节,Java中无论汉子还是英文字母都是用Unicode编码来表示的。所以,在Java中,char类型变量可以存储一个中文汉字。
29. object中定义了哪些方法?
- getClass(); 获取类结构信息
- hashCode() 获取哈希码
- equals(Object) 默认比较对象的地址值是否相等,子类可以重写比较规则
- clone() 用于对象克隆
- toString() 把对象转变成字符串
- notify() 多线程中唤醒功能
- notifyAll() 多线程中唤醒所有等待线程的功能
- wait() 让持有对象锁的线程进入等待
- wait(long timeout) 让持有对象锁的线程进入等待,设置超时毫秒数时间
- wait(long timeout, int nanos) 让持有对象锁的线程进入等待,设置超时纳秒数时间
- finalize() 垃圾回收前执行的方法
30.hashCode的作用是什么?
hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,hashCode是用来在散列存储结构中确定对象的存储地址的;
31. 简述一下面向对象的”六原则一法则”。
- 单一职责原则:一个类只做它该做的事情。
- 开闭原则:软件实体应当对扩展开放,对修改关闭。
- 依赖倒转原则:面向接口编程。
- 接口隔离原则:接口要小而专,绝不能大而全。
- 合成聚合复用原则:优先使用聚合或合成关系复用代码。
- 迪米特法则:迪米特法则又叫最少知识原则,一个对象应当对其他对象有尽可能少的了解。
32. java中的Math.round(-1.5) 等于多少呢?
JDK 中的 java.lang.Math 类:
- round() :返回四舍五入,负 .5 小数返回较大整数,如 -1.5 返回 -1。四舍五入:+0.5下取整
- ceil() :返回小数所在两整数间的较大值,如 -1.5 返回 -1.0。
- floor() :返回小数所在两整数间的较小值,如 -1.5 返回 -2.0。
33. 如何将字符串反转呢?
- 使用 StringBuilder 或 StringBuffer 的 reverse 方法,本质都调用了它们的父类 AbstractStringBuilder 的 reverse 方法实现。(JDK1.8)
- 使用chatAt函数,倒过来输出;
34. 描述动态代理的几种实现方式,它们分别有什么优缺点
-
JDK动态代理
-
CGLIB动态代理
-
JDK原声动态代理时java原声支持的、不需要任何外部依赖、但是它只能基于接口进行代理
-
CGLIB通过继承的方式进行代理、无论目标对象没有没实现接口都可以代理,但是无法处理final的情况
35. 匿名内部类是什么?如何访问在其外面定义的变量呢?
匿名内部类还有以下特点:
- 没有名字
- 匿名内部类必须继承一个抽象类或者实现一个接口。
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
- 匿名内部类不能访问外部类方法中的局部变量,除非该变量被声明为final类型
36.64位jvm int长度是多少
32 位和 64 位的 JVM 中,int 类型变量的长度是相同的,都是 32 位或者 4 个字节(一个字节8位)
37.java中,为何方法wait(),notify() 和 notifyAll() 是在 Object 类中定义的,而非在 Thread 类中定义?
因为synchronized中的这把锁可以是任意对象,所以任意对象都可以调用wait()和notify();所以wait和notify属于Object。
专业说:因为这些方法在操作同步线程时,都必须要标识它们操作线程的锁,只有同一个锁上的被等待线程,可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。
也就是说,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。
38.为什么 char 数组比 String 更适合存储密码?
由于字符串在 Java 中是不可变的,如果你将密码存储为纯文本,它将在内存中可用,直到垃圾收集器清除它,并且为了可重用性,会存在 String在字符串池中, 它很可能会保留在内存中持续很长时间,从而构成安全威胁。
由于任何有权访问内存转储的人都可以以明文形式找到密码,这是另一个原因,你应该始终使用加密密码而不是纯文本。
由于字符串是不可变的,所以不能更改字符串的内容,因为任何更改都会产生新的字符串,而如果你使用char[]
,你就可以将所有元素设置为空白或零。
因此,在字符数组中存储密码可以明显降低窃取密码的安全风险。
39.什么是serialVersionUID?如果你不定义这个,会发生什么?
serialVersionUID是一个private static final long型ID,当它被印在对象上时,它通常是对象的哈希码,你可以使用serialver这个JDK工具来查看序列化对象的serialVersionUID。SerialVerionUID用于对象的版本控制。也可以在类文件中指定serialVersionUID。不指定serialVersionUID的后果是,当你添加或修改类中的任何字段时,则已序列化类将无法恢复,因为为新类和旧序列化对象生成的serialVersionUID将有所不同。Java序列化过程依赖于正确的序列化对象恢复状态,并在序列化对象序列版本不匹配的情况下引发java.io.InvalidClassException无效类异常。
40.如果类中的一个成员未实现可序列化接口,会发生什么情况?
如果尝试序列化实现可序列化的类的对象,但该对象包含对不可序列化类的引用,则在运行时将引发不可序列化异常NotSerializableException,在可序列化类中添加新字段时要注意。
41.simpledateformat线程为什么不安全
在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。
解决方案:
-
将SimpleDateFormat定义成局部变量
-
加一把线程同步锁:synchronized(lock)
-
使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本。
42.java枚举类是否可以继承
枚举类使用enum定义后在编译后默认继承了java.lang.Enum类,而不是普通的继承Object类。enum声明类继承了Serializable和Comparable两个接口。且采用enum声明后,该类会被编译器加上final声明(同String),故该类是无法继承的。