1. 解释下什么是面向对象?面向对象和面向过程的区别?
面向对象(Object-Oriented,简称OO)是一种程序设计范式或编程范式,也是一种程序开发的方法。它将对象作为程序的基本单元,将程序和数据封装在对象中,以提高软件的可重用性、灵活性和扩展性。
在面向对象编程中,有以下几个核心概念:
- 对象:对象是数据和操作(方法)的封装体,是面向对象编程的核心。
- 类:类是对象的模板或蓝图,定义了对象应具有的数据和方法。
- 继承:一个类可以继承另一个类的属性和方法,这有助于实现代码的重用。
- 封装:将数据和方法隐藏在对象内部,只提供必要的接口与外部交互,保证了数据的安全性和完整性。
- 多态:不同的对象对同一消息做出不同的响应,这提高了代码的灵活性和扩展性。
面向对象与面向过程的主要区别体现在以下几个方面:
- 关注点不同:
- 面向过程关注的是过程,即解决问题的步骤。它分解问题为一系列函数或过程,然后依次调用这些函数或过程来解决问题。
- 面向对象关注的是对象,它将问题分解为一系列对象,并通过这些对象之间的交互来解决问题。
- 代码组织方式不同:
- 面向过程的代码通常是一系列函数的集合,这些函数按照特定的顺序被调用以完成任务。
- 面向对象的代码则是通过类来组织,每个类定义了一组对象的行为和状态,而对象之间的交互则通过消息传递来实现。
- 可扩展性和重用性不同:
- 面向过程的代码通常较难扩展和重用,因为函数之间的依赖关系可能很复杂,修改一个函数可能会影响到其他多个函数。
- 面向对象的代码则通过继承、封装和多态等机制,使得代码更易于扩展和重用。子类可以继承父类的属性和方法,封装可以隐藏对象的内部状态,多态则使得对象可以根据其类型对同一消息做出不同的响应。
- 思维方式不同:
- 面向过程更接近于人类的自然思维方式,即按照步骤逐步解决问题。
- 面向对象则更接近于人类认识世界的方式,即通过识别和理解对象以及它们之间的关系来解决问题。
2. 面向对象的三大特性?分别解释下?
面向对象的三大特性是:封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。这三大特性是面向对象编程(OOP)的基石,它们共同增强了代码的可重用性、可维护性和可扩展性。下面我将分别解释这三个特性:
封装(Encapsulation)
封装是隐藏对象的属性和实现细节,仅对外公开接口(方法)与对象进行交互的一种机制。封装的主要目的是保护数据不被随意修改,同时简化编程。通过封装,可以将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类提供的方法进行操作。这样可以保证数据的安全性和完整性,防止因外部程序的错误操作而破坏数据。
封装的好处包括:
- 数据隐藏:防止外部代码直接访问或修改对象的内部状态。
- 提高安全性:通过限制对数据的访问,可以减少错误并防止恶意访问。
- 简化接口:只暴露必要的操作,使代码更易于理解和使用。
继承(Inheritance)
继承是一种代码复用机制,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。子类可以拥有父类的所有属性和方法,并且还可以添加或覆盖自己的属性和方法。这样,子类就可以利用父类已有的代码,无需重新编写相同的代码,从而提高了代码的重用性。
继承的好处包括:
- 代码重用:减少重复代码,提高开发效率。
- 扩展性:子类可以基于父类进行扩展,添加新的功能。
- 维护性:如果父类的代码发生变化,所有子类都会自动继承这些变化。
多态(Polymorphism)
多态是指允许一个接口被多种类型对象实现,或者在运行时确定对象实际类型并执行相应的方法。多态性语言具有灵活、抽象、行为共享、代码共享的优势,可以很好地解决应用程序函数名称冲突的问题。
多态的实现方式主要包括接口实现和继承类的重写(Override)。当子类继承了父类并重写了父类的方法时,子类对象就可以以父类对象的身份出现,并执行自己的方法。这种机制允许我们编写更加通用和灵活的代码。
多态的好处包括:
- 灵活性:可以在运行时确定对象的行为,而无需提前知道其确切类型。
- 可扩展性:可以轻松添加新的子类,而无需修改现有代码。
- 代码重用:通过多态,可以利用已有的代码来处理不同的对象类型。
封装、继承和多态共同构成了面向对象编程的三大特性,它们使得代码更加模块化、可重用和易于维护。通过合理运用这些特性,可以编写出更加健壮、高效和可扩展的程序。
3. JDK、JRE、JVM 三者之间的关系?
JDK(Java Development Kit)是Java开发工具包,主要用于Java程序的开发。它包含了Java编译器、解释器、文档生成工具、打包工具等开发工具,以及JRE(Java Runtime Environment)。JDK的主要目标是提供开发者在编写、测试和调试Java应用程序时所需的一切。
JRE是Java运行时环境,它包含了运行Java应用程序所必需的所有组件,如JVM(Java Virtual Machine)、Java核心类库以及Java应用程序启动器等。JRE并不包含任何开发工具,如编译器或调试器,因此它主要用于运行已经编译好的Java程序,而不是开发新的Java程序。
JVM是Java虚拟机的缩写,是JRE的一个重要组成部分。JVM是一个虚构出来的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现Java程序的跨平台运行。Java程序在编译后会被转换成字节码,然后由JVM进行解释和执行。JVM还负责内存管理、垃圾回收等关键任务,确保Java程序的正常运行。
JDK包含了JRE和用于开发Java程序的各种工具,而JRE则包含了JVM和Java核心类库,用于运行Java程序。JVM则是JRE的一部分,负责解释和执行Java字节码,实现Java程序的跨平台运行。这三者共同构成了Java开发与运行环境的基础,为Java程序的开发和运行提供了强大的支持。
4. 重载和重写的区别?
重载(Overloading)
重载是发生在同一个类中,方法名相同但参数列表(包括参数类型、参数顺序或参数个数)不同的方法之间的现象。重载是编译时多态。
例如:
public class OverloadExample {
void show(int a) {
System.out.println("show with int parameter");
}
void show(double a) {
System.out.println("show with double parameter");
}
}
上面的例子中,show
方法被重载了两次,一次接受 int
类型的参数,另一次接受 double
类型的参数。
重写(Overriding)
重写是子类对父类中的方法进行重新定义,方法名和参数列表必须与父类中被重写的方法相同,返回类型也必须相同或者是父类返回类型的子类型(协变返回类型)。重写是运行时多态。
例如:
class Animal {
void makeSound() {
System.out.println("The animal makes a sound");
}
}
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("The dog barks");
}
}
上面的例子中,Dog
类重写了 Animal
类中的 makeSound
方法。当我们创建一个 Dog
对象并调用 makeSound
方法时,将执行 Dog
类中的 makeSound
方法,而不是 Animal
类中的方法。
重载与重写的区别
- 发生位置:重载发生在同一个类中,而重写发生在子类和父类之间。
- 方法名与参数:重载要求方法名相同但参数列表不同;重写要求方法名和参数列表都相同。
- 返回类型:重载对返回类型没有特殊要求;重写要求返回类型必须与父类中被重写的方法的返回类型相同或者是其子类型。
- 访问修饰符:重载对访问修饰符没有特殊要求;重写要求子类方法的访问权限不能低于父类方法的访问权限(例如,如果父类方法是
public
的,那么子类方法也必须是public
的)。 - 多态性:重载是编译时多态,即编译器在编译时根据方法参数列表确定调用哪个方法;重写是运行时多态,即程序在运行时根据对象的实际类型确定调用哪个方法。
5. Java 中是否可以重写一个 private 或者 static 方法?
在Java中,不能重写(Override)一个private
或static
方法。这是因为重写的一个基本前提是方法必须在父类和子类中都是可见的,而private
方法仅在声明它的类内部可见,因此无法被继承或重写。
对于static
方法,虽然它们可以在子类中重新声明,但这并不是重写,而是隐藏(或称为“遮蔽”,Hiding)。隐藏和重写是不同的概念。重写涉及到动态分派(运行时多态性),而隐藏则是静态分派(编译时确定)。当子类中的方法名与父类中的static
方法名相同时,子类的static
方法会隐藏父类的static
方法。这意味着当你使用子类引用调用该方法时,将执行子类中的方法,而不是父类中的方法。但这与多态性无关,只是简单地基于方法所在的类来调用方法。
因此,如果你想在子类中改变某个方法的行为,并确保这种行为变化在通过子类对象调用该方法时生效(即实现多态性),那么该方法在父类中不能是private
或static
的。它必须是public
的(或至少是protected
的,以便在同一包中的其他类可以访问它)。
6. 构造方法有哪些特性?
Java构造方法具有以下几个特性:
- 名称与类名相同:构造方法的名称必须与定义它的类的名称完全相同,包括大小写。这一特性确保了当我们创建类的实例时,能够明确知道要调用哪个构造方法。
- 无返回类型:构造方法没有返回类型,包括void类型。它们的目的是初始化新创建的对象。
- 自动调用:构造方法在类被实例化时自动调用,用于初始化对象的实例变量。如果没有显式地定义构造方法,编译器会自动生成一个默认的无参构造方法。但是,如果显式地定义了一个或多个构造方法,编译器将不再自动生成默认构造方法。
- 可以有参数:构造方法可以有参数,也可以无参数。无参数的构造方法称为默认构造方法。构造方法的参数用于为新创建的对象提供初始化值。
- 可以重载:构造方法可以重载,即可以有多个同名但参数列表不同的构造方法。这使得我们可以根据实际需要为类提供多个不同的初始化方式。
- 可以相互调用:通过使用关键字“this”来调用同一个类中的其他构造方法,以便重用代码。需要注意的是,一个构造方法调用另一个构造方法时,调用语句必须写在代码块的第一行。
- 默认访问修饰符:如果没有显式指定构造方法的访问修饰符,那么它的默认访问修饰符是public。但也可以显式地使用private、protected或public来修饰构造方法,以控制其在类中的可见性和可访问性。
Java构造方法的特性主要体现在其名称、返回类型、自动调用、参数、重载、相互调用以及访问修饰符等方面。这些特性使得构造方法成为Java面向对象编程中不可或缺的一部分,用于初始化对象并为其分配所需的资源。
7. 在 Java 中定义一个不做事且没有参数的构造方法有什么作用?
在Java中定义一个不做事且没有参数的构造方法(通常称为无参构造方法或默认构造方法)可以有几个作用:
- 自动调用:当创建类的实例时,如果没有显式地调用其他构造方法,Java会自动调用这个无参构造方法。如果类中没有定义任何构造方法,编译器会自动生成一个这样的无参构造方法。但是,一旦在类中定义了至少一个构造方法,编译器就不会再自动生成这个默认的无参构造方法。因此,明确地定义一个无参构造方法可以确保类的用户能够不带参数地创建类的实例。
- 初始化实例变量:虽然无参构造方法本身可能不执行任何操作,但它仍然可以用于初始化类的实例变量。这些初始化操作可以隐式地通过声明变量时进行,或者显式地在构造方法体内部进行。即使构造方法体为空,只要类的实例变量有默认值或已经通过声明进行了初始化,无参构造方法仍然可以确保这些变量在对象创建时被正确初始化。
- 提供扩展点:当子类继承父类时,子类可以通过调用
super()
来显式地调用父类的无参构造方法。这允许子类在初始化自身之前,先执行父类的一些初始化逻辑。如果父类没有定义无参构造方法,子类将无法调用它,这可能导致初始化逻辑的问题。因此,在父类中定义一个无参构造方法可以为子类提供一个清晰的扩展点。 - 代码清晰性和一致性:即使无参构造方法不执行任何操作,它的存在也可以使类的结构更加清晰和一致。它向类的用户表明,可以通过不带参数的方式创建类的实例,这对于API设计和文档来说是有益的。
需要注意的是,虽然无参构造方法可能看起来没有实际作用,但在某些情况下它可能是必要的。因此,在定义类时,考虑是否需要一个无参构造方法是一个好的编程实践。
8. Java 中创建对象的几种方式?
在Java中,创建对象的主要方式通常有以下几种:
- 使用new关键字:
这是最常见和最直接的方式。通过调用类的构造方法,可以创建一个新的对象实例。例如:
MyClass obj = new MyClass();
- 使用克隆(Clone)方法:
如果一个类实现了Cloneable
接口,并覆盖了Object
类中的clone()
方法,那么可以通过调用对象的clone()
方法来创建该对象的一个副本。注意,直接调用Object
类的clone()
方法会抛出CloneNotSupportedException
异常,因此必须正确处理。
MyClass obj1 = new MyClass();
MyClass obj2 = (MyClass) obj1.clone();
- 使用反序列化:
如果一个对象已经被序列化(即转换为字节流),那么可以通过反序列化这些字节来重新创建对象。这通常用于对象的持久化存储和网络传输。
// 假设已经有一个序列化的对象输出到某个文件
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file.txt"));
MyClass obj = (MyClass) ois.readObject();
ois.close();
- 使用工厂模式:
工厂模式是一种创建对象的设计模式,它封装了对象的创建逻辑。工厂方法返回一个类型为接口或抽象类的对象,客户端调用工厂方法而无需关心具体实现类。这有助于降低耦合度,提高代码的可维护性和可扩展性。
public interface MyInterface {
// ...
}
public class MyClass implements MyInterface {
// ...
}
public class MyFactory {
public static MyInterface createObject() {
return new MyClass();
}
}
// 使用工厂方法创建对象
MyInterface obj = MyFactory.createObject();
- 使用构建者模式(Builder Pattern):
构建者模式用于构建复杂对象的实例,允许用户通过不同的配置来创建对象,并且通常包含多个可选参数。
public class MyClassBuilder {
private int param1;
private String param2;
public MyClassBuilder setParam1(int param1) {
this.param1 = param1;
return this;
}
public MyClassBuilder setParam2(String param2) {
this.param2 = param2;
return this;
}
public MyClass build() {
return new MyClass(param1, param2);
}
}
// 使用构建者模式创建对象
MyClass obj = new MyClassBuilder().setParam1(10).setParam2("value").build();
- 使用依赖注入(Dependency Injection):
在大型应用或框架中,依赖注入框架(如Spring)负责创建和管理对象的生命周期。你可以通过配置文件或注解来指定如何创建和注入对象依赖。这种方式通常与工厂模式或构建者模式结合使用。
请注意,创建对象的具体方式可能会根据你的应用程序的架构和需求而有所不同。在实际开发中,选择最适合你当前场景的方式是非常重要的。
标签:面试题,Java,构造方法,对象,子类,基础,父类,方法 From: https://blog.csdn.net/jianing1018/article/details/136856296