面向对象编程.xmind

1.什么是面向对象编程?

1.面向对象与面向过程

1.面向过程:

面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。面对过程适合一些较为简单的问题。

●优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、Linux/Unix等一 般采用 面向过程开发,性能是最重要的因素。
●缺点:没有面向对象易维护、易复用、易扩展。

2.面向对象:

面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
物以类聚,分类的思维模式,思考问题首先会解决问题需要哪些分类,然后对这些分类进行单独思考。最后,才对某个分类下的细节进行面向过程的思索。
面向对象适合处理复杂的问题,适合处理需要多人协作的问题!

●优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统更加灵活、更加易于维护。
●缺点:性能比面向过程低。

3.从“人把大象装进冰箱”的例子理解

image.png

4.面向对象的思想概述

  • 程序员从面向过程的执行者转化成了面向对象的指挥者

  • 面向对象分析方法分析问题的思路和步骤:

    • 根据问题需要,选择问题所针对的现实世界中的实体。
    • 从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
    • 把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
    • 将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。

      2.什么是面向对象?

      面向对象编程(Object-Oriented Progranmming,OOP)
      面向对象编程的本质:以类的方式组织代码,以对象的组织(封装)数据。
      三大特性:封装,继承,多态。
      从认识论角度考虑:先有对象后有类。对象,是具体的事物。类,是抽象的,是对对象的抽象。
      从代码运行角度考虑:先有类后有对象。类是对象的模板。

2.方法的定义和调用

1.回顾方法的定义

  • 修饰符
  • 返回类型
  • break&return
  • 方法名规范
  • 参数列表
  • 异常抛出

    2.回顾方法的调用

    1.静态方法与非静态方法

    静态方法中不能调用非静态方法,非静态方法可以直接调用静态方法。

    1.静态方法(可以直接调用)

    定义方法时带static关键字。
    当类加载静态方法区时,静态方法就已经在内存里拥有了自己的实际空间,因此静态方法无需实例化。
    在外部调用静态方法时,可以使用”类名.方法名”的方式, 也可以使用”对象名.方法名”的方式。而实例方法只有后面这种方式。也就是说,调用静态方法可以无需创建对象。

    2.非静态方法(先实例化再调用)

    在内存中没有自己的实际空间,需要new一下,new的作用就是在堆里按照非静态的方式申请空间,存放非静态的信息。即非静态方法需要实例化。

    1. public class Person {
    2. public static String getType(){
    3. return "人类";
    4. }
    5. public String getName(){
    6. return "张三";
    7. }
    8. }

    3.静态方法和非静态方法的区别

    静态方法和非静态方法的区别总结如下:
    1、静态方法属于类所有,类实例化前即可使用;
    2、非静态方法可以访问类中的任何成员,静态方法只能访问类中的静态成员;
    3、因为静态方法在类实例化前就可以使用,而类中的非静态变量必须在实例化之后才能分配内存;
    4、static内部只能出现static变量和其他static方法!而且static方法中还不能使用this等关键字,因为它是属于整个类;
    5、静态方法效率上要比实例化高,静态方法的缺点是不自动进行销毁,而实例化的则可以做销毁;
    6、静态方法和静态变量创建后始终使用同一块内存,而使用实例的方式会创建多个内存。
    主要区别:静态方法在创建对象前就可以使用了,非静态方法必须通过new出来的对象调用。
    静态方法与实例方法在性能和占用内存上没有明显的区别,是否声明为静态方法需要从类型的非静态字段、事件、面向对象扩展和多态这三方面来考虑。

    2.形参与实参

    形参和实参——形式参数和实际参数类型要符合。

    1.实参(argument)

    全称为”实际参数”是在调用时传递给函数的参数. 实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。

    2.形参(parameter)

    形参是函数被调用时用于接收实参值的变量。全称为”形式参数” 由于它不是实际存在变量,所以又称虚拟变量。是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数.在调用函数时,实参将赋值给形参。因而,必须注意实参的个数,类型应与形参一一对应,并且实参必须要有确定的值。

    3.形参与实参的区别

    形参出现在函数定义中,在整个函数体内都可以使用, 离开该函数则不能使用。
    实参出现在主调函数中,进入被调函数后,实参变量也不能使用

形参和实参的功能是作数据传送。发生函数调用时, 主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送
1.形参变量只有在被调用时才分配内存单元,在调用结束时, 即刻释放所分配的内存单元。因此,形参只有在函数内部有效。 函数调用结束返回主调函数后则不能再使用该形参变量。
2.实参可以是常量、变量、表达式、函数等, 无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值, 以便把这些值传送给形参。 因此应预先用赋值,输入等办法使实参获得确定值。
3.实参和形参在数量上,类型上,顺序上应严格一致, 否则会发生“类型不匹配”的错误。
4.函数调用中发生的数据传送是单向的。 即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。 因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化。
5.当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变
如果函数的参数是指针类型变量,在调用该函数的过程中,传给函数的是实参的地址,在函数体内部使用的也是实参的地址,即使用的就是实参本身。所以在函数体内部可以改变实参的值。

  1. public class Demo04{
  2. public static void main(String[] args){
  3. int a = 1;//实参
  4. System.out.println(a);//1
  5. Demo04.change(a);
  6. System.out.println(a);//1
  7. }
  8. //返回值为空
  9. public static void change(int a){//形参
  10. a=10;
  11. }
  12. }

3.引用传递和值传递

1.值传递:

在方法被调用时,实参通过形参把它的内容副本传入方法内部,此时形参接收到的内容是实参值的一个拷贝,因此在方法内对形参的任何操作,都仅仅是对这个副本的操作,不影响原始值的内容。

  1. public class Demo04{
  2. public static void main(String[] args){
  3. int a = 1;
  4. System.out.println(a);//1
  5. Demo04.change(a);
  6. System.out.println(a);//1
  7. }
  8. //返回值为空
  9. public static void change(int a){//形参
  10. a=10;
  11. }
  12. }

2.引用传递

“引用”也就是指向真实内容的地址值,在方法调用时,实参的地址通过方法调用被传递给相应的形参,在方法体内,形参和实参指向通愉快内存地址,对形参的操作会影响的真实内容。

  1. public class Demo05{
  2. public static void main(String[] args){
  3. Person person = new Person();
  4. System.out.println(person.name);//null
  5. Demo05.change(person);
  6. System.out.println(person.name);//秦江
  7. }
  8. public static void change(Person person){
  9. person.name="秦江";
  10. }
  11. //定义了一个Person类,有一个属性:name
  12. class Person{
  13. String name;//null
  14. }

3.值传递与引用传递小结

因此可见:在Java中所有的参数传递,不管基本类型还是引用类型,都是值传递,或者说是副本传递。
只是在传递过程中:
如果是对基本数据类型的数据进行操作,由于原始内容和副本都是存储实际值,并且是在不同的栈区,因此形参的操作,不影响原始内容。
如果是对引用类型的数据进行操作,分两种情况,一种是形参和实参保持指向同一个对象地址,则形参的操作,会影响实参指向的对象的内容。一种是形参被改动指向新的对象地址(如重新赋值引用),则形参的操作,不会影响实参指向的对象的内容。
小结一例:

  1. public class Main {
  2. private static void getMiddleOne(boolean b, Boolean boo, Boolean[] arr){
  3. b = true;
  4. boo = new Boolean(true);
  5. arr[0] = true;
  6. }
  7. //测试
  8. public static void main(String[] args) {
  9. boolean b = false;
  10. Boolean boo = new Boolean(false);
  11. Boolean[] arr = new Boolean[]{false};
  12. getMiddleOne(b, boo, arr);
  13. System.out.println(b);
  14. System.out.println(boo.toString());
  15. System.out.println(arr[0]);
  16. /**
  17. * output:
  18. * false
  19. * false
  20. * true
  21. */
  22. }
  23. }

1、基本数据类型的值就是数值本身,所以示例中的b的值就是false;包装类因为会自动装箱拆箱,所以可以和基本类型一样处理,所以示例中boo的值就是false;数组是引用类型,所以arr的值就是指向该Boolean[]的引用。

2、java中只有值传递没有引用传递,所以传入getMiddleOne方法的三个参数分别是b的值拷贝, boo的值拷贝, arr的值拷贝

通过上面两点就可以清楚了,getMiddleOne方法中执行的 b=true 和 boo = new Boolean(true) 都是把新值赋给了他们的拷贝,所以不改变原变量的值;同样,arr[0] = true 是把true复制给了arr的拷贝所指向的数组的第一个元素,arr的值和arr的拷贝的值都是该数组的引用,所以arr的拷贝所指向的数组和arr所指向的数组是同一个,所以改变arr的拷贝的数组的元素会同样影响到原变量arr。

3.类与对象的创建

1.类与对象的关系

1.类

  • 类是一种抽象的数据类型,它是对某一类事物整体描述/定义,但是并不能代表某一个具体的事物。
  • 动物、植物、手机、电脑.
  • Person类、 Pet类、 Car类等,这些类都是用来描述/定义某-类具体的事物应该具备的特点和行为。

    1.属性与方法

    静态的属性 属性=成员变量=field=域,字段
    动态的行为 方法=成员方法=函数=method
    image.png

    2.对象

  • 对象是抽象概念的具体实例

  • 张三就是人的一个具体实例,张三家里的旺财就是狗的一个具体实例。
  • 能够体现出特点,展现出功能的是具体的实例,而不是一个抽象的概念.

image.png

  • 可以理解为:类 = 抽象概念的人;对象 = 实实在在的某个人
  • 面向对象程序设计的重点是类的设计
  • 类的设计,其实就是类的成员的设计

    1. class Person{
    2. //属性
    3. String name;
    4. int age=1;
    5. boolean isMale;
    6. //方法
    7. public void eat(){
    8. System.out.println("人可以吃饭");
    9. }
    10. public void sleep(){
    11. System.out.println("人可以睡觉");
    12. }
    13. public void talk(String language){
    14. System.out.println("人可以说话,使用的是:"+language);
    15. }
    16. }

    2.创建与初始化对象

    使用new关键字创建对象
    使用new关键字创建的时候,除了分配内存空间之外,还会给创建好的对象进行默认的初始化以及对类中构造器的调用。 ```java //创建Person类的对象 Person p1=new Peerson(); //Scanner scanner=new Scanner(System.in);

//调用对象的结构,属性,方法 //调用属性,”对象.属性” p1.name=”Tom”; p1.isMale=true; System.out.println(p1.name);

//调用方法:“对象+方法”

  1. 类中的构造器也称为构造方法,是在进行创建对象的时候必须要调用的。并且构造器有以下俩个**特点:**<br /> 1.必须和类的名字相同<br /> 2. 必须没有返回类型,也不能写void
  2. <a name="k7XtC"></a>
  3. ## 3.Java虚拟机的内存结构
  4. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/1667248/1642151911093-9e50f704-4461-42d2-b43b-3bdc9b7b4c30.png#clientId=u3e567829-f6ac-4&crop=0&crop=0&crop=1&crop=1&from=paste&id=u8b705ed5&margin=%5Bobject%20Object%5D&name=image.png&originHeight=344&originWidth=469&originalType=binary&ratio=1&rotation=0&showTitle=false&size=149066&status=done&style=none&taskId=u4f892d93-2fc6-44bd-9a90-3efaf044031&title=)
  5. - 堆(Heap),此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。这一点在 Java虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配。
  6. - 通常所说的栈(Stack),是指虚拟机栈。虚拟机栈用于存储局部变量等。局部变量表存放了编译期可知长度的 各种基本数据类型(booleanbyte char short int float long double)、对象引用(reference类型, 它不等同于对象本身,是对象在堆内存的首地址)。 方法执行完,自动释放。
  7. - 方法区(Method Area),用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  8. <a name="ocWTA"></a>
  9. # 4.类的成员之属性
  10. <a name="XVJD2"></a>
  11. ## 1.属性(成员变量)与局部变量(形参)
  12. [https://www.yuque.com/doncic-uzmzo/qgp7m6/gefg26#WnMhA](#WnMhA)可结合上段阅读
  13. <a name="SVqEq"></a>
  14. ### 1.相同点
  15. 1.定义变量的格式:`数据类型 变量名=变量值;`<br />2.先声明,后使用<br />3.变量都有其对应的作用域
  16. <a name="zw5FY"></a>
  17. ### 2.不同点
  18. 1.在类中声明的位置不同:**属性**:直接定义在类的一对{}内<br /> ** 局部变量**:声明在方法内,方法形参,代码块内,构造器形参,构造器内部的变量。<br />2.关于权限修饰符的不同:属性:可以在声明属性时,指明其权限,使用修饰符。<br /> 常见的权限修饰符:privatepublic,缺省,
  19. <a name="om58N"></a>
  20. # 5.构造器(构造方法)
  21. <a name="igHlD"></a>
  22. ## 1.什么是构造器?
  23. 构造器通常也叫构造方法、构造函数,构造器在每个项目中几乎无处不在。当你new一个对象时,就会调用构造器。构造器格式如下:
  24. ```java
  25. [修饰符,比如public] 类名 (参数列表,可以没有参数){
  26. //不能有return
  27. }
  28. public class People {
  29. ......
  30. }

2.默认构造器

如果没有定义构造器,则会默认一个无参构造器,这就是为什么你定义了一个对象,比如 People,没有定义任何构造器却可以new这个对象,比如 new People() 。如果自定义了构造器,则会覆盖默认构造器。
新建一个类,不提供任何构造器,编译器会默认提供一个无参构造器,这就是为什么没定义任何构造器,却可以new 某个对象()。

  1. public class People {
  2. }

以上这个People类,可以直接通过 new People()来实例化。

3.如何禁止对象被外部创建

  1. 一些特殊需求,不希望定义的对象被外部创建(典型的就是单例了),那直接将构造器的修饰符改为 private 即可。这样就不能在外部通过new来创建这个对象了。<br /> 如果不希望People在外部通过new People()来实例化,只需要将构造器定义为private
  1. public class People {
  2. private People(){
  3. }
  4. }

4.构造器重载

  1. 与普通方法一样,构造器也支持重载。一个对象中是可以支持同时定义多个构造器,通过不同的参数列表来实现重载。经常看到代码中new一个对象时,有时传入参数,有时又可以不用传。比如:new People()跟new People(“张三”),这里就是重载了。<br /> 重载可以简单理解为:同个方法名,不同的参数列表。如果希望People能在外部通过new People() new People(“字符串”) 来实例化,则通过以下代码即可。
  1. public class People {
  2. //通过new People()调用
  3. public People(){
  4. }
  5. //通过new People("字符串") 调用
  6. public People(String str){
  7. }
  8. }

5.构造器的继承

  1. 子类构造器会默认调用父类无参构造器,如果父类没有无参构造器,则必须在子类构造器的第一行通过 super关键字指定调用父类的哪个构造器,具体看下文例子。final类是不允许被继承的,编译器会报错。很好理解,由于final修饰符指的是不允许被修改,而继承中,子类是可以修改父类的,这里就产生冲突了,所以final类是不允许被继承的。<br /> 定义父类构造器,由于该构造器自定义了一个带参构造器,覆盖了默认的无参构造器,所以不能直接 new SuperClass() 调用了,除非再定义一个无参构造器。
  1. /**
  2. * 父类构造器
  3. */
  4. public class SuperClass {
  5. /**
  6. * 自定义带参构造器
  7. */
  8. public SuperClass(String str){
  9. System.out.println("父类的带参构造方法,参数为:" + str);
  10. }
  11. }

定义子类构造器,继承SuperClass,由于SuperClass没有无参构造器,所以必须在子类构造器中通过 super(“字符串”)来调用,否则编译器会报错。

  1. /**
  2. * 子类构造器
  3. */
  4. public class SubClass extends SuperClass {
  5. /**
  6. * 无参构造器
  7. */
  8. public SubClass(){
  9. //由于SuperClass没有无参构造器,所以必须在子类构造器中通过 super("字符串")来调用,否则编译器会报错。
  10. //如果没定义该句,则编译器会默认调用 super()
  11. super("");
  12. }
  13. /**
  14. * 带参构造器
  15. */
  16. public SubClass(String subStr){
  17. //由于SuperClass没有无参构造器,所以必须在子类构造器中通过 super("字符串")来调用,否则编译器会报错。
  18. //如果没定义该句,则编译器会默认调用 super()
  19. super(subStr);
  20. }
  21. }

6.构造器、静态代码块、构造代码块的执行顺序

1.无继承的情况

  • 无继承的情况下的执行顺序
    静态代码块:只在程序启动后执行一次,优先级最高
    构造代码块:任何一个构造器被调用的时候,都会先执行构造代码块,优先级低于静态代码块
    构造器:优先级低于构造代码块
    总结一下优先级:静态代码块 > 构造代码块 > 构造器

    1. public class People {
    2. static {
    3. System.out.println("静态代码块,程序启动后执行,只会执行一次");
    4. }
    5. /**
    6. * 默认的无参构造器
    7. */
    8. public People(){
    9. System.out.println("默认构造器");
    10. }
    11. /**
    12. * 构造器重载,自定义一个带参构造器
    13. * @param str
    14. */
    15. public People(String str){
    16. System.out.println("带参构造器,参数:" + str);
    17. }
    18. {
    19. System.out.println("构造代码块,每次调用构造方法都会执行一次");
    20. }
    21. }

    实例化People

    1. public static void main(String[] args){
    2. System.out.println("--------------people----------------");
    3. People people = new People();
    4. System.out.println("--------------people1----------------");
    5. People people1 = new People("张三");
    6. }

    执行以上代码,输出:

    1. --------------people----------------
    2. 静态代码块,程序启动后执行,只会执行一次
    3. 构造代码块,每次调用构造方法都会执行一次
    4. 默认构造器
    5. --------------people1----------------
    6. 构造代码块,每次调用构造方法都会执行一次
    7. 带参构造器,参数:张三

    2.有继承的情况

    有继承的情况下的执行顺序:
    父类静态代码块:只在程序启动后执行一次,优先级最高
    子类静态代码块:只在程序启动后执行一次,优先级低于父类静态代码块
    父类构造代码块:父类任何一个构造器被调用的时候,都会执行一次,优先级低于子类静态代码块
    父类构造器:优先级低于父类构造代码
    子类构造代码块:子类任何一个构造器被调用的时候,都会执行一次,优先级低于父类构造器
    子类构造器:优先级低于子类构造代码块
    总结一下优先级:父类静态代码块 > 子类静态代码块 > 父类构造代码块 > 父类构造器 > 子类构造代码块 > 子类构造器

定义父类SuperClass

  1. /**
  2. * 父类构造器
  3. */
  4. public class SuperClass {
  5. {
  6. System.out.println("父类构造代码块,每次调用构造方法都会执行的");
  7. }
  8. /**
  9. * 父类无参构造方法
  10. */
  11. public SuperClass(){
  12. System.out.println("父类的默认构造方法");
  13. }
  14. /**
  15. * 重载,自定义父类带参构造方法
  16. * @param str
  17. */
  18. public SuperClass(String str){
  19. System.out.println("父类的带参构造方法,参数为:" + str);
  20. }
  21. static {
  22. System.out.println("父类的静态代码块,程序启动后执行,只会执行一次");
  23. }
  24. }

定义子类SubClass,继承SuperClass

  1. /**
  2. * 子类构造器,继承SuperClass
  3. */
  4. public class SubClass extends SuperClass {
  5. static {
  6. System.out.println("子类的静态代码块,程序启动后执行,只会执行一次,先执行父类的,再执行子类的");
  7. }
  8. {
  9. System.out.println("子类构造代码块,每次调用构造方法都会执行的");
  10. }
  11. /**
  12. * 无参构造器
  13. */
  14. public SubClass(){
  15. //这里没有指定调用父类哪个构造器,会默认调用 super(),调用父类的无参构造器public SuperClass()
  16. }
  17. /**
  18. * 重载构造器,多传两个参数
  19. * @param str
  20. * @param str1
  21. */
  22. public SubClass(String str,String str1){
  23. //必须写在构造器第一行,调用父类构造器 public SuperClass(String str)
  24. super(str);
  25. System.out.println("子类带参构造器:" + str1);
  26. }
  27. }

实例化SubClass

  1. public static void main(String[] args){
  2. System.out.println("--------------subClass1----------------");
  3. SubClass subClass1 = new SubClass();
  4. System.out.println("--------------subClass2----------------");
  5. SubClass subClass2 = new SubClass("子类第一个参数","子类第二个参数");
  6. }

执行以上代码,输出:

  1. --------------subClass1----------------
  2. 父类的静态代码块,程序启动后执行,只会执行一次
  3. 子类的静态代码块,程序启动后执行,只会执行一次,先执行父类的,再执行子类的
  4. 父类构造代码块,每次调用构造方法都会执行的
  5. 父类的默认构造方法
  6. 子类构造代码块,每次调用构造方法都会执行的
  7. --------------subClass2----------------
  8. 父类构造代码块,每次调用构造方法都会执行的
  9. 父类的带参构造方法,参数为:子类第一个参数
  10. 子类构造代码块,每次调用构造方法都会执行的
  11. 子类带参构造器:子类第二个参数

7.作用

  1. new 本质在调用构造方法。
    2.初始化对象的值。

    8.快速生成构造器

    Alt+Insert 自动生成构造器

    6.面向对象的三大特征

    1.封装

    1.封装的定义

    在面向对象程式设计方法中,封装(Encapsulation)是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法。
    通常,应禁止直接访问一个对象中数据的实际表示,而应通过操作接口来访问,这称为信息隐藏。
    封装可以被认为是一个保护屏障,防止该类的代码和数据被外部类定义的代码随机访问。
    要访问该类的代码和数据,必须通过严格的接口控制。
    总结:属性私有,get/set

    2.封装的优点

    1. 良好的封装能够减少耦合。
    1. 类内部的结构可以自由修改。
    1. 可以对成员变量进行更精确的控制。
    1. 隐藏信息,实现细节。

      3.程序设计的原则

      我们程序设计要追求“高内聚,低耦合”。
      高内聚就是类的内部数据操作细节自己完成,不允许外部干涉;
      低耦合:仅暴露少量的方法给外部使用。

      4.实现Java封装的步骤

  1. 修改属性的可见性来限制对属性的访问(一般限制为private),例如:

    1. public class Person {
    2. private String name;
    3. private int age;
    4. }

    这段代码中,将 nameage 属性设置为私有的,只能本类才能访问,其他类都访问不了,如此就对信息进行了隐藏。
    2. 对每个值属性提供对外的公共方法访问,也就是创建一对赋取值方法,用于对私有属性的访问,例如:

    1. public class Person{
    2. private String name;
    3. private int age;
    4. public int getAge(){
    5. return age;
    6. }
    7. public String getName(){
    8. return name;
    9. }
    10. public void setAge(int age){
    11. this.age = age;
    12. }
    13. public void setName(String name){
    14. this.name = name;
    15. }
    16. }

    采用 this 关键字是为了解决实例变量(private String name)和局部变量(setName(String name)中的name变量)之间发生的同名的冲突。

    5.Java封装的实例

    EncapTest.java 文件代码:

    1. public class EncapTest{
    2. private String name;
    3. private String idNum;
    4. private int age;
    5. public int getAge(){
    6. return age;
    7. }
    8. public String getName(){
    9. return name;
    10. }
    11. public String getIdNum(){
    12. return idNum;
    13. }
    14. public void setAge( int newAge){
    15. age = newAge;
    16. }
    17. public void setName(String newName){
    18. name = newName;
    19. }
    20. public void setIdNum( String newId){
    21. idNum = newId;
    22. }
    23. }

    以上实例中public方法是外部类访问该类成员变量的入口。
    通常情况下,这些方法被称为getter和setter方法。
    因此,任何要访问类中私有成员变量的类都要通过这些getter和setter方法。
    通过如下的例子说明EncapTest类的变量怎样被访问:
    RunEncap.java 文件代码:

    1. /* F文件名 : RunEncap.java */
    2. public class RunEncap{
    3. public static void main(String args[]){
    4. EncapTest encap = new EncapTest();
    5. encap.setName("James");
    6. encap.setAge(20);
    7. encap.setIdNum("12343ms");
    8. System.out.print("Name : " + encap.getName()+
    9. " Age : "+ encap.getAge());
    10. }
    11. }

    以上代码编译运行结果如下:

    1. Name : James Age : 20

    Alt+Insert :自动生成getter和setter方法。

    2.继承

    1.继承的概念

    继承是java面向对象编程技术的一块基石,因为它允许创建分等级层次的类。
    继承就是子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
    生活中的继承:
    image.png
    兔子和羊属于食草动物类,狮子和豹属于食肉动物类。
    食草动物和食肉动物又是属于动物类。
    所以继承需要符合的关系是:is-a父类更通用,子类更具体
    虽然食草动物和食肉动物都是属于动物,但是两者的属性和行为上有差别,所以子类会具有父类的一般特性也会具有自身的特性。

    2.类的继承格式

    在 Java 中通过 extends 关键字可以申明一个类是从另外一个类继承而来的,一般形式如下: ```java class 父类 { }

class 子类 extends 父类 { }

  1. <a name="NProj"></a>
  2. ### 3.为什么需要继承
  3. 接下来我们通过实例来说明这个需求。<br />开发动物类,其中动物分别为企鹅以及老鼠,要求如下:
  4. - 企鹅:属性(姓名,id),方法(吃,睡,自我介绍)
  5. - 老鼠:属性(姓名,id),方法(吃,睡,自我介绍)
  6. **企鹅类:**
  7. ```java
  8. public class Penguin {
  9. private String name;
  10. private int id;
  11. public Penguin(String myName, int myid) {
  12. name = myName;
  13. id = myid;
  14. }
  15. public void eat(){
  16. System.out.println(name+"正在吃");
  17. }
  18. public void sleep(){
  19. System.out.println(name+"正在睡");
  20. }
  21. public void introduction() {
  22. System.out.println("大家好!我是" + id + "号" + name + ".");
  23. }
  24. }

老鼠类:

  1. public class Mouse {
  2. private String name;
  3. private int id;
  4. public Mouse(String myName, int myid) {
  5. name = myName;
  6. id = myid;
  7. }
  8. public void eat(){
  9. System.out.println(name+"正在吃");
  10. }
  11. public void sleep(){
  12. System.out.println(name+"正在睡");
  13. }
  14. public void introduction() {
  15. System.out.println("大家好!我是" + id + "号" + name + ".");
  16. }
  17. }

从这两段代码可以看出来,代码存在重复了,导致后果就是代码量大且臃肿,而且维护性不高(维护性主要是后期需要修改的时候,就需要修改很多的代码,容易出错),所以要从根本上解决这两段代码的问题,就需要继承将两段代码中相同的部分提取出来组成 一个父类
公共父类:

  1. public class Animal {
  2. private String name;
  3. private int id;
  4. public Animal(String myName, int myid) {
  5. name = myName;
  6. id = myid;
  7. }
  8. public void eat(){
  9. System.out.println(name+"正在吃");
  10. }
  11. public void sleep(){
  12. System.out.println(name+"正在睡");
  13. }
  14. public void introduction() {
  15. System.out.println("大家好!我是" + id + "号" + name + ".");
  16. }
  17. }

这个Animal类就可以作为一个父类,然后企鹅类老鼠类继承这个类之后,就具有父类当中的属性和方法,子类就不会存在重复的代码维护性也提高代码也更加简洁提高代码的复用性(复用性主要是可以多次使用不用再多次写同样的代码) 继承之后的代码:
企鹅类:

  1. public class Penguin extends Animal {
  2. public Penguin(String myName, int myid) {
  3. super(myName, myid);
  4. }
  5. }

老鼠类:

  1. public class Mouse extends Animal {
  2. public Mouse(String myName, int myid) {
  3. super(myName, myid);
  4. }
  5. }

4.继承类型

Java中支持单继承, Java 不支持多继承,但支持多重继承。
image.png

5.继承的特性

  • 子类拥有父类非 private 的属性、方法。
  • 子类可以拥有自己的属性和方法,即子类可以对父类进行扩展。
  • 子类可以用自己的方式实现父类的方法。
  • Java 的继承是单继承,但是可以多重继承,单继承就是一个子类只能继承一个父类,多重继承就是,例如 B 类继承 A 类,C 类继承 B 类,所以按照关系就是 B 类是 C 类的父类,A 类是 B 类的父类,这是 Java 继承区别于 C++ 继承的一个特性。
  • 提高了类之间的耦合性(继承的缺点,耦合度高就会造成代码之间的联系越紧密,代码独立性越差)。

    6.继承关键字

    继承可以使用 extends 和 implements 这两个关键字来实现继承,而且所有的类都是继承于 java.lang.Object,当一个类没有继承的两个关键字,则默认继承object(这个类在 java.lang 包中,所以不需要 import)祖先类。

    1.extends关键字

    在 Java 中,类的继承是单一继承,也就是说,一个子类只能拥有一个父类,所以 extends 只能继承一个类。 ```java public class Animal { private String name;
    private int id; public Animal(String myName, String myid) {
    1. //初始化属性值
    } public void eat() { //吃东西方法的具体实现 } public void sleep() { //睡觉方法的具体实现 } }

public class Penguin extends Animal{ }

  1. <a name="zpKCc"></a>
  2. #### 2.implements关键字
  3. 使用 implements 关键字可以变相的使java具有多继承的特性,使用范围为类继承接口的情况,可以同时继承多个接口(接口跟接口之间采用逗号分隔)。
  4. ```java
  5. public interface A {
  6. public void eat();
  7. public void sleep();
  8. }
  9. public interface B {
  10. public void show();
  11. }
  12. public class C implements A,B {
  13. }

3.super 与 this 关键字

super关键字:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
this关键字:指向自己的引用。
super注意点:
1. super 调用父类的构造方法,必须在构造方法的第一个
2. super 必须只能出现在子类的方法或者构造方法中!
3. super和 this 不能同时调用构造方法!
Vs this:
代表的对象不同:
this:本身调用者这个对象
super:代表父类对象的应用
前提
this:没有继承也可以使用
super:只能在继承条件才可以使用
构造方法
this() ; 本类的构造
super():父类的构造

  1. class Animal {
  2. void eat() {
  3. System.out.println("animal : eat");
  4. }
  5. }
  6. class Dog extends Animal {
  7. void eat() {
  8. System.out.println("dog : eat");
  9. }
  10. void eatTest() {
  11. this.eat(); // this 调用自己的方法
  12. super.eat(); // super 调用父类方法
  13. }
  14. }
  15. public class Test {
  16. public static void main(String[] args) {
  17. Animal a = new Animal();
  18. a.eat();
  19. Dog d = new Dog();
  20. d.eatTest();
  21. }
  22. }

输出结果为:

  1. animal : eat
  2. dog : eat
  3. animal : eat

4.final关键字

final 关键字声明类可以把类定义为不能继承的,即最终类;或者用于修饰方法,该方法不能被子类重写:

  • 声明类:

    1. final class 类名 {//类体}
  • 声明方法:

    1. 修饰符(public/private/default/protected) final 返回值类型 方法名(){//方法体}

    :实例变量也可以被定义为 final,被定义为 final 的变量不能被修改。被声明为 final 类的方法自动地声明为 final,但是实例变量并不是 final

    7.构造器

    子类是不继承父类的构造器(构造方法或者构造函数)的,它只是调用(隐式或显式)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表。
    如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
    有关构造器内容见上。

    8.重写与重载

    1.重写(Override)

    重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参都不能改变。即外壳不变,核心重写!
    重写的好处在于子类可以根据需要,定义特定于自己的行为。 也就是说子类能够根据需要实现父类的方法。 ```java class Animal{ public void move(){

    1. System.out.println("动物可以移动");

    } }

class Dog extends Animal{ public void move(){ System.out.println(“狗可以跑和走”); } }

public class TestDog{ public static void main(String args[]){ Animal a = new Animal(); // Animal 对象 Animal b = new Dog(); // Dog 对象

  1. a.move();// 执行 Animal 类的方法
  2. b.move();//执行 Dog 类的方法

} }

  1. 以上实例编译运行结果如下:
  2. ```java
  3. 动物可以移动
  4. 狗可以跑和走

在上面的例子中可以看到,尽管 b 属于 Animal 类型,但是它运行的是 Dog 类的 move方法。
这是由于在编译阶段,只是检查参数的引用类型。
然而在运行时,Java 虚拟机(JVM)指定对象的类型并且运行该对象的方法。
因此在上面的例子中,之所以能编译成功,是因为 Animal 类中存在 move 方法,然而运行时,运行的是特定对象的方法。

  1. class Animal{
  2. public void move(){
  3. System.out.println("动物可以移动");
  4. }
  5. }
  6. class Dog extends Animal{
  7. public void move(){
  8. System.out.println("狗可以跑和走");
  9. }
  10. public void bark(){
  11. System.out.println("狗可以吠叫");
  12. }
  13. }
  14. public class TestDog{
  15. public static void main(String args[]){
  16. Animal a = new Animal(); // Animal 对象
  17. Animal b = new Dog(); // Dog 对象
  18. a.move();// 执行 Animal 类的方法
  19. b.move();//执行 Dog 类的方法
  20. b.bark();
  21. }
  22. }

以上实例编译运行结果如下:

  1. TestDog.java:30: cannot find symbol
  2. symbol : method bark()
  3. location: class Animal
  4. b.bark();

该程序将抛出一个编译错误,因为b的引用类型Animal没有bark方法。

2.方法的重写规则

  • 参数列表与被重写方法的参数列表必须完全相同。
  • 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。
  • 父类的成员方法只能被它的子类重写。
  • 声明为 final 的方法不能被重写。
  • 声明为 static 的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为 private 和 final 的方法。
  • 子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
  • 重写的方法能够抛出任何非强制异常,无论被重写的方法是否抛出异常。但是,重写的方法不能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常,反之则可以。
  • 构造方法不能被重写。
  • 如果不能继承一个类,则不能重写该类的方法。

    3.Super 关键字的使用

    当需要在子类中调用父类的被重写方法时,要使用 super 关键字。 ```java class Animal{ public void move(){
    1. System.out.println("动物可以移动");
    } }

class Dog extends Animal{ public void move(){ super.move(); // 应用super类的方法 System.out.println(“狗可以跑和走”); } }

public class TestDog{ public static void main(String args[]){

  1. Animal b = new Dog(); // Dog 对象
  2. b.move(); //执行 Dog类的方法

} }

  1. 以上实例编译运行结果如下:
  2. ```java
  3. 动物可以移动
  4. 狗可以跑和走

4.重载(Overload)

重载(overloading) 是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
最常用的地方就是构造器的重载。

5.重载规则

  • 被重载的方法必须改变参数列表(参数个数或类型不一样);
  • 被重载的方法可以改变返回类型;
  • 被重载的方法可以改变访问修饰符;
  • 被重载的方法可以声明新的或更广的检查异常;
  • 方法能够在同一个类中或者在一个子类中被重载。
  • 无法以返回值类型作为重载函数的区分标准。

    1. public class Overloading {
    2. public int test(){
    3. System.out.println("test1");
    4. return 1;
    5. }
    6. public void test(int a){
    7. System.out.println("test2");
    8. }
    9. //以下两个参数类型顺序不同
    10. public String test(int a,String s){
    11. System.out.println("test3");
    12. return "returntest3";
    13. }
    14. public String test(String s,int a){
    15. System.out.println("test4");
    16. return "returntest4";
    17. }
    18. public static void main(String[] args){
    19. Overloading o = new Overloading();
    20. System.out.println(o.test());
    21. o.test(1);
    22. System.out.println(o.test(1,"test3"));
    23. System.out.println(o.test("test4",1));
    24. }
    25. }

    6.重写与重载之间的区别

    image.png

    7.总结

    方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。

  • (1)方法重载是一个类中定义了多个方法名相同,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载(Overloading)。

  • (2)方法重写是在子类存在方法与父类的方法的名字相同,而且参数的个数与类型一样,返回值也一样的方法,就称为重写(Overriding)。
  • (3)方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。

image.png
image.png

3.多态

1.什么是多态?

多态是同一个行为具有多个不同表现形式或形态的能力。
多态就是同一个接口,使用不同的实例而执行不同操作,如图所示:
image.png

多态性是对象多种表现形式的体现。

现实中,比如我们按下 F1 键这个动作:

  • 如果当前在 Flash 界面下弹出的就是 AS 3 的帮助文档;
  • 如果当前在 Word 下弹出的就是 Word 帮助;
  • 在 Windows 下弹出的就是 Windows 帮助和支持。

同一个事件发生在不同的对象上会产生不同的结果。

2.多态的优点

    1. 消除类型之间的耦合关系
    1. 可替换性
    1. 可扩充性
    1. 接口性
    1. 灵活性
    1. 简化性

      3.多态存在的三个必要条件

  • 存在继承关系

  • 子类重写了父类的方法
  • 父类引用指向子类对象:Parent p = new Child();

image.png
注意:多态是方法的多态,属性没有多态性。

  1. class Shape {
  2. void draw() {}
  3. }
  4. class Circle extends Shape {
  5. void draw() {
  6. System.out.println("Circle.draw()");
  7. }
  8. }
  9. class Square extends Shape {
  10. void draw() {
  11. System.out.println("Square.draw()");
  12. }
  13. }
  14. class Triangle extends Shape {
  15. void draw() {
  16. System.out.println("Triangle.draw()");
  17. }
  18. }

当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。
多态的好处:可以使程序有良好的扩展,并可以对所有类的对象进行通用处理。
以下是一个多态实例的演示,详细说明请看注释:

  1. public class Test {
  2. public static void main(String[] args) {
  3. show(new Cat()); // 以 Cat 对象调用 show 方法
  4. show(new Dog()); // 以 Dog 对象调用 show 方法
  5. Animal a = new Cat(); // 向上转型
  6. a.eat(); // 调用的是 Cat 的 eat
  7. Cat c = (Cat)a; // 向下转型
  8. c.work(); // 调用的是 Cat 的 work
  9. }
  10. public static void show(Animal a) {
  11. a.eat();
  12. // 类型判断
  13. if (a instanceof Cat) { // 猫做的事情
  14. Cat c = (Cat)a;
  15. c.work();
  16. } else if (a instanceof Dog) { // 狗做的事情
  17. Dog c = (Dog)a;
  18. c.work();
  19. }
  20. }
  21. }
  22. abstract class Animal {
  23. abstract void eat();
  24. }
  25. class Cat extends Animal {
  26. public void eat() {
  27. System.out.println("吃鱼");
  28. }
  29. public void work() {
  30. System.out.println("抓老鼠");
  31. }
  32. }
  33. class Dog extends Animal {
  34. public void eat() {
  35. System.out.println("吃骨头");
  36. }
  37. public void work() {
  38. System.out.println("看家");
  39. }
  40. }

执行以上程序,输出结果为:

  1. 吃鱼
  2. 抓老鼠
  3. 吃骨头
  4. 看家
  5. 吃鱼
  6. 抓老鼠

4.多态问题小口诀

成员变量,静态方法看左边;
在java多态中的非静态方法,编译看左边,运行看右边。
编译时候,看左边的对象有没有该方法,运行时候结果看new的对象时谁,就调用谁的方法。
Animal c = new Cat(); 左边是 Animal 类(或接口) 右边是 Cat()类; 在编译的时候编译器不管你右边是什么类,只要左边的Animal类(或接口)能编译通过就不会报错。但是运行的时候就要按照右边的Cat()类实际情况来运行。

5.子类父类的方法调用与执行问题

  • 一个对象的实际类型是确定的,可以指向的引用类型就不确定了:父类的引用指向子类。
  • 子类能调用的方法都是自己的或者继承父类的;父类可以指向子类,但是不能调用子类独有的方法。
  • 对象能执行哪些方法,主要看对象左边的类型,跟右边关系不大。
  • 若子类重写了父类的方法,则执行子类的方法。

    6.不存在多态的情况

  1. static方法
  2. final方法
  3. private方法

    7.多态的实现方式

    1.方式一:重写:

    这个内容已经在上一章节详细讲过,就不再阐述,详细可访问:https://www.yuque.com/doncic-uzmzo/qgp7m6/gefg26#yTif7

    2.方式二:接口

    1. 生活中的接口最具代表性的就是插座,例如一个三接头的插头都能接在三孔插座中,因为这个是每个国家都有各自规定的接口规则,有可能到国外就不行,那是因为国外自己定义的接口类型。
    1. java中的接口类似于生活中的接口,就是一些方法特征的集合,但没有方法的实现。

      3.方式三:抽象类和抽象方法

      8.instanceof关键字

      1.简介

      java 中的instanceof是一个运算符,而且是二目运算符,即有两个操作数。该运算符是用来在运行时指出对象是否是特定类的一个实例。instanceof通过返回一个布尔值来指出,这个对象是否是这个特定类或者是它的子类的一个实例。

      2.用法

      result = object instanceof class

      3.参数

      Result:布尔类型。
      Object:必选项。任意对象表达式。
      Class:必选项。任意已定义的对象类。
      说明:
      如果 object 是 class 的一个实例,则 instanceof 运算符返回 true。如果 object 不是指定类的一个实例,或者 object 是 null,则返回 false。
      这里说明下
  • 类的实例包含本身的实例,以及所有直接或间接子类的实例。

  • instanceof左边显式声明的类型与右边操作元必须是同种类或存在继承关系,也就是说需要位于同一个继承树,否则会编译错误。

    4.代码示例

    ```java

package com.instanceoftest;

interface A{} class B implements A{

} class C extends B {

}

class instanceoftest { public static void main(String[] args){ A a=null; B b=null; boolean res;

  1. System.out.println("instanceoftest test case 1: ------------------");
  2. res = a instanceof A;//因为a是null,所以结果为false
  3. System.out.println("a instanceof A: " + res);
  4. res = b instanceof B; //因为b是null,所以结果为false
  5. System.out.println("b instanceof B: " + res);
  6. System.out.println("/ninstanceoftest test case 2: ------------------");
  7. a=new B();
  8. b=new B();
  9. res = a instanceof A;//因为a是B类型的实例,B又是A的一个实现类,所以结果为true
  10. System.out.println("a instanceof A: " + res);
  11. res = a instanceof B;//因为a是B类型的实例,所以结果为true
  12. System.out.println("a instanceof B: " + res);
  13. res = b instanceof A;
  14. System.out.println("b instanceof A: " + res);
  15. res = b instanceof B;
  16. System.out.println("b instanceof B: " + res);
  17. System.out.println("/ninstanceoftest test case 3: ------------------");
  18. B b2=(C)new C();
  19. res = b2 instanceof A;//因为b2是C类型的实例,而C是B的子类,B又是A的一个实现类,所以结果为true
  20. System.out.println("b2 instanceof A: " + res);
  21. res = b2 instanceof B;//因为b2是C类型的实例,所以结果为true
  22. System.out.println("b2 instanceof B: " + res);
  23. res = b2 instanceof C;//因为b2是C类型的实例,而C是B的子类,所以结果为true
  24. System.out.println("b2 instanceof C: " + res);

} }

  1. **结果:**
  2. ```java
  3. instanceoftest test case 1: ------------------
  4. a instanceof A: false
  5. b instanceof B: false
  6. instanceoftest test case 2: ------------------
  7. a instanceof A: true
  8. a instanceof B: true
  9. b instanceof A: true
  10. b instanceof B: true
  11. instanceoftest test case 3: ------------------
  12. b2 instanceof A: true
  13. b2 instanceof B: true
  14. b2 instanceof C: true

5.instanceof 应用场景

需要用到对象的强制类型转换时,需要使用instanceof进行判断。

9.抽象类

1.引入

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类
抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。
由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用。也是因为这个原因,通常在设计阶段决定要不要设计抽象类
父类包含了子类集合的常见的方法,但是由于父类本身是抽象的,所以不能使用这些方法。
在 Java 中抽象类表示的是一种继承关系,一个类只能继承一个抽象类,而一个类却可以实现多个接口

2.抽象类

在 Java 语言中使用 abstract class 来定义抽象类。
例如在如下的代码中:

  1. /* 文件名 : Employee.java */
  2. public abstract class Employee
  3. {
  4. private String name;
  5. private String address;
  6. private int number;
  7. public Employee(String name, String address, int number)
  8. {
  9. System.out.println("Constructing an Employee");
  10. this.name = name;
  11. this.address = address;
  12. this.number = number;
  13. }
  14. public double computePay()
  15. {
  16. System.out.println("Inside Employee computePay");
  17. return 0.0;
  18. }
  19. public void mailCheck()
  20. {
  21. System.out.println("Mailing a check to " + this.name
  22. + " " + this.address);
  23. }
  24. public String toString()
  25. {
  26. return name + " " + address + " " + number;
  27. }
  28. public String getName()
  29. {
  30. return name;
  31. }
  32. public String getAddress()
  33. {
  34. return address;
  35. }
  36. public void setAddress(String newAddress)
  37. {
  38. address = newAddress;
  39. }
  40. public int getNumber()
  41. {
  42. return number;
  43. }
  44. }

注意到该 Employee 类没有什么不同,尽管该类是抽象类,但是它仍然有 3 个成员变量,7 个成员方法和 1 个构造方法。 现在如果你尝试如下的例子:

  1. /* 文件名 : AbstractDemo.java */
  2. public class AbstractDemo
  3. {
  4. public static void main(String [] args)
  5. {
  6. /* 以下是不允许的,会引发错误 */
  7. Employee e = new Employee("George W.", "Houston, TX", 43);
  8. System.out.println("\n Call mailCheck using Employee reference--");
  9. e.mailCheck();
  10. }
  11. }

当你尝试编译 AbstractDemo 类时,会产生如下错误:

  1. Employee.java:46: Employee is abstract; cannot be instantiated
  2. Employee e = new Employee("George W.", "Houston, TX", 43);
  3. ^
  4. 1 error

3.继承抽象类

我们可以通过以下方式继承 Employee 类的属性:

  1. /* 文件名 : Salary.java */
  2. public class Salary extends Employee
  3. {
  4. private double salary; //Annual salary
  5. public Salary(String name, String address, int number, double
  6. salary)
  7. {
  8. super(name, address, number);
  9. setSalary(salary);
  10. }
  11. public void mailCheck()
  12. {
  13. System.out.println("Within mailCheck of Salary class ");
  14. System.out.println("Mailing check to " + getName()
  15. + " with salary " + salary);
  16. }
  17. public double getSalary()
  18. {
  19. return salary;
  20. }
  21. public void setSalary(double newSalary)
  22. {
  23. if(newSalary >= 0.0)
  24. {
  25. salary = newSalary;
  26. }
  27. }
  28. public double computePay()
  29. {
  30. System.out.println("Computing salary pay for " + getName());
  31. return salary/52;
  32. }
  33. }

尽管我们不能实例化一个 Employee 类的对象,但是如果我们实例化一个 Salary 类对象,该对象将从 Employee 类继承 7 个成员方法,且通过该方法可以设置或获取三个成员变量。

  1. /* 文件名 : AbstractDemo.java */
  2. public class AbstractDemo
  3. {
  4. public static void main(String [] args)
  5. {
  6. Salary s = new Salary("Mohd Mohtashim", "Ambehta, UP", 3, 3600.00);
  7. Employee e = new Salary("John Adams", "Boston, MA", 2, 2400.00);
  8. System.out.println("Call mailCheck using Salary reference --");
  9. s.mailCheck();
  10. System.out.println("\n Call mailCheck using Employee reference--");
  11. e.mailCheck();
  12. }
  13. }

以上程序编译运行结果如下:

  1. Constructing an Employee
  2. Constructing an Employee
  3. Call mailCheck using Salary reference --
  4. Within mailCheck of Salary class
  5. Mailing check to Mohd Mohtashim with salary 3600.0
  6. Call mailCheck using Employee reference--
  7. Within mailCheck of Salary class
  8. Mailing check to John Adams with salary 2400.

4.抽象方法


如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。
Abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。
抽象方法没有定义,方法名后面直接跟一个分号,而不是花括号。

  1. public abstract class Employee
  2. {
  3. private String name;
  4. private String address;
  5. private int number;
  6. public abstract double computePay();
  7. //其余代码
  8. }

声明抽象方法会造成以下两个结果:

  • 如果一个类包含抽象方法,那么该类必须是抽象类。
  • 任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

    继承抽象方法的子类必须重写该方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该抽象方法,否则,从最初的父类到最终的子类都不能用来实例化对象。
    如果Salary类继承了Employee类,那么它必须实现computePay()方法:

    1. /* 文件名 : Salary.java */
    2. public class Salary extends Employee
    3. {
    4. private double salary; // Annual salary
    5. public double computePay()
    6. {
    7. System.out.println("Computing salary pay for " + getName());
    8. return salary/52;
    9. }
    10. //其余代码
    11. }

    5.抽象类总结规定

    1. 抽象类不能被实例化(初学者很容易犯的错),如果被实例化,就会报错,编译无法通过。只有抽象类的非抽象子类可以创建对象。
    1. 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
    1. 抽象类中的抽象方法只是声明,不包含方法体,就是不给出方法的具体实现也就是方法的具体功能。
    1. 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
    1. 抽象类的子类必须给出抽象类中的抽象方法的具体实现,除非该子类也是抽象类。

      10.接口

      1.定义

      接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法
      接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。
      除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。
      接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

      2.接口与类的相似点

  • 一个接口可以有多个方法。

  • 接口文件保存在 .java 结尾的文件中,文件名使用接口名。
  • 接口的字节码文件保存在 .class 结尾的文件中。
  • 接口相应的字节码文件必须在与包名称相匹配的目录结构中。

    3.接口与类的区别

  • 接口不能用于实例化对象。

  • 接口没有构造方法。
  • 接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法。
  • 接口不能包含成员变量,除了 static 和 final 变量。
  • 接口不是被类继承了,而是要被类实现。
  • 接口支持多继承。

    4.接口特性

  • 接口中每一个方法也是隐式抽象的,接口中的方法会被隐式的指定为 public abstract(只能是 public abstract,其他修饰符都会报错)。

  • 接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 private 修饰会报编译错误)。
  • 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现接口中的方法。

    5.抽象类和接口的区别

    1. 抽象类中的方法可以有方法体,就是能实现方法的具体功能,但是接口中的方法不行。
    1. 抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是 public static final 类型的。
    1. 接口中不能含有静态代码块以及静态方法(用 static 修饰的方法),而抽象类是可以有静态代码块和静态方法。
    1. 一个类只能继承一个抽象类,而一个类却可以实现多个接口。

      6.接口的声明

      接口的声明语法格式如下:
      1. [可见度] interface 接口名称 [extends 其他的接口名] {
      2. // 声明变量
      3. // 抽象方法
      4. }
      Interface关键字用来声明一个接口。下面是接口声明的一个简单例子。 ```java / 文件名 : NameOfInterface.java / import java.lang.*; //引入包

public interface NameOfInterface { //任何类型 final, static 字段 //抽象方法 }

  1. 接口有以下特性:
  2. - 接口是隐式抽象的,当声明一个接口的时候,不必使用**abstract**关键字。
  3. - 接口中每一个方法也是隐式抽象的,声明时同样不需要**abstract**关键字。
  4. - 接口中的方法都是公有的。
  5. ```java
  6. /* 文件名 : Animal.java */
  7. interface Animal {
  8. public void eat();
  9. public void travel();
  10. }

7.接口的实现

当类实现接口的时候,类要实现接口中所有的方法。否则,类必须声明为抽象的类。
类使用implements关键字实现接口。在类声明中,Implements关键字放在class声明后面。
实现一个接口的语法,可以使用这个公式:

  1. ...implements 接口名称[, 其他接口名称, 其他接口名称..., ...] ...

实例

  1. /* 文件名 : MammalInt.java */
  2. public class MammalInt implements Animal{
  3. public void eat(){
  4. System.out.println("Mammal eats");
  5. }
  6. public void travel(){
  7. System.out.println("Mammal travels");
  8. }
  9. public int noOfLegs(){
  10. return 0;
  11. }
  12. public static void main(String args[]){
  13. MammalInt m = new MammalInt();
  14. m.eat();
  15. m.travel();
  16. }
  17. }

以上实例编译运行结果如下:

  1. Mammal eats
  2. Mammal travels

重写接口中声明的方法时,需要注意以下规则:

  • 类在实现接口的方法时,不能抛出强制性异常,只能在接口中,或者继承接口的抽象类中抛出该强制性异常。
  • 类在重写方法时要保持一致的方法名,并且应该保持相同或者相兼容的返回值类型。
  • 如果实现接口的类是抽象类,那么就没必要实现该接口的方法。

在实现接口的时候,也要注意一些规则:

  • 一个类可以同时实现多个接口。
  • 一个类只能继承一个类,但是能实现多个接口。
  • 一个接口能继承另一个接口,这和类之间的继承比较相似。

    8.接口的继承

    一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。
    下面的Sports接口被Hockey和Football接口继承: ```java // 文件名: Sports.java public interface Sports { public void setHomeTeam(String name); public void setVisitingTeam(String name); }

// 文件名: Football.java public interface Football extends Sports { public void homeTeamScored(int points); public void visitingTeamScored(int points); public void endOfQuarter(int quarter); }

// 文件名: Hockey.java public interface Hockey extends Sports { public void homeGoalScored(); public void visitingGoalScored(); public void endOfPeriod(int period); public void overtimePeriod(int ot); }

  1. Hockey接口自己声明了四个方法,从Sports接口继承了两个方法,这样,实现Hockey接口的类需要实现六个方法。<br />相似的,实现Football接口的类需要实现五个方法,其中两个来自于Sports接口。
  2. <a name="xV2yp"></a>
  3. #### 9.接口的多继承
  4. Java中,类的多继承是不合法,但接口允许多继承。<br />在接口的多继承中extends关键字只需要使用一次,在其后跟着继承接口。 如下所示:
  5. ```java
  6. public interface Hockey extends Sports, Event

以上的程序片段是合法定义的子接口,与类不同的是,接口允许多继承,而 Sports及 Event 可以定义或是继承相同的方法。

10.标记接口

最常用的继承接口是没有包含任何方法的接口。
标记接口是没有任何方法和属性的接口。它仅仅表明它的类属于一个特定的类型,供其他代码来测试允许做一些事情。
标记接口作用:简单形象的说就是给某个对象打个标(盖个戳),使对象拥有某个或某些特权。
例如:java.awt.event 包中的 MouseListener 接口继承的 java.util.EventListener 接口定义如下:

  1. package java.util;
  2. public interface EventListener
  3. {}

没有任何方法的接口被称为标记接口。标记接口主要用于以下两种目的:

  • 建立一个公共的父接口:正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案。
  • 向一个类添加数据类型:这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型。

    11.内部类

    内部类就是在一个类的内部再定义一个类。比如A类中定义一个B类,那么B类相对A类来说就称为内部类,而A类相对B类来说就是外部类了。