Java对象与类

你之因此能优于别人,正是由于你坚持了别人所不能坚持的。
本文相关代码在个人Github,欢迎Star~
https://github.com/zhangzhibo1014/DaBoJava

前言

在此以前,咱们已经把Java基础的基础语法总结了一下,今天咱们来学习一下面向对象的相关知识,今天的内容理论性偏多,但愿你们能耐心的看完,相信会收获不少。都说 Java 是面向对象程序设计的语言,那么究竟什么是面向对象呢?若是你没有面向对象程序设计的应用背景,那么和我一块儿来认真的阅读本文吧!java

面向对象程序设计概述

面向对象程序设计(简称 OOP ),是当今主流的设计范型。面向对象程序是由对象组成的,每一个对象包含对用户公开的特定功能部分和隐藏的实现部分。在 OOP 中,没必要关心对象的具体实现,只要能知足用户的需求便可。git

类( class)是构造对象的模板或蓝图。由类构造(construct) 对象的过程称为建立类的实例 (instance )。github

封装( encapsulation , 有时称为数据隐藏) 是与对象有关的一个重要概念。从形式上看,封装不过是将数据和行为组合在一个包中, 并对对象的使用者隐藏了数据的实现方式。对象中的数据称为实例域( instance field ), 操纵数据的过程称为方法( method ) 。对于每一个特定的类实例(对象)都有一组特定的实例域值。这些值的集合就是这个对象的当前状态( state )。不管什么时候,只要向对象发送一个消息,它的状态就有可能发生改变。sql

OOP 的另外一个原则会让用户自定义 Java 类变得垂手可得,这就是:能够经过扩展一个类来创建另一个新的类。在扩展一个已有的类时, 这个扩展后的新类具备所扩展的类的所有属性和方法。在新类中,只需提供适用于这个新类的新方法和数据域就能够了。编程

对象

要想使用 OOP ,必定要清楚对象的三个主要特性segmentfault

  • 对象的行为(behavior)- 能够对对象施加哪些操做,或能够对对象施加哪些方法?
  • 对象的状态(state)- 当施加那些方法时,对象如何响应?
  • 对象标识(identity)- 如何辨别具备相同行为与状态的不一样对象?

识别类

在面向对象的学习中,咱们首先要学会设计类,而后在往类中添加所需的方法。微信

识别类的简单规则是在分析问题的过程当中寻找名词,而方法对应着动词。框架

例如:在订单处理系统中,有这样一些名词:ide

  • 商品(Item)
  • 订单(Order)
  • 送货地址(Shipping address)
  • 付款(Payment)
  • 帐户(Account)

这些名词极可能成为类 ItemOrder 等。函数

接下来, 查看动词:商品被添加到订单中, 订单被发送或取消, 订单货款被支付。对于每个动词如:“ 添加”、“ 发送”、“ 取消” 以及“ 支付”, 都要标识出主要负责完成相应动做的对象。例如,当一个新的商品添加到订单中时, 那个订单对象就是被指定的对象, 由于它知道如何存储商品以及如何对商品进行排序。也就是说,add 应该是 Order 类的一个方法, 而 Item 对象是一个参数。

面向对象的特色

  • 将复杂的事情简单化
  • 面向对象将之前的过程当中的执行者,变成了指挥者

    过程和对象在咱们程序中是如何体现的呢?
    过程其实就是函数,对象是将函数等一些内容进行了封装
  • 面向对象思想符合人们思考习惯的一种思想

面向对象的三大特征

  • 封装(下面会介绍)
  • 继承(下面会介绍)
  • 多态(下面会介绍)

自定义类

要想建立一个完整的程序, 应该将若干类组合在一块儿, 其中只有一个类有 main 方法。

Employee类

在Java中,最简答的类的形式以下:

class Employee {
    //成员变量
    field1;
    field2;
    ....
    //构造器
    constructor1;
    constructor2;
    ...
    //成员方法
    method1;
    method2;
    ....
}

类的成员

在类中定义其实都称之为成员。成员有两种:

  • 成员变量:其实对应的就是事物的属性
  • 成员方法:其实对应的就是事物的行为

成员变量和局部变量的区别?

  1. 成员变量直接定义在类中

    局部变量定义在方法中,参数上,语句中

  2. 成员变量在这个类中有效

    局部变量只在本身所属的大括号内有效,大括号结束,局部变量失去做用域

  3. 成员变量存在于堆内存中,随着对象的产生而存在,消失而消失

    局部变量存在于栈内存中,随着所属区域的运行而存在,结束而释放

构造器

构造器 用于给对象进行初始化,是给与之对应的对象进行初始化。

  • 构造器与类同名,在建立类的对象时,构造器会运行。
  • 构造器总会伴随 new 操做符的执行被调用。
  • 每个类能够有一个以上的构造器
  • 构造器能够有 0 个、 1 个或多个参数
  • 构造器没有返回值
public Student {

    private String name; //声明变量name,存储学生的姓名
    private int age; //声明变量age,存储学生的年龄
    
    //无参构造器
    public Student() {
        
    }
    //带有一个参数的构造器
    public Student(String aName) {
        name = aName;
    }
    //带有两个参数的构造器
    public Student(String aName, int aAge){
        name = aName;
        age = aAge;
    }
}
new Student();// 使用此方法new一个对象实例会调用Student()构造器,name被初始化为null,age被初始化为0
new Student("Tom");//使用此方法new一个对象实例会调用Student(String aName)构造器,age被初始化为0

封装

定义 指隐藏对象的属性和实现细节,仅提供对外公共访问方式

// 自定义Employee类
class Employee {

    // 成员变量
    private String name;
    private double salary;
    private LocalDate hireDay;

    // 构造器 或 构造函数
    public Employee(String n, double s, int year, int month, int day) {
        name = n;
        salary = s;
        hireDay = LocalDate.of(year, month, day);
    }

    // 成员方法
    // 获取姓名
    public String getName() {
        return name;
    }
    //获取薪资
    public double getSalary() {
        return salary;
    }
    //获取雇用日期
    public LocalDate getHireDay() {
        return hireDay;
    }
    /**
     * 按百分比涨工资
     * @param byPercent   百分比
     */
    public void raiseSalary(double byPercent) {
        double raise = salary * (byPercent / 100);
        salary += raise;
    }
}

使用private来修饰的成员变量为私有的,只能被当前类使用,体现了良好的封装性。
getName() getSalary() getHireDay()使用public来修饰,供外界来访问类的私有属性

静态变量与静态方法

静态变量

若是将变量定义为static ,每一个类中只有一个这样的变量,每个对象都共享这样一个static 变量,这个static 变量不属于任何对象,只属于这个类

public Student {
    // 该静态变量只属于Student类,无论声明多少个学生对象,每一个学生都共有这一个学校名。都是清华大学
    private static String schoolName = "清华大学";
}

成员变量和静态变量的区别

  1. 成员变量所属于对象,因此也称为实例变量

    静态变量所属于类,因此也称为类变量

  2. 成员变量存在于堆内存中

    静态变量存在于方法中

  3. 成员变量随着对象建立而存在,随着对象被回收而消失

    静态变量随着类加载而存在,随着类的消失而消失

  4. 成员变量只能被对象所调用

    静态变量,也能够被对象调用,也能够被类调用。

静态常量

静态变量用的比较少,但静态常量用的相对比较多

例如,例如在Math类中定义一个静态常量

public Math {
    private static final double PI = 3.1415926;
}

在程序中,可使用Math.PI 的方式来使用静态常量,若是省去 static ,则必须经过 Math 的对象来访问 PI

静态方法

静态方法是一种不能向对象实施操做的方法。

public static String getSchoolName(){
    return schoolName;
}

静态方法只能经过类名去访问。 example: Student.getSchoolName();

何时定义静态成员呢??

  1. 成员变量。(数据共享时静态化)

    该成员变量的数据是不是全部对象都同样

    若是是,那么该变量须要被静态修饰,由于是共享数据

    若是不是,那么就说这是对象的特有数据,要存储到对象中

  2. 成员函数。(方法中没有调用特有数据时就定义静态)

    如何判断成员函数是否被静态修饰呢?

    只要参考,该函数内是否访问了对象中特有的数据

    若是有访问特有数据,那么方法不能被静态修饰

    若是没有访问特有数据,那么这个方法须要被静态修饰

重载

有些类可能有不少个构造器。例如

public Student {

    private String name; //声明变量name,存储学生的姓名
    private int age; //声明变量age,存储学生的年龄
    
    //无参构造器
    public Student() {
        
    }
    //带有一个参数的构造器
    public Student(String aName) {
        name = aName;
    }
    //带有两个参数的构造器
    public Student(String aName, int aAge){
        name = aName;
        age = aAge;
    }
}

这种特征叫作重载(overload)。

若是多个方法有相同的名字、不一样的参数,便产生了重载。

Java容许重载任何方法,不只仅是构造器。

不能有两个名字相同、 参数类型也相同却返回不一样类型值的方法,这不是方法的重载。

初始化块

在一个类的声明中,能够包含多个代码块。只要构造类的对象,这些块就会被执行。例如:

public Student{
    private String name;
    private int age;
    //初始化块
    {
        age = 18;
    }
}
不管使用哪一个构造器构造对象,age变量都在对象初始化块中被初始化。
首先运行初始化块,而后才运行构造器的主体部分。

对于静态成员初始化,可使用静态初始化块

public Student{
    private static String schoolName;
    
    static{
        schoolName = "清华大学";
    }
}

没有显式初始化的成员变量会默认进行初始化

  • 数值型默认值是 0
  • 布尔型默认值是 false
  • 对象引用默认值是 null

静态初始化块、初始化块、构造函数同时存在时的执行顺序:
静态初始化块 -> 初始化块 -> 构造函数

public class Demo3 {
    public static void main(String[] args) {
        People people = new People();
        System.out.println(people.toString());
    }

}

class People {
    private String name;
    private int age;

    {
        System.out.println("构造块");
    }

    static {
        System.out.println("静态构造块");
    }

    public People() {
        System.out.println("Person 构造器");
    }

    public String toString() {
        return getClass().getName() + "[name=" + name + ",age=" + age + "]";
    }
}
执行结果:
静态构造块
构造块
Person 构造器
People[name=null,age=0]  //默认初始化值

this 与 final

this表明当前对象。就是所在方法所属对象的引用

  • 调用格式: this(实际参数)
  • this 对象后面跟上 . 调用的是成员变量和成员方法
  • this 对象后面跟上()调用的是本类中的对应参数的构造函数

final

  1. 这个关键字是一个修饰符,能够修饰类,方法,变量。
  2. final 修饰的类是一个最终类,不能够被继承。
  3. final 修饰的方法是一个最终方法,不能够被覆盖。
  4. final 修饰的变量是一个常量,只能赋值一次。

Java 容许使用包( package ) 将类组织起来。借助于包能够方便地组织本身的代码,并将本身的代码与别人提供的代码库分开管理。

类的导入

一个类可使用所属包中的全部类, 以及其余包中的公有类( public class。)

咱们能够采用两种方式访问另外一个包中的公有类

  • 在每一个类名以前添加完整的包名。

    java.tiie.LocalDate today = java.tine.LocalDate.now();
  • 使用 import 语句

    import java.util .*;
    LocalDate today = LocalDate.now();

在发生命名冲突的时候,就不能不注意包的名字了。例如,java.utiljava.sql 包都有日期( Date) 类。在每一个类名的前面加上完整的包名。

java.util.Date deadline = new java.util.Date();
java.sql.Date today = new java.sql.Date();

静态导入

import 语句不只能够导入类,还增长了导入静态方法和静态变量的功能

import java.lang.System.*;

out.println();

将类放入包中

要想将一个类放入包中, 就必须将包的名字放在源文件的开头,包中定义类的代码以前。

package com.robin.java;

若是没有在源文件中放置 package 语句, 这个源文件中的类就被放置在一个默认包( defaulf package ) 中。

类的设计技巧

  • 必定要保证数据私有(这是最重要的,要保证类的封装性)
  • 必定要对数据初始化
  • 不要在类中使用过多的成员变量(也就是说能够把某些变量拆为一个类,下降耦合性)
  • 不是全部的成员变量都须要 getset 方法
  • 类名和方法名要能体现具体的职责

继承

定义子类

关键字 extends

public Animal{
    private int age;
    
    public void eat(){
        System.out.println("aminal eat food");
    }
}
// 狗继承动物
public Dog extends Animal{
    private String sex;
}
Animal称之为:超类,基类,父类
Dog称之为:子类,派生类

Dog不只从超类中继承了age属性,并且还定义了属性sex,此时Dog类中有age,sex两个属性
eat()方法,Dog也能够同时使用

方法的覆盖

超类中的方法不必定彻底适用于子类,因此须要提供一个新的方法来覆盖超类中的方法。

Animal中的eat()方法是用来吃食物,而Dog中也须要eat()方法,可是须要吃骨头,所以咱们能够提供一个新的方法来覆盖超类中的方法
public void eat() {
    System.out.println("Dog eat bone");
}

注意

子类在重写超类的方式时,子类方法不能低于父类方法的可见性。如超类的方法是 public ,子类必定为 public

继承的好处

  • 提升代码的复用性
  • 让类与类之间产生了关系,提供了另外一个特征多态的前提
  • Java 中只支持单继承

子父类出现后,类中的成员都有了哪些特色

  1. 成员变量

    当子父类出现同样的属性时,子类类型的对象,调用该属性,值是子类的属性值。

    若是想要调用父类的属性值,须要使用一个关键字: super

    this 表明是本类类型的对象引用

    super 表明是子类所属父类中内存空间的引用

  2. 成员函数

    当子父类中出现了如出一辙的方法时,创建子类对象会运行子类中的方法

    因此这种状况,是函数的另外一个特性:覆写(重写,复写)

  3. 构造函数

    发现子类构造函数运行时,先运行了父类的构造函数。为何呢?

    缘由:子类的全部构造函数的第一行,其实都有一条隐身的语句 super()

super()this() 是否能够同时出如今构造器中。
两个语句只能有一个定义在第一行,因此只能出现其中一个。

抽象类

若是自下而上在类的继承层次结构中上移,位于上层的类更具备通用性,甚至可能更加抽象。从某种角度看, 祖先类更加通用, 人们只将它做为派生其余类的基类,而不做为想使用的特定的实例类。

在不断抽取过程当中,将共性内容中的方法声明抽取,可是方法不同,没有抽取,这时抽取到的方法,并不具体,须要被指定关键字 abstract 所标示,声明为抽象方法。

抽象类的特色

  1. 抽象方法只能定义在抽象类中,抽象类和抽象方法必须由 abstract 关键字修饰(能够描述类和方法,不能够描述变量)
  2. 抽象方法只定义方法声明,并不定义方法实现
  3. 抽象类不能够被建立对象(实例化)
  4. 只有经过子类继承抽象类并覆盖了抽象类中的全部抽象方法后,该子类才能够实例化。不然,该子类仍是一个抽象类

抽象类细节

  1. 抽象类中是否有构造函数?

    有,用于给子类对象进行初始化

  2. 抽象类是否能够定义非抽象方法?

    能够,其实,抽象类和通常类没有太大区别,都是在描述事物,只不过抽象类在描述事物时,有些功能不具体。因此抽象类和通常类在定义上,都是须要定义属性和行为的。只不过,比通常类多了一个抽象函数,并且比通常类少了一个建立对象的部分

  3. 抽象关键字abstract和哪些不能够共存?

    final private static

  4. 抽象类可不能够不定义抽象方法?

    能够。抽象方法目的仅仅为了避免让该类建立对象

访问控制符

Java 中提供了4种访问控制符

  • private - 仅对本类可见
  • public - 对全部类课件
  • protected - 对本包和全部子类可见
  • 默认的 - 对本包可见

Obejct - 全部类的超类

Object 类是 Java 中全部类的始祖, 在 Java 中每一个类都是由它扩展而来的。可是并不须要这样写:

public class Student extends Object

equals方法

public boolean equals(Object obj) {
    return (this == obj);
}

Object 类中的 equals 方法用于检测一个对象是否等于另一个对象。在 Object 类中,这个方法将判断两个对象是否具备相同的引用。若是两个对象具备相同的引用, 它们必定是相等的。

Java语言规范要求 equals 方法具备如下特性

  • 自反性:对于任何非空引用 xx.equals(x) 应该返回 true
  • 对称性: 对于任何引用 xy,当且仅当 y.equals(x) 返回 truex.equals(y) 也应该返回 true
  • 传递性: 对于任何引用 xyz ,若是 x.equals(y) 返回 truey.equals(z) 返回 truex.equals(z) 也应该返回 true
  • 一致性: 若是 xy 引用的对象没有发生变化,反复调用 x.equals(y) 应该返回一样的结果
  • 对于任意非空引用 xx.equals(null) 应该返回 false
public boolean equals(Object otherObject){
    if (this == otherObject) return true;//检测this与otherObject是否引用同一个对象
    if (otherObject == null) return false;//检测otherObject是否为null,若是是返回false
    if (getClass() != otherObject.getClass()) return false;//比较this与otherObject是否属于同一个类
    ClassName other = (ClassName)otherObject;//将otherObject转为相应类类型变量
    return field1 == other.field1 &&
            Object.equals(field2, other.field2) &&
            .... ; //对每项成员变量进行比较
}

hashCode方法

散列码( hash code ) 是由对象导出的一个整型值。散列码是没有规律的。

因为 hashCode 方法定义在 Object 类中, 所以每一个对象都有一个默认的散列码,其值为对象的存储地址。

  • 理论上对象相同,hashcode 必定相同
  • hashcode 相同,对象不必定相同

toString方法

用于返回表示对象值的字符串

Object中的toString()
public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}    
类名@哈希值 = getClass().getName()+'@'+Integer.toHexString(hasCode())//默认格式

在自定义类中建议重写 toString 方法,用来返回类中的各个属性值

public Student{
    private String name;
    private int age;
    
    public String toString() {
        return getClass().getName() + "[name=" + name + ",age=" + age + "]";
    }
}

Class getClass() : 获取任意对象运行时所属字节码文件对象

String getName() : 返回这个类的名字

继承的设计技巧

  • 将公共操做和变量放在超类
  • 不要使用受保护的变量
  • 除非全部继承的方法都有意义,不然不要使用继承

多态

函数自己就具有多态性,某一种事物有不一样的具体的体现

体现 :父类引用或者接口的引用指向了本身的子类对象。Animal a = new Cat()

多态的好处 :提升了程序的扩展性

多态的弊端 :当父类引用指向子类对象时,虽然提升了扩展性,可是只能访问父类中具有的方法,不能够访问子类中特有的方法。

多态的前提

  1. 必需要有关系,好比继承或者实现
  2. 一般会有覆盖操做

若是想用子类特有的方法,如何判断对象是哪一个具体的子类类型呢?

能够经过一个关键字 instanceof 判断对象是否实现了指定的接口或继承了指定的类

格式: <对象 instanceof 类型> 判断一个对象是否所属于指定类型

Student instanceof Person == true; //Student继承了Person

多态在子父类中的成员上的体现特色

  1. 成员变量:在多态中,子父类成员变量同名

    在编译期:参考引用型变量所属的类中是否有调用的成员(编译时不产生对象只检查语法错误)

    在运行期:参考引用型变量所属的类中是否有调用的成员

    成员变量 - 编译运行都看 - 左边

  2. 成员函数

    在编译期:参考引用型变量所属的类中是否有调用方法

    在运行期:参考的是对象所属的类中是否有调用方法

    成员函数 - 编译看左边 - 运行看右边

  3. 静态函数

    在编译期:参考引用型变量所属的类中是否有调用的成员

    在运行期:参考引用型变量所属的类中是否有调用的成员

    静态函数 - 编译运行都看 - 左边

包装类与自动装箱和拆箱

包装类

有时, 须要将 int 这样的基本类型转换为对象。 全部的基本类型都冇一个与之对应的类。

Integer 类对应基本类型 int。一般, 这些类称为包装器。

这些对象包装器类拥有很明显的名字:IntegerLongFloatDoubleShortByteCharacterBoolean (前 6 个类派生于公共的超类 Number)。对象包装器类是不可变的,即一旦构造了包装器,就不容许更改包装在其中的值。同时, 对象包装器类仍是 final , 所以不能定义它们的子类。

关于包装类的具体使用,后续在经常使用类的文字中详细介绍

自动装箱和拆箱

当int值赋给Integer对象时,将会自动装箱
Integer i = 3;
Integer i = Integer.valueOf(3);
这种变换称之为自动装箱
当将一个Integer对象赋给一个int值时,将会自动地拆箱
Integer i = new Integer(3);
int n = i;
int n = i.intValue();
这种变化称之为自动拆箱

五大基本原则

  • 开闭原则

    让你的设计应当对扩展开放 ,对修改关闭抽象化 是开闭原则的关键。

    用抽象构建框架,用实现扩展细节。

  • 里氏替换原则

    全部引用基类(父类)的地方必须能透明地使用其子类的对象

    通俗的说:软件中若是可以使用基类对象,那么必定可以使用其子类对象。

    在程序中尽可能使用基类类型来对对象进行定义,在运行过程当中使用子类对象。

    子类能够扩展父类的功能,但不能改变父类原有的功能。

  • 依赖倒置原则

    要针对接口编程,不用针对实现编程

    层模块不该该依赖底层模块,他们都应该依赖抽象。抽象不该该依赖细节,细节应该依赖于抽象。
    依赖三种写法:
    1.构造函数注入
    2.Setter依赖注入
    3.接口注入
    依赖原则本质:经过抽象(接口或抽象类)使各个类或模块的实现彼此独立,不互相影响,实现模块间的松耦合。
    原则使用:
    每一个类尽可能有接口或抽象类,或者抽象类和接口二者都具有
    变量的类型尽可能使接口或者抽象类
    任何类都不该该从具体类派生
    尽可能不要覆写基类的方法
    结合里氏替换原则
  • 单一职责

    在软件系统中,一个类只负责一个功能领域中的相应职责

    应该仅有一个引发它变化的缘由。

    该原则的核心就是解耦和加强内聚性

  • 接口隔离职责

    将一个接口拆分多个接口,知足不一样的实现类。

总结

面向对象的思想博大精深,所以咱们不只要学会编写代码, 更更更 重要的是学会面向对象的思想。

相关代码记录于GitHub中,欢迎各位伙伴 Star

有任何疑问 微信搜一搜 [程序猿大博] 与我联系~

若是以为对您有所帮助,请 点赞收藏 ,若有不足,请评论或私信指正,谢谢~

相关文章
相关标签/搜索