培训系列 Java面向对象初步

在编程学习的早期阶段,许多人首先接触的是C语言。作为一种经典的面向过程语言,C语言为理解编程基础概念提供了坚实的基础。然而,在处理大型或复杂的项目时,面向过程的语言可能会导致诸多挑战,尤其是在代码管理和维护方面。

随着项目规模的扩大,面向对象的编程语言成为了更合适的选择。这类语言通过封装、继承和多态等概念,提供了更高的代码可重用性和更强的模块化能力。Java语言就是其中的典型代表。它不仅支持面向对象的核心原则,而且广泛应用于各种规模的软件开发项目中。因此,在本节课中,我们将重点讲解Java语言中的面向对象编程。这将帮助大家深入理解并熟练运用面向对象的概念和技巧。

在开始本课程之前,请确保你已经熟悉Java的基本语法,这将为理解更高级的概念奠定基础。通过这种方式,我们可以更有效地掌握面向对象编程,进而提高软件开发的质量和效率。

面向对象引入

想要学习面向对象,首先要清楚地认识到面向对象与面向过程有什么区别,弄明白了这一点,才能彻底理解面向对象到底好在哪

面向对象与面向过程

此处将通过一个例子带大家了解何为面向过程,何为面向对象。

大家想想这样一个场景,早上起床从宿舍骑自行车前往教学楼,这个场景如何通过编程语言进行描述呢?

面向过程:

  • 首先实现人从床上起来的逻辑 – 需要很多行代码
  • 实现下楼的逻辑 – 需要很多行代码
  • 接下来实现上车逻辑 – 需要很多行代码
  • 实现骑自行车逻辑 – 需要很多行代码
  • 然后实现下车逻辑 – 需要很多行代码
  • 实现上楼逻辑 – 需要很多行代码
  • 最后实现进入教室逻辑 – 需要很多行代码

面向对象:

  • 分别制造一个人对象,床对象,楼梯对象,自行车对象,教室对象 – 需要很多行代码
  • 床对象.下床(人对象) – 仅需一行代码
  • 楼梯对象.下楼(人对象) – 仅需一行代码
  • 自行车对象.上车(人对象) – 仅需一行代码
  • 自行车对象.骑行(人对象) – 仅需一行代码
  • 自行车对象.下车(人对象) – 仅需一行代码
  • 楼梯对象.上楼(人对象) – 仅需一行代码
  • 教室对象.进入(人对象) – 仅需一行代码

上面的例子很直观的描述了什么是面向过程,什么又是面向对象,同时我们可以明显的感受到面向对象的优势,实际上面向过程就类似于我们啥都不干,直接拿砖块盖房子,而面向对象就类似于先画好图纸,以后盖房子的时候都直接根据图纸来盖房子,不需要再思考怎么盖了。

从更加本质的角度上来说,面向过程是将一件事情分成了很多步骤,把这些步骤都完成,整件事情也就完成了,而面向对象是把整件事情中包含的所有物体都抽象为一个个的对象,每个对象都拥有自己的属性和能做的事情,通过这些对象,对于整件事情进行模拟,整件事情也就完成了。由此可见,面向过程更加繁琐,而面向对象更加简单易懂。

面向对象基础概念

类和对象

何为类

盖房子使用的图纸

何为对象

根据图纸盖出来的房子

类与对象的关系

类和对象

类中都有什么

  • 属性:比如学生的姓名,年龄,学号等该类型的固有信息
  • 方法:比如吃饭,睡觉,学习等该类型的动态行为

类的特性

封装性

封装是面向对象的核心思想。它有两层含义;一层含义是把对象的属性和行为看成一个密不可分的整体,将这两者“组合”在一起(即封装在对象中);另一层含义指信息隐藏,将不想让外界知道的信息隐藏起来,例如,学开车时只需要知道如何操作汽车,不必知道汽车内部是如何工作的。

继承性

绘图3

我们把任何动物都拥有的属性和行为放到动物类中(比如【属性:年龄、体重】【行为:吃饭、睡觉】),然后我们让猫猫类、狗狗类和兔兔类都继承动物类,此时猫猫类、狗狗类和兔兔类都拥有了任何动物共有的属性和行为,因此在猫猫类、狗狗类和兔兔类中,我们不需要把动物共有的属性和行为再写一遍了,我们只需要在其中定义他们所特有的属性和行为。而金毛、二哈和柯基都继承了狗狗类,因此他们都具有狗狗所共有的属性和行为,我们只需要定义他们所特有的属性和行为即可。

多态性

同样是吃饭,猫猫一般吃鱼,狗狗一般吃骨头,而兔兔一般吃胡萝卜,如果一个数组中存放了几种不同的动物,我们让这些动物一起去食堂吃饭,我们让不同种类的对象执行相同的行为(吃饭),但是最终他们表现出来的结果却不一样(猫吃鱼、狗吃骨头、兔兔吃胡萝卜),这种现象就叫多态,就是同种行为表现出不同状态。

面向对象基础

类的定义

class 类名 {
    成员变量;
    成员方法;
}

示例

/**
 * 学生类
 */
public class Student {
    /**
     * 姓名
     */
    String name;
    /**
     * 年龄
     */
    int age;
    /**
     * 性别
     */
    String sex;

    /**
     * 读书方法
     */
    void read(){
        System.out.println("大家好,我是"+name+",我在看书!");
    }
}

对象的创建与使用

创建对象

类名 对象名 = new 类名();
Student stu = new Student();

使用对象

对象名.属性名
对象名.方法名
stu.name = "张三";
System.out.println("姓名:" + stu.name);
stu.read();

完整示例

public class Main {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.name = "张三";
        System.out.println("姓名:" + stu.name);
        stu.read();
    }
}

对象的内存结构

对象内存图

思考一下下面这段代码的输出

public class Main {
    public static void main(String[] args) {
        // 值类型
        int a=10;
        int b=a;
        System.out.println("a="+a);
        System.out.println("b="+b);
        System.out.println("修改b的值后:");
        b=20;
        System.out.println("a="+a);
        System.out.println("b="+b);

        // 引用类型(对象)
        Student stu1=new Student();
        Student stu2=null;
        stu2=stu1;
        stu1.name="张三";
        stu1.age=10;
        stu2.age=20;
        System.out.println("修改stu2的值后:");
        System.out.println("stu1.name="+stu1.name);
        System.out.println("stu1.age="+stu1.age);
        System.out.println("stu2.name="+stu2.name);
        System.out.println("stu2.age="+stu2.age);
    }
}

搞懂了上面这段代码输出结果的原理,对象的内存结构自然也就明白啦。

访问控制权限

private < default < protected < public

权限修饰符

/**
 * 测试权限修饰符
 */
public class Test {
    /**
     * 私有变量
     */
    private int privateInt;
    /**
     * 默认变量
     */
    int defaultInt;
    /**
     * 受保护变量
     */
    protected int protectedInt;
    /**
     * 公共变量
     */
    public int publicInt;
}

权限修饰符使用规范

封装

为什么要封装

我们现在可以随意的访问对象中的属性和方法,但是很多时候我们并不希望类的外部能够访问某些属性和方法,就比如人类拥有心跳方法,但心跳方法我们不希望外界能够使用,外界随意使用会很危险,因此我们就需要使用权限修饰符private,使心跳方法变成一个私有的方法,只能在类的内部使用。

this关键字的使用

调用本类中的属性、方法、构造方法

如何实现封装

使用权限修饰符private

/**
 * 学生类
 */
public class Student {
    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age < 0) {
            System.out.println("年龄不能为负数!");
            return;
        }
        this.age = age;
    }

    /**
     * 读书方法
     */
    void read() {
        System.out.println("大家好,我是" + name + ",刚满" + age + "岁~~~" + ",我在看书!");
    }
}

public class Main {
    public static void main(String[] args) {
        Student stu = new Student();
        stu.setName("张三");
        stu.setAge(-20);
        stu.setAge(18);
        stu.read();
    }
}

思考一下,如何让某个属性只读或只写

构造方法

在此之前,我们初始化对象中的属性,都是先创建一个对象,然后逐个为其中的属性赋值,这种方法并不是很方便,那我们有没有办法在创建对象的时候就给他赋上初值呢?

/**
 * 学生类
 */
public class Student {

    /**
     * 无参构造方法
     */
    public Student() {
    }

    /**
     * 有参构造方法
     * @param name 姓名
     * @param age 年龄
     */
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age < 0) {
            System.out.println("年龄不能为负数!");
            return;
        }
        this.age = age;
    }

    /**
     * 读书方法
     */
    void read() {
        System.out.println("大家好,我是" + name + ",刚满" + age + "岁~~~" + ",我在看书!");
    }
}

public class Main {
    public static void main(String[] args) {
        Student stu = new Student("张三", 18);
        stu.read();
    }
}

注意事项

  • 构造方法的名称必须与类名一致
  • 构造方法名称前不能有任何返回值类型的声明
  • 不能在构造方法中使用return返回一个值,但可以单独写return语句作为方法的结束

构造块

用{}包裹,与构造方法同级,会在构造方法执行前(创建对象之前)执行

static关键字

前面讲到的类中的属性,都是需要创建对象后,通过 对象.属性 的方式使用,也就是这些属性都是属于对象的,而如果某个属性通过static关键字修饰,那么它将会属于类(通过 类.属性 使用),不仅是属性,方法也可以通过static修饰,效果一样。

示例

// 静态属性
类名.属性名
// 静态方法
类名.方法名
/**
 * 学生类
 */
public class Student {

    /**
     * 无参构造方法
     */
    public Student() {
    }

    /**
     * 有参构造方法
     *
     * @param name 姓名
     * @param age  年龄
     */
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;
    /**
     * 学校
     */
    String school = "A大学";

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age < 0) {
            System.out.println("年龄不能为负数!");
            return;
        }
        this.age = age;
    }

    /**
     * 读书方法
     */
    void info() {
        System.out.println("大家好,我是" + name + ",刚满" + age + "岁~~~,在" + school + "上大学");
    }
}
public class Main {
    public static void main(String[] args) {
        Student stu1 = new Student("张三", 18);
        Student stu2 = new Student("李四", 20);
        Student stu3 = new Student("王五", 22);
        stu1.info();
        stu2.info();
        stu3.info();

        stu1.school= "B大学";
        stu1.info();
        stu2.info();
        stu3.info();
    }
}

上述示例中,张三、李四和王五是最开始是同一所大学 A大学 的学生,现在他们的大学改名叫 B大学 ,此时这里只改动了stu1的大学名称,而stu2和stu3的大学名称依然是 A大学 ,由此可见,我们想要让大学改名,就要把所有学生对象的大学名称全部更改成 B大学 ,假设有一千亿个学生,我们就要修改一千亿次。

上述这种情况肯定是不合理的,究其原因,其实是因为每个对象的大学属性是独立的,但如果大学属性 属于类 ,那么所有的学生对象都是共享大学属性的,只要使用 类名.属性名 修改了大学属性,那么属于该类型的所有对象的大学属性也就同步更新了。

使用静态属性后的代码

/**
 * 学生类
 */
public class Student {

    /**
     * 无参构造方法
     */
    public Student() {
    }

    /**
     * 有参构造方法
     *
     * @param name 姓名
     * @param age  年龄
     */
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    /**
     * 姓名
     */
    private String name;
    /**
     * 年龄
     */
    private int age;
    /**
     * 学校
     */
    static String school = "A大学";

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age < 0) {
            System.out.println("年龄不能为负数!");
            return;
        }
        this.age = age;
    }

    /**
     * 读书方法
     */
    void info() {
        System.out.println("大家好,我是" + name + ",刚满" + age + "岁~~~" + ",在" + school + "上学。");
    }
}

静态属性和方法的内存原理

静态成员变量内存机制

静态方法内存机制

静态代码块

在类第一次被使用的时候执行

静态代码块 -> 构造代码块 -> 构造方法

继承

绘图3

基础语法

class 父类{
    ...
}
class 子类 extends 父类{
    ...
}

示例

/**
 * 动物类
 */
public class Animal {
    private String name;
    private int age;
    public final String COLOR = "白色";

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}
/**
 * 狗狗类
 */
public class Dog extends Animal{
}
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("旺财");
        dog.setAge(3);
        System.out.println(dog.getName() + "的年龄是" + dog.getAge() + "岁,颜色是" + dog.COLOR);
    }
}

注意事项

  • Java仅支持单继承,不支持多继承
  • 多个类可以继承同一个父类,比如狗狗、猫猫和兔兔都继承动物
  • 允许多层继承,比如狗狗继承了动物,二哈继承了狗狗

方法重写

/**
 * 动物类
 */
public class Animal {
    void shout() {
        System.out.println("动物叫!");
    }
}

/**
 * 狗狗类
 */
public class Dog extends Animal{
    @Override
    void shout() {
        System.out.println("汪汪汪!");
    }
}

super关键字

super.属性
super.方法(参数1, 参数2, ...)
/**
 * 狗狗类
 */
public class Dog extends Animal{
    @Override
    void shout() {
        super.shout();
        System.out.println("汪汪汪!");
    }
}

final关键字

  • 修饰类,不可被继承
  • 修饰方法,不可被重写
  • 修饰变量,变为常量,只能赋初值

抽象类

有些时候,父类中某些方法的逻辑可以确定,有些方法的逻辑只有到了子类中才能确定,比如说叫声方法,动物类没法确定具体的叫声,而子类中可以确定,比如狗狗汪汪汪,猫猫喵喵喵,此时我们就需要用到抽象类,抽象类不能直接创建对象。

abstract class 抽象类名称 {
    属性;

    // 普通方法
    访问权限 返回值类型 方法名称(参数) {
        return 返回值;
    }

    // 抽象方法
    访问权限 abstract 返回值类型 抽象方法名称(参数);
}

示例

/**
 * 动物类
 */
public abstract class Animal {

    void eat() {
        System.out.println("吃东西!");
    }

    abstract void shout();
}

/**
 * 狗狗类
 */
public class Dog extends Animal{
    @Override
    void shout() {
        System.out.println("汪汪汪!");
    }
}
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.eat();
        dog.shout();
    }
}

接口

我们在打游戏的时候,很多东西都有加血和扣血方法,怪物可以加血扣血,角色可以加血扣血,建筑也可以加血扣血,他们是截然不同的东西,父类是不一样的,很难找到上述几种类型的公共父类,但他们却拥有相同的几种行为,接口就是为了这种情况而存在的,任何具备加血和扣血操作的类型,都可以实现血量控制接口,以实现所有拥有加血扣血操作的类型的规范统一。

我们在生活中其实也常常用到接口,比如电脑的USB接口,鼠标,键盘,手机,U盘,等等各种毫不相关的东西都可以使用同样的USB接口,这是因为这些设备都遵循了USB的协议,就像是上面那个例子中截然不同的东西都具备加血和扣血操作一样。

定义接口

[public] interface 接口名 [implements 接口1, 接口2, ...] {
    [public] [static] [final] 数据类型 常量名 = 常量;
    [public] [abstract] 返回值的数据类型 方法名(参数列表);
    [public] static 返回值的数据类型 方法名(参数列表){}
    [public] default 返回值的数据类型 方法名(参数列表){}
}

// 其实接口中最常用的一般是之定义抽象方法,也就是第二条 [public] [abstract] 返回值的数据类型 方法名(参数列表); 其他几条了解一下即可

实现接口

修饰符 class 类名 implements 接口1, 接口2,...{
    ...
}

接口支持多实现,比如有一个血量控制接口,一个飞行控制接口,对于只有加血扣血操作的类型,只需要实现血量控制接口,对于没有生命值但是能飞的小精灵,只需要实现飞行控制接口,而对于即拥有生命值,又能飞的主角或者载具来说,就既要实现血量控制接口,也要实现飞行控制接口。

示例

/**
 * 血量控制接口
 */
public interface IBloodControl {
    void addBlood(int blood);
    void reduceBlood(int blood);
}

/**
 * 飞行控制接口
 */
public interface IFlyControl {
    void fly();
}
public class Bird implements IFlyControl, IBloodControl{

    private int blood;

    @Override
    public void addBlood(int blood) {
        this.blood += blood;
    }

    @Override
    public void reduceBlood(int blood) {
        this.blood -= blood;
    }

    @Override
    public void fly() {
        System.out.println("鸟儿飞行!");
    }
}

抽象类和接口

从本质上来讲,接口就像是文章的大纲,等着我们去填充(实现接口),而抽象类更像是写了一半的文章,但是还没完全写完,没写完的部分先列好大纲,等之后在子类中填充。

多态

对象类型转换

向上转型

子类对象 -> 父类对象

父类类型 父类对象 = 子类实例;

因为子类对象一定属于其父类,就比如二哈一定属于狗狗,狗狗一定属于动物一样,所以能够直接隐式转换

向下转型

父类对象 -> 子类对象

子类类型 子类对象 = (子类)父类对象;

父类对象不一定属于子类,就比如动物对象不一定是狗狗对象,所以想要进行类型转换需要强制类型转换

多态的使用

/**
 * 动物类
 */
public abstract class Animal {

    abstract void shout();
}

/**
 * 狗狗类
 */
public class Dog extends Animal{
    @Override
    void shout() {
        System.out.println("汪汪汪!");
    }
}
/**
 * 猫猫类
 */
public class Cat extends Animal{
    @Override
    void shout() {
        System.out.println("喵喵喵!");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal dog = new Dog();
        dog.shout();
        Animal cat = new Cat();
        cat.shout();
    }
}

多态的好处

小动物们开了一家KTV,猫猫狗狗和兔兔一起去唱歌,通过代码实现上述场景。

其实上述场景中,我们可以实例化一个猫猫对象,一个狗狗对象,一个兔兔对象,然后依次调用他们的叫声方法,但是这样很麻烦,如果有一千个动物,我们就要调用不同对象的叫声方法一千次,有没有更好的办法呢?

猫猫狗狗和兔兔其实都属于动物,我们可以让他们都转型成动物类型,然后放到一个动物类型的列表(List)里面,然后遍历这个列表,在循环体中调用每一个元素的叫声方法。

/**
 * 动物类
 */
public abstract class Animal {
    abstract void shout();
}
/**
 * 猫猫类
 */
public class Cat extends Animal{
    @Override
    void shout() {
        System.out.println("喵喵喵!");
    }
}
/**
 * 狗狗类
 */
public class Dog extends Animal{
    @Override
    void shout() {
        System.out.println("汪汪汪!");
    }
}
/**
 * 兔兔类
 */
public class Rabbit extends Animal{
    @Override
    void shout() {
        System.out.println("哞哞哞!");
    }
}
public class Main {
    public static void main(String[] args) {
        List<Animal> animals = new ArrayList<>();
        animals.add(new Dog());
        animals.add(new Cat());
        animals.add(new Rabbit());
        animals.forEach(Animal::shout);
    }
}

instanceof关键字

instanceof关键字用于判断一个对象是否属于某个类(或接口)的实例,语法格式如下:

对象 instanceof 类(或接口);

Object类(万类之祖)

Object是任何类的父类,所有的类都会直接或间接的继承Object类,其中有一些通用的方法我们可以重写,例如输出形式,比较,哈希值等。

除特殊说明,博客文章均为东篱原创,依据 CC BY-SA 4.0 许可证进行授权,转载请附上出处链接及本声明。
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇