Java 学习笔记·七 —— Java 基础语法·面向对象之封装、多态、继承

面向对象

面向过程思想,把关注点放在一件事或一个活动中涉及到的步骤(也就是过程)上的思想

面向对象,把关注点放在一件事或一个活动中涉及到的人或者事物(也就是对象)上的思想

面向对象的三大特征:

  • 封装(encapsulation)
  • 继承(inheritance)
  • 多态(polymorphism)

类与对象

Java 中通过 “类” 来描述事物,类主要由属性和行为构成

对象,某一类事物的某个具体的存在

类是属性和行为的集合,是一个抽象的概念

对象是该类事物的具体体现,是一种具体的存在

类的生命周期

  1. 加载(Loading),java 源文件编译成 class 文件,jvm 将 class 文件读入内存,放入方法区
  2. 连接(Linking):验证(Verification) → 准备(Preparation) → 解析(Resolution)
  3. 初始化(Initialization)
  4. 使用(Using)
  5. 卸载(Unloading)

加载、链接、初始化,三个阶段就是类加载阶段 Class Loading

类的定义与使用

定义类的过程,就是把一系列相关事物共同的属性和行为抽取出来的过程;

事物的属性,在类中叫做 成员变量
事物的行为,在类中叫做 成员方法

创建与使用对象:

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

对象名.变量名
对象名.方法名(...)

注意:
成员变量 : 定义在类中、方法外
成员方法 :去掉 static 修饰符的方法

自定义对象作为方法的参数时,其和数组作为方法的参数的道理是一样的,传递的是参数的内存地址

成员变量与局部变量的区别

  • 成员变量: 有默认初始值

    定义位置: 类中、方法外

    作用范围: 类中

    内存中位置: 堆内存

    生命周期:随对象的创建而存在,随对象的消失而消失

  • 局部变量: 无默认初始值,必须先赋值再使用

    定义位置: 方法中,或者形式参数

    作用范围: 方法中

    内存中位置: 栈内存

    生命周期:随方法的调用而存在,随方法调用的结束而消失

  • Java 中使用变量的规则:

    使用变量(变量重名)遵循 “就近原则”,如果局部位置有,就使用;没有就去本类的成员位置寻找,有就使用,没有就去父类中寻找,找到就使用,否则报错

不同数据类型的默认值:

boolean:falsebyte:0short:0char:int:0long:0float:0.0double:0.0String:nullString[]:null

char 类型变量后面什么也没有输出。不过,这并不是 char 类型变量没有默认值,而是 默认值为 “空字符”,也就是 ‘\u0000’,数值为 0
证明见下面代码

public class CharDefaultValue {
    static char c;
    public static void main(String[] args) {
        System.out.println((int) c);    // 0
        System.out.println(c == '\u0000');  // true
    }
}

构造函数

java 构造函数,也叫构造方法,是 java 中的一种特殊函数,用来初始化对象

构造函数没有返回类型,函数名和类名保持一致

new 对象产生后,在内存中开辟空间,然后使用构造方法(构造器)完成对象的初始化工作

格式:

// 1
修饰符 类名(参数列表) {
}
// 2
直接类名 (参数列表) {
}
  • 构造函数可以调用构造函数
  • 方法名必须与类名相同
  • 没有返回值
  • 构造函数中可以有 return 关键字,但是不能有具体的返回值类型

注意:

  1. 如果没有提供任何构造方法,系统会给出默认无参构造
  2. 如果已经提供任何构造方法,系统不再提供无参构造
  3. 构造方法可以重载
  4. 构造函数不是手动调用的,而是对象被创建时,jvm 调用的

符合 JavaBean 标准的类,必须是具体的、公共的,并且具有无参数的构造方法,提供用来操作成员变量的 setget 方法

匿名构造块

构造代码块的格式:

{ } 

代码块的作用:对象统一初始化

在对象创建之前,都会执行这个代码块

构造函数重载

构造函数重载是多态的一个典型特例

类中有多个构造函数,参数列表不同

重载构造函数可以实现对象的多种初始化行为

访问权限

在 Java 中,针对类、成员方法和属性提供了四种访问级别,分别是 privatedefaultprotectedpublic

  • private (当前类访问级别):对于私有成员变量和方法,只有在本类中创建该类的对象时,这个对象才能访问自己的私有成员变量和类中的私有方法

  • default (包访问级别): 类的成员变量和方法什么修饰符都没有,又叫包修饰符,只有类本身成员和当前包下类的成员可以访问

  • protected (子类访问级别): 用 protected 修饰的成员变量和方法能被该类的成员以及其子类成员访问,还可以被同一个包中的其他类的成员访问

  • public (公共访问级别): 无论访问类和被访问类是否在同一个包中,都可以访问

本类本包子类其他类
private
默认
protected
public

用法:

private 数据类型 变量名;
private 返回值类型 方法名(参数列表){}

案例:

A:给成员变量添加 private 修饰后,测试类中将不能直接访问

B:由于 private 的特性,需要在 Student 类中添加访问该属性的方法,供其它类调用

C:属性的操作一般都是取值和赋值,所以添加对应的公共方法: getXxx()setXxx(参数)

D:在测试类中通过 getXxx()setXxx(参数) 方法来实现属性的访问

    public static void main(String[] args) {
        Student2 stu = new Student2();
        stu.setAge(20);
        stu.setName("twe");
        System.out.println(stu.getAge());
        System.out.println(stu.getName());
        stu.study();
	}

封装

封装,可以提高安全性、复用性,并将复杂的事物简单化

Java 中的封装

将一系列相关事物的共同属性和行为提取出来,放到一个类中,隐藏对象的属性和实现细节,仅对外提供公共的访问方式

隐藏对象数据的实现细节,意味着一个类可以全面地改变存储数据的方式,只要使用同样的方式操作数据
其他对象就不会知道或介意所发生的变化

  • 封装原则
    1. 将不需要对外提供的内容都隐藏起来
    2. 隐藏属性,但是提供公共方法对其访问

封装的关键

绝对不能让类中的方法直接访问其他类的数据(属性),程序仅通过对象的方法与对象的数据进行交互

  • 方法的封装性:
    将繁多的代码整合在一起,以一个方法的形式呈现

  • 方法的安全性:
    调用者不需要知道方法的具体实现就可以使用

  • 方法的复用性:
    方法可以被重复使用

  • 方法的简单化:
    繁多的代码以一个方法的形式呈现,仅通过调用方法就可以实现功能,代码维护也变得简单

  • 类的封装性:
    现实事物的属性和行为都包含在类中

  • 类的安全性:
    成员变量和成员方法的私有化

  • 类的复用性:
    类的对象可以被重复使用

  • 类的简单化:
    类的对象包含了更多的功能,使用起来更方便

封装好处

提高了数据访问的安全性
隐藏了实现细节

应该禁止直接访问一个对象中数据的实际表示,而通过操作方法来访问,这称为数据的隐藏

程序设计追求: 高内聚、低耦合
高内聚: 类的内部数据操作细节自己完成,不允许外部干涉
低耦合: 仅暴露方法给外部使用

继承

Java 中的继承

Java 中的继承,指的是通过扩展一个类来建立另一个类的过程;也就是让类与类之间产生父子关系

Java 中所有的类都直接或间接地继承自:java.lang.Object
因此,每一个类都有 toString()equals()hashCode() 方法
toString() : 返回对象的字符串表示形式
equals() : 用于判断两个对象是否相同,如果没有重写,等价于 ==
hashCode() :返回对象的哈希码

被继承的类叫做父类(基类、超类);
继承的类叫做子类(派生类)

格式:

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

子类可以继承父类的 非私有 成员(成员变量、成员方法)

package cn.itcast.demo;
public class Parent {
    // 成员变量
    private String name;
    private int age;
    // 构造方法
    public Parent() {
    }
    public Parent(String name, int age) {
        this.name = name;
        this.age = age;
    }
    // setXXX()
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    // getXXX()
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
}
public class Child extends Parent {
}
public class ObjectOrientedProgramDemo3 {
    public static void main(String[] args) {
        Child c = new Child();
        c.setName("小黑"); // 调用父类方法
        // 子类继承父类的非私有成员
        // c.age();    // 报错,找不到该属性
        System.out.println(c.getName());
	}
}

继承的使用场景

子类通过继承,拥有了父类的非私有成员,是开发中的常见做法

继承的使用场景如下:

  • 向上抽取

    多个类中存在相同的属性和行为时,可以将这些内容提取出来放到一个新类中,让这些类与新类产生父子关系实现代码复用

  • 向下扩展

    当需要扩展已有的类的功能时,可以通过继承已有的类, 在子类中添加新功能或重新实现已有的功能,对父类(和已有的类)没有影响

案例分析:
分别定义 Dog类Mouse类Pig类,它们共有的属性有:nameagesex,共有的行为有:eat()Dog类Mouse类 特有的属性为coatColor(毛色),三者特有的行为分别是:watch()burrow()snore()
A:
定义 Dog类 ,属性和行为: nameagesexcoatColor ; eat() , watch()
B:
定义 Pig类,属性和行为: nameagesex; eat() , snore()
C:
定义 测试类,分别创建两种动物的对象并使用
D:
抽取 Dog类Pig类 共性内容,定义到 类Animal 中: name, age, sex, eat()
E:
Dog类Pig类 继承 Animal类,删掉重复内容
F:
定义 Mouse类,继承 Animal类,特有的属性和行为: burrow()
G:
测试类 中创建 Mouse 的对象并使用

案例代码:

package cn.itcast.demo;
public class ObjectOrientedProgramDemo3 {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.eat();
        d.watch();
        Pig p = new Pig();
        p.eat();
        p.snore();
    }
}

Animal 类:

package cn.itcast.demo;
public abstract class Animal {
    // 私有的成员变量
    private String name;
    private int age;
    private String sex;
    // 无参构造和全参构造
    public Animal() {}
    public Animal(String name, String sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
    // set
    public void setName(String name) {
        this.name = name;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public void setAge(int age) {
        this.age = age;
    }
    // get
    public String getName() {
        return this.name;
    }
    public String getSex() {
        return this.sex;
    }
    public int getAge() {
        return this.age;
    }
    // 成员方法
/*    public void eat() {
        System.out.println(this.name + " can eat. ");
    }*/
    // 抽象方法
    public abstract void eat();
}

Pig 类 :

package cn.itcast.demo;
public class Pig extends Animal {
    // snore
    public void snore() {
        System.out.println(getName() + " can snore. ");
    }
    @Override
    public void eat() {
        System.out.println(getName() + "eat cheese");
    }
}

Dog 类:

package cn.itcast.demo;
public class Dog extends Animal {
    // watch
    public void watch() {
        System.out.println(this.getName() + " watch home. ");
    }
    @Override
    public void eat() {
        System.out.println(getName() + "eat the bone");
    }
}

继承的优缺点

优点
  • 功能复用

    直接将已有的属性和行为继承过来,可实现功能的复用,可以节省大量的工作

  • 便于扩展新功能

    在已有功能的基础上,更容易建立、扩展新功能

  • 结构清晰,简化认识

    同属于一个继承体系的相关类,他们之间的结构层次清晰,简化了人们对于代码结构的认识

  • 易维护性

    不同类之间的继承关系,让这些事物之间保持了一定程度的一致性,大大降低了维护成本

缺点
  • 打破了封装性

    父类向子类暴露了实现细节,打破了父类对象的封装性

  • 高耦合性

    类与类之间紧密的结合在一起,相互依赖性高

程序设计的追求:
低耦合、高内聚

耦合:
两个或者更多模块相互依赖于对方
内聚:
模块内部结构紧密,独立性强

继承关系中类成员的使用

当子父类中定义了同名的成员变量

查找变量的原则:就近原则

查找变量的顺序:局部变量 > 成员变量 > 父类 > 更高的父类 > … > Object

访问父类变量的方式:super.父类变量名
super当前对象父类的引用(父类内存空间的标识)

对象初始化顺序:先初始化父类内容,再初始化子类内容

thissuper 的区别

this :本质是对象,从本类中开始找

super :本质是父类内存空间的标识,从父类开始找

当子父类中定义了同名的成员方法

查找方法的原则:就近原则

查找方法的顺序:本类 > 父类 > 更高的父类 > … > Object

访问父类方法的方式:super.父类方法名()

定义重名方法的 前提

  • 父类功能 不能完全满足现实需求,扩展父类功能;
  • 父类功能 已经过时,重新实现父类功能

继承关系中子父类构造方法的使用

创建子类对象时,优先调用父类构造方法

子类构造方法的第一行,隐含有语句 super() ,用于调用父类的默认无参构造

在子类创建对象时,必须先初始化该对象的父类内容,若父类中不存在默认无参构造,必须手动调用父类的其他构造

Java 中继承的特点

  • 单继承

    Java 只支持类的单继承,但是支持多层(重)继承

    Java 支持接口的多继承,语法为:接口A extends 接口B, 接口C, ...

public class Fruit {
	//水果类
}
public class Apple extends Fruit {
	//苹果类
}
public class Orange extends Fruit {
	//橘子类
}
public class Fuji extends Apple {
	//富士苹果类
}
public class GreenApple extends Apple {
	//青苹果类
}
  • 私有成员不能继承

    只能继承父类的非私有成员(成员方法、成员变量)

  • 构造方法不能继承

    构造方法用于初始化本类对象

    创建子类对象时,需要调用父类构造初始化该对象的父类内容;若父类构造可以被继承,该操作则会造成调用的混乱

  • 继承体现了 is a 的关系

    子类符合 is a 父类的情况下,才使用继承,其他情况,不建议使用

多态

多态,多种状态,指同一对象在不同情况下表现出不同的状态或行为

Java 的多态表现在:

  • 方法重写
  • 方法重载
  • 构造函数重载

Java 中实现多态的步骤

  1. 要有继承(或者实现)关系

  2. 要有方法重写

  3. 父类引用指向子类对象(is a 关系)

public class Test {
	public static void main(String [] args) {
		// 父类引用指向子类对象
		Animal a = new Dog();
	}
}

为什么父类引用可以指向子类对象
因为二者满足 子类 is a 父类 的关系,所以任何一个 Dog 都可以以 Animal 的形式使用

当父类引用指向子类对象时:

  • 加载类:

    创建子类对象时,先加载父类,再加载子类

  • 构造方法:

    先执行父类的构造方法,初始化子类对象的父类成员部分,然后再初始化子类成员部分

  • 成员方法:

    类的成员方法在方法区开辟空间,并有一个地址值,该类的每一个对象都会记录方法区中的地址

    在类的加载过程中,创建 虚拟方法表,记录了子父类方法重写的信息;通过父类引用调用方法时,会查找虚拟方法表,看该方法是否被重写,如果表中记录了重写信息,则执行子类的重写方法

当父类型变量作为参数时,可以接收任意子类对象

对于子父类中定义的同名的成员变量

成员变量不能重写

成员变量的使用,取决于调用该变量的类型

多态的好处与弊端

多态的好处
  • 可维护性
    基于继承关系,只需要维护父类代码,提高了代码的复用性,大大降低了维护程序的工作量

  • 可扩展性
    把不同的子类对象都当作父类看待,屏蔽了不同子类对象间的差异,做出了通用的代码,以适应不同的需求,实现了向后兼容

封装: 隐藏了数据的实现细节,让数据的操作模块化,提高了代码的复用性
继承: 复用方法,从对象的行为这个层面,提高了代码的复用性
多态: 复用对象,程序运行时同一个对象表现出不同的行为

多态的弊端

不能使用子类特有成员

解决办法:
向下转型(前提是,必须准确知道该父类引用指向的子类类型)

类型转换

向上转型

对象向上转型:子类转化为父类
格式:父类名称 对象名称 = new 子类名称();
把创建的子类对象当作父类看待使用,可以使用父类的属性和方法,但是不能使用子类的属性和方法

  • 向上转型(自动类型转换
//子类型转换成父类型

Animal animal = new Dog();

向下转型

对象向下转型:父类转换为子类
格式:子类 引用 = (子类)父类对象 ;
强制类型转换,但是可能会出现异常 ClassCastException

当需要使用子类特有的功能时,需要进行向下类型转换

  • 向下类型(强制类型转换
class Person {
}
class Student extends Person {
}
public class Test {
	public static void main(String[] args) {
		// 错误
		Person p1 = new Person();
		Student s1 = (Student) p;
		// 正确
		Person p2 = new Student();
		Student s2 = (Student) p2;
	}
}

注意:
只能在继承层次内进行转换,否则可能造成异常(ClassCastException
将父类对象转换成子类之前,使用 instanceof 进行检查

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值