Java 面试题 - 基础篇

本文详细阐述了JDK与JRE的区别,介绍了Java中操作符、静态变量、局部变量、String/StringBuffer/StringBuilder的区别,讨论了内存泄漏、final/finally/finalize的关键字用法,以及Java中数据类型选择和异常处理的最佳实践。
摘要由CSDN通过智能技术生成

JDK和JRE的区别

JDK 是Java 的开发工具包,可以理解为是java开发的核心。其中主要包含了三方面的内容:

  • JRE - Java程序的运行环境
  • Java的基础类库(Java API)
  • Java的工具包

JRE 是java程序的运行环境,所有的Java程序必须依赖jre才能运行。jre包括两部分:JVM和一些核心类库。jvm是用来解释字节码的,但是只有jvm是不能完全解释字节码文件,还需要一些核心类库,这些类库都存放在jre目录底下的lib文件中。

&操作符和&&操作符的区别

首先,&符号它表示一个 and 的意思,也就是说两边的条件都需要成立。

在使用 & 时,两个操作数都会被求值。而&&,当第一个操作数取值为 false 时,另一个操作数就不会被求值,这种短路求值可以提高程序的执行效率。

静态变量、成员变量、局部变量的区别

  • 静态变量:静态变量是使用关键字 “static” 声明在类中的变量。它属于类而不是对象,因此所有实例共享相同的静态变量。静态变量存储在静态存储区,它们在程序启动时就会被初始化,并且在程序执行期间一直存在。静态变量通常用于表示与类相关的常量或计数器。
  • 成员变量:成员变量是声明在类中的变量,它属于类的每个对象。每个对象都有自己的成员变量实例。成员变量用于描述对象的特征或状态,因此每个对象可以拥有不同的成员变量的值。
  • 局部变量:局部变量是在方法、函数或代码块内部声明的变量。它们只在声明它们的作用域内可见,一旦作用域结束,变量就会被销毁。局部变量通常用于存储临时的中间值和执行代码块所需的数据。

String、StringBuffer、 StringBuilder 的区别

可变性

String 类中使用 final 关键字字符数组保存字符串private final char value[],所以 String 对象是不可变的。而 StringBuild 和 StringBuffer 都继承自 AbstractStringBuilder 类,在 AbstractStringBuilder 中也是使用字符数组来保存字符串,char[] value 但是没有用 final 关键字修饰,所以这两种对象是可变的。

线程安全性

String 中对象是不可变的,也就可以理解为常量,线程安全。

AbstractStringBuilder 是 StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本类型,如 expandCapacity.append.insert.indexOf 等公共方法。StringBuffer 对方法或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder 没有对方法加同步锁,所以是非线程安全的。

性能

String 类型每次进行改变时,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。

StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。

相同情况下使用 StringBuilder 仅能获得 10% ~ 15% 左右的性能提升,但却要冒多线程不安全的风险。

总结

操作少量数据 使用 String。

单线程操作 字符串缓冲区下 操作大量数据 使用 StringBuilder。

多线程操作 字符串缓冲区下 操作大量数据 使用 StringBuffer。

String str = “i” 和 String str = new String(“1”) 一样吗

不一样,因为内存的分配方式不一样。

  • String str = “i”的方式
    • JVM 会将其分配到常量池中,此时仅产生一个字符串对象。
  • String str = new String(“i”)
    • 创建了两个对象,一个常量池中的“i”,一个是new创建在堆上的对象
    • JVM 会先在堆内存分配一个 String 对象,然后该对象指向常量池的字符串常量对象,如果字符串之前不存在,相当于创建了2 个对象。

float f=3.4; 是否正确

不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型(downcasting,也称为窄化)会造成精度损失,因此需要强制类型转换 float f =(float)3.4; 或者写成 float f =3.4F;。

Java 中应该使用什么数据类型来计算价格

如果不是特别关心内存和性能而需要进行精确的价格计算的话,使用 BigDecimal,否则使用预定义精度的double 类型。

==与 equals 的区别

==:判断两个对象的地址值是不是相等。即:判断两个对象是不是同一个对象。(基本数据类型比较的是值,引用数据类型比较的是内存地址)

equals:判断两个对象是否相等,equals一般分两种情况,如下:

  • 类没有覆盖 equals 方法,则通过 equals() 比较该类的两个对象时,等价于通过“==”比较这两个对象。
  • 类覆盖了 equals 方法,一般我们都覆盖 equals 方法来判断两个对象的内容是否相等;若他们的内容相等,则返回 true(即:认为这两个对象相等)。

接口和抽象类的区别是什么

首先,Java中接口(interface)和抽象类(abstract class)是两种用于实现多态性和封装性的机制。

  • 定义:接口是一种完全抽象的类,它只包含方法的声明而没有具体实现;抽象类是一个有抽象方法的类,可以包含非抽象方法的实现。
  • 实现:一个类可以实现多个接口,通过关键字implements来实现接口,实现接口的类必须实现接口中声明的所有方法;一个类只能继承一个抽象类,通过关键字extends来继承抽象类,继承抽象类的子类可以选择性地实现抽象方法。
  • 构造函数:接口不能有构造函数,因为接口不能被实例化;抽象类可以有构造函数,用于初始化抽象类的成员变量。
  • 访问修饰符:接口中的方法默认是public的,且不能使用其他访问修饰符;抽象类中的方法可以有不同的访问修饰符。
  • 变量:接口中的变量默认是public、static、final的常量;抽象类中可以包含各种类型的变量。
  • 单继承和多实现:一个类只能继承一个抽象类,但可以实现多个接口。
  • 设计目的:接口的设计目的是为了实现类的多态性,通过接口可以实现不同类的对象以相同的方式调用接口中定义的方法;抽象类的设计目的是为了提供一个通用的父类,用于抽象出多个子类的共同特征和行为。

在使用时需要根据具体的需求和设计目的来选择使用接口还是抽象类。如果需要实现多个类的共同行为,并且这些类之间没有明显的层次关系,可以使用接口;如果需要提供一个通用的父类,并且这些类之间有明显的层次关系,可以使用抽象类。

Java 中的值传递和引用传递

  • 值传递

在方法的调用过程中,实参把它的实际值传递给形参,此传递过程就是将实参的值复制一份传递到函数中,这样如果在函数中对该值(形参的值)进行了操作将不会影响实参的值。因为是直接复制,所以这种方式在传递大量数据时,运行效率会特别低下。

  • 引用传递

引用传递弥补了值传递的不足,如果传递的数据量很大,直接复过去的话,会占用大量的内存空间,而引用传递就是将对象的地址值传递过去,函数接收的是原始值的首地址值。在方法的执行过程中,形参和实参的内容相同,指向同一块内存地址,也就是说操作的其实都是源数据,所以方法的执行将会影响到实际对象。

  • 基本数据类型(如 int、float、char 等)是通过值传递的,对形参的修改不会影响到实参。
  • 引用类型(如对象、数组等)是通过引用传递的,形参和实参指向同一个内存地址(同一个对象),因此对参数的修改会影响到实际的对象。
  • 对于一些特殊的引用类型,比如 String、Integer、Double 等包装类,虽然它们是引用类型,但在方法参数传递时,表现出来的行为更接近于值传递。即使对它们进行了修改,也不会影响到原始对象的值。因此,可以简单理解为传值。

Java 中的异常

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。

Exception 和 Error 的区别

exception(异常)和 error(错误)的父类都是 Throwable 类,它们的区别如下:

  • exception 是指程序在正常执行过程中可能发生的可以测的问题。异常是可以被捕获和处理的,可以通过编写异常处理代码来处理异常情况。Java 中的异常是通过 Exception 类及其子类表示的。对于异常,应该尽可能处理异常,使程序恢复运行,不应该随意终止异常。
  • error 是指程序在运行过程中出现的严重问题,通常是无法恢复或无法处理的。错误表示系统级的异常,他们不应该被捕获或处理,而是应该由 Java 虚拟机或操作系统来处理。对于系统崩溃、虚拟机错误、内存空间不足、方法调用栈溢出等错误导致的应用程序中断,仅靠程序本身无法恢复和预防,遇到这样的错误,建议让程序终止。

Exception类Error类,二者都是 Java 异常处理的重要子类,各自都包含大量子类。

Exception 包含但不仅限于以下子类:

  • NullPointerException:空指针异常,当应用程序试图在一个空对象上调用方法时抛出。
  • ArrayIndexOutOfBoundsException:数组下标越界异常,当应用程序试图访问数组中不存在的元素时抛出。
  • IllegalArgumentException:非法参数异常,当方法接收到一个不合法或不适当的参数时抛出。
  • IOException:输入输出异常,当发生输入输出操作异常时抛出。
  • ClassNotFoundException:类未找到异常,当试图加载类时找不到相应的类时抛出。

Error 包含但不仅限于以下子类:

  • OutOfMemoryError:内存溢出错误,当应用程序尝试申请更多内存,而系统没有足够的内存可用时抛出。
  • StackOverflowError:栈溢出错误,当方法调用栈的深度超过了虚拟机所允许的最大深度时抛出。
  • NoClassDefFoundError:类定义未找到错误,当试图加载类的定义时找不到相应的类定义时抛出。

常见的 RuntimeException,运行时异常与非运行异常的区别

  • NullPointerException:当应用程序试图在一个对象上调用实例方法,而该对象没有被实例化时,抛出该异常。
  • ArrayIndexOutOfBoundsException:当应用程序试图访问数组中的一个不存在的索引时,抛出该异常。
  • IllegalArgumentException:当向方法传递了一个不合法或不正确的参数时,抛出该异常。
  • ClassCastException:当试图将一个对象强制转换为不是类或接口的子类时,抛出该异常。
  • ArithmeticException:当出现除数为零的情况时,抛出该异常。

运行时异常与非运行时异常的区别主要在于它们对于异常处理的要求和处理方式不同:

  • 运行时异常(RuntimeException):继承自 RuntimeException 类的异常被称为运行时异常。运行时异常通常是由程序的逻辑错误引起的,如数组越界访问、空指针引用、类型转换错误等。运行时异常的特点是在编译阶段不会强制进行异常处理,如果没有显式地捕获和处理,编译器不会发出警告或错误。在运行时发生这些异常时,也不会强制要求处理,但是应该注意到这些异常的出现可能意味着程序中存在潜在的问题。
  • 非运行时异常:所有的异常类都直接或间接地继承自 Exception 类,但不是继承自 RuntimeException。非运行时异常在编译器会强制要求进行异常捕获和处理,否则会导致编译错误。这些异常包括 IOException、SQLException、ClassNotFoundException 等。这些异常通常是由外部环境或者输入错误引起的,需要程序员显式地捕获并处理。

总的来说,运行时异常在编译阶段不需要强制异常处理,而非运行时异常在编译阶段强制要求异常处理。但无论是哪种类型的异常,都应该根据实际情况进行合适的异常处理。

Java 反射

Java的反射机制允许程序在运行时获取类的信息、调用类的方法和操作类的属性,从而实现了对Java对象的动态操作。通过反射,可以在不知道类名的情况下获取类的信息,创建类的对象,操作类的属性,调用类的方法等,这使得Java程序具有更大的通用性和灵活性。

在实际开发中,像Spring、Spring Boot、MyBatis等框架大量使用了反射机制,通过反射技术实现自动装配、依赖注入、AOP(面向切面编程)等功能。同时,许多常用的设计模式如工厂模式、单例模式、代理模式等,也可以借助反射机制来实现更加灵活和通用的代码。

总之,反射机制为Java程序提供了更大的灵活性和通用性,但在实际应用中也需要慎重考虑性能开销和潜在的安全隐患。反射是一把双刃剑,合理地使用反射,可以使代码更加灵活,但同时也需要谨慎思考其使用场景及潜在影响。

final 关键字

final 关键字修饰主要用在三个地方:变量、方法、类。

修饰变量:如果是基本数据类型,则器数值一旦在初始化之后便不能修改;如果是引用类型,则在对其初始化之后便不能再让其指向另一个对象。

修饰方法:表示该方法不可被子类重写或修改。使用 final 修饰方法的原因有两个,一个是把方法锁定防止被修改,一个是效率。

修饰类:表示该类不能被继承,final 类中的所有成员方法都会被隐式地指定为 final 方法。

浅拷贝和深拷贝区别

浅拷贝和深拷贝是针对对象的复制而言的,它们的区别在于复制的程度和复制后对象的引用关系。

  • 浅拷贝:
    浅拷贝是指只复制对象本身,而不复制对象内部的引用类型数据。当进行浅拷贝时,被复制对象和复制后的对象会共享内部引用类型数据,即复制后的对象中的引用类型数据指向的仍然是原对象中引用类型数据的地址。如果原对象中的引用类型数据发生了改变,浅拷贝后的对象中的引用类型数据也会随之改变。
  • 深拷贝:
    深拷贝是指不仅复制对象本身,还会复制对象内部的引用类型数据,即将内部引用类型数据也进行复制。当进行深拷贝时,被复制对象和复制后的对象之间是完全独立的,内部引用类型数据指向的地址不一样。即使原对象中的引用类型数据发生了改变,深拷贝后的对象中的引用类型数据不会受到影响。

在Java中,使用clone()方法可以进行浅拷贝,而通过序列化和反序列化对象可以实现深拷贝。

Java 内存泄漏

尽管对象不再被程序引用,但垃圾回收器无法将其回收,从而导致内存泄漏。内存泄漏主要发生在堆、静态对象引用、未关闭的流和连接上:

  • 堆内存泄漏:堆内存是存储新建Java对象的地方,如果有对象不再被使用却未被回收,就会导致堆内存泄漏。调整堆内存大小可以部分解决这个问题。
  • 静态对象引用:静态类型对象的引用在整个生命周期内不会被垃圾回收,因此需要格外注意对关键字static的使用,尤其是对于任何集合或大型类的静态声明。这会使对象的生命周期与JVM的生命周期同步,导致无法及时回收。
  • 未关闭的流:忘记关闭流会导致内存泄漏,因为未关闭的流会导致低层资源的泄漏,如文件描述符、打开连接等。JVM会跟踪这些重要资源,进一步导致内存泄漏。
  • 未关闭的连接:对未关闭的连接(如数据库、FTP服务器等)的处理也可能导致内存泄漏。使用完连接后一定要及时关闭,否则会导致资源泄漏和内存泄漏。

final finally finalize区别

Final, finally, 和 finalize是在Java中具有不同含义和用途的关键字。

  • final是一个修饰符,可以用来修饰类、方法和变量。被final修饰的类不能被继承,被final修饰的方法不能被重写,被final修饰的变量是一个常量,它的数值在初始化后不能被改变。
  • finally是用于异常处理的关键字,它是与try-catch结构配合使用的。无论是否有异常被抛出,finally中的代码都会被执行,通常用于释放资源或完成一些必要的清理工作。例如,关闭文件流、数据库连接等。
  • finalize()是Object类中的一个方法,它是由垃圾收集器在回收对象之前调用的。在Java中,你可以重写这个方法来在对象被销毁前执行特定的清理操作,比如关闭文件或释放其他资源。但是,由于finalize()的执行时间不确定,并且垃圾收集并不保证一定会执行,所以在实际应用中,更推荐使用try-finally块或者其他资源管理方法来确保资源的及时释放。
  • 32
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值