Java—面向对象篇

183 阅读12分钟

Hello,这是本平台的第一篇文章,如果喜欢,欢迎多多支持呦!


类和对象

面向对象程序设计(OOP) 想要理解OOP,必须先理解类和对象。

  • 类 是构造对象的模板,对象 是类的实例。 类作为对象的规范而存在,其中有成员变量成员函数,可以认为成员函数中包括构造函数,而通过构造函数可以构造一个对象。 构造函数应与类名相同,所以我们会看到这样的一幕: Scanner in = new Scanner (System.in); Scanner ()就表示调用了一个构造函数,括号里的System.in只不过是往构造函数中传入了一个参数。 这同时也为我们引出了对象······
  • 我们使用 new+构造函数 创建对象。其中in是对象变量,但对象变量是对象的管理者,并非对象本身。

关于对象的定义,在《Java核心卷》中提到:定义对象应先了解对象的三个特征。 1. 对象的行为-通过方法定义 2. 对象的状态-状态的改变通过调用方法实现 3. 对象的标识-作为类的实例,每个对象的标识都不同 例如:我们熟悉的in.nextInt(); 就是对象变量调用了Sanner类的方法(函数)nextInt。这就定义了对象的行为,可以让对象为我们做事。

上文提到的方法,就是类中的成员函数。想要创建自己的类,首先应该明白以下三条书写规则。

  1. 构造函数:public +类名 () {}
  2. 成员变量:private +变量类型+变量名称
  3. 成员函数:public +函数类型 +函数名称() {}

访问修饰符

大家可能会注意到前面的public/private,这是Java访问修饰符。 权限顺序:public>protected>default>private

  • public: 使变量/函数在任何地方都可以被访问。
  • protected: 表示同一个包内可以访问,以及不在同一个包中的子类。
  • default: 表示同一个包内可以访问。
  • private: 只能用于成员变量和成员函数,仅限于一个类中。

最后来简单介绍封装,封装可以被认为是一个保护屏障,防止当前类的代码和数据被外部类定义的代码随机访问,一般使用private即可实现这种功能。

还要明确以下几点规则:

  • 在一个源文件(.java)中,只能有一个公有(public)类,但可以有无数个非公有类。
  • public类名要与源文件名相同,构造函数要与类名相同。
  • 程序中会存在默认构造器。

成员变量与成员函数

类由成员变量成员函数组成。 类定义了对象中所具有的变量,这些变量称为成员变量。每一个对象都有自己的变量,和同一类的其他对象是分开的,所以成员变量的生存期是对象的生存期,作用域是类内部的成员函数。

package、import

  • package Java 使用包(package)这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class)、接口、枚举(enumerations)和注释(annotation)等。 Java是用文件夹去管理包的。包的名字可以加 . 来表明文件夹的层次。
  • import 为了能够使用某一个包的成员,我们需要在 Java 程序中明确导入该包。使用 "import" 语句可完成此功能。import 可以提供一个路径,使得编译器可以找到某个类。 比如在使用Scanner类之前通常会有一句import java.util.Scanner; 如果不用import,就需要写出类的全名:(包.类)

静态域与静态方法

  • 具有static修饰符的变量: 具有static修饰符的变量叫做类变量(静态变量)。 表明变量不在任何对象中,而是在类中,且只有一个。 而每一个对象对于所有的实例域却都有自己的一份拷贝。

  • 具有static修饰符的函数: 具有static修饰符的函数叫做类函数。表明函数不属于任何对象,而是属于这个类。,static函数和static函数之间可以访问。

  • 关于static函数的调用: 静态(static)方法可以不用创建对象就调用。 注:在调用时,通过类调用或通过对象调用都是相同的,但更推荐通过类调用类函数/类变量。

补充:程序执行时发生了什么?

1. 静态优先,构造随后。 static修饰的成员变量和方法是属于类的,Java中的静态代码块是在虚拟机加载类的时候就执行的,而且只执行一次。而普通变量是属于对象的,调用类的构造方法是在new一个对象的时候执行的。 2. 先父再子。 由于子类继承了父类,就拥有了父类的所有属性和方法(除了构造方法),在创建子类对象时,先会执行构造函数的第一句super()来调用父类对应的构造方法。所以,会先向上追溯到Object,然后在依次向下执行类的初始化块和构造方法,直到当前子类为止。

隐式参数和显式参数

在程序示例1-1中,使用了this关键字。在执行void getName(String n)方法时,该方法存在两个参数,一个是隐式参数,就是在调用方法前创建的dog对象。如果不使用this,name=n将会调用Dog类的对象dog的实例域name设置为值n,dog.name=n。 如果使用this,this就会表示该隐式参数。 另一个是显式参数,也就是在方法括号中的String n

程序示例1-1

public class Main {
    public static void main(String[] args) {
        //new一个对象,交给dog来管理。
        Dog dog=new Dog(12,"red",1,"dog");
        //使用对象变量调用方法
        dog.getName();
    }
}
class Dog {
    //成员变量
    private int size;
    private String colour;
    private int age;
    private String name;
    //构造函数(无传入参数)
    public Dog(){
    }
    //构造函数(有传入参数)
    public Dog(int size, String colour, int age, String name) {
        this.size = size;
        this.colour = colour;
        this.age = age;
        this.name = name;
    }
    //以下为成员函数,表示这个类存在的方法,表示类可以调用的方法。
    void getName(){
        System.out.println(name);
    }
    @Overload
    void getName(String n){
        this.name=n;
        System.out.println(name);
    }
}

继承(extends)

面向对象程序设计语言有三大特性:封装、继承和多态性。前面已经介绍过封装,接下来我们来介绍继承。

  • 什么是继承? 继承是一个is-a关系,表示父类与子类之间的关系,子类通过继承父类所有非私有的方法,从而达到重复使用父类数据和方法的效果。
  • 为什么要用继承? 降低代码的复用性。需要注意的是,Java并不支持多继承(一个子类继承两个父类),但是可以通过接口来实现多继承。

继承关键字

final关键字

final修饰变量:final实例域一般和static一起使用,修饰变量时,表示变量不可以进行修改。 final 修饰类:说明该类不允许任何类继承。 final修饰方法,该方法不能被子类重写

super关键字

super有两个用途,一个是调用父类的构造器,一个是调用父类的方法

  • 作为前者 当父类的构造器无传参时,构造子类对象时会先进行父类的定义初始化和调用构造器。父类构造器有传参时,则必须使用super关键字来表示调用父类的构造器。
  • 作为后者 若子类和父类中有同名的函数,可以使用super.调用父类的函数。 参考程序实例2-1

instance of关键字

instance of通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。

判断LinkedHashMap是否为HashMap的子类?

	boolean o=false;
      LinkedHashMap linkedHashMap=new LinkedHashMap();
      o=linkedHashMap instanceof HashMap;
      System.out.println(o);  //true

程序示例2-1

测试类

public class ManagerTest {
    public static void main(String[] args) {
        Employee employee=new Employee("Wang",1000);
        System.out.println(employee.getSalary());
    }
}

Manager子类继承Employee类

//extends关键字,表示Manager方法继承于Employee。
class Manager extends Employee{
    private double salary;

    public Manager(String name,double baseSalary,double salary){
        //使用super关键字,调用父类构造器。
        super(name,baseSalary);
        this.salary=salary;
    }
    //子类重写父类方法
    @Override
    public double getSalary() {
        //使用super关键字,调用父类方法。
        return super.getSalary()+salary;
    }
	}
}

Employee父类

class Employee{
    public String name;
    public double baseSalary;
    public Employee(String name,double baseSalary){
        this.name=name;
        this.baseSalary=baseSalary;
    }
    public double getSalary(){
        return baseSalary;
    }

重写(Override)& 重载(Overload)

@Override

  • 定义 重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
  • 使用 Java的多数类都重写了Object类中的方法。

实现重写,子类能够根据需要实现父类的方法。

@Overload

  • 定义 一个类中定义了多个方法名相同,而参数数量/类型/次序不同的方法,则称为方法的重载(Overload)。
  • 使用 在编写类时,一般会写一个空参的构造器,和一个带参的构造器。这里就使用到了重载。

方法重载(Overload)是一个类的多态性表现,而方法重写(Override)是子类与父类的多态性表现

Object类

Java中所有的类都是从Object继承的,也就是所有类的父类。进而可知,所有其他类都可以使用/重写Object类的方法。

  • 常用的Object类的方法
  1. toString方法,用来显示对象的字符串形式。
  2. equal()方法,在Object类中的equals方法只是简单地比较地址是否相等。 而多数Java类中实现了重写equals方法,使其比较内容是否相等。在比较两个对象是否相等时,一般使用equals方法,当然前提是对象所属的类已经重写equals了。

多态

现实事物经常会体现出多种形态,如学生,学生是人的一种,则一个具体的同学张三既是学生也是人,即出现两种形态。
Java作为面向对象的语言,同样可以描述一个事物的多种形态。如Student类继承了Person类,一个Student的对象便既是Student,又是Person。

  • 多态的前提 继承+重写+向上造型。 Java的对象变量是多态的,能保存不止一种类型的对象。
  • 向上造型(多态的体现) 父类引用指向子类对象。
  • 多态的好处 可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。

这里就使用了父类引用指向子类对象(向上造型)。实现向上造型必须满足两个条件:

  1. 在编译阶段,只是检查参数的引用类型,把多态的对象变量manager看作是父类类型。
  2. 可以调用那些方法看父类,而方法具体执行看子类是否重写。

简单来说,就是编译看左,运行看右。

抽象(Abstract)和接口(Interface)

  • 抽象 父类只是定义了一个抽象函数的概念,而具体的方法要交给子类实现
  • 抽象的两个规则 如果一个方法是抽象的,那么它的类也应该是抽象的。 如果父类是抽象的,子类必须实现父类的抽象方法或变为抽象类。

在这里需要注意,抽象类是不能进行实例化的。

  • 接口 接口就是抽象方法的集合。在学习中,经常把接口和抽象类放在一起进行学习。

文章链接:接口和抽象类的区别

程序示例3-1

测试类

public class Text {
    public static void main(String[] args) {
        //父类引用指向子类对象。
        Person p =new Student(12,"Sivan",123);
        Student s=new Student(12,"Sivan",123);
        System.out.println(p.getName());  //Sivan
        //编译不通过,因为父类中没有此方法。
//        p.getNumber();
        //编译通过,运行通过
        System.out.println(s.getNumber()); //123
        p.eat();
        s.eat();
        System.out.println(p.equals(s));  //true
    }
}

function接口

//定义接口,接口中的方法都是抽象的
interface function{
    public void eat();
    public void walk();
}

Person类

//类调用接口
class Person implements function{
    private int age;
    private String name;
    //实现两个构造器,重载的实例
    public Person(){

    }
    public Person(int age,String name){
        this.age=age;
        this.name=name;
    }
    public String  getName(){
        return name;
    }
    //实现接口的功能
    public void eat(){
        System.out.println("EAT");
    }
    public void walk(){
        System.out.println("WALK");
    }
}

Student子类,继承Person

class Student extends Person {
   private int number; //学号
    public Student(int age, String name, int number) {
        super(age, name);
        this.number = number;
    }
    public int getNumber() {
        return number;
    }
    //重写接口方法
    @Override
    public void eat(){
        System.out.println("Student EAT");
    }
    //重写Object类的方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return number == student.number;
    }
}

枚举类

Java 枚举是一个特殊的类,一般表示一组常量,比如一年的 4 个季节,一个年的 12 个月份,一个星期的 7 天,方向有东南西北等。 Java 枚举类使用 enum 关键字来定义,各个常量使用逗号 , 来分割。

枚举类的使用

  • 枚举类的使用说明
  1. 类的对象只有有限个,确定的。
  2. 需要定义一组常量时,建议使用枚举类。
  3. 如果枚举类只有一个对象,可以看作是单例模式的实现方式。
  • 自定义枚举类
  1. jdk5.0之前,自定义。程序示例:EnumText1.java
  2. jdk5.0之后,使用enum关键字定义枚举类。程序示例:EnumText2.java
  • 枚举类中的方法 程序示例:EnumText2.java
  • 枚举类使用接口的情况 程序示例:EnumText2.java

最后的补充:Junit测试方法 JUnit是单元测试的基础单元测试框架。每编写完一个函数之后,都应该对这个函数的方方面面进行测试。 在每个方法前加入@Test,alt+enter即可导入Junit进行单元测试。