Java
关于Java常见面试题及解答
个人学习使用。
java
1.java中"=="和equals以及hashcode的区别?
==
基本数据类型(byte char int short long floatdouble boolean)使用==的话就是比较他们的值。
如果是复合数据类型(String Integer等)那么 ==就是比较他们的内存存放的地址值。
equals
默认的equals方法都是调用Object类的equals方法,该方法主要是判断对象的引用是不是同一内存地址,其实在方法内部依然是使用==来判断。
以下是Object类equals方法代码:
public boolean equals(Object obj){
retrun (this == obj);
}
那么为何我们在进行字符串(String类型)判断使用equals时会发现比较的是值。原因就是像String类会对equals进行一个重写。
以下是String类equals方法重写代码:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = count;
if (n == anotherString.count) {
char v1[] = value;
char v2[] = anotherString.value;
int i = offset;
int j = anotherString.offset;
while (n-- != 0) {
if (v1[i++] != v2[j++])
return false;
}
return true;
}
}
return false;
}
该方法的判断步骤:
1.如果A和B都是同一个String对象,那么返回true.
2.如果A和B都是String类则继续,否则返回flase.
3.判断A,B的字符串长度是否一样,不一样返回flase.
4,逐个字符进行比较,如果有不相等的字符,那么返回flase.
equals有以下几个特性需要注意:
1.自反性:对任意引用值m,都有m.equals(m),返回true;
2.对称性:对于任意引用的m、n,当n.equals(m),返回为true时,那么m.equals(n)也一定返回为true;
3.传递性:如果m.equals(n)=true, n.equals(l)=true,则m.equals(l)=true ;
4.一致性:如果参与比较的对象没任何改变,则对象比较的结果也不应该有任何改变
5. 非空性:任何非空的引用值m,m.equals(null)的返回值一定为false
hashCode
hashCode()方法是给对象返回一个数值,这个数值就是哈希码。其作用可以用来减少查询次数,提高效率。比如遇到Set这种无序的集合,元素不能重复,这个时候如果使用equals的话,每增加一个新元素都要跟之前的元素进行对比,耗时长效率低。所以采用hashCode的方法的话,就能定位到他的内存地址,如果该地址上没有元素的话,就保存在该位置,如果有元素,再调用equals进行比较,相同就不存,不相同就散列到其他位置。通过这个办法可以大大降低比较次数,提高效率。
可参考:https://blog.csdn.net/m0_37700275/article/details/82800590
2. 基本数据类型
序号 | 数据类型 | 位数 | 字节 | 默认值 | 取值范围 | 举例说明 |
---|---|---|---|---|---|---|
1 | byte | 8 | 1字节 | 0 | -2^7 - 2^7-1 | byte b = 10; |
2 | short | 16 | 2字节 | 0 | -2^15 - 2^15-1 | short s = 10; |
3 | int | 32 | 4字节 | 0 | -2^31 - 2^31-1 | int i=10; |
4 | long | 64 | 8字节 | 0 | -2^63 - 2^63-1 | long l=10; |
5 | float | 32 | 4字节 | 0.0 | -2^31 - 2^31-1 | float f=10.0f; |
6 | double | 64 | 8字节 | 0.0 | -2^63 - 2^63-1 | double d=10.0d; |
7 | char | 16 | 2字节 | 空 | 0 - 2^16-1 | char c=‘c’’; |
8 | boolean | 8 | 1字节 | false | true、false | boolean b=true; |
3. int和Integer的区别
1.Integer是int的封装类;Integer变量必须实例化之后才能使用,而int则可以直接使用;
2.Integer实际上是对象的引用,当new一个Integer时,就生成了一个指针指向该对象,因此通过Integer生成的两个对象永远不会相等,因为他们的其内存地址不同,这点与int类型不同;
3.当int变量与Integer变量进行比较时,只要两个变量的值是相等的,结果则为true,这是因为包装了Integer变量与int变量比较时,会自动拆箱为int类型,实际上就是两个int变量在比较。
4.两个Integer变量进行比较,如果两个都不是新new的对象且变量的值在-128到127之间,那么比较结果为true,否则为false;
参考:https://www.cnblogs.com/guodongdidi/p/6953217.html
4.探讨Java多态的理解
多态是Java的四大基础特性之一,简单来说就是让同一行为有多种不同的表现形式或者形态的能力,多态就是同一个接口根据不同实例进行不同的操作。多态存在必须要具备继承、重写、父类引用指向子类对象三个条件。通常应用在变量赋值、方法传参等方面。多态有两种转型:向上转型、向下转型。向上转型是指父类引用指向子类对象,他只能访问父类中拥有的方法和属性。向下转型(将父类强转为子类对象)则可以调用子类中的特殊方法,但是使用向下转型的时候需要先向上转型。为了保证转型的顺利进行,Java中提供了一个关键字:instanceof,该关键字的用法是用来判断前面对象是不是后面类的子类的实例。
可参考:https://blog.csdn.net/qq895627041/article/details/82109576
5.String StringBuffer和StringBuilder区别
String是字符串常量,不可变对象,因此每次对String进行操作,都会生成一个新的String对象,因此如果用String来进行大量的字符串拼接会降低效率。
StringBuffer和StringBuilder与 String不同,可以对对象进行多次修改,不产生新的对象。StringBuffer是线程安全的,而StringBuilder是线程不安全的,但是速度有优势。初始化时,只有String可以赋值NULL。
少量数据适合使用String;
多线程操作字符串缓冲区下操作大量数据 StringBuffer;
单线程操作字符串缓冲区下操作大量数据 StringBuilder。
参考:https://blog.csdn.net/weixin_41101173/article/details/79677982
6.内部类
在Java中,将一个类放置于另一个类内部或者方法内,这样的类叫作内部类。
内部类主要有四种:静态内部类、成员内部类、局部内部类、匿名内部类。
静态内部类
使用static来修饰,只能访问外部类的静态方法和变量;
不需要将静态内部类的实例对象绑定在外部类对象上;
静态内部类属于外部类,而不属于外部类的对象;
生成静态类对象:Outer.inner inner=new Outer.inner()。
成员内部类
定义在外部类内部,可以访问外部类的所有成员属性和成员方法;
外部类如果想要访问成员内部类的成员需要先创建一个内部类对象,通过该对象来访问;
class Circle {
private double radius = 0;
public Circle(double radius) {
this.radius = radius;
getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}
private Draw getDrawInstance() {
return new Draw();
}
class Draw { //内部类
public void drawSahpe() {
System.out.println(radius); //外部类的private成员
}
}
}
成员内部类需要依附于外部类;
创建成员内部类的方式如下:
public class Test {
public static void main(String[] args) {
//第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建
//第二种方式:
Outter.Inner inner1 = outter.getInnerInstance();
}
}
class Outter {
private Inner inner = null;
public Outter() {
}
public Inner getInnerInstance() {
if(inner == null)
inner = new Inner();
return inner;
}
class Inner {
public Inner() {
}
}
}
成员内部类像是外部类的一个成员,可以用多种权限修饰。
局部内部类
局部内部类是定义在一个方法或者一个作用域内,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
局部内部类只能访问被final修饰的变量;
不能加任何访问修饰符。
匿名内部类
使用匿名内部类前提是继承一个父类或者是实现一个接口,将方法在new的对象后面{}中实现。
参考:https://blog.csdn.net/hellocsz/article/details/81974251
7.抽象和接口的区别
抽象 | 接口 |
---|---|
声明关键字abstract | 声明关键字interface |
需要被子类继承 | 需要被类实现 |
能作方法声明和方法实现 | 只能作方法声明 |
抽象类是普通变量 | 接口的变量只能是static final类型 |
作用于抽象具体对象 | 作用于抽象具体对象 |
具有具体的方法和属性 | 只有抽象方法和不可变量 |
实现关键字为extends | 实现关键字为inplements |
8.重载和重写的区别
重写(Override)
子类对父类的方法的实现过程进行重新编译,返回值和形参都不能改变,即外壳不变,内容改变。
重写的好处在于子类可以根据自己的需求实现父类的方法。
重写的规则:
1.参数与被重写的一致
2.返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类
3.访问权限不能比父类中被重写的方法的访问权限更低,例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected
4.父类成员方法只能被子类重写
5.static不能被重写,但是可以被再次声明,final不能被重写
6.构造方法不能被重写
7.子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法
8.子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法
9.重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以
10.如果不能继承一个方法,则不能重写这个方法
Super关键字的使用
当需要在子类中调用父类的被重写方法时,要使用super关键字。
重载(overloading)
重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。
重载规则:
1.被重载的方法必须改变参数列表(参数个数或类型不一样);
2.被重载的方法可以改变返回类型;
3.被重载的方法可以改变访问修饰符;
4.被重载的方法可以声明新的或更广的检查异常;
5.方法能够在同一个类中或者在一个子类中被重载。
6.无法以返回值类型作为重载函数的区分标准。
总结
方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
(1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。
(2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
(3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
参考:https://www.runoob.com/java/java-override-overload.html
9.线程和进程的区别
进程:是系统进行分配和管理资源的基本单位,一个程序就是一个进程
线程: 进程的一个执行单元, 是进程内调度的实体、是CPU调度和分派的基本单位, 是比进程更小的独立运行的基本单位。线程也被称为轻量级进程, 线程是程序执行的最小单位
一个程序至少一个进程,一个进程至少一个线程
进程拥有独立的地址空间,每启动一个进程,系统就会分配地址空间,建立数据表来维护代码段、堆栈段和数据段, 因此这种操作非常昂贵。
而线程是共享进程中的数据,使用相同的地址空间,因此cpu切换一个线程所耗费的时间要比进程小很多,开销也小很多。
线程的通信也更方便,同一进程下的线程共享全局变量,静态变量等资源,而进程之间的相互通信需要以通信的形式进行,如何写好同步和互斥是多线程的难点。
进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
参考:https://blog.csdn.net/cisco_huang/article/details/87191183
10.序列化的方式
主要方式有三种:
1.通过实现Serializable接口,这是隐式序列化,这是最简单的序列化方式,会自动化所有的非Static和transient关键字修饰的成员变量
2.序列化方式二:实现Externalizable接口——显式序列化
Externalizable接口继承自Serializable, 我们在实现该接口时,必须实现writeExternal()和readExternal()方法,而且只能通过手动进行序列化,并且两个方法是自动调用的,因此,这个序列化过程是可控的,可以自己选择哪些部分序列化
3.实现Serializable接口+添加writeObject()和readObject()方法——显+隐
参考:https://blog.csdn.net/ahuqihua/article/details/81331316