java基础之面向对象

面向对象思想的理解

        面向对象是一个很抽象的概念,如果连面向对象是什么都不知道的时候直接去写java程序,完全是照猫画虎,很多东西都不理解,面向对象是学习java语言的基础。

        要了解面向对象,我们首先了解一下面向过程,面向过程的编程思想就是我想让这个程序实现什么样的功能,我们自己来定义各种属性和方法来完成这件事,主体是我,而面向对象的编程思想是我让对象完成什么样的功能,我是个命令发出者,而完成这个功能的是对象,对象就类似于一个实体,他有自己的生命,我发出命令后他去执行然后完成这件事。对于同一件事两种思想有着不同的思维方式。举个例子说明一下吧,比如我下班回家开门这件事情吧,面向过程的思想就是我把门打开了,打开这个动作在我身上,映射到编程中就是我定义了一个打开门的方法,我调用了这个方法门开了,而面向对象的思想是打开门这件事不是我做的,我只是一个命令的发布者,打开门是门的动作,不是我的动作,就是我推了一下门给门一个信息,门的屁股扭了扭,开了,映射到程序中就是,门是一个对象,我调用了门这个对象的开门方法才打开的门,因为我不是门,怎样开我不知道,我的屁股可没这个功能。

        面向对象是以我们现实世界的思想来构建系统的,而面向过程是以功能为主来构建系统,我们现实世界中拥有各种各样的物体,他们都有着自己的属性和行为,我们可以把他们看做一个个对象,对象之间相互沟通构成我们的世界,应用到编程里就是我们要构建一个系统,这个系统是由各种各样的对象构成的,对象是属性和方法封装组成的,而这些对象又有共性可以划分为一类,对象就成为了类的实例,在我们所构建的系统中光有对象还不可以,我们必须要维护对象之间的关系,这样才构成一个完整的系统。

        面向对象的语言有java、c#、c++等,面向过程的语言有c等,面向对象的语言主要有三大特性:封装、继承、多态,在下面会一一介绍。

一.  类与对象之间的关系

          Java中我们说对象是一个生命体,他有自己的属性和方法,是功能的持有者与完成者,那么对象是怎么被创建出来的呢,就是通过类来创建,类就是对象的模板,我们在描述类的时候大多只关心两个方面的内容:属性和行为,通过定义类我们有了创建对象的模板,对象是具体的事物,我们通过“new”来创建对象,类是框架,通过这个框架我们具体要造出什么的对象,是我们自己去指定的,以现实生活为例,汽车是对象,但是汽车又各不相同,比如有4个轮子的,8个轮子的,有敞篷的还有封闭的,但是他们都是汽车,他们可以归为一类,制造汽车的工厂就好比我们定义的类,这个工厂有着创造汽车的功能,通过“new”你可以制造出各种各样的汽车,简单说就是我们定义类,通过类new出来对象。在类中我们要定义对象的属性和行为,定义在类中的属性叫做成员变量,也叫作实例变量,行为叫做成员函数。

          在我们定义的类中一般是不需要写主函数的,因为类是创建对象的工厂,他的作用是用来描述一类事物,他不需要独立运行,具体对象使我们要用到的,而什么时候用,怎么用需要我们自己来决定的,我们要构建系统,这个系统中有各种对象,也就有各种工厂,我们只需要在一个主函数中,创建对象并使用就足够了,没有必要让每个类都独立运行。

二.  对象在内存中的体现

        我们首先说一说java中内存的划分,内存分为五大部分:寄存器、本地方法区、方法区、栈、堆。我们重点先说栈和堆,栈内存中存放的为局部变量,就是定义在函数中的各种变量,一旦变量所属的作用域结束,变量就自动释放了;堆内存中存放的是数组与对象,我们可以理解为堆为栈的引用内存

        只要是通过new出来的对象都存放在堆内存中,堆内存的特点是每一个实体都有一个首地址值,对象所占的是一片区域,这个区域中含有各种的内容,比如成员变量,成员变量又是一片小区域,这片小区域属于对象所占的大区域中。

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

        1.成员变量定义在类中,整个类都可以访问到,局部变量定义在函数、语句、局部代码块中,只在所属的区域有效。

        2.成员变量存在于堆内存的对象中,局部变量存在于栈内存的方法中

        3.成员变量随着对象的寿命而定,局部变量随所属区域的结束而结束。

        4.成员变量具有默认初始化值,局部变量没有默认初始化值,所以定义局部变量一定要先赋值,再使用,如果使用的时候局部变量没有赋值,虚拟机就会报错。

注意点:当成员变量与局部变量重名的情况时,会优先执行局部变量,因为局部变量在栈中,当栈中没有此变量时才会去堆中寻找。例:

 

//定义一个类描述人这类事物
class Person
{
  /*人的属性有年龄、姓名
   * 定义在类中的属性叫做成员变量(实例变量)
   * */
  int age;
  String name;
  //定义人的方法,显示姓名和年龄
  public void show()
  {
     /*
      * 我们在这定义一个局部变量age,赋值为22,show方法进栈内存,
      * 在栈中能寻找到局部变量age,不会去堆内存再寻找成员变量age了
      **/
     int age =22;
     System.out.println(name+":"+age);//小宏:22
  }
}
//在主函数类中创建Person对象
public class Test1 {
  public static void main(String[] args) {
     Person p1 = new Person();
     p1.age =23;
     p1.name = "小宏";
     p1.show();
  }
}
/*  打印:
* 小宏:22
* */

 

三.  封装的思想

        封装是为了隐藏,我们知道类是创建对象的工厂,我们这个工厂不是想进就进的,我们可是一个有管理有尊严的厂子,很多东西我是不用暴露给外界的,你如果想进来,你必须得是我们需要的,比如我这厂子是建房子的,你给我运进来一堆城管,我还怎么干活,所以java给我们提供了封装的思想,我把我的属性什么的都藏起来,你要想运进或者运出东西,我给你提供相应的方法。

        我们建立一个类,把里面的属性私有化,其他类就不能直接访问我的属性了,怎么私有化,我们可以在我们要隐藏的属性前面用private修饰,就代表这个属性是本类私有的,然后我再提供公共的方法,这个公共方法就像是大门,以前我没封装时,你给我运城管进来,现在我有门了,你还想把城管运进来,看我不打爆你的眼睛,同样有进来的门就有出去的门,外界想要获取我的属性我也要给他们建门,所以我们写的java类时一般都会私有化属性,然后提供get方法(获取)和set方法(修改),我们在这两个方法中加入判断语句,用来甄别是否是我们需要的数据。

封装的优点我们总结一下:

        1.   将变化隔离,我们不能直接去操作类中的属性了

        2.   便于使用,我们可以通过方法获取或者修改相应的数据。

        3.   提高重用性

        4.   提高了安全性

注意点:private是私有修饰符,私有是封装的一种形式,但封装不一定非得使用私有。

四.  构造函数

        1.构造函数是对对象进行初始化的,使得对象被创建出来后便具有某些特性,他有以下特点:函数名字同类名一样,没有具体的返回值。

         而什么时候我们定义构造函数呢,就是我们在定义类时,为了描述一类事物,而这类事物已经具备了一些属性,我们会把这些属性定义在构造函数中,就是通过这个类创建的对象都有这些属性,这些属性是这类事物所共有的,这就是构造函数对对象进行初始化的意义所在。

        2.当我们创建一个类时,如果我们不自己定义构造函数,这个类中会有一个默认的构造函数,这个构造函数时空参的,如果我们定义了,这个默认的构造函数就被覆盖掉了。

        3.构造函数与一般函数的区别:

                ①构造函数当对象创建时,也就是我们new对象时便开始调用,只要你new对象了,那么就会启动构造函数,因为构造函数就是为了对象的初始化而被定义的。

                一般函数则是需要时才进行调用,也就是当我们创建出一个对象来,这个对象拥有很多方法,我调用某个方法,他才被会被虚拟机加载。

                ②构造函数只能被调用一次,就是在对象被初始化时调用一次,而一般函数可循环使用,非常环保。

         4.一个类中出现了多个构造函数,也就是我要创建的对象脸不太一样,虽然是一类的,但稍微有些区别,比如一个类是用来描述电脑的,有的电脑带串口,有的不带,这时我们要定义两个个构造函数,带串口参数的和不带串口参数的,如果出现了多个构造函数必须以重载的方式进行。

         5.构造函数中是可以有return语句的,不过作为正常人我们是很少去写这个return的,因为构造函数就是为了初始化对象的,他也没返回值,我也不可以单独调用,我写个return就相当于个大括号结束,只有说我判断这个构造函数的参数如果他不是我想要的参数时,我return,不给他初始化对象,但是这特别少见,基本可以忽略。

         6.this关键字

         我们通过构造函数来初始化对象,而我们定义构造函数时为了更好的区分我到底是给哪些属性赋值让他创建出对象,我们通常会把构造函数中的参数名称和类中的实例变量名(事物属性)统一起来,这样提高了代码的阅读性,但是问题也随之而来了,就是我们了解到当局部变量和实例变量同名的时候,java会去栈内存直接找到局部变量,而不会去堆内存找我们真正想赋值的实例变量的,怎么区分这两者呢,java给我们提供了一个关键字叫做this。

         this代表着当前对象,在构造函数中,哪个对象调用了this所在的函数,this便表征哪个对象,以示例来更好的说明一下:

 

class Person
{
    int age;
    String name;
    //构造函数中参数名和我们的实例变量名是一样的,我们在实例变量前加this关键字
    Person(String name,int age)
    {
       this.name = name;
       this.age = age;
 
    }
}

         在实例变量前加this代表这个变量是属于对象的,存在于堆内存中,和右边的局部变量是两回事,不要把左右两边都当成是局部变量来看。

         在类中我们的函数调用实例变量时,其实所有的实例变量前面都有“this.”只不过java把他隐藏了。

         构造函数是可以调用的,只有构造函数才能调用构造函数,注意这种构造函数的调用只能写在第一行,而且调用的方式很特殊,就是this

 

class Person
{
    int age;
    String name;
    int num;
    //我们在这里已经定义了name的初始化
    Person(String name)
    {
       this.name =name;
    }
/*
*我们想在这个构造函数中调用上一个构造函数,我们使用this关键字this(name)就是
*就是调用Person(String name)这个构造函数。
*/
    Person(String name,int age)
    {
       this(name);
       this.age =age;
    }
}

 

 

         7.构造函数与构造代码块的区别

         构造代码块的形式是:{初始化语句},构造代码块同构造函数一样也是初始化对象的,但是构造代码块会初始化他内部全部的属性,构造函数则可以通过重载的形式来有选择性的初始化对象。

五.  static关键字

         1.   static关键字是静态的意思,用来修饰成员变量和成员函数,用static修饰后的成员可以实现数据的共享。

         2.   被修饰后的成员有以下的特点:

                  1>  随着类的加载而加载

                  2>  优先于对象存在,被static修饰后的成员变量或成员函数,就升级了,不再单独属于某一个对象,而是属于工厂的正式员工了,这样做的局限性在于在静态的方法  中不可以有非静态的成员引用。

                  3>  被所有的对象共享,也就是这个正式员工责任变重了,你要对类所生产的所有对象负责。

                  4>  可以直接使用类名调用,如果你被static修饰了,你就是类中的一员,我们可以直接使用“类名.成员名称”来调用。

         注意点:静态方法只可访问静态成员;静态方法不可以写this、super关键字

         3.   成员变量与静态变量的区别

                  1>  生命周期不同,成员变量的生命周期等同于对象的生命周期,而静态变量的生命周期依据类的生命周期而定,一个类被虚拟机加载后,一般当虚拟机停止时这个类才归西,所以静态变量的可以存活很长时间。

                  2>  调用方式不同:静态变量可以被对象调用,还可以被类调用,而成员变量只能被对象调用。

                  3>  数据的存储位置不同:成员变量存储在堆中,静态变量存在于方法区的静态区中。

         4.   主函数解析

                  主函数也是静态的,我们在要在主函数中调用非静态的内容怎么办,我总不可能把所有的类,成员,函数都写成静态的吧,这样的话想想就很酸爽啊,既然不能这样做,我们可以在主函数中new对象出来,然后通过对象来调用这些非静态的成员,这样问题就迎刃而解了。

         让我们来看看主函数,主函数是固定写法,要不然虚拟机就不认识他,主函数是虚拟机执行我们代码的入口。

                  public:权限必须为最大,因为虚拟机要调用这个函数,如果你写成private那么他的权限就是最小的,虚拟机会找不到他

                  static:虚拟机加载主函数时是没有对象的,所以他必须是静态的,要不然根本无法调用,主函数成为静态后我们可以通过类名.main来执行主函数。

                  void:主函数不需要返回值,虚拟机即便拿到返回值也没鸟用,所以就定义成void。

                  String[] args:主函数的接收参数为字符串类型的数组,默认的是由虚拟机传入的长度为0的字符串。

         5.static的使用

         定义静态变量需求就是这个变量被所有的对象所共有,而且这个变量不需要修改。

        定义静态函数的标准就是是否需要访问特有数据,如果需要访问特有数据,那么就不要定义成成静态的,如果不需要的那么就定义成静态的,就是说静态函数中不可以有私有数据。比如说java的工具类中一般都是静态函数,因为工具类中没有定义成员变量他是被其他类调用完成某一个功能,所以不用new对象出来,直接类名调用即可,这样可以优化内存。

         6.静态代码块

         格式:static{}

         静态代码块的特点是随着类的加载而加载,也就是只加载一次,他可以用来对类进行初始化。

六.  继承

         1.   继承的概念

         继承就是一个类是另一类的所属,他们之间有着父子关系,也就是儿子要继承他爹的遗产,就产生了继承。

         class类名(儿子) extends 类名(父亲)

         继承的特点:

                  1>  提高的代码的复用性,父类的资产子类可以继承过来

                  2>  为多态提供了前提

         2.   单继承和多继承

                  单继承指的是一个子类只能有一个直接父类,也就是他只能extends一个父类;多继承指的是一个子类可以有多个父类,也就是他可以extends多个父类。但是很遗憾的是java不允许我们多继承,因为会产生不确定性,比如说多个父类中拥有一个同名的成员,到底该继承哪个。

                  Java虽然不允许多继承但是允许多层继承,也就是A—>B—>C—>D,这样就出现了继承的体系,形成一个族谱。当我们使用一个继承体系时,查看这个体系的顶层类,了解该体系的基本功能,而功能的实现,多数情况是由最底层的子类完成,这时候的功能已经完全确定好了。

        3.  继承的使用注意事项

                   我们要使用继承一般是当类与类直接有着所属关系时,比如说绿萝是绿植的一种,仙人掌也是绿植的一种那么绿萝和仙人掌就可以继承绿植;如果我们单纯的为了获取类中的成员时我们最好不使用继承。

        4.   super关键字

                  super这个关键字,我的理解是和this的表征相同,当子类中的成员变量和父类中重名时我们用super来区分父类成员变量,在变量前加super表示这个是父类中的变量,不同的地方在于this代表着对象,而super代表着父类空间,当子类new对象时,会在堆中开辟出一个空间,这个空间会有两个空间,一个存父类,一个存子类。

          注意点:子类不能直接访问父类中的私有成员,但父类可以对外提供方法。

         5.   覆盖

         当子类与父类拥有相同的方法,子类的方法会覆盖掉父类的方法。

                  如果我们要对一个类进行子类的扩展,子类要保留父类的功能声明,但要定义自己的特有功能,也就是明修栈道暗度陈仓,虽然我好像是在调用父类的方法,但是只是他的名,而方法主体却是子类自己定义的。

         注意点

                  ①子类方法覆盖父类方法,子类的权限要大于父类的权限,

                  ②父类的方法不能为private的,因为那是父类的私有方法,子类不可能覆盖掉。

                  ③如果父类的方法是静态的,子类的方法也是静态的才能覆盖

                  ④父类和子类的方法拥有相同的名字,相同的参数,但是返回值类型不同,这时编译器也会报错,只有子父类同名、同参、同返回时才可以覆盖,返回值类型不同 时,这个方法既不是子类的特有方法,也不是要覆盖父类的方法,那么我到底应该返回什么呢,会出现不确定性,所以编译器会报错。

         6.   继承中的构造函数初始化

         当出现继承后,构造函数的初始化就复杂了一些,让我们重新看一看对象的初始化过程:

                  Person s = new Person();

                  1>JVM会读取指定目录下的Person.class文件,并加载进内存,

                  他会先加载Person的父类(有直接父类的情况)

                  2>在堆内存中开辟空间,分配地址

                  3>在对象的空间中,对对象的属性进行默认初始化

                  4>调用类的构造函数进行初始化

                  5>在构造函数初始化的时候,如果有直接父类。会先进行父类构造函数的初始化(这时候子类的成员变量并无显示初始化,还是默认的初始化值)

                  6>父类构造函数初始化完毕后,才开始子类构造函数的初始化(显示初始化)

                  7>初始化完毕后,将地址值赋给引用变量。

 

class Fu
{
	Fu()
	{
		super();//调用的公共父类Object
		show();//调用的是子类的show方法
		return;
	}
	void show()
	{
		System.out.println("Fu run");
	}
}
class Zi extends Fu
{
	int Num=8;
	/*
	Num 默认初始化后——Zi()构造初始化——(super)Fu()父类构造初始化————显示的Num的默认初始化值
	*/
    Zi()
	{
		super();
		//--------------------------------分割线,之前要先初始化父类的构造函数
		System.out.println("Zi run..."+Num);
		return;
	}
	void show()
	{
		System.out.println("Zi run..."+Num);
	}
}
class  Test1
{
	public static void main(String[] args) 
	{
		Zi z = new Zi();
		z.show();
		/*
		 * 打印的是
		 * 	Zi run...0
			Zi run...8
			Zi run...8
		 * */
	}
}

 

 

 

         7.final关键字

                  1>final就是最终的意思,他可以用来修饰类、方法、变量。

                  2>被final修饰的类不能被继承;方法不能被覆盖;变量只能被赋值一次;用final修饰的变量不能被修改,也就是这个变量成为了常量,因此这个变量要起一个有意义的名字。

七.  抽象类

         1.   abstract关键字

                  abstract用来表征一个方法或者类是抽象的,抽象的出现是因为当我们描述一类事物没有足够的信息,我们只有抽象的去描述这个事物,比如狗是一个具体的事物,但是动物却是抽象的事物,我们用来描述动物的类没有足够的信息去表征这个动物到底是狗还是猫,所以我们把他定义成抽象类。抽象类我们可以理解成不同的事物向上抽取出来的共性内容。

         2.   抽象特点:

                  1>  抽象是方法只有声明没有实现时候才定义的,抽象方法必须定义在抽象类中

                  2>  抽象类不能被实例化,因为无法使用

                  3>  抽象类必须由子类覆盖掉他全部的抽象方法时这个子类才能去创建对象

         3.   抽象类注意点

                  1>  抽象类中有构造函数,可以用来给子类进行初始化

                  2>  一个抽象类中可以没有抽象的方法,这样做的原因是不让该类产生对象,通常这种类中的方法只有方法体({})而没有内容

                  3>  abstract不可以与private、static、final共存

                  4>  抽象类一定是一个父类

                  5>  抽象类与一般类的异同:

                           相同点:抽象类与一般类都是用来描述事物的,都在内部定义了成员

                           不同点:

                                    ①一般类有足够的信息去描述事物

                                    ②抽象类可以定义抽象方法,也可以定义非抽象方法

                                    ③抽象类不可以被实例化

         4.   我们通过一个示例来了解一下抽象类的使用,在这个例子中我们定义一个抽象类,这个抽象类用来描述一个公司的职员,职员有很多种,比如说干活的程序员,搞管理的项目经理,他们可以向上抽取出来一个公有的的信息就是职员,我们用抽象类来描述职员。

 

/*
抽象类的练习
需求:程序员属性有:姓名,工号,薪水,
		行为:写程序,吃饭
		项目经理属性:姓名,工号,薪水,奖金
		行为:管理,吃饭
*/
//描述职员,职员都有共有的东西姓名、工号、薪水,和公有行为吃饭
abstract class Employee
{
	private String name;
	private String id;
	private double pay;
<span style="white-space:pre">	</span>//构造方法用于给子类对象初始化
	Employee(String name ,String id,double pay)
	{
		this.name  = name;
		this.id = id;
		this.pay = pay;
	}
	//定义一个公有方法吃饭
	abstract void eat();

}
//描述程序员,程序员继承自职员
 class Programmer extends Employee
{
	Programmer(String name,String id,double pay)
	{
		//利用父类中的构造函数来初始化
		super(name,id,pay);
		System.out.println("name="+name+"  id="+id+"  pay="+pay);
	}
	//程序员的行为
	public void work()
	{
		System.out.println("写程序");
	}
	//只有覆盖掉父类中的抽象方法才能创建实例
	public void eat()
	{
		
		System.out.println("吃榨菜");
	}
}
//描述经理,经理继承自职员
class Manager extends Employee
{
	//定义经理的私有属性:奖金
	private double bouns;

	Manager(String name,String id,double pay,double bouns)
	{
		//利用父类中的构造函数来初始化
		super(name,id,pay);
		this.bouns = bouns;
		System.out.println("name="+name+"  id="+id+"  pay="+pay+" bouns="+bouns);
	}

	//经理的行为
	public void work()
	{
		System.out.println("管理");	
	}
	//只有覆盖掉父类中的抽象方法才能创建实例
	public void eat()
	{
		
		System.out.println("吃辣条");
	}
}
public class Demo {

	public static void main(String[] args) {
		//创建一个程序员对象名字小宏,工号是1,工资是10000
    	Programmer p = new Programmer("小宏","1",10000);
		p.work();
		p.eat();
		//创建一个经理对象名字叫小绿,工号是2,工资12000,奖金是5000
		Manager m =  new Manager("小绿","2",12000,5000);
		m.work();
		m.eat();
	}
}
/*打印:
 * 	name=小宏  id=1  pay=10000.0
	写程序
	吃榨菜
	name=小绿  id=2  pay=12000.0 bouns=5000.0
	管理
	吃辣条
 * */

 

八.  接口

         1.接口的慨念

                  当一个抽象类中所有的方法都是抽象方法时,这个类可以以另一种形式定义,那就是接口,接口的定义使用关键字interface,接口中的常见成员有两个:全局常量和抽象方法,这个两个成员的修饰符都是固定的:

         全局变量:public static final

         抽象方法:public abstract

         例:

interface Person
{
   //定义一个全局变量AGE
   public static final int AGE = 99;
   //定义一个抽象方法
   public abstract void show();
}

 

         这个接口最后也是生成Person.class文件。

         2.使用接口的方法,我们要使用到一个关键字implements,这个关键字名字叫实现,意为实现了一个接口:

         class Student  implements Person//类Student实现Person接口

         注意一个类要实现一个接口必须要覆盖掉接口中所有的方法才可以实例化,否则他还是一个抽象类

         3.接口的使用特性:

                  1>可以实现多实现,不存在多继承中父类成员混淆的问题,因为接口中都是抽象方法没有方法体,即便多个接口中的方法名相同也不会出现调用混乱的问题,因为压根就没有方法体,只有一个声明。

                  class Student   implements Person ,son//一个类Student实现了Person和Son两个接口

                  2>一个类在继承的同时还可以实现多个接口

                  Class Student  extends Stu implements Person,son//一个类Student继承了一个类Stu并实现了Person和Son两个接口

                  3>一个接口可以实现多继承,注意是多继承的是接口而不是类,也就是接口和接口可以是继承关系而不是实现关系

                  interface Person exteds A,B//一个接口Person继承了A和B两个接口

         4.   接口的特点

                  1>  接口是对外暴露的规则

                  2>  接口是程序的功能扩展

                  3>  接口降低了程序间的耦合性

                  4>  接口可以用来多实现,提高了程序的复用性

         5.   接口和抽象类的异同

                  相同点:都是不断向上抽取共性而来,并且都是抽象的

                  不同点:抽象类需要被继承,而且只能是单继承,接口需要实现,而且可以多实现;抽象类中可以定义抽象和非抽象两类方法,而接口中全部都是抽象方法

         6.接口类型的变量

                  接口类型的变量他的指向是其子类对象

九.  多态

         1.多态的概念

         多态就是一个对象对应着多种形态,在代码中的体现就是一个父类或者接口类型的变量指向其子类创建出来的对象,例如动物是一个抽象的事物,我们用接口或者抽象类描述,狗是一个具体的事物,我们有足够的信息去描述他,他可以直接new出来对象,当定义一个动物类型的变量指向狗这个对象,这就是多态:

         动物 d = new 狗();

         2.多态的前提和利弊

         前提:多态必须是有关系的类,例如继承和实现,只有拥有关系才能使用多态,而且子类必须覆盖掉父类的方法。

         好处:提高了代码的扩展性,前期定义的代码可以被后期调用

         弊端:就是前期定义的内容不可以调用后期子类中的特有方法。

         3.类型转换

         父类 Animal 子类Cat

         Animal a = new Cat();

         这是多态的体现,我们也称之为类型提升,就是父类Animal类型的变量a指向了子类Dog的对象,这时候子类类型就提升为父类类型了,但是这个时候我们无法使用子类的特有方法,因为类型提升以后不可以使用子类的特有内容,我们可以使用类型转换来把父类类型向下转型为子类类型:

         Cat d = (Cat) a;

         这时候我们就可以使用子类的特有内容了。注意点就是我们内存中的对象始终都只有一个,就是new Cat();自始至终都是子类对象在做着类型的切换,我们以示例来解释一下:

 

/*
多态的转型
注意点:
自始至终都是子类对象在做着类型的变换
*/
 
//定义一个父类动物(抽象的),里面定义抽象方法eat
abstract class Animal
{
	abstract void eat();
}
//定义子类猫
class Cat extends Animal
{
	//覆盖掉父类的方法才能创建对象
	void eat()
	{
	     System.out.println("吃鱼");
	}
	//定义子类的特有方法
	void show()
	{
	     System.out.println("抓老鼠");
	}
}
class Test1
{
	public static void main(String[] args)
	{
	     /*
	  	向上转型
	     	作用为限制对特有功能的访问
	     */
	     Animal A = new Cat();//自动类型提升,猫提升为动物但是猫的特有内容无法执行
	     A.eat();
	     //A.show();//这个方法是子类的特有方法,无法被父类所使用
	     
	     /*
		     向下转型
		     作用为能使用子类中的特有方法
	     */
	     Cat C = (Cat)A;//自动类型向下,把动物类型的A转为猫类型的C
	     C.eat();
	     C.show();
	}
}
/*打印:
	吃鱼
	吃鱼
	抓老鼠
	*/

 

         当我们向下进行类型转换时,非常容易出现一个问题就是类型转换异常,比如Dog和Cat是Animal的子类,我们new出来一个Dog对象并向下转型:

                  Animal a = new Dog();

                  Dog d =(Dog)a;

         如果我们不小心写错了:

                  Dog d = (Cat)a;

         这时候java会给我们报出一个ClassCastException异常,代表类型转换错误,因为a是Dog型,你怎么能把他转成Cat型的呢。

         4.类型判断

         类型判断使用instanceof关键字,这个关键字用于判断对象的具体类型,当传入函数中一个父类型的对象是,要判断这个对象是不是我们需要的对象,就使用这个关键字,例如父类是Animal,子类是Dog,Cat

 

public static void method(Animal a)
	{
		//如果a是Cat类型的,我们就可以对他使用类型转换
		if( a instanceof Cat)
			Cat c = (Cat)a;
}

 

类型判断最为常用的地方就是在向下转型时用于判断是否使我们所需类型,这就避免了类型转换异常的发生。

         5. 多态的成员的运行:

                  1>成员变量特点:

                  编译运行都参考左边的变量所属的类

                  2>成员函数(非静态)

                  就是编译看左边,运行看右边(子类方法会覆盖掉父类中的同名方法)

                  3>静态函数

                  编译时,参考引用型所属类中是否有成员函数,有,编译通过

                  运行时,参考引用型所属类中是否有成员函数,有,执行父类中的。函数就是都看左边

                  其实对于静态函数,不需要看对象,直接类名调用即可

十. 内部类

      1.将一个类定义在类的内部

      classA{

           classB{}

         }

         这样生成A$B.class与A.class文件

         2.内部类的访问特点

                  1>内部类可以直接访问外部类的成员,因为有this关键字表征

                  2>外部类要访问内部类必须建立内部类对象来访问

         3.内部类的使用

         外部类.内部类 变量名 = new 外部类().new 内部类();

         注意点:如果内部类中有静态的成员(属性或者方法),内部类必须也是静态的,这个时候内部类就相当于外部类,我们可以通过类名直接调用

外部类.内部类 变量名 = new 外部类.内部类();

 

class Test
{
	private static int num = 3;

	 static class Inner
	{
		static void show()
		{
			System.out.println("num="+num);
		}
		public static void function()//如果内部类中的方法是静态的那么内部类也必须是静态的
		{
			System.out.println("function="+num);	
		}
	}
	 void method()
	{
		Inner in = new Inner();
		in.show();
	}
}
class  ArrayTool
{
	public static void main(String[] args) 
	{
		//构建外部类对象通过method方法构建内部类
		Test T = new Test();
		T.method();

		//如果内部类是静态的,就相当于一个外部类
		Test.Inner I1 = new Test.Inner();
		I1.show();

		//如果内部类是静态的,成员是静态的
		Test.Inner.function();
	}
}
/*打印:
 * 	num=3
	num=3
	function=3
 * */

 

         4.局部内部类

         内部类不仅可以存放在成员位置上也可以存放在局部位置上,比如内部类可以放在函数中,内部类在局部位置上的时候如果要访问局部位置上的属性,那么这个属性必须是被final修饰的。

class Outer
{
    //成员变量num
    int num = 3;
    void show()
    {
        //如果内部类要访问局部变量,那么这个局部变量一定是final的
       final int x=5;
       //定义局部内部类
       class Inner
       {
           void inShow()
           {
              int num = 4;
              //打印外部类成员变量num和函数中的局部变量x
              System.out.println("num="+Outer.this.num+" x="+x);
           }
       }
       //外部调用外部类的show方法时我们运行内部类
       Inner I = new Inner();
       I.inShow();
    }
}
class Test1
{
    public static void main(String[] args)
    {
       //调用外部类的show方法
       new Outer().show();
    }
}
/*打印:
 * num=3 x=5
 * */

 

         5.   匿名内部类

         匿名内部类就是内部类的简写格式,使用匿名内部类必须有前提,就是内部类必须继承一个类或者实现一个接口,因为匿名内部类没有名字你不能直接new类的名字。

         格式:new 父类名(或者接口名)(){方法};

         当父类是抽象类或者接口的时候我们是不能直接new对象的,但是注意我们写的是new 父类(){方法},我们在{}中覆盖父类的方法,这就相当于我们new了一个子类对象出来。

         匿名内部类的应用之一就是当函数参数是接口类型的,而且接口中的方法不超过三个我们可以用匿名内部类作为实际参数进行传递:

 

//定义一个接口,其中的方法不超过三个
interface Inner
{
	void show1();
	void show2();
}
class Test1
{
	//定义一个方法用来接收接口类型的数据
	public static void show(Inner in)
	{
		//调用其中的方法
		in.show1();
		in.show2();
	}
	public static void main(String[] args) 
	{
		//调用show方法,以匿名内部类的方式往这个方法中传出接口类型的子类对象
		show(new Inner(){
			public void show1()
			{
				System.out.println("A");
			}
			public void show2()
			{
				System.out.println("B");
			}
		});//打印出A B
	}
}

 

十一. 对象的实例化过程

         当我们定义一个类,如果这个类是一个子类,而且有构造代码块,对象的初始化过程是

         1.   子类构造函数进栈,成员变量默认初始化。

         2.   Super关键字启用,父类构造函数进栈并运行

         3.   成员变量显示初始化

         4.   构造代码块加载

         5.   子类构造方法运行,所以此时打印变量为已经初始化好的值

示例:

 

//定义一个父类
class Fu
{
  //父类中的成员变量num为3
  int num =3;
  //父类构造函数调用show方法
  Fu()
  {
     System.out.println("父类构造代码块");
     show();
  }
  //父类中的show方法
  void show()
  {
     System.out.println("Fushow:"+num);
  }
}
//定义子类继承父类
class Zi extends Fu
{
  //子类中的成员变量num为1
   int num = 1;
   //定义构造代码块,打印num值,这时候num已经显示初始化
  {
     System.out.println("构造代码块num="+num);
     //给num重新赋值为2
     num = 2;
  }
  //子类构造方法
  Zi()
  {
     super();
     System.out.println("子类构造代码块num="+num);
     show();
  }
  //子类定义show方法会覆盖掉父类的方法
  void show()
  {
     System.out.println("子类show方法num="+num);
  }
}

class Demo
{
  public static void main(String[] args)
  {
     new Zi();
  }
}
/*
 * 打印:
        父类构造代码块
        子类show方法num=0
        构造代码块num=1
        子类构造代码块num=2
        子类show方法num=2
 **/

 

十二. Object类浅析

         Object类是java中所有类的根类,只要你的类没有明确的继承关系,这个类的默认父类就是Object,Object具备这所有对象都具备的共性内容,我们看一看Object类中的方法。

         1.   equals方法

         这个方法用于比较其他对象是否与此对象相等,equals用于比较内容

         equals具有以下特性:

                  1>  自反性,对于任何非空引用值x,x.equals(x)都返回true

                  2>  传递性,对任何非空引用值x、y、z,当x.equals(y)为true,y.equals(z)也为true,那么x.equals(z)为true。

                  3>  一致性,对任何非空引用值x、y,多次调用x.equals(y)始终返回true或者false,前提是对象中equals所比较的信息没有被修改。

         注意任何非空引用变量x,x.equals(null)结果都是false。

         在Object中初始的equals方法是比较地址值,所以我们需要在子类中复写equals方法来实现我们需要比较的内容。

         equals方法与==的区别

          ==专门用于比较两个变量所存储的数值是否相同,要比较两个基本数据类型数据或者两个引用变量是否相等,使用==操作;当变量指向的对象类型的,那么这个时候对应两块内存,例Object obj = new Object();首先obj这个变量在栈中占一块内存,newObject()在堆中占一块内存,然后obj这个变量指向堆中对象的地址,如果我们要比较两个变量是否对应同一个对象,这时候我们用==,即比较两个变量所对应的内存中对象的地址。

         equals方法用于比较两个对立的对象内容是否相同,就好比去比较两辆汽车的型号是否相同,它比较的两个对象是独立的,例如:

         String s1 = new String(“小宏”);

         String s2 = new String(“小宏”);

          现在我们有两个对象了,s1与s2分别指向不同的对象,他们的首地址是不同的,如果我们调用s1 == s2会返回false,而这两个对象中的内容是相同的,用s1.equals(s2)将返回true,注意点是在String类中已经复写了Object类的equals方法,他比较的是字符串的内容而不是地址值,所以我们在使用equals方法时一定要自己去定义比较的内容,如果不覆盖equals方法,那么我们还是在比较对象的地址值,这个时候==和equals就没有区别了。

         2.    hashCode()

         这个方法用于返回对象的哈希码值,Object中的hashCode方法是调用底层算法给出的,通常我们也要覆盖hashCode方法,如果根据equals方法两个对象是相等的那么这两个对象调用hashCode方法时所返回的结果也得是相等的。

         如果equals方法比较两个对象不相等,hashCode返回值不一定要非得是不同的整数,也可以是相同的,但我们一般会让他们生成不同的数值,这样可以提高哈希表的性能。在集合中有关于这部分的内容,再着重讲述一下。

         3.   getClass()

         获取当前对象所属的字节码文件,调用这个方法会返回一个Class字节码,这个字节码是被虚拟机先加载进内存的,通过这个字节码我们才能new出各种对象,在反射中会涉及到。

         4.   toString()

         返回该对象的字符串表现形式,一般都会在子类中复写这个方法,把他的相关属性以字符串的形式返回,以便直观的了解我们的对象。

 
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值