【JAVA】 02-Java对象细节

链接:

目录:

<>


  • <>

内容待整理:

面向过程:

代表语言-c;即通过函数体现,并不断调用函数,执行完成过程,结束)

面向对象入门:

基于面向过程的升级;(笔记第二页的总结);举例:买电脑-自己买-面向过程-查信息,查报价,组装监督,扛回家,以及调用一个懂行的人做这些事,这个人具备所需功能;差异-面向对象的好处-三点-要记下来会考;万物皆对象-所见之物皆对象,脑海中想到一个概念,也可以抽象定性为一个对象;面试中的面向对象-回答模板一系列;)
(需求:将大象装进冰箱的一系列思考:需求中尽量寻找对象-从问题领域中抽取对象,一般情况“名词”大多是对象;功能属于哪个对象-要看哪个对象最清楚该功能-eg张三开冰箱,开是冰箱的功能;总结:1234;对象-功能分析练习:人开门(门的功能),人刹车(车的功能),人开车(车的功能),毕老师用电脑上课(用电脑和上课都是老师的功能))
(引入对象定义:类的真正意义在于描述事物,并非仅封装代码;类定义属性和行为,而属性和行为统称为事物中的成员;成员分为两种:成员属性-代码体现为成员变量,成员行为-代码体现为函数;成员变量与局部变量-成员变量定义在类中,类似全局变量,作用域大,局部变量定义在函数中;)
(内存图解:栈比堆大-栈释放内存块,堆释放内存慢;堆内存中的变量都有默认初始化值,String默认初始化为null;基本类型-变量中自己存储值,引用类型-变量中只存储实体的地址值;)
(对应代码:day06-carDemo.java)
(对象图解1)

(对象图解2)

(对象图解3)

类与对象的关系:)

(成员变量与局部变量的区别:4点;核心总结(见笔记),栈和堆里放什么,和数据类型没有关系)

匿名对象:

(类类型的变量一定指向对象,引用型变量不指向对象时指向null;匿名对象的示例:Car c = new Car(); c.run();简化为new Car().run();;匿名对象的作用:简化书写;匿名对象的局限性(凡是简化,必有局限性):局限性图解;使用场景:当对象对方法进行调用且只调用一次时可简化为匿名对象,当对象需要调用多次成员时,不可简化为匿名对象;匿名对象作为实参:将“Car c = new Car(); show(c);”简化为“show(new Car());”)
(匿名对象局限性-图一)

(基本数据和引用数据的参数传递过程:图解)

封装:

(封装:面向对象思想特征(封装继承多态,没体现就不叫面向对象开发)之一;好处:3点;举例:机箱、组装机-至少一面留口,隐藏板卡设备的细节,对外提供插口以及开关等访问内部细节的方式-面对的细节越少越简单;)
(私有private:权限修饰符,修饰后只能在类内访问,不能在所在类以外直接访问,被隐藏-私有仅仅是封装的一种体现形式;私有只能修饰成员,不能修饰局部;类中不需要对外提供的内容都私有化,包括属性和行为,属性如private age,行为如private swap();隐藏后还需要提供访问方式:通过方法的方式,并可以在方法中加入逻辑判断-比如在setAge中加入对年龄值的判断,否则抛出异常;属性一律私有化,安全防止被编辑,并提供访问方法(一个属性对应两个方法)-规范写法(不是规定):赋值set和取值get,set变量名,get变量名-比如setAge, getAge;)

构造函数:

作用:

  • 为了对象一创建,就对其进行初始化,比如新来的学生一转来就有姓名年龄不用再赋值;

代码体现:

  • 没有返回值类型(因为构造对象创建完就结束,不需要结果,void也不写,区别于一般函数),名称与类型一致包括大小写,没有具体返回值;

构造函数的默认添加与自定义:

  • new 类名();中的()其实就是构造函数的调用,当自定义时,若有形参,调用时()中需要有对应的实参,new 类名(某实参)-则在对象创建过程中将实参传递给对象,而<类名.功能>则必须是对象创建完成后;

默认构造函数:

  • 只要定义一个类,该类中就默认有一个空参数的构造函数,是编译器编译时添加到class文件中的 ,如果自定义了构造函数,默认的空参数构造函数就不自动添加了,格式:类名(){},使用场景:看需求,若对象需要一初始化就需要具备一些内容,就写构造函数,否则不写,用默认,用set方法指定;

内存图解:

  • 开辟空间后属性值的初始化-两步-先默认初始化,后构造函数初始化;构造函数初始化前,先在栈中进行构造函数进栈,其中的形参没有默认初始化值,等待实参赋值,然后先从局部找赋值给的变量(比如例中的name),若有,结束,若没有,从对象找,哪个对象调用的,就赋值给那个对象;)
    (图解1和2)

注意点:3点

  • 1、一个类中可以有多个构造函数,以重载形式体现;(注:重载只与参数列表相关,与返回值类型无关!)
  • 2、构造函数中可以有return语句,用于结束或提前结束初始化功能;(判断if(age<0){return;} 直接结束,不做其他动作)
  • 3、构造函数可以被private私有修饰 <私有只能修饰成员,不能修饰局部>,作用:其他程序无法创建该类的对象,但在本类内可以创建。

构造函数与一般函数的区别

(目前:主函数,一般函数,构造函数)

  • 1、写法不同:没有返回值类型,但有return,函数名固定为与类名一致;
    (此条不重要)
  • 2、运行不同:构造函数是对象一创建就会调用,一般函数是对象创建后按需调用;
    (但仍需要set函数,因为可能需要修改,并且可能需要修改多次)
  • 3、调用次数:构造函数仅在对象创建时调用一次,而一般函数可以按需调用多次。
  • tips:

构造函数之间的调用

  • 用this关键字调用:
    • 在构造函数中调用其他构造函数的格式:this(实参列表);
    • 构造函数是对象初始化时调用,通过this关键字来记录对象的地址,并通过this来明确被初始化的对象;
    • 被对象调用的构造函数一旦进栈,就会有一个this关键字来存放调用这个函数的对象
  • 图解:

构造函数注意事项:

  • 1、构造函数可以调用一般函数,把动作作为初始化的一部分,而一般函数不能调用构造函数,对象初始化完毕之后才是一般函数主动登场;
  • 2、若构造函数中调用到了其他构造函数,则调用构造函数这条语句(如this)必须定义在构造函数的第一行(因为初始化动作先执行),否则编译报错;
  • 3、同一个构造函数中不能同时调用两个或以上构造函数(this要放在第一句);
  • 4、不要出现两个构造函数反复相互调用的情况(死递归-不是循环,一堆构造函数进栈,只进不出,栈内存溢出)。

this关键字:

区分同名变量:

  • 1、 方法同名-用参数列表区分,变量同名-就区别不出来了
  • 2、this关键字的另一个作用:标识成员变量
    • 可省略,但当局部变量和成员变量同名时必须用this标识出成员变量;
    • 若不标出,则函数调用时,先在局部找,当找不到时才去对象找,此时若局部变量和全局变量同名,则局部找到直接执行了。

应用:

  • 1、tips:
    • 结果为两种时-布尔型(对、错)- 比如“判断两人是否是同龄人 - 是或否”
    • 结果为三种时-int型(正、负、0)- 比如“两人比较年龄大小 - 大于,小于,相等”
  • 2、当函数中使用到了调用该函数的对象时,用this表示这个对象
    • eg:判断两人(Person对象)是否是同龄人-传另一个Person对象进来与已有Person对象比较 return pp.age == this.age;

static关键字

引入

  • 创建对象就是为了产生实例,并进行数据的封装
  • 而调用功能却没有用到这些对象中封装的数据
  • 运行能通过,但堆内存被浪费
  • eg. 创建对象,构造函数初始化一系列动作之后,仅调用了一个不涉及数据的sleep()功能,则堆内存被浪费。

修饰函数

  • static:成员修饰符,被修饰的方法除了可以被对象调用外,还可以被类名调用
  • 使用场景:定义功能时,如果功能不需要访问类中定义的成员变量(非静态)时,改功能就需要静态修饰。
    (谁访问了成员变量,谁就是非静态)

静态方法的使用注意事项

  • 1、静态方法不能访问非静态的成员,而非静态可以访问静态成员。
    • 静态的弊端:访问出现局限性
    • 静态的好处:可以直接被类名调用
  • 2、静态方法中不允许出现this,super关键字
    • 静态是随着类的加载而加载,随着类的消失而消失
    • 静态优先于对象存在,被对象共享(静态不要用对象调用,要用类名调用,这样比较专业)
    • 静态先存在于内存中,无法访问后来的对象中的数据,所以静态无法访问非静态,而且内部无法书写this
    • 因为这时对象有可能不存在,this没有任何指向

main函数是静态的

  • test:

    • 设功能show()不写静态,当要在同类的main函数中调用时报错:无法从静态上下文中访问非静态变量
    • 静态的调用:对象调用和类名调用
    • 写在主函数中的show()其实是类名调用:类名.show(); 本类中,省略了类名
    • 此时只能对象调用:new 类名().show()
  • 主函数揭秘:public static void main(String[] args)

    • 主函数各个值的解释:

      • public:权限最大
      • static:不需要对象,之家用给定的类名就可以访问该函数了
      • void:不需要返回值(不需要返回什么东西给虚拟机)
      • main:函数名。该名称是固定的,但是不是关键字(关键字不作为名字存在)
      • (String[] args):主函数的参数列表,表示字符串数组类型的参数
      • args:arguments-参数简写,就是一个变量名,这是主函数里唯一能改变的
    • 打印args:[Ljava.lang.String;@1db9742

      • [:数组类型
      • String:数组中元素的类型--对象类型
      • @:分隔符
      • 1db9742:对象的哈希值,通过哈希算法得到的一个数值
      • 结论:根据这个结果可知,JVM传递了一个字符串类型的数组实体
    • 打印args.length:0

      • 长度为0,说明没有元素
      • 结论:JVM传递的是new String[0]
    • 打印args[0]:报错:脚标越界

    • 命令行输入java 类名 haha xixi hehe(后三个为要传递的值)之后再打印args[0]:haha

      • 最早从外界给主函数传值就是这种方法,非常不常用,只做了解。
      • 现在图形界面化了。

静态变量

  • 静态是成员修饰符,只能修饰成员
  • 使用场景:当该成员变量的值,每一个独享都一致时,就对该成员变量进行静态修饰。
  • eg:计算圆的面积要用到pi,每个圆对象中都存储一份pi有点浪费空间,加静态关键字修饰,实现对象共享。
    return radius*radius*pi(完整为类名.pi,可略写)

静态变量和成员变量的区别

  • 1、所属范围不同

    • 静态变量所属于类,成员变量所属于对象
    • 静态变量也称为类变量,成员变量也称为实例变量(或对象变量)
  • 2、调用不同

    • 静态变量可以被对象和类调用(一般都是类名调用)
    • 成员变量只能被对象调用
  • 3、加载时期不同

    • 静态变量随着类的加载而加载
    • 成员变量随着对象的加载而加载
  • 4、内存存储区域不同

    • 静态变量存储在方法区(数据共享区)中
    • 成员变量存储在堆内存中
  • 小结

    • 静态变量--属于类--存在方法区--可被类和对象调用
    • 成员变量--属于对象--存在堆中--只能被对象调用

静态加载的内存图解

(02-staticMemory1-4)



静态代码块

<面试相关,开发中不常用的小技巧>

  • 需求:类一加载,需要做一些动作,不一定需要对象
  • 学习目标:了解加载顺序
  • 静态代码块:
    • 特点:随着类的加载而加载,并且仅执行一次
    • 作用:给类进行初始化(优于对象先做)
  • 静态代码块与静态变量:
    • 静态变量有两次初始化,一次默认初始化,一次显示初始化
    • 静态代码块在静态变量初始化后执行的(因为静态代码块有可能用到这个静态变量)

构造代码块&局部代码块

<开发中很少用,甚至很少见>

  • 相同点:

    • 构造代码块和局部代码块都是“没有名字,只有大括号”
  • 不同点:

    • 构造代码块放在类里,局部代码块放在方法里
  • 构造代码块:

    • 给所有对象初始化,只要创建对象就会被调用,不创建对象不调用
    • 因此这里可以定义不同构造函数的共性代码
    • ps:构造函数只给对应的对象针对性地初始化
    • 执行顺序:
      • 静态先执行(静态优先于对象),构造代码块后执行(有对象才能有它)
      • 构造代码块在成员变量显示初始化之后执行(成员变量:默认初始化 --> 显示初始化)
  • 局部代码块:

    • 作用:控制局部变量的生命周期,一旦执行玩,其中的局部变量直接释放内存,后面再用会显示找不到
    • 关注内存使用占用情况的老外用的多,而实际开发中用得很少

对象的创建过程

<重要!!!!! 面试会问!!!!!! 涉及执行时的各种先后顺序,多看多记>

执行顺序分析

  • 有静态,静态优先:

    • 静态变量先进行两次初始化
    • 静态代码块有类即执行
  • 构造函数:

    • 父类super()
    • 成员变量初始化
    • 有构造代码块时,执行
    • 最后执行构造函数自定义的初始化
  • 对比:

    • 静态:

      • 类加载即执行,先静态变量两次初始化,后静态代码块执行,仅一次
    • 构造:

      • 对象创建即执行,构造代码块先,构造函数后,构造代码块每创建对象都执行
      • 对象创建后,先对象中的属性默认初始化,后构造函数初始化(父类--成员变量显示初始化--构造代码块初始化--其他自定义初始化)
    • 小结:

      • 先变量,后块,最后函数

图解:

  • 包含多个图
    (createObjMemory1-4)



单例模式

单例模式的场景

  • 设计模式:

    • 解决某一类问题的行之有效的解决办法(思想)
    • 软件行业:23种主流设计模式(来源于建筑行业,设计好的模式、模板之类)
  • 单例(Singleton)设计模式:

    • 学习设计模式必须先弄清楚它是解决什么问题的
    • 单例解决的问题:可以保证一个类的对象的唯一性
    • 综上:单例--单独的实例,单独的对象
  • 使用场景:

    • 比如多个程序都要使用同一个配置文件中的数据,并且要实现数据的共享和交换
    • 必须将多个数据封装到一个对象中,而且多个程序操作的是同一个对象
    • 即,必须保证这个配置文件的唯一性。

单例模式的代码体现:

  • 思路与步骤分析:

    • 一个类只要提供了构造函数,就可以产生多个对象,无法保证唯一
      故:不让其他程序建立对象,即私有化构造函数。
    • 若不让其他程序创建对象,对象何在?
      故:自己在本类中创建一个对象,好处是可控。
    • 创建完成后,需要给其他程序提供访问的方式。
  • 代码分析:

    • 首先:获取实例的功能一般定义为:getInstance()
    • 1、调用getInstance()时:不能通过对象调用,只能用类名调用,故这个方法是静态的
    • 2、因为getInstance()是静态的,则其中涉及到的对象变量也必须是静态的
    • 3、如果拿不到getInstance()这个方法,则类中的东西都不能用,因此getInstance()方法是public的
    • 4、将存储对象的变量私有,并提供另外的访问方法,而不是直接调用存储对象的变量,道理同set变量名、get变量名等方法的设置,安全可控,不对外提供。

单例模式的体现

  • 饿汉式:

    • 存储对象的变量:默认初始化为空null,显示初始化为创建对象new 对象();,类一加载,堆里就有对象
    • 开发常用:就是要用这个对象,无所谓先加载后加载;如果不是要用,就不写这个类
  • 懒汉式:

    • 单例的延迟加载方式
    • 存储对象的变量:设为空null,当调用getInstance()方法时,进行判定变量是否为空并在为空时设置为new 对象();,即只有调用获取对象的方法时才创建,不浪费内存
    • 面试常见
  • 其他:若面试问“第三种是什么?”

    • 答:论写法,不止三种,内部类的加载方式,集合的加载方式,请问您指的是哪一种
    • (很可能说的是集合这种,即可以进行单例模式的子类扩展。但非常不常见。)

单例模式的应用:

  • 描述超人
  • 超人对象应该是唯一的,为了保证超人对象的唯一性,可使用单例模式解决。

继承

概述

  • 多个类中存在相同属性和行为时,将这些内容抽取到一个单独的类中,其他多个类无需再定义这些属性和行为,只需继承即可。
  • 多个类称为子类,单独的类称为父类或基类或超类
  • 子类可以直接访问父类中的非私有的属性和行为
  • 通过extends关键字连接父类和子类: class SubDemo extends Demoo
  • 好处:

    • 提高了代码的复用性
    • 让类与类之间产生了关系,给另一个特征“多态”提供了前提(没继承,没多态)
  • 使用场景:

    • 类与类之间存在所属(is a)关系时,即XX是YY中的一种(苹果是水果的一种)

单继承&多继承&多重继承

  • 单继承和多继承

    • 单继承:一个子类只能有一个父类
    • 多继承:一个子类可以有多个父类
  • java允许单继承,不允许多继承

    • 当多个父类具有相同功能且里面的内容不一时,产生了不确定性,java不允许不确定性
    • java进行了改进,保留了多继承的好处,改良了弊端,多<多实现>来做多继承类似功能
  • 多重继承:

    • 多重继承即链式继承,类似于:A是B的父类,B是C的父类,或类似二叉树的树形结构,构成继承体系
    • 学习继承体系:看顶层,建底层 <学习其他体系同此口诀>

      • 即参阅顶层的类中的内容,了解这个体系的基本功能(共性功能),使用这个体系时,再深入理解最子类的功能,并创建最子类的对象

子父类中成员变量的特点 -- super

  • 继承出现后,在代码中的体现,重点在于成员的体现,包括:成员变量、成员函数、构造函数。

  • 针对“成员变量”的一种特殊情况(注:开发中不写,父类中有的,子类不再重新定义,但是面试会问到):

    • 子父类中定义了一模一样的成员变量,都存在于子类对象中
    • 如何在子类中直接访问同名的父类中的变量呢?通过super关键字。
    • 代码体现:本类:this.num,父类:super.num
  • 内存图解

(图名:ziFuMemory1)

  • tips:
    • 相同时的顺序:先局部后成员,在成员中先找自己的,没有再找父类有没有。
    • 如图,new只有一个zi类对象,故只有一个子类对象,其父类的成员也存放在子类中
    • 父类成员加静态修饰:改变所在空间了(静态进静态代码区)
      • 子类不能直接访问父类的私有内容,但可以间接访问,属性私有后一般会提供set、get等访问接口
    • 父类成员加私有修饰:不改变,只是加一个标记表示权限降低,仍在子类的堆内存中
    • 方法区:先加载父类后子类;栈内存:子类先进栈后出栈,父类后进栈先出栈
  • super和this的异同:

    • 相同点:
      this -- 代表本类的对象的引用
      super -- 代表父类的内存空间
    • 不同点:
      this -- 可以直接用在语句中:
      • eg. 打印语句(this) -- 输出对象的类型和哈希值
        super -- 不可以直接用,super单独存在没有意义,并不代表对象,只代表父类那片空间:
      • eg. 打印语句(super) -- 报错:"缺." -- super必须带着.或()使用

子父类中成员函数的特点 -- 重写

  • 重写的介绍:

    • 重写:override,又叫复写,覆盖(注:前面学过函数的另一个特性:重载overlord)
    • 描述:在子类中包含了一个与父类中所含函数同名但内容不同的函数时,调用时执行子类中的函数而覆盖了父类中的函数。
    • 注:方法区中,子类空间中包含一个父类的引用super,指向自己的父类
      重名时:惯例,先找自己空间,有则执行,没有时,再找父类空间
  • 重写的应用:

    • 案例分析:

      随着电话升级,其中的“来电显示”功能从老版的只显示电话号码升级为显示电话号码、姓名和大头贴
    • 直接修改源码的缺点:费劲,但可以解决。但是不建议,不利于后期的维护和拓展
    • 解决:单独描述单独封装,并且由于新电话也是老电话的一种,故可直接通过继承获取父类中的内容
    • 重新定义“新来电显示”的功能(newshow)不合适:这样子类中就有重复的两个功能了,算是代码设计出了问题
    • 若需要保留父类中原有的部分功能,可通过super调用(void show{ super.show(); ... ... }
  • 重写的注意事项:

    • 1、子类覆盖父类,必须保证全要大于或等于父类的权限(权限目前包括:public,private,默认)
      • 子父类权限相同:可以
        • 子默认,父public:不可以,public权限最大,而子类为默认,反而变低了,不行
        • 子public,父默认:可以,子大于父
        • 子默认,父private:子大于父,但父私有,子类访问不到了
          <私有private不列在覆盖范围之内!!>
      • 2、静态覆盖静态:子父类中只要有一个是静态,则子父类都需要是静态才行
      • 3、写法注意:必须一模一样,包括函数的返回值类型、函数名、参数列表都要相同
        • 设子父类的其他都相同,返回值不同,则不行
        • 函数名和参数列表都相同,仅返回值类型不同的函数放在同一个class中:不行,产生了调用的不确定性
          • 注:不算重载,因为重载与返回值类型无关,只看参数列表

子父类中构造函数的特点 -- super

  • 当子父类中都有构造函数时:先执行父类的构造函数,后执行子类的构造函数

    • 因为子类的所有构造函数中的第一行都有一句隐式的super(); -- 默认调用父类中的空参数的构造函数
    • super存在的原因:
      • 子类继承父类中的内容,因此子类在初始化时,必须先到父类中去执行<父类中的初始化动作>,才可以更方便地使用父类中的内容。
  • 当父类中没有空参数构造函数时:

    • 子类的构造函数必须通过显示的super语句指定要访问的父类中的构造函数
    • 子类的super(n) -- 小括号中放了什么,子类就向父类的构造函数传递了什么参数
    • 这就是<子类实例化过程>
  • 示例:

    • 同存在空参和有参的子父类且没有定义super时,分别new不同的子类对象时:
      (注:以下为伪代码)
      Fu(){ 输出语句(A); }
      Fu(int x){ 输出语句(B+x); }
      Zi(){ 输出语句(C); }
      Zi(int x){ 输出语句(D+x); }
      new Zi()
      new Zi(6)
    • 结果:ACAD6 -- 子类中只有默认的空参super()
  • 注意点:

    • 1、当子类的构造函数第一行写了this()调用本类其他构造函数时,默认的super()就没有了

      • this或super:都是只能定义在构造函数的第一行,因为初始化动作要先执行
      • 子类中无论写了多少this()调用,要至少有一个走父类
    • 2、父类构造函数中是否有隐式super?有的

      • 注意:只要是构造函数,默认第一行都是super() -- 走父类是必然!
      • Object:

        • java中定义的所有对象的父类,是类层次结构中的根类,虚拟机启动就在了,这里面也有构造函数,但它没有父类
          • 子父类中,Zi()不直接继承Object(),是按继承顺序走的:子-->父-->Object
            (不能对着爷爷喊爸,会乱套的)
  • 总结:

    • 类中的构造函数默认第一行都有隐式的super()语句,在访问父类中的构造函数
      因此,父类的构造函数既可以给自己的对象初始化,也可以给自己的子类的对象初始化
    • 如果默认的隐式super语句没有对应的构造函数,则必须在构造函数中通过this或者super的形式明确调用的构造函数
    • this和super不能在同一个构造函数中出现,因为都需要定义在第一行。初始化动作先执行。
  • super的应用(即子类实例化过程的应用,开发中使用频繁)

    • 使用super调用父类中的构造函数的场景:
      只要使用父类的指定初始化动作,就在子类中通过super(参数列表)的格式进行调用。

final关键字

引入:

  • 针对继承的弊端:打破封装性

  • 针对此弊端,可以不让其他类继承该类,就不会重写
  • 引入关键字final

final关键字的介绍:

  • 一个修饰符:作为修饰符,放在其他前,修饰符位置中,放在权限修饰符之后
  • 可以修饰类、方法、变量(成员变量、局部变量、静态变量)

特点:

  • 1、final修饰的类的一个最终类,不能再派生子类

    如果类中出现部分可以重写,部分不可以:只要让指定的方法最终化即可
  • 2、final修饰符的方法是最终方法,不可以重写

  • 3、final修饰的变量是一个常量,只能被赋值一次

    • 把程序中不变的数据都写成final,并起一个看上去阅读性很强的名字,这样比较专业,增强阅读性
    • 书写规范:被final修饰的常量名的所有字母都大写。多单词时,单词间用下划线连接
    • 若final修饰的变量没有显示初始化,则报错:可能尚未初始化变量xx
    • final修饰后的变量,其实变成了常量,会进常量池;
      为了访问方便,可以加一个静态修饰符,这样用类名即可访问

抽象类

引入:

  • 示例:

    • 狗-吼叫行为,狼-吼叫行为,发现共性,可以向上抽取为犬科的吼叫行为
    • 问题:虽然都是吼叫行为,但具体的叫的内容是不一样的,是不确定的
    • 在类中标识出这种不确定性,用关键字abstract(抽象)
  • 抽象类:

    • 在描述事物时,没有足够的信息描述一个事物,这时该事物就是抽象事物。
    • 具体类继承抽象类:比如狼类和狗类是犬科的子类
    • 抽象本身就是继承的一种延伸,又有不同,不断向上抽取最终抽取到了不具体的内容

特点:

  • 1、抽象类和抽象方法都需要被abstract修饰

    抽象方法一定要定义在抽象类中
  • 2、抽象类不可以创建实例。(即不能用new创建对象)

    原因:调用抽象方法没有意义
  • 3、只有覆盖(重写)了抽象类中所有的抽象方法后,其子类才可以实例化,否则该子类还是一个抽象类

  • ps:之所以继承,更多的是在思想,是面对共性类型操作会更简单。

细节问题:

  • 1、抽象类一定是个父类

    • 因为是不断向上抽取而来的,抽象类只有最终被使用,才有意义。而抽象类本身不能创建实例,只有写了子类,拿来用,才有意义。
  • 2、抽象类中有构造函数

    • 虽然不能给自己的对象初始化,但可以给自己的子类对象初始化
    • 抽象类和一般类的异同点:

      • 相同点:
        1、它们都可以用来描述事物
        2、它们之中都可以定义属性和行为
      • 不同点:
        1、 一般类可以具体地描述事物,抽象类描述事物的信息不具体
        2、抽象类中可以定义一个成员:抽象函数
        3、一般类可以创建对象,而抽象类不能创建对象
  • 3、抽象类中可以不定义抽象方法

    • 不定义时这个抽象类的意义:仅仅是不让该类创建对象
  • 4、抽象关键字不可以与之共存的关键字:final,private,static(非法的修饰符组合)

    • final:final修饰完的类是最终类,没有子类,而抽象类必须有子类,冲突了
    • private:抽象方法需要被覆盖,加私有后,子类无法使用
    • static:静态随着类的加载而加载,直接能被类名调用,而被抽象类名直接调用没有意义
  • 注:abstract可以修饰类和方法,但不能修饰变量。

练习

  • 描述:

    • 程序员和项目经理(属性和行为)
    • 抽取共性得到父类--雇员类
    • 工作行为不确定,向上抽取为抽象方法
    • 调用时:父类有的直接拿来用,父类没有的自己定义一下
  • 注:

    • 抽象类看似鸡肋,但不写在父类中,意味着该方法不是共有的,而是子类特有的,是父类不具备的功能,与事实不符。
    • 出发点:不是代码,是先描述事物。事物该有什么,就得有什么。至于父类做不了的,由子类来具体做即可。
    • 例如:老板不关心具体工作内容,只需指挥:员工工作,即可,否则老板很头大

接口

介绍-定义&实现:

  • 引入:

    • 当一个抽象类中的方法都是抽象方法时,通过另一种特殊形式--接口--来体现
    • 接口是一种特殊的抽象类:例如interface Demo{}编译后产生的文件仍然是Demo.class这样的类文件
  • 接口的定义--先介绍两种

    • 1、定义变量:

      • 变量必须有固定的修饰符public static final修饰,故接口中的变量也称为常量(则每个字母大写)
    • 2、定义方法:

      • 方法有固定的修饰符public abstract -- 不抽象的方法在接口里定义不了
    • 两组修饰符public static finalpublic abstract不写可以吗?

      • 只要是接口中的,这两组修饰符都是固定的,如果不写,编译器会帮你加上
      • 实际开发可以不写
      • 最好写上,比如public,如果不写,子类重写时容易忘记写,则导致权限不够
  • 接口的特点:

    • 1、接口不可以创建类(都有抽象方法)
    • 2、子类必须覆盖掉接口中所有的抽象方法后,才可以实例化,否则子类是一个抽象类
  • 接口的子类:

    • 类与类的关系是继承(已经写好,拿来用即可)
      类与接口的关系是实现(抽象,没写好,需要子类重写才能用)
    • 类与接口的关系:通过关键字implements标识
      接口:用来被“实现”的

多实现

  • 接口最重要的体现:多实现

    • 解决多继承的弊端,将多继承这种机制在java中通过多实现完成
    • 多实现:实现多个接口
  • 如何解决多继承弊端:

    • 多继承弊端:当多个父类中有相同功能时,子类调用会产生不确定性
    • 核心原因:多继承父类中,功能有主体,而导致调用运行时,不确定运行哪个主体
    • 多实现:接口中的功能都没有方法体,由子类来明确
    • 示例:当接口A和B具有相同功能,类C implements A,B -- 具体功能在子类中定义
      (但一般不这么写,两个功能相同,两个功能都继承,非常多余)
  • 注意点:关于既然是子类来具体实现,那么implements接口的意义何在问题的思考

    • 不写,编译运行没问题。但过分关注代码了。
    • 接口描述事物,而子类想具备已有功能,只要和接口产生关系即可,没必要自己再琢磨如何定义该功能了,只要建立该功能的内容即可
    • 产生关系是很重要的:并且后面的多态中各种关系也是很重要的

继承同时多实现

  • 基于接口的扩展

    • 子类通过继承父类扩展功能,通过继承扩展的功能都是子类应该具备的基础功能
    • 如果子类想要继续扩展其他类中的功能呢?通过实现接口来完成。
    • 代码体现:class Zi extends implements Inter{... ...}
  • 接口的出现避免了单继承的局限性

    • 父类中定义的是事物的基本功能
    • 接口中定义的是事物的扩展功能

接口的多继承(不常见,仅作了解)

  • 接口出现后的一些细节:

    • 类与类之间:继承关系(is a)
    • 类与接口之间:实现关系(like a)
    • 接口与接口之间:继承关系,并且可以多继承
    • 故:可以说,java支持多继承,即接口的多继承
  • 接口的多继承:

    • 代码体现:interface Inter B extends InterA, InterAA{ ... ... }
    • 实现Inter B接口的类需要复写父接口及其父接口中的所有方法
  • 思想:面向对象-->面向接口-->面向框架

    • 面向接口:不管具体,只面对抽象
    • 面向框架:只面对封装成的框架
    • 关注的东西越来越向上,越来越简单(简单:细节越少越简单)

没有抽象方法的抽象类

  • 遗留问题:抽象类总是否可以不定义抽象类?

    • 可以。原因仅是不让该类创建对象
  • 没有抽象方法的抽象类的意义详解:

    • 问题:

      • 当接口中有多个方法,而实现该接口的一个子类仅需要用到其中一个方法
      • 虽然如此,但必须覆盖全部的方法才能使用否则自身是抽象类,无法实例化。
      • 这时可以将剩余的方法覆盖为有实体没有内容的空方法 eg. public void show(){}
      • 行得通,但是代码复用性差
    • 解决:

      • 为提高复用性,可以将重复的代码抽取封装,即将不用的方法单独抽取到一个独立的类中
      • 因此可以定义一个类,让这个类去实现接口,并覆盖接口中农的所有方法,而子类直接继承这个独立的类即可。
    • tips:

      • 这个独立的类并不知道这些方法的具体实现内容,所以只能为了后期子类创建对象方便,进行空实现
      • 这时,这个类创建对象是没有意义的。不需要创建对象,故将其抽象化
      • 这就是没有抽象方法的抽象类(AWT体系中用到了)

接口的思想

  • 举例:笔记本电脑和USB接口的故事

    • 1、接口的出现对功能是实现了扩展
      (不知道以后会用到什么设备,但只要接口符合,就可以用)
    • 2、接口的出现定义了规则(想要被笔记本用,设备需要符合其规则)
    • 3、接口的出现降低了耦合性(解耦)
      (耦合性:紧密联系程度,依赖关系,耦合性越强,独立性越差)
      (降低耦合性,可提高其独立性)
      (划分模块:高内聚低耦合)
  • 接口的出现完成了解释,说明有两方,一方在使用这个规则,一方在实现这个规则

    • 比如笔记本电脑使用这个规则,而外围设备在实现这个规则

接口和抽象类的区别

  • 示例:缉毒与犬与缉毒犬

    • 缉毒犬既需要犬的功能,又需要缉毒的功能,无法直接多继承
    • 可以多实现:犬和缉毒都是接口,缉毒犬多实现
    • 注意:
      • 类负责描述事物的基本功能,接口负责描述事物的扩展功能
      • 缉毒犬是犬中的一种,是is a关系,可以将犬定义为类
      • 缉毒是犬的一个扩展功能,可以将缉毒定义为接口
    • 提高复用性 不等于 “少写代码”
      • 事先定义好以后,虽然用的时候再具体定义,但是仍然提高了复用性
      • 定义好以后,只面对接口,不面对细节
  • 总结:

    • 抽象类是描述事物的基本功能,可以定义非抽象的方法
      接口是描述事物的扩展功能,且只能定义抽象方法
    • 类与类之间是继承关系,is a关系
      类与接口之间是实现关系,like a关系

多态

好处&弊端&前提

  • 示例:

    • 猫-吃,捉老鼠,狗-吃,看家,向上抽取为:"动物-吃"的父类
  • 体现:

    • 父类的引用或者接口的引用指向了自己的子类对象
    • Dog d = new Dog(); //Dog对象的类型是Dog类型
    • Animal a = new Dog(); //Dog对象的类型:右边是Dog类型,左边是Animal类型
  • 好处

    • 提高了程序的扩展性
  • 弊端

    • 通过父类引用操作子类对象时,只能使用父类中已有的方法,不能操作子类特有的方法
  • 前提:

    • 1、必须有关系:继承,实现
    • 2、通常都有重写操作

转型

  • 转型:转变型态

    • 当父类型引用指向子类对象时,就是让子类对象进行了类型的提升(向上转型)Dog-->Animal
    • 向上转型的好处:提高了扩展性,隐藏了子类型
    • 向上转型的弊端:不能使用子类型的特有方法
      • 若想使用子类的特有方法,可以向下转型--强制转换
  • 调用子类的特有方法--向下转型

    • Animal a = new Dog(); -- Animal父类型,new Dog()是子对象
    • Dog d = (Dog) a -- 将a转型为Dog类型,即向下转型
    • 向下转型的使用场景:当需要使用子类型的特有内容时
    • 注:无论是向上转型还是向下转型,最终都是子类对象做着类型的变化
      (父类是抽象类,也new不出来)
  • 向下转型的注意事项:

    • 向下转型因为不确定具体子类对象类型,所以容易引发“ClassCastExcept”--类型转换异常
    • 为避免这个问题,需要在向下转型前,做类型的判断(必须做!)-- 为了程序的健壮性
    • 判断类型:使用关键字instanceof,表示“是否是所属于谁的实例”
      代码体现:if(a instanceof Cat){} -- 表示判断a指向的对象的类型是否是Cat类型
  • 转型总结:

    • 1、什么时候使用向上转型?

      • 提高程序的扩展性,不关心子类型(子类型被隐藏)
      • 需要用子类的特有方法吗:如果不需要,那么向上转型
    • 2、什么时候使用向下转型?

      • 需要使用子类型的特有方法时
      • 但一定要使用instanceof进行类型判断,避免发生“ClassCastExcept”

关于“笔记本电脑外接设备”的练习

  • 问题:

    • 每次添加外接设备就改动源码,这个扩展性是非常差的,耦合性过高
  • 设计上的改进:

    • 事先定义好一些规则(契约),笔记本只要使用这些规则就行
    • 外接设备只要符合特定规则,就可以被笔记本所用了
  • 规则在java中的体现:接口(即,接口就是规则)

    • 1、描述接口 interface USB{}
    • 2、描述笔记本电脑:运行功能,使用接口功能
      class NoteBook{ public void run(){} public void useUSB(USB usb){} }
      接口类型的变量(三个引用型变量之一),接口类型的变量指向自己的子类对象
    • 3、描述鼠标、描述键盘:implements USB并在自己的类中重写USB的功能即可
  • 注:另有“反射技术”:鼠标一放,笔记本一运行,自己把鼠标加进去的技术

多态的成员调用的特点(面试多见!!!)

  • 1、成员变量的特点:编译和运行都参考左边--编译运行看左边

    • 当子父类中出现同名的成员变量时,多态调用该变量时:Fu f = new Zi(); 输出语句(f.num);
      • 编译时期:参考引用型变量所属的类中是否有被调用的成员变量,若没有,编译失败(注:父类没有,而子类有时,仍然编译失败)
      • 运行时期:调用引用型变量所属的类中的成员变量
  • 2、成员函数的特点:编译看左边,运行看右边

    • 当子父类中出现同名的成员函数时
      • 编译:参考左边,如果没有,编译报错(父类没有时,编译失败)(注:编译时还没有对象)
      • 运行:参考右边的对象所属的类 -- 当子类中没有时,运行父类中的函数
  • 3、静态函数的特点:编译运行看左边

    • 静态函数是静态地绑定到类上的(和对象无关,甚至不存在对象也不影响)
    • 成员函数是动态地绑定到对象上的(谁调用,指向谁)
    • 真正开发时,静态方法是不会被多态调用的,因为静态方法不属于对象,而是属于类,用类名调用即可。
  • 总结:

    • 对于成员变量和静态函数,编译和运行都看左边
    • 对于成员函数,编译看左边,运行看右边

Object

Object与API

  • Object类是所有类的根类,定义了所有对象都具备的功能

  • API:应用程序接口(Application Programming Interface)

    • API文档的使用
  • java源码的查看:

    • 安装路径的jdk文件夹下的src.zip文件是源文件,先找到所在包,里面会有对应的java文件
    • 注:类定义中有native:java有五片区域,栈 堆 方法区 寄存器 本地方法区,native就是在本地方法区中

equals方法

  • 源码的代码体现:

    public boolean equals(Object obj){ return (this == obj) }
  • java定义的equals方法:判断的是地址

  • 当需要判断其他相等的功能时,重写该方法

  • 代码体现:假设需要判断Person类的年龄age是否相等

    • 1、首先判断调用该方法的对象和传递进来的对象是否是同一个
      if(this == obj){return true;}
    • 2、age是Person类的属性,父类Object是不具备的,需要向下转型,向下转型都需要做判断
      if(!(obj instanceof Person)){(抛出异常)}
      否则进行向下转型
      Person p = (Person)obj;
    • 3、返回结果
      return this.age = p.age;

toString方法

  • 源码定义的toString方法:

    • 输出“对象类型+@+哈希值”
    • print方法打印的数据都是字符串,toString方法自动加上
  • API文档建议所以子类都重写此方法,以按照需要的方式输出

内部类

体现

  • 内部类(或称内置类,嵌套类)

    • 当A类总的内容要被B类直接访问,而A类还需要创建B的对象,访问B的内容时
      这时可以将B类定义到A类的内部,这样访问更为便捷。
      B类称为内部类
  • 访问方式:

    • 内部类可以直接访问外部类中的所有成员,包括私有的
    • 外部类要想访问内部类中的成员,必须先创建内部类的对象。
  • java文件编译产生的类文件注意:

    • java代码:class Outer{ class Inner{ ... } } class InnerClassDemo{ ... }
    • 则编译产生三个类文件:
      • InnerClassDemo.class
      • Outer$Inner.class -- Inner一旦作为内部类,就有了所属,需要标明,因为别的类中也可能定义同名内部类
        • 注:使用$连接符而不是句点连接符:避免和文件后缀的句点混淆造成歧义。
      • Outer.class

作为成员时可以用的修饰符

  • 内部类被访问的方式-情况一:内部类在成员位置上的被访问方式

    • 成员被指定修饰符所修饰可分三种情况 -- public,private,static
    • 不定义修饰符时:只调用外部类时,内部类不进内存,需要访问时再new
  • public:不多见,因为更多时候,内部类已经被封装到外部类中,不直接对外提供(面试用)

  • private:供内部类使用,比较多见

  • static:分多种情况讨论

    • 测试情况一:直接访问Outer类中的Inner内部类的非静态成员(即内外类均非静态时)
      • 创建内部类的对象即可。
      • 内部类作为成员,应该先有外部类对象,再有内部类对象
      • 代码体现:两个new,开发少见,面试多见 Outer.Inner in = new Outer().new Innter();
    • 测试情况二:对静态内部类中的非静态成员进行调用
      • 因为内部类是静态,所以不需要创建Outer对象,直接创建内部类对象即可
      • 代码体现:一个new Outer.Inner2 in = new Outer.Inner2();
    • 测试情况三:访问静态内部类中的静态成员时
      • 静态内部类随着外部类而加载,而其中的静态成员也随着类的加载而加载,故不需要对象,直接类名调用即可。
      • 代码体现: Outer.Inner2.staticShow();
  • tips:

    • 内部类被静态修饰后,随着外部类的加载而加载(class),可以把一个静态内部类理解为一个外部类
    • 非静态内部类中不允许定义静态成员,仅允许在非静态内部类中定义静态常量(static final)
    • 需要在内部类中定义静态变量时,必须内部类也要被静态修饰

内部类访问外部类的原因

  • 内部类中的方法分别访问处于不同位置的变量num时

    • 当外部类成员变量、内部类成员变量、内部类中的方法中的局部变量定义了同名且不同值的变量num时:
    • num:访问最近的,局部有,就访问局部
    • this.num:this用于区分局部和成员,指向调用该方法的对象,即访问内部类中的成员变量
    • Outer.this.num:明确了哪个类,即访问外部类中的成员变量
  • 内部类访问外部类的原因(原理)

    • 为什么内部类能直接访问外部类中的成员?
    • 因为内部类其实持有了外部类的引用 “外部类.this”
    • 静态内部类中不持有“外部类.this”,而是直接使用“外部类名”
  • tips:

    • 默认包含“类名.this.变量名”,不歧义时省略

局部内部类的特点

  • 局部内部类:定义在局部的内部类

    • 示例:定义在函数中的内部类 void method(){ ... class Inner{....} ... }
  • 局部内部类编译结果:$后加数字

    • 示例:Outer$1Inner.class
    • $后加类名:内部类在成员位置上
    • $后加数字再加类名:内部类定义在局部位置上
  • 内部类定义在局部时,只能访问被final修饰的局部变量

    • 因为编译生成的class中直接操作那个最终数值了
    • 不能访问非最终的局部变量是因为生命周期太短了,有可能需要访问时已经从内存弹出了
    • (老师没讲,私以为,final修饰后进常量区,没有final修饰后局部一结束就弹了)
    • (但是没加final没有报错,查了其他资料,也说需要加,不知道是java8升级了还是怎么回事)

内部类的继承或者实现

  • 内部类的名字比较特殊:

    • 看API文档时,带句点“.”,说明是内部类或者内部接口
    • 类不能被静态修饰,但是内部类可以(当定义在成员位置时)
    • 内部接口:接口里定义接口,生成的也是xxx$xxx.class文件
  • 内部类的延伸:

    • 内部类可以继承或者实现外部其他的类或者接口。

内部类对象对外提供功能的访问方式

  • 内部类的延伸(这部分听得云里雾里的)

    • 好处:通过内部类的方式对类进行继承重写,或者接口进行实现。
    • 通过公共的方式对其内部类对象进行访问。因为通常内部类很有可能被外部类封装其中。
    • 我们就可以通过父类或者接口的方式访问到内部类的对象。

匿名内部类

  • 匿名内部类的定义:

    • 匿名内部类:其实就是一个带有内容的子类对象(提到匿名内部类,必为子类对象)
    • 格式:new 父类or接口(){子类的内容},调用功能直接后接“.function();”
    • 匿名内部类就是内部类的简化形式
    • 匿名内部类的前提:内部类必须要继承父类或者实现接口。
    • 注意:父类有很多功能时,不建议匿名,代码阅读性会非常差。
  • 匿名内部类的使用

    • 匿名内部类中包含多个方法时,使用多态,将创建的匿名内部类赋值给父类型的变量保存以便可以调用不同方法
    • 代码体现:接口Inter的内部类 -- 父类或接口 变量名 = new 父类或接口(){ .... };
    • 注意:匿名内部类中的方法不要过多,否则阅读性很差。超过两个时,不建议写为匿名内部类。
  • 匿名内部类的练习

    • 代码补充时根据主函数调用语句推测:

      • Outer.method().show();
      • Outer.method():Outer类总有一个method方法,这个方法是静态的
      • Outer.method().show():能调用show方法的必然是对象(非静态只能被对象调用),说明method方法运算完应该返回一个对象。能调用Inter中的show方法,说明这个对象的类型是Inter
    • 面试题示例的核心考查点:匿名内部类必然是子类对象

      • 对比两个匿名内部类,一个用经典格式,另一个起了名字赋值给父类型的变量
      • 一个编译通过,一个编译失败
      • 注意点:匿名内部类都是子类对象,当赋值给父类型的变量时,涉及到多态中的类型提升,此时该子类对象提升为父类型,编译看左边,当父类型中没有调用的方法时,编译报错

END

posted @ 2019-06-28 21:20  anliux  阅读(1103)  评论(0编辑  收藏  举报