【Java】Java核心知识点与相应面试技巧(七)——类与对象(二)

发布于:2025-03-31 ⋅ 阅读:(30) ⋅ 点赞:(0)

Java 类与对象篇

1.上期面试题解析:


上文链接https://blog.csdn.net/weixin_73492487/article/details/146607026


  1. 创建对象时的内存分配过程?

① 加载类 ② 堆内存分配空间 ③ 默认初始化 ④ 显式初始化 ⑤ 构造器执行

  1. this和super能否同时出现?

不能,二者都必须位于构造器首行

  1. 以下代码输出什么?
    class A {
        int i = 10;
        void print() { System.out.println(i); }
    }
    
    class B extends A {
        int i = 20;
        public static void main(String[] args) {
            new B().print(); 
        }
    }
    

输出10(方法看运行时类型,字段看编译时类型)
方法看运行时类型:在 Java 中,方法的调用是动态绑定的,这意味着方法的调用是基于实际对象的类型来决定的,而不是对象的声明类型。
字段看编译时类型:而字段的访问是静态绑定的,即编译时绑定。即使对象的实际类型发生了变化,编译时类型决定了我们访问的是哪个字段。也就是说,字段的访问与对象的声明类型相关,而与实际类型无关,不受运行时类型影响。

2.多态(Polymorphism)

2.1 实现条件

  • 继承体系:存在父子类关系
  • 方法重写:子类重写父类方法
  • 向上转型:父类引用指向子类对象(向上转型:低转高,自动转换)
    Animal animal = new Dog(); // 向上转型
    

2.2 动态绑定机制

class Animal {
    void sound() { System.out.println("Animal sound"); }
}

class Dog extends Animal {
    @Override
    void sound() { System.out.println("Woof!"); }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.sound(); // 输出"Woof!"(运行时类型决定方法调用)
    }
}

核心特性

  • 编译看左边(检查父类是否存在该方法,声明/引用决定可以调用的方法,若不存在该方法,则编译错误)
  • 运行看右边(实际执行子类重写方法,引用指向的对象具体决定调用的方法)
  • 成员变量无多态(编译运行都看左边)

3. instanceof 与类型转换

3.1 类型检查

Object obj = new String("Hello");
if(obj instanceof String) { // 返回true
    System.out.println("是字符串类型");
}

分析:instanceof 用于判断一个对象是否是某个类的实例,或者是否实现了某个接口,并且返回一个布尔值

2.2 类型转换规则

转换类型 语法 风险
向上转型 自动转换 无风险
向下转型 强制转换 + 类型检查 可能抛出ClassCastException

分析:在进行类型转换之前,可以使用 instanceof 来判断是否可以安全地进行转换,从而避免 ClassCastException。

安全转换模式

if(animal instanceof Dog) {
    Dog dog = (Dog)animal; // 安全转换
}

父类与子类间的类型转换:

//父类Animal
class Animal {
    public void eat() {
        System.out.println("Animal is eating");
    }
}

//子类Dog继承了父类的eat方法
class Dog extends Animal {
    public void bark() {
        System.out.println("Dog is barking");
    }
}

public class Test {
    public static void main(String[] args) {
    	//一个对象的实际类型是确定的,但可以指向的引用类型就不确定了
    	//父类引用指向了子类
        Animal animal = new Dog();
        animal.eat();  // 可以调用 Animal 类中的方法
        
        // animal.bark();  // 编译错误,Animal 类型没有 bark 方法

        if (animal instanceof Dog) {
            Dog dog = (Dog) animal;  // 强制类型转换
            dog.bark();  // 可以调用 Dog 类中的 bark 方法
        }
    }
}


4. static 关键字详解

4.1 静态成员 vs 实例成员

维度 静态成员 实例成员
所属层级 类级别 对象级别
内存分配 类加载时初始化 对象创建时分配
访问方式 类名.成员 或 对象.成员 必须通过对象访问
生命周期 与类共存亡 随对象回收而销毁
线程安全 需额外同步控制 实例隔离更安全

4.2 静态代码块与初始化顺序

class MyClass {
    static int staticVar;	//静态变量
    int instanceVar;
    
    // 静态代码块(类加载时执行一次,只执行一次)
    static {
        staticVar = 100;
        System.out.println("静态代码块");
    }
    
    // 实例代码块(每次new对象时执行,用于赋初始值)
    {
        instanceVar = 50;
        System.out.println("实例代码块");
    }
    public MyClass(){
    	System.out.println("构造方法");
    }
}

public class Test{
	public static void main(String[] args){
	MyClass mc=new MyClass();
	System.out.println("==========");
	MyClass ms=new MyClass();
	}
}

初始化顺序
静态变量/代码块 → 实例变量/代码块 → 构造器

输出:
静态代码块
实例代码块
构造方法
==========
实例代码块
构造方法

4.3 static修饰特性

静态变量:
  • 静态变量被所有实例共享
  • 静态变量在内存中只有一份副本
  • 静态变量的生命周期与类的生命周期相同,即在类加载时分配内存,直到程序结束时才释放(从属于类)
静态方法:
  • 静态方法只能访问静态变量和调用其他静态方法
  • 可以通过类名直接调用,且不能直接访问实例变量和实例方法
  • 不能使用this/super关键字
  • 不能被重写(但可隐藏)
class Parent {
   static void staticMethod() {
       System.out.println("Parent static method");
   }
}

class Child extends Parent {
   //与父类方法相独立
   static void staticMethod() {
       System.out.println("Child static method");
   }
}

public class Test {
   public static void main(String[] args) {
       Parent p = new Parent();
       Parent c = new Child();
       Child d = new Child();

       p.staticMethod();  // 输出 "Parent static method"
       c.staticMethod();  // 输出 "Parent static method" (静态方法调用是根据引用类型来决定的)
       d.staticMethod();  // 输出 "Child static method" (静态方法调用是根据引用类型来决定的)
 	  //静态方法是属于类的,而不是对象的。所以,当你调用静态方法时,它是通过类名来解析的,而不是通过对象引用来解析
    //当子类定义了一个与父类中静态方法相同的方法时,在子类中的静态方法不会被真正地“重写”,父类和子类的静态方法仍然是相互独立的。
   }
}

静态代码块:
  • 静态代码块在类加载时自动执行,且只执行一次。
  • 通常用于静态变量的初始化。
静态导入:
  • 使用 import static 语句导入类的静态成员,可以直接访问类的静态方法或静态变量。
import static java.lang.Math.*;

public class Test {
    public static void main(String[] args) {
        double result = sqrt(25);  // 直接使用 Math 类中的 sqrt 方法,无需写类名(Math.sqrt())
        System.out.println(result);  // 输出 5.0
    }
}

静态内部类(由 static 修饰的内部类):
  • 静态内部类不依赖外部类的实例,可以直接创建
  • 可以访问外部类的静态成员,但不能访问外部类的实例成员和实例方法
class Outer {
    static int staticVar = 10;
    int var; 	//静态内部类不可访问
	//由static修饰的内部类(静态内部类)
    static class Inner {
        void display() {
            System.out.println("Static Inner class, staticVar: " + staticVar);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer.Inner();  // 直接通过外部类创建静态内部类的实例
        inner.display();  
    }
}

输出:
Static Inner class, staticVar: 10


5. 抽象类(Abstract Class)

5.1 定义与特征

abstract class Animal {
    // 抽象方法
    abstract void sound();
    
    // 非抽象方法/具体方法(子类会继承)
    void sleep() {
        System.out.println("Sleeping...");
    }
}

class Dog extends Animal {
    // 实现抽象方法
    @Override
    void sound() {
        System.out.println("Bark");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Dog();
        animal.sound(); // 输出 "Bark"
        animal.sleep(); // 输出 "Sleeping..."
    }
}

定义:
抽象类是一种不能直接实例化的类。它可以包含抽象方法和非抽象方法。抽象方法是没有实现的方法,只有方法的声明,没有方法体,子类需要实现这些抽象方法。抽象类通常用于定义一种通用的模板或接口,子类继承它并提供具体的实现。

核心特性

  • abstract关键字修饰
  • 不能实例化(只能被继承
  • 可以包含抽象方法和具体方法(抽象方法必须在抽象类中)
    • 抽象方法:没有方法体,必须在子类中实现。
    • 非抽象方法:有方法体,子类可以继承并使用,也可以覆盖(重写)
  • 子类必须实现(重写)所有抽象方法(除非子类也是抽象类,那么就需要子类的子类去实现这些抽象方法)
  • 可以包含构造方法:抽象类可以有构造方法,子类通过super()调用父类的构造方法。
  • 可以包含成员变量(任意访问修饰符)
abstract class Shape {
	//成员变量
    int x, y;

    // 抽象类的构造方法,初始化共同的属性
    public Shape(int x, int y) {
        this.x = x;
        this.y = y;
    }
	//抽象方法,必须实现
    abstract void draw();
}

class Circle extends Shape {
    int radius;

    public Circle(int x, int y, int radius) {
        super(x, y);  // 调用父类构造方法
        this.radius = radius;
    }

    @Override
    void draw() {
        System.out.println("Drawing a circle at (" + x + ", " + y + ") with radius " + radius);
    }
}

public class Test {
    public static void main(String[] args) {
        Circle circle = new Circle(5, 10, 7);
        circle.draw();  // 输出: Drawing a circle at (5, 10) with radius 7
    }
}

5.2 使用场景

  • 代码复用:多个子类共享部分实现
  • 模板方法模式:定义算法框架,具体步骤由子类实现
  • 强制规范:要求子类必须实现特定功能

6. 接口(Interface)

6.1 定义与演进

// Java 8+ 接口(是一种约束,只有抽象方法)
public interface Flyable {
    // 常量(默认 public static final)
    double MAX_SPEED = 1000.0;
    
    // 抽象方法(默认 public abstract)
    void fly();
    
    // 默认方法(Java 8+)默认方法有方法体,可以提供默认实现。
	//默认方法是 可选 的,接口的实现类可以选择覆盖它,也可以使用接口提供的默认实现。
    default void land() {
        System.out.println("Landing...");
    }
    
    // 静态方法(Java 8+),可以通过接口直接调用,而不需要依赖于接口的实现类。
	//静态方法 不能被实现类重写,因为它属于接口本身。
    static void showMaxSpeed() {
        System.out.println("Max speed: " + MAX_SPEED);
    }
    
    // 私有方法(Java 9+)只能在接口内部使用,不能被接口的实现类直接访问。
    private void checkSpeed() {
        // 内部复用代码
    }
}

定义:
接口是一个抽象类型,它是类与类之间的一种协议,用来指定类必须实现的方法。接口只包含 抽象方法(没有方法体)和 常量,从 Java 8 开始,接口也可以包含 默认方法(有实现)和 静态方法。

核心特性

  • 不能直接实例化: 你不能创建接口的对象,必须通过实现接口的类来实例化对象
  • 没有构造方法: 接口没有构造方法,因此不能创建接口实例
  • implements实现,支持多继承多实现(一个类可实现多个接口)
  • 所有方法默认public abstract(Java 8前)
  • 变量默认public static final
  • Java 8+ 支持默认方法和静态方法
  • Java 9+ 支持私有方法

示例:

interface Logger {
    private void log(String message) {
        System.out.println("Logging message: " + message);
    }

    default void info(String message) {
        log(message);  // 接口调用自身私有方法
    }

    default void warn(String message) {
        log(message);  // 接口调用自身私有方法
    }
}

class MyLogger implements Logger {
    // 不需要实现 log() 方法,只需要调用 info() 或 warn() 方法,来提示登陆信息
}

public class Test {
    public static void main(String[] args) {
        MyLogger logger = new MyLogger();
        logger.info("Info message");
        logger.warn("Warning message");
        // 输出:
        // Logging message: Info message
        // Logging message: Warning message
    }
}

6.2 使用场景

  • 多继承行为:定义多个能力(Flyable, Runnable等)
  • 策略模式:通过不同实现类改变算法
  • API定义:解耦接口与实现(面向接口编程)
  • 函数式接口(@FunctionalInterface):Lambda表达式基础

7. 抽象类 vs 接口对比

维度 抽象类 接口(Java 8+)
实例化 不能 不能
方法实现 可包含具体方法 默认方法/静态方法/私有方法
成员变量 任意类型和修饰符 只能是public static final常量
继承/实现 单继承(extends) 多实现(implements)
构造器
设计目的 代码复用 + 规范约束 行为规范定义
访问控制 支持各种访问修饰符 默认public(Java 9支持private)

⚡ 高频面试题

  1. 以下代码输出什么?

    class A {
        static { System.out.print("1"); }
        { System.out.print("2"); }
        public A() { System.out.print("3"); }
    }
    
    class B extends A {
        static { System.out.print("4"); }
        { System.out.print("5"); }
        public B() { System.out.print("6"); }
    }
    
    public class Test {
        public static void main(String[] args) {
            new B(); 
        }
    }
    
  2. 静态方法能否调用非静态方法?为什么?

  3. 如何实现线程安全的单例模式?

  4. 以下代码是否合法?

    class Test {
        static int a = b; 
        static int b = 10;
    }
    
  5. 何时选择抽象类?何时选择接口?

  6. 以下代码是否合法?为什么?

    abstract class A {
        abstract void method1();
        final void method2() {}
    }
    
    interface B {
        default void method3() {}
        static void method4() {}
    }
    


网站公告

今日签到

点亮在社区的每一天
去签到