【Java 面试】Java基础必背面试题(精华版)
关于
目前网络上有许多Java相关面试题的总结,但基本上都是差不多的题目,以下文章内容中的题目参考整理自互联网,但相关题目答案主要是参考自网络、博客进行汇总整理,其中部分是由我查阅书籍按照自己的理解修改后做出的答案,并精心挑选出来的一些面中率高、有价值的题目,非常基础的题目或是简单易回答的题目就省略了。可堪称Java面试必背系列。
Java入门
1、什么是Java?谈谈你对Java的认识。
Java作为一门优秀的高级程序设计语言
,早在1996年第一次发布时就引起了人们的极大兴趣,但Java同时并不只是一种语言,而是一个完整的平台
,有一个庞大的类库,其中包含了很多可重用的代码以及还一个提供了诸如能保证安全性、跨操作系统的可移植性以及自动垃圾回收机制等服务的执行环境。
2、Java有什么特性(特点)?
-
简单性
Java语言的简单性可以从两个方面讨论:一是
简单易学
,当然这是相比较而言的,与C++相比它没有过于复杂、难以理解的语法,剔除了C++语言中不常用的、难以理解的、易于混淆的语法特性;二是体现在小
,Java的目标之一是支持开发能够在小型机器上独立运行的软件。 -
面向对象
Java是一门纯面向对象的高级程序设计语言,比如哪怕只是输出一条hello world 语句也需要去编写一个类。
-
跨平台 可移植性
使用Java语言编写的应用在真正意义上做到了程序的可移植,也就是经常听到的
一次编译到处运行
,这一跨平台特性的实现主要得益于它的字节码文件和JVM机制。 -
安全与健壮性
Java语言非常强调进行早期的问题检测,后期动态(运行时)检测以及消除容易出错的情况。也就是说,
Java编译器能够检测许多在其他语言中仅在运行时才能够检测出来的问题
。此外,从一开始Java语言就设计成能够防范各种攻击,其中包括运行时堆栈溢出,破坏自己的进程空间之外的内存,未经授权读写文件等等。所以Java语言,可以构建防病毒防篡改的系统。 -
编译与解释并存
Java语言源代码经过Javac命令进行编译生成对应的字节码.class文件,而这些字节码文件是在JVM也就是Java虚拟机上通过解释器去解释执行的,所以Java源代码要想执行需要经过编译和解释两个过程。
-
多线程
多线程可以带来更快的交互响应和实时行为。Java语言支持多线程编程,有能力去处理高并发环境下的程序执行。
3、Java和C++两者之间的关系和区别?
Java语言是在C++语言的基础之上衍生出来的,JVM虚拟机就是用C/C++来编写的,Java和C++都是面向对象的高级编程语言
。相对而言,Java更容易学习,并且编程环境更为简单,二者的区别有很多,这里简单列举以下几点:
-
1.Java为
纯
面向对象的语言,能够直接反应现实生活中的对象,容易理解,编程更容易。 -
2.Java语言可以跨平台执行,具有很好的移植性。
-
3.Java提供了很多内置的类库,简化了开发人员的程序设计工作,缩短了项目的开发时间。
-
4.Java语言提供了对多线程的支持,提供了对网络通信的支持,最重要的是提供了垃圾回收器,这使得开发人员从对内存的管理中解脱出来。
-
5.Java去除了C++语言中难以理解、容易混淆的特性,例如头文件、指针、结构、单元、运算符重载、虚拟基础类、多重继承等,使得程序更加严谨、简洁。
-
6.C++支持多重继承,Java语言不支持多重继承。但是Java引入了更易于理解的接口的概念,可以同时实现多个接口。由于接口也具有多态特性,因此在Java语言中可以通过实现多个接口来实现与C++语言中多重继承类似的目的,弥补了Java类中的仅支持单继承的缺陷。
Java其实也可以说是由C++发展而来,保留了C++的大部分内容,其编程方式类似于C++,但是摒弃了C++的诸多不合理之处,从根本上解决了C++的固有缺陷。使得Java句法更清晰,规模更小,更易学,同时更趋于健壮性,安全性和平台无关性。
4、JDK、JRE和JVM
-
JDK
JDK是Java Development Kit的缩写,是
Java开发工具包
。JDK是整个JAVA的核心,包括了Java运行环境(JRE),Java工具(javac/java/jdb等)和Java基础的类库(即Java API )。 -
JRE
JRE是Java Runtime Environment的缩写,是
Java运行时环境
。包含JVM标准实现及Java核心类库。 JRE是Java运行环境,并不是一个开发环境,所以没有包含任何开发工具(如编译器和调试器),只是针对于使用Java程序的用户。 -
JVM
JVM是Java Virtual Machine的缩写,是
Java虚拟机
。它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。JVM是JRE的一部分。它是整个Java实现跨平台的最核心的部分
,负责解释执行字节码文件,是可运行java字节码文件的虚拟计算机。所有平台的上的JVM向编译器提供相同的接口,而编译器只需要面向虚拟机,生成虚拟机能识别的代码,然后由虚拟机来解释执行。
JDK
> Java运行时环境JRE
> Java虚拟机Java基础
5、Java语言中有哪些变量,他们之间有什么区别?
Java中的变量可分为三种类型:局部变量
,成员变量
和静态变量
。它们直接的区别主要体现在以下几个维度:
-
声明位置不同
局部变量可声明在方法、构造方法或者语句块中。成员变量声明在一个类中,但在方法、构造方法和语句块之外。静态变量在类中以 static 关键字声明,但也必须在方法构造方法和语句块之外。
-
生命周期不同
局部变量在代码被执行的时候创建,当它们执行完成后,将会被销毁。成员变量在对象创建的时候创建,在对象被销毁的时候销毁。静态变量在程序开始时创建,在程序结束时销毁,也可以说是在类加载时创建,在类销毁时销毁。
-
作用域不同
局部变量只在声明它的方法、构造方法或者语句块中可见。成员变量和静态变量对于类中的所有方法、构造方法或者语句块都是可见的。一般情况下应该把成员变量设为私有。
-
初始化不同
局部变量没有默认值,所以局部变量被声明后,必须经过初始化,才可以使用。成员变量和静态变量具有默认值。数值型变量的默认值是 0,布尔型变量的默认值是 false,引用类型变量的默认值是 null。
-
存储位置不同
局部变量是在栈上分配的。成员变量随着对象实例的初始化被加载到堆内存中。静态变量随着类的加载储存在静态存储区。
-
修饰符不同
局部变量不可以使用访问修饰符以及static关键字修饰,而成员变量和静态变量可以使用访问修饰符以及static关键字修饰。三种变量都可以使用final关键字修饰。
6、Java 是值传递,还是引用传递?
Java 语言是值传递
。Java 语言的方法调用只支持参数的值传递。当一个对象实例作为一个参数被传递到方法中时,参数的值就是对该对象的引用值,类似于C语言中的指针变量
。对象的属性可以在被调用过程中被改变,但对对象引用的改变是不会影响到调用者的。
JVM 的内存分为堆和栈,其中栈中存储了基本数据类型和引用数据类型实例的地址,也就是对象地址。而对象所占的空间是在堆中开辟的,所以传递的时候可以理解为把变量存储的对象地址给传递过去,因此引用类型也是值传递。
7、说说&和&&的区别
-
&和&&都可以用作
逻辑与
的运算符,表示逻辑与(and),当运算符两边的表达式的结果都为true时,整个运算结果才为true,否则,只要有一方为false,则结果为false。 -
&&还具有短路的功能
,即如果第一个表达式为false,则不再计算第二个表达式,例如,对于if(str != null && !str.equals(“”))表达式,当str为null时,后面的表达式不会执行,所以不会出现,NullPointerException,如果将&&改为&,则会抛出NullPointerException异常。 -
&还可以用作位运算符
,当&操作符两边的表达式不是boolean类型时,&表示按位与操作,我们通常使用0x0f来与一个整数进行&运算,来获取该整数的最低4个bit位,例如,0x31 & 0x0f的结果为0x01。
8、Switch语句中表达式是否可以为String类型?
在switch(expr1)中,expr1只能是一个整数表达式或者枚举常量
,整数表达式可以是int基本类型或Integer包装类型,由于byte,short,char都可以隐含转换为int,所以,这些类型以及这些类型的包装类型也是可以的,但是switch 不支持 long 类型
。
注意:从 java1.7开始 switch 开始支持 String。
9、"=="和equals方法究竟有什么区别?
==操作符专门用来比较两个变量的值
是否相等,也就是用于比较变量所对应的内存中所存储的数值是否相同,要比较两个基本类型的数据或两个引用变量值是否相等,只能用==操作符。
如果一个变量指向的数据是引用类型的,那么,这时候涉及了两块内存,对象本身占用一块内存(堆内存),变量也占用一块内存,例如Objet obj = new Object();变量obj是一个内存,new Object()是另一个内存,此时,变量obj所对应的内存中存储的数值就是对象占用的那块内存的首地址。对于指向对象类型的变量,如果要比较两个变量是否指向同一个对象,即要看这两个变量所对应的内存中的数值是否相等,这时候就需要用==操作符进行比较。
equals方法是用于比较两个独立对象的内容
是否相同,它比较的两个对象是独立的。例如,对于下面的代码:
String a=new String("hello");
String b=new String("hello");
在实际开发中,我们经常要比较传递进行来的字符串内容是否等,例如,String input = input.equals(“quit”),许多人稍不注意就使用==进行比较了,这是错误的。记住,字符串的比较基本上都是使用equals方法
。
如果一个类没有自己定义equals方法,那么它将继承Object类的equals方法,Object类的equals方法的实现代码如下:
boolean equals(Object o){
return this==o;
}
==操作符,也是在比较两个变量指向的对象是否是同一对象,这时候使用equals和使用==会得到同样的结果,如果比较的是两个独立的对象则总返回false。如果编写的类希望能够比较该类创建的两个实例对象的内容是否相同,那么你必须覆盖equals方法,由你自己写代码来决定在什么情况即可认为两个对象的内容是相同的
==
对于基本类型和引用类型的作用效果是不同的:
-
对于基本数据类型来说,
==
比较的是值。 -
对于引用数据类型来说,
==
比较的是对象的内存地址。
因为 Java 只有值传递,所以,对于 == 来说,不管是比较基本数据类型,还是引用数据类型的变量,其本质比较的都是值,只是引用类型变量存的值是对象的地址。
equals()
不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals()
方法存在于Object
类中,而Object
类是所有类的直接或间接父类,因此所有的类都有equals()
方法。
equals()
方法存在两种使用情况:
-
类没有重写
equals()
方法 :通过equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是Object
类equals()
方法。 -
类重写了
equals()
方法 :一般我们都重写equals()
方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
10、什么是可变长参数?
从 Java 5 开始,Java 支持定义可变长参数,所谓可变长参数就是允许在调用方法时传入不定长度的参数
。遇到方法重载时会优先匹配固定参数的方法,因为固定参数的方法匹配度更高。另外,Java 的可变参数编译后实际会被转换成一个数组,我们看编译后生成的 class
文件就可以看出来。
11、静态变量和实例变量的区别?
-
在语法定义上的区别
:静态变量前要加static关键字,而实例变量前则不加。 -
在程序运行时的区别
:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了,一个类不管创建多少个对象,静态变量在内存中有且仅有一个副本。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。
12、静态方法和实例方法的区别?
静态方法属于类本身,随着类的加载而被加载到内存中,而实例方法属于对象,只有当对象进行实例化后才会在堆区中分配内存。
1、调用方式
在外部调用静态方法时,可以使用 类名.方法名
的方式,也可以使用 对象.方法名
的方式,而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象 。
不过,需要注意的是一般不建议使用 对象.方法名
的方式来调用静态方法。这种方式非常容易造成混淆,静态方法不属于类的某个对象而是属于这个类。
因此,一般建议使用 类名.方法名
的方式来调用静态方法。
2、访问类成员是否存在限制
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),不允许访问实例成员(即实例成员变量和实例方法),而实例方法不存在这个限制。
13、请说出访问控制修饰符public,private,protected,以及不写时的区别
在 Java 语言中,访问控制修饰符有 4 种。
-
1.private
用 private 修饰的类成员,只能被该类自身的方法访问和修改,而不能被任何其他类(包括该类的子类)访问和引用。因此,private 修饰符具有最高的保护级别。
-
2.friendly(默认)
如果一个类没有访问控制符修饰,说明它具有默认的访问控制特性。这种默认的访问控制权规定,该类只能被
同一个包中
的类访问和引用,而不能被其他包中的类使用,即使其他包中有该类的子类
。这种访问特性又称为包访问性(package private)。同样,类内的成员如果没有访问控制符,也说明它们具有包访问性,或称为友元(friend)。定义在同一个文件夹中的所有类属于一个包,所以前面的程序要把用户自定义的类放在同一个文件夹中(Java 项目默认的包),以便不加修饰符也能运行。
-
3.protected
用保护访问控制符 protected 修饰的类成员可以被三种类所访问:
该类自身、与它在同一个包中的其他类以及在其他包中的该类的子类。
使用 protected 修饰符的主要作用,是允许其他包中它的子类来访问父类的特定属性和方法,否则可以使用默认访问控制符。 -
4.public
当一个类被声明为 public 时,它就具有了被其他包中的类访问的可能性,只要包中的其他类在程序中使用 import 语句引入 public 类,就可以访问和引用这个类。
类中被设定为 public 的方法是这个类对外的接口部分,意味着任何类中的任何方法都可以调用这些方法,避免了程序的其他部分直接去操作类内的数据,实际就是数据封装思想的体现。每个 Java 程序的主类都必须是 public 类,也是基于相同的原因。
作用域 | 当前类 | 同一package | 子孙类 | 其他package |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | × |
friendly | √ | √ | × | × |
private | √ | × | × | × |
14、Overload和Override的区别?Overloaded的方法是否可以改变返回值的类型?
Overload是重载的意思,Override是覆盖的意思,也就是重写。他们都是作用在方法级别上的。
重载,通常是在同一个类中(或者父类和子类之间)它是指我们可以定义一些名称相同的方法,通过定义不同的输入参数来区分这些方法,然后再调用时,JVM就会根据不同的参数样式,来匹配合适的方法执行。在使用重载要注意以下的几点:
-
1)在使用重载时只能通过
不同的参数样式
。例如,不同的参数类型,不同的参数个数,不同的参数顺序(当然,同一方法内的几个参数类型必须不一样,例如可以是fun(int,float),但是不能为fun(int,int)); -
2)
不能通过访问权限、返回类型、抛出的异常进行重载
; -
3)
方法的异常类型和数目不会对重载造成影响
; -
4)
对于继承来说,如果某一方法在父类中是访问权限是priavte,那么就不能在子类对其进行重载,如果定义的话,也只是定义了一个新方法,而不会达到重载的效果
。
覆盖,就是覆盖了一个方法并且对其重写,以求达到不同的作用。对我们来说最熟悉的覆盖就是对接口方法的实现,在接口中一般只是对方法进行了声明,而我们在实现时,就需要实现接口声明的所有方法。除了这个典型的用法以外,我们在继承中也可能会在子类覆盖父类中的方法。在覆盖要注意以下的几点:
-
1)覆盖的方法的标志必须要和被覆盖的方法的
标志完全匹配
,才能达到覆盖的效果; -
2)覆盖的方法的返回值必须和被覆盖的方法的
返回一致
; -
3)覆盖的方法所抛出的异常必须和被覆盖方法的所抛出的
异常一致,或者是其子类
; -
4)
被覆盖的方法不能为private
,否则在其子类中只是新定义了一个方法,并没有对其进行覆盖。
总结:
重写发生在运行期,是子类对父类的允许访问的方法的实现过程进行重新编写。重写就是子类对父类方法的重新改造,外部样子不能改变,内部逻辑可以改变。
方法的重写要遵循“两同两小一大”
-
“两同”即方法名相同、形参列表相同;
-
“两小”指的是子类方法返回值类型应比父类方法返回值类型更小或相等,子类方法声明抛出的异常类应比父类方法声明抛出的异常类更小或相等;
-
“一大”指的是子类方法的访问权限应比父类方法的访问权限更大或相等。
注意:如果方法的返回类型是 void 和基本数据类型,则返回值重写时不可修改。但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。
至于重载的方法是否可以改变返回值的类型这个问题,需要分情况来说。如果几个重载方法的参数列表不一样,它们的返回者类型当然也可以不一样。但是如果两个方法的参数列表完全一样,是否可以让它们的返回值不同来实现重载。这是不行的,因为假设该类中有两个名称和参数列表完全相同的方法,仅仅是返回类型不同,Java就无法确定编程者倒底是想调用哪个方法了,因为它无法通过返回结果类型来判断。
15、this 关键字有什么作用?
this代表所在类的当前对象的引用(地址值)
,即代表当前对象。
-
this代表了当前对象的引用。
-
this关键字可以用在实例方法和构造器中。
-
this用在方法中,谁调用这个方法,this就代表谁。
-
this用在构造器,代表了构造器正在初始化的那个对象的引用。
-
this可以区分变量是访问的成员变量还是局部变量。
16、final 关键字有什么作用?
final 表示不可变、最终的意思,可用于修饰类、变量和方法:
-
被 final 修饰的
类不可以被继承
-
被 final 修饰的
方法不可以被重写
-
被 final 修饰的变量不可改变,
被 final 修饰的变量必须被显式第指定初始值
,还得注意的是,如果是引用类型变量这里的不可变指的是变量的引用不可变,但引用指向的内容是可变的,也就是如果是基本数据类型变量那么代表其数值不可发生改变,如果是引用数据类型变量那么代表其引用不可改变,引用指向的对象内容可以发生改变。
17、static关键字有什么作用?
在Java中,static是一个修饰符(关键字),用于修饰类的成员方法、类的成员变量,另外可以编写static代码块来优化程序性能;还可以用在内部类的定义上,表示为静态内部类。被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载后,就可以通过类名去进行访问。
-
static修饰的方法一般称作
静态方法
,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对象,既然都没有对象,就谈不上this了。并且由于这个特性,在静态方法中不能访问类的非静态成员变量和非静态成员方法,因为非静态成员方法/变量都必须依赖具体的对象才能够被调用。但是要注意的是,虽然在静态方法中不能访问非静态成员方法和非静态成员变量,但是在非静态成员方法中是可以访问静态成员方法/变量的。 -
static修饰的变量也称为
静态变量
,静态变量和非静态变量的区别是:静态变量被所有对象共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static成员变量的初始化顺序按照定义的顺序进行初始化。 -
static关键字还有一个比较重要的作用就是用来形成
静态代码块
以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来依次执行每个static块,并且只会执行一次。 - static块可以优化程序性能,是因为它的特性:只会在类被初次加载的时候执行一次。
18、简单谈谈transient关键字。
为什么要用transient关键字?
当持久化对象时,可能有一个特殊的对象数据成员(如用户的密码,银行卡号等),我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。
transient关键字有什么作用?
transient是短暂的意思。transient是Java语言的关键字,用来表示一个域不是该对象串行化的一部分。当一个对象被串行化的时候,transient型变量的值不包括在串行化的表示中,然而非transient型的变量是被包括进去的。 也就是对于transient 修饰的成员变量,在类实例的序列化处理过程中会被忽略
。
transient关键字使用特点?
(1)一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问。 (2)transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口。 (3)被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化(如果反序列化后类中static型变量还有值,则值为当前JVM中对应static变量的值)
19、构造器有哪些特点,是否可被重写?
构造方法特点如下:
-
名字必须与类名相同。
-
没有返回值,且不能用 void 声明构造函数。
-
生成类的对象时自动执行,无需调用。
构造器不能被重写,假设可以重写的话
,根据重写的概念可知需要保证方法名和参数列表与父类的相一致,但是根据构造器的概念可知要求构造方法名必须和本类名保持一致,此时二者概念冲突相互违背,所以构造器不可以被重写,但可以被重载Overload。
20、String 和 StringBuilder、StringBuffer 的区别?
String 和 StringBuilder、StringBuffer 均被final关键字修饰,表示不可被继承。三者的区别主要可以从以下几个维度考虑:
是否可变
String
的值被创建后不能修改,任何对 String
的修改都会引发新的 String
对象的生成。
StringBuilder
与 StringBuffer
都继承自 AbstractStringBuilder
类,在 AbstractStringBuilder
中也是使用字符数组保存字符串,不过没有使用 final
和 private
关键字修饰,最关键的是这个 AbstractStringBuilder
类还提供了很多修改字符串的方法比如 append
方法。
线程安全性
String
中的对象是不可变的,也就可以理解为常量,线程安全。AbstractStringBuilder
是 StringBuilder
与 StringBuffer
的公共父类,定义了一些字符串的基本操作,如 expandCapacity
、append
、insert
、indexOf
等公共方法。StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的。
性能
jdk1.8 之前每次对 String
类型进行改变的时候,都会生成一个新的 String
对象,然后将指针指向新的 String
对象,性能较差。StringBuffer
每次都会对 StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder
相比使用 StringBuffer
仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。
对于三者使用的总结:
-
操作少量的数据: 适用
String
-
单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder
-
多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
21、final、finally、finalize 的区别?
-
final 用于修饰变量、方法和类:final 修饰的类不可被继承;修饰的方法不可被重写;修饰的变量不可改变,如果是基本数据类型变量那么代表其数值不可发生改变,如果是引用数据类型变量那么代表其引用不可改变,引用指向的对象内容可以发生改变。
-
finally 作为异常处理的一部分,它只能在
try/catch
语句结构中,并且附带一个语句块表示这段语句最终一定被执行(无论是否抛出异常),经常被用在需要释放资源的情况下,System.exit (0)
可以阻断 finally 执行。 -
finalize 是在
java.lang.Object
里定义的方法,也就是说每一个对象都有这么个方法,这个方法在gc
启动,该对象被回收的时候被调用。 - 注意:
一个对象的 finalize 方法只会被调用一次,finalize 被调用不一定会立即回收该对象
,所以有可能调用 finalize 后,该对象又不需要被回收了,然后到了真正要被回收的时候,因为前面调用过一次,所以不会再次调用 finalize 了,进而产生问题,因此不推荐使用 finalize 方法。
22、String 类为什么是不可变?有什么特点?字符串拼接是如何实现的?
众所周知,String
类中使用 final
关键字修饰字符数组来保存字符串,我们知道被 final
关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象,但是对象的内容是可以改变的,数组呢就是一种引用类型的变量。因此,final
关键字修饰的数组保存字符串并不是 String
不可变的根本原因,因为这个数组保存的字符串是内容可变的(final
修饰引用类型变量的情况)。
String
真正不可变有下面几点原因:
-
保存字符串的数组被
final
修饰且为私有的,并且String
类没有提供/暴露修改这个字符串的方法。 -
String
类被final
修饰导致其不能被继承,进而避免了子类破坏String
不可变。
在Java中String 类的确是不可变的,不可变类有一些优点,比如因为它的对象是只读的,所以多线程并发访问也不会有任何问题。当然也有一些缺点,比如每个不同的状态都要一个对象来代表,可能造成性能上的问题。使用“+”运算符的拼接操作,其实是会生成新的对象。例如:
String a = "hello ";
String b = "world!";
String ab = a + b;
jdk1.8 之前
在Java8 时JDK 对“+”号拼接进行了优化,上面所写的拼接方式会被优化为基于 StringBuilder 的 append 方法进行处理。Java 会在编译期对“+”号进行处理。也就是说其实上面的代码其实相当于:
String a = "hello ";
String b = "world!";
StringBuilder sb = new StringBuilder();
sb.append(a);
sb.append(b);
String ab = sb.toString();
此时,如果再笼统的回答:通过加号拼接字符串会创建多个 String 对象,因此性能比 StringBuilder 差,就是错误的了。因为本质上加号拼接的效果最终经过编译器处理之后和 StringBuilder 是一致的。
当然,循环里拼接还是建议用 StringBuilder,因为循环一次就会创建一个新的 StringBuilder 对象。
23、字符串常量池的作用了解吗?
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
String aa = "ab"; // 放在常量池中
String bb = "ab"; // 从常量池中查找
System.out.println(aa==bb);// true
24、String s = new String("abc")创建了几个对象?
很明显,一个或两个。如果字符串常量池已经有“abc”,则是一个;否则,两个。
当字符串常量池没有 “abc”,此时会创建如下两个对象:
-
一个是字符串字面量 "abc" 所对应的、字符串常量池中的实例
-
另一个是通过 new String() 创建并初始化的,内容与"abc"相同的实例,在堆中。
25、intern 方法有什么作用?
String类的intern()方法是一个Native本地方法,可以通过调用 String 的 intern 方法在程序运行时向字符串常量池中添加新的字符串引用。它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串(即 equals()方法为 true,也就是内容一样),则返回常量池中字符串的引用,否则,将新的字符串放入常量池,并返回新字符串的引用,不同版本的JAVA虚拟机对此方法的实现可能不同。
-
如果当前字符串内容存在于字符串常量池(即 equals()方法为 true,也就是内容一样),直接返回字符串常量池中的字符串
-
否则,将此 String 对象添加到池中,并返回 String 对象的引用
Java OOP
26、什么是面向对象程序设计(OOP)?你是如何理解面向对象的?有何优点?
面向对象程序设计OOP是当下主流的程序设计泛型,它取代了20世纪70年代的结构化或过程式编程技术。面向对象编程更符合我们的思维逻辑,将我们现实生活中的对象进行抽象成类进行使用,能够直接反应现实生活中的对象,容易理解,使编程更容易。面向对象更加适合解决规模较大的问题。
面向对象程序设计的优点主要有:
-
1、易维护
采用面向对象思想设计的结构,可读性高,由于继承的存在,即使改变需求,那么维护也只是在局部模块,所以维护起来是非常方便和较低成本的。 -
2、质量高
在设计时,可重用现有的,在以前的项目的领域中已被测试过的类使系统满足业务需求并具有较高的质量。 -
3、代码可重用性强(效率高)
在软件开发时,根据设计的需要对现实世界的事物进行抽象,产生类。使用这样的方法解决问题,接近于日常生活和自然的思考方式,势必提高软件开发的效率和质量。
-
4、易扩展
由于继承、封装、多态的特性,自然设计出高内聚、低耦合的系统结构,使得系统更灵活、更容易扩展,而且成本较低。
27、面向对象和面向过程的区别?
面向对象和面向过程是两种不同的程序设计泛型也可以说是不同的编程思想,两者的主要区别在于解决问题的方式
不同:
-
面向过程把解决问题的过程拆成一个个方法或者叫函数,通过一个个方法(函数)相互调用执行去解决问题,典型代表的语言比如C语言。
-
面向对象会先抽象出类,然后进行类实例化用对象执行方法的方式解决问题。 使用类和对象目的是为了写出通用的代码,加强代码的重用,屏蔽差异性,典型代表的语言比如Java语言。
28、面向对象的特点?
-
封装
所谓封装就是利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体。数据被保护在抽象数据类型的内部,尽可能地隐藏内部的细节,只保留一些对外接口使之与外部发生联系。用户无需知道对象内部的细节,但可以通过对象对外提供的接口来访问该对象。
简单说就是把一个对象的状态信息(也就是属性)隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是提供一些可以被外界访问的方法来操作属性。也就是把⼀个对象的属性私有化,同时提供⼀些可以被外界访问的属性的方法。
-
一是把对象的属性和行为看成一个密不可分的整体,将这两者封装在一个不可分割的独立单元(即对象)中
-
二是信息隐藏,把不需要让外界知道的信息隐藏起来,尽可能
-
-
IS-A 关系,例如 Cat 和 Animal 就是一种 IS-A 关系,因此 Cat 可以继承自 Animal,从而获得 Animal 的属性和方法。
关于继承有以下三个要点:
-
子类拥有父类对象所有的属性和⽅法(包括私有属性和私有方法),但是父类中的私有属性和方法子类是无法访问,只是拥有。
-
子类可以拥有自己属性和方法,即子类可以对父类进行扩展。
-
子类可以用自己的方式实现父类的方法。
-
-
多态
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即⼀个引用变量到底会指向哪个具体类的实例对象,该引用变量发出的方法调用到底是哪个具体类中实现的⽅法,必须在由程序运行期间才能决定。
在 Java 中有两种形式可以实现多态:继承(多个子类对同一方法的重写)和接口(实现接口并覆盖接口中同一方法)
多态的特点:
-
对象类型和引用类型之间具有继承(类)/实现(接口)的关系;
-
引用类型变量发出的方法调用的到底是哪个类中的方法,必须在程序运行期间才能确定;
-
多态不能调用“只在子类存在但在父类不存在”的方法;
-
如果子类重写了父类的方法,真正执行的是子类覆盖的方法,如果子类没有覆盖父类的方法,执行的是父类的方法
-
29、封装、继承和多态的优点分别有什么?
封装
-
减少耦合
: 可以独立地开发、测试、优化、使用、理解和修改 -
减轻维护的负担
: 可以更容易被程序员理解,并且在调试的时候可以不影响其他模块 -
有效地调节性能
: 可以通过剖析确定哪些模块影响了系统的性能 -
提高软件的
可重用性
-
降低了构建大型系统的风险
: 即使整个系统不可用,但是这些独立的模块却有可能是可用的
继承:
-
提高类代码的复用性
-
提高了代码的维护性
多态:
-
消除类型之间的耦合关系
-
可替换性
-
可扩充性
-
接口性
-
灵活性
-
简化性
30、Java中实现多态的机制是什么?
Java语言实现多态靠的是父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,而程序调用的方法在运行期才动态绑定,也就是说引用变量所指向的具体实例对象的方法是内存里正在运行的那个对象的方法,而不是引用变量的类型中定义的方法。
31、Java 创建对象有哪几种方式?
Java 中有以下四种创建对象的方式:
-
new 创建新对象
-
通过反射机制
-
采用 clone 机制
-
通过序列化机制
前两者都需要显式地调用构造方法。对于 clone 机制,需要注意浅拷贝和深拷贝的区别,对于序列化机制需要明确其实现原理,在 Java 中序列化可以通过实现 Externalizable 或者 Serializable 来实现。
32、接口和抽象类有什么共同点和区别?
在Java中,使用abstract
关键字修饰的类即为抽象类,接口可以说成是抽象类的一种特例,使用interface
关键字表示定义。
共同点 :
-
都不能被实例化对象。
-
都可以包含抽象方法。
-
都可以有默认实现的方法(Java 8 可以用
default
关键在接口中定义默认方法)。
区别 :
-
在方法定义上,含有抽象方法的类必须定义为抽象类,抽象类中可以包含非抽象方法。抽象类中定义抽象方法必须在具体子类中实现,所以,
不能有抽象构造方法或抽象静态方法
。如果抽象类的子类没有实现抽象父类中的所有
抽象方法,那么子类也必须定义为abstract类型。接口中的方法定义默认为public abstract类型也就是说接口中的所有方法都必须是抽象的
。所有方法在接口中不能有实现(Java 8 开始接口方法可以有默认实现)。再有就是抽象类中可以包含普通静态方法,在JDK1.8之前接口中不能包含静态方法。抽象类中的抽象方法的访问类型可以是public,protected和默认类型,但接口中的抽象方法只能是public类型的,并且默认即为public abstract类型。 -
在变量定义上,接口中的成员变量只能是
public static final
类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员变量的访问类型可以任意,但接口中定义的变量只能是public static final类型。 -
在子类实现上,一个类可以实现多个接口,但只能实现一个抽象类。接口自己本身可以通过 extends 关键字扩展多个接口。
-
在构造方法上,抽象类可以有构造方法,接口中不能有构造方法。
-
在设计层面上,抽象是对类的抽象,是⼀种模板设计,抽象类主要用于代码复用,而接口是对行为的抽象,是⼀种行为的规范。
-
在 JDK8 中,接口也可以定义静态⽅法,可以直接用接口名调用。实现类和实现是不可以调用的。如果同时实现两个接口,接口中定义了一样的默认⽅法,则必须重写,不然会报错。
-
总结⼀下 JDK7 ~JDK9 Java 中接口的变化:
-
在 jdk 7 或更早版本中,接口里面只能有常量变量和抽象方法。这些接口方法必须由选择实现接口的类实现。
-
jdk 8 的时候接口可以有默认方法和静态方法功能。
-
jdk 9 在接口中引入了私有方法和私有静态方法。
33、深拷贝和浅拷贝区别了解吗?
-
浅拷贝:仅拷贝原对象的成员变量的值,也就是基本数据类型变量的值,和引用数据类型变量的地址值,而对于引用类型变量指向的堆中的对象不会拷贝。
-
深拷贝:完全拷贝一个对象,拷贝原对象的成员变量的值,其中堆中的对象也会拷贝一份。
因此深拷贝是安全的,浅拷贝的话如果有引用类型,那么拷贝后对象,引用类型变量修改,会影响原对象
。
浅拷贝如何实现呢?
Object 类提供的 clone()方法可以非常简单地实现对象的浅拷贝。
深拷贝如何实现呢?
-
重写克隆方法:重写克隆方法,引用类型变量单独克隆,这里可能会涉及多层递归。
-
序列化:可以先将原对象序列化,再反序列化成拷贝对象。
34、什么是字节码?采用字节码的好处是什么?
在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class
的文件),它不面向任何特定的处理器,只面向JVM虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。
35、为什么说 Java 语言“编译与解释并存”?
当我们编写完Java 源程序后首先要经过javac命令进行编译,得到以.class为后缀的字节码文件,这种字节码文件必须由 Java 解释器来解释执行,然后使用java命令启动Java虚拟机去解释执行字节码文件两个步骤。此时 Java 语言既具有编译型语言的特征,也具有解释型语言的特征。所以Java语音就是编译与解释并存。
36、Java语言的跨平台是如何做到的?
Java代码执行过程
:Java编译器通过生成与特定的计算机体系结构无关的字节码指令文件,这是一种编译过的代码,只要有Java运行时系统。这些编译后的代码可以在许多不同处理器上运行,精心设计的字节码不仅可以很容易地在任何机器上解释执行,而且还可以动态的转换成本地机器代码。(或者这样说:当使用Java编译器编译Java程序时,生成的是与平台无关的字节码,这些字节码只面向JVM。不同平台的JVM都是不同的,但它们都提供了相同的接口。JVM是Java程序跨平台的关键部分,只要为不同平台实现了相应的虚拟机,编译后的Java字节码就可以在该平台上运行。)
Java语言中的基本数据类型
,比如int永远为32位的整数,即占4个字节大小,然而,在C /C++中int数据类型可能是16位整数也有可能是32位整数,唯一的限制,就是int类型的字节数不能低于short int的,并且不能高于long int的,在Java语言当中数值类型有固定的字节数,这就消除了代码移植时的一个令人头痛的问题。
此外,Java语言当中二进制数据以固定的格式进行存储和传输,消除了字节顺序的困扰,并且字符串采用标准的Unicode的格式存储。
以上几点都为Java语言的可移植性提供了支持。
37、什么是包装类?为什么需要包装类?
Java 中有 8 个基本类型,JDK中分别为其提供了与之对应的 8 个包装类:
-
byte -- Byte
-
boolean -- Boolean
-
short -- Short
-
char -- Character
-
int -- Integer
-
long -- Long
-
float -- Float
-
double -- Double
为什么需要包装类:
-
基本数据类型使用方便、简单、高效,但不支持泛型、集合元素不支持
-
基本类型不符合面向对象思维
-
包装类提供很多方法,方便使用,如 Integer 类 toHexString(int i)、parseInt(String s) 方法等等
38、什么是抽象类?有什么特点?
在Java中被abstract
关键字修饰的类即为抽象类,abstract
抽象类不能创建的实例对象。含有抽象方法的类必须定义为抽象类,抽象类中可以包含非抽象方法。抽象类中定义抽象方法必须在具体子类中实现,所以,不能有抽象构造方法或抽象静态方法
。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为abstract类型。
39、什么是Java匿名类?有什么特点?
匿名类是指没有类名的内部类,必须在创建时使用 new
语句来声明类。其语法形式如下:
new <类或接口>() {
// 类的主体
};
这种形式的 new 语句声明一个新的匿名类,它对一个给定的类进行扩展,或者实现一个给定的接口。使用匿名类可使代码更加简洁、紧凑,模块化程度更高。
-
继承一个类,重写其方法。
-
实现一个接口(可以是多个),实现其方法。
匿名类有如下特点:
-
1)匿名类和局部内部类一样,可以访问外部类的所有成员。如果匿名类位于一个方法中,则匿名类只能访问方法中 final 类型的局部变量和参数。
-
2)匿名类中允许使用非静态代码块进行成员初始化操作。
-
3)匿名类的非静态代码块会在父类的构造方法之后被执行。
40、什么是内部类?有什么特点?
内部类就是在一个类的内部定义的类,比如在类 Outer 的内部再定义一个类 Inner,此时类 Inner 就称为内部类
(或称为嵌套类),而类 Outer 则称为外部类
(或称为宿主类)。内部类可以很好地实现隐藏,一般的非内部类是不允许有 private 与 protected 权限的,但内部类可以。内部类拥有外部类的所有元素的访问权限
。
内部类的特点如下:
-
内部类仍然是一个独立的类
,在编译之后内部类会被编译成独立的.class
文件,但是前面冠以外部类的类名和$
符号。 -
内部类不能用普通的方式访问
。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否为 private 的。 -
内部类声明成静态的,就不能随便访问外部类的成员变量,仍然是只能访问外部类的静态成员变量。
-
外部类只有两种访问级别:public 和默认;在方法体外面定义的内部类的访问类型可以是public、protecte、默认的、private等4种类型,它们决定这个内部类的定义对其他类是否可见。局部内部类前面不能有访问类型修饰符,但这种内部类的前面可以使用final或abstract修饰符。这种内部类对其他类是不可见的即其他类无法引用这种内部类,但是这种内部类创建的实例对象可以传递给其他类访问。这种内部类必须是先定义,后使用。
-
在外部类中可以直接通过内部类的类名访问内部类。
-
在外部类以外的其他类中则需要通过内部类的完整类名访问内部类。
-
内部类与外部类不能重名。
41、内部类可以分为那几种?分别有哪些特点?
内部类可以分为:实例内部类、静态内部类和成员内部类。
实例内部类
-
实例内部类是指
没有用 static 修饰的内部类
,有的地方也称为非静态内部类。 -
在外部类的静态方法和外部类以外的其他类中,必须通过外部类的实例创建内部类的实例。
-
在实例内部类中,可以访问外部类的所有成员。注意:如果有多层嵌套,则内部类可以访问所有外部类的成员。
-
在外部类中不能直接访问内部类的成员,而必须通过内部类的实例去访问。
-
外部类实例与内部类实例是一对多的关系,也就是说一个内部类实例只对应一个外部类实例,而一个外部类实例则可以对应多个内部类实例。
-
在实例内部类中不能定义 static 成员,除非同时使用 final 和 static 修饰。
静态内部类
-
静态内部类是指
使用 static 修饰的内部类
。 -
在创建静态内部类的实例时,不需要创建外部类的实例。
-
静态内部类中可以定义静态成员和实例成员。外部类以外的其他类需要通过完整的类名访问静态内部类中的静态成员,如果要访问静态内部类中的实例成员,则需要通过静态内部类的实例。
-
静态内部类可以直接访问外部类的静态成员,如果要访问外部类的实例成员,则需要通过外部类的实例去访问。
局部内部类
-
局部内部类是指
在一个方法中定义的内部类
。 -
局部内部类必须是先定义,后使用。
-
局部内部类与局部变量一样,不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。
-
局部内部类只在当前方法中有效。
-
局部内部类中不能定义 static 成员。
-
局部内部类中还可以包含内部类,但是这些内部类也不能使用访问控制修饰符(public、private 和 protected)和 static 修饰符修饰。
-
在局部内部类中可以访问外部类的所有成员。
-
在局部内部类中只可以访问当前方法中 final 类型的参数与变量。如果方法中的成员与外部类中的成员同名,则可以使用 <OuterClassName>.this.<MemberName> 的形式访问外部类中的成员。
42、自动装箱与拆箱了解吗?原理是什么?
所谓自动拆装箱就是指基本数据类型与之其对应的包装类之间的相互转换
。
-
装箱:将基本类型用它们对应的引用类型包装起来;
-
拆箱:将包装类型转换为基本数据类型;
从字节码中,我们发现装箱其实就是调用了 包装类的valueOf()
方法,拆箱其实就是调用了 xxxValue()
方法。注意:如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。
43、Object 类的常见方法有了解吗?
在Java中,Object 类是一个特殊的类,是所有类的父类,也就是说所有类都可以调用它的方法。它主要提供了以下 11 个方法,大概可以分为六类:
对象比较:
-
public native int hashCode()
:native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的 HashMap。 -
public boolean equals(Object obj)
:用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写,用于比较字符串的值是否相等。
对象拷贝:
-
protected native Object clone() throws CloneNotSupportedException
:naitive 方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为 true,x.clone().getClass() == x.getClass() 为 true。Object 本身没有实现 Cloneable 接口,所以不重写 clone 方法并且进行调用的话会发生 CloneNotSupportedException 异常。
对象转字符串:
-
public String toString()
:返回类的名字@实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
多线程调度:
-
public final native void notify()
:native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。 -
public final native void notifyAll()
:native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。 -
public final native void wait(long timeout) throws InterruptedException
:native 方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 。timeout 是等待时间。 -
public final void wait(long timeout, int nanos) throws InterruptedException
:多了 nanos 参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 毫秒。 -
public final void wait() throws InterruptedException
:跟之前的 2 个 wait 方法一样,只不过该方法一直等待,没有超时时间这个概念
反射:
-
public final native Class<?> getClass()
:native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
垃圾回收:
-
protected void finalize() throws Throwable
:通知垃圾收集器回收对象
44、你重写过 hashCode() 和 equals()么?为什么重写 equals() 时必须重写 hashCode() 方法?
hashCode() 有什么用?
hashCode()
的作用是获取哈希码(为一个int
整数),也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置。
hashCode()定义在 JDK 的 Object
类中,这就意味着 Java 中的任何类都包含有 hashCode()
方法。另外需要注意的是: Object
的 hashCode() 方法是本地方法,也就是用 C 语言或 C++ 实现的,该方法通常用来将对象的内存地址转换为整数之后返回。
public native int hashCode();
散列表存储的是键值对(key-value),它的特点是:能根据“键”快速的检索出对应的“值”。这其中就利用到了散列码!
为什么要有 hashCode?
这里以在HashSet集合类中使用hashCode()方法解释:
当把对象元素加入 HashSet
容器时,HashSet
会先计算对象的 hashCode
值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashCode
值作比较,如果没有相符的 hashCode
,HashSet
会假设对象没有重复出现,则直接添加进容器中。但是如果发现有相同 hashCode
值的对象,这时会调用 equals()
方法来检查 hashCode
相等的对象是否真的相同。如果两者相同,HashSet
就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals
的次数,相应就大大提高了执行速度。
其实, hashCode()
和 equals()
都是用于比较两个对象是否相等。
那为什么 JDK 还要同时提供这两个方法呢?
这是因为在一些容器(比如 HashMap
、HashSet
)中,有了 hashCode()
之后,判断元素是否在对应容器中的效率会更高(参考添加元素进HastSet
的过程)!
我们在前面也提到了添加元素进HastSet
的过程,如果 HashSet
在对比的时候,同样的 hashCode
有多个对象,它会继续使用 equals()
来判断是否真的相同。也就是说 hashCode
帮助我们大大缩小了查找成本。
那为什么不只提供 hashCode()
方法呢?
这是因为两个对象的hashCode
值相等并不代表两个对象就相等。
那为什么两个对象有相同的 hashCode
值,它们也不一定是相等的?
因为 hashCode()
所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的 hashCode
)。
总结下来就是 :
-
如果两个对象的
hashCode
值相等,那这两个对象不一定相等(哈希碰撞)。 -
如果两个对象的
hashCode
值相等并且equals()
方法也返回true
,我们才认为这两个对象相等。 -
如果两个对象的
hashCode
值不相等,我们就可以直接认为这两个对象不相等。
相信大家看了我前面对 hashCode()
和 equals()
的介绍之后,下面这个问题已经难不倒你们了。
为什么重写 equals() 时必须重写 hashCode() 方法?
因为两个相等的对象的 hashCode
值必须是相等。也就是说如果 equals
方法判断两个对象是相等的,那这两个对象的 hashCode
值也要相等。
如果重写 equals()
时没有重写 hashC这是因为两个对象的
hashCode值相等并不代表两个对象就相等。因为
hashCode()所使用的哈希算法也许刚好会让多个对象传回相同的哈希值。越糟糕的哈希算法越容易碰撞,但这也与数据值域分布的特性有关(所谓哈希碰撞也就是指的是不同的对象得到相同的
hashCode)。ode()
方法的话就可能会导致 equals
方法判断是相等的两个对象,hashCode
值却不相等。
总结 :
-
equals
方法判断两个对象是相等的,那这两个对象的hashCode
值也要相等。 -
两个对象有相同的
hashCode
值,他们也不一定是相等的(哈希碰撞)。
Java 异常
45、简单说说Java 中异常处理体系。
在 Java 语言中Throwable
是所有错误或异常的基类。 Throwable 又分为Error
和Exception
,其中 Error 是系统内部错误,比如虚拟机异常,是程序无法处理的。Exception
是程序问题导致的异常,又分为两种:
-
Checked Exception 受检异常:编译器会强制检查并要求处理的异常。
-
Runtime Exception 运行时异常:程序运行中出现异常,源代码编译过程中并不强制要求我们处理的异常,比如我们熟悉的空指针、数组下标越界等等
46、Java中异常的处理方式有哪些?
Java中针对异常的处理主要有两种方式:抛出和捕获。
-
当遇到异常不进行具体处理,可以继续抛给调用者 (throw,throws)
抛出异常有三种形式,一是使用 throw关键字,二是使用throws关键字,还有一种系统自动抛异常。
需要注意的是throws 用在方法上,后面跟的是异常类,可以跟多个;而 throw 用在方法内部,后面跟的是异常对象。
-
还可以使用try catch进行捕获异常
在 catch 语句块中补货发生的异常,并进行处理。
try {
//包含可能会出现异常的代码以及声明异常的方法
}catch(Exception e) {
//捕获异常并进行处理
}finally { }
//可选,必执行的代码
}
47、说几种常用的运行时异常类。
-
NullPointerException
空指针异常、 -
NumberFormatException
字符串转换为数字异常、 -
ArrayIndexOutOfBoundsException
数组索引越界异常、 -
ClassCastException
类型转换异常、 -
ArithmeticException
算术异常
48、Exception 和 Error 有什么区别?
在 Java 中,所有的异常都有一个共同的祖先 java.lang
包中的 Throwable
类。Throwable
类有两个重要的子类,也就是Exception类和Error类。
-
Exception
:程序本身可以处理的异常,可以通过catch
来进行捕获。Exception
又可以分为 Checked Exception (受检查异常,必须处理否则无法通过编译) 和 Unchecked Exception (不受检查异常,可以不处理)。 -
Error
:Error
属于程序无法处理的错误 ,我们没办法通过catch
来进行捕获 。例如Java 虚拟机运行错误(Virtual MachineError
)、虚拟机内存不够错误(OutOfMemoryError
)、类定义错误(NoClassDefFoundError
)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。
49、Throwable 类常用方法有哪些?
Throwable是 Java 语言中异常体系的基类,也就是所有错误或异常的父类。常用方法有获取异常发生时的简要描述信息getMessage()、获取异常发生时的详细信息toString()、获取异常对象的本地化信息getLocalizedMessage()如果子类没有覆盖该方法,则该方法返回的信息与 getMessage()返回的结果相同以及在控制台上打印 Throwable
对象封装的异常信息的printStackTrace()方法。
-
String getMessage()
: 返回异常发生时的简要描述 -
String toString()
: 返回异常发生时的详细信息 -
String getLocalizedMessage()
: 返回异常对象的本地化信息。使用Throwable
的子类覆盖这个方法,可以生成本地化信息。如果子类没有覆盖该方法,则该方法返回的信息与getMessage()
返回的结果相同 -
void printStackTrace()
: 在控制台上打印Throwable
对象封装的异常信息
50、Checked Exception 和 Unchecked Exception 有什么区别?
Checked Exception 即受检查异常,Java 代码在编译过程中,如果受检查异常没有被 catch
或者throw
处理的话,就没办法通过编译 。
除了RuntimeException
及其子类以外,其他的Exception
类及其子类都属于受检查异常 。常见的受检查异常有: IO 相关的异常、ClassNotFoundException
、SQLException
...。
Unchecked Exception 即 不受检查异常 ,Java 代码在编译过程中 ,我们即使不处理不受检查异常也可以正常通过编译。
RuntimeException
及其子类都统称为非受检查异常,例如:NullPointerException
、NumberFormatException
(字符串转换为数字)、ArrayIndexOutOfBoundsException
(数组越界)、ClassCastException
(类型转换错误)、ArithmeticException
(算术错误)等。
51、说说在Java中try-catch-finally 如何使用?
try-catch-finally 语句在Java中用于控制捕获异常。
-
try
块: 将有可能发生异常的代码写入try语句块中,用于捕获异常。其后可接零个或多个catch
块,如果没有catch
块,则必须跟一个finally
块。 -
catch
块: 用于处理 try 捕获到的异常,可以有多个,以便根据不同的异常类型进行不同的处理。 -
finally
块: 无论try语句块中的代码是否抛出异常,若抛出异常也无论该异常是否被catch进行捕获,finally
块里的语句都会被执行。 try
块或catch
块中遇到return
语句时,finally
注意:不要在 finally 语句块中使用 return! 当 try 语句和 finally 语句中都有 return 语句时,try 语句块中的 return 语句不会被执行。
52、Java中的异常处理机制的简单原理和应用。
异常是指Java程序运行时所发生的非正常情况或错误,Java使用面向对象的方式来处理异常,它把程序中发生的每个异常也都分别封装到一个对象来表示的,该对象中包含有异常的信息。
Java对异常进行了分类,不同类型的异常分别用不同的Java类表示,所有异常的根类为java.lang.Throwable
,Throwable下面又派生了两个子类:Error和Exception
,Error 表示应用程序本身无法克服和恢复的一种严重问题,程序只有死的份了,例如,说内存溢出和线程死锁等系统问题。Exception表示程序还能够克服和恢复的问题,其中又分为系统异常和普通异常,系统异常是软件本身缺陷所导致的问题,也就是软件开发人员考虑不周所导致的问题,软件使用者无法克服和恢复这种问题,但在这种问题下还可以让软件系统继续运行或者让软件死掉,例如数组脚本越界(ArrayIndexOutOfBoundsException),空指针异常(NullPointerException)、类转换异常(ClassCastException);普通异常是运行环境的变化或异常所导致的问题,是用户能够克服的问题,例如,网络断线,硬盘空间不够,发生这样的异常后,程序不应该死掉。
java为系统异常和普通异常提供了不同的解决方案,编译器强制普通异常必须try..catch处理或用throws声明继续抛给上层调用方法处理,所以普通异常也称为checked异常,而系统异常可以处理也可以不处理,所以,编译器不强制用try..catch处理或用throws声明,所以系统异常也称为unchecked异常。
AI模拟面试
本文章中全部题目已经收录到了我的AI模拟面试客户端软件中,关于这个软件看这里: