JAVA SE上——八股1
1、面向对象编程有哪些特性?(371/1759=21.1%)
面向对象编程(Object-Oriented Programming,简称 OOP)是一种以对象为核心的编程范式,它通过模拟现实世界中的事物及其关系来组织代码。OOP 具有三大核心特性:封装、继承、多态。接下来我会逐一详细说明这些特性。
第一,封装(Encapsulation)。
封装是指将==数据(属性)和行为(方法)捆绑在一起,并对外隐藏对象的内部实现细节。通过访问修饰符(如 private、protected 和 public),我们可以控制哪些部分是对外可见的==,哪些是内部私有的。这种机制提高了代码的安全性和可维护性。例如,在 Java 中,我们通常会将类的属性设置为 private,并通过 getter 和 setter 方法提供受控的访问方式。
第二,继承(Inheritance)。
继承==允许一个类(子类)基于另一个类(父类)来构建,从而复用父类的属性和方法==。通过继承,子类不仅可以拥有父类的功能,还可以扩展或重写父类的行为。Java 中使用 extends 关键字实现继承。例如,我们可以通过定义一个通用的 Animal 类,然后让 Dog 和 Cat 类继承它,这样就避免了重复编写相同的代码。继承体现了“is-a”的关系,比如“狗是一个动物”。
第三,多态(Polymorphism)。
多态是指同一个方法调用可以根据对象的实际类型表现出不同的行为。多态分为两种形式:编译时多态(方法重载)和运行时多态(方法重写)。运行时多态是==通过动态绑定==实现的,即程序在运行时决定调用哪个方法。
例如,如果父类 Animal 有一个 makeSound() 方法,子类 Dog 和 Cat 可以分别重写这个方法,当调用 animal.makeSound() 时,具体执行的是 Dog 或 Cat 的实现。多态使得代码更加灵活和可扩展。
拓展:面向对象和面向过程的区别
(1)面向对象编程(OOP)
定义:
面向对象编程是一种以对象为核心的编程范式,它将现实世界中的事物抽象为对象,并通过对象之间的交互来完成任务。OOP 的核心思想是“万物皆对象”。
特点:
以对象为中心,强调数据和行为的封装。
使用==类(Class)作为模板来创建对象(Object)==。
支持三大特性:封装、继承和多态。
优点:
模块化:代码结构清晰,易于维护和扩展。
复用性高:通过继承和组合可以复用已有代码。
灵活性强:支持多态,能够适应复杂需求的变化。
可读性强:贴近现实世界的思维方式,便于理解和协作。
缺点:
性能开销较大:由于引入了类、对象等概念,运行效率可能不如面向过程高效。
学习较难:需要理解类、对象、继承等概念,初学者可能觉得复杂。
(2)面向过程编程(POP)
定义:
面向过程编程是一种==以过程(函数)为核心的编程范式==,它将程序分解为一系列函数或步骤,按照顺序执行任务。
特点:
以==函数为中心,强调逻辑流程。
数据和函数分离,通常通过参数传递数据。
程序由一个个独立的函数组成,函数之间通过调用关系协作==。
优点:
简单直接:逻辑清晰,适合小型项目或简单任务。
性能较高:没有额外的对象开销,运行效率更高。
易于实现:不需要复杂的类设计,开发速度快。
缺点:
扩展性差:随着项目规模增大,代码容易变得混乱,难以维护。
复用性低:函数之间的耦合度高,难以复用代码。
不适合复杂系统:面对复杂业务逻辑时,代码会显得冗长且难以管理。
2、接口、普通类和抽象类有什么区别和共同点(412/1759=23.4%)
在 Java 中,接口、普通类和抽象类是构建面向对象程序的三种重要结构。普通类用于==描述具体的对象,抽象类用于定义具有共性的基类,而接口则用于定义行为规范。它们各自有不同的用途和特点,但也有一定的共同点。接下来,我会从定义、方法实现、继承关系以及成员变量==这4个方面详细讲解它们的区别,然后再总结它们的共同点。
第一个是定义上的区别。
普通类是一个完整的、具体的类,可以直接实例化为对象。它包含属性和方法,并且可以有构造方法。
抽象类是一个不能直接实例化的类,通常用来作为其他类的基类。它可以包含抽象方法(没有实现的方法)和具体方法(有实现的方法)。
接口是一种完全抽象的结构,用于定义行为规范。它只包含抽象方法(Java 8 之后可以包含默认方法和静态方法)。
第二个是方法实现上的区别。
普通类的所有方法都可以有具体实现(即方法体)。
抽象类可以包含具体方法和抽象方法。
接口默认只包含抽象方法(Java 8 后可以包含默认方法和静态方法)。
第三是继承关系上的区别。
普通类支持单继承(一个类只能继承一个父类)。
抽象类也支持单继承(一个类只能继承一个抽象类)。
接口支持多实现(一个类可以实现多个接口)。
第四是成员变量上的区别。
普通类和抽象类都可以有各种类型的成员变量(实例变量、静态变量等)。
接口只能有常量(public static final)。
接下来讲一下共同点,一共有3点。
1)首先,它们都是==面向对象编程的基础结构,都可以用来组织代码,实现封装、继承和多态等特性。
2)其次,它们都可以包含方法,尽管接口中的方法默认是抽象的。
3)最后,它们都可以被继==承或实现,普通类可以通过继承扩展功能,抽象类和接口则需要子类继承或实现后才能使用。
拓展:
1.为什么会有普通类、抽象类和接口?
普通类、抽象类和接口的诞生,是计算机科学和软件工程发展过程中对编程范式不断优化的结果。它们的出现不仅是为了满足不同层次的需求,更是为了应对软件开发中的复杂性、可维护性和扩展性挑战。
(1)普通类的诞生:从机器语言到面向对象
在计算机发展的早期,程序设计==主要依赖于机器语言和汇编语言,程序员需要直接操作硬件资源(如寄存器、内存地址等)。这种方式虽然高效,但代码难以阅读、维护和复用==。
随着==高级语言(如 Fortran、C)的出现,函数和结构化编程逐渐成为主流。程序员可以通过函数封装逻辑,通过结构体组织数据,从而提高了代码的模块化程度。然而,这种基于过程的编程方式仍然存在局限性:数据和行为分离==,导致代码耦合度高,难以管理复杂的系统。 缺乏对现实世界中“事物”的直观建模能力。
为了解决这些问题,面向对象编程(OOP)应运而生。普通类作为 OOP 的核心构建单元,将==数据(属性)和行为(方法)封装在一起==,模拟了现实世界中的实体或功能模块。普通类的特点是具体性,即它描述了一个可以直接使用的对象。
普通类的意义:
直接实例化: 普通类可以直接创建对象,用于表示具体的实体(如 Car、Person)或功能模块(如 Calculator)。
简单易用: 对于==不需要复用或扩展的场景==,普通类是最合适的选择。
历史背景: 普通类的诞生标志着从过程式编程向面向对象编程的转变,使得代码更加贴近人类的思维方式。
(2)抽象类的诞生:从代码复用到部分抽象
随着软件规模的增长,程序员发现许多类之间存在共同的行为或属性。例如,Car 和 Bike 都属于交通工具(Vehicle),它们共享某些属性(如品牌、速度)和行为(如移动)。然而,这些类的具体实现可能有所不同(如汽车靠轮子移动,飞机靠翅膀飞行)。
在这种情况下,普通类无法很好地满足需求,因为普通类要求所有方法都必须有具体实现。于是==,抽象类被引入作为一种中间层的设计工具==。抽象类允许定义一组共同的行为或属性,同时保留部分未实现的方法供子类实现。
抽象类的意义:
代码复用: 抽象类可以包含已实现的方法(如通用的 displayBrand() 方法),供子类复用。
灵活扩展: 子类只需实现特定的抽象方法(如 move()),从而避免重复编写相同的代码。
历史背景: 抽象类的出现是对普通类的一种补充,解决了“部分共性”的问题,使得代码更加模块化和可扩展。
(3)接口的诞生:从单继承到多行为规范
尽管抽象类提供了一种强大的设计工具,但它有一个重要的限制:Java 中的类只能继承一个父类(单继承)。这意味着如果一个类需要遵循多个行为规范(如 Flyable 和 Swimmable),抽象类就无法满足需求。
为了解决这一问题,接口被引入作为==一种完全抽象的类型。接口只定义行为规范,而不关心具体的实现细节。通过实现接口,一个类可以同时具备多种行为能力,从而突破了单继承的限制。==
接口的意义:
行为规范: 接口定义了一组==行为标准(如 Flyable 的 fly() 方法),供实现类遵循。==
多继承支持: 一个类可以实现多个接口,从而具备多种行为能力(如既能飞又能游泳的鸟类)。
解耦设计: 接口==将行为规范与具体实现分离==,使得代码更加灵活和可维护。
历史背景: 接口的引入是对抽象类的进一步抽象,弥补了 Java 单继承的不足,同时也推动了==面向接口编程(Interface-Oriented Programming)==的发展。
(4)三者的协同作用:从单一到多层次设计
普通类、抽象类和接口并不是孤立存在的,而是相辅相成,共同构成了 Java 面向对象编程的强大设计能力:
普通类==用于描述具体的实体或功能模块,是面向对象的基础==。
抽象类用于==描述一组具有共同特征的对象==,提供了代码复用和扩展的能力。
接口用于==定义行为规范,实现了多继承和解耦设计==。
协同示例:
假设我们需要设计一个动物世界,其中包含鸟类、鱼类和昆虫类。
使用普通类定义具体的动物(如 Sparrow、Shark)。
使用抽象类定义一组共同的行为(如 Animal 的 eat() 方法)。
使用接口定义特定的行为规范(如 Flyable 的 fly() 方法和 Swimmable 的 swim() 方法)。
3、深拷贝和浅拷贝区别了解吗?(603/1759=34.3%)
深拷贝和浅拷贝的核心区别在于是否递归地复制对象内部的引用类型数据,接下来,我会从==定义、实现方式以及使用场景==三个方面详细讲解它们的区别。
首先是定义上的区别,
浅拷贝是指==创建一个新对象,但新对象中的引用类型字段仍然指向原对象中引用类型的内存地址==。换句话说,浅拷贝只复制了对象本身,而没有复制对象内部的引用类型数据。修改新对象中的引用类型数据会影响原对象。
深拷贝是==指创建一个新对象,并且递归地复制对象内部的所有引用类型数据。==换句话说,深拷贝不仅复制了对象本身,还复制了对象内部的所有引用类型数据。修改新对象中的引用类型数据不会影响原对象。
其次是实现方式上的区别,
浅拷贝可以使用 Object 类的 clone() 方法,也可以使用实现 Cloneable 接口并重写 clone() 的方法。
深拷贝可以手动对引用类型字段进行递归拷贝,也可以使用序列化(Serialization)的方式将对象序列化为字节流,再反序列化为新对象。
最后是使用场景上的区别,
==浅拷贝适用于当对象内部的引用类型数据==不需要独立复制的情况。
==深拷贝适用于当对象内部的引用类型数据需要完全独立==的情况。
拓展:什么是引用拷贝?一图说明三者的关系和区别
在 Java 中,引用拷贝(Reference Copy)是指将一个对象的引用赋值给另一个变量。换句话说,两个变量指向同一个对象,而不是创建一个新的对象。这种操作不会复制对象本身,而是复制对象的引用地址。
(1)引用拷贝的工作原理
当我们在 Java 中操作对象时,变量实际上==存储的是对象的引用(即内存地址),而不是对象本身。引用拷贝的核心在于:原始变量和新变量都指向同一个对象。== 对其中一个变量的操作会影响另一个变量,因为它们共享同一个对象。
(2)引用拷贝的特点
- 共享对象:引用拷贝并不会创建新的对象,而是让两个变量共享同一个对象。因此,对对象的任何修改都会影响所有引用该对象的变量。
- 节省内存:因为没有创建新的对象,引用拷贝不会额外占用内存空间。
- 潜在风险:如果多个变量共享同一个对象,可能会导致意外的副作用。例如,一个变量对对象的修改可能会影响到其他变量。
(3)引用拷贝的应用场景
- 性能优化:在需要频繁传递对象的场景中,引用拷贝可以避免创建新对象,从而提高性能。
- 共享状态:当多个变量需要共享同一个对象的状态时,引用拷贝是一个简单且高效的选择。
- 不可变对象:如果对象是不可变的(如
String),引用拷贝不会带来副作用,因为对象本身无法被修改。
(4)一图说明三者的关系和区别
![[Pasted image 20251104172814.png]]
4、int和Integer的区别(576/1759=32.7%)
int 和 Integer 是 Java 中用于表示整数的两种类型,接下来我会从==定义、使用方式以及使用场景==这3个方面来详细说明。
第一个是==定义==上的区别,
int 是 Java 的基本数据类型,直接存储数值,占用固定的 4 字节内存空间,范围是从 -2,147,483,648 到 2,147,483,647。
而 Integer 是 int 的包装类,它是一个对象,通过引用指向存储的数值,因此除了存储数值本身外,还需要额外的内存开销。
第二个是==使用方式==上的区别,
int 是一种原始类型,可以直接声明和赋值。
而 Integer 必须实例化后才能使用,它提供了更多的功能,比如支持泛型、序列化、缓存以及一些实用方法。
第三个是==使用场景==上的区别,
当需要高效处理整数时,优先使用 int。
当需要将整数作为对象使用时,泛型场景,选择 Integer。
拓展:
1.为什么需要 int 和 Integer?
(1) 基本数据类型的需求
在计算机底层,整数是最基础的数据类型之一,直接映射到硬件层面的操作(如寄存器运算)。为了==高效处理整数运算==,Java 引入了基本数据类型 int,它直接存储数值,占用固定的 4 字节内存空间。
这种设计使得 int 非常高效,但它的==局限性也很明显:无法作为对象使用==,不能参与面向对象编程中的某些操作(如泛型、集合类等)。
(2) 面向对象的需求
Java 是一门面向对象的语言,很多场景需要将数据封装成对象。例如:泛型(Generics)要求参数必须是对象类型,而不能是基本数据类型。序列化(Serialization)需要对象支持,以便将数据持久化或通过网络传输。缓存机制需要对整数进行复用,以提高性能和节省内存。
因此,Java 设计了 Integer 作为 int 的包装类,解决了这些面向对象的需求。
5、什么是自动拆箱/装箱?(587/1759=33.4%)
自动拆箱和装箱是为了提高代码的简洁性,它简化了基本数据类型与对应的包装类之间的转换。接下来我会详细解释什么是自动装箱和自动拆箱,以及它们的注意事项。
首先说一下==自动装箱==,
自动装箱是指==将基本数据类型(如 int、double、boolean 等)自动转换为对应的包装类对象==(如 Integer、Double、Boolean 等)。这个过程由编译器自动完成,无需手动调用包装类的构造方法或静态方法。
当==存储一个基本数据类型到需要用到对象的场景中==(例如集合),Java 编译器会检测到基本数据类型需要被转换为包装类对象,编译器会自动调用包装类的 valueOf() 方法来创建对应的包装类对象,生成的对象会被存储到目标位置。
接下来说一下自动拆箱,
自动拆箱是指将包装类对象(如 Integer、Double、Boolean 等)自动转换为对应的基本数据类型(如 int、double、boolean 等)。同样,这个过程也是由编译器自动完成的。
当你从一个需要对象的场景中取出值并赋给基本数据类型时,Java 编译器会检测到目标变量是一个基本数据类型。编译器会自动调用包装类的 xxxValue() 方法,比如 intValue()、doubleValue() 等,来获取基本数据类型的值。返回的基本数据类型值会被赋给目标变量。
最后说一下注意事项,一共有3点需要注意
第一个是性能问题,频繁的自动装箱和拆箱可能会导致额外的性能开销,因为每次都需要创建或转换对象。
第二个是空指针异常,如果对一个 null 的包装类对象进行自动拆箱操作,会抛出 NullPointerException。
第三个是缓存机制,某些包装类(如 Integer、Boolean 等)会对==常用值进行缓存。==
拓展:
1.发展过程:从 JDK 1.0 到现代 Java
(1) JDK 1.0:引入 int 和 Integer
在 Java 的最初版本(JDK 1.0)中,int 和 Integer 被同时引入。int 用于高效处理整数。Integer 提供了基本的对象封装功能,但当时的功能相对有限。
(2) JDK 5:自动装箱/拆箱机制
在 JDK 5 中,Java 引入了 自动装箱(Autoboxing)和自动拆箱(Unboxing) 机制。
自动装箱:将 int 自动转换为 Integer,例如 Integer num = 100;。
自动拆箱:将 Integer 自动转换为 int,例如 int value = num;。
这一改进极大地简化了代码编写,减少了手动转换的繁琐。
(3) JDK 8:优化缓存机制
在 JDK 8 中,Integer 的缓存机制得到了进一步优化。
默认情况下,Integer 缓存范围是 -128 到 127,可以通过 JVM 参数调整缓存范围。
这一优化提高了性能,尤其是在频繁使用小整数的场景中。
(4) 现代 Java:更高效的使用方式
随着 Java 的不断发展,在性能敏感的场景下,优先使用 int。在需要面向对象功能的场景下,使用 Integer。同时,Java 的编译器和运行时环境也不断优化,使得 int 和 Integer 的切换更加高效。
更多推荐



所有评论(0)