如何理解面向对象程序设计
两种基本程序思想
- 面向过程程序设计(POP)
- 面向对象程序设计(OOP)
面向对象程序设计的基本特征
- 封装性
- 继承性
- 多态性
两种编程基本思想的比较
- POP
- 该怎么做(How to do?)
- 程序=算法+数据结构
- 自上而下,步步求精,以过程为中心,以算法为驱动
- OOP
- 该让谁来做(What to do?)
- 程序=对象+消息传递=(数据+方法)+消息传递
类
类的简介
- 将具有相同属性以及相同行为的一组对象成为类(Class);类是组成Java的基本要素,它封装了一类对象的状态和方法;
类的定义
[标识符] class 类名称 { 零到多个构造器(构造方法)定义.. 零到多个成员变量.. 零到多个方法.. }
- 标识符
- default
- 仅限同一个包(package)内访问
- private
- 仅限当前的类内部访问,不可以修饰类和接口(interface)
- public
- 既可跨类访问,又可跨包访问
- protected
- 仅限类本身的方法和子类访问,不可以修饰类和接口
- final
- 无法改变或者说终态的,如果修饰类则意味着这个类不可以被继承
- default
- 类名称
- 从语法来看,只要是一个合法的标识符即可作为类名称;但是从程序的可读性角度出发,Java类名必须是一个或多个有意义的单词连缀而成,每个单词首字母大写,其它字母全部小写,单词与单词之间不要使用任何分隔符
- 三种常见的类的成员
- 构造方法(constructor)
- 是一种特殊的方法,又叫构造器,用来初始化该类的一个新的对象,构造方法和类名同名,而且不返回数据类型。如果没有编写构造方法,Java会提供一个默认的构造方法。
- 属性(field)
- 属性即成员变量,有时还直译为字段。
- 方法
- 构造方法(constructor)
类的属性
- 又称为字段或者成员变量
- [修饰符] 属性类型 属性名[=默认值]
- 属性名
- 从语法来看,只要是一个合法的标识符即可作为属性名;但是从程序的可读性角度出发,Java属性应该由一个或多个有意义的单词连缀而成,第一个单词首字母小写,其它单词首字母大写,其它字母全部小写,单词与单词之间不要使用任何分隔符
- 修饰符
- public
- protected
- private
- static
- 被修饰的变量称为类变量,它们属于类本身,它们被类的实例所共享
- 还可以修饰类中的方法,成为类方法,表明它是这个类共有的
- final
类的构造方法
- 语法格式
[修饰符] 构造方法名(形参列表) { //0到多条可执行语句 }
- 构造方法名必须与类名相同
- 构造方法既不能定义返回值类型,也不能使用void声明此构造方法没有返回值,如果为构造方法定义了返回值类型,或使用void声明构造方法没有返回值,Java会把这个构造方法当初普通方法来处理
对象
对象的简介
- 对象(Object)是类的实例化后的产物;对象的静态特征抽象为属性,用数据来描述,也就是变量;对象的动态特征抽象为行为,用一组代码表示,完成对数据的操作,也就是方法;
对象的声明
类名 对象名=new 类名();
- 此时就会调用的对应类名的构造方法来创建一个新的类的对象
- 栈内存和堆内存
- 堆栈溢出(Stack Overflow)
- 由于栈内存比较小,如果栈内存不慎耗尽,就会产生堆栈溢出,这将导致整个运行中的程序崩溃(Crash)
- 全球最受欢迎的IT技术网站就叫做Stack Flow,如中文网站不能找到解决方案,则可以尝试在这个网站寻找高质量的英文解决方案
- 堆栈溢出(Stack Overflow)
对象的使用
- 对象名称.属性名
- 对象名称.方法名()
匿名对象
- 即没有名字的对象,具体说就是只开辟了堆内存空间,而没有栈内存指向的对象
对象的比较
- ==运算符
- 比较两个对象的内存地址(引用值)是否相等
- 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用的不多,一般是新手使用,因为不知道有修饰符这个东西
- 私有Private
- 封装问题的总结
- 目的
- 实现“信息隐藏”(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调用,系统将会在执行子类构造器之前,隐式调用父类无参数的构造器
- 使用super可在子类的构造方法中调用父类的构造方法
- 被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; }
- 引用变量的强制类型转换通过(type)variable的方式进行
- 转型问题,总结为一句话就是父类引用指向子类对象,而子类引用不能指向父类对象。
- 向上转型:父亲对象通过子类对象去实例化,向上转型可以自动完成
- 继承是子类使用父类的方法;而多态则是父类使用子类的方法。更确切来说,多态是父类使用被子类覆盖的同名方法,如果子类的方法是全新的,父类不存在同名的方法,则父类也不能使用子类自己独有的“个性化方法”
学习了
学习java的入门级好文,加油~
为了看这篇文章 我特意找回了密码 ✗酷酷的✗
去个人中心授权QQ快捷登录,从此不再多记一个密码 ✗得意✗
确实方便