Java 基础学习(六)对象数组、继承与访问控制

2023-12-13 09:55:08

1 对象数组

1.1 对象类型数组

1.1.1 什么是对象数组

在Java中,对象数组是指由对象类型的元素组成的数组。与基本类型数组不同,对象数组可以存储对象的引用。

对象数组的声明和初始化方式与基本类型数组类似。例如,可以使用以下语法声明和创建一个对象数组:

ClassName[] arrayName = new ClassName[arrayLength];

其中,ClassName 是指对象类型的类名,arrayName 是对象数组的名称,arrayLength 是数组的长度。

对象数组在Java中具有广泛的用途。它们允许存储和操作多个对象的引用,使代码更加灵活和可扩展。无论是存储对象集合、实现数据结构还是进行排序和搜索,对象数组都是一个重要的数据结构。

1.1.2初始化对象数组

当我们创建一个对象数组时,数组中的每个元素会被初始化为对象的默认值。对象数组的默认值是 null,表示数组中的元素尚未被赋予有效的对象引用。

例如:

Student[ ]    students = new  Student [ 3 ] ;

其内存分配如下图所示:

?

从上图示可以看出,new Student[4]实际是分配了4个空间用于存放4个Student类型的引用,并赋初始值为null,而并非是分配了4个Student类型的对象。

对象数组的初始化可以通过两种方式进行:静态初始化和动态初始化。

1.1.3静态初始化

静态初始化:在静态初始化中,我们可以直接为对象数组的每个元素赋值。在初始化时,可以提供具体的对象引用,将其分配给数组的每个位置。这种方式在声明数组时进行。

Student[] students = {new Student("Alice"), new Student("Bob"), new Student("Carol")};

在上述示例中,我们创建了一个 Student 类的对象数组 students,并使用静态初始化方式为每个元素赋值。

?

1.1.4动态初始化

动态初始化:在动态初始化中,我们先创建一个指定长度的对象数组,然后逐个为每个元素分配对象的引用。这种方式需要使用 new 关键字来分配数组的内存空间,并使用构造函数为每个元素创建对象。

Student[] students = new Student[3];
students[0] = new Student("Alice");
students[1] = new Student("Bob");
students[2] = new Student("Carol");

在上述示例中,我们首先创建了一个长度为3的 Student 类的对象数组 students,并随后为每个元素分配了具体的对象引用。

无论是静态初始化还是动态初始化,对象数组中的每个元素在创建时都会被初始化为默认值 null。在初始化之后,我们可以通过赋值操作将具体的对象引用存储到数组的每个位置。

需要注意的是,当我们访问未初始化或为 null 的数组元素时,会抛出 NullPointerException 异常。因此,在使用对象数组之前,务必确保数组中的每个元素已经被正确地初始化。

1.1.5【案例】使用对象数组管理一组学生

下面是一个示例,展示了如何使用对象数组来管理一组学生,每个学生具有姓名、年龄、性别和成绩属性。

public class ObjectArrayDemo {
    public static void main(String[] args) {
        // 创建学生对象数组
        Student[] students = new Student[3];
        students[0] = new Student("Alice", 18, 'F', 85.5);
        students[1] = new Student("Bob", 20, 'M', 90.0);
        students[2] = new Student("Carol", 19, 'F', 92.5);
        // 遍历并打印学生信息
        for (int i = 0; i < students.length; i++) {
            Student student = students[i];
            System.out.println("Name: " + student.name);
            System.out.println("Age: " + student.age);
            System.out.println("Gender: " + student.gender);
            System.out.println("Score: " + student.score);
            System.out.println();
        }
    }
}
class Student {
    String name;
    int age;
    char gender;
    double score;
    public Student(String name, int age, char gender, double score) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.score = score;
    }
}

在上述示例中,我们定义了一个 Student 类,该类包含了学生的姓名、年龄、性别和成绩属性。我们使用构造函数来初始化学生对象的属性。

在 main 方法中,我们创建了一个长度为3的学生对象数组 students,并使用动态初始化为每个元素赋值。然后,使用 for循环遍历数组,并通过调用对象的访问方法打印每个学生的信息。

2 继承

2.1 利用继承复用代码

2.1.1 什么是继承

继承是面向对象编程中的一种重要概念,它允许一个类(称为子类或派生类)从另一个类(称为父类或基类)继承属性和方法。通过继承,子类可以获得父类的特性,并且可以添加自己特定的特性,形成类之间的层次关系。

继承的主要目的是实现代码的重用和扩展。在之前的学生和教师的案例中,我们发现学生和教师类有很多共同的属性和方法,比如姓名、年龄和性别。如果每次都要重新定义这些共同的属性和方法,会导致代码冗余,增加开发和维护的工作量。而通过继承,我们可以将这些共同的属性和方法定义在父类中,子类通过继承获得了父类的属性和方法,无需重复编写,可以减少代码的冗余。

?

另外,子类还可以添加自己特定的属性和方法,以满足特定需求。例如,学生类可以添加学生特有的属性和方法,比如学号、选课等,而教师类可以添加教师特有的属性和方法,比如工号、授课等。这样,继承不仅实现了代码的重用,还实现了代码的扩展性,使得我们可以根据具体需求定制不同的子类。

继承还支持多层次的关系,即一个类可以作为另一个类的子类,同时又可以作为其他类的父类。这种层次结构可以形成类的继承链,使得代码更加灵活和可扩展。通过继承链,子类可以继承父类的属性和方法,同时作为父类为其他子类提供了基础。

继承是面向对象编程中的重要概念,它通过建立类之间的层次关系,实现了代码的重用和扩展。子类可以继承父类的属性和方法,并且可以添加自己特定的特性和行为,从而形成类之间的层次结构。继承提供了一种灵活而强大的机制,让我们能够更好地组织和管理代码,提高开发效率和代码质量。

2.1.2 利用泛化设计父类

如何设计父类呢,如何利用继承复用代码,解决代码重复问题呢?可以利用泛化把多个类型的公共属性和方法抽取到一个公共父类中。

泛化是面向对象编程中的一种关系,它描述了一般性和特殊性之间的关系。通过泛化,我们可以识别出多个类之间共享的属性和方法,并将它们提取到一个公共的父类中。这样,子类可以继承父类的属性和方法,并且可以添加自己特定的属性和方法。

泛化的过程涉及以下几个步骤:

  • 识别共享的属性和方法:观察多个类之间的相似性,找出它们共同具有的属性和方法。
  • 设计父类:创建一个父类,将共享的属性和方法定义在父类中。父类应该抽象和通用化,不涉及具体的业务逻辑。
  • 子类继承:让需要共享属性和方法的类继承父类。子类可以继承父类的属性和方法,并且可以添加自己特定的属性和方法。
  • 使用继承的类:通过创建子类的对象来使用继承的类。可以使用子类对象调用父类的方法,并且可以在子类中调用或重写父类的方法。

?

泛化的好处在于代码的重用和简化。通过抽取共享的属性和方法到父类中,我们避免了重复编写相似的代码,提高了代码的复用性。同时,当需要修改或扩展共享的属性和方法时,只需要在父类中进行修改,所有的子类都会继承这些变更,减少了维护的工作量。

总而言之,泛化是一种将多个类型的公共属性和方法抽取到一个公共父类中的过程。它通过继承的方式实现代码的重用和扩展,提高了代码的灵活性和可维护性。

2.1.3【案例】使用泛化设计父类

我们现在有两个类,一个是Student(学生),一个是Teacher(老师),它们都有共同的属性:姓名(name)、年龄(age)、性别(gender):

class Student {
    String name;
    int age;
    char gender;
    double score;
    public Student(String name, int age, char gender, double score){
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.score = score;
    }
}
class Teacher{
    String name;
    int age;
    char gender;
    double salary;
    public Teacher(String name, int age, char gender, double salary){
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.salary = salary;
    }
}

在现有的代码中,我们可以识别出这些共同的属性,并将它们泛化到一个名为Person(人)的父类中。

首先,我们创建一个Person类,并将共同的属性添加到该类中:

class Person{
    String name;
    int age;
    char gender;
}

接下来,我们重构Student和Teacher类,让它们继承自Person类,并在构造器中初始化继承的属性:

class Student extends Person{
    double score;
    public Student(String name, int age, char gender, double score){
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.score = score;
    }
}
class Teacher extends Person{
    double salary;
    public Teacher(String name, int age, char gender, double salary){
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.salary = salary;
    }
}

现在,我们可以创建Student和Teacher的对象,并测试它们从父类Person继承的属性:

Student s1 = new Student("Tom", 12, '男', 99.5);
Teacher t1 = new Teacher("Jack", 30, '男', 10000.0);
//测试学生继承的属性
System.out.println(s1.name);
System.out.println(s1.age);
//测试老师继承的属性
System.out.println(t1.name);
System.out.println(t1.age);
//测试学生特有的属性
System.out.println(s1.score);
//测试老师特有的属性
System.out.println(t1.salary);

通过泛化和继承的方式,我们成功地将学生和老师类中共同的属性提取到了父类Person中。子类继承了父类的属性,并且可以添加自己特定的属性。这样做的好处在于减少了重复代码的编写,提高了代码的复用性,并且使得代码更加易于维护和扩展。

以上就是利用泛化设计父类的步骤和示例代码。通过这个案例,我们可以更好地理解继承和泛化的概念,以及它们在面向对象编程中的作用。

2.1.4 Java继承语法

在Java语言中,继承是通过使用关键字"extends"实现的。继承的语法结构为:子类名 extends 父类名。通过继承,子类可以继承父类的成员变量和成员方法,并且可以添加自己特定的成员变量和成员方法。

需要注意的是,Java语言不支持多重继承,一个类只能继承一个父类,但一个父类可以有多个子类。

继承的特性包括:

  • 单继承:Java不支持类的多继承,这意味着一个类只能有一个超类(父类),但一个超类可以有多个子类。这种单一继承的特性保证了类之间的层次结构清晰明确。
  • 传递性:子类不仅可以继承直接父类的属性和方法,还可以继承父类所继承的其他父类的属性和方法。这种传递性的特性使得类的继承关系形成了一个层次结构,子类可以逐级继承父类的特性,使代码的复用更加灵活和高效。

继承的示意图如下所示:

?

通过继承,子类可以继承父类的属性和方法,同时也可以添加自己特定的属性和方法。这种继承关系使得代码更加模块化和可维护,减少了代码的冗余和重复编写。

继承是面向对象编程中重要的概念,它允许类之间建立层次关系,实现代码的重用和扩展。在设计和开发过程中,合理地使用继承可以提高代码的可读性、可维护性和可扩展性。

2.1.5【案例】继承的传递性示例

下面是一个示例代码,用于演示继承的传递性:

public class ExtendsDemo3 {
    public static void main(String[] args) {
        /*
         * 继承的传递性
         */
        Coo coo = new Coo();
        System.out.println(coo.a);
        System.out.println(coo.b);
        System.out.println(coo.c);
    }
}
class  Aoo{
    int a;
}
class Boo extends Aoo{ // Boo继承自Aoo,拥有a属性
    int b;
}
class Coo extends Boo{ // Coo继承自Boo,拥有a和b属性,新增c属性
    int c;
}

在上述代码中,我们定义了三个类:Aoo、Boo和Coo。其中,Boo继承自Aoo,Coo继承自Boo。

在主程序中,我们创建了一个Coo对象,并打印了其继承的属性a、b和新增的属性c的值。

由于继承具有传递性,Coo类继承了Boo类的所有属性和方法,而Boo类又继承了Aoo类的属性和方法。因此,Coo类实际上拥有了Aoo类的属性a、Boo类的属性b以及新增的属性c。

通过创建Coo类的对象,我们可以访问和操作这些继承的属性,实现了代码的复用和扩展。

继承的传递性使得类之间的关系更加清晰和有序,提供了一种便捷的方式来组织和管理代码。同时,继承还提供了一种灵活的机制,可以在子类中添加新的属性和方法,以满足特定的需求。

2.2 继承中的构造器

2.2.1 重用构造器

在上述案例中,我们使用继承将共享的属性和方法抽取到了父类Person中。然而,我们也注意到子类的构造器中出现了重复的冗余代码,即对父类属性的初始化。

Java中的构造器是不能被继承的,子类不能直接继承父类的构造器。但是,Java提供了一种机制让子类能够调用父类的构造器,从而解决这个问题。

子类可以使用关键字super调用父类的构造器。通过调用父类的构造器,可以将公共属性的初始化代码抽取到父类中,然后子类只需调用父类的构造器,而不需要再重复初始化父类的属性。

2.2.2【案例】重用构造器示例

下面是一个修改后的案例示例:

class Person{
    String name;
    int age;
    char gender;
    public Person(String name, int age,
                  char gender){
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
}
class Student extends Person {
    double score;
    public Student(String name, int age, char gender, double score) {
        super(name, age, gender); // 调用父类的构造器
        this.score = score;
    }
}
class Teacher extends Person {
    double salary;
    public Teacher(String name, int age, char gender, double salary) {
        super(name, age, gender); // 调用父类的构造器
        this.salary = salary;
    }
}

在修改后的代码中,我们通过在子类的构造器中使用super关键字调用父类的构造器。通过这种方式,子类可以继承父类的属性,并且将属性的初始化工作交给父类的构造器来完成。

通过使用super调用父类的构造器,我们避免了重复编写相似的属性初始化代码,提高了代码的复用性和可维护性。

总结起来,通过在子类的构造器中使用super关键字调用父类的构造器,可以将公共属性的初始化代码抽取到父类中,实现代码的重用和简化。这样,子类只需关注自己特定的属性和逻辑,提高了代码的可读性和可维护性。

2.2.3 关于 super() 方法

在继承关系中,super() 方法用于子类调用父类的构造器。

下面是关于 super() 方法的一些要点:

  • super() 方法在子类的构造器中使用。
  • 如果在子类的构造器中没有显式地写上 super(),编译器会自动添加一个默认的 super() 调用,用于调用父类的无参数构造器。但是,如果父类没有无参构造器,会导致编译错误。
  • 通过 super(参数) 的形式,可以调用父类中的有参数构造器。
  • super() 方法必须写在子类构造器的第一行,即在子类构造器执行之前调用父类的构造器。

使用 super() 方法可以在子类构造器中显式地调用父类的构造器,以完成父类的属性的初始化工作。

总结起来,super() 方法用于子类调用父类的构造器,可以在子类构造器中使用,用于完成父类属性的初始化。它可以自动调用父类的无参构造器,也可以通过指定参数来调用父类的有参数构造器。注意,super() 方法必须写在子类构造器的第一行。

2.2.4【案例】调用父类构造器

在继承关系中,如果父类没有无参数构造器,而子类又需要调用父类的构造器进行属性的初始化,可以使用 super(参数) 的形式来显式地调用父类的有参数构造器。

下面是一个示例,展示了父类没有无参数构造器时,如何使用 super(参数) 调用父类的有参数构造器:

class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }
}
class Student extends Person {
    int age;
    public Student(String name, int age) {
        super(name);  // 调用父类的有参数构造器
        this.age = age;
    }
}

在这个示例中,父类 Person 没有无参数构造器,而子类 Student 需要使用父类的构造器进行属性的初始化。通过使用 super(name),子类的构造器调用了父类的有参数构造器来初始化父类的属性。这样就实现了子类对父类属性的初始化。

通过这种方式,即使父类没有无参数构造器,我们仍然可以在子类中正确地进行属性的初始化。

?

请注意,当父类没有无参数构造器时,子类的构造器必须显式地调用父类的构造器,否则会导致编译错误。

2.3 方法的继承

2.3.1 利用继承复用方法

利用继承可以实现方法的复用,子类可以继承父类的方法并在子类中直接使用。这样可以避免在子类中重复编写相同的方法,提高了代码的复用性和可维护性。

下面是一个示例,展示了子类如何继承父类的方法:

class Person{
    String name;
    int age;
    char gender;
    public Person(String name, int age,
                  char gender){
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    public void printInfo(){
        System.out.println("姓名:" + name);
        System.out.println("年龄:" + age);
        System.out.println("性别:" + gender);
    }
}
class Student extends Person{
    double score;
    public Student(String name, int age,
                   char gender, double score){
        super(name, age, gender); //调用父类构造方法
        this.score = score;
    }
}
class Teacher extends Person{
    double salary;
    public Teacher(String name, int age,
                   char gender, double salary){
        super(name, age, gender); //调用父类构造方法
        this.salary = salary;
    }
}
public class Demo01 {
    public static void main(String[] args) {
        Student s1 = new Student("Tom", 12, '男', 99.5);
        Teacher t1 = new Teacher("Jack", 30, '男', 10000.0);
        //测试继承的方法
        s1.printInfo();
        t1.printInfo();
    }
}

在这个示例中,父类 Person 中定义了一个 printInfo() 方法,子类 Student 和 Teacher 继承了父类的方法。在 Demo01 类的 main 方法中,我们创建了一个 Student 对象和一个 Teacher 对象,并分别调用它们的 printInfo() 方法。由于子类继承了父类的方法,所以子类对象可以直接调用父类的方法,无需在子类中重复定义。

通过继承复用方法,我们可以避免在子类中重复编写相同的方法逻辑,提高了代码的效率和可维护性。同时,如果需要对继承的方法进行修改或扩展,只需要在父类中进行修改,所有的子类都会继承这些变更。这样可以减少代码的冗余,并且使得代码更加清晰和易于维护。

2.3.2 方法的重写

方法的重写(Override)是指子类在继承父类的方法后,对该方法进行修改或重新实现,以满足子类的特定需求。

在继承中,如果子类需要对父类的方法进行修改或定制化,可以使用方法重写。子类可以重写(覆盖)父类的方法,使得子类在调用该方法时执行子类自定义的逻辑。方法的重写保持了方法的名称、参数列表和返回类型相同,但方法体可以被子类重新定义。

方法重写的规则如下:

  • 子类的重写方法必须和父类的被重写方法具有相同的方法名、参数列表和返回类型。
  • 子类的重写方法的访问修饰符不能比父类的被重写方法的访问修饰符更严格(即不能降低访问权限)。
  • 子类的重写方法不能抛出比父类的被重写方法更宽泛的异常(可以抛出相同的异常或子类异常)。

2.3.3【案例】方法的重写

以下是一个示例,展示了方法的重写:

class Person {
    public void eat() {
        System.out.println("人在吃饭");
    }
}
class Student extends Person {
    @Override
    public void eat() {
        System.out.println("学生在食堂吃饭");
    }
}
public class Demo7 {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.eat(); // 输出:人在吃饭
        Student s1 = new Student();
        s1.eat(); // 输出:学生在食堂吃饭
    }
}

在这个示例中,父类 Person 定义了一个 eat() 方法,子类 Student 继承了父类的方法并进行了重写。在 Demo7 类的 main 方法中,我们分别创建了一个 Person 对象和一个 Student 对象,并调用它们的 eat() 方法。由于子类重写了父类的方法,所以在调用子类对象的 eat() 方法时,执行的是子类自定义的逻辑。

通过方法的重写,子类可以修改或定制化从父类继承的方法,使得方法更适应子类的特定需求。这样可以增加代码的灵活性和可扩展性,同时保持了继承的代码复用的优势。

2.3.4使用super关键字

在继承关系中,子类可以使用super关键字来访问父类中的属性和方法。super关键字提供了一种明确引用父类成员的方式,尤其在父类和子类存在相同属性或方法名的情况下特别有用。

一般情况下,子类可以直接访问继承自父类的属性和方法。但是当子类中存在与父类同名的属性或方法时,就会发生命名冲突,编译器无法区分使用哪一个。这时候可以使用super关键字来明确指定访问父类中的成员。

super关键字的一般形式为super.属性名和super.方法名(),其中super.属性名用于访问父类的属性,super.方法名()用于调用父类的方法。

2.3.5【案例】使用super关键字示例

以下是一个示例,展示了super关键字的使用:

class Person {
    String name;
    int age;
    char gender;
    public Person(String name, int age, char gender) {
        this.name = name;
        this.age = age;
        this.gender = gender;
    }
    public void printInfo() {
        System.out.println("姓名:" + name);
        System.out.println("年龄:" + age);
        System.out.println("性别:" + gender);
    }
}
class Student extends Person {
    double score;
    public Student(String name, int age, char gender, double score) {
        super(name, age, gender); // 调用父类构造方法
        this.score = score;
    }
    @Override
    public void printInfo() {
        super.printInfo(); // 调用父类的printInfo方法
        System.out.println("成绩:" + score);
    }
}
public class Demo8 {
    public static void main(String[] args) {
        Student s1 = new Student("Tom", 12, '男', 99.5);
        // 测试继承的方法
        s1.printInfo();
    }
}

在这个示例中,父类Person定义了属性name、age和gender,以及方法printInfo()。子类Student继承了父类的属性和方法,并在子类中重写了printInfo()方法。在子类的构造方法中,使用super关键字调用父类的构造方法来初始化继承的属性。在子类的printInfo()方法中,使用super关键字调用父类的printInfo()方法,以显示父类的信息。然后子类再输出自己特有的属性。

通过使用super关键字,子类可以明确引用父类的属性和方法,解决了父子类型中可能出现的属性和方法冲突问题,确保了正确的访问和调用。这样可以使代码更加清晰和可维护,提高了代码的可读性和可扩展性。

2.3.6 经典面试题目:重写与重载的区别

重写(Override)和重载(Overload)是面向对象编程中的两个重要概念,常常在面试中被问及。它们有以下几个区别:

  • 定义:重写是指在子类中重新定义父类的方法,方法名、参数列表和返回类型都相同。重载是在同一个类中定义多个方法,方法名相同但参数列表不同。
  • 关联:重写涉及继承关系,即子类继承父类的方法并对其进行重新定义。重载是同一个类中的方法之间的关系,通过方法的参数列表的差异来区分。
  • 方法签名:重写要求子类方法与父类方法具有相同的方法签名,包括方法名、参数列表和返回类型。重载要求方法名相同,但参数列表必须不同(个数、类型、顺序)。
  • 功能:重写用于在子类中重新定义父类的方法,可以根据子类的需要实现不同的功能。重载用于处理同一个类中不同的输入情况,通过参数的差异来选择不同的方法。
  • 编译时决定:重写是在运行时动态绑定的,即根据对象的实际类型来确定调用的方法。重载是在编译时静态绑定的,根据传入的参数类型和个数来选择合适的方法。

总结起来,重写和重载的区别在于定义位置、关联性、方法签名、功能和编译时决定。重写用于子类对父类方法的重新定义,重载用于同一个类中多个方法的差异处理。理解它们的区别对于理解面向对象编程的核心概念和方法的灵活应用非常重要。

3 访问控制

3.1 包

3.1.1 什么是 package

定义类时需要指定类的名称,但是如果仅仅将类名作为类的唯一标识,则不可避免的出现命名冲突问题,这会给组件复用以及团队间的合作造成很大的麻烦!因为原则上来说,类名是不可以重复的。

Java中利用package对类进行分类,方便管理代码,减少命名冲突,不同的包中可以定义同名类。

这点与利用文件夹对文件进行分类效果类似。如下图所示:

3.1.2 package 语句

在Java语言中,命名冲突问题是用包(package)的概念来解决的,也就是说,在定义一个类时,除了定义类的名称一般还要指定一个包的名称,定义包名的语法如下所示:

package 包名;

?需要注意的是,在定义包时,package语句必须写在Java源文件的最开始处,即在类定义之前,如下面的语句将为Point类指定包名为“test”:

package test;
class Point{
    ……
}

一旦使用package指定了包名,则类的全称应该是“包名.类名”,如上语句的Point类的全称为test.Point。

使用package即可以解决命名冲突问题,只要保证在同一个包中的类名不重复即可,而不同的包中可以定义相同的类名,例如:test1.Point和test2.Point是两个截然不同的名称,虽然类名相同,但包名不同,亦表示两个完全不同的类。

在命名包名时,包名可以有层次结构,在一个包中可以包含另外一个包,可以按照如下的方法定义package语句:

package 包名1.包名2…包名n;

在实际应用中,包的命名常常是多层次的。因为如果各个公司或开发组织的程序员都随心所欲的命名包名的话,依然不能从根本上解决命名冲突的问题,依然不利于软件的复用。因此,在指定包名时应该按照一定的规范,例如:

org.apache.commons.lang.StringUtil

如上类的定义可以分为4个部分,其中,StringUtil是类名,org.apache.commons.lang是多层包名,其含义如下:org.apache表示公司或组织的信息(是这个公司或组织域名的反写);commons表示项目的名称信息;lang表示模块的名称信息。

3.1.3 声明包的语法要点

声明包的语法要点如下:

1、必须在java源文件的第一行使用package声明包,package语句只能有一行

2、package可以不写,不写表示使用默认包。

  • Java官方建议类一定有package

3、package相同的类放置在同一个包中

4、包名要符合Java命名规范

  • 建议包名采用小写字母,多个单词用点“.”隔开

5、一个包中可以定义多个类,但是类名不能同名

6、包编译以后变成多对应的文件夹

3.1.4 全限定名

全限定名:也称为全名,是指由包名和类名组成的完整名称。Java程序编译和运行期间都采用全名。

在实际使用时,调用其他包中的类时,可以使用全限定名,如下图所示:

?

3.1.5 import

如果位于同一个包中,或者使用了import语句导入,则可以省略包名直接写类名。如下图所示:

?

注意:当遇到相同类名时,就不能省略包名了。

3.1.6【案例】包的示例

编写代码,定义不同的包并分别包含类,分别测试调用方法。代码示意如下:

package day06.demo02;
public class Foo {
}
package day06.demo03;
public class Foo {
}
package day06.demo01;
public class Demo01 {
    public static void main(String[] args) {
        /*
         * 使用全限定名(全名)访问其他包中的类
         */
        day06.demo02.Foo foo = new day06.demo02.Foo();
        day06.demo03.Foo foo2 = new day06.demo03.Foo();
    }
}
package day06.demo01;
import day06.demo02.Foo;
public class Demo02 {
    public static void main(String[] args) {
        /*
         * 使用import导入其他包中的类,可以直接使用,不需要全限定名
         */
        Foo foo = new Foo();
        //如果需要在一个源文件中使用两个同名类,就必须使用全限定名访问
        day06.demo03.Foo foo2 = new day06.demo03.Foo();
    }
}

3.2 访问控制修饰符

3.2.1 什么是访问控制

访问控制:用于设定类、属性、方法等资源的可见范围。利用访问控制可以控制数据及方法的可见范围,避免意外篡改,保护软件组件(对象)的安全可靠。Java提供访问控制修饰词,实现软件组件之间的访问控制。

?

3.2.2 访问控制修饰符

Java提供访问控制修饰词,实现软件组件之间的访问控制。访问控制修饰词可以定义在被访问资源前,用于控制被修饰的资源可访问范围。合理使用访问控制修饰词限定资源的可见范围,就可以避免资源的意外篡改,提高软件组件的安全性。

Java提供4个访问修饰词:

  • public :公有的,修饰类、修饰属性、修饰方法、修饰构造器
  • protected :保护的,修饰类中的成员:属性、方法、构造器等
  • 默认的:不写任何修饰词,修饰类、修饰属性、修饰方法、修饰构造器
  • private :私有的,修饰类中的成员:属性、方法、构造器等

3.2.3 控制范围

这些修饰词的控制范围如下表:

?

需要注意的是:

  • 类内部资源始终是可以访问的
  • 公有的可以在任何位置访问

3.2.4 public与private

public是公有的,其可见范围最大,被public修饰的资源可以在任何位置使用。

private是私有的,其可见范围最小,被private修饰的资源仅仅在当前类体内部可见,离开类体范围就不能被访问了。

查看如下代码示例:

类Goo 的公共属性b和公共方法add() 可以被从类外和包外访问;类Goo 的私有属性a则不可以。

3.2.5【案例】public与private示例

编写代码,测试pbulic 与 private 。代码示意如下:

package day06.demo04;
public class Goo {
    private int a = 6;
    public int b = 5;
    public int add() {
        //私有属性a可以在类内部访问,属性a属于当前类私有
        return a + b;
    }
}
package day06.demo05;
import day06.demo04.Goo;
public class Demo03 {
    public static void main(String[] args) {
        /*
         * 测试 public 和 private 的区别
         */
        Goo goo = new Goo();
        // System.out.println(goo.a); //编译错误,不可见
        System.out.println(goo.b);   //公有属性可以访问
        System.out.println(goo.add());//公有方法可以访问
    }
}

3.2.6 public与默认

默认访问修饰是指不写任何修饰词的情况,默认修饰的资源其可见范围是当前包内部可见,在包的外部是不能访问包内默认修饰的资源。因为在开发工作中很少使用包作为访问控制的范围,所以默认修饰在实际开发工作中极少使用!

查看如下代码示例:

?

类Koo 的默认修饰的属性a不可以从包外访问,公共属性b则可以被从包外访问;二者皆可被从包内访问。

3.2.7【案例】public与默认示例

编写代码,测试pbulic 与 默认访问修饰符。代码示意如下:

package day06.demo06;
public class Koo {
    int a = 6; //默认修饰
    public int b = 9; //公有修饰
}
package day06.demo06;
public class Demo04 {
    public static void main(String[] args) {
        /*
         * 访问公有和默认的属性
         */
        Koo koo = new Koo();
        //同一个包demo06中可以访问默认属性
        System.out.println(koo.a);
        //同一个包中也可以访问公有属性
        System.out.println(koo.b);
    }
}
package day06.demo07;
import day06.demo06.Koo;
public class Demo05 {
    public static void main(String[] args) {
        /*
         * 访问公有和默认的属性
         */
        Koo koo = new Koo();
        //不在同一个包demo06中不能访问默认属性a
        //System.out.println(koo.a);
        //同一个包中也可以访问公有属性
        System.out.println(koo.b);
    }
}

3.2.8 protected与默认

protected意思是“保护的”,protected修饰的资源其可见范围是子类和当前包内部可见。因为在开发工作中很少使用包作为访问控制的范围,所以保护的修饰资源主要是为子类使用。特别是从子类“泛化”到父类中的属性和方法经常定义为保护的,这样子类就可以通过继承得到这些属性和方法。

查看如下代码示例:

类Xoo 的属性a可以被包外的子类访问,因为为其设置了 protected 修饰符;而属性b则不可以被包外的子类访问。

3.2.9【案例】protected与默认示例

编写代码,测试protected与 默认访问修饰符。代码示意如下:

package day06.demo08;
public class Xoo {
    protected int a = 6; //保护属性
    int b = 8;           //默认属性
}
package day06.demo08;
public class Demo06 {
    public static void main(String[] args) {
        /*
         * 在统一个包中访问默认和保护的属性
         */
        Xoo xoo = new Xoo();
        System.out.println(xoo.a);
        System.out.println(xoo.b);
    }
}
package day06.demo09;
import day06.demo08.Xoo;
public class Demo07 {
    public static void main(String[] args) {
        /*
         * 不在同一个包中时候,访问另外一个包中的 默认和保护属性
         */
        Xoo xoo = new Xoo();
        //默认和保护的属性都是包内可见
        //System.out.println(xoo.a); //不可见
        //System.out.println(xoo.b); //不可见
    }
}
package day06.demo09;
import day06.demo08.Xoo;
public class Yoo extends Xoo {
    public void test() {
        //在子类中可以访问继承到的保护属性
        System.out.println(a);
        //默认属性:不支持跨包访问
        //System.out.println(b);
    }
    public static void main(String[] args) {
        Yoo yoo = new Yoo();
        yoo.test();
    }
}

3.2.10 封装

面向对象有3大基本特征:封装、继承、多态。

封装(Encapsulation)是指,一种将抽象性函数接口的实现细节部分包装、隐藏起来的方法。同时,它也是一种防止外界调用端,去访问对象内部实现细节的手段,这个手段是由编程语言本身来提供的。

可以简单理解为:封装数据和算法细节,暴露可以安全访问的方法和数据。比如,利用对象封装数据,利用方法封装计算过程。

Java中的封装就是采用访问修饰词实现的:

  • 需要隐藏的属性和方法定义为私有的private
  • 需要留给子类使用的属性和方法定义为保护的protected
  • 确实需要公开访问的属性和方法定义为公有的public
  • 不清楚使用那种访问控制修饰符时候,有限选择私有private
  • 默认的访问控制不使用

文章来源:https://blog.csdn.net/2301_79227925/article/details/134903236
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。