文章目录
前言
面向对象的三大特征:封装、继承、多态
一、类和对象
1、软件开发方式
面向过程
一种较早的编程思想,强调的是该怎么去做–即功能的
执行过程,先干什么,后干什么。
面向过程思想中的函数(类似于方法)是一等公民,每个函数负责完成某一个功能,用以接受输入数据,函数对输入数据进行处理,然后输出结果数据。
而每一个功能我们都使用函数(类似于方法)把这些步骤一步一步实现,使用的时候依次调用函数就可以了。
因而,面向过程的设计思想,系统软件适应性差,可拓展性差,维护性低。
面向对象
一种基于面向过程的新的编程思想,顾名思义该思想是站在对象的角度思考问题,把多个功能合理的放到不同对象里,强调的是该让谁来做。
面向对象最小的程序单元是类,必须先存在类的定义,再有对象,而具备某种功能的实体,称为对象。
举个简单的例子
面向过程和面向对象各有千秋,面向对象更符合我们常规的思维方式,稳定性好,可重用性强,易于开发大型软件产品,有良好的可维护性,它拥有三大特征:
封装(Encapsulation)
继承(Inheritance)
多态(Polymorphism)
2、成员变量和局部变量
成员变量:又称之为字段(Field)。直接定义在类中,方法外面。
局部变量:除了成员变量,其他都是局部变量,具体存在于三个地方
· 方法内
· 方法的形参
· 代码块中(一对花括号)
变量根据定义的位置不同,也决定了各自的作用域是不同的,关键看变量所在的那对花括号{ }
局部变量:从开始定义的位置开始,只能在自己所在的花括号内有效
成员变量:在所定义的类中都有效
{ } 也常常被称为作用域,作用域可以嵌套,内层作用域可以访问外层作用域的变量,反之不成立。
变量的初始化表示在内存中开辟存储空间,只有初始化之后,才能使用。
局部变量:没有初始值,所以必须先声明后赋值再使用
成员变量:默认是有初始值的,不同类型的初始值,如下图:
变量的生命周期,表示变量在内存在能存活多久。
成员变量:存储在堆内存中,随着对象的销毁而销毁
局部变量:存储在栈内存中,随着所定义方法的调用结束而销毁
局部变量存储在方法中,每次调用方法都会在栈空间开辟一块内存空间——栈帧,方法调用结
束,栈帧就被销毁了,内存中的存储的变量数据也销毁了。
##3. 类(重点)
类是拥有相同特征(状态)和相同行为(功能)的多个事物的抽象(抽出像的部分)描述。
在程序中,类就可以作为一种新型的数据类型。可以使用类来声明变量。
描述类或者说定义类,就从状态和行为上分析,那么怎么来表示状态和行为呢?
· 使用成员变量来表示特征(状态)
· 使用成员方法来表示行为(功能)
语法格式
public class 类名 {
//可编写0到N个成员变量
[修饰符] 数据类型 变量名1;
[修饰符] 数据类型 变量名2;
//可编写0到N个成员方法
[修饰符] 返回值类型 方法名称(参数){
//方法体
}
}
注意
· 成员变量和方法都 不 能使用static修饰,修饰符是可选用的,都先不使用任何修饰符
· 在面向对象设计中,描述对象的类和测试类分开来编写
· 在描述对象的类中,不需要定义main方法,专门在测试类中提供main方法。
对象操作(重点掌握)
对象 : 对象是类的一个具体实例,它用于描述一个具体的个体。
对象是独立的,唯一的个体。
对象一定属于某一类(型),具备该类的特性和行为。
创建对象
类名 对象变量名 = new 类名();
- 直接打印对象的时候,打印的是类似于数组一样的hashCode值(先不管,后面讲)
System.out.println(对象变量名);//格式如:类名@3294e4f4
- 匿名对象:创建对象之后没有赋给某一个变量,只能使用一次(先知道)
new 类名();
对象操作字段(成员变量)
- 给字段设置数据
对象变量名.字段名 = 值;
- 获取字段数据
数据类型 变量 = 对象变量名.字段名;
对象调用方法
对象变量名.方法(参数);
4.类和对象的关系
面向对象思想中有两个非常重要的概念,类和对象,其中:类(class),是对某一类事物的抽象描述(状态和行为)
对象(object),表示现实生活中该类事物的个体,也称之为实例。
类可以看作是对象的数据类型,就好比无论你是谁,只要是人,那么类型就是人。
5.构造器/构造方法(重点掌握)
构造器,也称之为构造方法(Constructor),作用是用来创建对象和给对象做初始化操作,专门用于构
造一个对象。
Student student = new Student();
这段代码,是在通过调用Student类的构造器,来创建对象。
// 普通方法
[修饰符] 返回值 方法名(参数列表){
}
// 构造方法语法
[修饰符] 类名(参数列表){ }
1、构造器名称必须和类名相同
2、不能定义返回类型
3、构造器中不能使用return语句
默认的构造器
如果源文件中没有定义构造器,编译器在编译源文件的时候,会创建一个缺省的构造器。
默认构造器的特点:无参数、无方法体。
类是否使用public修饰(下面结论知道就行):
使用,则编译器创建的构造器也使用public修饰
不使用,则编译器创建的构造器也不使用public修饰
记住构造器的定义语法和功能,手动写出构造器代码,认识同一个类中的多个构造器之间是重载关系。
二、封装
1、封装思想
把对象的字段和方法存放在一个独立的模块中(类)
信息隐藏,尽可能隐藏对象的数据和功能的实现细节
封装的好处:
提高组件的重用性,把公用功能放到一个类中,谁需要该功能,直接调用即可
保证数据的安全性,防止调用者随意修改数据
2、JavaBean规范
JavaBean是一种某些符合条件的特殊类,但是必须遵循一定的规范:
1、类必须使用public修饰
2、必须保证有公共无参数构造器。即使手动提供了带参数的构造器,也得手动提供无参数构造器
3、字段使用private修饰(私有化)
4、每个字段提供一对getter和setter方法
getter方法:仅仅用于返回某一个字段的值;如果操作的字段是boolean类型的,此时是is方法,把 getName 变成 isName。
public String getName(){ return name; //返回name字段存储的值 }
setter方法:仅仅用来给某一个字段设置值,实际开发过程中,可以融入校验逻辑。
public void setName(String n){ name = n; //把传过来的参数n的值,存储到name字段中 }
注意:每一个字段都得使用private修饰,并提供一对getter/setter方法。
IDEA工具可以自动生成标准的getter/setter
IDEA快捷键:Alt+ins,选择getter/setter
3、访问权限修饰符
访问修饰符,决定了有没有权限访问某个成员(资源)。
封装其实就是要让有些类看不到另外一些类中定义的字段和方法。Java提供了不同的访问权限修饰符来限定类中的成员让谁可以访问到。
· private:表示当前类私有的,类访问权限,只能在本类中操作,离开本类之后就不能直接访问
· 不写(缺省):表示当前包私有,包访问权限,定义和调用只能在同一个包中,才能访问
· protected:表示子类访问权限,同包中的可以访问,即使不同包但是有继承关系也可以访问
· public:表示公共的,可以在当前项目中任何地方访问
4、this关键字
解决成员变量和局部变量的二义性
使用 this.变量名 的语法,此时访问的就是成员变量
5、构造器和setter方法的选用
构造器和setter方法都可以给对象设置数据:
构造器,在创建对象的时候设置初始数据,只能初始化一次。
setter方法,创建对象后再设置初始数据,可以设置多次。
三、继承
面向对象的继承思想,可以解决多个类存在共同代码的问题:
· 被继承的类,称之为父类
· 继承父类的类,称之为子类
· 父类:存放多个子类共同的字段和方法
· 子类:存放自己特有的(独有的)字段和方法
经过继承后,子类拥有了父类的特征和行为,子类拥有了父类的字段和方法
1、继承语法
语法
public class 父类名{
// 存放多个子类共同的字段和方法
}
public class 子类名 extends 父类名{
// 存放自己特有的(独有的)字段和方法
}
Java中类只支持单继承,但是支持多重继承。也就是说一个子类只能有一个直接的父类,父类也
可以再有父类
如果父类中的成员使用public和protected修饰,子类都能继承.
如果父类和子类在同一个包中,使用缺省访问修饰的成员,此时子类可以继承到
如果父类中的成员使用private修饰,子类继承不到。private只能在本类中访问
父类的构造器,子类也不能继承,因为构造器必须和当前的类名相同
一句话:子类继承父类的非私有成员(字段和方法),但构造器除外
2、方法覆盖
当子类继承父类的方法不能满足自身需要时,子类就可以根据自身需要对父类的同名方法进行重
写。子类存在一个和父类一模一样的方法时,我们就称之为子类覆盖了父类的方法,也称之为重写
(override)。
方法覆盖的细节:
- 方法签名必须相同 (方法签名= 方法名 + 方法的参数列表)
- 子类方法的返回值类型:要么和父类方法的返回类型相同,要么是父类方法返回值类型的子类
- 子类方法的访问权限 >= 父类方法访问权限 ;如果父类方法是private,子类方法不能重写。==> 重写建立在继承的基础上,没有继承,就不能重写。
- 子类方法中声明抛出的异常小于或等于父类方法声明抛出异常类型
精华:直接拷贝父类中方法的定义粘贴到子类中,再重新编写子类方法体,打完收工
覆盖中super关键字
在子类中的某一个方法中需要去调用父类中被覆盖的方法,此时得使用super关键字。
public class Ostrich extends Bird{
public void fly() {
System.out.println("拍拍翅膀,快速奔跑...");
}
public void say() {
super.fly();//调用父类被覆盖的方法
fly();//调用本类中的方法
}
}
如果调用被覆盖的方法不使用super关键字,此时调用的是本类中的方法。
1、super关键字表示父类对象的意思,更多的操作,后面再讲。
2、super.fly() 可以翻译成调用父类对象的fly方法。
3、抽象
任何事物都是具有普遍性和特殊性的,抽象类就是将生活中具体的事物进行抽象化,一般化,普遍化然后在程序中表现出来的一种形式。
用来捕捉子类的通用特性的,是被用来创建继承层级里子类的模板
作为子类的模板,要有子类,具有子类相同特性,现实方法交给子类重写
抽象类
使用abstract修饰的类
public abstract class 类名 {
}
抽象类能够作为一种条件限制子类
抽象类中无法创建对象
抽象类的特点:
抽象类不能创建对象,调用没有方法体的抽象方法没有任何意义
抽象类中可以同时拥有抽象方法和普通方法
抽象类要有子类才有意义,子类必须实现父类的所有的抽象方法,除非子类也是抽象类
抽象方法
使用abstract修饰的方法
public abstract 返回类型 方法名(参数);
抽象方法的特点:
使用abstract修饰,没有方法体,留给子类去覆盖
抽象方法必须定义在抽象类或接口中
Object类
Object本身表示对象类的意思,是Java中的根类,要么是一个类的直接父类,要么就是一个类的间接父
类。
public class A{
}
//其实等价于
public class A extends Object{
}
因为所有类都是Object类的子类, 所有类的对象都可以调用Object类中的方法,常见的方法:
boolean equals(Object obj):拿当前调用该方法的对象和参数obj做比较
在Object类中的equals方法和“ == ”符号相同,都是比较对象是否是同一个的存储地址。
==
== 符号到底比较的是什么:
比较基本数据类型:比较两个值是否相等
比较对象数据类型:比较两个对象是否是同一块内存空间
每一次使用new关键字,都表示在堆中创建一块新的内存空间。
四、接口
接口是一种体现了约定和实现相分离的思想的规范;接口也体现低耦合思想(在开发过程中,如果想要解耦,一定要想到接口)
接口定义和多继承性
接口可以认为是一种特殊的类,但是定义类的时候使用class关键字,定义接口使用interface关键字。
public interface 接口名{
//抽象方法1();
//抽象方法2();
}// 接口命名规范:大写的驼峰命名法。
在java中,接口也可以继承,一个接口可以继承多个接口,也就是说一个接口可以同时继承多个接口
接口实现类
因为接口中的方法是抽象的,没有方法体,所以接口是不能创建对象的,此时必须定义一个类去实 现接口,并覆盖接口中的方法,这个类称之为实现类,实现类和接口之间的关系称之为实现关系
public class 类名 implements 接口名{
// 覆盖接口中抽象方法
}
实现类实现接口后,必须实现接口中的所有抽象方法,完成功能代码,此时接口和实现类之间的关系:
···接口:定义多个抽象方法,仅仅定义有哪些功能,却不提供实现。
···实现类:实现接口,实现接口中抽象方法,完成功能具体的实现。
根据方法覆盖原则:子类方法的访问修饰符必须大于等于父类方法的访问修饰符,接口中的方法都是
public修饰的,所以实现类中的方法只能使用public修饰。
实现类可以继承父类,可以同时实现一个或多个接口,继承在前,实现在后。
接口是一种引用数据类型,可以用来声明变量,并接收(引用)所有该接口的实现类对象
eg : 定义一个青蛙类(Frog)继承于动物类(Animal),同时实现于会走路(IWalkable),会游泳
(ISwimable)的接口,
public class Frog extends Animal implements ISwimable,IWalkable{
public void walk() {
System.out.println("跳啊跳...");
}
public void swim() {
System.out.println("游啊游..");
}
}
接口总结
接口表示一种规约(规范、标准),它里面定义了一系列抽象方法(功能),它可以被多个类实
现。
实现类实现接口,必须实现接口中的所有抽象方法。我们经常说:接口约定了实现类应该具备的能
力。
// IWalkable接口约定了walk()的规范
public interface IWalkable {
void walk();
}
//Person必须实现接口中的所有抽象方法 => 实现类具有接口中定义的功能
public class Person implements IWalkable {
@Override
public void walk() {
System.out.println("人类走路...");
}
}
//=> 接口约定了实现类应该具备的功能
五、多态
当通过同一引用类型的变量,由于引用的实例不同,对同一行为产生的结果不同。
多态思想
在继承关系,是一种 is A 的关系,也即 什么是什么 ,也就说子类是父类的一种特殊情况,有如下代
码:
例如:如果Student 继承于 Person,我们就可以说,学生 is 人类
public class Animal{}
public class Dog extends Animal{}
// Dog is a Animal
public class Cat extends Animal{}
// Cat is a Animal
那么我们可以认为狗和猫都是一种特殊的动物,那么可以使用动物类型来表示狗或猫。
Dog d = new Dog(); //创建一只狗对象,赋给子类类型变量
Animal a = new Cat(); //创建一只猫对象,赋给父类类型变量
此时对象(a)具有两种类型:
编译时类型:声明对象变量的类型——>Animal
运行时类型:对象的真实类型 ——>new Dog
当编译类型和运行类型不一致的时候,此时多态就产生了:
注意:编译类型必须是运行类型的父类或接口。
所谓多态,简单地理解 , 表示一个对象具有多种形态。
操作继承关系
父类 变量名 = new 子类();
变量名.方法();
Animal 类
public class Animal {
public void shout() {
System.out.println("Animal...shout...");
}
}
Cat 类·
public class Cat extends Animal{
public void shout() {
System.out.println("妙妙妙...");
}
}
Dog类
public class Dog extends Animal{
public void shout() {
System.out.println("旺旺旺...");
}
}
测试类
public class AnimalDemo {
public static void main(String[] args) {
// 声明一个动物
Animal animal = null;
// animal引用了Cat对象,程序运行过程中,animai的真实类型是一只猫
animal = new Cat();
animal.shout();
// animal引用了Dog对象,程序运行过程中,animai的真实类型是一只狗
animal = new Dog();
animal.shout();
}
}
result:
妙妙妙...
旺旺旺...
父类引用变量指向于子类对象,调用方法时实际调用的是子类的方法。
操作实现关系
接口 变量名 = new 实现类();
变量名.方法();
ISwimable 接口:
public interface ISwimable {
void swim();
}
frish实现类
public class Fish implements ISwimable{
public void swim() {
System.out.println("游啊游...");
}
}
Person类
public class Person implements ISwimable {
@Override
public void swim() {
System.out.println("喝了很多水,才学会在水里游泳");
}
}
测试类:
public class FishDemo {
public static void main(String[] args) {
// 声明一个ISwimable接口类型的变量
ISwimable swimable = null;
swimable = new Person();
swimable.swim();
swimable = new Frog();
swimable.swim();
}
}
运行结果:游啊游...
接口引用变量指向实现类对象,调用方法时实际调用的是实现类实现接口的方法。
多态时方法调用问题
把子类对象赋给父类变量,此时调用方法:
Animal animal = new Cat();
animal.shout();
那么 animal 对象调用的shout方法,是来自于Animal中还是Cat中?
如图:先判断shout方法是否在父类Animal类中:
找不到:编译报错
找 到:再看shut方法是否在子类Cat类中:
找不到:运行父类方法
找 到:运行子类方法(这个才是真正的多态方法调用)
多态中的类型转换
类型转换
自动类型转换:把子类对象赋给父类变量(多态)
Animal a = new Dog();
Object obj = new Dog(); //Object是所有类的根类
强制类型转换:把父类类型对象赋给子类类型变量。
子类类型 变量 = (子类类型)父类对象;
前提:该对象的真实类型应该是子类类型,在开发过程中,如果需要调用子类特有的方法时,一定要进行强制类型转换
instanceOf
instanceof 运算符:判断该对象是否是某一个类/父类/接口的实例,在开发中运用不是很多
语法格式: boolean b = 对象A instanceof 类B; //判断 A对象是否是 B类的实例?如果是,返回true
总结
提示:这里对文章进行总结:
设计一个对成员变量的访问做出一些限定,不允许外界随意访问类时,这就需要实现类的封装。
类的封装,就是将类中的属性私有化,即用private关键字来修饰,使得私有属性只能在它所在的类中被访问。
实际开发中一些类存在很多相识的变量和方法,创建公共父类有利于代码的重复利用
面向接口编程,体现的就是多态,其好处:把实现类对象赋给接口类型变量,屏蔽了不同实现类之
间的实现差异,从而可以做到通用编程。