【Java学习笔记(二)】类与对象&面向对象的三大特点

如何理解面向对象程序设计

两种基本程序思想

  • 面向过程程序设计(POP)
  • 面向对象程序设计(OOP)

面向对象程序设计的基本特征

  • 封装性
  • 继承性
  • 多态性

两种编程基本思想的比较

  • POP
    • 该怎么做(How to do?)
    • 程序=算法+数据结构
    • 自上而下,步步求精,以过程为中心,以算法为驱动
  • OOP
    • 该让谁来做(What to do?)
    • 程序=对象+消息传递=(数据+方法)+消息传递

类的简介

  • 将具有相同属性以及相同行为的一组对象成为类(Class);类是组成Java的基本要素,它封装了一类对象的状态和方法;

类的定义

 [标识符] class 类名称
{
零到多个构造器(构造方法)定义..
零到多个成员变量..
零到多个方法..
}
  • 标识符
    • default
      • 仅限同一个包(package)内访问
    • private
      • 仅限当前的类内部访问,不可以修饰类和接口(interface)
    • public
      • 既可跨类访问,又可跨包访问
    • protected
      • 仅限类本身的方法和子类访问,不可以修饰类和接口
    • final
      • 无法改变或者说终态的,如果修饰类则意味着这个类不可以被继承
  • 类名称
    • 从语法来看,只要是一个合法的标识符即可作为类名称;但是从程序的可读性角度出发,Java类名必须是一个或多个有意义的单词连缀而成,每个单词首字母大写,其它字母全部小写,单词与单词之间不要使用任何分隔符
  • 三种常见的类的成员
    • 构造方法(constructor)
      • 是一种特殊的方法,又叫构造器,用来初始化该类的一个新的对象,构造方法和类名同名,而且不返回数据类型。如果没有编写构造方法,Java会提供一个默认的构造方法。
    • 属性(field)
      • 属性即成员变量,有时还直译为字段。
    • 方法

类的属性

  • 又称为字段或者成员变量
  • [修饰符] 属性类型 属性名[=默认值]
  • 属性名
    • 从语法来看,只要是一个合法的标识符即可作为属性名;但是从程序的可读性角度出发,Java属性应该由一个或多个有意义的单词连缀而成,第一个单词首字母小写,其它单词首字母大写,其它字母全部小写,单词与单词之间不要使用任何分隔符
  • 修饰符
    • public
    • protected
    • private
    • static
      • 被修饰的变量称为类变量,它们属于类本身,它们被类的实例所共享
      • 还可以修饰类中的方法,成为类方法,表明它是这个类共有的
    • final

类的构造方法

  • 语法格式
    [修饰符] 构造方法名(形参列表)
    {
     //0到多条可执行语句
    }
    
  • 构造方法名必须与类名相同
  • 构造方法既不能定义返回值类型,也不能使用void声明此构造方法没有返回值,如果为构造方法定义了返回值类型,或使用void声明构造方法没有返回值,Java会把这个构造方法当初普通方法来处理

对象

对象的简介

  • 对象(Object)是类的实例化后的产物;对象的静态特征抽象为属性,用数据来描述,也就是变量;对象的动态特征抽象为行为,用一组代码表示,完成对数据的操作,也就是方法;

对象的声明

  • 类名 对象名=new 类名();
    • 此时就会调用的对应类名的构造方法来创建一个新的类的对象
  • 栈内存和堆内存
    • 堆栈溢出(Stack Overflow)
      • 由于栈内存比较小,如果栈内存不慎耗尽,就会产生堆栈溢出,这将导致整个运行中的程序崩溃(Crash)
      • 全球最受欢迎的IT技术网站就叫做Stack Flow,如中文网站不能找到解决方案,则可以尝试在这个网站寻找高质量的英文解决方案

对象的使用

  • 对象名称.属性名
  • 对象名称.方法名()

匿名对象

  • 即没有名字的对象,具体说就是只开辟了堆内存空间,而没有栈内存指向的对象

对象的比较

  • ==运算符
    • 比较两个对象的内存地址(引用值)是否相等
  • equals()方法
    • 比较两个对象的内容是否一致

对象数组的使用

  • 类可有理解为用户自定义的数据类型,因此可以用数组来存放
  • 声明方式
    • Person p[]; p=new Person[3];
    • Person p[]=new Person[3];
    • Person p[]={new Person(), new Person(), new Person()};
    • 或者也可以用for循环,略

饿汉式与懒汉式单例模式

  • 单例模式又叫做Singleton模式,指的是一个类,在一个JVM里,只有一个实例存在
  • 用private修饰类的构造方法即可
  • 区别
    • 饿汉式
      • private static 类名 instance = new 类构造方法();
      • 提供一个getInstance方法返回instance
    • 懒汉式
      • private static 类名 instance;此时instance指向null
      • 提供一个getInstances方法返回instance,此时应该加入判断语句,判断null==Instance,则instance==new 类构造方法();
  • 单例模式三要素
    • 构造方法私有化
    • 静态属性指向实例
    • public static的getInstance方法,返回第二部的静态属性

类的封装、继承与多态

面向对象的三大特点

  • 封装的含义
    • 将描述某类事物的数据与处理这些数据的函数封装在一起,形成一个有机整体,称为类
    • 它指的是将对象的状态信息隐藏在对象内部,不允许外部程序直接访问对象内部信息,而是通过该类所提供的方法来实现对内部信息的操作和访问
  • 继承的含义
    • 对象是类的一个实例(Instance)
    • 当某一个新类A继承某一既有类B时,表示这个新类A具有既有类B的所有成员,同时对既有类的成员作出了修改,抑或是增加了新的成员。保持已有类的特性而构造新类的过程称为继承
  • 多态的含义
    • 多态性可分为两类
      • 方法多态性,体现在方法的重载和覆写上
      • 对象多态性,体现在父、子对象之间的转换上
        • 向上转型(Upcast)(自动转型):父类 父类对象 = 子类实例
        • 向下转型(Downcast)(强制转型):子类 子类对象 =(子类) 父类对象

封装的实现

  • 隐藏
    • 被关键词static修饰的静态方法是不能被覆盖的,在Java中,这种特性称为隐藏(Hide)
    • 事实上,所有的静态方法都属于类,而非对象
  • Java访问权限修饰符
    • 私有Private
      • 类:只有内部类允许私有,只能在当前类中被访问
      • 属性:只能被当前类访问
      • 方法:只能被当前类访问
    • 默认Default
      • 类:可以被当前包中的所有类访问
      • 属性:可以被相同包中的类访问
      • 方法:可以被相同包中的类访问
    • 保护Protected
      • 类:只有内部类可以设为保护权限,相同包中的类和其子类可以访问
      • 属性:可以被相同包中的类和当前类的子类访问
      • 方法:可以被相同包中的类和当前类的子类访问
    • 公有Public
      • 类、属性、方法:可以被所有的类访问
    • 一般什么情况下使用什么修饰符?
      • 属性通常用private封装起来
      • 方法一般使用public用于被调用
      • 会被子类继承的方法,通常是用protected
      • default用的不多,一般是新手使用,因为不知道有修饰符这个东西
  • 封装问题的总结
    • 目的
      • 实现“信息隐藏”(Information Hidding)
    • 好处
      • 良好的封装可以提高代码的模块化程度,它防止了对象之间不良的相互影响,是程序达到强内聚(许多功能尽量在类的内部独立完成,不让外面干预),弱耦合(提供给外部尽量少的方法调用)的最终目标
    • 何时使用
      • 关于封装与否并没有一个明确的规定,不过从程序设计的角度来说,一般说来设计好的程序的类中的属性都是需要封装的额

继承的实现

  • 继承的基本概念
    • class 子类名 extends 父类
    • 只能直接继承父类中的公有属性和公有方法,而隐含(不可见的)的继承了私有属性,Java的子类不能获得父类的构造方法
  • 继承的限制
    • 在Java中不允许多重继承但允许多层继承,但一般情况下不宜超过三层,即每一个子类只能有一个直接父类,但是子类还可以继续往下继承得到子类的子类,以此类推
    • 从父类继承的私有成员,不能被子类直接使用
    • 子类在进行对象实例化时,从父类继而来的数据成员需要先调用父类的构造方法来初始化,然后再用子类的构造方法来初始化本地的数据成员
      • 使用super可在子类的构造方法中调用父类的构造方法
        • 例如:
          public class Person{
           private String name;
           private String sex;
           private int age;
           public Person(){}
           public Person(String name,int age){
             this.name=name;
             this.age=age;  
            }
           public Person(String name,String sex,int age){
             this(name,age);//调用带有着两个形参的构造方法的初始化代码
             this.sex=sex;
           }
         }
         public class Woman extends Person{
           private String sex;
           public Woman(String name,int age){
            //调用了父类的构造方法
            super(name,age);
            this.sex="女";
           }             
         }
        
        
      • 不管是否使用super调用来执行父类构造器的初始化代码,子类构造器总会调用父类构造器一次,有如下几种情况
        • 子类构造器执行体的第一行使用super显示调用父类构造器,系统将根据super调用里传入的实参列表调用父类对应的构造器
        • 子类构造器执行体的第一行代码使用this显示调用本类重载的构造器,系统将根据this调用里传入的实参列表调用本类中的另一个构造器。执行本类中另一个构造器时即会调用父类构造器
        • 子类构造器执行体中既没有super调用,也没有this调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器
    • 被final修饰的类不能再被继承
  • 覆写(Override)
    • 方法的覆写
      • 覆写又叫重写,当一个子类继承一个父类,如果子类中定义了和父类方法同名的方法时,就称子类中的这个方法覆写了父类中的方法
      • 方法的重写要遵循“两同两小一大”规则
        • 两同:方法同名、形参列表相同
        • 两小:子类方法返回值类型应比父类方法返回值类型更小或者相等,子类方法抛出的异常类应比父类方法抛出的异常类更小或相等
          • 被覆写的方法不能拥有比父类更为严格的访问控制权限
            • 私有private<默认default<公有public
            • 如果父类是private权限,子类无法感知,也就不存在所谓的覆写了;
        • 一大:子类方法的访问权限应比父类方法的访问权限更大或相等
        • 覆盖方法和被覆盖方法要么都是类方法,要么都是实例方法
    • 属性的覆写
      • 从开发的角度来说,为了满足类的封装性,类中的属性一般都需要使用private封装,一旦封装后,子类压根就“看不见”父类的属性成员,子类定义的同名属性成员,其实就是一个“全新的”数据成员,所谓的覆写操作就完全没有意义了
  • 深度认识类的继承
    • 子类的对象实例化过程
      • 先调用父类构造,再调用子类构造
      • 执行父类构造器时,系统会再次上溯执行其父类构造器…以此类推,创建任何Java对象,最先执行的总是java.lang.Object类的构造器
    • super关键字的使用
      • 有时候我们也称父类为超类(super-class);主要功能是,如果子类覆写了父类的方法,或者子类的变量隐藏了父类的变量时,可以通过super完成子类调用父类中的内容,也就是调用父类中的属性或方法
      • 调用构造方法的用法
        • super(参数1,参数2,……)
        • super()必须写在子类构造方法的第一行
      • 调用属性和方法的用法
        • super.父类中的属性
        • super.父类中的方法();
    • 如果在某个方法中访问名为a的成员变量,但是没有显示指定调用者,则系统查找a的顺序为
      • 查找该方法中是否有名为a的局部变量
      • 查找当前类中是否包含名为a的成员变量
      • 查找a的直接父类中是否包含名为a的成员变量,依次上溯a的所有父类,知道java.lang.Object类,如果最终不能找到名为a的成员变量,则出现编译错误
    • 限制子类的访问
      • 加上private关键字
    • 一种特殊的情形
        class Parent{
          public String tag="父类的tag";
        }
        class Kids extends Parent{
          public String tag=“子类的tag”;
        }
      ......
           Kids k=new Kis();
           //程序不可以访问k的私有变量tag,以下语句将引起编译错误
           //System.out.println(k.tag);
           //将k变量显示地向上转型为Parent之后,即可访问tag实例变量
           //此时将输出“父类的tag”
           System.out.println(((Parent)k).tag);
      ......
      
  • 使用继承需要注意的问题
    • 子类继承父类时,子类可以从父类继承得到成员变量和方法,如果访问权限允许,子类可以直接访问父类的成员变量和方法,继承是实现类复用的重要手段,但继承带来一个最大的坏处:破坏封装。
    • 为了保证父类具有良好的封装性,不会被子类随意改变,设计父类通常应遵循如下原则
      • 尽量隐藏父类的内部数据。尽量把父类的所有成员变量都设置成private访问类型,不要让子类直接访问父类的成员变量
      • 不要子类可以随意访问、修饰父类的方法。父类中那些仅为辅助其他的工具方法,应该使用private访问控制符修饰,让子类无法访问;如果父类中的方法需要被外部类调用,则必须以public来修饰,但又不希望子类重写该方法,可以使用final修饰符来修饰该方法;如果希望父类的某个方法被子类重写,但不希望被其它类自由访问,则可以使用protected来修饰该方法
      • 尽量不要在父类构造器中调用将要被子类重写的方法
    • 何时需要从父类派生新的子类
      • 保证子类是一种特殊的父类
      • 以下条件满足之一
        • 子类需要有额外增加属性,而不仅仅是属性的改变
        • 子类需要增加自己独有的行为方式(包括增加新的方法或重写父类的方法)
  • 利用组合实现复用
    • 如果需要复用一个类,还可以把类当成另一个类的组合成分,从而允许新类直接复用该类的public方法
    • 组合即在一个新类中以private的方式实例化一个旧类的对象,通过这个对象复用旧类的方法和属性
  • 继承和组合的选择
    • 大部分时候,继承关系中从多个子类里抽象出共有父类的过程,类似于组合关系中从多个整体类里提取组合类的过程;继承关系中从父类派生子类的过程,则类似于组合关系中把被组合类组合到整体类的过程
    • 继承是对已有的类做一番改造,以此获得一个特殊的版本,简而言之,就是将较为抽象的类改造成能适用于某些特定需求的类。例如,狼和动物,使用继承关系更能表达现实意义,用一个动物来合成一匹狼毫无意义。
    • 如果两个类之间有明确的整体、部分关系,例如人需要复用手来写字、脚来走路,此时就应该采用组合关系来实现代码复用
    • 总而言之,继承要表达是一种“是(is-a)关系”,而组合表达的是“有(has-a)”的关系

多态的实现

  • Java引用变量有两个类型:一个是编译时类型,一个是运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。如果编译时类型和运行时类型不一致,就可能出现所谓的多态(Polymorphism)
  • 方法多态性
    • 方法的重载
  • 对象多态性
    • 向上转型:父亲对象通过子类对象去实例化,向上转型可以自动完成
      • 例如 Parent marry=new Kids(); 此时子类是一种特殊的父类,因此Java允许把一个子类对象直接赋给一个父类引用变量,无需任何类型转换。此时marry引用变量的编译类型是Parent,而运行时类型是Kids,当运行时调用该引用变量的方法(子类覆写了父类的方法)时,其方法行为总是表现出子类方法的行为特征,而不是父类方法的行为特征,这就可能出现:相同的变调用同一个方法时呈现出多种不同的行为特征,这就是多态。而如果是子类自己独有的方法,直接调用会发生编译错误(可以通过反射机制来执行这种方法)。其次对象的实例变量不具备多态性,此时marry的变量以及其对应的值都来源于Parent,而不是Kids
    • 向下转型:父类的对象可以转换为子类对象,这时必须要进行强制的类型转换,向下转型必须进行强制类型转换
      • 引用变量的强制类型转换通过(type)variable的方式进行
        • 例如
         Parent marry= new Kids();   // 这就叫 upcasting (向上转型)
        
         // 现在 marry 引用指向一个Kids对象
         Kids ming = (Kids)marry; // 这就叫 downcasting (向下转型)
        
      • 进行强制类型转换时需要注意
        • 基本类型之间的转换只能在数值类型之间进行,这里所说的数值类型包括整数型、字符型和浮点型。但数值类型和布尔类型之间不可以进行类型转换
        • 引用类型之间的转换只能在具有继承关系的两个类型之间进行,如果是两个没有任何继承关系的类型,则无法进行类型转换,否则编译时就会出现错误。如果试图把一个父类实例转换成子类类型,则这个对象必须实际上是子类实例才行(即编译时类型为父类类型,而运行时类型是子类类型),否则将在运行时发生ClassCastException
      • instanceof运算符
        • 此运算符的前一个操作数通常是一个引用类型变量,后一个操作数通常是一个类(也可以是接口,接口也是一种特殊的类),它用于判断前面的对象是否是后面的类,或者其子类、实现类的实例。如果是则返回true,不是则返回false
        • 使用时需要注意:运算符前面操作数的编译类型要么与后面的类相同,要么与后面的类具有父子继承关系,否则会引起编译错误
        • 考虑到强制类型转换时可能出现异常,因此进行类型转换之前应先通过instanceof运算符来判断是否可以成功转换。
          • 例如:
           if (marry instanceof Kids){
            Kids ming = (Kids)marry;
           }
          
    • 转型问题,总结为一句话就是父类引用指向子类对象,而子类引用不能指向父类对象。
  • 继承是子类使用父类的方法;而多态则是父类使用子类的方法。更确切来说,多态是父类使用被子类覆盖的同名方法,如果子类的方法是全新的,父类不存在同名的方法,则父类也不能使用子类自己独有的“个性化方法”

人已赞赏
Java编程语言

【Java学习笔记(一)】数据类型、变量与常量、数组以及注释

2020-2-9 22:56:48

Java编程语言

【Java学习笔记(三)】变量、方法和初始化块

2020-3-1 15:00:52

4 条回复 A文章作者 M管理员
  1. 学习java的入门级好文,加油~

  2. 为了看这篇文章 我特意找回了密码

    • 去个人中心授权QQ快捷登录,从此不再多记一个密码

    • 确实方便

个人中心
今日签到
有新私信 私信列表
搜索