第三天面试题
已经理解可以直接背每个问题的总结
文章目录
- 1.什么是方法重载以及规则?
- 方法重载(Method Overloading)
- 方法重载的规则
- 示例代码
- 注意事项
- 总结
- 2.形参和实参的区别?
- 形参(Formal Parameter)和实参(Actual Parameter)的区别
- 1. **定义位置**
- 2. **作用**
- 3. **使用方式**
- 4. **生命周期**
- 5. **示例代码**
- 6. **关键区别总结**
- 总结
- 3.值传递和引用传递的区别?
- 4.构造函数和成员方法的区别?
- 1. 值传递(Pass by Value)
- 2. 引用传递(Pass by Reference)
- 3. 特殊情况:按共享传递(Pass by Sharing)
- 总结
- 5.this关键字的理解?
- 1. **区分成员变量和局部变量**
- 2. **调用当前类的其他构造方法**
- 3. **返回当前对象**
- 4. **在内部类中访问外部类对象**
- 5. **作为参数传递**
- 总结
- 6.请分别阐述面向对象三大特征?
- 1. **封装(Encapsulation)**
- 核心思想:
- 优点:
- 示例:
- 2. **继承(Inheritance)**
- 核心思想:
- 优点:
- 示例:
- 3. **多态(Polymorphism)**
- 核心思想:
- 优点:
- 示例:
- 总结
- 7.继承的规则?
- 1. **单继承**
- 2. **访问控制**
- 3. **`super` 关键字**
- 4. **方法重写**
- 5. **构造方法**
- 6. **`final` 类和方法**
- 7. **`Object` 类**
- 8. **抽象类和接口**
- 9. **继承与初始化顺序**
- 总结
- 8.四种访问权限修饰符是什么?它们的访问范围分别是什么?
- 1. **`private`**
- 2. **`default`(包私有)**
- 3. **`protected`**
- 4. **`public`**
- 访问权限修饰符的总结
- 使用场景
- 9.什么是方法的重写以及重写的规则?
- 重写的规则
- 示例
- 总结
- 10.方法重写和方法重载的区别?
- 1. **定义**
- 2. **使用场景**
- 3. **规则**
- 4. **示例**
- 方法重写(Override)
- 方法重载(Overload)
- 5. **总结对比**
- 关键区别
- 11.super和this的区别?
- 1. `this` 关键字
- 2. `super` 关键字
- 总结
- 12.Java 多态的实现形式与表现在哪里?
- 1. 方法重载(Compile-time Polymorphism)
- 2. 方法重写(Runtime Polymorphism)
- 多态的表现
- 总结
- 13.static 能修饰哪些结构?修饰以后,有什么特点?
- 1. 静态变量(Static Variables)
- 2. 静态方法(Static Methods)
- 3. 静态代码块(Static Blocks)
- 4. 静态内部类(Static Nested Classes)
- 总结
- 14.静态方法和实例方法有何不同?
- 1. 定义和声明
- 2. 调用方式
- 3. 访问权限
- 4. 生命周期
- 5. 使用场景
- 示例代码
- 总结
- 15.final 可以用来修饰哪些结构,分别表示什么意思?
- 1. 修饰变量(Final Variables)
- 2. 修饰方法(Final Methods)
- 3. 修饰类(Final Classes)
- 总结
- 16.Java 中怎么创建一个不可变对象?
- 1. 使用 `final` 关键字
- 2. 将所有字段声明为 `private` 和 `final`
- 3. 不提供修改方法
- 4. 在构造函数中初始化所有字段
- 5. 如果字段是可变对象,返回其副本
- 示例代码
- 使用示例
- 总结
- 17.抽象类的意义?
- 1. **提供模板**
- 2. **代码重用**
- 3. **限制实例化**
- 4. **定义抽象方法**
- 5. **实现多态性**
- 6. **设计良好的架构**
- 示例
- 总结
- 18.abstract class 和 interface 的区别?
- 1. **定义方式**
- 2. **多继承**
- 3. **访问修饰符**
- 4. **字段**
- 5. **实现方式**
- 6. **用途**
- 示例
- 总结
- 19.浅拷贝和深拷贝的区别?
- 1. **定义**
- 2. **实现方式**
- 3. **内存管理**
- 4. **使用场景**
- 示例
- 总结
- 20.内部类的分类?
- 1. **成员内部类(Member Inner Class)**
- 2. **静态内部类(Static Nested Class)**
- 3. **局部内部类(Local Inner Class)**
- 4. **匿名内部类(Anonymous Inner Class)**
- 总结
1.什么是方法重载以及规则?
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数、参数类型、参数顺
序不同即可。
与返回值类型/修饰符/方法体/形参名称无关,在调用时,jvm 虚拟机通过参数列表的不
同来区分同名方法。
方法重载有分为: 成员方法重载 和 构造方法重载;
注:
重载存在于父类和子类,当前类中。
重载可以抛出不同的异常,可以有不同的修饰符
方法重载(Method Overloading)
方法重载是指在同一个类中定义多个同名方法,但这些方法的参数列表(参数类型、数量或顺序)不同。重载允许方法根据传入的参数执行不同的操作。
方法重载的规则
-
方法名必须相同:
- 重载的方法必须使用相同的方法名。
-
参数列表必须不同:
- 参数的数量、类型或顺序必须至少有一项不同。
- 仅返回值类型不同不足以构成重载。
-
返回值类型可以不同:
- 返回值类型不影响重载,但参数列表必须不同。
-
访问修饰符可以不同:
- 重载方法的访问修饰符(如
public
、private
)可以不同。
- 重载方法的访问修饰符(如
-
不能仅通过抛出异常不同来重载:
- 方法的异常列表不同不足以构成重载。
-
发生在同一个类中:
- 方法重载只能在同一个类中定义。
示例代码
public class Calculator {
// 方法1:两个整数相加
public int add(int a, int b) {
return a + b;
}
// 方法2:三个整数相加(参数数量不同)
public int add(int a, int b, int c) {
return a + b + c;
}
// 方法3:两个浮点数相加(参数类型不同)
public double add(double a, double b) {
return a + b;
}
// 方法4:整数和浮点数相加(参数顺序不同)
public double add(int a, double b) {
return a + b;
}
// 方法5:浮点数和整数相加(参数顺序不同)
public double add(double a, int b) {
return a + b;
}
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(2, 3)); // 调用方法1,输出: 5
System.out.println(calc.add(2, 3, 4)); // 调用方法2,输出: 9
System.out.println(calc.add(2.5, 3.5)); // 调用方法3,输出: 6.0
System.out.println(calc.add(2, 3.5)); // 调用方法4,输出: 5.5
System.out.println(calc.add(2.5, 3)); // 调用方法5,输出: 5.5
}
}
注意事项
-
不能通过返回值类型重载:
public int add(int a, int b) { return a + b; } public double add(int a, int b) { return a + b; } // 错误:参数列表相同,返回值不同
-
不能通过访问修饰符重载:
public int add(int a, int b) { return a + b; } private int add(int a, int b) { return a + b; } // 错误:参数列表相同
-
不能通过抛出异常重载:
public int add(int a, int b) throws IOException { return a + b; } public int add(int a, int b) throws SQLException { return a + b; } // 错误:参数列表相同
总结
方法重载通过参数列表的不同来实现同名方法的多样化调用,增强了代码的灵活性和可读性。重载的关键是参数列表必须不同,而返回值类型、访问修饰符和异常列表不影响重载。
2.形参和实参的区别?
形参:形式参数,定义在方法的声明,相当于变量的声明。
实参:实际参数,定义在方法的调用,相当于给形参赋值
形参(Formal Parameter)和实参(Actual Parameter)的区别
形参和实参是方法调用中的两个重要概念,它们的区别主要体现在定义位置、作用和使用方式上。
1. 定义位置
-
形参:定义在方法的声明中,是方法签名的一部分。
- 形参是方法定义时使用的占位符,用于接收调用时传入的值。
- 例如:
public void printSum(int a, int b) { // a 和 b 是形参 System.out.println(a + b); }
-
实参:在方法调用时传递给方法的具体值或变量。
- 实参是调用方法时实际传入的值。
- 例如:
printSum(5, 10); // 5 和 10 是实参
2. 作用
-
形参:
- 形参的作用是定义方法需要接收的参数类型和数量。
- 形参是局部变量,只在方法内部有效。
- 形参的值由实参传递而来。
-
实参:
- 实参的作用是为形参提供具体的值。
- 实参可以是常量、变量、表达式或方法的返回值。
3. 使用方式
-
形参:
- 形参在方法内部作为变量使用,可以参与运算或操作。
- 例如:
public void printSum(int a, int b) { int sum = a + b; // a 和 b 是形参,参与运算 System.out.println(sum); }
-
实参:
- 实参在方法调用时传递给形参。
- 例如:
int x = 5; int y = 10; printSum(x, y); // x 和 y 是实参,传递给形参 a 和 b
4. 生命周期
-
形参:
- 形参的生命周期仅限于方法的执行期间。
- 方法调用结束后,形参被销毁。
-
实参:
- 实参的生命周期取决于其定义位置。
- 如果实参是局部变量,则其生命周期与所在代码块一致;如果是全局变量,则生命周期更长。
5. 示例代码
public class Main {
// 方法定义:a 和 b 是形参
public static void printSum(int a, int b) {
System.out.println("Sum: " + (a + b));
}
public static void main(String[] args) {
int x = 5;
int y = 10;
// 方法调用:x 和 y 是实参
printSum(x, y); // 输出: Sum: 15
}
}
6. 关键区别总结
特性 | 形参 | 实参 |
---|---|---|
定义位置 | 方法声明中 | 方法调用中 |
作用 | 定义方法需要接收的参数 | 为形参提供具体的值 |
使用方式 | 方法内部作为变量使用 | 方法调用时传递给形参 |
生命周期 | 方法执行期间 | 取决于实参的定义位置 |
示例 | void printSum(int a, int b) | printSum(5, 10) |
总结
- 形参是方法定义时的占位符,用于接收调用时传入的值。
- 实参是调用方法时实际传递的值。
- 形参和实参通过方法调用建立联系,实参的值传递给形参,从而在方法内部使用。
3.值传递和引用传递的区别?
参数是基本数据类型时,将实参基本数据类型变量的“数据值”副本传递给形参,简称
值传递。因此,就算是改变了对象副本,也不会影响源对象的值。
参数是引用数据类型时,将实参引用数据类型变量的“地址值”(对象的引用)传递给形
参,简称引用传递。因此,对引用对象所做的改变会反映到所有的对象上
4.构造函数和成员方法的区别?
1、构造函数名要和类名一致,成员方法是自定义名称,最好是动词;
2、构造函数不需要定义返回值类型,成员方法必须定义返回值类型;
3、构造函数是创建对象以及完成对对象成员的初始化工作,成员方法代表对象的某一个
行为;
4、构造函数是当使用 new 关键字的时候,由 jvm 来调用; 成员方法是由实例化出来的对
象来调用;
在编程中,值传递和引用传递是两种不同的参数传递方式,主要区别在于如何处理传递给函数或方法的参数。
1. 值传递(Pass by Value)
- 定义:将实际参数的值复制一份,传递给函数或方法。函数内部操作的是这个副本,不会影响原始数据。
- 特点:
- 函数内部对参数的修改不会影响原始变量。
- 适用于基本数据类型(如整数、浮点数、字符等)。
- 示例:
输出:def modify_value(x): x = x + 10 print("函数内修改后的值:", x) num = 5 modify_value(num) print("函数外原始值:", num)
说明:函数内修改后的值: 15 函数外原始值: 5
num
的值在函数外未改变。
2. 引用传递(Pass by Reference)
- 定义:将实际参数的引用(内存地址)传递给函数或方法。函数内部操作的是原始数据。
- 特点:
- 函数内部对参数的修改会影响原始变量。
- 适用于复杂数据类型(如数组、对象、列表等)。
- 示例:
输出:def modify_list(lst): lst.append(4) print("函数内修改后的列表:", lst) my_list = [1, 2, 3] modify_list(my_list) print("函数外原始列表:", my_list)
说明:函数内修改后的列表: [1, 2, 3, 4] 函数外原始列表: [1, 2, 3, 4]
my_list
在函数外被修改。
3. 特殊情况:按共享传递(Pass by Sharing)
- 一些语言(如Python、Java)采用按共享传递,即传递的是对象的引用副本,函数内部可以修改对象内容,但不能重新赋值。
- 示例:
输出:def reassign(lst): lst = [4, 5, 6] print("函数内重新赋值后的列表:", lst) my_list = [1, 2, 3] reassign(my_list) print("函数外原始列表:", my_list)
说明:重新赋值不影响原始列表。函数内重新赋值后的列表: [4, 5, 6] 函数外原始列表: [1, 2, 3]
总结
特性 | 值传递 | 引用传递 | 按共享传递 |
---|---|---|---|
传递内容 | 值的副本 | 内存地址 | 引用的副本 |
修改影响 | 不影响原始数据 | 影响原始数据 | 修改内容影响,重新赋值不影响 |
适用数据类型 | 基本数据类型 | 复杂数据类型 | 复杂数据类型 |
典型语言 | C(基本类型)、Java(基本类型) | C++(引用)、C#(ref) | Python、JavaScript |
理解这些概念有助于编写高效、正确的代码。
5.this关键字的理解?
在 Java 中,this 关键字比较难理解,它的作用和其词义很接近。
它在方法内部使用,即这个方法所属对象的引用;
它在构造器内部使用,表示该构造器正在初始化的对象。
当在方法内需要用到调用该方法的对象时,就用 this。
我们还可以用 this 来区分属性和局部变量。
this 可以调用成员变量、成员方法、构造器;
在Java中,this
是一个引用当前对象的关键字,主要用于以下几种场景:
1. 区分成员变量和局部变量
当成员变量与局部变量同名时,this
用于明确引用当前对象的成员变量。
public class Person {
private String name;
public void setName(String name) {
this.name = name; // 使用 this 区分成员变量和参数
}
}
2. 调用当前类的其他构造方法
在构造方法中,this()
可以调用当前类的其他构造方法,必须放在构造方法的第一行。
public class Person {
private String name;
private int age;
public Person() {
this("Unknown", 0); // 调用另一个构造方法
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
3. 返回当前对象
this
可以作为方法的返回值,返回当前对象。
public class Person {
private String name;
public Person setName(String name) {
this.name = name;
return this; // 返回当前对象
}
}
4. 在内部类中访问外部类对象
在内部类中,外部类名.this
用于访问外部类的当前对象。
public class Outer {
private String name = "Outer";
public class Inner {
public void printOuterName() {
System.out.println(Outer.this.name); // 访问外部类的成员
}
}
}
5. 作为参数传递
this
可以作为参数传递给其他方法或构造方法。
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public void printName() {
print(this); // 将当前对象作为参数传递
}
private void print(Person person) {
System.out.println(person.name);
}
}
总结
this
指代当前对象,常用于区分成员变量和局部变量、调用其他构造方法、返回当前对象、访问外部类对象或作为参数传递。- 在静态上下文中(如静态方法或静态代码块)不能使用
this
,因为静态成员不依赖于对象实例。
理解 this
的用法有助于编写更清晰、易维护的代码。
6.请分别阐述面向对象三大特征?
三大特性是:封装,继承,多态
所谓封装,就是隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而
提高系统的可扩展性、可维护性。
所谓继承,就是子类继承父类‘所有’的特征和行为,使得子类对象(实例)具有父类
的成员变量和方法,或类从父类继承方法,使得子类具有父类相同的行为。
多态是指不同的对象可以对同一消息做出不同的响应
面向对象编程(OOP)的三大核心特征是:封装、继承和多态。它们是面向对象设计的基石,帮助开发者构建模块化、可复用和易维护的代码。
1. 封装(Encapsulation)
封装是将数据(属性)和行为(方法)捆绑在一起,并隐藏内部实现细节,仅通过公开的接口与外界交互。
核心思想:
- 隐藏实现细节:只暴露必要的接口,内部实现对外部不可见。
- 保护数据:通过访问控制(如
private
、protected
)限制对数据的直接访问,防止数据被意外修改。
优点:
- 提高代码的安全性,防止外部直接修改内部数据。
- 降低耦合度,模块之间通过接口交互,内部变化不影响外部调用。
- 提高代码的可维护性和可读性。
示例:
public class Person {
// 私有属性,外部无法直接访问
private String name;
private int age;
// 公开的getter和setter方法,用于访问和修改属性
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age > 0) { // 数据校验
this.age = age;
}
}
}
2. 继承(Inheritance)
继承是指一个类(子类)可以基于另一个类(父类)创建,继承父类的属性和方法,并可以扩展或重写这些属性和方法。
核心思想:
- 代码复用:子类可以直接使用父类的功能,避免重复代码。
- 扩展功能:子类可以在父类的基础上添加新的属性和方法。
- 方法重写:子类可以重写父类的方法,实现多态。
优点:
- 提高代码的复用性。
- 支持层次化设计,便于扩展和维护。
示例:
// 父类
class Animal {
public void eat() {
System.out.println("Animal is eating.");
}
}
// 子类继承父类
class Dog extends Animal {
// 重写父类方法
@Override
public void eat() {
System.out.println("Dog is eating.");
}
// 扩展新方法
public void bark() {
System.out.println("Dog is barking.");
}
}
3. 多态(Polymorphism)
多态是指同一个行为(方法)在不同的对象中具有不同的实现方式。多态分为编译时多态(方法重载)和运行时多态(方法重写)。
核心思想:
- 同一接口,不同实现:通过父类引用调用子类对象的方法,具体执行哪个方法由对象的实际类型决定。
- 动态绑定:在运行时确定调用的具体方法。
优点:
- 提高代码的灵活性和扩展性。
- 支持面向接口编程,降低模块之间的耦合度。
示例:
// 父类
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound.");
}
}
// 子类1
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks.");
}
}
// 子类2
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat meows.");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Animal(); // 父类对象
Animal myDog = new Dog(); // 子类对象
Animal myCat = new Cat(); // 子类对象
myAnimal.makeSound(); // 输出: Animal makes a sound.
myDog.makeSound(); // 输出: Dog barks.
myCat.makeSound(); // 输出: Cat meows.
}
}
总结
特征 | 核心思想 | 优点 |
---|---|---|
封装 | 隐藏实现细节,保护数据,提供公开接口 | 提高安全性、降低耦合度、增强可维护性 |
继承 | 子类继承父类的属性和方法,支持代码复用和扩展 | 提高代码复用性、支持层次化设计 |
多态 | 同一接口在不同对象中有不同实现,支持动态绑定 | 提高灵活性、支持面向接口编程 |
这三大特征共同构成了面向对象编程的核心思想,帮助开发者设计出高内聚、低耦合的系统。
7.继承的规则?
1、一个类可以被多个子类继承。
2、Java 中类的单继承性:一个类只能有一个父类(直接父类)。
3、子父类是相对的概念。
4、子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类。
5、子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。
在Java中,继承是面向对象编程的重要特性,允许一个类(子类)继承另一个类(父类)的属性和方法。以下是Java继承的主要规则:
1. 单继承
- Java只支持单继承,即一个类只能有一个直接父类。
- 但可以通过接口实现多重继承。
class A {}
class B extends A {} // 合法
class C extends A, B {} // 非法
2. 访问控制
private
:子类无法直接访问父类的私有成员。protected
和public
:子类可以访问父类的这些成员。default
(包私有):同一包内的子类可以访问。
class Parent {
private int privateField;
protected int protectedField;
public int publicField;
}
class Child extends Parent {
void accessFields() {
// privateField = 10; // 非法
protectedField = 20; // 合法
publicField = 30; // 合法
}
}
3. super
关键字
- 用于调用父类的构造方法、属性或方法。
- 必须在子类构造方法的第一行使用。
class Parent {
Parent() {
System.out.println("Parent Constructor");
}
}
class Child extends Parent {
Child() {
super(); // 调用父类构造方法
System.out.println("Child Constructor");
}
}
4. 方法重写
- 子类可以重写父类的方法,方法签名必须一致。
- 重写方法的访问权限不能比父类更严格。
- 使用
@Override
注解确保正确重写。
class Parent {
void display() {
System.out.println("Parent Display");
}
}
class Child extends Parent {
@Override
void display() {
System.out.println("Child Display");
}
}
5. 构造方法
- 子类构造方法默认调用父类的无参构造方法。
- 如果父类没有无参构造方法,子类必须显式调用父类的有参构造方法。
class Parent {
Parent(int x) {
System.out.println("Parent Constructor with arg");
}
}
class Child extends Parent {
Child() {
super(10); // 必须调用父类有参构造方法
System.out.println("Child Constructor");
}
}
6. final
类和方法
final
类:不能被继承。final
方法:不能被子类重写。
final class FinalClass {} // 不能被继承
class Parent {
final void finalMethod() {
System.out.println("Final Method");
}
}
class Child extends Parent {
// void finalMethod() {} // 非法
}
7. Object
类
- 所有类默认继承
Object
类,它是Java类层次结构的根类。
class MyClass {} // 隐式继承 Object
8. 抽象类和接口
- 抽象类:可以包含抽象方法和具体方法,不能实例化。
- 接口:定义方法签名,类可以实现多个接口。
abstract class AbstractClass {
abstract void abstractMethod();
}
interface MyInterface {
void interfaceMethod();
}
class MyClass extends AbstractClass implements MyInterface {
void abstractMethod() {}
public void interfaceMethod() {}
}
9. 继承与初始化顺序
- 父类静态代码块
- 子类静态代码块
- 父类实例代码块和构造方法
- 子类实例代码块和构造方法
class Parent {
static { System.out.println("Parent Static Block"); }
{ System.out.println("Parent Instance Block"); }
Parent() { System.out.println("Parent Constructor"); }
}
class Child extends Parent {
static { System.out.println("Child Static Block"); }
{ System.out.println("Child Instance Block"); }
Child() { System.out.println("Child Constructor"); }
}
public class Main {
public static void main(String[] args) {
new Child();
}
}
总结
- Java支持单继承,通过接口实现多重继承。
- 子类可以访问父类的非私有成员。
- 使用
super
调用父类构造方法或成员。 - 方法重写时,签名必须一致,访问权限不能更严格。
- 构造方法默认调用父类无参构造方法。
final
类和方法不能被继承或重写。- 所有类默认继承
Object
类。 - 抽象类和接口用于定义规范和实现多重继承。
- 类初始化的顺序遵循特定规则。
这些规则帮助开发者正确使用继承,构建可维护的代码。
8.四种访问权限修饰符是什么?它们的访问范围分别是什么?
Java 的四种访问权限修饰符 public、protected、缺省、 private;
public:同一个类、同一包中、不同包中的子类、不同包中的非子类都可以访问。
protected:同一个类、同一包中、不同包中的子类可以访问。
缺省:同一个类、同一包中可以访问。
private:只能在当前类中访问
在Java中,有四种访问权限修饰符,用于控制类、方法、变量和构造方法的访问范围。它们的访问范围从最严格到最宽松依次为:
1. private
- 访问范围:仅在定义它的类内部可见。
- 特点:
- 不能从类外部访问,包括子类。
- 通常用于封装类的内部实现细节。
- 示例:
class MyClass { private int privateField = 10; private void privateMethod() { System.out.println("Private Method"); } } public class Main { public static void main(String[] args) { MyClass obj = new MyClass(); // obj.privateField; // 非法,无法访问 // obj.privateMethod(); // 非法,无法访问 } }
2. default
(包私有)
- 访问范围:同一包内的类可以访问。
- 特点:
- 如果不显式指定访问修饰符,默认为
default
。 - 不同包中的类无法访问。
- 如果不显式指定访问修饰符,默认为
- 示例:
package com.example; class MyClass { int defaultField = 20; // 默认访问修饰符 void defaultMethod() { System.out.println("Default Method"); } } class AnotherClass { void accessDefault() { MyClass obj = new MyClass(); System.out.println(obj.defaultField); // 合法 obj.defaultMethod(); // 合法 } }
3. protected
- 访问范围:
- 同一包内的类可以访问。
- 不同包中的子类可以访问。
- 特点:
- 通常用于允许子类继承和访问父类的成员。
- 示例:
package com.example; public class Parent { protected int protectedField = 30; protected void protectedMethod() { System.out.println("Protected Method"); } } package com.another; import com.example.Parent; class Child extends Parent { void accessProtected() { System.out.println(protectedField); // 合法 protectedMethod(); // 合法 } }
4. public
- 访问范围:所有类都可以访问。
- 特点:
- 最宽松的访问权限。
- 通常用于对外暴露的接口或常量。
- 示例:
package com.example; public class MyClass { public int publicField = 40; public void publicMethod() { System.out.println("Public Method"); } } package com.another; import com.example.MyClass; public class Main { public static void main(String[] args) { MyClass obj = new MyClass(); System.out.println(obj.publicField); // 合法 obj.publicMethod(); // 合法 } }
访问权限修饰符的总结
修饰符 | 类内部 | 同一包内 | 不同包的子类 | 不同包的非子类 |
---|---|---|---|---|
private | ✔️ | ❌ | ❌ | ❌ |
default | ✔️ | ✔️ | ❌ | ❌ |
protected | ✔️ | ✔️ | ✔️ | ❌ |
public | ✔️ | ✔️ | ✔️ | ✔️ |
使用场景
private
:封装类的内部实现细节。default
:包内共享,但不对外暴露。protected
:允许子类继承和扩展。public
:对外暴露接口或常量。
合理使用这些修饰符可以提高代码的安全性和可维护性。
9.什么是方法的重写以及重写的规则?
从父类继承过来的方法,不满足子类的需求,那么就需要对继承而来的方法进行改造,
也称为方法的重写、覆盖。
重写的规则:
1、子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表;
2、子类重写的方法的返回值类型不能大于父类被重写的方法的返回值类型,是基本数据类型或void返回值类型必须保持一致;
3、子类重写的方法使用的访问权限不能小于父类被重写的方法的访问权限;
4、子类方法抛出的异常不能大于父类被重写方法的异常;
5、声明为 final 的方法不能被重写;声明为 static 的方法不存在重写;
在Java中,方法的重写(Override) 是指子类重新定义父类中已有的方法。重写允许子类根据自身需求提供特定实现,同时保持方法名称、参数列表和返回类型不变。
重写的规则
-
方法签名一致:
- 子类方法的方法名、参数列表必须与父类方法完全相同。
-
返回类型:
- 返回类型必须与父类方法一致,或者是其子类(协变返回类型)。
-
访问权限:
- 子类方法的访问权限不能比父类方法更严格。例如,父类方法是
protected
,子类方法可以是protected
或public
,但不能是private
。
- 子类方法的访问权限不能比父类方法更严格。例如,父类方法是
-
异常声明:
- 子类方法抛出的异常不能比父类方法更宽。子类可以不抛出异常,或抛出父类方法异常的子类,但不能抛出新的或更宽泛的检查异常。
-
非静态方法:
- 只能重写父类的非静态方法,不能重写静态方法(静态方法属于类,不属于实例)。
-
final方法:
- 不能重写被
final
修饰的方法。
- 不能重写被
-
super关键字:
- 子类可以通过
super
调用父类的被重写方法。
- 子类可以通过
示例
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog();
myDog.makeSound(); // 输出: Dog barks
}
}
在这个例子中,Dog
类重写了Animal
类的makeSound
方法,调用时会执行Dog
类的实现。
总结
- 重写允许子类提供特定实现。
- 必须遵循方法签名、返回类型、访问权限等规则。
- 使用
@Override
注解有助于编译器检查重写是否正确。
10.方法重写和方法重载的区别?
重载和重写的区别如下
1.定义不同---重载是定义相同的方法名,参数不同;重写是子类重写父类的方法。
2.范围不同---重载是在一个类中,重写是子类与父类之间的。
3.多态不同---重载是编译时的多态性,重写是运行时的多态性。
4.返回不同---重载对返回类型没有要求,而重写要求返回类型,有兼容的返回类型。
5.参数不同---重载的参数个数、参数类型、参数顺序可以不同,而重写父子方法参数必须相同。
6.修饰不同---重载对访问修饰没有特殊要求,重写访问修饰符的限制一定要大于被重写方法的访问修饰符。
方法重写(Override) 和 方法重载(Overload) 是 Java 中两个不同的概念,它们的主要区别在于定义、使用场景和规则。以下是它们的详细对比:
1. 定义
-
方法重写(Override):
- 子类重新定义父类中已有的方法。
- 方法名、参数列表和返回类型必须与父类方法一致。
- 用于实现多态性。
-
方法重载(Overload):
- 在同一个类中定义多个方法,方法名相同但参数列表不同(参数类型、数量或顺序)。
- 返回类型可以不同,但仅返回类型不同不足以构成重载。
- 用于提供方法的不同版本,适应不同的输入。
2. 使用场景
-
方法重写:
- 用于子类扩展或修改父类的行为。
- 常见于继承关系中。
-
方法重载:
- 用于同一个类中提供多个功能相似但参数不同的方法。
- 常见于工具类或功能扩展。
3. 规则
特性 | 方法重写(Override) | 方法重载(Overload) |
---|---|---|
方法名 | 必须相同 | 必须相同 |
参数列表 | 必须相同 | 必须不同(类型、数量或顺序) |
返回类型 | 必须相同或为子类(协变返回类型) | 可以不同 |
访问权限 | 不能比父类方法更严格 | 可以任意 |
异常声明 | 不能抛出更宽泛的检查异常 | 可以不同 |
所属范围 | 子类和父类之间 | 同一个类中 |
静态方法 | 不能重写静态方法 | 可以重载静态方法 |
final 方法 | 不能重写 final 方法 | 可以重载 final 方法 |
注解支持 | 通常使用 @Override 注解 | 不需要特定注解 |
4. 示例
方法重写(Override)
class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks");
}
}
Dog
类重写了Animal
类的makeSound
方法。
方法重载(Overload)
class Calculator {
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
Calculator
类中有多个add
方法,参数列表不同,构成重载。
5. 总结对比
特性 | 方法重写(Override) | 方法重载(Overload) |
---|---|---|
目的 | 子类修改或扩展父类行为 | 提供方法的不同版本 |
方法签名 | 必须完全相同 | 方法名相同,参数列表不同 |
多态性 | 支持运行时多态 | 不支持多态 |
继承关系 | 需要继承关系 | 不需要继承关系 |
静态方法 | 不能重写 | 可以重载 |
关键区别
- 方法重写 是子类对父类方法的重新实现,用于多态。
- 方法重载 是同一个类中方法名的复用,用于提供不同参数的方法版本。
11.super和this的区别?
this和super都可以调用类中的属性、方法、构造方法,但是this调用的是本类操作,而super是由子类调用父类操
作。
在 Java 中,super
和 this
是两个关键字,它们用于引用类的不同部分。以下是它们的主要区别:
1. this
关键字
- 指向当前对象:
this
关键字用于引用当前对象的实例变量和方法。它可以帮助区分实例变量和参数变量,尤其是在构造函数中。 - 访问实例变量和方法:可以用
this
来调用当前对象的实例方法或访问实例变量。 - 构造函数调用:可以在构造函数中使用
this()
来调用同一个类的其他构造函数。
示例:
class Example {
private int value;
public Example(int value) {
this.value = value; // 使用 this 来区分实例变量和参数
}
public void display() {
System.out.println("Value: " + this.value); // 使用 this 访问实例变量
}
}
2. super
关键字
- 指向父类对象:
super
关键字用于引用父类的实例变量和方法。它可以用于访问被子类隐藏的父类成员。 - 访问父类构造函数:可以在子类的构造函数中使用
super()
来调用父类的构造函数。 - 访问父类方法:可以使用
super
来调用父类的方法,尤其是在子类重写了父类的方法时。
示例:
class Parent {
public void display() {
System.out.println("Parent class display method.");
}
}
class Child extends Parent {
public void display() {
super.display(); // 调用父类的 display 方法
System.out.println("Child class display method.");
}
}
总结
this
用于引用当前对象的实例变量和方法,主要用于当前类的上下文。super
用于引用父类的实例变量和方法,主要用于父类的上下文。
这两个关键字在类的继承和对象的上下文中起着重要的作用,帮助开发者更好地管理和访问类的成员。
12.Java 多态的实现形式与表现在哪里?
实现形式:
1、继承类
2、实现接口
多态表现:
多态要有动态绑定,否则就不是多态,方法重载也不是多态(因为方法重载是编译期决
定好的,没有后期也就是运行期的动态绑定)当满足这三个条件:1、有继承 2、有重写 3、
要有父类引用指向子类对象
Java 中的多态是面向对象编程的一个重要特性,它允许对象以多种形式出现。多态的实现形式主要有两种:方法重载(Compile-time Polymorphism)和方法重写(Runtime Polymorphism)。以下是这两种形式的详细说明:
1. 方法重载(Compile-time Polymorphism)
方法重载是指在同一个类中,可以定义多个同名但参数列表不同的方法。编译器根据方法的参数类型和数量来决定调用哪个方法。这种多态在编译时就能确定。
示例:
class MathUtils {
// 方法重载
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
}
public class Main {
public static void main(String[] args) {
MathUtils math = new MathUtils();
System.out.println(math.add(5, 10)); // 调用 int add(int, int)
System.out.println(math.add(5.5, 10.5)); // 调用 double add(double, double)
System.out.println(math.add(1, 2, 3)); // 调用 int add(int, int, int)
}
}
2. 方法重写(Runtime Polymorphism)
方法重写是指子类可以重写父类的方法,以提供特定的实现。运行时多态是通过引用类型的不同来实现的,具体调用哪个方法是在运行时决定的。通常通过父类引用指向子类对象来实现。
示例:
class Animal {
public void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
}
class Cat extends Animal {
@Override
public void sound() {
System.out.println("Cat meows");
}
}
public class Main {
public static void main(String[] args) {
Animal myDog = new Dog(); // 父类引用指向子类对象
Animal myCat = new Cat(); // 父类引用指向子类对象
myDog.sound(); // 输出: Dog barks
myCat.sound(); // 输出: Cat meows
}
}
多态的表现
- 灵活性:多态使得代码更加灵活,能够处理不同类型的对象而不需要知道具体的类型。
- 可扩展性:通过多态,可以轻松地添加新的子类而不需要修改现有代码。
- 代码重用:可以通过父类引用来调用子类的方法,减少代码重复。
总结
Java 中的多态通过方法重载和方法重写两种形式实现,分别在编译时和运行时表现出不同的特性。多态是实现代码灵活性和可扩展性的关键机制。
13.static 能修饰哪些结构?修饰以后,有什么特点?
在 Java 类中,可用 static 修饰属性、方法、代码块、内部类。
被修饰后的成员具备以下特点:
static 修饰变量,我们称之为静态变量。静态变量属于类,在内存中只有一个副本(所有实例都指向同一个内存地址)。只要静态变量所在的类被加载,这个静态变量就会被分配空间。
static 修饰方法,我们称之为静态方法。随着类的加载而加载,优先于对象存在。static 方法中不能使用 this 和 super 关键字,不能调用非 static 方法,只能访问所属类的静态成员变量和成员方法。
static 修饰代码块,我们称之为静态代码块。static 代码块在类中是独立与成员变量和成员函数的代码块,他不在任何一个方法体内,JVM 在加载类的时候会执行static 代码块,如果有多个 static 代码块,JVM 将会按顺序来执行,static 代码块经常会被用来初始化静态变量,需要注意的是 static 代码块只会被执行一次。
static 也可以修饰类,但是只能修饰内部类。
在 Java 中,static
关键字可以修饰以下几种结构:
1. 静态变量(Static Variables)
- 定义:静态变量是属于类的变量,而不是属于某个特定的对象。所有对象共享同一个静态变量。
- 特点:
- 只会在内存中存在一份,节省内存。
- 可以通过类名直接访问,不需要创建对象。
- 在类加载时初始化,生命周期与类相同。
示例:
class Counter {
static int count = 0; // 静态变量
Counter() {
count++; // 每创建一个对象,count 增加
}
}
public class Main {
public static void main(String[] args) {
new Counter();
new Counter();
System.out.println(Counter.count); // 输出: 2
}
}
2. 静态方法(Static Methods)
- 定义:静态方法是属于类的方法,可以直接通过类名调用,而不需要创建对象。
- 特点:
- 不能访问非静态变量和非静态方法,因为它们需要对象的上下文。
- 可以访问静态变量和调用其他静态方法。
- 适用于不依赖于对象状态的方法。
示例:
class MathUtils {
static int add(int a, int b) { // 静态方法
return a + b;
}
}
public class Main {
public static void main(String[] args) {
int sum = MathUtils.add(5, 10); // 直接通过类名调用
System.out.println(sum); // 输出: 15
}
}
3. 静态代码块(Static Blocks)
- 定义:静态代码块是在类加载时执行的代码块,用于初始化静态变量。
- 特点:
- 只会执行一次,且在类加载时执行。
- 可以用于复杂的静态变量初始化。
示例:
class Example {
static int value;
static {
value = 10; // 静态代码块
System.out.println("Static block executed");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Example.value); // 输出: 10
}
}
4. 静态内部类(Static Nested Classes)
- 定义:静态内部类是定义在另一个类内部的类,并且用
static
修饰。 - 特点:
- 可以访问外部类的静态成员,但不能访问外部类的非静态成员。
- 可以独立于外部类的实例创建。
示例:
class Outer {
static int outerStaticVar = 5;
static class Inner { // 静态内部类
void display() {
System.out.println("Outer static variable: " + outerStaticVar);
}
}
}
public class Main {
public static void main(String[] args) {
Outer.Inner inner = new Outer.Inner();
inner.display(); // 输出: Outer static variable: 5
}
}
总结
static
关键字在 Java 中用于修饰变量、方法、代码块和内部类。使用 static
的结构具有共享性、独立于对象的特性,并且在类加载时初始化。
14.静态方法和实例方法有何不同?
静态方法和实例方法的区别主要体现在两个方面:
在外部调用静态方法时,可以使用"类名.方法名"的方式,也可以使用"对象名.方法名" 的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。
静态方法在访问本类的成员时,只允许访问静态成员(即静态成员变量和静态方法),
而不允许访问实例成员变量和实例方法;实例方法则无此限制。
静态方法和实例方法是 Java 中两种不同类型的方法,它们在定义、调用和访问方面有显著的区别。以下是它们的主要不同点:
1. 定义和声明
-
静态方法:
- 使用
static
关键字声明。 - 属于类本身,而不是类的实例。
- 使用
-
实例方法:
- 不使用
static
关键字声明。 - 属于类的实例(对象),需要通过对象来调用。
- 不使用
2. 调用方式
-
静态方法:
- 可以通过类名直接调用,不需要创建类的实例。
- 例如:
ClassName.staticMethod();
-
实例方法:
- 必须通过类的实例来调用。
- 例如:
ClassName obj = new ClassName(); obj.instanceMethod();
3. 访问权限
-
静态方法:
- 只能访问静态变量和调用其他静态方法。
- 不能直接访问实例变量和实例方法,因为它们需要对象的上下文。
-
实例方法:
- 可以访问类的所有成员,包括静态变量、静态方法、实例变量和实例方法。
- 可以使用
this
关键字来引用当前对象。
4. 生命周期
-
静态方法:
- 在类加载时被加载,生命周期与类相同。
- 只会存在一份,所有对象共享。
-
实例方法:
- 在创建对象时被加载,生命周期与对象相同。
- 每个对象都有自己的实例方法副本。
5. 使用场景
-
静态方法:
- 适用于不依赖于对象状态的方法,例如工具类方法(如
Math
类中的方法)。 - 常用于工厂方法、单例模式等。
- 适用于不依赖于对象状态的方法,例如工具类方法(如
-
实例方法:
- 适用于需要访问或修改对象状态的方法。
- 用于实现对象的行为和功能。
示例代码
class Example {
static int staticVar = 10; // 静态变量
int instanceVar = 20; // 实例变量
// 静态方法
static void staticMethod() {
System.out.println("Static method called");
System.out.println("Static variable: " + staticVar);
// 不能访问实例变量和实例方法
// System.out.println("Instance variable: " + instanceVar); // 错误
}
// 实例方法
void instanceMethod() {
System.out.println("Instance method called");
System.out.println("Instance variable: " + instanceVar);
System.out.println("Static variable: " + staticVar); // 可以访问
}
}
public class Main {
public static void main(String[] args) {
// 调用静态方法
Example.staticMethod(); // 输出: Static method called
// 创建对象并调用实例方法
Example obj = new Example();
obj.instanceMethod(); // 输出: Instance method called
}
}
总结
静态方法和实例方法在定义、调用、访问权限和使用场景上有明显的区别。静态方法适用于不依赖于对象状态的操作,而实例方法则用于需要访问或修改对象状态的操作。
15.final 可以用来修饰哪些结构,分别表示什么意思?
在 Java 中声明类、变量和方法时,可使用关键字 final 来修饰,表示“最终的”。
final 修饰的类不能被继承。
final 修饰的方法不能被子类重写,方法一旦被修饰,JVM 会尝试将其内联,以提高运
行效率。
final 修饰的变量不可以被改变,如果修饰引用,那么表示引用不可变,引用指向的内
容可变。final 修饰的变量我们称之为常量,会在编译阶段存入常量池中。
在 Java 中,final
关键字可以用来修饰以下几种结构,具体含义如下:
1. 修饰变量(Final Variables)
- 定义:当一个变量被声明为
final
时,它的值在初始化后不能被改变。 - 含义:
- 对于基本数据类型,
final
变量的值一旦赋值后就不能再修改。 - 对于引用数据类型,
final
变量的引用不能改变,但可以修改对象的内部状态。
- 对于基本数据类型,
示例:
final int x = 10; // 基本数据类型
// x = 20; // 错误,无法修改
final List<String> list = new ArrayList<>();
list.add("Hello"); // 可以修改对象的内部状态
// list = new ArrayList<>(); // 错误,无法改变引用
2. 修饰方法(Final Methods)
- 定义:当一个方法被声明为
final
时,表示该方法不能被子类重写(override)。 - 含义:
- 确保方法的实现不被改变,通常用于提供安全性和一致性。
示例:
class Parent {
final void display() { // final 方法
System.out.println("This is a final method.");
}
}
class Child extends Parent {
// void display() { // 错误,无法重写 final 方法
// System.out.println("Trying to override.");
// }
}
3. 修饰类(Final Classes)
- 定义:当一个类被声明为
final
时,表示该类不能被继承。 - 含义:
- 确保类的实现不被改变,通常用于安全性和设计目的。
示例:
final class FinalClass {
void show() {
System.out.println("This is a final class.");
}
}
// class SubClass extends FinalClass { // 错误,无法继承 final 类
// }
总结
final
变量:一旦赋值后不能再修改,确保常量性。final
方法:不能被子类重写,确保方法的实现不被改变。final
类:不能被继承,确保类的设计不被扩展。
使用 final
关键字可以提高代码的安全性和可维护性,防止意外的修改和不必要的继承。
16.Java 中怎么创建一个不可变对象?
不可变对象是实例,其状态在初始化后不会改变。 例如, String 是一个不可变的类,
一旦实例化,其值就永远不会改变。
1.对象的状态在构造函数之后都不能被修改,任何修改应该通过创建一个新对象来实现。
2.所有的对象属性应该都设置为 final。
3.类要设置为 final,确保不要继承 Class 修改了不变特性。
在 Java 中,创建一个不可变对象(Immutable Object)通常涉及以下几个步骤。不可变对象的特性是,一旦创建,其状态(属性值)就不能被改变。下面是创建不可变对象的常见方法:
1. 使用 final
关键字
将类声明为 final
,以防止其他类继承它。
2. 将所有字段声明为 private
和 final
这可以确保字段只能在构造函数中被赋值,并且不能被外部访问或修改。
3. 不提供修改方法
不要提供任何可以修改对象状态的方法(如 setter 方法)。
4. 在构造函数中初始化所有字段
确保所有字段在对象创建时被初始化。
5. 如果字段是可变对象,返回其副本
如果对象的字段是可变类型(如数组、集合等),则在构造函数中使用它们的副本,并在访问时返回副本,而不是直接返回原始对象。
示例代码
以下是一个不可变对象的示例:
public final class ImmutablePoint {
private final int x; // x 坐标
private final int y; // y 坐标
// 构造函数
public ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
// 获取 x 坐标
public int getX() {
return x;
}
// 获取 y 坐标
public int getY() {
return y;
}
// 如果需要,可以提供一个方法来返回一个新的对象
public ImmutablePoint move(int deltaX, int deltaY) {
return new ImmutablePoint(this.x + deltaX, this.y + deltaY);
}
}
使用示例
public class Main {
public static void main(String[] args) {
ImmutablePoint point = new ImmutablePoint(1, 2);
System.out.println("X: " + point.getX() + ", Y: " + point.getY());
// 尝试修改点的位置,实际上会创建一个新的对象
ImmutablePoint newPoint = point.move(3, 4);
System.out.println("New X: " + newPoint.getX() + ", New Y: " + newPoint.getY());
System.out.println("Original X: " + point.getX() + ", Original Y: " + point.getY());
}
}
总结
通过上述步骤,可以创建一个不可变对象。不可变对象在多线程环境中是安全的,因为它们的状态在创建后不会改变。这种设计模式在 Java 中被广泛使用,尤其是在需要保证数据一致性和安全性的场景中。
17.抽象类的意义?
1、为其他子类提供一个公共的类型。
2、封装子类中重复定义的内容。
3、定义抽象方法,子类虽然有不同的实现,但是定义时是一致的。
抽象类在 Java 和其他面向对象编程语言中具有重要的意义和用途。以下是抽象类的主要意义:
1. 提供模板
抽象类可以被视为一种模板,它定义了一组方法和属性,但不提供具体的实现。这使得子类可以继承这些方法,并提供具体的实现。这样可以确保所有子类遵循相同的接口和结构。
2. 代码重用
通过使用抽象类,可以在多个子类之间共享通用的代码。抽象类可以包含已实现的方法,这样子类就可以直接使用这些方法,而不必重复实现相同的逻辑。
3. 限制实例化
抽象类不能被直接实例化,这意味着它只能作为其他类的基类使用。这有助于确保只有具体的子类可以被实例化,从而避免了创建不完整或不合逻辑的对象。
4. 定义抽象方法
抽象类可以包含一个或多个抽象方法,这些方法没有实现,子类必须重写这些方法以提供具体的实现。这种机制强制子类实现特定的功能,确保接口的一致性。
5. 实现多态性
通过抽象类和抽象方法,可以实现多态性。不同的子类可以实现相同的抽象方法,允许客户端代码以统一的方式处理不同的对象。这使得代码更加灵活和可扩展。
6. 设计良好的架构
使用抽象类有助于设计良好的软件架构。它可以帮助开发者清晰地定义类之间的关系和层次结构,从而使代码更易于理解和维护。
示例
以下是一个简单的抽象类示例:
abstract class Animal {
// 抽象方法
abstract void sound();
// 已实现的方法
void sleep() {
System.out.println("Sleeping...");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Bark");
}
}
class Cat extends Animal {
@Override
void sound() {
System.out.println("Meow");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.sound(); // 输出: Bark
dog.sleep(); // 输出: Sleeping...
Animal cat = new Cat();
cat.sound(); // 输出: Meow
cat.sleep(); // 输出: Sleeping...
}
}
总结
抽象类在面向对象编程中是一个强大的工具,它提供了一个灵活的方式来定义和实现类之间的关系。通过使用抽象类,开发者可以创建更具可维护性、可扩展性和一致性的代码结构。
18.abstract class 和 interface 的区别?
1、抽象类是由 abstract 修饰的类,接口不是类。
2、抽象类可以定义任意访问权限的数据、抽象方法、普通成员方法; 接口只能定义公共的静态的常量、公共抽象方法;
3、对于子类而言,它只能继承一个抽象类,但是却可以实现多个接口。如果一个类实现了接口,它必须实现接口中声明的所有方法;但是类可以不实现抽象类声明的所有抽象方法,但是前提 该类必须也得声明为抽象类。
4、抽象层次不同。抽象类是对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。
5、跨域不同。抽象类所跨域的是具有相似特点的类,而接口却可以跨域不同的类。
6、设计层次不同。抽象类是自底向上抽象而来的,接口是自顶向下设计出来的。
7、抽象类和接口都不能被实例化
在 Java 中,abstract class
(抽象类)和 interface
(接口)都是用于定义类的蓝图,但它们之间有一些重要的区别。以下是它们的主要区别:
1. 定义方式
-
抽象类:可以包含抽象方法(没有实现)和具体方法(有实现)。可以有构造函数、字段和状态。
abstract class Animal { abstract void sound(); // 抽象方法 void sleep() { // 具体方法 System.out.println("Sleeping..."); } }
-
接口:只能包含抽象方法(Java 8 及以后的版本可以包含默认方法和静态方法)。接口不能有构造函数和状态(字段),但可以有常量(
public static final
)。interface Animal { void sound(); // 抽象方法 default void sleep() { // 默认方法 System.out.println("Sleeping..."); } }
2. 多继承
-
抽象类:一个类只能继承一个抽象类(单继承),但可以实现多个接口。
-
接口:一个类可以实现多个接口(多继承),这使得接口在设计时更灵活。
3. 访问修饰符
-
抽象类:可以使用各种访问修饰符(
public
,protected
,private
)来控制方法和字段的可见性。 -
接口:接口中的方法默认是
public
,字段默认是public static final
,不能使用其他访问修饰符。
4. 字段
-
抽象类:可以有实例字段(状态),可以是任何类型。
-
接口:只能有常量(
public static final
),不能有实例字段。
5. 实现方式
-
抽象类:子类使用
extends
关键字继承抽象类,并实现其抽象方法。 -
接口:类使用
implements
关键字实现接口,并提供接口中所有抽象方法的实现。
6. 用途
-
抽象类:适用于需要共享代码的类层次结构,适合于有共同特征的类。
-
接口:适用于定义一组功能或行为,适合于不同类之间的契约,强调行为而非状态。
示例
以下是一个简单的示例,展示了抽象类和接口的用法:
// 抽象类示例
abstract class Animal {
abstract void sound();
void sleep() {
System.out.println("Sleeping...");
}
}
// 接口示例
interface Swimmable {
void swim();
}
// 具体类实现抽象类和接口
class Dog extends Animal {
@Override
void sound() {
System.out.println("Bark");
}
}
class Fish extends Animal implements Swimmable {
@Override
void sound() {
System.out.println("Blub");
}
@Override
public void swim() {
System.out.println("Swimming...");
}
}
public class Main {
public static void main(String[] args) {
Animal dog = new Dog();
dog.sound(); // 输出: Bark
dog.sleep(); // 输出: Sleeping...
Animal fish = new Fish();
fish.sound(); // 输出: Blub
fish.sleep(); // 输出: Sleeping...
((Swimmable) fish).swim(); // 输出: Swimming...
}
}
总结
抽象类和接口在 Java 中各有其用途和特性。选择使用抽象类还是接口取决于具体的设计需求和场景。一般来说,如果需要共享代码和状态,使用抽象类;如果需要定义行为的契约,使用接口。
19.浅拷贝和深拷贝的区别?
共同点:对于数据类型是基本数据类型的成员变量,无论是深拷贝还是浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
区别点:对于数据类型是引用数据类型的成员变量,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。深拷贝对引用数据类型的成员变量的对象图中所有的对象都开辟了内存空间;
在 Java 中,浅拷贝(shallow copy)和深拷贝(deep copy)是两种对象复制的方式,它们之间的主要区别在于如何处理对象的引用和内部状态。以下是它们的详细区别:
1. 定义
-
浅拷贝:创建一个新对象,但不复制对象内部的引用对象。新对象的字段与原对象的字段指向同一内存地址,因此对引用对象的修改会影响到原对象。
-
深拷贝:创建一个新对象,并递归地复制原对象的所有字段,包括引用对象。新对象的字段与原对象的字段指向不同的内存地址,因此对引用对象的修改不会影响到原对象。
2. 实现方式
-
浅拷贝:可以通过
Object
类的clone()
方法实现,默认情况下,clone()
方法执行的是浅拷贝。class Person implements Cloneable { String name; int age; Address address; // 引用类型 @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); // 浅拷贝 } } class Address { String city; String country; }
-
深拷贝:需要手动实现,通常通过序列化或在
clone()
方法中显式地复制引用对象。class Person implements Cloneable { String name; int age; Address address; // 引用类型 @Override protected Object clone() throws CloneNotSupportedException { Person cloned = (Person) super.clone(); // 浅拷贝 cloned.address = (Address) address.clone(); // 深拷贝 return cloned; } } class Address implements Cloneable { String city; String country; @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); // 浅拷贝 } }
3. 内存管理
-
浅拷贝:新对象和原对象共享相同的引用对象,因此内存使用更少,但可能导致意外的副作用。
-
深拷贝:新对象和原对象拥有各自的引用对象,内存使用更多,但避免了意外的副作用。
4. 使用场景
-
浅拷贝:适用于不需要独立副本的场景,或者当对象的引用类型字段不需要被修改时。
-
深拷贝:适用于需要完全独立副本的场景,尤其是当对象的引用类型字段可能会被修改时。
示例
以下是一个简单的示例,展示了浅拷贝和深拷贝的区别:
class Address {
String city;
public Address(String city) {
this.city = city;
}
}
class Person implements Cloneable {
String name;
Address address;
public Person(String name, Address address) {
this.name = name;
this.address = address;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 浅拷贝
}
// 深拷贝实现
protected Object deepClone() throws CloneNotSupportedException {
Person cloned = (Person) super.clone();
cloned.address = new Address(this.address.city); // 深拷贝
return cloned;
}
}
public class Main {
public static void main(String[] args) throws CloneNotSupportedException {
Address address = new Address("New York");
Person person1 = new Person("Alice", address);
// 浅拷贝
Person person2 = (Person) person1.clone();
person2.address.city = "Los Angeles"; // 修改引用对象
System.out.println(person1.address.city); // 输出: Los Angeles (受影响)
System.out.println(person2.address.city); // 输出: Los Angeles
// 深拷贝
Person person3 = (Person) person1.deepClone();
person3.address.city = "Chicago"; // 修改引用对象
System.out.println(person1.address.city); // 输出: Los Angeles (不受影响)
System.out.println(person3.address.city); // 输出: Chicago
}
}
总结
浅拷贝和深拷贝在对象复制时的行为有显著不同。选择使用哪种拷贝方式取决于具体的需求和对象的结构。在需要独立副本的情况下,深拷贝是更安全的选择,而在不需要独立副本的情况下,浅拷贝可以提高性能。
20.内部类的分类?
成员内部类:
最普通的内部类,它的定义位于另一个类的内部;
成员内部类可以无条件访问外部类的所有成员变量、成员方法、静态变量、静态方法;
但是外部类想要访问内部类的成员,必须先实例化内部类对象;
如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。
成员内部类可以使用任意的访问修饰符。
成员内部类不能有静态方法、静态属性和静态初始化块。
静态内部类:
静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字 static。
静态内部类不能使用外部类的非 static 成员变量或者方法。外部类想要访问静态内部
类成员,必须先实例化内部类对象;
如果要创建静态内部类的对象,不需要依赖于外部类对象,但是需要依赖外部类。
静态内部类可以定义成员变量、成员方法甚至于静态变量、静态方法和静态代码块;
静态内部类可以使用任意的访问修饰符。
局部内部类:
局部内部类是定义在一个方法或者一个作用域里面的类
局部内部类可以无条件访问外部类的所有成员变量、成员方法、静态变量、静态方法;
如果要创建局部内部类的对象,前提是必须存在一个外部类的对象。
局部内部类只能使用默认的访问修饰符;
局部内部类不能有静态方法、静态属性和静态初始化块。
简单来说: 它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域
内。
匿名内部类:
匿名内部类有多种形式,其中最常见的一种形式莫过于新建一个接口对象 / 抽象类对
象,并且实现这个接口声明 / 类中原有的方法了
直接 new 一个接口,并实现这个接口声明的方法,在这个过程其实会创建一个匿名内部
类实现这个接口,并重写接口声明的方法,然后再创建一个这个匿名内部类的对象
new 一个已经存在的接口/抽象类,并且选择性的实现这个类中的一个或者多个非
final 的方法,这个过程会创建一个匿名内部类对象继承对应的类 / 抽象类,并且重写对
应的方法。
在 Java 中,内部类(Inner Class)是定义在另一个类内部的类。内部类可以访问外部类的成员(包括私有成员),并且可以帮助组织代码。根据不同的特性和用途,内部类可以分为以下几种类型:
1. 成员内部类(Member Inner Class)
成员内部类是定义在外部类的内部,并且与外部类的实例相关联。它可以访问外部类的所有成员,包括私有成员。
class OuterClass {
private String outerField = "Outer Field";
class InnerClass {
void display() {
System.out.println(outerField); // 访问外部类的私有成员
}
}
}
2. 静态内部类(Static Nested Class)
静态内部类是定义在外部类内部,但使用 static
修饰。它不依赖于外部类的实例,可以直接通过外部类的类名访问。静态内部类只能访问外部类的静态成员。
class OuterClass {
static String staticOuterField = "Static Outer Field";
static class StaticInnerClass {
void display() {
System.out.println(staticOuterField); // 访问外部类的静态成员
}
}
}
3. 局部内部类(Local Inner Class)
局部内部类是在外部类的方法内部定义的类。它只能在该方法内部使用,并且可以访问方法的局部变量(前提是这些变量必须是 final
或者是“有效的 final”)。
class OuterClass {
void outerMethod() {
final String localVariable = "Local Variable";
class LocalInnerClass {
void display() {
System.out.println(localVariable); // 访问局部变量
}
}
LocalInnerClass localInner = new LocalInnerClass();
localInner.display();
}
}
4. 匿名内部类(Anonymous Inner Class)
匿名内部类是没有名字的内部类,通常用于实现接口或继承类。它在定义的同时被实例化,常用于事件处理和回调。
class OuterClass {
void outerMethod() {
// 匿名内部类实现 Runnable 接口
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Running in an anonymous inner class");
}
};
new Thread(runnable).start();
}
}
总结
Java 中的内部类提供了灵活的方式来组织代码和实现封装。不同类型的内部类适用于不同的场景,选择合适的内部类可以提高代码的可读性和可维护性。