Bootstrap

Java 随笔记: 面向对象编程(二)

目录

1. 包

2. 访问修饰符

3. 封装

4. getter和setter方法

5. Java Bean 类

6. 继承

7. super 关键字 

8. 方法重写

9. 多态

10. 动态绑定机制

11. Object 类


1. 包

在Java中,包(package)是一种组织代码的机制。它是一种将相关类、接口和其他代码组织在一起的方式,用于提供命名空间、可见性控制、代码组织和代码复用。

以下是关于Java中包的详细介绍:

  1. 包的命名规范

    • 包名由多个单词组成,每个单词用小写字母表示。
    • 包名的组织结构应该与文件系统中的目录结构相对应。
    • 通常使用域名倒序作为包名的前缀,以确保全局唯一性。
  2. 包的声明

    • 在Java源文件的开头使用package关键字声明包名。
    • 包声明语句应该在import语句之前,类声明语句之前。
    • 示例代码:
      package com.example.myapp;
      
      import com.example.myapp.utils.Utility;
      
      public class MyClass {
          // 类的定义
      }
      
  3. 包的导入
    • 使用import语句导入其他包中的类或接口。
    • 导入语句应该在类声明语句之前。
    • 示例代码:
      import java.util.ArrayList;
      import java.util.List;
      
      public class MyClass {
          // 类的定义
      }
      
  4. 包的访问控制

    • 默认情况下,同一个包中的类可以互相访问,默认访问权限是包级别。
    • 使用publicprivateprotected等关键字可以更改类和成员的访问权限。
    • 其他包中的类可以通过导入语句和类的全限定名进行访问。
  5. 包的层级结构

    • 包可以嵌套使用,形成层级结构。
    • 包使用点号.作为分隔符进行层级划分。
    • 包的层级结构可以反映项目的组织结构,便于代码的管理和查找。
  6. 包的使用

    • 使用方式:将相关的类和接口放在同一个包中,以便于组织和管理。
    • 利用包可以将类按照功能或模块进行划分,提高代码的可读性和可维护性。
    • 包可以作为命名空间,避免类名冲突。
  7. 默认包

    • 如果没有为类或接口指定包名,则它们被视为属于默认包。
    • 默认包是没有包名的类和接口所属的包。
    • 尽可能避免使用默认包,因为它会造成命名冲突和可维护性的问题。
  8. 常见的Java包

    • Java提供了许多常见的包,用于实现各种功能和提供各种库。
    • 一些常见的Java包包括java.langjava.utiljava.iojava.net等。
    • java.lang是Java的核心包,包含了Java的基本类和接口,如ObjectString等。这些类和接口是所有Java程序的基础,无需显示地导入就可以使用。
    • java.util提供了各种实用工具类,如集合、日期、正则表达式等。
    • java.io用于处理输入输出操作,如文件读写、流操作等。
    • java.net用于网络编程,提供了网络通信的类和接口。

合理使用包可以提高代码的可读性、可维护性和可扩展性,避免类名冲突,提供了命名空间的概念。通过包的层次结构,可以更好地组织和管理代码,使代码结构更加清晰和有序。

2. 访问修饰符

在Java中,访问修饰符(Access Modifier)用来控制类、变量、方法和构造函数的访问权限。Java提供了四种访问修饰符:public、protected、private和默认(package-private)。

  1. public:public是最高级别的访问修饰符,它指示某个类、变量、方法或构造函数可以被任何其他类访问。即公共的,可以从任何地方访问。

  2. protected:protected修饰符指示某个类、变量、方法或构造函数只能被其子类访问,以及在同一个包内的任何类。protected成员在子类中可见,但在包外部不可见。

  3. private:private是最低级别的访问修饰符,它指示某个类、变量、方法或构造函数只能被定义它们的类访问。私有成员只能在其所在的类中访问,其他类无法访问。

  4. 默认(package-private):默认访问修饰符在Java中是没有关键字来表示的,它指示某个类、变量、方法或构造函数可以被同一个包中的其他类访问,但对于不在同一个包中的类是不可见的。如果没有使用任何访问修饰符(即没有使用public、protected或private),则默认访问修饰符自动应用。

访问级别访问控制修饰符同类同包子类不同包
公开public
受保护protected
默认
私有private

访问修饰符的使用可以帮助我们控制类的封装性和访问范围,从而保证程序的安全性和可维护性。以下是一些使用访问修饰符的示例:

public class PublicClass {
    public int publicVariable;
    public void publicMethod() {
        // 可以在任何地方访问
    }
}

class DefaultClass {
    int defaultVariable; // 默认访问修饰符
    void defaultMethod() {
        // 只能在同一个包内访问
    }
}

class ProtectedClass {
    protected int protectedVariable; // 受保护的成员
    protected void protectedMethod() {
        // 只能在同一个包内或子类中访问
    }
}

class PrivateClass {
    private int privateVariable; // 私有成员
    private void privateMethod() {
        // 只能在当前类中访问
    }
}

需要注意的是,访问修饰符的使用应该根据具体的情况进行选择,以保证类和成员的访问范围符合需求,并且遵循封装原则。

3. 封装

封装是面向对象编程中的一个重要概念,它指的是将数据和对数据的操作封装在一个类中,通过定义访问方法来控制对数据的访问。封装可以隐藏数据的实现细节,只暴露需要给外部使用的接口,提高代码的安全性和可维护性。

Java中的封装通过访问修饰符和访问方法实现。通常将成员变量声明为private,限制直接访问,然后使用public的访问方法来访问和修改成员变量的值

下面是一个简单的示例:

public class MyClass {
    private int myVar;

    public int getMyVar() {
        return myVar;
    }

    public void setMyVar(int value) {
        myVar = value;
    }
}

在这个示例中,myVar被声明为private,外部类无法直接访问它。通过getMyVar()方法可以获取myVar的值,而setMyVar()方法可以设置myVar的值。这样,外部类在使用MyClass时只能通过这两个方法来访问和修改myVar的值,而无法直接操作。

封装的优点有:

  1. 数据隐藏:封装可以隐藏类的内部实现细节,只暴露必要的接口,减少了对外部的依赖,提高了数据的安全性。
  2. 代码重用:封装可以将数据和操作封装在一个类中,方便代码的重用,提高了代码的可维护性和可读性。
  3. 灵活性:通过封装,可以在不改变外部代码的情况下修改类的内部实现,提高了代码的灵活性。
  4. 可扩展性:封装可以隐藏类的内部细节,当需要扩展功能时,只需在类的内部进行修改,不会影响到外部代码。

封装是面向对象编程的重要原则之一,它使得代码更加模块化、可复用和可维护。在设计类时,应该考虑使用封装来隐藏数据和实现细节,提供必要的访问方法来操作数据,以提高代码的质量和可扩展性。

4. getter和setter方法

getter和setter方法是Java编程中常用的一种封装技术,用于对类的私有属性进行访问和修改。这两种方法通常被称为访问器(Accessor)和修改器(Mutator)

  1. getter方法

    • getter方法用于获取类的私有属性的值,并将其返回。
    • getter方法的命名规范是以"get"开头,后面跟着属性的名称,且首字母大写。例如,如果有一个私有属性叫做"age",则对应的getter方法应该命名为"getAge"。
    • getter方法一般是公共方法(public),可以被其他类调用。
    • getter方法通常不接收任何参数,只返回属性的值。
  2. setter方法

    • setter方法用于修改类的私有属性的值。
    • setter方法的命名规范是以"set"开头,后面跟着属性的名称,且首字母大写。例如,如果有一个私有属性叫做"age",则对应的setter方法应该命名为"setAge"。
    • setter方法一般是公共方法(public),可以被其他类调用。
    • setter方法通常接收一个参数,用于将属性设置为指定的值。参数的类型应与属性的类型一致。

使用getter和setter方法的好处有:

  • 封装性:通过将属性设置为私有的,可以控制对属性的访问。其他类只能通过getter和setter方法来访问和修改属性的值,从而保护属性的安全性。
  • 可控性:通过setter方法,可以对属性进行校验和逻辑控制,确保属性的合法性和一致性。
  • 可读性:通过getter方法,可以提供属性的值,方便其他类获取并使用。

下面是一个简单示例,说明如何使用getter和setter方法:

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

    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 && age <= 120) {
            this.age = age;
        } else {
            System.out.println("Invalid age value.");
        }
    }
}

// 在其他类中使用getter和setter方法:
public class Main {
    public static void main(String[] args) {
        Person person = new Person();
        person.setName("Tom");
        person.setAge(25);

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

以上示例中,Person类有两个私有属性name和age,通过getter和setter方法提供对这两个属性的访问和修改。在Main类中,通过调用setter方法设置属性的值,再通过getter方法获取属性的值,并打印输出。

5. Java Bean 类

Java Bean类是一种符合Java语言编码规范的特殊类,用于封装数据和行为的组件。Java Bean类通常具有私有属性、公共的getter和setter方法以及无参构造函数

Java Bean类的特点如下:

  1. 属性:Java Bean类通常包含私有的成员变量,用于存储对象的状态或数据。这些属性可以是基本类型(如int、double等),也可以是自定义类型(如其他类或接口)。属性的命名通常采用驼峰命名法,例如"firstName"或"age"。

  2. getter和setter方法:Java Bean类提供公共的getter和setter方法来访问和修改属性的值。getter方法用于获取属性的值,setter方法用于设置属性的值。通过getter和setter方法,可以控制对属性的访问,并添加逻辑判断。

  3. 无参构造函数:Java Bean类应该提供一个无参的构造函数,以便其他程序可以通过实例化对象来调用该类的方法。这个构造函数通常是公共的,并且不执行任何具体的操作。无参构造函数使得Java Bean类可以通过反射创建对象。

  4. 序列化支持:Java Bean类通常需要支持序列化,以便对象可以在网络传输或存储到文件中。为此,Java Bean类应该实现java.io.Serializable接口。

使用Java Bean类的主要好处包括:

  • 封装数据:Java Bean类可以将相关的数据封装在一个对象中,便于统一管理和传递。
  • 数据一致性:通过添加逻辑判断和保证数据完整性的代码到setter方法中,可以确保数据的一致性。
  • 可重用性:Java Bean类可以被多个程序模块或组件使用,提高了代码的重用性。
  • 可读性和可维护性:Java Bean类通过提供清晰的接口,使得代码更加可读和可维护。

总之,Java Bean类是一种用于封装数据和行为的特殊类。它具有私有属性、公共的getter和setter方法以及无参构造函数。Java Bean类的主要作用是封装数据和行为,并提供清晰的接口,提高代码的可读性、可维护性和可重用性。

6. 继承

继承是面向对象编程中的一种重要概念,它允许一个类继承另一个类的属性和方法。在Java中,继承是通过使用关键字"extends"来实现

继承的主要概念如下:

  1. 父类和子类:在继承关系中,被继承的类称为父类(或超类、基类),继承该类的类称为子类(或派生类)。父类中定义的属性和方法可以被子类继承和使用。

  2. extends关键字:在Java中,使用关键字"extends"来声明一个类继承另一个类。例如,如果有一个父类叫做"Animal"和一个子类叫做"Cat",则子类可以声明为"public class Cat extends Animal"。

  3. 继承的特点

    • 子类可以继承父类的属性和方法。这意味着子类可以使用父类中的成员变量和方法,而不需要重新定义。
    • 子类可以扩展父类的功能。子类可以添加新的属性和方法,或者重写父类中的方法,以满足自己的需求。
    • 子类可以实现多态性。通过继承,子类对象可以被当做父类对象使用,可以直接赋值给父类类型的变量或作为参数传递给父类类型的方法。
    • 子类的访问权限不能超过父类。子类可以继承父类的公共和受保护的成员,但不能继承父类的私有成员。
    • 子类无法继承父类的私有方法和构造方法。子类也无法继承父类的静态成员变量和静态方法。子类可以继承父类的私有属性,但不能调用。此外,父类的构造方法不可以被子类直接调用,只能在子类的构造方法中通过使用super关键字来调用。
  4. 方法重写:子类可以重写父类中的方法,以便根据自己的需求提供新的实现。方法重写使用@Override注解来标识,子类方法的签名(名称和参数)必须与父类方法一致。

  5. super关键字:在子类中,可以使用super关键字来调用父类中的属性和方法。super关键字可以用于引用父类的构造函数、成员变量和成员方法。

继承的好处在于代码的重用和扩展。通过继承,可以避免在不同的类中重复定义相同的属性和方法,提高了代码的复用性。同时,通过继承,可以在子类中扩展父类的功能,实现更丰富的业务逻辑。

需要注意的是,继承应该符合"is-a"的关系,即子类是父类的一种特殊类型。继承关系应该遵循单一继承原则,即一个类只能直接继承一个父类。在Java中,一个类可以同时实现多个接口,从而实现多重继承的效果

7. super 关键字 

在Java中,super是一个关键字,用于引用父类的成员变量、成员方法和构造方法

使用super关键字有以下几种情况:

  1. 访问父类的成员变量:当子类与父类有同名的成员变量时,可以使用super关键字来引用父类的成员变量。

    例如:

    public class Parent {
       int age = 30;
    }
    public class Child extends Parent {
       int age = 20;
    
       public void display() {
          System.out.println(super.age); // 输出父类的age变量值
          System.out.println(this.age); // 输出子类的age变量值
       }
    }
    
  2. 调用父类的成员方法:当子类与父类有同名的成员方法时,可以使用super关键字来调用父类的成员方法。

    例如:

    public class Parent {
        public void display() {
            System.out.println("This is parent class");
        }
    }
    public class Child extends Parent {
        public void display() {
            super.display(); // 调用父类的display方法
            System.out.println("This is child class");
        }
    }
    
  3. 调用父类的构造方法:当子类需要在自己的构造方法中调用父类的构造方法时,可以使用super关键字来调用父类的构造方法。

    例如:

    public class Parent {
        public Parent(String name) {
            System.out.println("Parent's name is " + name);
        }
    }
    public class Child extends Parent {
        public Child(String name, int age) {
            super(name); // 调用父类的构造方法
            System.out.println("Child's age is " + age);
        }
    }
    

在使用super关键字时,有以下几点需要注意:

  • super关键字必须在子类中使用,并且只能在子类的非静态方法中使用。

  • super关键字只能访问父类的直接成员,不能访问父类的私有成员。

  • super关键字必须在子类的构造方法的第一行显示调用。

  • super关键字只能用于访问直接父类的成员,无法用于访问更高层次的父类成员。 

8. 方法重写

方法重写(Method overriding)是指子类对父类已经存在的方法进行重新定义或重新实现。在Java中,方法重写主要有以下几个特点:

  1. 子类必须继承父类,并且子类中的重写方法必须与父类中的被重写方法具有相同的名称、参数列表和返回类型。

  2. 重写方法不能比父类的方法更严格,即重写方法的访问修饰符不能比父类的方法更低(例如,父类的方法是public,子类的重写方法不能是private)。

  3. 重写方法的异常类型不能比父类的方法更多,即重写方法抛出的异常类型不能比父类方法抛出的异常类型更广泛。

  4. 静态方法不能被重写,只能被隐藏

  5. 构造方法不能被重写

当子类和父类中具有相同名称和参数列表的方法时,子类对象调用该方法时会优先调用子类中的方法。这种优先调用子类方法而不是父类方法的机制称为方法重写。

方法重写的目的是为了实现多态性。利用方法重写,可以在不改变方法名称和参数列表的情况下,为不同的对象实现不同的行为。

下面是一个简单的示例代码:

// 父类
class Animal {
    public void sound() {
        System.out.println("动物发出声音");
    }
}

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

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.sound(); // 输出:动物发出声音

        Dog dog = new Dog();
        dog.sound(); // 输出:狗发出汪汪声

        Animal animal2 = new Dog();
        animal2.sound(); // 输出:狗发出汪汪声
    }
}

在上述示例代码中,子类Dog重写了父类Animal中的sound方法,并实现了不同的行为。当父类引用指向子类对象时,调用sound方法时会优先调用子类中的方法。

需要注意的是,方法重写只能针对非静态方法,静态方法不能被重写,只能被隐藏(即在子类中定义一个与父类中的静态方法相同的静态方法)

9. 多态

在Java中,多态是面向对象编程的一个重要概念,允许不同类型的对象对同一个方法做出不同的响应。多态性有两种形式:编译时多态和运行时多态。

  1. 编译时多态(Compile-time Polymorphism)

    • 发生在编译阶段。
    • 根据方法的参数列表和返回类型确定调用哪个方法
    • 方法重载是一种体现,即在同一个类中定义多个方法,具有相同名称但参数列表不同。
    • 当调用一个重载方法时,编译器会根据传入的参数的类型和数量来确定使用哪个重载方法。
  2. 运行时多态(Runtime Polymorphism)

    • 发生在程序运行时。
    • 根据对象的实际类型确定调用哪个方法
    • 方法重写和接口实现是体现多态性的方式。
    • 方法重写:子类可以重写父类的方法,通过父类引用指向子类对象时,根据对象实际类型调用对应子类方法。
    • 接口实现:类实现接口时,必须实现接口定义的方法,通过接口引用指向实现类对象时,根据对象实际类型调用对应实现类方法。
    • 运行时多态是通过虚拟方法表(Virtual Method Table)来实现的,在运行时根据对象的实际类型来动态绑定调用的方法。​​​​

在Java中实现多态性的关键概念和语法包括

  • 继承:子类继承父类的属性和方法,可以通过父类引用访问继承的属性和方法。
  • 方法重写:子类可以重写父类的方法,在运行时根据对象实际类型调用对应子类方法。
  • 向上转型:子类对象赋值给父类引用变量,实现对不同子类对象的统一处理。
  • 向下转型:父类引用转换为子类引用,访问子类特有方法和属性。
  • 动态绑定:运行时确定具体调用哪个方法,根据对象实际类型确定执行方法。
  • 抽象类和接口:抽象类包含抽象方法,接口定义一组方法规范,实现类必须实现接口中定义的方法。

以下是一个简单的Java代码示例,演示了多态性的应用:

// 父类
class Animal {
    String name = "Animal";

    public void makeSound() {
        System.out.println("Animal makes a sound");
    }
}

// 子类
class Dog extends Animal {
    String name = "Dog";

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

    public void wagTail() {
        System.out.println("Dog wags tail");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal dog; // 编译类型为Animal
        dog = new Dog(); // 运行类型为Dog

        // 成员变量访问
        System.out.println(dog.name); // 输出 "Animal",因为成员变量访问不具有多态性

        // 向上转型
        dog.makeSound(); // 调用的是Dog类的makeSound方法

        // 向下转型
        Dog specificDog = (Dog) dog;
        specificDog.wagTail(); // 可以访问Dog类特有的方法
    }
}

在多态中,成员变量的访问是根据引用变量的类型而不是对象的实际类型决定的。这是因为成员变量是静态绑定的,也就是在编译时期就已经确定了。 

编译类型(Compile-time type)是指在编译时确定的变量类型,即变量声明时的类型。它决定了编译器在编译时可以调用的成员方法和成员变量

运行类型(Runtime type)是指在运行时实际对象的类型,即通过new关键字创建的对象的类型。它决定了Java虚拟机在运行时具体执行的方法。

需要注意的是,多态性只适用于实例方法,而不适用于静态方法和私有方法

通过多态性,我们可以通过父类引用变量来引用子类对象,从而实现对不同子类对象的统一处理。这提供了代码的灵活性,可以根据具体情况执行不同的方法。

10. 动态绑定机制

Java中的动态绑定是指在运行时根据对象的实际类型来确定应调用的方法的机制。这意味着在编译时无法确定要调用的方法是哪一个,而是要等到运行时进行确定

Java中实现动态绑定的关键是通过继承和方法重写来实现。当一个子类继承了一个父类并重写了父类的方法时,当调用该方法时,会根据实际的对象类型来确定调用子类的方法还是父类的方法。

具体来说,Java中的动态绑定机制遵循以下规则

  1. 父类对象可以引用子类对象,但是子类对象不能引用父类对象。
  2. 在编译时,编译器会根据引用变量的类型决定要调用的方法。如果引用变量是父类类型,那么调用的是父类的方法;如果引用变量是子类类型,那么调用的是子类的方法。
  3. 在运行时,JVM会根据实际对象的类型来确定调用的方法。如果实际对象是子类类型,那么会调用子类的方法。如果实际对象是父类类型,那么只能调用父类的方法,而无法调用子类重写的方法。
  4. 如果子类重写了父类的方法,那么在调用该方法时,只会调用子类的方法,不会调用父类的方法。

动态绑定的好处是可以在运行时根据对象的类型进行多态的调用,提高代码的灵活性和可维护性。但是也需要注意,在使用动态绑定时要确保继承关系正确,并且在方法重写时要满足重写规则,以避免出现意外的结果。

11. Object 类

在Java中,Object类是所有类的根类它是一个预定义的类,位于java.lang包中

Object类有以下特征:

  1. toString方法:Object类重写了toString方法,返回一个对象的字符串表示。通常情况下,toString返回的字符串由类名、@符号和对象的哈希码组成。如果需要自定义对象的字符串表示,可以在具体的类中重写该方法。

  2. equals方法:Object类的equals方法用于比较两个对象是否相等。默认情况下,equals方法比较的是对象的内存地址,即两个对象引用是否指向同一个内存地址。如果需要比较对象的内容是否相等,可以在具体的类中重写该方法。

  3. hashCode方法:Object类的hashCode方法用于返回一个对象的哈希码。默认情况下,hashCode方法返回的是对象的内存地址的整数形式。如果需要根据对象的内容生成哈希码,可以在具体的类中重写该方法。

  4. getClass方法:Object类的getClass方法用于返回一个对象的运行时类。该方法返回一个Class对象,可以通过该对象获取类的信息。

  5. finalize方法:Object类定义了一个空的finalize方法。该方法在垃圾回收器确定一个对象没有被引用时被调用。可以在具体的类中重写该方法,进行一些资源释放的操作。

  6. wait、notify和notifyAll方法:Object类定义了这些方法,用于实现线程之间的通信。wait方法使当前线程等待,直到接收到notify或notifyAll方法的通知。notify方法用于唤醒因调用wait方法而等待的线程。

除了以上常用的方法,Object类还提供了一些其他方法,如clone方法用于复制对象、getClassLoader方法用于获取类的加载器、finalize方法用于垃圾回收等。

由于所有类都直接或间接地继承自Object类,因此可以在所有的Java类中使用Object类提供的方法。这使得Object类成为Java中非常重要的一个类。

;