MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java面向对象编程基础

2021-01-234.0k 阅读

类与对象

类的定义

在Java中,类是一种用户自定义的数据类型,它是对现实世界中一类具有相同属性和行为的事物的抽象。类定义了对象的状态(通过成员变量表示)和行为(通过成员方法表示)。 以下是一个简单类的定义示例:

public class Dog {
    // 成员变量(表示狗的属性)
    String name;
    int age;

    // 成员方法(表示狗的行为)
    void bark() {
        System.out.println(name + " is barking.");
    }
}

在上述代码中,Dog 类有两个成员变量 nameage,分别表示狗的名字和年龄。还有一个成员方法 bark,表示狗叫的行为。

对象的创建与使用

对象是类的实例。一旦定义了类,就可以创建该类的对象,并通过对象来访问类的成员变量和调用成员方法。

public class Main {
    public static void main(String[] args) {
        // 创建Dog类的对象
        Dog myDog = new Dog();
        myDog.name = "Buddy";
        myDog.age = 3;

        // 调用对象的方法
        myDog.bark();
    }
}

main 方法中,首先使用 new 关键字创建了 Dog 类的一个对象 myDog。然后给 myDog 的成员变量 nameage 赋值,并调用了 bark 方法。

构造方法

构造方法是一种特殊的方法,用于在创建对象时初始化对象的状态。构造方法的名称必须与类名相同,并且没有返回类型。

public class Dog {
    String name;
    int age;

    // 构造方法
    public Dog(String dogName, int dogAge) {
        name = dogName;
        age = dogAge;
    }

    void bark() {
        System.out.println(name + " is barking.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 使用构造方法创建对象
        Dog myDog = new Dog("Buddy", 3);
        myDog.bark();
    }
}

在上述代码中,Dog 类有一个构造方法 Dog(String dogName, int dogAge),在创建 Dog 对象时,通过构造方法传递参数来初始化 nameage 成员变量。

访问修饰符

访问修饰符用于控制类、成员变量和成员方法的访问权限。Java 中有四种访问修饰符:publicprivateprotected 和默认(没有修饰符)。

  1. public:被 public 修饰的类、成员变量和成员方法可以被任何其他类访问。
  2. private:被 private 修饰的成员变量和成员方法只能在本类内部被访问。
  3. protected:被 protected 修饰的成员变量和成员方法可以被本类、同一包中的其他类以及不同包中的子类访问。
  4. 默认(没有修饰符):默认访问权限的类、成员变量和成员方法可以被同一包中的其他类访问。

以下是一个示例展示不同访问修饰符的使用:

public class AccessModifiersExample {
    // public 成员变量
    public int publicVar;
    // private 成员变量
    private int privateVar;
    // protected 成员变量
    protected int protectedVar;
    // 默认访问权限成员变量
    int defaultVar;

    // public 方法
    public void publicMethod() {
        System.out.println("This is a public method.");
    }

    // private 方法
    private void privateMethod() {
        System.out.println("This is a private method.");
    }

    // protected 方法
    protected void protectedMethod() {
        System.out.println("This is a protected method.");
    }

    // 默认访问权限方法
    void defaultMethod() {
        System.out.println("This is a default method.");
    }
}

封装

封装的概念

封装是面向对象编程的一个重要原则,它将对象的状态(成员变量)和行为(成员方法)包装在一起,并对外部隐藏对象的内部实现细节。通过封装,可以提高代码的安全性和可维护性。

使用封装的好处

  1. 数据隐藏:通过将成员变量设为 private,外部类无法直接访问和修改对象的内部数据,从而保护数据的完整性和安全性。
  2. 简化接口:外部类只需要通过公开的成员方法来与对象进行交互,不需要了解对象内部的复杂实现,使得代码的使用更加简单。
  3. 可维护性:当对象的内部实现发生变化时,只要公开的接口不变,对外部类的影响就很小,便于代码的维护和扩展。

实现封装

在Java中,通常将成员变量声明为 private,然后提供 publicgettersetter 方法来访问和修改这些成员变量。

public class Person {
    private String name;
    private int age;

    // getter 方法获取 name
    public String getName() {
        return name;
    }

    // setter 方法设置 name
    public void setName(String name) {
        this.name = name;
    }

    // getter 方法获取 age
    public int getAge() {
        return age;
    }

    // setter 方法设置 age
    public void setAge(int age) {
        if (age >= 0) {
            this.age = age;
        } else {
            System.out.println("Age cannot be negative.");
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("Alice");
        person.setAge(25);

        System.out.println("Name: " + person.getName());
        System.out.println("Age: " + person.getAge());

        person.setAge(-5);
    }
}

在上述代码中,Person 类的 nameage 成员变量被声明为 private,通过 getNamegetAge 方法获取其值,通过 setNamesetAge 方法设置其值。在 setAge 方法中,还进行了数据验证,确保年龄不会被设置为负数。

继承

继承的概念

继承是面向对象编程的另一个重要特性,它允许一个类(子类)从另一个类(父类)中获取属性和行为。通过继承,可以实现代码的复用,提高开发效率。

继承的语法

在Java中,使用 extends 关键字来表示继承关系。

class Animal {
    String name;

    void eat() {
        System.out.println(name + " is eating.");
    }
}

class Dog extends Animal {
    int age;

    void bark() {
        System.out.println(name + " is barking.");
    }
}

在上述代码中,Dog 类继承自 Animal 类,Dog 类自动拥有了 Animal 类的 name 成员变量和 eat 成员方法,同时还可以定义自己特有的成员变量 age 和成员方法 bark

重写(Override)

当子类需要对从父类继承的方法进行不同的实现时,可以重写该方法。重写的方法必须满足以下条件:

  1. 方法名、参数列表和返回类型必须与父类中的方法完全相同(对于返回类型,在Java 5.0及以上版本,子类重写方法的返回类型可以是父类方法返回类型的子类)。
  2. 访问修饰符不能比父类中被重写方法的访问修饰符更严格。
class Animal {
    void makeSound() {
        System.out.println("Animal makes a sound.");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.makeSound();

        Dog dog = new Dog();
        dog.makeSound();

        Animal animalAsDog = new Dog();
        animalAsDog.makeSound();
    }
}

在上述代码中,Dog 类重写了 Animal 类的 makeSound 方法。在 main 方法中,分别创建了 Animal 对象、Dog 对象以及将 Dog 对象赋值给 Animal 类型的变量,然后调用 makeSound 方法,根据对象的实际类型,会调用相应的方法实现。

super 关键字

super 关键字用于在子类中访问父类的成员变量、成员方法和构造方法。

  1. 访问父类成员变量:当子类的成员变量与父类的成员变量同名时,可以使用 super 关键字来访问父类的成员变量。
  2. 调用父类成员方法:子类重写父类方法后,如果还需要调用父类的方法实现,可以使用 super 关键字。
  3. 调用父类构造方法:在子类的构造方法中,可以使用 super 关键字调用父类的构造方法,并且 super 调用必须是子类构造方法的第一行代码。
class Animal {
    String name;

    Animal(String name) {
        this.name = name;
    }

    void eat() {
        System.out.println(name + " is eating.");
    }
}

class Dog extends Animal {
    int age;

    Dog(String name, int age) {
        super(name);
        this.age = age;
    }

    @Override
    void eat() {
        super.eat();
        System.out.println(name + " is a dog and it's " + age + " years old.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("Buddy", 3);
        dog.eat();
    }
}

在上述代码中,Dog 类的构造方法通过 super(name) 调用了 Animal 类的构造方法来初始化 name 成员变量。Dog 类重写的 eat 方法中,先通过 super.eat() 调用了父类的 eat 方法,然后输出了狗特有的信息。

多态

多态的概念

多态是指同一个方法调用,根据对象的实际类型不同,会产生不同的行为。多态是通过继承和重写来实现的,它提高了代码的灵活性和可扩展性。

编译时多态(方法重载,Overload)

方法重载是指在同一个类中,允许存在多个同名方法,但这些方法的参数列表(参数个数、参数类型或参数顺序)必须不同。方法重载是在编译时确定调用哪个方法,因此也称为编译时多态。

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

    double add(double a, double b) {
        return a + b;
    }

    int add(int a, int b, int c) {
        return a + b + c;
    }
}

public class Main {
    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        int result1 = calculator.add(2, 3);
        double result2 = calculator.add(2.5, 3.5);
        int result3 = calculator.add(2, 3, 4);

        System.out.println("Result 1: " + result1);
        System.out.println("Result 2: " + result2);
        System.out.println("Result 3: " + result3);
    }
}

在上述代码中,Calculator 类有三个 add 方法,它们具有不同的参数列表,构成了方法重载。在 main 方法中,根据传递的参数类型和个数,编译器会选择合适的 add 方法进行调用。

运行时多态(方法重写,Override)

运行时多态是指在程序运行时,根据对象的实际类型来决定调用哪个类的重写方法。运行时多态需要满足以下条件:

  1. 存在继承关系。
  2. 子类重写了父类的方法。
  3. 父类类型的变量引用子类对象。
class Shape {
    void draw() {
        System.out.println("Drawing a shape.");
    }
}

class Circle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a circle.");
    }
}

class Rectangle extends Shape {
    @Override
    void draw() {
        System.out.println("Drawing a rectangle.");
    }
}

public class Main {
    public static void main(String[] args) {
        Shape shape1 = new Circle();
        Shape shape2 = new Rectangle();

        shape1.draw();
        shape2.draw();
    }
}

在上述代码中,CircleRectangle 类继承自 Shape 类,并重写了 draw 方法。在 main 方法中,Shape 类型的变量 shape1shape2 分别引用了 CircleRectangle 对象,在调用 draw 方法时,会根据对象的实际类型,调用相应子类的 draw 方法,体现了运行时多态。

抽象类与接口

抽象类

  1. 抽象类的定义:抽象类是一种不能被实例化的类,它通常用于作为其他类的基类,为子类提供一个通用的框架。抽象类可以包含抽象方法和具体方法。抽象方法是只有方法声明而没有方法体的方法,需要由子类来实现。
  2. 语法:使用 abstract 关键字来定义抽象类和抽象方法。
abstract class Animal {
    String name;

    // 抽象方法
    abstract void makeSound();

    // 具体方法
    void eat() {
        System.out.println(name + " is eating.");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks.");
    }
}

在上述代码中,Animal 类是一个抽象类,它有一个抽象方法 makeSound 和一个具体方法 eatDog 类继承自 Animal 类,并实现了 makeSound 抽象方法。

接口

  1. 接口的定义:接口是一种特殊的抽象类型,它只包含抽象方法(在Java 8及以上版本,接口还可以包含默认方法和静态方法)。接口不能被实例化,它用于定义一组方法的签名,实现接口的类必须实现接口中定义的所有方法。
  2. 语法:使用 interface 关键字来定义接口,使用 implements 关键字来实现接口。
interface Drawable {
    void draw();
}

class Circle implements Drawable {
    @Override
    void draw() {
        System.out.println("Drawing a circle.");
    }
}

class Rectangle implements Drawable {
    @Override
    void draw() {
        System.out.println("Drawing a rectangle.");
    }
}

在上述代码中,Drawable 是一个接口,它定义了一个抽象方法 drawCircleRectangle 类实现了 Drawable 接口,并实现了 draw 方法。

抽象类与接口的区别

  1. 抽象类可以有构造方法,接口不能有构造方法:抽象类的构造方法用于初始化子类继承的成员变量,而接口只是定义方法签名,不需要构造方法。
  2. 抽象类可以包含成员变量和具体方法,接口只能包含常量和抽象方法(Java 8 之前):在Java 8及以上版本,接口可以包含默认方法和静态方法,但成员变量只能是 public static final 类型的常量。
  3. 一个类只能继承一个抽象类,但可以实现多个接口:这使得接口在实现多继承方面更具灵活性。

内部类

成员内部类

  1. 定义:成员内部类是定义在另一个类内部的类,它作为外部类的一个成员存在。成员内部类可以访问外部类的所有成员,包括 private 成员。
  2. 语法
public class Outer {
    private int outerVar = 10;

    public class Inner {
        void display() {
            System.out.println("Outer variable: " + outerVar);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.display();
    }
}

在上述代码中,Inner 类是 Outer 类的成员内部类。在 main 方法中,先创建了 Outer 类的对象 outer,然后通过 outer.new Inner() 创建了 Inner 类的对象 inner,并调用了 innerdisplay 方法,该方法可以访问外部类 Outerprivate 成员变量 outerVar

静态内部类

  1. 定义:静态内部类是使用 static 关键字修饰的内部类。静态内部类不能直接访问外部类的非静态成员,因为静态内部类不依赖于外部类的实例。
  2. 语法
public class Outer {
    private static int outerStaticVar = 20;
    private int outerNonStaticVar = 30;

    public static class Inner {
        void display() {
            System.out.println("Outer static variable: " + outerStaticVar);
            // 以下代码会报错,不能访问外部类的非静态成员
            // System.out.println("Outer non - static variable: " + outerNonStaticVar);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Outer.Inner inner = new Outer.Inner();
        inner.display();
    }
}

在上述代码中,Inner 类是 Outer 类的静态内部类。在 main 方法中,直接通过 new Outer.Inner() 创建了静态内部类的对象,静态内部类的 display 方法可以访问外部类的静态成员变量 outerStaticVar,但不能访问非静态成员变量 outerNonStaticVar

局部内部类

  1. 定义:局部内部类是定义在方法内部的类,它的作用域仅限于该方法内部。局部内部类可以访问方法的局部变量,但这些局部变量必须是 final 类型(在Java 8及以上版本,实际上是 effectively final,即虽然没有显式声明为 final,但在使用过程中没有被重新赋值)。
  2. 语法
public class Outer {
    void outerMethod() {
        int localVar = 40;
        class Inner {
            void display() {
                System.out.println("Local variable: " + localVar);
            }
        }
        Inner inner = new Inner();
        inner.display();
    }
}

public class Main {
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.outerMethod();
    }
}

在上述代码中,Inner 类是定义在 outerMethod 方法内部的局部内部类。Inner 类的 display 方法可以访问 outerMethod 方法的局部变量 localVar

匿名内部类

  1. 定义:匿名内部类是没有名字的内部类,它通常用于创建只使用一次的类实例。匿名内部类必须继承一个类或实现一个接口。
  2. 语法
interface Drawable {
    void draw();
}

public class Main {
    public static void main(String[] args) {
        Drawable drawable = new Drawable() {
            @Override
            void draw() {
                System.out.println("Drawing anonymously.");
            }
        };
        drawable.draw();
    }
}

在上述代码中,通过 new Drawable() {... } 创建了一个实现 Drawable 接口的匿名内部类实例,并将其赋值给 drawable 变量。然后调用了 drawabledraw 方法。匿名内部类适用于创建临时的、只使用一次的对象。

通过以上对Java面向对象编程基础的详细介绍,包括类与对象、封装、继承、多态、抽象类与接口以及内部类等方面,希望能帮助读者深入理解Java面向对象编程的核心概念和技术,并能在实际开发中熟练运用。