面向对象的编程

本文详细介绍了面向对象编程的基础知识,包括类与实例的概念,如何定义和使用类,以及new操作符的作用。讲解了private和public字段的访问控制,方法的重载和继承机制。还探讨了抽象类、接口、多态性以及子类型多态的特性,并强调了子类重写方法的规则。此外,提到了静态和实例方法的区别以及泛型和参数多态在Java中的应用。
摘要由CSDN通过智能技术生成
7 Object-Oriented Programming (OOP)
面向对象的编程
java基础知识准备
现实世界中,我们定义了“人”这种抽象概念,“人”可以定义为一个类(class),而具体的人则是实例(instance):
类 / classclass Person { }
小明实例 / mingPerson ming = new Person()

定义class

在Java中,创建一个类,例如,给这个类命名为Person,就是定义一个class

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

定义了class,只是定义了对象模版,而要根据对象模版创建出真正的对象实例,必须用new操作符。

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

Person ming = new Person();

有了指向这个实例的变量,我们就可以通过这个变量来操作实例。访问实例变量可以用变量.字段,例如:

ming.name = "Xiao Ming"; // 对字段name赋值
ming.age = 12; // 对字段age赋值
System.out.println(ming.name); // 访问字段name

Person hong = new Person();
hong.name = "Xiao Hong";
hong.age = 15;

直接操作field,容易造成逻辑混乱。为了避免外部代码直接去访问field,我们可以用private修饰field,拒绝外部访问:

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

我们需要使用方法(method)来让外部代码可以间接修改field

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;
    }
}

所以,一个类通过定义方法,就可以给外部代码暴露一些操作的接口,同时,内部自己保证逻辑一致性。

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

class Hello {
    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 + "!");
        }
    }
}

这种方法名相同,但各自的参数不同,称为方法重载Overload)。

继承是面向对象编程中非常强大的一种机制,它首先可以复用代码。当我们让StudentPerson继承时,Student就获得了Person的所有功能,我们只需要为Student编写新增的功能。

Java使用extends关键字来实现继承:

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) { … }
}

可见,通过继承,Student只需要编写额外的功能,不再需要重复代码。

 注意:子类自动获得了父类的所有字段,严禁定义与父类重名的字段!

在OOP的术语中,我们把Person称为超类(super class),父类(parent class),基类(base class),把Student称为子类(subclass),扩展类(extended class)。

继承有个特点,就是子类无法访问父类的private字段或者private方法。

这使得继承的作用被削弱了。为了让子类可以访问父类的字段,我们需要把private改为protected。用protected修饰的字段可以被子类访问。

super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName

静态/实例方法
在类中使用static修饰的静态方法会随着类的定义而被分配和装载入内存中;

而非静态方法属于对象的具体实例,只有在类的对象创建时在对象的内存中才有这个方法的代码段

编译器只为整个类创建了一个静态变量的副本,也就是只分配一个内存空间,虽然可能有多个实例,但这些实例共享该内存

接口(Interface)
接口之间可以继承与扩展,一个类可以实现多个接口,一个接口可以有多种实现类

接口:确定ADT规约;

 实际中更倾向于使用接口来定义变量

类:实现ADT

Java的接口中不能含有constructors,但是从Java 8开始接口中可以含有static工厂方法,可用其替代constructors

default
通过default方法,可以在接口中统一实现某些功能,无需在各个类中重复实现它。好处是以增量式为接口增加额外的功能而不破坏已经实现的类

重写(Overriding)
严格继承:子类只能添加新方法,无法重写超类中的方法

如果想要一个java中方法不能被重写,必须要加上前缀final

父类型中的被重写函数体不为空:意味着对其大多数子类型来说,该方法是可以被直接复用的。对某些子类型来说,有特殊性,故重写父类型中的函数,实现自己的特殊要求
如果父类型中的某个函数实现体为空,意味着其所有子类型都需要这个功能,但各有差异,没有共性,在每个子类中均需要重写
重写时,可以利用super()来复用父类型中函数的功能


抽象类(Abstract Class)
抽象方法:只有声明没有具体实现的方法。用关键词abstract来定义

抽象类:如果一个类含有至少一个抽象方法,则被称为抽象类

接口:一个只含有抽象方法的抽象类

如果某些操作是子类型都共有,但彼此有差别,可以在父类型中设计抽象方法,在各子类型中重写

接口和抽象类都不能实例化!

多态、子类型、重载(Polymorphism, subtyping and overloading)

多态的三种类型
特殊多态(Ad hoc polymorphism):重载
参数化多态(Parametric polymorphism):泛型
子类型多态、包含多态(Subtyping):继承
特殊多态和重载(Overloading)
重载:多个方法具有同样的名字,但有不同的参数列表或返回值类型

重载是一种静态多态,根据参数列表进行"最佳匹配",进行静态类型检查

重载的解析在编译阶段,与之相反,重写的方法是在运行阶段进行动态类型检查

参数列表必须不同
相同/不同的返回值类型
相同/不同的public/private/protected
可以声明新的异常


参数多态和泛型(Generic)
泛型擦除:运行时泛型类型消除(如:List<String>运行时是不知道String的),所以,不能使用泛型数组(如: Pair < String >[] foo = new Pair < String >[42]; 是错误的!不能被编译!)

如下是一个错误的实例:

List<Object> a; List<String> b; a = b;

通配符(Wildcards),只在使用泛型的时候出现,不能在定义中出现。 如:List< ? extends Animal >

?extends T 和 ?super T 分别表示T和它的所有子/父类

子类型多态、继承
重写时,子类的规约要强于父类的规约(更弱的前置条件,更强的后置条件)
子类的可见性要强于父类(即父类如果是public,子类不能为private)
子类不能比父类抛出更多的异常
(详情见LSP原则)

注:Java无法检测1,但是可以检测出2、3

子类型多态:不同类型的对象可以统一的处理而无需区分。

instanceof
instanceof()判断对象运行时的类型

注:其父类也会判为true,如 a instanceof Object 始终为true

getclass()获取当前类型

List<Object>不是List<String>的父类
List<String>是ArrayList<String>的父类
List<?> 是 List<String>的父类
注:重写equal()方法时,需要注意参数类型,必须也是Object类型

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值