【面试题整理】

抽象类与接口的区别?

抽象类(Abstract Class)和接口(Interface)是Java中用于实现多态和封装的两种机制,它们有以下区别:

定义和实现:抽象类是一个类,可以包含成员变量、构造方法、普通方法和抽象方法,可以有部分方法的实现。接口是一种纯粹的规范,只能包含常量和抽象方法,所有方法都没有实现。

继承关系:一个类只能继承一个抽象类,通过使用extends关键字来实现继承。一个类可以实现多个接口,通过使用implements关键字来实现接口。这意味着接口支持多重继承,而抽象类不支持。

构造方法:抽象类可以有构造方法,接口不能有构造方法。由于接口没有实例化的概念,因此不需要构造方法。

默认实现:抽象类可以有普通方法的实现,子类可以直接继承并使用这些实现。接口中的所有方法都是抽象的,没有默认的实现。从Java 8开始,接口可以有默认方法(default method)和静态方法(static method),提供了在接口中实现方法的能力。

设计目的:抽象类用于表示一种抽象的、有共同特征的类,可以作为其他类的基类。接口用于定义一种规范,描述了一组相关的操作或功能,可以被类实现。

根据使用的场景和需求,选择使用抽象类还是接口有一些原则。抽象类适合用于表示一种层次结构中的通用概念,提供默认的实现,并通过继承来共享代码。接口适合用于定义一组规范和约定,并由多个类来实现,实现类可以同时实现多个接口,实现更灵活的多态性。

分别讲讲 final、static 和 synchronized 可以修饰什么,以及修饰后的作用?

static
static 方法
static 方法一般称作静态方法,由于静态方法不依赖于任何对象就可以进行访问,因此对于静态方法来说,是没有 this 的,因为它不依附于任何对象,既然都没有对象,就谈不上 this 了。
public class StaticTest {
public static void a(){
}
public static void main(String[]args){
StaticTest.a();
}
}
static 变量
static 变量也称作静态变量,静态变量和非静态变量的区别是:静态变量被所有的对象所共享,在内存中只有一个副本,它当且仅当在类初次加载时会被初始化。而非静态变量是对象所拥有的,在创建对象的时候被初始化,存在多个副本,各个对象拥有的副本互不影响。
static 代码块
static 关键字还有一个比较关键的作用就是 用来形成静态代码块以优化程序性能。static 块可以置于类中的任何地方,类中可以有多个 static 块。在类初次被加载的时候,会按照 static 块的顺序来执行每个 static 块,并且只会执行一次。
public class StaticTest {
private static int a ;
private static int b;
static {
a = 1;
b = 2;
}
final
final 变量
凡是对成员变量或者本地变量(在方法中的或者代码块中的变量称为本地变量)声明为 final 的都叫作 final 变量。final 变量经常和 static 关键字一起使用,作为常量。
private final int aa = 1;
static {
a = 1;
b = 2;
}
private void init(){
aa = 2;//报错编译器会提示 不能赋值。。
}
final 方法
final 也可以声明方法。方法前面加上 final 关键字,代表这个方法不可以被子类的方法重写。如果你认为一个方法的功能已经足够完整了,子类中不需要改变的话,你可以声明此方法为 final。final 方法比非 final 方法要快,因为在编译的时候已经静态绑定了,不需要在运行时再动态绑定。
public static void main(String[]args){
StaticTest.a();
}
class StaticTest2 extends StaticTest{
public final void a(){ //这边就会编译器提示不能重写
}
}
**final 类 **
其实更上面同个道理,使用 final 来修饰的类叫作 final 类。final 类通常功能是完整的,它们不能被继承。Java 中有许多类是 final 的,譬如 String,Interger 以及其他包装类。
synchronized
synchronized 是 Java 中解决并发问题的一种最常用的方法,也是最简单的一种方法。synchronized 的作用主要有三个:

确保线程互斥的访问同步代码
保证共享变量的修改能够及时可见
有效解决重排序问题。
synchronized 方法
有效避免了类成员变量的访问冲突:
private synchronized void init(){
aa = 2;
}
synchronized 代码块
这时锁就是对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的 instance 变量(它得是一个对象)来充当锁。
public final void a(){
synchronized (lock){
//代码
}
}
@Override
public void run() {
}

Java 中深拷贝与浅拷贝的区别?

在Java中,深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是对象复制的两种不同方式。

浅拷贝是指创建一个新对象,然后将原始对象的字段值复制给新对象的字段。这意味着新对象和原始对象将共享相同的引用类型字段,而不是复制它们的内容。因此,对于引用类型字段的修改会影响到原始对象和新对象。浅拷贝可以通过clone()方法或复制构造函数来实现。

深拷贝是指创建一个新对象,并递归地复制原始对象的所有字段,包括引用类型字段。这意味着新对象和原始对象拥有独立的引用类型字段,它们的修改互不影响。深拷贝可以通过序列化和反序列化、手动逐个复制字段等方式来实现。

下面是深拷贝和浅拷贝的区别:

复制内容:浅拷贝仅复制对象的字段值,而深拷贝会递归复制对象的所有字段,包括引用类型字段的内容。

共享引用:浅拷贝会共享原始对象和新对象的引用类型字段,即它们指向相同的内存地址。深拷贝会创建独立的引用类型字段,使得原始对象和新对象的引用类型字段指向不同的内存地址。

对原始对象的影响:浅拷贝对原始对象的修改会反映在新对象上,而深拷贝不会受到影响。

对性能的影响:深拷贝通常需要更多的时间和资源,因为它需要递归复制对象的所有字段。

根据需求和对象结构的复杂程度,选择适当的拷贝方式是很重要的。浅拷贝通常适用于简单的对象结构,而深拷贝则适用于包含复杂引用类型字段的对象,确保对象之间的独立性和数据完整性。

谈谈 Error 和 Exception 的区别?

在Java中,Error和Exception都是Throwable类的子类,用于表示程序中的异常情况。它们之间的区别主要体现在以下几个方面:

Error(错误):Error表示严重的错误,通常是不可恢复的,例如虚拟机错误(如OutOfMemoryError、StackOverflowError)或者系统错误(如ThreadDeath)。当出现Error时,程序通常无法继续执行,因此一般不对其进行捕获和处理。

Exception(异常):Exception表示非正常的情况,但通常可以被捕获和处理。Exception又分为两种类型:

    Checked Exception(受检异常):这些异常在编译时必须进行处理,否则会产生编译错误。例如,IOException、SQLException等。程序在处理Checked Exception时,可以使用try-catch语句进行捕获和处理,或者在方法声明中使用throws关键字声明该异常的抛出。

    Unchecked Exception(非受检异常):这些异常是RuntimeException及其子类的实例,不需要在编译时进行强制处理。例如,NullPointerException、ArrayIndexOutOfBoundsException等。程序对于Unchecked Exception可以选择性地进行捕获和处理,但不强制要求。

异常处理:通常情况下,程序应该尽可能地处理异常,以提高程序的健壮性和可靠性。对于Checked Exception,应该在编译时强制进行处理,以确保异常被正确处理。对于Unchecked Exception,可以根据实际情况选择是否处理,但建议至少记录异常信息或进行适当的处理。

总结:
Error表示严重的错误,通常是不可恢复的,而Exception表示非正常的情况,通常可以被捕获和处理。Exception又分为Checked Exception和Unchecked Exception,其中Checked Exception需要在编译时进行处理,而Unchecked Exception不需要强制处理。异常处理是保证程序健壮性和可靠性的重要手段。

请简述一下 String、StringBuffer 和 StringBuilder 三者的区别?

1.String 为字符串常量,StringBuffer与StringBuilder字符串变量,从而效率:3.String2.StringBuffer1.StringBuilder(一般情况下);
2.StringBuffer是线程安全的,而StringBuilder为非线程安全;
3.String 是不可变的对象, 每次对 String 类型进行改变的等同于生成了一个新的 String 对象,经常改变内容的字符串不建议使用 String;
4.对StringBuffer 类改变,每次结果都会对 StringBuffer 对象本身进行操作,而不是生成新的对象,再改变对象引用,经常改变内容的字符串建议使用 StringBuffer ;
5.StringBuffer 上的主要操作为 append 和 insert 方法。

”equals” 与 “==”、“hashCode” 的区别和使用场景?

equals 比较的是值和地址,如果没有重写equals方法,其作用与==相同;
在String中重写了equals方法,比较的是值是否相等;
hashCode用于散列数据结构中的hash值计算;
equals两个对象相等,那hashcode一定相等,hashcode相等,不一定是同一个对象;

什么是反射机制?反射机制的应用场景有哪些?

Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类中的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。
应用场景:

逆向代码,例如反编译
与注解相结合的框架,如 Retrofit
单纯的反射机制应用框架,例如 EventBus(事件总线)
动态生成类框架 例如Gson

谈谈如何重写 equals() 方法?为什么还要重写 hashCode()?

hashcode()
hashCode 的存在主要用于查找的快捷性,如 Hashtable, HashMap 等,hashCode 是用来在三列存储结构中确定对象的存储地址的。
如果两个对象相同,就是适用于 euqals(java.lang.Object) 方法,那么这两个对象的 hashCode一定相同。
如果对象的euqals 方法被重写,那么对象的 hashCode 也尽量重写,并且产生 hashCode 使用的对象,一定要和 equals 方法中使用的一致,否则就会违反上面提到的第二点。
两个对象的 hashCode 相同,并不一定表示这两个对象就相同,也就是不一定适用于equals() 方法,只能够说明这两个对象在三列存储结构中,如 Hashtable.,他们存在同一个篮子里。
以上话以前摘录自一篇博客,讲的非常好。
equals(Object obj)
如果一个类没有重写 equals(Object obj)方法,则等价于通过 == 比较两个对象,即比较的是对象在内存中的空间地址是否相等。
如果重写了equals(Object ibj)方法,则根据重写的方法内容去比较相等,返回 true 则相等,false 则不相等。

Java 中 IO 流分为几种?它们之间有什么区别?

IO 流分为几种
Java中的流分为两种,一种是字节流,另一种是字符流,分别由四个抽象类来表示(每种流包括输入和输出两种所以一共四个):InputStream,OutputStream,Reader,Writer。Java中其他多种多样变化的流均是由它们派生出来的.

字符流和字节流是根据处理数据的不同来区分的。字节流按照8位传输,字节流是最基本的,所有文件的储存是都是字节(byte)的储存,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,再储存这些字节到磁盘。

1.字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串;

2.节流提供了处理任何类型的IO操作的功能,但它不能直接处理Unicode字符,而字符流就可以。

谈谈你对 Java 泛型中类型擦除的理解,并说说其局限性?

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

如在代码中定义的List和List等类型,在编译后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来说是不可见的。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法避免在运行时刻出现类型转换异常的情况。类型擦除也是Java的泛型实现方法与C++模版机制实现方式之间的重要区别。

String 为什么要设计成不可变的?

String在Java中被设计为不可变的,主要基于以下几个原因:

安全性:由于String是不可变的,它的值在创建后不能被修改。这种不可变性使得String对象在多线程环境下是线程安全的,不需要额外的同步措施。这样可以避免并发访问带来的竞态条件和数据不一致性问题。

缓存和性能优化:String对象的不可变性使得它们可以被安全地缓存。在Java中,String常量池(String Pool)是一块特殊的内存区域,用于存储字符串常量,这样相同的字符串只需要在内存中存储一份,不同的字符串可以共享同一个实例。这样可以提高内存利用率,并且在比较字符串时可以使用引用的比较(==),而不是逐个字符比较,从而提升性能。

传参和哈希:由于String的不可变性,它可以安全地作为方法参数传递,不会被修改。此外,String还被广泛用于哈希表(如HashMap)的键,由于哈希表的工作原理是通过哈希码进行快速查找,如果String是可变的,那么在修改字符串后哈希码也会改变,导致哈希表无法正确定位到对应的键。

线程安全和设计简洁:String的不可变性使得它在多线程环境下更容易使用和理解。不可变对象的状态是固定的,无需担心它在多个线程间的修改。此外,不可变性还可以简化代码设计,因为无需考虑对象状态的变化。

总结:
String被设计为不可变的,主要是出于安全性、缓存和性能优化、传参和哈希、线程安全和代码设计简洁等方面的考虑。不可变性使得String在多线程环境下是线程安全的,并且可以安全地进行缓存和共享,提高性能和内存利用率。

说说你对 Java 注解的理解?

Java 注解是一种用于提供元数据(metadata)的标记机制。它是从 JDK 5 开始引入的一项重要特性,通过在代码中添加注解来为程序元素(类、方法、字段等)附加额外的信息,以便于编译器、开发工具和运行时环境在处理代码时能够获取和利用这些信息。

注解可以用于多种用途,包括:

编译时的静态检查:通过定义自定义的注解,可以在编译时对代码进行静态检查,发现潜在的错误或者问题,提高代码的质量和可维护性。

自动生成代码:通过注解处理器(Annotation Processor)可以读取源代码中的注解,并根据注解生成额外的代码,减少样板代码的编写。

运行时的动态处理:通过反射机制,可以在运行时获取程序元素上的注解信息,从而进行一些动态的处理,例如配置初始化、权限检查、日志记录等。

文档生成:注解可以用于生成文档或者其他形式的文档化信息,使得代码更易于理解和使用。

Java 注解的定义和使用相对简单,使用 @ 符号将注解应用于目标元素上。注解可以携带参数,参数可以是预定义的类型(如基本类型、字符串、枚举等),也可以是注解类型、数组或者其他类型。通过元注解(Meta-Annotation)可以对注解本身进行注解,用于定义注解的作用范围、生命周期和其他属性。

Java 提供了一些内置的注解,如 @Override、@Deprecated、@SuppressWarnings 等。同时,也可以自定义注解来满足特定的需求。

总的来说,Java 注解是一种用于提供元数据的机制,通过在代码中添加注解来为程序元素附加额外的信息,从而实现编译时检查、代码生成、动态处理和文档生成等功能,提高代码的可读性、可维护性和灵活性。

谈一谈 Java 成员变量、局部变量和静态变量的创建和回收时机?

1.成员变量:属于类,也就是对象,对象实例化它创建,对象销毁他销毁,举个栗子:
class Test{
var name =“github”; //成员变量
fun myName() {
Toast.showShort(context, this.name); //自己可以直接用,别的类需要实例化一下才能用,
}
}
2.局部变量:是一般多是方法中定义的变量(生命周期相对其他两个是最短的),创建和回收 都会随着方法的使用改变
3.静态变量:比较常用,大家都可以直接调用(类名直接 . ***就可以了 ) ,随着类创建而创建,类消失它也消失

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值