面试题总结-----Java基础知识

文章目录

参考内容来自:
https://snailclimb.gitee.io/javaguide/#/

1、 java语言的特点?

(1)简单易学;
(2)面向对象(封装、继承、多态)
(3)跨平台性(Java自带的虚拟机很好的实现了跨平台性)
以前跨平台性是java的最大优势,但是随着虚拟技术的发展,通过docker就可以实现跨平台,这也表示跨平台不是java独有的特点了。
(4)安全性
(5)支持多线程
(6)支持网络编程且方便
(7)编译与解释并存
(8)高性能
(9)可移植性
(10)动态性

2、java与C++的区别?

(1)java不提供指针来直接访问内存,程序内存更加安全;
(2)java的类是单继承的,C++支持多重继承;虽然Java的类不可以多继承,但是接口可以多继承。
(3)java有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存;
(4)C++同时支持方法重载和操作符重载,但是java只支持方法重载。

3、为什么java可以实现一次编写,到处运行?

因为java是一种高级语言,要经过编译和解释。通过编译将.java文件生成.class文件,也就是字节码文件,然后通过java虚拟机(JVM)将字节码文件解释为机器语言。这个.class文件就是可以到处运行的文件,java中提供了不同平台的虚拟技术,所以在不同的平台上,JVM会将.class文件解释为不同的机器语言,然后进行实现。这就是一次编写,到处运行。

4、什么是JVM、JDK、JRE?

1.JVM(java虚拟机)是运行java字节码文件的虚拟机。jvm有针对不同系统的特定实现,目的是为了使用相同的字节码文件,会在不同的系统上产生相同的结果,这也是“一次编写,到处运行”d的体现。
JVM并不是只有一种,只要满足JVM的规范,每个人或公司都可以开发自己的JVM.
2.JDK是功能齐全的Java SDK,它拥有JRE所拥有的一切,还有编译器(javac)和工具(javadoc和jdb)。它能创建和编译程序。
3.JRE是是java运行时环境。它是运行已编译java程序所需的所有内容的集合,包括Java虚拟机,java类库,java命令和其他的一些基础构件。但是它不能用于创建新程序。

5、字符型常量和字符串常量的区别?

(1)字符型常量是单引号引起的一个字符;字符串常量是双引号引起的0个或多个字符。
(2)字符型常量相当于一个整型值(ASCII值),可以参加表达式运算;字符串常量代表一个地址值(该字符串在内存中存放的位置)。
(3)字符型常量只占两个字节;字符串常量占若干个字节。

字符和字节的区别?

字节(byte)是计量单位,表示数据量多少,是计算机信息技术用于计量存储容量的一种计量单位,通常情况下一字节等于八位.

字符是计算机中使用的字母、数字、字和符号,比如’A’、'B’等。一般在英文状态下一个字母占用一个字节,一个汉字占用两个字节。(在不同的编码中占用的字节数不同).

ASCII码中:一个英语字母为一个字节,一个中文汉字为两个字节。 UTF-8编码中:一个英文字母为一个字节,一个中文为三个字节.

6、一个java文件中可以有多个类吗?

可以有多个类,但是只有一个类能被public修饰,且这个类要与.java文件的文件名一样。

7、为什么java中不使用全局变量?

全局变量是可以从任何范围访问的变量,它允许你从任何其他类或函数内部访问和修改值。
平时java定义的全局变量只是相对于某个类里的,并不是真正意义上的全局变量。
因为java语言是一门面向对象的编程语言,所有的内容都是属于类的一部分,java不使用真正意义上的全局变量是为了防止数据和类成员被其他程序的其他部分有意或无意的修改。。

8、重载和重写的区别?

重载:

重载就是同一个类中多个同名方法根据不同的传参来执行不同的逻辑处理。
比如:构造方法的重载:

public Hello(String name){
}
public Hello(){
}

java允许重载任何方法,而不只是构造器方法。
发生在同一个类中(或父类和子类之间)。
重载方法名必须相同,参数类型不同,个数不同,顺序不同,方法返回值或访问修饰符可以不同

重写:

重写发生在运行期,是子类对父类允许访问的方法的实现过程进行重新编写。
两同两小一大:
方法名和参数列表必须相同;
子类的返回值类型 <= 父类的返回值类型;
子类抛出异常范围 <= 父类抛出异常范围;
子类方法的访问权限>父类方法的访问权限.

如果父类的方法被private,final,static修饰,则子类不能进行重写。
但是被static修饰的方法能够再次被声明。
构造方法无法被重写。

如果方法的返回值类型是void和基本数据类型,则返回值重写时不可修改;
但是如果方法的返回值是引用类型,重写时是可以返回该引用类型的子类的。

重写原因:子类与父类一旦产生继承关系之后,实际上子类会继承父类中的全部定义,但是这里面也可能会出现不合适的场景,子类如果发现父类中设计不足并且需要保留父类中的方法或者属性名称的情况下就会发生覆写。
在这里插入图片描述

9、java的数据类型有哪些?

java的数据类型有基本数据类型引用数据类型两种。

基本数据类型

基本数据类型有八种:
其中有四种整数型:byte,int,long ,short
两种浮点型:double,float
一种字符类型:char
一种布尔类型:boolean
这八种类型的默认值以及所占空间大小如下:

基本类型字节位数默认值取值范围
byte180-2^7 ~ 2^7-1
short2160-2^15 ~ 2^15-1
int4320-2^31 ~ 2^31-1
long8640L-2^63 ~ 2^63
char216‘u0000’0~65535
float4320f1.4E-45 ~ 3.4028235E38
double8640d4.9E-324 ~ 1.7976931348623157E308
boolean1falsetrue/false

对于布尔类型,逻辑上理解是占用一位。
java中使用long类型一定要在数值后面加上L,否则将作为整型解析。

引用数据类型

在java中除了八种基本数据类型,其他的数据类型全是引用数据类型,如:类、接口类型、数组类型、枚举类型、注解类型、字符串类型等。
java的引用数据类型相当于C语言中的指针类型,引用事实上就是指针,是指向一个对象的内存地址。引用数据类型变量中保持的是指向对象的内存地址。很多资料提到java不支持指针计算,但是指针类型还是保留下来了,只是在java中称为引用数据类型。

基本数据类型和引用数据类型的区别?

(1)存储位置
基本数据类型:
存储在栈中。
引用数据类型:
内容存放在堆中,内存地址存放在栈中。
(2)传递方式
基本数据类型:
数值传递
引用数据类型:
引用传递

值传递和引用传递的区别?

值传递指的是在方法调用时,传递的参数是按值的拷贝传递。
引用传递指的是在方法调用时,传递的参数是按引用进行传递,传递的是引用的地址,也就是变量所对应的内存空间的地址。传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间),引用类型的传递后的修改会影响原来的值。

10、基本数据类型和包装类型的区别?

在实际程序开发过程中,用户输入的信息数据都是以字符串形式进行存储的,而在程序开发中,我们需要把字符串数据根据需求转换成指定的基本数据类型,要想实现字符串与基本数据类型数据之间的转换,就要使用包装类型。
包装类型就是将基本数据类型包装起来,使其具有对象的性质,并且可以添加属性和方法。
(1)包装类型不赋值就是null,而基本数据类型有默认值且不是null;
(2)包装类型可以用于泛型,但是基本类型不可以;
(3)基本数据类型的局部变量存放在java虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被static修饰的)存放在Java虚拟机的堆中。
包装类型属于对象类型,几乎所有的对象实例都存放在堆中。
(4)相比与对象类型,基本数据类型占用的空间非常小。

11、什么是自动装箱和拆箱?原理是什么?

装箱:将基本数据类型用它们对应的引用数据类型包装起来;
拆箱:将包装类型转换成基本数据类型;
在jdk1.5之后就不需要手动装箱拆箱,提供有自动装箱和拆箱。

Integer i=10; //装箱
int n=i;//拆箱

装箱其实就是调用了包装类的ValueOf()方法;
拆箱是调用了xxxValue()方法;
如果频繁装箱拆箱,会严重影响系统的性能,应该尽量避免不必要的装箱拆箱操作。

12、String中 “==” 与 equals的区别?

如果比较的基本数据类型,那么比较的是值;
如果比较的是引用数据类型,比较的是内存地址。
(因为java只有值传递,所以对于 == 来说,不管比较基本数据类型,还是引用数据类型的变量,其本质都是比较他们的值,只是引用类型变量存的值是对象的地址)
equals()不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等。equals 直接对字符串的内容进行判断,区分大小写。equals()方法存在于Object类中,而Object 类是所有类的直接或者间接父类,因此所有类都有equals()方法。

equals()方法存在两种使用情况:
(1)类没有重写equals()方法:通过equals()比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是Object类equals()方法。
(2)类重写了equals()方法: 一般我们都是重写equals()方法来比较两个对象中的属性是否相等;若他们的属性相等,则返回true(即认为这两个对象相等)。

hashCode()有什么作用?

hashCode()的作用是获取哈希码,也称为散列码。这个哈希码的作用是确定该对象在哈希表中的索引位置
hashCode()定义在JDK的Object类中,这就意味着java中任何类都包含hashCode()函数。
需要注意的是:Object的hashCode()方法是本地方法,也就是用C语言和C++实现的,该方法通常用来将对象的内存地址转换为整数之后返回。
散列表存储的是键值对。它的特点是:能根据键快速的检索出对应的值。这其中就利用到了散列码。

为什么要有hashCode?

hashCode和equals()都是用于比较两个对象是都相等.
那么为什么jdk还要同时提供这两个方法呢?
这是因为在一些容器(比如hashMap,hashSet)中,有了hashCode()之后,判断元素是否在对应容器的效率会更高。
为什么效率会更高?
当把对象加入hashSet时,hashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他已加入的对象的hashCode值作比较,如果没有相符的hshCode,hashSet会假设对象没有重复出现。但是如果发现有相同的hashCode值的对象,这时会调用equals()方法来检查hashCode相等的对象是否真的相等。如果两者相同,hsahSet就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样就大大减少了equals的次数,相应就大大提高了执行速度。
为什么不只提供hashCode方法呢?
这是因为两个对象的hashCode值相等不代表两个对象就相等。
为什么两个对象的hashCode值相等,他们却不一定相等?
因为hashCode()所使用的哈希算法也许刚好会让多个对象传回相同的哈希值,越糟糕的哈希算法越容易碰撞,但是这也与数据值域分布的特征有关(哈希碰撞就是指不同的对象得到相同的hashCode).
总结下来就是:
(1)如果两个对象的hashCode值相等,那么这两个对象不一定相等(哈希碰撞)。
(2)如果两个对象的hashCode值相同,通过equals()方法比较的值也相等,那么这两个对象一定相等。
(3)如果这两个对象的hashCode值不相等,那么这两个对象一定不相等。

为什么重写equals()方法的时候也要重写hashCode方法?

因为两个对象相等的前提是hashCode值一定相等,也就是说如果equals方法判断的值是相等的,那么两个对象的hashCode值也是相等的,才能说明这两个对象是相等的。
如果重写equals()方法没有重写hashCode方法的话,可能会导致equals方法判断是两个相等的对象,但是他们的hashCode值却不相等。

注:

if(s == null || s.equals(""));

s == null 表示的是地址数值为空,就是没有地址,是一个空对象,不是一个字符串。
s.equals(“”) 表示有地址,但是里面的内容是空的,""代表声明了一个对象实例,这个对象实例的值是一个长度为0的空字符串。

13、super和this的区别?

(1) super和this都可以调用构造方法,super是由子类调用父类构造,而this是调用本类构造,都放在构造方法首行,所有这两个不能同时出现。
(2)在程序类汇总使用this表示先从本类查找所需要的属性或方法,如果本类不存在则查找父类定义,如果使用super则表示不查找子类,直接查找父类。
(3)this可以表示当前对象。

14、介绍一个Java的三大特征?

(1)封装
是指隐藏对象的属性和实现细节,仅对外提供公共的访问方式。将成员变量私有化,对外提供对应的setter,getter方法对其进行方法。提高对数据访问的安全性。
Java提供了private、protected和public三个访问权限符来实现良好的封装。
(2)继承
从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并扩展新的能力。
在java中,被继承的类叫做父类或超类,继承父类的加子类或派生类。
提供extends关键字来让子类继承父类。
Java是单继承。
(3)多态
相同类型的变量,调用同一种方法,表现出不同的行为特征。
实现多态的两种方式分别是:子类继承父类、类实现接口。
核心就在对父类方法的重写和对接口方法的实现,以此在运行时达到不同的执行效果。
从静态和动态的角度来划分,多态可以分为编译时多态运行时多态
编译时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的方法,编译之后会变成两个不同的方法。
运行时多态是动态的,主要指方法的重写,是通过动态绑定来实现的。

15、标识符和关键字的区别是什么?

在编写程序时,需要大量为程序、类、变量、方法等取名字,于是就有了标识符。简单来说,标识符就是一个名字。
有一些标识符被赋予了特殊的含义,只能用于特定的地方,比如private,new,import,for,try…这些被赋予特殊含义的标识符都是关键字。
所有的关键字都是小写的,在IDE中会以特殊颜色显示。
注意:true,false,null 不是关键字 ,是字面值,同时也不可以作为标识符来使用。

官方文档中:
在这里插入图片描述

16、continue、break、return的区别是什么?

在循环结构中,当循环条件不满足或循环次数达到要求时,循环会正常结束。但是,有时候可能需要在循环的过程中,提前终止循环,因为发生了某种条件。这就需要这几个关键词。
(1)continue :指跳出当前这一次循环,继续执行下一次循环。
(2)break: 指跳出整个循环体,继续执行循环下面的语句。
(3)return 用于跳出此方法,结束该方法的运用。

17、public static void main(String args[])语句分析

public 描述的是一种访问权限,主方法是一切的开始点,开始点一定是公共的。
static 程序的执行是通过类名称完成的,所以static表示此方法是有类直接调用。
void 主方法是一切的起点,起点开始就无法返回。
main 是一个系统定义好的方法名称,被称为主方法。
String args[] 字符串的名称,可以实现程序启动参数的接收。

18、注释有哪几种形式?

(1)单行注释
(2)多行注释
(3)文档注释
(注释不是越多越好)

19、什么是字节码?采用字节码的好处是什么?

在java中,JVM可以理解的代码就叫做字节码(.class文件),它不面向任何特定的处理器,只面向虚拟机。java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以java程序运行时相对来说还是比较高效的,不过和c++,go等语言相比还是有一定的差距,而且由于字节码并不针对一种特定的机器,因此Java程序无序重新编译便可以在多种不同操作系统的计算机上运行。

我们需要注意的是 .class->机器码 这一步,在这一步JVM类加载器首先会加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的,所以后面引进了JIT编译器,而JIT属于运行时编译。当JIT编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用,所以说Java是编译和共存的语言。

20、为什么说java语言编译与解释共存?

这是因为java语言既有编译型语言的特征,也具有解释型语言的特征。因为java程序要先经过编译,后解释两个步骤。由java编写的程序需要先经过编译步骤,生成字节码文件,这种字节码文件必须由Java解释器来解释执行。

21、什么是抽象类?什么是接口?它们两个有什么区别?

抽象类

基本定义: 抽象类的主要作用在于对子类中覆写方法进行约定,在抽象类里面可以定义一些抽象方法来实现这样的约定。抽象方法是指使用了abstract关键字定义的并且没有提供方法体的方法,而且抽象方法所在的类必须为抽象类,抽象类必须使用abstract关键字来定义(在普通方法上追加抽象方法就是抽象类)
注意: 抽象类不是一个完成的类。
抽象类中的抽象方法(其前有abstract 修饰)不能使用private,native,synchronized,static访问修饰符修饰。
如果要使用抽象类,必须按照以下几点:
(1)抽象类必须提供有子类,子类使用extends继承一个抽象类
(2)抽象类的子类(不是抽象类)一定要覆写抽象类中的全部抽象方法
(3)抽象类中的对象实例化可以利用对象多态性通过子类向上转型的方式完成。
(4)抽象方法没有方法体

abstract class Message{ //抽象类
	private String type;
	public abstract String getConnectInfo(); //抽象方法
	public void String setType(String type){ //普通方法
	this.type=type;
	}
}
class DataMessage extends Message{   //子类继承抽象类
	public String getConnectInfo(){  //抽象方法覆写
	return "测试";
	}
}

抽象类的相关说明:
(1)抽象类无法直接实例化,抽象类的构造方法不能创建实例,只能被子类的实例调用;
(2)抽象类中主要是进行过渡操作使用,所以当使用抽象类开发时,往往处理的是继承问题所带来的代码重复处理;
(3)在定义抽象类时绝对不能使用final关键字来定义,因为抽象类必须有子类,而final是不能有子类的;
(4)抽象类照片那个运行没有抽象方法,但是即使没有抽象方法,也无法使用new来实例化对象,即使抽象类没有实例化对象,也无法直接使用关键字new获取抽象类对象,必须依赖子类对象完成。
(5)抽象类中可以提供有static方法,并且该方法不受抽象类对象的局限。

接口

基本定义: 接口是特殊的抽象类。接口和抽象类一样不能实例化对象。
类是一种具体实现类,而接口定义了一种规范。接口定义了某一批类所需要遵守的规范,接口并不关心这些类的内部状态,也不关心这些类里方法的实现细节,它只规定这批类里必须提供某些方法,提供这些方法的类就可满足实际需求。
原始接口中只能定义抽象方法和全局变量,不能定义普通方法。但是java8对其进行了改进,允许接口定义默认方法,这些默认方法可以提供方法实现。除了添加默认方法,接口中也可以定义static方法(但是不奉行这种做法)。
接口使用原则:
(1)接口需要被子类实现,一个子类可以实现多个父接口;
(2)子类(不是抽象类),那么一定要覆写接口中全部的方法;
(3)接口对象可以利用子类对象的向上转型进行实例化;
(4)在接口中,所有的抽象方法访问权限都是public,写不写都一样。方法不写的时候也是public,而不是default,所有重写时只能用public。

先继承后实现:

class MessageImpl extends Database implements IMessage

全部由抽象方法和全局变量所组成。

接口中都是抽象方法吗?

java8以前,接口中可以包含的方法:

1.常量
2.抽象方法

java 8中,接口中可以包含的内容有:

1.常量
2.抽象方法
3.默认方法
4.静态方法

java9中,接口可以包含的内容有:

1.常量
2.抽象方法
3.默认方法
4.静态方法
5.私有方法

抽象类与接口的共同点和区别?

共同点:
(1)都不能被实例化
(2)都可以包含抽象方法
(3)都可以有默认实现方法(java8可以用default在接口中定义默认方法)
区别:
(1)接口主要用于对类的行为进行约束,实现了某个接口就具有了对应的行为;抽象类是代码复用,强调的是所属关系。
(2)一个类只能继承一个类,但是可以实现多个接口
(3)接口中的成员变量只能是 public static final类型的,不能被修改且必须有初始值,而抽象类的成员变量默认default,可在子类中被重新定义,也可以被重新赋值。

22、深拷贝和浅拷贝的区别是什么?什么是引用拷贝?

浅拷贝: 在拷贝一个对象时,对对象的基本数据类型的成员变量进行拷贝,但是对引用类型的成员变量只进行引用的传递,当对引用类型的内容修改会影响被拷贝的对象。简而言之,浅拷贝只复制所拷贝的对象,而不复制它引用的对象。
深拷贝: 在拷贝一个对象时,对对象的基本数据类型的成员变量进行拷贝,对引用类型的成员变量进行拷贝时,创建了一个新的对象来保存引用类型的成员变量。简而言之:深拷贝把要复制的对象所引用的对象都复制了一遍。
引用拷贝: 引用拷贝就是两个不同的引用指向同一个对象。
在这里插入图片描述

23、面向对象和面向过程的区别?

面向对象会先抽象出对象,然后用对象执行方法的形式去解决问题。
面向过程把解决问题的过程拆成一个个方法,通过一个个方法的执行解决问题。比如C语言。
面向对象开发的程序一般更易于维护、易复用、易扩展。

24、成员变量和局部变量有什么区别?

从以下四个方面来看:
语法形式:
从语法形式上来看,成员变量是属于类的,局部变量是在代码块或方法中定义的。
成员变量可以被public,private,static等修饰符修饰,而局部变量不能被访问控制符及static修饰。但是成员变量和局部变量都能被final所修饰。
存储方式:
从变量在内存中的存储方法来看,如果成员变量是使用static修饰的,那么这个成员变量是属于类的,如果没有使用static修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量存在于栈内存。
生存时间:
从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动生成,随着方法的调用结束而消亡。
默认值:
从变量是否有默认值来看,成员变量如果没有被赋初始值,则会自动以类型的默认值而赋值(一种情况例外,final修饰的成员变量必须显显式的赋值),而局部变量则不会自动赋值。

25、 一个类的构造方法的作用是什么?

构造方法是一种特殊的方法,只要作用是完成对象的初始化工作。

如果一个类没有声明构造方法,该程序能正常的进行吗?

可以。因为一个类就算没有声明构造方法,也有默认的无参构造方法存在。如果我们自己添加了构造方法(无论是否有参数),这时候就不会再添加默认的无参构造方法了。这时候new一个对象时,就要根据自己写的构造方法进行参数传递了(无参构造时new一个对象不传递参数)。如果我们重载了有参的构造方法,记得把无参构造方法也写一遍,防止后面创建对象时踩坑。

构造方法有哪些特点?是否可被override?

特点:
名字与类型相同。
没有返回值,但不能用void声明构造函数。
生成类的对象自动执行,无序调用。

构造方法不能被override(重写),但它可以被overload(重载)。

26、String、StringBuffer、StringBuilder的区别?

从以下三个方面来讲:
可变性:
String是不可变类。
StringBuilder和StringBuffer是可变长类。
StringBuilder 与 StringBuffer都继承自AbstractStringBuilder 类,在AbstractStringBuilder 中也是使用字符数组保存字符串,不过没有使用final和private关键字修饰,它们是可变长的。最关键是AbstractStringBuilder 类还提供了很多修改字符串的方法,比如append()方法。
线程安全性:
String中的对象是不可变的,也就可以理解为常量,线程安全。
StringBuffer线程安全,StringBuilder线程不安全,因此使用StringBuilder比StringBuffer快。
AbstractStringBuilder是StringBuilder和StringBuffer的公共父类,定义了一些字符串的基本操作。StringBuffer对方法加了同步锁或者对调用的方法加了同步锁,所以线程是安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全的。
性能:
每次对String类型进行改变时,都会生成一个新的String对象,然后将指针指向新的String对象。
StringBuffer每次都会对StringBuffer对象本身进行操作,而不是生成新的对象并改变对象的引用。
相同情况下使用StringBuilder相比使用StringBuffer能获得10%~15%左右的性能提升,但是却要冒多线程不安全的风险。
更改字符串
使用String时,对字符串做任何更改操作都会创建一个新的对象。
使用StringBuilder和StringBuffer时,因为它们是可变长类,所以做更改操作不会创建新的对象,会在原来的字符串上进行更改。

对于三者的使用:
(1)操作少量的数据:String
(2)单线程操作字符串缓存去下操作大量数据:StringBuilder
(3)多线程操作字符串缓存区下操作大量数据:StringBuffer.

String为什么是不可变的?

(1)保存字符串的数组被final修饰且为私有,并且String类没有提供、暴露修改这个字符的方法。
(2)String类被final修饰导致其不能被继承,进而避免了子类破坏String不可变。

什么是不可变:
给一个已有字符串“abcd”第二次赋值为“abcdef”,它不是在原内存地址上修改数据,而是重新指向一个新对象。

String不可变的原因不能简单的归因为它保存字符串的数组被final修饰,它的关键在于 它的底层实现。
我们都知道被final修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型不能改变,修饰的变量是引用类型则不能再指向其他对象,指的是引用地址不可变,但是这个数组保存的字符串是可变的。

扩容机制

字符串拼接是用“+”还是StringBuilder?

java语言本身不支持运算符重载,“+”和“+=”是专门为String类重载过的运算符,也是java中仅有的两个重载过的元素符。
对象引用和“+”的字符串拼接方式,实际上是通过StringBuilder 调用append()方法实现的,拼接完成之后调用toString()得到一个String对象。
不过,在循环中调用“+”进行字符串的拼接的话,存在比较明显的缺陷:编译器不会创建单个StringBuilder来复用,会导致创建过多的StringBuilder对象。
如果直接使用StringBuilder对象进行字符串拼接的话,就不会存在这个问题。

String中equals方法和Object中equals方法有什么区别?

String中equals方法是重写后的,比较的是字符串的值是否相等。
Object的equals方法比较的是对象的内存地址。

final修饰StringBuffer后还可以append吗?

可以。
final修饰的是一个引用变量,那么这个引用始终只能指向这个对象,但是这个对象内部的属性是可以变化的。

27、字符串常量池有什么作用?

字符串常量池是jvm为了提升性能和减少内存消耗针对字符串(String类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
两种实例化方式:
1、直接赋值:

String a="abc";//放在常量池中
string b="abc";//从常量池中查找
System.out.println(a==b); //true

2.构造方法

String s=new String("abc");
String t=new String("abc");
System.out.print(s==t);//false

s和t属于两个不同的对象,所以不相等。

String s=new String(“abc”)创建了几个字符串对象

会创建一个或者两个字符串。
1.如果字符串常量池中已存在字符串常量“abc”,则只会在堆空间创建一个字符串常量"abc"
2.如果字符串常量池中没有字符串常量“abc”,那么它会首先在字符串常量池中创建,然后在堆空间中创建,因此将创建2个对象。

String c1=new String("adc");//堆内存的地址值
String c2="abc";//放在常量池
System.out.println(s1==s2);//false,一个是堆内存,一个是常量池内存
System.out.println(s1.equals(s2));//true

String 类型的变量和常量做"+"运算时发生了什么?

String s1="abc";
String s2="de";
String s3="abc"+"de";//常量池中的对象
String s4=s1+s2;//在堆上创建新的对象
String s5="abcde";//常量池中的对象
System.out.println(s3 == s4);//false
System.out.println(s3 == s5);//true
System.out.println(s4 == s5);//false

28、int和Integer的区别?

(1)int是基本数据类型,默认值为0,代表整型数据
(2)Integer是int的包装类,是引用数据类型,默认值是null.
(3)int和Integer都可以表示某个整型类型
(4)Integer实际上是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象,而int是直接存储数据值。
(4)Integer可以区分出未赋值和0的区别,而int无法表示出未赋值的情况。
(5)如果只是进行一些加减乘除的操作,可以使用int类型;但是如果要想按照对象来进行操作处理,就要使用Integer来声明。
(6)int在初始化时,可以直接写成 int a=1;Integer 可以写成 Integer a=new Integer(1)的形式,但是因为java中的自动装箱和自动拆箱机制,所以也可以写成Integer a=1的形式;

29、final、finally、finalize的区别?

final:用于声明属性、方法和类,分别表示属性不可变、方法不可覆盖、被其修饰的类不可继承;
finally:异常处理语句结构的一部分,表示总是执行;
finalize:Object类的一个方法,在垃圾回收时会调用被回收对象的finalize

30、&和&&的区别?

Java中&和&&都是表示与的逻辑运算符,都表示逻辑运输符and,当两边的表达式都为true时,整个运算结果才为true,否则为false.
&& :有短路功能,当一个表达式的值为false时,则不会再计算第二个表达式的值。
&:不管第一个表达式结果是否为true,第二个都会执行。除此之外,&还可以用作位运算符,当&两边的表达式不是Boolean类型的时候,&表示按位操作。

31、switch语句能否作用在byte上,能否作用在long上,能否作用在String上?

在switch(expr 1)中,expr1只能是一个整数表达式或者枚举常量。而整数表达式可以是int基本数据类型或者Integer包装类型。由于byte,short,char可以隐式的转换为int,所以,这些类型以及这些类型的包装类型也是可以的。而long和String类型都不符合switch的语法规定,并且不能被隐式的转换成int类型,所以,它们不能作用于switch语句中。不过。需要注意的是在jdk1.7之后,switch就可以作用在String上了。

32、Object常用方法有哪些?

clone方法:用于创建并返回当前对象的一份拷贝;
getClass方法:用户返回当前运行时对象的Class;
toString方法:返回对象的字符串表示形式;
finalize方法:实例被垃圾回收器回收时触发的方法;
equals方法:用于比较两个对象的内存地址是否相等,一般需要重写;
hashCode方法:用于返回对象的哈希值;
notify方法:唤醒一个在此对象监视器上等待的线程。如果有多个线程在等待只会唤醒一个;
notifyAll方法:作用跟notify()一样,只不过会唤醒此对象监视器上等待的所有线程,而不是一个线程;
wait方法:让当前对象等待;

33、String类的常用方法有哪些?

indexOf():返回指定字符的索引;
charAt():返回指定索引出的字符串;
replace():字符串替换;
trim():去除字符串两端的空白;
split():分割字符串,返回一个分割后的字符串数组;
getBytes():返回字符串的byte类型数组;
Length():返回字符串长度;
toLowerCase():将字符串转换成小写字母;
toUpperCase():将字符串转成大写字母;
subString():截取字符串;
equals():字符串比较;

34、JAVA创建对象的几种方式?

1.使用new关键字
2.使用Class类中的newInstance方法,它是通过调用无参构造函数来创建对象(反射)
3.使用clone()方法
4.反序列化,使用ObjectInputStream中的ReadObject()方法。

35、什么是failfast和failsafe?

failfast:快速失败机制
在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行修改了,则会抛出Concurrent Modification Exceptiony.
原理:
迭代器在遍历集合中的元素时,会在遍历过程中使用一个modCount变量,集合在遍历期间如果内容发生变化,就会改变ModCount的值。每当迭代器使用next()遍历下一个元素前,都会检查modCount变量是否为expectedmodCount值,不一样的话就会抛出异常。
场景:
java.util包下的集合类都是快速失败的,不能再迭代过程中被修改(并发修改)
fail-safe:安全失败机制
遍历时不是直接在集合内容上访问的,而是先复制原有的集合内容,在拷贝的集合上进行遍历
原理:
迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合的修改不会被迭代器检测到,所以不会触发异常。
场景:
java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。

36、什么是copyOnWrite?

写时复制。
它的基本思想是:在并发情况下,对于共享数据,当有线程想要修改这个数据时,它会先把这个数据拷贝下来,然后对这个数据的拷贝进行修改,修改完后,再将原数据的引用指向新的数据。
这样就可以实现对这个数据并发的读,而不需要加锁。
JUC包下对它的实现有copyOnWriteArrayList和copyOnWriteArraySet.

37、什么是AQS和CAS?

AQS:

是一个抽象类,主要用来构建锁和同步器。
AbstractQueuedSynchronizer.它在java.util.concurrent.locks包下。
是除了java自带的synchronized关键字之外的锁机制。

基本思想:
如果被请求的共享资源是空闲的,那么就将这个请求资源的线程设置为有效的工作线程,并将这个资源锁定;
如果被请求的共享资源不是空闲的,就需要一套线程堵塞等待和唤醒时锁分配机制,这个是用CLH来实现的,就是将暂时获取不到锁的线程加入到一个队列中。
CLH是一个虚拟的双向列表,也就是说不存在队列实例,只存在节点之间的关联关系。
AQS就是基于CLH队列,用volatile修饰共享变量,线程通过CAS去改变状态符,成功则获取锁,不成功则加入等待队列,等待被唤醒。
AQS底层使用了模板方法模式。

CAS

它是一种无锁原子算法,在不使用锁的情况下来实现多线程之间的变量同步。

Compare and Swap
java.util.concurrent包下的原子类就是使用CAS实现乐观锁。
实现思想:
它含有三个参数:V,E,N ,V表示待更新的内存值,E表示预期值,N表示新值。
当V=E时,才将V更新为N;
V != E时,操作失败或重新再来
重新再来可以使用自旋(不断重试CSA操作),可以通过配置自旋次数来防止死循环。

缺点:
(1)只能保证一个共享变量的原子操作
(2)在并发量大的情况下,CAS将消耗大量的CPU资源
(3)ABA问题
CAS会检查待修改的值与期望的值一样不一样,会有这样一种情况:就是这个值原本是A,被修改为B,又被修改为A,这样它在检测时是检测不出来它被修改过。
如果是简单的数据结构,不会有什么问题,但是如果是复杂的数据结构就可能产生问题。
比如:
1、线程1欲删除A节点,B节点成为头节点,正要执行CAS(A,A,B)时,时间片用完,切换到线程2
2、线程2删除A、B节点
3、线程2加入C、A节点
4、线程1重现获取时间片,执行CAS(A,A,B)
5、丢失C节点
解决这个问题的方法:追加版本号,每次改变version+1

38、泛型

什么是泛型?

泛型,就是我们在定义一个类、接口、方法时可以指定类型参数。

泛型有什么作用、好处?

1.在编译时类型检查:当我们使用泛型的时候,向容器中加入非特定类型对象在编译阶段就会报错。假如不使用泛型,我们可以在容器中存入任意类型,容易出现类型转换异常。
2.不需要进行强制类型转换:使用泛型后容器就可以记住存入容器中的对象的类型
3.提高了代码的可读性:使用泛型后开发人员一看就知道容器中存放的是哪种类型的对象。

什么是泛型擦除?

是指Java中的泛型只在编译器有效,在运行期会被删除,也就是说所有的泛型参数在编译器后都会被清除。
Java的泛型是在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全的强制转型。
在编译器编译后,泛型的转换规则如下:

List、List 擦除后的类型为 List;
List[]、List[] 擦除后的类型为 List[];
List<? extends E>、List<? super E> 擦除后的类型为 List;
List<T extends Serialzable & Cloneable> 擦除后类型为 List

正因为泛型擦拭,所以
泛型类的class对象相同;
不能对泛型数组进行初始化
instanceof不允许存在泛型参数

泛型擦拭存在的一些问题?

1.强制类型转换问题
如果使用List< Integer >,可以使用反射的方式插入一个String类型的数据。
在这里插入图片描述
2.引用传递问题
T在后期会被转换成Object类型,那么如果对引用再进行转换:

List<Object> list=new ArrayList<String>();

按String类型存入,后期String类型会被转换成Object类型,那么String类型就没有意义了。

解决方式:
使用< T extends Parent>
这样虚拟机就会将数据转化为Parent类型,而不是Object类型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值