java面向对象(封装-继承-多态)

时间:2024-03-22 19:35:38

框架图

java面向对象(封装-继承-多态)

理解面向对象

  • 面向对象是相对面向过程而言
  • 面向对象和面向过程都是一种思想
  • 面向过程强调的是功能行为
  • 面向对象将功能封装进对象,强调具备了功能的对象。
  • 面向对象是基于面向过程的。

面向对象的特点
是一种符合人们思考习惯的思想
可以将复杂的事情简单化
将程序员从执行者转换成了指挥者
完成需求时:

  • 先要去找具有所需的功能的对象来用。
  • 如果该对象不存在,那么创建一个具有所需功能的对象。
  • 这样简化开发并提高复用。

面向对象开发,设计,特征
开发的过程:其实就是不断的创建对象,使用对象,指挥对象做事情。
设计的过程:其实就是在管理和维护对象之间的关系
面向对象的特征:

  • 封装
  • 继承
  • 多态

类与对象的关系

计算机语言是虚拟的,但描述的是现实中的事物,java中描述事物通过类的形式体现,类是具体事物的抽象,概念上的定义。对象即是该类事物实实在在存在的个体。

类与对象的关系好比汽车的设计图纸,参照这个设计图制造的汽车,具有颜色、轮胎等特征。图纸就是类,汽车就是堆内存中的实体对象。我们可以对某个汽车所改观,比如换颜色换轮胎,和其他汽车没有关系。

生活中描述事物无非就是描述事物的属性和行为。Java中用类class来描述事物也是如此:
属性:对应类中的成员变量。
行为:对应类中的成员函数。
定义类其实就是在定义类中的成员(成员变量和成员函数)。

匿名对象

匿名对象是对象的简化形式

对比如下:

 Car c = new Car();
c.color="red"
c.run(); new Car().run();

匿名对象两种使用情况

  • 当对对象方法仅进行一次调用的时,如果对一个对象进行多个成员调用,必须给这个对象起名。
  • 匿名对象可以作为实际参数进行传递,函数调用完成后自动释放,没了引用指向,匿名对象在堆内存中成为垃圾被自动回收。

成员变量和局部变量

a.无论成员变量还是局部变量使用前都需要声明;

b. 局部变量使用前必须初始化;成员变量可以不用初始化,其每个类型的的成员变量都有一个初始值:byte、short、int、long类型初始值为 0;float、double类型初始值为0.0;char类型的初始'\u0000';boolean类型的初始值为false;

c.局 部变量没有访问权限修饰符,不能用public,private,protected来修饰。局部变量不能用static修饰,没有”静态局部变量“。局 部变量的作用域仅限于定义它的方法,在该方法的外部无法访问它;成员变量定义在类中,在整个类中都可以被访问。如果权限允许,还可以在类的外部使用成员变 量;

d.局部变量的生存周期与方法的执行期相同。当方法执行到定义局部变量的语句时,局部变量被创建,执行到它所在的作用域的最后一条语句时,局部变量被销毁;类的成员变量,如果是实例成员变量,它和对象的生存期相同。而静态成员变量的生存期是整个程序运行期;

e.在同一个方法中,不允许有同名的局部变量。在不同的方法中,可以有同名的局部变量,它们互不干涉。 局部变量可以和成员变量同名,且在使用时,局部变量具有更高的优先级。

局部变量代码示例

 public class localVariable
{
public void method_1()
{
int va = 0; //正确
public int pva; //错误,不能有访问权限
static int sa; //错误,不能是静态的
final int CONST = 10; //正确,可以是常量
double va =0.0; //错误,与前面的va同名
vb = 100.0; //错误,vb还未定义
double vb;
vb = 100.0; //正确,现在可以使用了
}
public void method_2()
{
va = 0; //错误,method_1()中的变量va在此不可用
int CONST = 100; //正确,它与method_1()中的CONST不同
}
}

局部变量与成员变量同名示例

 public class localVSmember
{
private int iVar = 100;
public void method_1()
{
int iVar; //正确可以与成员变量同名
iVar = 200; //这里访问的是局部变量
this.iVar = 300; //这里访问的是成员变量
}
public void method_2(){
iVar = 400; //这里访问的是成员变量
}
}
//因为局部变量优先级高于成员变量,同一函数内调用,同名的局部变量会屏蔽掉成员变量。为了访问被屏蔽的成员变量,需要使用"this"关键字,它表示的是"本类对象"(所在函数所属对象)成员变量其实还是对象在调用

构造函数

对象一建立就会调用与之对应的构造函数,用于给对象进行初始化。当一个类中没有定义构造函数时,那么系统会默认给该类加入一个空参数的构造函数。当在类中自定义了构造函数后,默认的构造函数就没有了。

构造函数和一般函数在写法上不同,在运行上也有不同:

  • 构造函数是在对象一建立就运行。给对象初始化;而一般方法是对象调用才执行,给是对象添加对象具备的功能。
  • 一个对象建立,构造函数只运行一次;而一般方法可以被该对象调用多次。

什么时候定义构造函数呢?
当分析事物时,该事物存在具备一些特性或者行为,那么将这些内容定义在构造函数中。

构造函数的特点:

  • 函数名与类名相同
  • 不用定义返回值类型(区别void)
  • 不可以写return语句
  • 多个构造函数是以重载的形式存在的

构造代码块
构造代码块和构造函数一样,也是为对象进行初始化用的。
共同点是在对象一建立的时候他们都在运行,区别在于构造代码块要优先于构造函数执行,同时,构造代码块是给所有对象进行统一的初始化,而构造函数则是给对应的对象进行初始化。

this关键字

this代本类对象的引用,也就是它所在函数所属对象的引用,简单说:哪个对象在调用this所在的函数,this就代表哪个对象。

this关键字的使用:

1.当成员变量和局部变量重名时,在方法中使用this时,表示的是该方法所在类中的成员变量。例如上面局部变量和成员变量同名的示例。
2.用于构造函数之间进行调用。注意:只能定义在构造函数的第一行,构造函数之间不允许相互调用,会形成死循环。

static关键字

static关键字用于修饰成员(成员变量和成员函数)。

被修饰后的成员具备以下特点:

  • 随着类的加载而加载,消失而消失。
  • 优先于对象存在
  • 被所有对象所共享
  • 可以直接被类名调用,格式为:类名.静态成员

使用注意
静态方法只能访问静态成员,非静态方法既可以访问静态也可以访问非静态。
因为静态优先于对象存在,所以静态方法中不可以写this,super关键字。
主函数是静态的

静态有利有弊
利处:对对象的共享数据进行单独空间的存储,节省空间。没有必要每一个对象中都存储一份。
        可以直接被类名调用。
弊端:生命周期过长。
        访问出现局限性。(静态虽好,只能访问静态)

什么使用静态?要从两方面下手:因为静态修饰的内容有成员变量和函数。
什么时候定义静态变量(类变量)呢?
当对象中出现共享数据时,该数据被静态所修饰。对象中的特有数据要定义成非静态存在于堆内存中。

什么时候定义静态函数呢?
当功能内部没有访问到非静态数据(没有操作对象的特有数据),那么该功能可以定义成静态的。

静态代码块
示例1

 class StaticCode
{
static
{
System.out.println("a");
}
}
class StaticCodeDemo
{
static
{
System.out.println("b");
}
public static void main(String[] args)
{ new StaticCode();
new StaticCode();//随类加载只执行一次且优先于主函数执行
System.out.println("over");
}
static
{
System.out.println("c");
}
}
//运行结果
//b c a over

示例2

 class StaticCode
{
int num = 9;
StaticCode()//无参数类型的构造函数
{
System.out.println("b");
}
static//静态代码块------给类初始化
{
System.out.println("a");
} {
System.out.println("c"+this.num);//构造代码块-------给对象初始化
} StaticCode(int x)//有参数类型的构造函数---------给对应对象初始化
{
System.out.println("d");
}
public static void show()
{
System.out.println("show run");
}
} class StaticCodeDemo
{
static
{
System.out.println("f");
}
public static void main(String[] args)
{
new StaticCode(4);
} } //运行结果:f a c9 d

特点:随着类的加载而执行,只执行一次,优先于主函数执行。类似构造代码块。用于给类进行初始化。表示:一个类进入内存,不需要对象的情况下,需要先做什么事情,较少使用。
注意:可能不止一个。位置不固定,可能在末尾。新建对象和调用类方法都可以加载类,但是创建一个没有实体指向的空变量不会加载类,例如,StaticCode s = null;,因为没有指向、没有意义。只有用到了类中的内容,例如,构造函数等,类才会被加载。
执行顺序:加载类—》静态变量—》静态代码块—》主函数—》加载对象—》加载属性—》构造代码块—》构造函数
静态代码块内部只能放静态成员,构造代码块内部能放各种成员变量。

对象的初始化

Person p = new Person(“zhangsan”,20);
该句话的执行步骤如下:

1.因为new用到了Person类,所以会先找到Person.class文件并加载到内存中,静态变量加载到方法区。同时,栈内存中分配空间给变量p。

2. 会执行该类中的静态代码块,如果有的话,同时给Person.class类进行初始化。

3. 在堆内存中开辟空间,分配内存地址。

4. 在堆内存中建立对象的特有属性(实例变量),并进行默认初始化。

5. 对属性进行显示初始化。

6. 对对象进行构造代码块初始化。

7. 对对象进行对应的构造函数初始化。

8. 将内存地址赋给栈内存中的p变量,p指向对象。

面向对象的特征

封装

封装:就是隐藏对象的属性和实现的细节,控制在程序中属性的读和写的访问级别,仅对外提供公共的访问方式。
好处:
•将变化隔离。
•便于使用。
•提高重用性。
•提高安全性。
封装原则:
•将不需要对外提供的内容都隐藏起来。
•把属性都隐藏,提供公共方法对其访问。

private(私有)关键字是一个权限修饰符。用于修饰成员(成员变量和成员函数),被私有化的成员只在本类中有效。private可以将成员变量私有化,对外提供对应的
set方法(返回值为void且有参数)和get方法(没有参数)对其进行访问,提高对数据访问的安全性。私有仅仅是封装的一种表现形式。

继承

多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承单独的那个类即可。多个类可以称为子类,单独这个类称为父类或者超类。

通过extends 关键字让类与类之间产生继承关系。
写法:class SubDemo extends Demo{}

  • 继承的出现提高了代码的复用性。
  • 继承的出现让类与类之间产生了关系,提供了多态的前提。

子类可以直接访问父类中的非私有的属性和行为。定义继承要注意不能仅为了获取其他类的功能,简化代码而继承,必须是类与类之间有所属关系才可以继承(is a)。

使用继承体系中的功能:先查阅体系中父类的描述,因为父类中定义的是该体系*性功能,通过了解共性功能就可以知道该体系的基本功能。具体调用时,要创建最子类的对象。因为有可能父类不能创建对象,如抽象类;而且创建子类对象可以使用更多的功能,包括基本功能和特有功能。总的说就是查阅父类功能,创建子类对
象使用功能。

Java语言中:java只支持单继承,不支持多继承。
因为多继承容易带来安全隐患:当多个父类中定义了相同功能,当功能内容不同时,子类对象不确定要运行哪一个。但是java保留这种机制。并用另一种体现形式来完成表示。多实现。
java支持多层继承。也就是一个继承体系。

继承出现后,子父类成员的特点:
类中成员:
1,变量。

如果子父类中出现非私有的同名成员变量时,子类要访问本类的变量用this,一般可以省略。子类要访问父类中的同名变量用super,不可以省略。父类private成员变量可以被继承但无法被访问。
2,函数。

当子类出现和父类一模一样的函数时,当子类对象调用该函数,会运行子类函数的内容。如同父类的函数被覆盖一样。这种情况是函数的另一个特性:重写(覆盖)
当子类继承父类,沿袭了父类的功能,到子类中,但是子类虽具备该功能,但是功能的内容却和父类不一致,这时,没有必要定义新功能,而是使用覆盖特殊,保留父类的功能定义,并重写功能内容。

  • 子类覆盖父类,必须保证子类权限大于等于父类权限,才可以覆盖,否则编译失败。
  • 静态只能覆盖静态。

注意区分:
重载:同一个类中,只看同名函数的参数列表。
重写:子父类方法要一模一样。(包括返回值类型)
3,构造函数。

子类不能覆盖父类构造函数,因为类名不一样。在对子类对象进行初始化时,父类的构造函数也会运行,那是因为子类的构造函数默认第一行有一条隐式的语句super();
super()会访问父类中空参数的构造函数。而且子类中所有的构造函数默认第一行都是super();(只能写在第一行)

为什么子类一定要访问父类中的构造函数?
因为父类中的数据子类可以直接获取。所以子类对象在建立时,需要先查看父类是如何对这些数据进行初始化的。所以子类在对象初始化时,必须要先访问一下父类中的构造函数。子类中至少会有一个构造函数会访问父类中的构造函数。
如果要访问父类中指定的构造函数,可以通过手动定义super显示语句的方式来指定。当然子类的构造函数第一行也可以手动指定this语句来访问本类中的构造函数。

final关键字

1,可以修饰类,函数,变量。
2,被final修饰的类不可以被继承。为了避免被继承,被子类复写功能。
3,被final修饰的方法不可以被复写。
4,
被final修饰的变量是一个常量只能赋值一次,既可以修饰成员变量,也可以修饰局部变量。当在描述事物时,一些数据的出现值是固定的,那么这时为了增强
阅读性,都给这些值起个名字。方便于阅读。而这个值不需要改变,所以加上final修饰。作为常量:常量的书写规范所有字母都大写,如果由多个单词组成,
单词间通过_连接。
5,内部类定义在类中的局部位置上是,只能访问该局部被final修饰的局部变量。

抽象类

当多个类中出现相同功能,但是功能主体不同,这时可以进行向上抽取。这时,只抽取功能定义,而不抽取功能主体。例如void work() ,工人和程序员都需要工作,但是工作的内容不会相同。不抽取主体只抽取方法再通过方法的覆盖来实现多态的属性。是一种运行时绑定。

抽象类的特点:
1,抽象方法一定在抽象类中。抽象类中可以不定义抽象方法,这样做仅仅是不让该类建立对象。
2,抽象方法和抽象类都必须被abstract关键字修饰。
3,抽象类不可以用new创建对象。因为调用抽象方法没意义。
4,抽象类中的抽象方法要被使用,必须由子类复写起所有的抽象方法后,建立子类对象调用。如果子类只覆盖了部分抽象方法,那么该子类还是一个抽象类。

接口

接口是对外暴露的规则,是程序的功能扩展。

格式:
interface {}
接口中的成员修饰符是固定的:

  • 成员常量:public static final----全局常量
  • 成员函数:public abstract

接口可以理解为一个特殊的抽象类,当类中的方法都是抽象的,该类可以通过接口的形式来表示。接口中的方法都是没有方法体的,便于以后的覆盖。

类与接口之间是“实现”关系,implements。父类中有非抽象内容可以直接拿来用,子类只有将接口中的内容全部实现后才能实例化,否则是抽象类。接口
是不可以创建对象的,因为有抽象方法。覆盖接口中的方法必须用public,因为接口中的方法都是共有的,抽象类的权限不确定。
接口可以被类多实现。一个类可以同时现实多个接口。也是对多继承的不支持的转换形式。依然需要覆盖每个接口的方法。即使多个接口有相同名称的方法也没有问题,因为没有方法体,不会产生矛盾,子类可以全部覆盖。子类在继承父类的同时,还可以实现多个接口。
类之间是继承关系;类和接口之间是实现关系;接口之间是继承关系,接口之间可以多继承,但是不能有重载函数。

接口的使用思路:

子类继承父类的全部功能;但是,有的子类需要某些功能,有的子类不需要某些功能,可以将这些功能作为接口,需要的用,不需要的不用,防止不需要的子类也带上这些功能,而且其他的子类也可以使用这些接口;如果所有的子类都需要这些功能,但是具体的内容不同,可以使用抽象类,子类自己进行覆盖,或者使用一般类,
少部分的子类通过覆盖获得自己所需要的功能。
基本功能定义在类中,扩展功能定义在接口中。例如培训班的学员基本功能都要学习,扩展功能有部分学员需要抽烟。

抽象类和接口异同
相同:
1,都可以在内部定义抽象方法。
2,通常都在顶层。
3,都不可以实例化,都需要子类来实现。

不同:
1,抽象类中可以定义抽象方法和非抽象方法,而接口中只能定义抽象方法。
2,抽象类只能单继承。接口的出现可以多实现,也就是说接口的出现避免了单继承的局限性。
3,继承和实现的关系不一致。继承:is a,实现:like a

多态

多态可以理解为事物存在的多种体现形态。 比如动物的体现形态:猫、狗等。

猫这个对象对应的类型是猫类型
•猫 x = new 猫();
同时猫也是动物中的一种,也可以把猫称为动物。
•动物 y = new 猫();

动物是猫和狗具体事物中抽取出来的父类型。

父类型引用指向了子类对象,这就是多态的体现。

多态的前提必须是类与类之间有关系。要么继承,要么实现。通常还有一个前提:方法存在覆盖。

好处:大大提高了程序的扩展性,便于后期维护。

弊端:但是只能使用父类的引用访问父类的成员。

 Animal a = new Cat();//类型提升。 向上转型。
a.eat(); //如果想要调用猫的特有方法时,如何操作?强制将父类的引用转成子类类型。向下转型。
Cat c = (Cat)a;
c.catchMouse();
//千万不要出现这样的操作,就是将父类对象转成子类类型。
// Animal a = new Animal();
// Cat c = (Cat)a;错误的。 //多态自始至终都是子类对象在做着变化。

自己写了个小代码加深下多态的理解

 class Polymorphic
{
public static void main(String[] args)
{
Boss boss=new Boss();
boss.command(new MDWorker());
boss.command(new MTWorker());
}
}
//父类员工类
abstract class Worker
{ String name;
int age; abstract void salaried();//领薪水的方法
abstract void doProject();//做项目的方法 } //市场部员工子类
class MDWorker extends Worker
{ public void salaried(){
System.out.println("领3K薪水!");
}
public void doProject(){
System.out.println("做市场调差项目!");
}
public void report(){
System.out.println("特有功能写报告!");
} }
//技术部员工子类
class MTWorker extends Worker
{ public void salaried(){
System.out.println("领4K薪水!");
}
public void doProject(){
System.out.println("做技术测试项目!");
}
public void coding(){
System.out.println("特有功能写代码!");
}
}
//老板类
class Boss
{ public void command(Worker w)//接收父类的引用
{ w.doProject();
w.salaried();
//w.report();编译不通过。想调用子类特有功能必须向下转型。
if(w instanceof MDWorker)
{
MDWorker md = (MDWorker)w;//父类引用强转为市场部子类对象,调用写报告的特有方法。
md.report();
}
else if(w instanceof MTWorker)
{
MTWorker mt = (MTWorker)w;
mt.coding();
} }
}

在多态中成员函数的特点:
在编译时期:参阅引用型变量所属的类中是否有调用的方法。如果有编译通过,如果没有编译失败。
在运行时期:参阅对象所属的类中是否有调用的方法。
简单总结就是:成员函数在多态调用时,编译看左边,运行看右边。

在多态中,成员变量的特点:
无论编译和运行,都参考左边(引用型变量所属的类)。

在多态中,静态成员函数和静态成员函数的特点:
无论编译和运行,都参考左边(引用型变量所属的类)。

接口实现多态

        实现      被使用
类——--——>接口——--——>主程序
将相似的类中的方法抽象出来放在接口。
主程序只需要使用接口的方法建立接口的引用,类只需实现了接口,有新类出现只要将接口的引用指向新类,无需改动主程序和接口,就降低了程序的耦合性。

Object类

Object类:是所有对象的直接或者间接父类,唯一没有父类的类,传说中的上帝。该类中定义的是所有对象都具备的功能。

注意:实际应用中一般都有复写object父类中的方法。

equals()方法

 Object类中的equals()方法如下:

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

Object类中的equals()方法等价于==。只有当继承Object的类覆写了equals()方法之后,子类实现了用equals()方法比较两个对象是否相等,才可以说equals()方法与==的不同。

toString()

 Object类中的toString()方法定义如下:

 public String toString()
{
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

覆写equals和toString例子

 class  CarDemo
{
public static void main(String[] args)
{
Car c1 = new Car("2013", "宝马");
Car c2 = new Car("2013", "宝马");
System.out.println(c1.equals(c2));
System.out.println(c1);
}
}
class Car {
private String year;
private String type;
Car(String year, String type){
this.year= year;
this.type =type;
}
//复写equals()方法
public boolean equals(Object obj){ //如果比较双方指向同一个对象,则直接返回true。
if( this == obj)
return true;
if(obj != null && getClass() == obj.getClass())//使用getClass方法来判断运行时对象是否是同类对象,还要防止对null的对象执行getClass操作。
{
Car c = (Car)obj;//强转转换向下转型,才能访问子类中year和type。
if((this.year== c.year) && (this.type == c.type))
return true;
}
return false;
}
//复写toString()方法
public String toString(){
return ("这是"+year+"年生产的"+type+"汽车。");
}
}  

运行结果java面向对象(封装-继承-多态)