Java基础篇常见面试问题总结

1. 你是怎样理解 OOP面向对象?

面向对象是利于语言对现实事物进行抽象。面向对象具有以下特征:

  • 继承:继承是从已有类得到继承信息创建新类的过程
  • 封装:封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义
    的接口
  • 多态性:多态性是指允许不同子类型的对象对同一消息作出不同的响应

2. 重载与重写区别

  • 重载发生在本类,重写发生在父类与子类之间
  • 重载的方法名必须相同,重写的方法名相同且返回值类型必须相同
  • 重载的参数列表不同,重写的参数列表必须相同
  • 重写的访问权限不能比父类中被重写的方法的访问权限更低
  • 构造方法不能被重写

3. 接口与抽象类的区别

  • 抽象类要被子类继承,接口要被类实现
  • 抽象类可以有构造器、接口不能有构造器
  • 抽象类:可以有成员变量;接口:只能声明常量

4. 深拷贝与浅拷贝的理解

深拷贝和浅拷贝就是指对象的拷贝,一个对象中存在两种类型的属性,一种是基本数据类型,一种是实例对象的引用。

  • 浅拷贝是指,只会拷贝基本数据类型的值,以及实例对象的引用地址,并不会复
    制一份引用地址所指向的对象,也就是浅拷贝出来的对象,内部的类属性指向的
    是同一个对象
  • 深拷贝是指,既会拷贝基本数据类型的值,也会针对实例对象的引用地址所指向
    的对象进行复制,深拷贝出来的对象,内部的类执行指向的不是同一个对象

5. 什么是自动拆装箱? int和 Integer有什么区别

  • 装箱:将基本类型转换成包装类对象
  • 拆箱:将包装类对象转换成基本类型的值

java 为什么要引入自动装箱和拆箱的功能?

主要是用于 java集合中,List list=new ArrayList();
list 集合如果要放整数的话,只能放对象,不能放基本类型,因此需要将整数自动装箱成对象。

区别:

  • Integer是 int的包装类,int则是 java的一种基本数据类型
  • Integer变量必须实例化后才能使用,而 int变量不需要
  • Integer实际是对象的引用,当 new一个 Integer时,实际上是生成一个指针指向此对象;而 int则是直接存储数据值
  • Integer的默认值是 null,int的默认值是 0

6. ==和 equals()区别

  1. ==
    • 如果比较的是基本数据类型,那么比较的是变量的值
    • 如果比较的是引用数据类型,那么比较的是地址值(两个对象是否指向同一块内
      存)
  2. equals
    • 如果没重写 equals方法比较的是两个对象的地址值
    • 如果重写了 equals方法后我们往往比较的是对象中的属性的内容
    • equals 方法是从 Object类中继承的,默认的实现就是使用==

很多类(如 String, Integer 等)都重写了 equals() 方法,以提供逻辑上的比较。

7. String类 能被继承吗为什么用 final修饰

  • 不能被继承,因为 String类有 final修饰符,而 final修饰的类是不能被继承的
  • String 类是最常用的类之一,为了效率,禁止被继承和重写。
  • 为了安全。String类中如果方法可以重写,可能被植入恶意代码,破坏程序。Java的安全性也体现在这里。

8. final、finally、finalize区别

  1. final
    final 是一个关键字,用于限定变量、方法、和类的行为:

    • 变量:当 final 修饰一个变量时,这意味着该变量一旦被初始化之后,其值就不能再被修改(即常量)。
    • 方法:final 修饰的方法不能被子类重写。
    • 类:final 修饰的类不能被继承。
  2. finally
    finally 是一个块,通常与try和catch块一起使用,在异常处理中起到关键作用。finally 块中的代码段总是会执行,无论try块中的代码是否抛出异常。这使得finally块非常适合用于清理资源,比如关闭文件流或数据库连接。
    例如:

try {
    // 尝试执行的代码,可能会抛出异常
} catch (Exception e) {
    // 处理异常
} finally {
    // 清理代码,无论是否发生异常都会执行
}
  1. finalize
    finalize 用于对象被垃圾回收前的清理工作,但不建议使用。因为它是不可预测的,通常也不是必须的,而且可能导致性能问题。
@Override
protected void finalize() throws Throwable {
    try {
        // 清理资源等操作
    } finally {
        super.finalize();
    }
}

9. Object 类的常见方法有哪些?

在 Java 中,Object 类是所有类的超类,它提供了一些常见的方法,这些方法可以在任何 Java 对象上调用。以下是 Object 类的常见方法及其简要说明:

  1. equals(Object obj)

    • 用于比较两个对象是否“相等”。默认实现是比较对象的内存地址,子类通常会重写此方法以比较对象的内容。
  2. hashCode()

    • 返回对象的哈希码,通常与 equals 方法一起重写。根据 Java 规范,如果两个对象根据 equals 方法是相等的,那么它们的 hashCode 值也必须相等。
  3. toString()

    • 返回对象的字符串表示。默认实现返回类名和对象的内存地址,子类通常会重写此方法以提供更有意义的字符串表示。
  4. getClass()

    • 返回对象的运行时类,返回的 Class 对象包含有关该对象所属类的信息。
  5. clone()

    • 创建并返回对象的副本。类必须实现 Cloneable 接口才能合法地调用此方法,否则会抛出 CloneNotSupportedException 异常。
  6. finalize()

    • 在垃圾回收器决定回收对象之前调用,用于进行清理操作。已经被废弃,不推荐使用。
  7. wait()

    • 使当前线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。常用于线程间的同步。
  8. notify()

    • 唤醒在此对象监视器上等待的单个线程。常用于线程间的同步。
  9. notifyAll()

    • 唤醒在此对象监视器上等待的所有线程。常用于线程间的同步。

这些方法是 Java 中所有对象的基本方法,了解并合理使用它们可以帮助你更好地掌握 Java 编程。

10. 说一下集合体系

Java 集合,也叫作容器,主要是由两大接口派生而来:一个是 Collection接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。
在这里插入图片描述

  1. 核心接口
  • Collection 接口
    • 是集合层次结构的根接口,包含操作集合的基本方法。主要的子接口包括 List、Set 。
  • Map 接口
    • 不属于 Collection 接口的子接口,但经常与集合框架一起使用。它存储键值对(key-value pairs),不允许键重复。主要实现类包括 HashMap、LinkedHashMap 和 TreeMap。
  1. 主要实现类

set:

  • HashSet
    • 基于哈希表的实现,不保证顺序,适合快速查找和去重的场景。
  • LinkedHashSet
    • 保持插入顺序的 HashSet,适合需要顺序且去重的场景。
  • TreeSet
    • 基于红黑树的实现,保证元素的自然顺序或自定义顺序,适合需要排序的场景。

List:

  • ArrayList
    • 基于动态数组的实现,提供随机访问能力,适合频繁读取的场景。
  • LinkedList
    • 基于双向链表的实现,适合频繁插入和删除的场景。

map:

  • HashMap
    • 基于哈希表的实现,允许使用 null 键和 null 值,不保证顺序,适合快速查找和键值映射的场景。
  • LinkedHashMap
    • 保持插入顺序的 HashMap,适合需要顺序且快速查找的场景。
  • TreeMap
    • 基于红黑树的实现,保证键的自然顺序或自定义顺序,适合需要排序的键值映射场景。

10.1 说说 List, Set, Queue, Map 四者的区别?

  • List: 存储的元素是有序的、可重复的。
  • Set: 存储的元素不可重复的。
  • Queue: 按特定的排队规则来确定先后顺序,存储的元素是有序的、可重复的。
  • Map: 使用键值对(key-value)存储,类似于数学上的函数 y=f(x),“x” 代表 key,“y” 代表 value,key 是无序的、不可重复的,value 是无序的、可重复的,每个键最多映射到一个值。

10.2 如何选用集合?

我们主要根据集合的特点来选择合适的集合。比如:

  • 我们需要根据键值获取到元素值时就选用 Map 接口下的集合,需要排序时选择 TreeMap,不需要排序时就选择 HashMap,需要保证线程安全就选用 ConcurrentHashMap
  • 我们只需要存放元素值时,就选择实现Collection 接口的集合,需要保证元素唯一时选择实现 Set 接口的集合比如 TreeSetHashSet,不需要就选择实现 List 接口的比如 ArrayListLinkedList,然后再根据实现这些接口的集合的特点来选用。

10.3 为什么要使用集合?

当我们需要存储一组类型相同的数据时,数组是最常用且最基本的容器之一。与数组相比,Java 集合提供了更灵活、更有效的方法来存储多个数据对象。Java 集合框架中的各种集合类和接口可以存储不同类型和数量的对象,同时还具有多样化的操作方式。相较于数组,Java 集合的优势在于它们的大小可变、支持泛型、具有内建算法等。总的来说,Java 集合提高了数据的存储和处理灵活性,可以更好地适应现代软件开发中多样化的数据需求,并支持高质量的代码编写。

11. Java中有几种类型的流?

在 Java 中,流(Stream)通常用于输入和输出操作。Java 的 I/O 流主要分为两大类:字节流和字符流。每种类型的流都有输入流和输出流。以下是详细的分类:

  1. 字节流(Byte Streams)

字节流用于处理原始的二进制数据。它们以字节(8位)为单位来进行数据的读写操作。

  • 输入字节流(Input Byte Streams)

    • InputStream(抽象类):所有字节输入流的超类。
      • FileInputStream:从文件中读取字节。
      • BufferedInputStream:为另一个输入流添加缓冲功能。
  • 输出字节流(Output Byte Streams)

    • OutputStream(抽象类):所有字节输出流的超类。
      • FileOutputStream:将字节写入文件。
      • BufferedOutputStream:为另一个输出流添加缓冲功能。
  1. 字符流(Character Streams)

字符流用于处理字符数据,它们以字符(16位 Unicode)为单位来进行数据的读写操作。字符流适用于处理文本数据。

  • 输入字符流(Input Character Streams)
    • Reader(抽象类):所有字符输入流的超类。
      • FileReader:从文件中读取字符。
      • BufferedReader:为另一个输入流添加缓冲功能,并提供 readLine 方法读取一行文本。
      • InputStreamReader:将字节流转换为字符流,通常与其他字节流组合使用。
  • 输出字符流(Output Character Streams)
    • Writer(抽象类):所有字符输出流的超类。
      • FileWriter:将字符写入文件。
      • BufferedWriter:为另一个输出流添加缓冲功能,并提供 newLine 方法写入行分隔符。
      • OutputStreamWriter:将字符流转换为字节流,通常与其他字节流组合使用。

12. 请写出你最常见的 5个 RuntimeException

在这里插入图片描述

13. 谈谈你对反射的理解

  1. 反射机制

    所谓的反射机制就是 java语言在运行时拥有一项自观的能力。通过这种能力可以彻底了解自身的情况为下一步的动作做准备。

    Java 的反射机制的实现要借助于 4个类:class,Constructor,Field,Method;其中 class代表的是类对象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组成部分。

  2. Java反射的作用

    在 Java运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法。对于任意一个对象,可以调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于 Java语言的反射(Reflection)机制。

  3. Java反射机制提供功能

    • 在运行时判断任意一个对象所属的类。
    • 在运行时构造任意一个类的对象。
    • 在运行时判断任意一个类所具有的成员变量和方法。
    • 在运行时调用任意一个对象的方法

14. 什么是java序列化,如何实现java序列化?

Java 序列化(Serialization)是将对象的状态转换为字节流,以便将对象保存到存储介质(如文件、数据库)中或通过网络传输到远程主机的过程。反序列化(Deserialization)是将字节流转换回对象的过程。序列化允许对象的持久化存储和跨网络传输。

实现 Java 序列化的步骤

要实现 Java 序列化,需要完成以下步骤:

  1. 实现 Serializable 接口

    • 要使一个类的对象可序列化,该类必须实现 java.io.Serializable 接口。该接口是一个标记接口,没有任何方法。
  2. 使用 ObjectOutputStreamObjectInputStream

    • ObjectOutputStream 用于将对象序列化为字节流。
    • ObjectInputStream 用于将字节流反序列化为对象。

15. java new一个对象还有其他方式吗?

在Java中,除了使用new关键字来创建对象之外,还有其他几种方式可以实例化对象。以下是一些常见的方法:

  1. 通过反射(Reflection)
    使用Java的反射机制可以动态地创建对象。

    try {
        Class<?> clazz = Class.forName("com.example.MyClass");
        Object obj = clazz.getDeclaredConstructor().newInstance();
    } catch (Exception e) {
        e.printStackTrace();
    }
    
  2. 使用克隆(Clone)
    如果一个类实现了Cloneable接口,并且重写了clone()方法,可以通过克隆现有对象来创建新对象。

    public class MyClass implements Cloneable {
        @Override
        protected Object clone() throws CloneNotSupportedException {
            return super.clone();
        }
    }
    
    MyClass obj1 = new MyClass();
    try {
        MyClass obj2 = (MyClass) obj1.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    
  3. 使用序列化与反序列化
    通过将对象序列化为字节流然后反序列化,可以创建一个新的对象。

    import java.io.*;
    
    public class MyClass implements Serializable {
        private static final long serialVersionUID = 1L;
    }
    
    MyClass obj1 = new MyClass();
    MyClass obj2 = null;
    
    try {
        // 序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(obj1);
        oos.close();
    
        // 反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        obj2 = (MyClass) ois.readObject();
        ois.close();
    } catch (IOException | ClassNotFoundException e) {
        e.printStackTrace();
    }
    
  4. 通过工厂方法(Factory Method)
    工厂方法是设计模式中的一种,通过调用工厂类的静态方法来创建对象。

    public class MyClass {
        private MyClass() {
            // 私有构造函数
        }
    
        public static MyClass createInstance() {
            return new MyClass();
        }
    }
    
    MyClass obj = MyClass.createInstance();
    
  5. 使用DI框架(例如Spring)
    依赖注入框架可以管理对象的创建和生命周期。

    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    MyClass obj = (MyClass) context.getBean("myClassBean");
    

这些方法各有其适用的场景和优缺点,选择合适的方法取决于具体的应用需求和设计模式。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值