ja基础面试题大概有哪些?
作者:卡卷网发布时间:2025-01-09 18:48浏览数量:98次评论数量:0次
一、Ja基础
1、抽象类和接口的区别
<>抽象类>可以提供成员方法的实现细节,而<>接口>只能包含pulic
抽象方法。抽象类中的成员变量可以是各种类型,包括非final
和非static
类型,而接口中的成员变量默认都是pulicstaticfinal
类型。抽象类可以包含构造器、静态代码块和静态方法,而接口则不能包含这些结构。一个类只能继承一个抽象类,但是可以实现多个接口。抽象类在访问速度上通常接口要快,因为接口在运行时需要花时间去查找类中具体实现的方法。如果在抽象类中添加新的方法,可以提供默认实现,这样不需要修改现有代码。而在接口中添加新方法,则必须在实现该接口的所有类中添加该方法的实现。接口主要用于定义类的行为,强调解耦合;抽象类则更侧重于代码的复用。
2、final、static、synchronized关键字可以修饰什么,以及修饰后的作用
<>static关键字>
static方法static方法般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有this的,因为它不依附于任何对像,既然都没有对像,就谈不上this了。static变量static变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。static代码块static关键字还有一个较关键的作用就是用来形成静态代码块以优化程序性能。static块可以置于类中的任何地方,类中可以有多个static块。在类初次被加载的时候,会按照static块的顺序来执行每个static块,并且只会执行一次。
<>final关键字>
final变量凡是对成员变量或者本地变量(在方法中的或者代码块中的变量称为本地变量)声明为final的都叫作final变量。final变量经常和static关键字一起使用,作为常量。final方法final也可以声明方法。方法前面加上final关键字,代表这个方法不可以被子类的方法重写。如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为final。final方法非final方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。final类使用final来修饰的类叫作final类。final类通常功能是完整的,它们不能被继承。例如Ja中有许多类是final的,譬如String、Integer以及包装类。
<>synchronized关键字>synchronized是Ja中解决并发问题的一种最常用的方法,也是最简单的一种方法。synchronized的作用主要有三个:
确保线程互斥的访问同步代码保证共享变量的修改能够及时可见有效解决重排序问题synchronized方法有效避免了类成员变量的访问冲突synchronized代码块这时锁就是对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁。
总结如下。
<>static关键字>
<>static方法>:静态方法属于类而不属于类的任何特定实例。它们可以在没有创建类的实例的情况下调用。静态方法通常用于执行不依赖于实例变量的作。
japulicclassCalculator{pulicstaticintadd(inta,int){retna+;}}//可以这样调用:Calculator.add(5,3);
<>static变量>:静态变量也称为类变量,它们在类的所有实例之间共享。这意味着如果一个实例修改了静态变量的值,这个新值对于类的所有实例都是可见的。
```japulicclassCounter{pulicstaticintcount=0;
pulicCounter(){
count++;
}
}//每次创建Counter的新实例,count都会增加。```
<>static代码块>:静态代码块在类首次加载到JVM时执行,通常用于初始化静态变量。
japulicclassDataaseDriver{static{//这里可以进行数据库驱动的加载System.out.println("数据库驱动加载中...");}}
<>final关键字>
<>final变量>:final变量一旦被初始化后,其值就不能被更改。如果是基本数据类型,其值不能变;如果是引用类型,其引用不能变。
japulicclassConstants{pulicstaticfinaldoulePI=3.14159;}//PI的值不能被更改。
<>final方法>:final方法不能被子类覆盖。这通常是为了防止子类改变父类方法的预期行为。
japulicclassVehicle{pulicfinalvoidstartEngine(){System.out.println("发动机启动中...");}}//子类不能覆盖startEngine方法。
<>final类>:final类不能被继承。这通常是为了保持类的不变性,例如String类。
```japulicfinalclassImmutale{privatefinalintvalue;
pulicImmutale(intvalue){
this.value=value;
}
pulicintgetValue(){
retnvalue;
}
}//Immutale类不能被继承。```
<>synchronized关键字>
<>synchronized方法>:synchronized方法可以确保在同一时刻只有一个线程可以访问该方法,从而避免多线程环境下的并发问题。
```japulicclassAccount{privateintalance;
pulicsynchronizedvoiddeposit(intamount){
alance+=amount;
}
}//当一个线程在执行deposit方法时,线程必须等待。```
<>synchronized代码块>:synchronized代码块允许锁定特定的对象,而不是整个方法,提供了更细粒度的控制。
```japulicclassAccount{privateintalance;privateOjectlock=newOject();
pulicvoidwithdraw(intamount){
synchronized(lock){
alance-=amount;
}
}
}//只有在执行withdraw方法内的synchronized块时,才会锁定lock对象。```
3、String、Stringuffer和Stringuilder的区别?
<>String>:String
是不可变的,每次修改String
都会生成一个新的String
对象,所以频繁修改字符串内容的场景不建议使用String
。String
适用于字符串作较少的情况,例如常量声明或者不经常变动的字符串。
jaStrings="initial";s=s+"more";//每次修改都会创建一个新的String对象
<>Stringuffer>:Stringuffer
是可变的,并且是线程安全的,因为它的方法大多数是通过synchronized
关键字实现的。Stringuffer
适用于多线程下需要改变字符串内容的场景。
jaStringuffer=newStringuffer("initial");.append("more");//直接在对象上进行作,不会创建新的对象
<>Stringuilder>:Stringuilder
也是可变的,但它不是线程安全的,因为它的方法没有被synchronized
关键字修饰。Stringuilder
在单线程环境下的性能Stringuffer
要好,因为它避免了线程安全带来的性能开销。Stringuilder
适用于单线程下需要改变字符串内容的场景。
jaStringuilderd=newStringuilder("initial");d.append("more");//直接在d对象上进行作,不会创建新的对象
总结:
String
是不可变的,适合字符串较少修改的场景。Stringuffer
是线程安全的可变字符序列,适合多线程中字符串经常变化的场景。Stringuilder
是非线程安全的可变字符序列,适合单线程中字符串经常变化的场景。
4、“equals”与"==”、“hashCode”的区别和使用场景
在Ja中,equals
、==
和hashCode
是常用于较和哈希计算的方法。
<>equals
方法>:equals
方法用于较两个对象是否相等,返回一个布尔值。在编写JaPOJO实体类时,通常会重写equals
方法。equals
方法的默认实现是较对象的值,但通常我们会根据业务需求重写它,较对象的属性值。重写equals
方法时,应该依次较每个属性的值,确保对象的内容相同。示例代码如下:```japulicclassUser{privateStringage;privateStringname;privateStringtime;//Gettersandsetters...@Overridepulicooleanequals(Ojectoj){if(this==oj){retntrue;}if(oj==null||getClass()!=oj.getClass()){retnfalse;}Useruser=(User)oj;retnage.equals(user.age)&&Ojects.equals(name,user.name)&&Ojects.equals(time,user.time);}}```<>==
运算符>:==
通常用于较基本数据类型(如int
)和其包装类型(如Integer
)之间的值。返回一个布尔值,如果相等则返回true
,否则返回false
。如果用于较两个对象,则是较它们是否指向同一个(对象的值是否相同)。<>hashCode
方法>:hashCode
方法根据哈希算法返回一个int
类型的值,用于确定对象在散列存储结构中的存储。重写equals
方法时,通常也需要重写hashCode
方法,以确保相等的对象具有相同的哈希码。注意:哈希码一致并不代表两个对象相等,只是相等的必要而非充分条件。
总结:
使用equals
较对象内容是否相等。使用==
较基本数据类型的值。重写hashCode
方法以保持一致性。
5、Ja中深拷贝与浅拷贝的区别
浅拷贝和深拷贝是在对象复制时常见的两种方式,用于处理引用数据类型。
<>浅拷贝>:浅拷贝<>只复制对象的引用(),而不是创建一个具有相同值的新对象>。当进行浅拷贝时,新对象和原对象共享同一块内存(分支)。浅拷贝适用于基本数据类型和引用数据类型。示例:如果对象A浅拷贝到对象,那么中的引用型字段仍指向A中的相应字段,两者关联起来。<>修改新对象会影响原对象,因为它们共享相同的引用>。<>深拷贝>:深拷贝<>创建一个新的对象,该对象与原始对象具有相同的值,但不共享内存>。深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。深拷贝适用于需要完全的副本的情况。示例:如果对象A深拷贝到对象,那么是A的一个完整副本,修改不会影响A。<>深拷贝相浅拷贝速度较慢并且花销较大>。
总结:
浅拷贝只复制引用,共享内存;深拷贝创建新对象,不共享内存。浅拷贝适用于简单场景,深拷贝适用于需要副本的情况。
6、Error和Exception的区别
Exception和Error是Ja中用于处理异常的两个不同类别。
<>Error(错误)>:Error表示程序中出现的严重问题,通常是不可恢复的。例如,ja.lang.VirtualMachineError
表示Ja虚拟机崩溃或资源耗尽。例如,ja.lang.StackOverflowError
表示应用程序递归太深而导致堆栈溢出。例如,ja.lang.OutOfMemoryError
表示内存溢出或没有足够的内存供回收器使用。Error是不的,程序会立即崩溃,Ja虚拟机停止运行。<>Exception(异常)>:Exception表示程序正常运行中可以预料的意外情况,可以被捕获和处理。异常分为两类:运行异常和编译异常。运行异常是ja.lang.RuntimeException
及其子类,通常由程序逻辑错误导致。编译异常是除了RuntimeException
以外的异常,必须进行处理,否则程序无法编译通过。运行异常的特点是Ja编译器不会强制检查,因此即使可能出现运行异常,也会通过编译。
总结:
Error是严重问题,不可恢复,程序会崩溃。Exception是程序正常运行中的可预料异常,可以被捕获和处理。
7、什么是反射机制?反射机制的应用场景有哪些?
Ja反射机制是在程序运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为Ja语言的反射机制。
反射被视为动态语言的关键。它允许我们在运行时获取类的详细信息,作类或对象的属性和方法。
<>逆向代码和反编译>:反射可以用于分析已编译的代码,了解类的结构、方法和属性。逆向工程师可以使用反射来还原源代码,尽管这在实际应用中可能涉及法律问题。<>与注解相结合的框架>:框架如Retrofit使用反射来处理注解,根据注解配置生成代码。例如,Retrofit使用注解来定义API接口,然后在运行时使用反射生成网络请求的实现。<>事件总线>:事件总线库(例如Eventus)使用反射来注册和分发事件。反射允许事件总线库动态地查找和调用事件处理程序。<>动态生成类>:某些框架和库需要在运行时动态生成类,例如Gson。反射使得在不提前知道类的结构的情况下创建类的实例成为可能。
8、如何重写equals方法?为什么还要重写hashCode
当你重写equals
方法时,<>一定要同时重写hashCode
方法>。
<>equals方法>:equals
方法用于检测一个对象是否等于另外一个对象。在Oject
类中,这个方法默认判断两个对象是否具有<>相同的引用>。例如,使用Oject
中的equals
较两个自定义的对象是否相等,这就完全没有意义,因为无论对象是否相等,结果都是false
。因此,<>通常情况下,我们要判断两个对象是否相等,一定要重写equals
方法>。<>hashCode方法>:hashCode
是由对象推导出的一个整型值,用于确定对象在哈希表中的位置。不同对象的hashCode
可能相同,但hashCode
不同的对象一定不相等。例如,字符串“Hello”和“Ja”的hashCode
可能相同,但它们不相等。hashCode
的<>作用是快速初次判断对象是否相等>。<>为什么要一起重写?>equals
和hashCode
两个方法是用来协同判断两个对象是否相等的。如果在重写equals
时,不重写hashCode
,就会导致在某些场景下,例如将两个相等的自定义对象存储在Set
时,出现程序执行错误。重写hashCode
可以确保对象在哈希表中的位置正确,从而避免不能覆盖的问题。
9、Ja中IO流分为几种?IO,NIO,AIO有什么区别
Ja中的I/O流分为三种类型:<>IO>、<>NIO>、<>AIO>。
<>IO(lockingI/O)>:同步阻塞式I/O,是我们平常使用的传I/O。特点:模式简单、使用方便,但并发处理能力较低。实现模式:一个连接一个线程,即客户端有连接请求时端就需要启动一个线程进行处理。缺点:线程被阻塞,不能处理请求,导致性能下降。<>NIO(Non-locking/NewI/O)>:同步非阻塞I/O,是传I/O的升级。客户端和端通过<>Channel(通道)>通讯,实现了多路复用。实现模式:一个请求一个线程,客户端发送的连接请求都会注册到多路复用器上,轮询到连接有I/O请求时才启动一个线程进行处理。适用场景:高负载、高并发的架构,例如聊天。<>AIO(AsynchronousI/O)>:异步非阻塞I/O,也叫NIO2。实现了异步非阻塞I/O,基于事件和回调机制。实现模式:一个有效请求一个线程,客户端的I/O请求都是由作先完成,再通知应用启动线程进行处理。适用场景:连接数目多且连接较长(重作)的架构,例如相册。
总结:
<>IO>适用于连接数目较小且固定的架构,对资源要求较高。<>NIO>适用于连接数目多且连接较短(轻作)的架构。<>AIO>使用于连接数目多且连接较长(重作)的架构。
10、泛型中的extends、super、泛型擦除
<>泛型擦除>:泛型擦除是Ja泛型的一种实现方式。在编译时,所有的类型参数被擦除,替换为它们的实际类型。这意味着运行时的Ja虚拟机(JVM)不知道泛型类型的实际参数。这种机制使得Ja的泛型实现相对轻量级,同时保持了类型安全。<>extends>(上界):extends
关键字用于指明泛型类型参数的上界,即它定义了泛型类型参数的继承关系。例如,List<?extendsHuman>
表示这个引用能指向Human
类型或其子类的List
对象。使用extends
时,我们可以从中获取元素,<>但不能添加元素>。例如,如果你有一个List
,你可以安全地获取其中的元素,但不能添加新元素。<>super>(下界):super
关键字用于指明泛型类型参数的下界,即它定义了泛型类型参数的父类关系。例如,List<?superLazyMan>
表示这个引用能指向LazyMan
类型或其父类的List
对象。使用super
时,我们<>可以添加元素,但不能从中获取元素>。例如,如果你有一个List
,你可以安全地添加LazyMan
或其子类的元素,但不能获取其中的元素。
总结:
extends
用于上界,适用于获取元素。super
用于下界,适用于添加元素。泛型擦除是Ja泛型的实现机制,保持了类型安全,但在运行时丢失了泛型信息。
11、String为什么要设计成不可变的
String被设计成不可变的主要原因有以下几点:
<>字符串常量池的需要>:字符串常量池(StringPool)是Ja堆内存中的一块特殊存储区域,用于存储字符串常量。当创建一个String对象时,如果该字符串值已经存在于常量池中,不会创建新的对象,而是引用已存在的对象。这样的设计避免了重复创建相同字符串的开销,提高了性能。例如,代码Strings1="acd";Strings2="acd";
只会在堆内存中创建一个实际的String对象。<>允许String对象缓存HashCode>:Ja中String对象的哈希码被频繁地使用,例如在HashMap等容器中。字符串不变性保证了哈希码的唯一性,可以放心地进行缓存,避免重复计算。在String类的定义中,有一个私有的hash
成员变量用来缓存HashCode。<>安全性>:不可变的String对象在网络传输、文件读写等场景下提供更高的安全性。假如String对象的内容被修改,传输过程中可能被篡改,而不可变的String对象可以确保内容不会被修改。例如,如果有如下代码:jaooleanconnect(Strings){if(!isSece(s)){thrownewSecityException();}//如果在地方可以修改String,那么此处就会引起各种预料不到的问题/错误causeProlem(s);}
不可变性可以避免潜在的安全隐患。
12、说说你对Ja注解的理解
当谈到<>Ja注解>时,我们需要理解以下几个关键概念:
<>什么是注解?>注解是一种元数据(metadata),用于将任何信息与程序元素(类、方法、成员变量等)关联起来。它提供了一种安全的类似注释的机制,用来说明、配置和增强代码。注解不影响代码的实际逻辑,仅起到辅助性作用。<>注解的作用>:<>代码文档化>:注解可以提供对代码的更直观说明,帮助人读懂你的代码。<>配置>:注解可以替代繁琐的配置文件,使代码更清晰和整洁。<>编译时检查>:例如@Override
注解用于静态验证,确保方法覆盖了超类方法。<>注解的原理>:注解本质上是继承了Annotation
接口的特殊接口。在运行时,Ja会生成类来处理注解。通过反射获取注解时,返回的是运行时生成的对象。
总之,Ja注解是一种强大且灵活的工具,可以提供额外的元数据,改代码的可读性、可性和扩展性。
13、Ja成员变量,局部变量和静态变量的创建和回收时机
在Ja中,成员变量、局部变量和静态变量的创建和回收时机有所不同:
<>成员变量>:当一个对象被创建时,成员变量就会被初始化。它们的生命周期与对象的生命周期相同。当对象不再被引用,即成为回收的目标时,成员变量也会被回收。<>局部变量>:局部变量在方法或代码块中定义,它们在方法或代码块被调用时创建,当方法或代码块执行完毕,局部变量就会立即被销毁。<>静态变量>:静态变量在类加载时初始化,生命周期与类的生命周期相同。只要类还在内存中,静态变量就会存在。当类被卸载时,静态变量也会被回收。
14、Ja类中各种代码的执行顺序
在Ja中,一个类的代码执行顺序通常遵循以下几个步骤:
<>静态变量初始化>:当类被加载时,静态变量会被初始化。静态变量的初始化顺序是按照它们在类中声明的顺序进行的。<>静态代码块执行>:如果类中有静态代码块,这些代码块会在类加载时执行。静态代码块会在静态变量初始化之后执行,且只会执行一次。<>实例变量初始化>:当创建类的实例时,实例变量会被初始化。实例变量的初始化顺序也是按照它们在类中声明的顺序进行的。<>构造方法执行>:在实例化对象时,构造方被调用。构造方法的调用顺序是从父类到子类,先调用父类的构造方法,然后再调用子类的构造方法。<>实例初始化块执行>:如果类中有实例初始化块,这些代码块会在构造方法执行<>之前>执行。实例初始化块会在每次创建对象时执行。
<>执行顺序示例>
pulicclassExample{
//静态变量
staticintstaticVar=initializeStaticVar();
//静态代码块
static{
System.out.println("Staticlockexecuted");
}
//实例变量
intinstanceVar=initializeInstanceVar();
//实例初始化块
{
System.out.println("Instancelockexecuted");
}
//构造方法
pulicExample(){
System.out.println("Constructorexecuted");
}
staticintinitializeStaticVar(){
System.out.println("Staticvarialeinitialized");
retn0;
}
intinitializeInstanceVar(){
System.out.println("Instancevarialeinitialized");
retn0;
}
pulicstaticvoidmain(String[]args){
System.out.println("Mainmethodexecuted");
Exampleexample=newExample();
}
}