面向对象

类 class

对具有相同属性和功能的一类事物的抽象描述,是创建对象的模板,例如可以定义一个“Person”类来描述人类的共同特征和行为,如姓名,身高等属性,吃饭,睡觉等行为。

类的组成

成员变量:用于描述类的属性,代表对象的状态。如“Person”类中的“name”和“height”

成员方法:用于描述类的行为,定义对象可以执行的操作。如“Person”类中的“eat”和“sleep”

构造方法:一种特殊的方法,用于在创建对象时初始化对象的属性,构造方法的名称与类名相同,没有返回值

内部类 Inner Class

顾名思义,是定义在另一个类(即外部类Outer Class)内部的类,内部类又称为嵌套类(Nested Class),外部类又称为封闭类(Enclosing Class)

大致分为四种内部类:

  1. 成员内部类 Member Inner Class

    定义在外部类的成员位置上,与外部类的成员变量等同级(本质是外部类的实例成员),能访问外部类的所有成员(包括私有的),也能被外部类访问其所有成员(也包括私有的),可以添加任何访问修饰符,在其他类创建成员内部类的实例时,需要保证成员内部类在这个其他类中可见

    1
    2
    3
    4
    5
    6
    7
    class OuterClass {
    private String outerClassField = "outer class's field";

    class MemberInnerClass { // 成员内部类
    private String memberInnerClassField = "member inner class's field";
    }
    }
  2. 静态嵌套类 static Nested Class

    具体解析放在后文的static关键字内

  3. 局部内部类 Local Inner Class

    局部内部类定义在外部类的方法,构造方法或代码块内部,地位类似于方法的局部变量,其中不能包含静态成员,它的作用域仅限于定义它的块中,可以访问外部类的所有成员,也可以被外部成员访问其所有成员

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    class OuterClass {
    public OuterClass() {
    class LocalInnerClassA { // 在构造方法中定义局部内部类
    }
    }
    {
    class LocalInnerClassB { // 在实例代码块中定义局部内部类
    }
    }
    static {
    class LocalInnerClassC { // 在静态代码块中定义局部内部类
    }
    }
    public void fun() {
    int localVar;
    class LocalInnerClassD { // 在成员函数中定义局部内部类
    }
    }
    }
  4. 匿名内部类 Anonymous Inner Class

    匿名内部类是一种特殊的内部类,它没有显式的类名。匿名内部类通常用于创建一个类的实例,而这个类只需要使用一次。匿名内部类可以继承一个类或实现一个接口,并且可以在定义类的内容的同时创建对象

类什么时候会被加载?

  • 运行类中的main方法
  • 访问类中的静态成员变量/方法
  • 创建类的实例对象

对象

对象是类的实例,以类为模板,通过使用new关键字在内存中创建出一个实际存在的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Person{
//成员变量
String name;
int height;
//构造方法
//有参
public Person(String name, int height){
this.name = name;
this.height = height
}
//无参
public Person(){
name = "小邵"
height = 175
}
//成员方法
public void eat(){
System.out.println(name + "吃饱了。");
}
public void measure(){
System.out.println(name + "的身高是" + height + "cm。");
}
}

public class Main {
public static void main(String[] args){
//创建Person类对象
Person person = new Person("狗皇帝", 175);
//调用对象方法
person.eat();
person.measure();
}
}

重载 overload(外壳必须改变,核心可以改变)

含义:在一个类里面,方法名字相同而参数列表不同,返回类型可同可不同,这些方法被称为重载方法

(通俗理解的话就是下面这玩意)

image.png

目的:为了提供功能相似但输入参数不同的方法,以便于调用时根据参数选择合适的方法

使用场景:常用于创建一个操作不同类型的参数的多个版本

规则:

  1. 参数列表必须改变(个数/类型/顺序)
  2. 返回类型可以改变
  3. 实现过程可以改变
  4. 异常声明可以改变
  5. 访问限制可以改变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class overload{
public static void print(int a){
System.out.println("传入参数为int类型");
}
public static void print(String b){
System.out.println("传入参数为String类型");
}
public static void print(double c){
System.out.println("传入参数为double类型");
}
public static void main(String[] args){
overload.print(1);
overload.print("1");
overload.print(3.1415926);
}
}

重写 override(外壳不能改变,内核可以改变):

含义:当子类重写父类的方法时,称为override,此方法可以通过@Override注解来标记(非强制)。子类的方法必须与父类被重写的方法具有相同的名称,返回类型和参数列表

目的:通常是为了在子类中提供特定于子类的方法,或者为了子类拓展或修改父类的方法

使用场景:继承和多态

规则:

  1. 参数列表不能改变
  2. 返回类型可以为被重写方法的派生类
  3. 实现过程可以改变
  4. 异常声明不能比父类更加宽泛
  5. 访问限制不能比父类更加严格
  6. final修饰的方法不可被重写
  7. static修饰的方法不可被重写,但可以重新声明
  8. 构造方法不可重写
  9. 父类无法被子类访问的方法不可重写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class override {
public static class Dog{
public static void run(){
System.out.println("Dog run");
}
public void bark(){
System.out.println("Dog bark");
}
}
public static class Poodle extends Dog{
//@Override
//这是一个错误重写的示例,如果去掉注解前面的注释皓则会报错,因为static修饰的方法类型无法被重写
public static void run(){//这只是重新声明,而非重写
System.out.println("Poodle run");
}
@Override
public void bark(){
System.out.println("Poodle bark");
}
}
public static void main(String[] args){
Dog dog = new Dog();
Poodle poodle = new Poodle();
Dog.run();
dog.bark();
Poodle.run();
poodle.bark();
}
}

this关键字

什么是this?

this关键字是一个特殊引用变量,他持有对当前对象的引用。在类的非静态方法和构造函数中可以使用this关键字访问当前对象的成员变量和方法,即“当前自身对象“的代称。

基本用法:

1.访问当前对象的成员变量:

当局部变量与成员变量同名时,this关键字可用于区分它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Student {
private String name;

public Student(String name) {
this.name = name; // this.name 表示成员变量,name 表示构造函数参数
}

public void setName(String name) {
this.name = name; // this.name 表示成员变量,name 表示方法参数
}

public String getName() {
return this.name; // 访问当前对象的成员变量
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Student{
private String name;

public Student(String name){
name = name;//代码编写者的意图是将右侧的局部变量赋值给成员变量name
}
public String getName(){
return this.name;
}
public static void main(String[] args){
Student student = new Student("小明");
System.out.println(student.getName());
}
}

运行结果:

image.png

我们发现返回出来的结果为null,说明在对student初始化时并未将”小明“赋值给name,这是因为没用使用this关键字时,java认为左侧的name也是局部变量而非成员变量,即将局部变量的值赋值给了局部变量,而成员变量因为没有受到任何改动而维持默认值null

2.调用当前对象的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Student {
private String name;

public Student(String name) {
this.name = name;
}

public void displayInfo() {
System.out.println("Student name: " + this.name);
}

public void showInfo() {
this.displayInfo(); // 调用当前对象的displayInfo方法
}

public static void main(String[] args) {
Student student = new Student("Alice");
student.showInfo(); // Output: Student name: Alice
}
}

3.调用当前类的构造函数:

在类的构造函数中,可以使用this关键字来调用另一个构造函数以简化代码编写,这种方法也被称为”构造函数重载“。使用此方法需要注意:构造函数重载的语句必须是构造函数中的第一条语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Student {
private String name;
private int age;

public Student(String name) {
this(name, 18); // 调用另一个构造函数
}

public Student(String name, int age) {
this.name = name;
this.age = age;
}

public void displayInfo() {
System.out.println("Student name: " + this.name + ", age: " + this.age);
}

public static void main(String[] args) {
Student student1 = new Student("Alice");
student1.displayInfo(); // Output: Student name: Alice, age: 18

Student student2 = new Student("Bob", 20);
student2.displayInfo(); // Output: Student name: Bob, age: 20
}
}

4.返回当前对象本身:

this关键字可以作为方法的返回值,返回当前对象本身。这种用法在实现方法链的时候非常有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Student {
private String name;
private int age;

public Student setName(String name) {
this.name = name;
return this;
}

public Student setAge(int age) {
this.age = age;
return this;
}

public void displayInfo() {
System.out.println("Student name: " + this.name + ", age: " + this.age);
}

public static void main(String[] args) {
Student student = new Student();
student.setName("Alice").setAge(20).displayInfo(); // Output: Student name: Alice, age: 20
}
}

在上述例子中,setName和setAge方法返回this,使得调用可以链接在一起,实现了方法链

Final 关键字

final代表最终的,不可改变的,一般有四种使用方法

  1. 修饰类(被final修饰的类可以叫太监类,因为它不能具有子类)

    一个类如果被final修饰的话,这个类便不能再拥有子类,并且其中的所有成员方法都无法被覆盖重写

    1
    2
    3
    4
    5
    6
    final class FinalClass{
    public void display(){
    System.out.println("this is a final class");
    }
    }
    //尝试继承FinalClass会导致编译报错
  2. 修饰方法

    当final修饰一个方法后,该方法便不能被覆写

  3. 修饰局部变量

    对于基本数据类型来说,final意味着变量中的数据内容不可改变;

    对于引用数据类型来说,final意味着变量中的地址值不可改变,但数据内容是可以改变的(地址所指向的对象可以改变)。

  4. 修饰成员变量

    对于成员变量来说,经过final修饰后同样不可变

    由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再提供默认值

    对于final修饰后的成员变量,要么直接复制,要么从构造方法赋值

    必须保证类中所有重载的构造方法都会对final的成员变量进行复制

    如果选择在构造方法中复制的话,为了确保线程安全,通常会把setname()方法取消掉

static关键字

static静态,用于修饰类的成员(包括方法与变量),表示这些成员属于类本身而非某个具体实例(非静态则属于某个具体实例,依赖于实例对象进行访问/使用)

静态变量,也被称为类变量,在内存中只有一份副本,被该类中的所有实例对象所共享,一般将所有对象都相同的属性定义为静态变量

1
2
3
public class MyClass{
static int count = 0;
}//所有MyClass的实例对象都共享count变量

静态方法,也被称为类方法,可以直接通过类名调用,而无需构建实例来使用,静态方法只能使用静态变量与其他静态方法,无法访问非静态成员,而非静态方法可以访问静态变量及使用静态方法

1
2
3
4
5
public class MathPlus{
public static int add(int a, int b){
return a + b;
}
}//可以通过MathPlus.add(1,2)来调用

静态内部类

它与外部类的实例无关,可以直接创建实例。静态内部类不能直接访问外部类的非静态成员,但可以访问外部类的静态成员

1
2
3
4
5
6
7
8
9
public class OuterClass{
private static int outerStaticCount = 10;
private int outerInstancecount = 20 ;
public static class InnerClass{
public void accessOuterStaticCount(){
System.out.println("外部类的静态变量为" + outerStaticCount);
}
}
}

静态代码块,在类初始化的时候执行且仅执行一次(执行优先级高于非静态的初始化块),常用于对静态变量进行初始化

1
2
3
static{
STATEMENT
}

static关键字修饰的属性特点

  • 随着类的加载而加载
  • 优先于对象存在
  • 静态成员被所有对象共享
  • 可以直接使用类名访问

abstract关键字

abstract抽象的,主要用于修饰类和方法

  1. 修饰抽象类

    当abstract用于修饰类时,这个类就被称为抽象类,抽象类不能被实例化,也就是不能使用new关键字来创建该类的实例对象。抽象类一般作为一个通用的模板,使得子类可以继承抽象类并实现其中的抽象方法

  2. 修饰抽象方法

    抽象方法只有方法声明,没有方法体。抽象方法必须在抽象类中定义,子类继承抽象类时,必须实现父类中所有抽象方法,否则子类在声明时也必须声明为抽象类

访问权限修饰符

public 公共的

public是权限最大的修饰符,可以修饰类,成员变量,成员方法,构造方法。被其修饰后,可以在任何一个类,不管同不同包,任意使用

protected 受保护的

protected不能修饰外部类。被protected修饰后,只能被同包下的其他类访问。如果不同包下的类要访问被protected修饰的成员,这个类必须是其子类。 

default 默认的

如果一个类、变量、方法或构造函数没有使用任何访问权限修饰符,那么它具有默认的访问权限(default),也称为包访问权限。只有同一个包内的类可以访问这些成员。

private 私有的

private不能修饰外部类。被private修饰后,只能在其修饰的本类中访问,在其它类中无法被调用,但可以通过set和get方法向外界提供访问方式

作用范围

同一个类 同一个包 不同包的子类 不同包非子类
public
protected ×
default × ×
private × × ×

面向对象的三大特征

封装 Encapsulation

指将数据(属性)和操作数据的方法(行为)封装在一起,形成一个不可分割的整体,通过访问权限修饰符来实现,其目的是保护对象的内部状态不被外部直接访问,只能通过公共接口(方法)来访问和修改

封装的实现

  1. 属性私有化
  2. 方法公开化
  3. 在类中定义构造方法

继承 Inheritance

指一个类可以继承另一个类的属性和方法,通过extends来实现

继承的实现

  1. 声明继承关系

    1
    2
    3
    public class Dog extends Animal{
    STATEMENT;
    }
  2. 继承属性和方法

    子类将继承父类的所有变量,方法以及嵌套类,但不会继承父类的构造函数(但在创建子类对象时会默认调用父类无参构造方法),同时,父类的私有(private)成员也不能被子类直接访问

  3. 重写父类方法

    子类可以重写(override)父类方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Animal{
    public void makeSound(){
    System.out.println("Animal make a sound");
    }
    }

    public class Dog extends Animal{
    @Override
    public void makesound(){
    System.out.println("bark!")
    }
    }
  4. 添加新属性和方法

    子类可以添加新的属性和方法以拓展父类方法

super关键字

super关键字可以用来引用当前对象的父类(超类)的成员变量或方法,主要用途如下:

  1. 访问父类的成员变量:

    当子类与父类中有同名的成员变量时,可以使用super关键字访问父类的成员变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class Parent {
    int x = 10;
    }

    class Child extends Parent {
    int x = 20;

    void display() {
    System.out.println(x); // 输出子类的x,即20
    System.out.println(super.x); // 输出父类的x,即10
    }
    }

  2. 调用父类的构造方法:

    在子类的构造方法中,可以使用super来调用父类的构造方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Parent {
    int x;

    Parent(int x) {
    this.x = x;
    }
    }

    class Child extends Parent {
    int y;

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

  3. 访问父类的方法

    当子类和父类中有同名的方法时,可以使用super来调用父类的方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    class Parent {
    public void display() {
    System.out.println("Display method in Parent");
    }
    }

    class Child extends Parent {
    public void display() {
    System.out.println("Display method in Child");
    }

    public void show() {
    super.display(); // 调用父类的display方法
    this.display(); // 调用当前类的display方法
    }
    }

    public class Main {
    public static void main(String[] args) {
    Child child = new Child();
    child.show();
    }
    }
  4. 在子类构造方法中调用父类的构造方法:

    当子类的构造方法中没有使用super来调用父类的构造方法时,会默认调用父类的无参构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Parent {
    int x;

    Parent(int x) {
    this.x = x;
    }
    }

    class Child extends Parent {
    int y;

    Child(int y) {
    this.y = y;
    // super(); // 默认调用父类的无参构造方法
    }
    }

多态 Polymorphism

对象可以具有多种形式或类型(为不同类型的实体提供一个统一的接口),比如多种动物都具有“吃”这个动作,但具体下来,狗可以表现为啃骨头,猫可以表现为抓老鼠吃等等

多态的实现主要依靠接口和继承

静态类型与动态类型

在Java编程中,静态类型是指变量在声明时所确定的类型,而动态类型是指变量在运行时实际引用的对象类型。这一对概念对于理解Java的多态特性至关重要。

1
List61B<String> lst = new SLList<String>();

比如在这行代码中,在声明过程中,lst的类型为List61B,这是他的静态类型(static type),在实例化过程中,它使用了SLList构造函数进行实例化,这是它的动态类型(dynamic type)

当java运行一个被覆盖的方法时,他会在其动态类型中搜索适当的方法签名以运行

接口 Interface

接口是一系列方法的声明(并不是所有接口内部都必须有方法),是一些方法特征的集合(是解决Java无法使用多继承的一种解决方式),按我的理解来看对象到类到接口就是一个不断抽象的过程,把接口当作一个特殊的类就好,由全局变量和抽象方法组成(100%的抽象类,接口中必须全是抽象方法)

Interface

定义接口时需要使用interface关键字

1
2
3
interface NAME{
STATEMENT;
}

类与接口之间不是继承关系,而是实现关系,需要用到implements关键字(如果一个类需要实现某个接口的话,必须实现这个接口内的所有方法)

1
2
3
className implements interfaceName{
STATEMENT;
}