# 类和对象

面向对象(Object Oriented,OO)是相对于面向过程的一种思想,它有两个核心概念就是类和对象。它将现实中的实体抽象成软件系统中的对象,再通过对象之间的操作和关系来构成系统。面向过程是以过程为中心,按照一定的步骤操作来完成目标。

# 面向对象和面向过程

举一个例子来说明它们之间的区别,比如说我们团建出去旅游,要用两辆车,现在用两种思想来描述下登车过程:

  • 面向过程:

    • 定义一个GetInCar(Person,Car_ID,relationDict)方法,以及车和人的对应关系,可用一个字典来表示,Person_Car。
    • 进行登车操作:
      • GetInCar(张三,1,Person_Car)。
      • GetInCar(李四,2,Person_Car)。
      • GetInCar(王五,1,Person_Car)。
  • 面向对象:

    • 分析下有两个对象,定义这两个对象:
      • 人(Person类)有名字和所属车的属性,和登车方法GetInCar(Car_ID);
      • 车(Car类)要有ID和载客量等属性。
    • 进行登车操作:
      • Person(Name=张三).GetInCar(Car_ID=1);
      • Person(Name=李四).GetInCar(Car_ID=2);
      • Person(Name=王五).GetInCar(Car_ID=1);

这两种方式会有明显的差别,一般来说,现在程序总体是面向对象,涉及一些操作是面向过程。面向对象有什么好处呢?

  1. 可维护:当在安排过程中发现某辆车轮胎坏了,只需要把这个对象的轮胎属性替换掉即可,不会影响其他的。
  2. 可复用:当到景区后,人们选择去参加什么活动,这时候能重复Person类对象。
  3. 可扩展:现在不同的人想去不同的景区逛逛,只需要给Person类添加景区属性,就能区分开他们在哪个景区。

# 面向对象

通过对比了解面向对象后,现在熟悉下面向对象的主要特性:封装,继承和多态。

# 封装

封装是对外隐藏成员变量,属性,和实现细节,对外暴露可供外部操作的方法。这样做的好处有:

  1. 提高了代码的可维护性,隐藏了实现细节,比如一个类如果直接让外部访问它的属性,当修改的时候所有调用该属性的地方都要修改,当访问方法时,内部如何修改属性对外部来说都是不可见的。
  2. 提高了安全性,可在方法中写一些限制条件来限制外部访问,只有满足条件的才能访问。

# 继承

子类在父类的基础上派生,它继承了父类的一些属性,这会大大提高代码的复用性。在原有基础上进行扩展,更符合显示中对象之间的联系。

继承分单继承(只能继承一个父类),多继承(可继承多个父类)。Java中只有单继承,但是Java提供了接口(interface)来弥补了不能使用多继承的缺憾。

# 多态性

多态一般有两种形式:

  • 方法重载:一个方法有多种实现,每种实现参数各不相同,调用时按着参数来匹配相应的实现。
  • 对象多态:父类引用指向子类,并调用它们都有的相同方法,会随着调用对象不用而调用不同的方法。

# 类和对象关系

类是广义的概念,是众多对象抽象共同的特征而来的概念。类是对象的模板,而对象是类的实例。拿现实中的概念举个例子,比如"动物"和"猩猩","老虎","狮子"。动物就是后面三个概念的抽象概括,而后面三个是动物的具体实现。

来看下Java中类和对象的实现:

public class Person {
    String Name;
    int Car_ID;

    public void GetInCar(int car_id) {
        Car_ID = car_id;
    }

}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = p1;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

注意类实例化成对象时,所有对象的成员属性都是对象私有的,而类中的方法是对象共有的。方法的信息会保存在全局方法区中。

注意类属于引用类型,一个类的实例化过程可以看成是在内存中按照类的结构申请了一块内存,这时就涉及到堆(heap)和栈(stack)两种内存的概念:

  • 栈:编译器自动分配和释放,比如存储函数参数,局部变量等。
  • 堆:需要手动分配释放空间,一般是new申请的内容存储所在。

比如上面的p1变量就是存储在栈中,而后面new Person()开辟的空间是在堆中,p1中存放的是Person在堆中开辟的内存地址。p2也是存放的p1所指向的Person在堆中开辟的内存地址。注意当后面p1和p2都不指向Person开辟的地址时,Person实例的对象算是匿名对象,垃圾回收机制就会回收这片空间。

# 属性封装

根据面向对象中的封装原则,外界不应能直接访问类的属性,应该通过一组方法来获取或设置,拿Person举例:

public class Person {
    private String name;

    public String getName() {
        return name;
    }

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

    int Car_ID;

    public void GetInCar(int car_id) {
        Car_ID = car_id;
    }

}


public class Main {
    public static void main(String[] args) {
        Person p1 = new Person();
        p1.setName("xie");
        System.out.println(p1.getName());
    }
}
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

# 构造函数

构造方法是类中定义的一种特殊方法,我们平时实例化一个对象时用到关键字new,这个时候就是调用了构造函数。

构造方法和普通方法最大的区别在于,构造方法是在实例化对象的时候使用,而普通方法则是在实例化对象之后使用的。

public class Person {
    private String name;

    public Person() {
        System.out.println("this is default constructor!address:"+this);
    }

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

    public Person(String name, int car_ID) {
        super();
        this.name = name;
        Car_ID = car_ID;
    }

    public String getName() {
        return name;
    }

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

    int Car_ID;

    public void GetInCar(int car_id) {
        Car_ID = car_id;
    }

}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person();
        Person p2 = new Person("li");
        Person p3 = new Person("li",2);
    }
}
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
34
35
36
37
38
39
40
41

注意:

  • 构造方法的名称需要和类名保持一致,构造方法不允许有返回值类型声明。
  • 对象实例化(new ObjectName())是一定需要构造方法的,如果类中没有定义构造方法的化,会自动生成一个无参无返回值的默认构造方法。如果已经定义了一个构造函数,则不会自动生成默认构造方法。
  • 构造方法也属于方法,可以进行重载,尽量按照参数由少到多排列构造方法。
  • this在类中来指代当前对象,在使用this()来调用构造方法时,这句话一定要放在第一行。

# 类注意事项

Java类有很多名称,比如POJO(Plain Odrinary Java Object,普通Java对象),VO(Value Object,值对象),PO(Persistent Object,持久化对象),TO(Transfer Object,传输对象)。

  1. 类名称一定要有意义,能明确的描述事物。
  2. 类中的所有属性必须使用private封装,并提供getter()和setter()方法。
  3. 类中可以有很多构造方法,但是必须保留默认构造方法。
  4. 要获取对象详细信息的方法,可以定义该方法名为getInfo()。

# static属性

前面main方法中经常看到方法前面有static关键字,它的作用是:

  • 它可让类不用实例化就能通过类名调用该方法和属性。非static定义的属性和方法必须实例化之后通过对象名来调用。
  • 默认属性是对象实例化时私有的,但是如果用static修饰,它是该类所有对象共有的属性。
  • static定义的属性不在堆内存中,而是保存在全局数据区。
  • static定义的方法可以在没有实例化对象时使用,因此无法使用this。
  • static定义的方法不能调用非static的方法或属性。非static的方法或属性可以调用static的属性或方法。
public class TeamMember {
    private static int teamMemberNumbers=0;

    public TeamMember() {
        teamMemberNumbers++;
    }

    public int getTeamMemberNumbers() {
        return teamMemberNumbers;
    }

    public static int getTotalNumbers() {
        return teamMemberNumbers;
    }

}

public class Main {
    public static void main(String[] args) {
        TeamMember tm1 = new TeamMember();
        TeamMember tm2 = new TeamMember();
        TeamMember tm3 = new TeamMember();
        System.out.println("tm3.getTeamMemberNumbers:"+tm3.getTeamMemberNumbers());
        System.out.println("tm3.getTotalNumbers:"+tm3.getTotalNumbers());
        System.out.println("TeamMember.getTotalNumbers:"+TeamMember.getTotalNumbers());
    }
}
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

# 各种代码块

Java中一个{}括起来的就是一个代码块,但是在类中它有一些特殊作用,可作为类的构造代码块,它比构造方法提前执行,其中静态构造代码块是可以用来初始化一些静态属性的。

在JDK 1.7之前,Java一直存在bug,就是静态代码块优先于主方法,可用静态代码块来代替主方法,但是后面就不行了。

class TestCodeBlock{
    public TestCodeBlock(){
        System.out.println("TestCodeBlock Construction Method!");
    }

    {
        System.out.println("Construction Block!");
    }
    static {
        //可用来初始化一些静态属性。
        System.out.println("Static Construction Block!");
    }
}

public class Main {
    public static void main(String[] args) {
        {
            int x=10;
            System.out.println("x="+x);
        }
        new TestCodeBlock();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23