java面向对象编程知识小结(一)

2023-05-06 21:32:10 浏览数 (1)

面向对象编程知识小结

1、简要介绍

Java是一种面向对象的编程语言。面向对象编程,英文是Object-Oriented Programming,简称OOP。面向对象编程,是一种通过对象的方式,把现实世界映射到计算机模型的一种编程方法。

2、基本概念

2.1 类

class是一种对象模版,它定义了如何创建实例。而instance是对象实例,instance是根据class创建的实例。

  • 定义person类
代码语言:javascript复制
class Person {
    public String name;
    public int age;
}

2.2实例

用new操作符创建一个实例,然后,我们需要定义一个引用类型的变量来指向这个实例:

代码语言:javascript复制
Person ming = new Person();

上述代码创建了一个Person类型的实例,并通过变量ming指向它。

2.3. 方法

2.3.1简要介绍

一个class可以包含多个属性,例如,我们给Person类就定义了两个属性:name和age。但是,直接把属性(field)用public暴露给外部可能会破坏封装性。所以需要结合使用修饰符public、private、protected,然后定义方法,实现类的封装。

代码语言:javascript复制
public class Oop {
    public static void main(String[] args) {
        Person ming = new Person();
        ming.name = "Xiao Ming"; // 对字段name赋值
        ming.age = 12; // 对字段age赋值
    }
}
class Person {
    private String name;
    private int age;
}

例如上方代码中,将person的成员变量用private修饰,但未设置成员方法访问,在实例化对象后直接报错:

所以这个时候,为了既实现封装,又能访问、操作class的私有属性,就需要为类添加public修饰的方法。具体如下代码所示:

代码语言:javascript复制
public class Main {
    public static void main(String[] args) {
        Person ming = new Person();
        ming.setName("Xiao Ming"); // 设置name
        ming.setAge(12); // 设置age
        System.out.println(ming.getName()   ", "   ming.getAge());
    }
}

class Person {
    private String name;    //私有属性
    private int age;

    public String getName() {  //成员方法
        return this.name;
    }

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

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        if (age < 0 || age > 100) {
            throw new IllegalArgumentException("invalid age value");
        }
        this.age = age;
    }
}

2.3.2 this变量

在方法内部,可以使用一个隐含的变量this,它始终指向当前实例。因此,通过this.field就可以访问当前实例的字段。

2.3.3方法参数

方法可以包含0个或任意个参数。方法参数用于接收传递给方法的变量值。调用方法时,必须严格按照参数的定义一一传递。

代码语言:javascript复制
//示例代码
class Person {
    ...
    public void setNameAndAge(String name, int age) {//两个参数
        ...
    }
}

2.3.4构造方法

创建实例的时候,实际上是通过构造方法来初始化实例的。如下类中的有两个参数的构造方法。

代码语言:javascript复制
class Person {
    private String name;
    private int age;

    public Person(String name, int age) {//两个参数的构造方法
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

构造方法的名称就是类名构造方法的参数没有限制,在方法内部,也可以编写任意语句。但是,和普通方法相比,构造方法没有返回值(也没有void),调用构造方法,必须用new操作符。 任何类都有构造方法,如果一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句,例如:

代码语言:javascript复制
class Person {
    public Person() { //默认的构造方法,如果不显式定义构造方法的,编译器会自动生成一个类似这样的构造方法。
    }
}

要特别注意的是,如果我们自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法如下示例中,就会产生错误:

代码语言:javascript复制
public class Main {
    public static void main(String[] args) {
        Person p = new Person(); // 编译错误:找不到这个构造方法
    }
}

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

错误如下:

总之,想要使用什么样的构造方法,要自己显式定义出来,并且可以定义多个构造方法

2.3.5方法重载

在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。例如,在Hello类中,定义多个hello()方法:

代码语言:javascript复制
 public void hello() {
 //方法名相同,参数不同。
        System.out.println("Hello, world!");
    }

    public void hello(String name) {
        System.out.println("Hello, "   name   "!");
    }

    public void hello(String name, int age) {
        if (age < 18) {
            System.out.println("Hi, "   name   "!");
        } else {
            System.out.println("Hello, "   name   "!");
        }
    }

注意:方法重载的返回值类型通常都是相同的。 方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。

3、实现方式

3.1 继承

继承是面向对象编程中非常强大的一种机制,它首先可以复用代码。Java使用extends关键字来实现继承:

代码语言:javascript复制
class Person {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
}

class Student extends Person {
    // 不要重复name和age字段/方法,
    // 只需要定义新增score字段/方法:
    private int score;

    public int getScore() { … }
    public void setScore(int score) { … }
}

注意

  1. 通过继承,Student只需要编写额外的功能,不再需要重复代码。
  2. 在OOP的术语中,我们把Person称为超类(super class),父类(parent class),基类(base class),把Student称为子类(subclass),扩展类(extended class)。
  3. Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。
  4. 继承有个特点,就是子类无法访问父类的private字段或者private方法。为了让子类可以访问父类的字段,我们需要把private改为protected。
  5. 用protected修饰的字段可以被子类访问。protected关键字可以把字段和方法的访问权限控制在继承树内部,一个protected字段和方法可以被其子类,以及子类的子类所访问
  6. super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName。
  7. 正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。 在Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();
代码语言:javascript复制
class Student extends Person {
    protected int score;

    public Student(String name, int age, int score) {
        super(); // 自动调用父类的构造方法
        /*
        如果父类没有无参的构造方法,可以在super()中添加已定义且与构造方法相同的参数列表,如下所示
        super(name, age);  调用父类的构造方法Person(String, int)
        */
        this.score = score;
    }
}

结论:如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法。子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

从Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称。 示例:

代码语言:javascript复制
public sealed class Shape permits Rect, Circle, Triangle {
    ...
}

上述Shape类就是一个sealed类,它只允许指定的3个类继承它。 如果写:

代码语言:javascript复制
public final class Rect extends Shape {...}

是没问题的,因为Rect出现在Shape的permits列表中。 但是如果不是shape指定的三个类,则会报错,如:

代码语言:javascript复制
public final class Ellipse extends Shape {...}
// Compile error: class is not allowed to extend sealed class: Shape
  1. 向上转型 把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。向上转型实际上是把一个子类型安全地变为更加抽象的父类型:
代码语言:javascript复制
Student s = new Student(); //student 继承 person
Person p = s; // upcasting, ok
Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok
  1. 向下转型 和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting).例如:
代码语言:javascript复制
Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!

Person类型p1实际指向Student实例,Person类型变量p2实际指向Person实例。在向下转型的时候,把p1转型为Student会成功,因为p1确实指向Student实例,把p2转型为Student会失败,因为p2的实际类型是Person,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。 因此,向下转型很可能会失败。失败的时候,Java虚拟机会报ClassCastException。 为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型:

代码语言:javascript复制
Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false

Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true

Student n = null;
System.out.println(n instanceof Student); // false

instanceof实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为null,那么对任何instanceof的判断都为false。

3.2多态

在继承关系中,子类如果定义了一个与父类方法签名完全相同的方法,被称为覆写(Override)。 例如,在Person类中,我们定义了run()方法:

代码语言:javascript复制
class Person {
    public void run() {
        System.out.println("Person.run");
    }
}
在子类Student中,覆写这个run()方法:

class Student extends Person {
    @Override
    public void run() {
        System.out.println("Student.run");
    }
}

Override和Overload不同的是,如果方法签名不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override。

加上@Override可以让编译器帮助检查是否进行了正确的覆写,@Override不是必需的。希望进行覆写,但是不小心写错了方法签名,编译器会报错。如下所示:

代码语言:javascript复制
public class Main {
    public static void main(String[] args) {
    }
}

class Person {
    public void run() {}
}

public class Student extends Person {
    @Override // Compile error!
    public void run(String s) {}
}

Java的实例方法调用是基于运行时的实际类型的动态调用,而非变量的声明类型。 这个非常重要的特性在面向对象编程中称之为多态。它的英文拼写非常复杂:Polymorphic。

  1. 定义 多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法.
  2. 在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。
  3. 继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override.
  4. 如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承
  5. 对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改。

0 人点赞