面试笔记02-Java基础(二):面向对象

一、类与对象

1. 请解释类和对象的概念,以及它们之间的关系
答:

  • 是对具有相同属性和行为的一组对象的抽象描述。
    它定义了对象的属性(成员变量)和行为(方法),是创建对象的模板。
  • 对象是类的具体实例,是根据类创建出来的一个个独立个体,每个对象都有自己的状态(属性值)。
  • 类和对象的关系是抽象与具体的关系。
    类是对象的蓝图,对象是类的具体表现。
    例如,“汽车” 类描述了汽车的通用属性(颜色、品牌等)和行为(行驶、刹车等),而某一辆具体的红色宝马汽车就是 “汽车” 类的一个对象。

2. 简述构造方法的作用和特点
答:
构造方法的作用是在创建对象时对对象进行初始化操作,为对象的成员变量赋初始值。
其特点如下:

  • 方法名与类名相同:构造方法的名称必须和所在类的名称一致。
  • 没有返回值类型:构造方法不能有返回值类型,连 void 也不能有。
  • 可重载:一个类中可以有多个构造方法,它们的参数列表不同,以满足不同的初始化需求。例如:

    class Person {
    String name;
    int age;
    
    // 无参构造方法
    public Person() {
        name = "未知";
        age = 0;
    }
    
    // 带参构造方法
    public Person(String n, int a) {
        name = n;
    age = a;
    }
    }

二、重载与重写

1. 什么是方法重载?请举例说明。
答:
方法重载是指在一个类中可以定义多个方法名相同,但参数列表不同(参数的类型、个数或顺序不同)的方法。
调用时,Java 根据传入的实参类型、个数和顺序来决定调用哪个方法。
例如:

class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
    public double add(double a, double b) {
    return a + b;
    }
}

2. 方法重载是否与返回值类型有关?
答:
方法重载与返回值类型无关。

3. 什么是方法重写?重写需要满足哪些条件?
答:
方法重写是指子类重新定义父类中已有的方法。
需要满足以下条件:

  • 方法名、参数列表和返回值类型必须与父类被重写的方法相同(Java 5 开始,返回值类型可以是父类方法返回值类型的子类,即协变返回类型)。
  • 访问权限不能比父类被重写方法更严格。
  • 不能抛出比父类被重写方法更多、更宽泛的异常。

例如:

// 父类
class Animal {
    public void sound() {
        System.out.println("动物发声");
    }
}
// 子类
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("汪汪汪");
    }
}

4. final、static、private 修饰的方法能否被重写?
答:

  • final 修饰的方法不能被重写。
    因为 final 关键字表示该方法不能被修改,保证了方法的稳定性。
  • static 修饰的方法不能被重写,只能被隐藏。
    静态方法属于类,而不是对象,子类可以定义与父类静态方法相同的方法,但这不是重写,只是隐藏了父类的静态方法。
  • private 修饰的方法不能被重写。
    因为 private 方法只能在本类中访问,子类无法访问到父类的 private 方法,也就无法重写。

5. 请简要回答方法重载和方法重写的相同与不同?
答:

方法重载方法重写
定位位置同一个类中子类和父类
方法名要相同要相同
参数列表不同必须相同
返回值类型无关相同或为协变返回类型
访问权限无关子类方法不能比父类更严格
异常无关子类方法不能抛出比父类更多、更宽泛的异常

三、封装、继承、多态

1. 什么是封装,为什么要使用封装?
答:
封装是将对象的属性和实现细节隐藏起来,仅对外提供公共的访问方式(如 getter 和 setter 方法)。
使用封装的原因:

  • 提高代码的安全性,防止外部随意修改对象的属性;
  • 增强代码的可维护性,当内部实现改变时,只要对外接口不变,就不会影响其他代码;
  • 实现信息隐藏,让使用者只需关注接口,无需了解具体实现。

2. 如何在 Java 中实现封装?
答:
首先将类的属性声明为 private,限制外部直接访问。
然后提供公共的 getter 方法用于获取属性值,setter 方法用于设置属性值。例如:

class Student {
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

3. 什么是继承,继承有什么作用?
答:
继承是指一个类(子类)可以继承另一个类(父类)的属性和方法。
其作用包括:

  • 实现代码复用,避免重复编写相同的代码;
  • 建立类之间的层次关系,便于代码的组织和管理;
  • 为多态提供基础。

4. Java 中继承有哪些限制?
答:
Java 只支持单继承,即一个子类只能有一个直接父类,但可以实现多层继承。
另外,private 成员不能被继承,构造方法也不能被继承,但子类构造方法中可以通过 super() 调用父类构造方法。

5. 实现一个继承效果

// 定义一个父类:Animal
class Animal {
    // 父类的属性
    protected String name;
    // 父类的构造方法
    public Animal(String name) {
        this.name = name;
    }
    // 父类的方法
    public void eat() {
        System.out.println(name + " 正在吃东西");
    }
    public void sleep() {
        System.out.println(name + " 正在睡觉");
    }
}

// 定义一个子类:Dog,继承自 Animal 类
class Dog extends Animal {
    // 子类特有的属性
    private String breed;
    // 子类的构造方法
    public Dog(String name, String breed) {
        // 调用父类的构造方法来初始化从父类继承的属性
        super(name);
        this.breed = breed;
    }
    // 子类重写父类的方法
    @Override
    public void eat() {
        System.out.println(name + "(品种:" + breed + ")正在啃骨头");
    }
    // 子类特有的方法
    public void bark() {
        System.out.println(name + "(品种:" + breed + ")正在汪汪叫");
    }
}

// 测试类
public class InheritanceExample {
    public static void main(String[] args) {
        // 创建 Dog 类的对象
        Dog dog = new Dog("旺财", "金毛");
        // 调用从父类继承的方法
        dog.sleep();
        // 调用子类重写的方法
        dog.eat();
        // 调用子类特有的方法
        dog.bark();
    }
}

输出结果:

旺财 正在睡觉
旺财(品种:金毛)正在啃骨头
旺财(品种:金毛)正在汪汪叫

代码解释

  1. 父类 Animal:

    • 包含一个受保护的属性 name,用于存储动物的名字。
    • 有一个构造方法 Animal(String name) 用于初始化 name 属性。
    • 定义了两个方法 eat() 和 sleep(),分别表示动物吃东西和睡觉的行为。
  2. 子类 Dog:

    • 使用 extends 关键字继承自 Animal 类。
    • 有一个特有的属性 breed,用于存储狗的品种。
    • 构造方法 Dog(String name, String breed) 中使用 super(name) 调用父类的构造方法来初始化从父类继承的 name 属性。
    • 重写了父类的 eat() 方法,提供了狗吃东西的特定行为。
    • 定义了一个特有的方法 bark(),表示狗汪汪叫的行为。
  3. 测试类 InheritanceExample:

    • 在 main 方法中创建了 Dog 类的对象 dog。
    • 调用了从父类继承的 sleep() 方法、子类重写的 eat() 方法以及子类特有的 bark() 方法,展示了继承和方法重写的效果。

6. 什么是多态,如何实现多态?
多态是指同一个方法调用,由于对象不同可能会产生不同的行为。
实现多态有三个必要条件:

  • 继承
  • 方法重写
  • 父类引用指向子类对象

例如:

// 父类:动物类
class Animal {
    public void sound() {
        System.out.println("动物发声");
    }
}
// 子类:狗类
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("汪汪汪");
    }
}
// 子类:猫类
class Cat extends Animal {
    @Override
    public void sound() {
        System.out.println("喵喵喵");
    }
}
// 测试
public class Main {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.sound(); // 输出:汪汪汪

        animal = new Cat();
        animal.sound(); // 输出:喵喵喵
    }
}

四、抽象类与接口

1. 请简述抽象类和接口的概念及区别
答:
抽象类是用 abstract 关键字修饰的类,它可以包含抽象方法(只有声明,没有实现)和具体方法,也可以有成员变量。
接口是一种特殊的抽象类型,它只包含抽象方法(Java 8 及以后可包含默认方法和静态方法)和常量。

  • 语法层面:
    • 抽象类:
      • abstract class 定义;
      • 可以有构造方法;
      • 成员变量可以是各种类型;
    • 接口:
      • interface 定义;
      • 没有构造方法;
      • 成员变量默认是 public static final 修饰的常量
  • 设计层面:
    • 抽象类是对一类事物的抽象,是一种 “is – a” 关系;
    • 接口是对行为的抽象,是一种 “like – a” 关系。
      使用层面: 一个类只能继承一个抽象类,但可以实现多个接口。

2. 什么时候使用抽象类,什么时候使用接口?
答:

  • 抽象类:当需要为子类提供公共的方法实现和成员变量,且子类之间有共同的属性和行为,属于同一类事物时,使用抽象类。
    例如,不同种类的汽车都有启动、停止等行为,可将汽车抽象成抽象类。
  • 接口:当需要定义一组行为规范,多个不相关的类都要遵循这些规范时,使用接口。
    例如,飞机和鸟都能飞,“飞” 这个行为可以定义成接口。

3. 一个类能否同时继承抽象类和实现接口?
答: 可以。例如:class Bird extends Animal implements Flyable {...}

4. 实现一个同时继承抽象类和实现接口例子

// 定义抽象类:形状类
abstract class Shape {
    // 抽象方法,用于计算面积,具体实现由子类完成
    public abstract double area();

    // 具体方法,所有继承该抽象类的子类都可使用
    public void display() {
        System.out.println("这是一个形状。");
    }
}

// 定义接口:颜色接口
interface Colorable {
    // 接口中的抽象方法,用于设置颜色
    void setColor(String color);

    // Java 8 及以后支持的默认方法,提供了默认实现
    default void showColorInfo() {
        System.out.println("这是一个有颜色的形状。");
    }
}

// 测试类,继承抽象类 Shape 并实现接口 Colorable
class Circle extends Shape implements Colorable {
    private double radius;
    private String color;

    public Circle(double radius) {
        this.radius = radius;
    }

    // 实现抽象类的抽象方法,计算圆形面积
    @Override
    public double area() {
        return Math.PI * radius * radius;
    }

    // 实现接口的抽象方法,设置颜色
    @Override
    public void setColor(String color) {
        this.color = color;
        System.out.println("圆形的颜色设置为:" + color);
    }

    // 可以重写接口的默认方法
    @Override
    public void showColorInfo() {
        System.out.println("这是一个颜色为 " + color + " 的圆形。");
    }
}

// 主类,用于测试
public class Main {
    public static void main(String[] args) {
        Circle circle = new Circle(5.0);

        // 调用抽象类的具体方法
        circle.display();

        // 调用实现抽象类抽象方法的结果
        System.out.println("圆形的面积是:" + circle.area());

        // 调用接口的抽象方法
        circle.setColor("红色");

        // 调用接口的默认方法
        circle.showColorInfo();
    }
}

代码解释:
1. 抽象类 Shape:

  • 包含抽象方法 area(),该方法没有具体实现,需要子类去实现。
  • 包含具体方法 display(),所有继承该抽象类的子类都可以直接使用这个方法。
    2. 接口 Colorable:
  • 包含抽象方法 setColor(String color),实现该接口的类必须实现这个方法。
  • 包含默认方法 showColorInfo(),提供了默认的实现,实现类可以选择重写该方法。
    3. 测试类 Circle:
  • 继承自抽象类 Shape,实现了 area() 方法来计算圆形的面积。
  • 实现了接口 Colorable,实现了 setColor(String color) 方法来设置圆形的颜色,并且重写了 showColorInfo() 方法以提供特定的颜色信息。
    4. 主类 Main:
  • 创建了 Circle 类的对象。
  • 调用了抽象类的具体方法 display()。
  • 调用了实现抽象类抽象方法 area() 的结果。
  • 调用了接口的抽象方法 setColor(String color) 和默认方法 showColorInfo()。

五、内部类

1. 内部类有哪些类型,它们的访问权限有何不同?通过代码示例说明
答:
Java 内部类主要有四种类型:成员内部类、局部内部类、匿名内部类和静态内部类。

  • 成员内部类:
    成员内部类定义在外部类的内部,作为外部类的一个成员存在。
    它可以访问外部类的所有成员(包括私有成员),创建成员内部类对象需要先创建外部类对象。

    // 外部类
    class OuterClass {
    // 外部类私有属性
    private int outerData = 10;
    
    // 成员内部类
    class InnerClass {
        // 成员内部类方法
        void display() {
            System.out.println("访问外部类的私有成员: " + outerData);
        }
    }
    }
    // 测试类
    public class MemberInnerClassExample {
    public static void main(String[] args) {
        OuterClass outer = new OuterClass();
        OuterClass.InnerClass inner = outer.new InnerClass();
        inner.display();
    }
    }

    代码解释

    • InnerClass 是 OuterClass 的成员内部类。
    • InnerClass 中的 display 方法可以访问外部类的私有成员 outerData。
    • 在 main 方法中,需要先创建外部类对象 outer,再通过外部类对象创建内部类对象 inner。
  • 局部内部类:
    局部内部类定义在方法或代码块内部,其作用域仅限于该方法或代码块。
    它可以访问外部类的所有成员以及该方法或代码块中被 final 修饰(Java 8 及以后为事实上的 final)的局部变量。

    // 外部类
    class OuterClassForLocal {
    // 外部类私有属性(成员变量)
    private int outerData = 20;
    // 外部类方法
    void outerMethod() {
        // 局部变量
        final int localVar = 30; // Java 8 及以后可不显式声明为 final
    
        // 局部内部类
        class LocalInnerClass {
            void show() {
                System.out.println("外部类数据: " + outerData);
                System.out.println("局部变量: " + localVar);
            }
        }
        // 创建局部内部类对象
        LocalInnerClass localInner = new LocalInnerClass();
        localInner.show();
    }
    }
    // 测试类
    public class LocalInnerClassExample {
    public static void main(String[] args) {
        OuterClassForLocal outer = new OuterClassForLocal();
        outer.outerMethod();
    }
    }

    代码解释

    • LocalInnerClass 是定义在 outerMethod 方法内部的局部内部类。
    • LocalInnerClass 中的 show 方法可以访问外部类的成员 outerData 和方法中的局部变量 localVar
    • 局部内部类只能在定义它的方法内部使用。
  • 匿名内部类:
    匿名内部类没有类名,通常用于创建只使用一次的类对象。
    同样可以访问外部类的所有成员和方法中 final 修饰的局部变量。
    它必须继承一个父类或实现一个接口。

    // 接口
    interface MyInterface {
    void doSomething();
    }
    // 外部类
    public class AnonymousInnerClassExample {
    // 主方法
    public static void main(String[] args) {
        // 匿名内部类
        MyInterface obj = new MyInterface() {
            @Override
            public void doSomething() {
                System.out.println("匿名内部类实现接口方法");
            }
        };
        obj.doSomething();
    }
    }

    代码解释

    • 直接在创建对象时实现了 MyInterface 接口,没有显式定义类名,这就是匿名内部类。
    • 匿名内部类重写了 MyInterface 接口的 doSomething 方法。
    • 创建的匿名内部类对象赋值给 MyInterface 类型的引用 obj,并调用其方法。
  • 静态内部类:
    静态内部类使用 static 修饰,它只能访问外部类的静态成员。
    创建静态内部类对象不需要先创建外部类对象。

    // 外部类
    class OuterClassForStatic {
    //外部类静态私有属性
    private static int staticOuterData = 40;
    
    // 静态内部类
    static class StaticInnerClass {
        void printData() {
            System.out.println("访问外部类的静态成员: " + staticOuterData);
        }
    }
    }
    // 测试类
    public class StaticInnerClassExample {
    public static void main(String[] args) {
        OuterClassForStatic.StaticInnerClass staticInner = new OuterClassForStatic.StaticInnerClass();
        staticInner.printData();
    }
    }

    代码解释

    • StaticInnerClass 是使用 static 修饰的静态内部类。
    • StaticInnerClass 中的 printData 方法可以访问外部类的静态成员 staticOuterData
    • main 方法中,可以直接创建静态内部类对象,无需先创建外部类对象。

2. 静态内部类和非静态内部类在创建对象时有什么区别?
答:
非静态内部类(成员内部类、局部内部类、匿名内部类):
创建非静态内部类对象前,必须先创建外部类对象。
因为非静态内部类隐式持有外部类对象的引用,依赖于外部类对象存在。

静态内部类:
创建静态内部类对象不需要先创建外部类对象。
因为静态内部类不依赖于外部类的实例,它可以独立存在。

3. 成员内部类、局部内部类、匿名内部类、静态内部类经典使用场景
答:

  • 成员内部类
    • 实现多重继承效果:在 Java 中类只能单继承,但可以通过成员内部类继承其他类,实现类似多重继承的功能。
    • 事件监听机制:在 GUI 编程中,成员内部类可用于实现事件监听器,方便访问外部类的组件和状态。
  • 局部内部类
    • 方法内部的复杂逻辑封装:当一个方法中的逻辑较为复杂,需要一些辅助类来完成特定子任务时,可以使用局部内部类。
    • 临时数据处理:在某个方法中需要对一些临时数据进行处理,并且这些处理逻辑需要封装在一个类中时使用。
  • 匿名内部类
    • 事件处理:在 GUI 编程中,用于创建事件监听器,代码简洁,避免创建大量的监听器类。
    • 回调函数:在需要传递一个简单的回调函数时使用,例如线程的 Runnable 接口实现(Java8后使用lambda表达式更简洁)。
  • 静态内部类
    • 数据结构嵌套:当一个类需要包含另一个紧密相关的数据结构类时,使用静态内部类可以使代码结构更清晰。
    • 辅助工具类:在外部类中提供一些辅助功能的工具类,使用静态内部类可以避免创建不必要的外部类实例。

六、枚举类

1. 什么是 Java 中的枚举类,它有什么特点?
答:
Java 中的枚举类是一种特殊的类,用于定义一组固定的常量。
它的特点如下:

  • 常量集合:枚举类的实例是固定的,通常用于表示一组有限的、预定义的值。如一周的七天、一年的四季等。
  • 类型安全:枚举类型可以保证变量只能取枚举类中定义的值,避免了使用普通常量可能带来的类型不匹配问题。
  • 单例模式实现:枚举类天然支持单例模式,每个枚举常量在 JVM 中是唯一的实例。
  • 可添加方法和属性:枚举类可以像普通类一样拥有成员变量、方法等,并且可以为每个枚举常量提供不同的行为。

2. 如何在枚举类中定义构造方法、成员变量和方法?请举例说明。
答:
枚举类可以定义构造方法、成员变量和方法。
构造方法必须是私有的,因为枚举常量的实例是固定的,不允许外部创建新的实例。
例如:

// 枚举类:季节
enum Season {
    // 枚举常量,调用构造方法进行初始化
    SPRING("春天", "温暖"),
    SUMMER("夏天", "炎热"),
    AUTUMN("秋天", "凉爽"),
    WINTER("冬天", "寒冷");

    // 成员变量
    private final String name;
    private final String description;

    // 构造方法,必须是私有
    private Season(String name, String description) {
        this.name = name;
        this.description = description;
    }

    // 获取名称的方法
    public String getName() {
        return name;
    }

    // 获取描述的方法
    public String getDescription() {
        return description;
    }
}
// 测试类
public class EnumExample {
    public static void main(String[] args) {
        Season spring = Season.SPRING;
        System.out.println(spring.getName() + ":" + spring.getDescription());
    }
}

3. 枚举类在 switch 语句中的使用有什么优势?
答:
枚举类在 switch 语句中使用具有以下优势:

  • 类型安全:switch 语句的表达式类型为枚举类型时,case 标签只能是该枚举类的常量,编译器会进行类型检查,避免了使用普通整数或字符串常量可能出现的拼写错误等问题。
  • 代码可读性高:使用枚举常量作为 case 标签,代码更具可读性,能够清晰地表达每个分支的含义。
  • 维护方便:当枚举类的常量发生变化时,编译器会提示 switch 语句中可能需要更新的地方,便于代码的维护和扩展。
    // 枚举类:周天
    enum Weekday {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }
    // 测试类
    public class EnumSwitchExample {
    public static void main(String[] args) {
        // 此处today模拟外部参数的值
        Weekday today = Weekday.MONDAY;
        switch (today) {
            case MONDAY:
                System.out.println("今天是周一,开始新的一周");
                break;
            case TUESDAY:
                System.out.println("今天是周二,继续努力");
                break;
            // 其他 case 分支...
            default:
                System.out.println("未知的星期");
        }
    }
    }
版权声明:本文《面试笔记02-Java基础(二):面向对象》是由陶其原创撰写,首发于陶其的个人博客
转载声明:如需转载本文,请务必在转载处保留原文链接:https://www.tqazy.com/?p=1249,并明确注明文章来源。
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇