面向对象总结

面向对象总结

一、 面向对象

1.1 面向对象

面向对象就是:把数据及对数据的操作方法放在一起,作为一个相互依存的整体——对象。对同类对象抽象出其共性,形成类。类中的大多数数据,只能用本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。程序流程由用户在使用中决定。对象即为人对各种具体物体抽象后的一个概念,人们每天都要接触各种各样的对象,如手机就是一个对象。

面向对象编程(OOP: object-oriented programming)

面向对象思想(Object Oriented Programming):
一切客观存在的事物都是对象,万物皆对象。
任何对象,一定具有自己的特征和行为。
对象:
特征:称为属性,一般为名词,代表对象有什么。
行为:称为方法,一般为动词,代表对象能做什么。
对象的创建:
将对象保存在相同类型的myDog变量中myDog变量称为“对象名”或“引用名,
基于Dog类创建对象,访问属性:对象名.属性名 = 值;访问属性: 对象名.属性名; //取值
最后调用方法:对象名。方法名();

1.2 什么是类

类,是一个抽象的概念,不能直接使用,要使用类中的属性和功能,必须对类进行实例化,我们可以使用关键字new(静态Static修饰的不需要new来实现)。而实际上我们在创建对象时,除了使用关键字new之外,还得借助构造方法来完成对类的实例化。

1.3 类与对象的关系
类和对象是面向对象编程的基本概念。类是一种抽象的模板,它定义了一组具有相同属性和行为的对象。对象是类的具体的实例,它拥有类中声明的属性和方法,并且可以通过它们来实现不同的功能。类和对象之间的关系可以用以下几个方面来描述:

类是对象的模板,对象是类的实体。类描述了一类事物的共性特征,而对象则表示了一类事物中的个体差异。
类是抽象的,对象是具体的。类只是一个概念,它不能直接使用,而对象是真实存在的,它可以被创建、操作和销毁。
类是静态的,对象是动态的。类在编译时就已经确定了,它不会随着程序的运行而改变,而对象在运行时才被创建,它可以根据不同的情况而改变自己的状态和行为。
类和对象之间存在一对多的关系。一个类可以创建多个对象,每个对象都属于该类,但是每个对象都有自己独特的属性值和方法实现。

下面是一个类和对象的例子:

// 定义一个 Animal 类
public class Animal {
    // 定义两个属性 name 和 age
    String name;
    int age;

    // 定义一个构造方法,用来初始化对象
    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 定义一个 eat 方法,用来表示动物吃东西的行为
    public void eat() {
        System.out.println(name + " is eating.");
    }

    // 定义一个 sleep 方法,用来表示动物睡觉的行为
    public void sleep() {
        System.out.println(name + " is sleeping.");
    }
}

// 创建一个 Animal 类的对象 cat
Animal cat = new Animal("Tom", 3);

// 调用 cat 对象的属性和方法
System.out.println("The cat's name is " + cat.name);
System.out.println("The cat's age is " + cat.age);
cat.eat();
cat.sleep();

输出结果为:

The cat's name is Tom
The cat's age is 3
Tom is eating.
Tom is sleeping.
1.4 方法的重载

Java 方法的重载是一种实现多态的方式,它可以让同一个方法名根据不同的参数类型和个数来执行不同的逻辑。Java 方法的重载有以下几个条件和特点:

  • 条件:在同一个类中,方法名相同,但参数列表(参数类型或个数)不同的多个方法,就构成了方法的重载。返回类型、访问修饰符、异常声明等可以相同也可以不同,但不能仅以这些作为区分重载方法的依据。
  • 特点:重载仅对应方法的定义,与方法的调用无关。编译器会根据实际传递的参数类型和个数来选择合适的重载方法进行调用。如果找到一个完全匹配的方法,则直接调用该方法。如果找不到完全匹配的方法,则会尝试进行类型转换,以便匹配到一个更加通用的方法。如果还是找不到匹配的方法,则会报错。
  • 优点:重载可以提高代码的复用性和可读性,避免了为不同参数类型和个数的方法起不同的名字,使得代码更加简洁和统一。

下面是一个 Java 方法重载的例子:

// 定义一个类
public class Overloading {
    // 定义一个无参的 test 方法
    public int test(){
        System.out.println("test1");
        return 1;
    }
    // 定义一个有一个 int 参数的 test 方法,与第一个方法构成重载
    public void test(int a){
        System.out.println("test2");
    }
    // 定义一个有两个参数的 test 方法,参数顺序为 int, String,与前两个方法构成重载
    public String test(int a,String s){
        System.out.println("test3");
        return "returntest3";
    }
    // 定义一个有两个参数的 test 方法,参数顺序为 String, int,与前三个方法构成重载
    public String test(String s,int a){
        System.out.println("test4");
        return "returntest4";
    }
    // 定义一个主方法
    public static void main(String[] args){
        // 创建一个 Overloading 类的对象
        Overloading o = new Overloading();
        // 调用无参的 test 方法
        System.out.println(o.test());
        // 调用有一个 int 参数的 test 方法
        o.test(1);
        // 调用有两个参数,顺序为 int, String 的 test 方法
        System.out.println(o.test(1,"test3"));
        // 调用有两个参数,顺序为 String, int 的 test 方法
        System.out.println(o.test("test4",1));
    }
}

输出结果为:

test1
1
test2
test3
returntest3
test4
returntest4

从上面的例子可以看出,根据传递的参数类型和个数,编译器会自动选择合适的重载方法进行调用。

1.5 构造方法

Java构造方法是一种特殊的方法,它用于初始化一个类的对象。构造方法的名称必须和类名相同,没有返回值,也不能用void声明。构造方法可以有不同的参数列表,从而实现重载。构造方法可以调用其他构造方法,也可以调用父类的构造方法。如果一个类没有定义任何构造方法,编译器会自动为它生成一个默认的无参构造方法。

下面是一个Java构造方法的例子¹:

// 定义一个 Person 类
public class Person {
    // 定义两个属性 name 和 age
    private String name;
    private int age;

    // 定义一个无参构造方法
    public Person() {
        System.out.println("这是无参构造方法");
    }

    // 定义一个有一个 String 参数的构造方法
    public Person(String name) {
        System.out.println("这是有一个 String 参数的构造方法");
        this.name = name;
    }

    // 定义一个有两个参数的构造方法
    public Person(String name, int age) {
        System.out.println("这是有两个参数的构造方法");
        this.name = name;
        this.age = age;
    }

    // 定义一个打印信息的方法
    public void show() {
        System.out.println("姓名:" + name + ",年龄:" + age);
    }
}

// 创建一个 TestPerson 类,用来测试 Person 类
public class TestPerson {
    public static void main(String[] args) {
        // 创建一个 Person 类的对象 p1,使用无参构造方法
        Person p1 = new Person();
        p1.show(); // 姓名:null,年龄:0

        // 创建一个 Person 类的对象 p2,使用有一个 String 参数的构造方法
        Person p2 = new Person("张三");
        p2.show(); // 姓名:张三,年龄:0

        // 创建一个 Person 类的对象 p3,使用有两个参数的构造方法
        Person p3 = new Person("李四", 18);
        p3.show(); // 姓名:李四,年龄:18
    }
}

输出结果为:

这是无参构造方法
姓名:null,年龄:0
这是有一个 String 参数的构造方法
姓名:张三,年龄:0
这是有两个参数的构造方法
姓名:李四,年龄:18

从上面的例子可以看出,根据创建对象时传递的参数不同,编译器会自动选择合适的构造方法进行初始化。如果没有传递任何参数,则会调用默认的无参构造方法。

二、面向对象的三大特征

2.1 封装

Java的封装是一种将类的属性和方法隐藏起来,仅通过类的公共接口进行访问的机制,实现了对代码的隐蔽和保护。封装是面向对象编程的三大特性之一,另外两个是继承和多态。

封装的目的是为了保证类的内部数据和实现细节不被外部类随意修改或调用,从而提高了类的安全性和可维护性。封装也可以使类的设计更加简洁和清晰,避免了不必要的代码重复和冗余。

Java中实现封装的主要方式是通过访问修饰符来控制类、属性和方法的可见性。访问修饰符有四种:public、protected、default(无修饰符)和private。它们分别表示了不同的访问范围:

  • public:表示公开的,可以被任何类访问。
  • protected:表示受保护的,可以被同一个包中的类或者不同包中的子类访问。
  • default:表示默认的,可以被同一个包中的类访问。
  • private:表示私有的,只能被本类访问。

一般来说,一个类的属性应该用private修饰,以防止外部类直接访问或修改它们。而一个类的方法可以根据需要用不同的修饰符来定义,以提供不同级别的访问权限。如果一个属性需要被外部类访问或修改,那么应该为该属性提供公开的getter和setter方法,以便在方法中对属性进行合理的检查和处理。

下面是一个Java封装的例子:

// 定义一个 Student 类
public class Student {
    // 定义两个私有属性 name 和 score
    private String name;
    private int score;

    // 定义一个公开的构造方法,用于初始化对象
    public Student(String name, int score) {
        // 调用 setter 方法来给属性赋值
        setName(name);
        setScore(score);
    }

    // 定义一个公开的 getter 方法,用于获取 name 属性
    public String getName() {
        return name;
    }

    // 定义一个公开的 setter 方法,用于设置 name 属性
    public void setName(String name) {
        // 判断传入的参数是否为空或空字符串
        if (name == null || name.isEmpty()) {
            // 如果为空或空字符串,则抛出异常
            throw new IllegalArgumentException("Name cannot be null or empty");
        } else {
            // 如果不为空或空字符串,则给属性赋值
            this.name = name;
        }
    }

    // 定义一个公开的 getter 方法,用于获取 score 属性
    public int getScore() {
        return score;
    }

    // 定义一个公开的 setter 方法,用于设置 score 属性
    public void setScore(int score) {
        // 判断传入的参数是否在 0 到 100 之间
        if (score < 0 || score > 100) {
            // 如果不在 0 到 100 之间,则抛出异常
            throw new IllegalArgumentException("Score must be between 0 and 100");
        } else {
            // 如果在 0 到 100 之间,则给属性赋值
            this.score = score;
        }
    }

    // 定义一个公开的方法,用于打印学生信息
    public void printInfo() {
        System.out.println("Name: " + name + ", Score: " + score);
    }
}

// 创建一个 TestStudent 类,用于测试 Student 类
public class TestStudent {
    public static void main(String[] args) {
        // 创建一个 Student 类的对象 s1,并初始化其属性
        Student s1 = new Student("Alice", 90);
        // 调用 s1 对象的 printInfo 方法打印其信息
        s1.printInfo(); // Name: Alice, Score: 90

        // 创建一个 Student 类的对象 s2,并初始化其属性
        Student s2 = new Student("Bob", 80);
        // 调用 s2 对象的 printInfo 方法打印其信息
        s2.printInfo(); // Name: Bob, Score: 80

        // 尝试给 s1 对象的 name 属性赋值为 null
        s1.setName(null); // 抛出异常:Name cannot be null or empty

        // 尝试给 s2 对象的 score 属性赋值为 120
        s2.setScore(120); // 抛出异常:Score must be between 0 and 100
    }
}

从上面的例子可以看出,通过使用 private 修饰符,我们可以隐藏 Student 类的 name 和 score 属性,使得外部类不能直接访问或修改它们。通过使用 public 修饰符,我们可以提供公开的 getter 和 setter 方法,使得外部类可以通过这些方法来获取或设置属性的值。在这些方法中,我们可以对传入的参数进行合理的检查和处理,以保证属性的有效性和一致性。

2.2 继承

Java的继承是一种实现代码复用和多态的机制,它可以让一个类(子类)继承另一个类(父类)的属性和方法,并且可以在子类中添加新的属性和方法或者重写父类的方法。继承是面向对象编程的三大特性之一,另外两个是封装和多态。

继承的目的是为了实现类之间的层次关系,使得子类可以共享父类的公共特征,同时也可以根据自己的特殊需求进行扩展和修改。继承可以提高代码的可读性和可维护性,避免了不必要的代码重复和冗余。

Java中实现继承的主要方式是通过extends关键字来指定一个类继承另一个类。Java只支持单继承,即一个类只能有一个直接父类,但可以有多个间接父类。Java也支持多重继承,即一个类可以实现多个接口,从而继承多个接口中定义的抽象方法。我们会在后面介绍接口的概念。

继承的语法如下:

// 定义一个父类
class Parent {
    // 父类的属性和方法
}

// 定义一个子类,使用 extends 关键字继承父类
class Child extends Parent {
    // 子类的属性和方法
}

下面是一个Java继承的例子:

// 定义一个 Animal 类,作为父类
class Animal {
    // 定义一个 name 属性
    protected String name;

    // 定义一个构造方法,用于初始化 name 属性
    public Animal(String name) {
        this.name = name;
    }

    // 定义一个 eat 方法,用于表示动物吃东西的行为
    public void eat() {
        System.out.println(name + " is eating.");
    }
}

// 定义一个 Dog 类,使用 extends 关键字继承 Animal 类
class Dog extends Animal {
    // 定义一个 breed 属性
    private String breed;

    // 定义一个构造方法,用于初始化 name 和 breed 属性
    public Dog(String name, String breed) {
        // 使用 super 关键字调用父类的构造方法
        super(name);
        this.breed = breed;
    }

    // 重写父类的 eat 方法,用于表示狗吃东西的行为
    @Override
    public void eat() {
        System.out.println(name + " is eating dog food.");
    }

    // 定义一个 bark 方法,用于表示狗叫的行为
    public void bark() {
        System.out.println(name + " is barking.");
    }
}

// 创建一个 TestAnimal 类,用于测试 Animal 和 Dog 类
public class TestAnimal {
    public static void main(String[] args) {
        // 创建一个 Animal 类的对象 a1,并初始化其 name 属性为 "Tom"
        Animal a1 = new Animal("Tom");
        // 调用 a1 对象的 eat 方法
        a1.eat(); // Tom is eating.

        // 创建一个 Dog 类的对象 d1,并初始化其 name 和 breed 属性为 "Jack" 和 "Husky"
        Dog d1 = new Dog("Jack", "Husky");
        // 调用 d1 对象的 eat 方法
        d1.eat(); // Jack is eating dog food.
        // 调用 d1 对象的 bark 方法
        d1.bark(); // Jack is barking.
    }
}

从上面的例子可以看出,Dog 类继承了 Animal 类,因此它可以使用 Animal 类中定义的 name 属性和 eat 方法,并且可以在自己的类中添加新的 breed 属性和 bark 方法或者重写 eat 方法。在 Dog 类中,我们使用 super 关键字来调用父类的构造方法,以便初始化 name 属性。我们也使用 @Override 注解来标记重写父类的方法,以便提高代码的可读性和可维护性。

2.3 多态

Java的多态是一种实现不同对象对同一消息的不同响应的机制,它可以让一个方法或者一个对象具有多种形态。多态是面向对象编程的三大特性之一,另外两个是封装和继承。

多态的目的是为了实现类之间的解耦,使得子类可以替换父类,而不影响原有的代码逻辑。多态也可以提高代码的可扩展性和可维护性,避免了使用大量的if-else或switch-case语句。

Java中实现多态的主要方式是通过动态绑定,即在运行时根据对象的实际类型来决定调用哪个方法。Java也支持静态绑定,即在编译时根据引用类型来决定调用哪个方法。动态绑定需要使用继承和重写,而静态绑定需要使用重载。

动态绑定的语法如下:

// 定义一个父类
class Parent {
    // 定义一个方法
    public void method() {
        // 父类的方法实现
    }
}

// 定义一个子类,使用 extends 关键字继承父类
class Child extends Parent {
    // 重写父类的方法
    @Override
    public void method() {
        // 子类的方法实现
    }
}

// 创建一个父类类型的引用,指向一个子类类型的对象
Parent p = new Child();
// 调用引用的方法,会根据对象的实际类型来执行子类的方法
p.method();

静态绑定的语法如下:

// 定义一个类
class Test {
    // 定义一个无参的方法
    public void method() {
        // 无参方法的实现
    }
    // 定义一个有一个参数的方法,与无参方法构成重载
    public void method(int x) {
        // 有参方法的实现
    }
}

// 创建一个 Test 类的对象
Test t = new Test();
// 调用无参的方法,会根据引用类型来执行无参方法
t.method();
// 调用有一个参数的方法,会根据引用类型和参数类型来执行有参方法
t.method(10);

下面是一个Java多态的例子:

// 定义一个 Animal 类,作为父类
class Animal {
    // 定义一个 name 属性
    protected String name;

    // 定义一个构造方法,用于初始化 name 属性
    public Animal(String name) {
        this.name = name;
    }

    // 定义一个 eat 方法,用于表示动物吃东西的行为
    public void eat() {
        System.out.println(name + " is eating.");
    }
}

// 定义一个 Dog 类,使用 extends 关键字继承 Animal 类
class Dog extends Animal {
    // 定义一个 breed 属性
    private String breed;

    // 定义一个构造方法,用于初始化 name 和 breed 属性
    public Dog(String name, String breed) {
        // 使用 super 关键字调用父类的构造方法
        super(name);
        this.breed = breed;
    }

    // 重写父类的 eat 方法,用于表示狗吃东西的行为
    @Override
    public void eat() {
        System.out.println(name + " is eating dog food.");
    }

    // 定义一个 bark 方法,用于表示狗叫的行为
    public void bark() {
        System.out.println(name + " is barking.");
    }
}

// 创建一个 TestAnimal 类,用于测试 Animal 和 Dog 类
public class TestAnimal {
    public static void main(String[] args) {
        // 创建一个 Animal 类型的数组,存放不同类型的对象
        Animal[] animals = new Animal[3];
        animals[0] = new Animal("Tom");
        animals[1] = new Dog("Jack", "Husky");
        animals[2] = new Dog("Bob", "Poodle");

        // 遍历数组,调用每个对象的 eat 方法,实现多态
        for (Animal animal : animals) {
            animal.eat();
        }
    }
}

输出结果为:

Tom is eating.
Jack is eating dog food.
Bob is eating dog food.

从上面的例子可以看出,我们使用 Animal 类型的数组来存放不同类型的对象,包括 Animal 类和 Dog 类。当我们遍历数组并调用每个对象的 eat 方法时,会根据对象的实际类型来执行相应的方法。这就是多态的体现,即同一个方法在不同的对象上有不同的表现。

三、关键词

3.1 super关键字

Java的super关键字是一种用于引用父类对象的引用变量,它可以用来访问父类的属性、方法和构造方法。super关键字主要用于以下几个场景:

  • 在子类中,可以使用super关键字来访问父类的属性,以区分子类和父类的同名属性。例如,你可以定义一个Animal类,它有一个color属性,然后你可以定义一个Dog类,它继承了Animal类,并且也有一个color属性。在Dog类中,你可以使用super.color来引用父类的color属性,而不是子类的color属性。
  • 在子类中,可以使用super关键字来调用父类的方法,以实现代码的复用或者重写。例如,你可以定义一个Animal类,它有一个eat方法,然后你可以定义一个Dog类,它继承了Animal类,并且也有一个eat方法。在Dog类中,你可以使用super.eat()来调用父类的eat方法,或者重写父类的eat方法。
  • 在子类的构造方法中,可以使用super关键字来调用父类的构造方法,以初始化父类的属性。例如,你可以定义一个Animal类,它有一个name属性和一个带参数的构造方法,然后你可以定义一个Dog类,它继承了Animal类,并且也有一个breed属性和一个带参数的构造方法。在Dog类的构造方法中,你可以使用super(name)来调用父类的构造方法,以初始化name属性。

下面是一个Java super关键字的例子:

// 定义一个 Animal 类,作为父类
class Animal {
    // 定义一个 name 属性
    protected String name;

    // 定义一个构造方法,用于初始化 name 属性
    public Animal(String name) {
        this.name = name;
    }

    // 定义一个 eat 方法,用于表示动物吃东西的行为
    public void eat() {
        System.out.println(name + " is eating.");
    }
}

// 定义一个 Dog 类,使用 extends 关键字继承 Animal 类
class Dog extends Animal {
    // 定义一个 breed 属性
    private String breed;

    // 定义一个构造方法,用于初始化 name 和 breed 属性
    public Dog(String name, String breed) {
        // 使用 super 关键字调用父类的构造方法
        super(name);
        this.breed = breed;
    }

    // 重写父类的 eat 方法,用于表示狗吃东西的行为
    @Override
    public void eat() {
        System.out.println(name + " is eating dog food.");
    }

    // 定义一个 bark 方法,用于表示狗叫的行为
    public void bark() {
        System.out.println(name + " is barking.");
    }
}

// 创建一个 TestAnimal 类,用于测试 Animal 和 Dog 类
public class TestAnimal {
    public static void main(String[] args) {
        // 创建一个 Animal 类型的数组,存放不同类型的对象
        Animal[] animals = new Animal[3];
        animals[0] = new Animal("Tom");
        animals[1] = new Dog("Jack", "Husky");
        animals[2] = new Dog("Bob", "Poodle");

        // 遍历数组,调用每个对象的 eat 方法,实现多态
        for (Animal animal : animals) {
            animal.eat();
        }
    }
}

输出结果为:

Tom is eating.
Jack is eating dog food.
Bob is eating dog food.

从上面的例子可以看出,我们使用 super 关键字来调用父类的构造方法和方法,以实现代码的复用和重写。我们也使用 @Override 注解来标记重写父类的方法,以便提高代码的可读性和可维护性。

3.2 this关键字

this关键字是Java中常用的关键字,它可以用于表示当前对象的引用。this可以在类的实例方法中使用,指向当前正在调用该方法的对象。this主要用于以下几个场景:

  • 当局部变量和成员变量同名时,可以使用this关键字来引用成员变量,以区分局部变量和成员变量。例如,你可以定义一个类叫做Teacher,它有属性name和age,以及一个构造方法,用来初始化对象。在构造方法中,你可以使用this.name = name;来给成员变量name赋值,而不是局部变量name。
  • 在构造方法中,可以使用this关键字调用另一个构造方法,用于代码的复用。例如,你可以定义一个类叫做Student,它有属性name和score,以及两个构造方法。一个构造方法只有一个参数name,另一个构造方法有两个参数name和score。在第一个构造方法中,你可以使用this(name, 0);来调用第二个构造方法,实现了代码的复用。
  • 当需要将当前对象作为参数传递给方法或者构造方法时,可以使用this关键字。例如,你可以定义一个类叫做Person,它有属性name和age,以及一个show()方法,用来打印信息。然后你可以定义另一个类叫做Group,它有属性leader和members,以及一个add()方法,用来添加成员。在add()方法中,你可以使用this.leader.show();来调用当前对象的leader属性的show()方法。

下面是一个Java this关键字的例子:

// 定义一个 Person 类
public class Person {
    // 定义两个属性 name 和 age
    private String name;
    private int age;

    // 定义一个无参构造方法
    public Person() {
        System.out.println("这是无参构造方法");
    }

    // 定义一个有一个 String 参数的构造方法
    public Person(String name) {
        // 使用 this 关键字调用另一个构造方法
        this(name, 0);
        System.out.println("这是有一个 String 参数的构造方法");
    }

    // 定义一个有两个参数的构造方法
    public Person(String name, int age) {
        // 使用 this 关键字引用成员变量
        this.name = name;
        this.age = age;
        System.out.println("这是有两个参数的构造方法");
    }

    // 定义一个打印信息的方法
    public void show() {
        System.out.println("姓名:" + name + ",年龄:" + age);
    }
}

// 创建一个 TestPerson 类,用来测试 Person 类
public class TestPerson {
    public static void main(String[] args) {
        // 创建一个 Person 类的对象 p1,使用无参构造方法
        Person p1 = new Person();
        p1.show(); // 姓名:null,年龄:0

        // 创建一个 Person 类的对象 p2,使用有一个 String 参数的构造方法
        Person p2 = new Person("张三");
        p2.show(); // 姓名:张三,年龄:0

        // 创建一个 Person 类的对象 p3,使用有两个参数的构造方法
        Person p3 = new Person("李四", 18);
        p3.show(); // 姓名:李四,年龄:18

        // 创建一个 Group 类的对象 g1
        Group g1 = new Group(p3, 5);
        g1.show(); // 组长:李四,年龄:18;组员数量:5

        // 创建一个 Group 类的对象 g2
        Group g2 = new Group(new Person("王五", 20), 4);
        g2.show(); // 组长:王五,年龄:20;组员数量:4
    }
}

// 定义一个 Group 类
public class Group {
    // 定义两个属性 leader 和 members
    private Person leader;
    private int members;

    // 定义一个有两个参数的构造方法
    public Group(Person leader, int members) {
        // 使用 this 关键字引用成员变量
        this.leader = leader;
        this.members = members;
    }

    // 定义一个打印信息的方法
    public void show() {
        System.out.print("组长:");
        // 使用 this 关键字传递当前对象的 leader 属性给 show 方法
        this.leader.show();
        System.out.println("组员数量:" + members);
    }
}

输出结果为:

这是无参构造方法
姓名:null,年龄:0
这是有两个参数的构造方法
这是有一个 String 参数的构造方法
姓名:张三,年龄:0
这是有两个参数的构造方法
姓名:李四,年龄:18
这是有两个参数的构造方法
组长:姓名:李四,年龄:18;
组员数量:5
这是有两个参数的构造方法
组长:姓名:王五,年龄:20;
组员数量:4

从上面的例子可以看出,根据不同的情况,使用this关键字可以实现不同的功能,如引用成员变量、调用构造方法、传递当前对象等。

3.3 装箱和拆箱

Java的装箱和拆箱是一种实现基本数据类型和包装器类型之间相互转换的机制,它可以让基本数据类型具有对象的特征,实现更多的功能。装箱和拆箱是从Java 5开始引入的特性,它可以让我们在编写代码时更加方便和简洁。

装箱就是将基本数据类型转换为对应的包装器类型,例如将int转换为Integer,或者将boolean转换为Boolean。拆箱就是将包装器类型转换为对应的基本数据类型,例如将Integer转换为int,或者将Boolean转换为boolean。

Java支持自动装箱和拆箱,也就是说我们不需要显式地调用方法来进行转换,而是由编译器在编译时自动插入相应的代码。例如,我们可以直接将一个int赋值给一个Integer变量,或者将一个Integer变量参与运算,这些过程都会自动进行装箱或拆箱。

下面是一个Java装箱和拆箱的例子:

// 定义一个 int 类型的变量 i
int i = 10;
// 定义一个 Integer 类型的变量 j
Integer j = i; // 自动装箱,相当于 Integer j = Integer.valueOf(i);
// 定义一个 int 类型的变量 k
int k = j; // 自动拆箱,相当于 int k = j.intValue();
// 打印变量 i, j, k 的值
System.out.println(i); // 10
System.out.println(j); // 10
System.out.println(k); // 10

从上面的例子可以看出,我们可以直接将一个int赋值给一个Integer变量,或者将一个Integer变量赋值给一个int变量,这些过程都会自动进行装箱或拆箱。实际上,在编译时,编译器会自动插入相应的方法调用来实现转换。

那么,装箱和拆箱是如何实现的呢?其实我们可以在包装器类的源码中查看,每个包装器类都提供了两个方法来实现装箱和拆箱:

  • valueOf()方法:用于将基本数据类型转换为包装器类型。
  • xxxValue()方法:用于将包装器类型转换为基本数据类型。其中xxx代表对应的基本数据类型,例如intValue()、doubleValue()等。

下面是Integer类中这两个方法的具体实现:

public static Integer valueOf(int i) {
    if (i >= -128 && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

public int intValue() {
    return value;
}

从上面的代码可以看出,在valueOf()方法中,如果传入的参数在[-128,127]之间(这个范围可以通过系统属性配置),则会从缓存中返回对应的Integer对象;否则会创建一个新的Integer对象并返回。这样做的目的是为了提高性能和节省内存,因为[-128,127]之间的整数是经常使用到的。而在intValue()方法中,则直接返回Integer对象中存储的int值。

其他包装器类也有类似的实现方式,不过有一些细节上的差别。例如,Boolean类只有两个缓存对象:TRUE和FALSE;Character类缓存了[0,127]之间的字符;Double和Float类没有使用缓存机制。

四、Java类及其类的成员

4.1 访问权限修饰符

访问修饰符是Java中用来控制类、属性和方法的可见性和访问权限的关键字。访问修饰符有四种:public、protected、default(无修饰符)和private。它们分别表示了不同的访问范围:

  • public:表示公开的,可以被任何类访问。
  • protected:表示受保护的,可以被同一个包中的类或者不同包中的子类访问。
  • default:表示默认的,可以被同一个包中的类访问。
  • private:表示私有的,只能被本类访问。

我们可以通过以下表来说明访问权限:

访问控制修饰符当前类同一包内子孙类 (同一包)子孙类 (不同包)其他包
public公有YYYYY
protected受保护YYYY/N¹N
default默认YYYNN
private私有YNNNN
4.2 抽象类,抽象方法

Java的抽象类和抽象方法是一种用来描述多个类共享的行为,但不提供具体实现的机制。抽象类和抽象方法是实现面向对象编程中的抽象特性的一种方式,另外一种方式是接口。

抽象类是用abstract关键字修饰的类,它不能被实例化,只能被继承。抽象类可以包含抽象方法和非抽象方法,也可以包含属性、构造方法和静态方法。例如:

// 定义一个抽象类
abstract class Animal {
    // 定义一个属性
    protected String name;

    // 定义一个构造方法
    public Animal(String name) {
        this.name = name;
    }

    // 定义一个抽象方法
    abstract void makeSound();

    // 定义一个非抽象方法
    public void eat() {
        System.out.println(name + " is eating.");
    }
}

抽象方法是用abstract关键字修饰的方法,它没有方法体,只有方法签名。如果一个类包含了抽象方法,那么这个类必须被声明为抽象类,否则会报错。例如:

// 报错,因为包含了抽象方法
class Animal {
    // 定义一个抽象方法
    abstract void makeSound();
}

如果一个子类继承了一个抽象类,那么它必须实现父类中所有的抽象方法,除非它也被声明为抽象类。例如:

// 定义一个子类,继承了父类Animal
class Dog extends Animal {
    // 定义一个构造方法
    public Dog(String name) {
        // 调用父类的构造方法
        super(name);
    }

    // 实现父类的抽象方法
    @Override
    void makeSound() {
        System.out.println(name + " is barking.");
    }
}

使用抽象类和抽象方法的好处是可以提高代码的复用性和可维护性,避免了不必要的代码重复和冗余。通过定义一些通用的行为,我们可以让子类根据自己的特点来实现具体的细节,从而实现多态性。

4.3 静态属性,静态方法

Java的静态属性和静态方法是用static关键字修饰的属性和方法,它们属于类而不是对象,可以在没有创建对象的情况下直接通过类名访问或调用。静态属性和静态方法的主要作用是实现类级别的共享和操作,提高代码的复用性和效率。

静态属性和静态方法的特点如下:

  • 静态属性和静态方法只能访问或调用其他的静态成员,不能访问或调用非静态成员,因为非静态成员需要依赖对象而存在。
  • 静态属性和静态方法不受对象的影响,即使创建了多个对象,也不会影响静态属性和静态方法的值或行为。
  • 静态属性和静态方法在类加载时就已经分配了内存空间,并且只分配一次,直到类被卸载才释放内存。
  • 静态属性和静态方法可以被继承,但不能被重写,因为它们是属于类的,不涉及多态性。

下面是一个Java静态属性和静态方法的例子:

// 定义一个 Student 类
public class Student {
    // 定义一个非静态属性 name
    private String name;
    // 定义一个静态属性 school
    private static String school = "ABC";

    // 定义一个构造方法,用于初始化 name 属性
    public Student(String name) {
        this.name = name;
    }

    // 定义一个非静态方法 showName,用于显示 name 属性
    public void showName() {
        System.out.println("Name: " + name);
    }

    // 定义一个静态方法 showSchool,用于显示 school 属性
    public static void showSchool() {
        System.out.println("School: " + school);
    }
}

// 创建一个 TestStudent 类,用于测试 Student 类
public class TestStudent {
    public static void main(String[] args) {
        // 创建两个 Student 类的对象 s1 和 s2,并初始化 name 属性
        Student s1 = new Student("Alice");
        Student s2 = new Student("Bob");

        // 调用 s1 和 s2 对象的 showName 方法
        s1.showName(); // Name: Alice
        s2.showName(); // Name: Bob

        // 调用 Student 类的 showSchool 方法
        Student.showSchool(); // School: ABC

        // 修改 Student 类的 school 属性
        Student.school = "XYZ";

        // 再次调用 Student 类的 showSchool 方法
        Student.showSchool(); // School: XYZ

        // 无论修改了多少次 school 属性,s1 和 s2 对象都不会受到影响
        s1.showName(); // Name: Alice
        s2.showName(); // Name: Bob
    }
}

从上面的例子可以看出,我们可以在没有创建对象的情况下直接通过类名访问或调用静态属性和静态方法。我们也可以通过对象访问或调用静态成员,但这并不推荐,因为这样会造成混淆。我们还可以看到,无论修改了多少次静态属性的值,都不会影响对象的非静态属性或方法。

4.4 代码块
4.4.1 静态代码块

Java的静态代码块是一种用static关键字修饰的代码块,它可以用来进行类的静态初始化,即在类加载时执行一些操作。静态代码块只会执行一次,而且会在任何构造方法、静态方法或静态属性之前执行。

静态代码块的语法如下:

// 定义一个类
class Test {
    // 定义一个静态代码块
    static {
        // 静态代码块中的内容
    }
}

静态代码块的作用有以下几种:

  • 初始化一些静态属性,特别是那些不能直接赋值的属性,例如数组、集合等。
  • 调用一些静态方法,例如设置系统属性、注册驱动等。
  • 加载一些资源文件,例如图片、音频等。

下面是一个Java静态代码块的例子¹:

// 定义一个 Student 类
public class Student {
    // 定义一个静态属性 names,用于存放学生姓名
    private static List<String> names;

    // 定义一个静态代码块,用于初始化 names 属性
    static {
        names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        System.out.println("Static block executed.");
    }

    // 定义一个构造方法
    public Student() {
        System.out.println("Constructor executed.");
    }

    // 定义一个静态方法,用于打印 names 属性
    public static void printNames() {
        System.out.println(names);
    }
}

// 创建一个 TestStudent 类,用于测试 Student 类
public class TestStudent {
    public static void main(String[] args) {
        // 调用 Student 类的 printNames 方法
        Student.printNames(); // [Alice, Bob, Charlie]

        // 创建两个 Student 类的对象 s1 和 s2
        Student s1 = new Student();
        Student s2 = new Student();
    }
}

输出结果为:

Static block executed.
[Alice, Bob, Charlie]
Constructor executed.
Constructor executed.

从上面的例子可以看出,我们使用静态代码块来初始化names属性,这样我们就不需要在每个构造方法中重复添加姓名。我们还可以看到,静态代码块只执行了一次,并且在打印姓名和创建对象之前执行。

4.4.2 动态代码块

Java的动态代码块是一种用来在运行时执行一段代码的机制,它可以让我们在不修改源代码的情况下,动态地改变程序的行为。动态代码块的主要作用是实现一些特殊的功能,例如热部署、插件机制、测试框架等。

动态代码块的实现方式有以下几种:

  • 使用Java反射API,可以在运行时创建、修改和调用类、属性和方法。
  • 使用Java编译器API,可以在运行时编译一段字符串或文件中的Java代码,并加载到内存中。
  • 使用Java字节码操作库,可以在运行时生成、修改和执行字节码。
  • 使用Java脚本引擎,可以在运行时解释和执行一些脚本语言,例如JavaScript、Groovy等。

下面是一个使用Java编译器API实现动态代码块的例子:

// 导入相关的类
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

// 定义一个 TestDynamicCodeBlock 类
public class TestDynamicCodeBlock {

    // 定义一个 main 方法
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 定义一个字符串,表示要动态执行的 Java 代码
        String code = "public class Hello {\n" +
                "    public static void main(String[] args) {\n" +
                "        System.out.println(\"Hello, world!\");\n" +
                "    }\n" +
                "}";
        
        // 获取系统默认的 Java 编译器
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        // 获取标准文件管理器
        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
        // 创建一个临时文件,用于存放 Java 代码
        File tempFile = File.createTempFile("Hello", ".java");
        // 将字符串写入临时文件中
        org.apache.commons.io.FileUtils.writeStringToFile(tempFile, code);
        // 获取临时文件的文件对象
        Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays.asList(tempFile));
        // 编译临时文件
        compiler.getTask(null, fileManager, null, null, null, compilationUnits).call();
        // 关闭文件管理器
        fileManager.close();
        
        // 创建一个 URL 类加载器,用于加载编译后的类文件
        URLClassLoader classLoader = new URLClassLoader(new URL[]{tempFile.getParentFile().toURI().toURL()});
        // 加载 Hello 类
        Class<?> helloClass = classLoader.loadClass("Hello");
        // 调用 Hello 类的 main 方法
        helloClass.getMethod("main", String[].class).invoke(null, new Object[]{null});
    }
}

输出结果为:

Hello, world!

从上面的例子可以看出,我们使用Java编译器API来编译一段字符串中的Java代码,并将其保存为一个临时文件。然后我们使用URL类加载器来加载编译后的类文件,并通过反射调用其main方法。这样我们就实现了在运行时执行一段动态代码块。

4.4.3 代码块执行顺序

Java的代码块执行顺序是指在一个Java程序中,不同的代码块(如静态代码块、初始化代码块、构造方法等)是按照什么顺序执行的。代码块执行顺序的主要作用是实现类的初始化和对象的创建,以及控制程序的逻辑流程。

Java的代码块执行顺序有以下几个原则:

  • 静态代码块(用static关键字修饰的代码块)会在类加载时执行,且只执行一次,而且会按照它们在源码中出现的顺序执行。
  • 初始化代码块(没有任何修饰符的代码块)会在对象创建时执行,且每次创建对象都会执行,而且会按照它们在源码中出现的顺序执行。
  • 构造方法(与类名相同且没有返回值的方法)会在对象创建时执行,且每次创建对象都会执行,而且会在初始化代码块之后执行。
  • 如果一个类继承了另一个类,那么父类的静态代码块、初始化代码块和构造方法都会先于子类的相应代码块和方法执行,而且会按照上述原则执行。

下面是一个Java代码块执行顺序的例子¹:

// 定义一个父类
class Parent {
    // 定义一个静态属性
    static int a = 1;
    // 定义一个非静态属性
    int b = 2;

    // 定义一个静态代码块
    static {
        a = 10;
        System.out.println("Parent static block");
    }

    // 定义一个初始化代码块
    {
        b = 20;
        System.out.println("Parent init block");
    }

    // 定义一个构造方法
    public Parent() {
        System.out.println("Parent constructor");
    }
}

// 定义一个子类,继承父类
class Child extends Parent {
    // 定义一个静态属性
    static int c = 3;
    // 定义一个非静态属性
    int d = 4;

    // 定义一个静态代码块
    static {
        c = 30;
        System.out.println("Child static block");
    }

    // 定义一个初始化代码块
    {
        d = 40;
        System.out.println("Child init block");
    }

    // 定义一个构造方法
    public Child() {
        System.out.println("Child constructor");
    }
}

// 创建一个 Test 类,用于测试 Parent 和 Child 类
public class Test {
    public static void main(String[] args) {
        // 创建一个 Child 类的对象
        Child child = new Child();
        // 打印对象的属性值
        System.out.println(child.a); // 10
        System.out.println(child.b); // 20
        System.out.println(child.c); // 30
        System.out.println(child.d); // 40
    }
}

输出结果为:

Parent static block
Child static block
Parent init block
Parent constructor
Child init block
Child constructor
10
20
30
40

从上面的例子可以看出,当我们创建了一个Child类的对象时,程序首先会加载Parent类和Child类,并执行它们的静态代码块和静态属性赋值。然后程序会按照继承关系从父类到子类依次执行它们的初始化代码块、非静态属性赋值和构造方法。最后程序会打印出对象的属性值。

4.5 常量

Java的常量是指在程序中不会改变的值,它们可以提高代码的可读性和可维护性,避免了使用魔法数字或者硬编码的字符串。Java中有两种常量:字面量和符号常量。

字面量是指直接出现在程序中的值,例如整数、浮点数、字符、字符串等。字面量可以用来赋值给变量或者作为表达式的一部分。例如:

int a = 10; // 10 是一个整数字面量
double b = 3.14; // 3.14 是一个浮点数字面量
char c = 'A'; // 'A' 是一个字符字面量
String d = "Hello"; // "Hello" 是一个字符串字面量

符号常量是指用static和final修饰符定义的变量,它们的值在程序运行时不会改变。符号常量可以用来表示一些具有特殊意义的值,例如圆周率、最大值、最小值等。符号常量的命名规范是使用大写字母和下划线分隔单词,例如PI、MAX_VALUE等。符号常量的定义语法如下:

static final 类型 常量名 =;

例如:

static final double PI = 3.1415926; // 定义一个表示圆周率的常量
static final int MAX_VALUE = 100; // 定义一个表示最大值的常量
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农泡泡糖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值