D1: 面向对象编程的概念 类、对象以及引用 成员方法
D2: 构造方法、方法重载、this关键字、方法的传参和递归调用
D3: 封装、static关键字、单例设计模式
D4: 继承、访问控制、final关键字、多态以及eclipse的使用
D5: 多态、抽象类、接口、匿名内部类
什么是对象?这个对象有什么特征和行为(面向对象
)?如何用Java来翻译(面向对象编程
)?
对象太多了,代码重复量太大,也描述不过来,所以把他们共性的东西提取出来形成一个类
!如何在Java中写一个类呢(类的定义)?除了类本身的定义,类里面还有成员变量
和成员方法
!
类是抽象的概念,不能干活,要想干活就得依赖这一类的实体(对象
)!怎么创建对象呢?
创建对象的实质就是在堆区创建一块存储空间,如果这块空间没有记录下来,下次用的时候就不知道怎么找,为了记录这块存储空间,我们就得声明一个引用
!什么是引用啊?怎么创建引用呢?
如何访问里面的成员变量呢(成员方法
)?
类、变量、对象都存放在哪里呢(JVM内存结构
)?
什么是对象?万物皆对象
什么是面向对象?面向对象就是指以特征(属性)和行为(方法)的观点去分析现实世界中事务的方式
什么是面向对象编程?面向对象编程就是指先使用面向对象的方式进行分析,再使用任意一门面向对象的编程语言进行翻译的过程。
C语言:面向过程;C++:既面向对象又面向过程;Java:纯面向对象
如何学好面向对象?深刻理解面向对象编程的三大特征:封装、继承、多态
对象是客观存在的实体,在Java语言体现为内存空间的一块区域
类就是分类的概念,是对具有相同特征和行为的多个对象共性的抽象描述,在Java语言中包含描述特征的成员变量和描述行为的成员方法,是创建对象的模板
class 类名 {
类体;
}
如:
class Person {
}
注意:当类名由多个单词组成时,要求每个单词的首字母都要大写
class 类名 {
数据类型 成员变量名 = 初始值; //其中=初始值通常省略,但分号不能省略
}
如:
class Person {
String name;
int age;
}
注意:
当成员变量名由多个单词组成时,要求从第二个单词起每个单词首字母大写,第一个单词小写
扩展:
局部变量:主要指在方法体中声明的变量,作用范围从声明开始到方法体结束
成员变量:主要指在方法体外类体内声明的变量,作用范围从声明到类体结束
数值类型(byte、short、int、long、float、double):0
boolean:fasle
char:\u0000
引用类型:null
new 类名();
如:
new Person(); //创建Person类型的对象,由于该对象没有名字因此叫做"匿名对象"
a.当一个类定义完毕后,使用new关键字创建/构造对象的过程叫做类的实例化
b.创建对象的本质就是在内存中的堆区申请存储空间,来存放该对象独有的特征信息
在Java语言中使用引用数据类型声明的变量叫做引用型变量,简称为"引用"
引用变量主要用于记录对象在堆区中的内存地址信息,便于下次访问
类名 引用变量名;
如:
Person p; //表示声明Person类型的引用变量p,本质上在栈区申请存储空间
Person p = new Person(); //表示声明引用变量p来记录Person类型对象的地址信息
//先执行Person p,再执行new Person(),最后执行=
引用变量名.成员变量名;
如:
p.name = "zhangfei"; //表示使用引用变量p访问所指向堆区对象的姓名特征
成员变量的类型 | 默认初始值 |
---|---|
"数值类型 (byte、short、int、long、float、double) | |
" | 0 |
boolean型 | false |
char型 | \u0000 |
引用类型 | null |
class 类名 {
返回值类型 成员方法名(形参列表) {
成员方法体;
}
}
如:
class Person {
void show() {
System.out.println("没事出来秀一下");
}
}
注意:
当成员变量名由多个单词组成时,要求从第二个单词起每个单词首字母大写
(1)返回值类型
返回值主要指从方法体内向方法体外返回的数据内容
返回值类型主要指返回值的数据类型,可以是基本数据类型,也可以是引用数据类型
如:
若返回的数据内容是"hello",则返回值类型写String即可
在方法体中使用return关键字来返回数据内容并结束当前方法
若该方法不需要返回任何数据内容,则返回值类型写void即可
(2)形参列表
形式参数主要用于将方法体外的数据传入到方法体的内部,语法格式:
数据类型 形参名
形参列表主要指多个形式参数组成的整体,语法格式:
数据类型 形参名1,数据类型 形参名2,...
如:
当传入的数据内容是66和"hello"时,则形参列表写为:int i, String s即可
若该方法不需要传入任何数据内容时,则形参列表位置啥也不写即可
(3)成员方法体
成员方法体主要编写描述该方法功能的语句
如:
当该方法的功能就是打印时,则方法体中写System,out.println("...");即可
(1)语法格式
引用变量名.成员方法名(实参列表);
如:
p.show(); //表示使用引用变量p调用show方法
(2)注意事项
a.实际参数列表主要用于对形式参数列表进行初始化操作,因此实参的个数、类型、顺序等都必须与形参列表保持一致
b.实参可以传递直接量、变量、表达式以及方法的调用等
c.调用方法的本质就是根据方法名跳转过去执行完毕后再跳转回来
1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。
4.补充:栈用于存放程序运行过程当中所有的局部变量,一个运行的Java程序从开始到结束有多次的调用,JVM会为每一个方法的调用在栈中分配一个对应的空间,这个空间称为该方法的栈帧,一个栈帧对应一个正在调用中的方法,栈帧中存储了该方法的参数、局部变量等数据,当某一个方法调用完成后,其对应的栈帧将被删除
1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身
创建对象的时候,Person()方法名与类名相同,这种方法就叫构造方法
!
有参构造方法和无参构造方法,方法名相同,参数列表不同,这就引出了方法重载
!
在构造方法中this关键字
代表当前正在构造的对象,那么this关键字是如何使用的呢?
方法调用过程中,参数
是如何传递的呢?
this第二种使用方式示例中遇到的递归
什么时候使用呢?
Person p = new Person(); //声明Person类型的引用指向Person类型的对象
p.show(); //使用引用变量p调用show方法
Person(); //调用名字为Person的方法
show(); //调用名字为show的方法
class 类名 {
类名(形参列表) {
构造方法体;
}
}
如:
class Person {
Person(){
}
}
(1)构造方法的名称与类名完全相同,没有返回值类型,连void都不许有
(2)当使用new关键字构造对象时会自动调用构造方法进行成员变量的初始化工作
(3)没有构造方法会返回默认值
(1)当一个类(编译后的类)中没有自定义任何形式的构造方法时,编译器会自动添加一个无参(小括号里没有内容)的空(大括号里没有内容)构造方法,叫做默认/缺省构造方法,如:Person(){}
(2)若类中出现自定义构造方法,则编译器不再提供任何形式的构造方法
(3)当类中有非常量成员变量时,建议提供一个无参构造,一个有参构造(有几个成员变量就有几个参数)
在Java语言中若方法的名称相同但参数列表不同,这样的方法之间构成重载关系
方法重载的主要形式有:参数的个数不同、参数的类型不同、参数的顺序不同
与形参变量名和返回值类型无关,但建议返回值类型最好相同(返回值不同调用会很麻烦)
判断方法是否重载的核心:调用能否区分
对于调用者来说只需要记住一个方法名就可以调用各种不同的版本实现不同的效果
如:
char c = 'a';
System.out.println(c);
int i = 10;
System.out.println(i);
double d = 3.14;
System.out.println(d);
...
在构造方法中this关键字代表当前正在构造的对象
在成员方法中this关键字代表当前正在调用的对象
原理分析
当成员方法中访问成员变量时默认会加上this.(相当于汉语中"我的"),当不同的引用调用同一个成员方法时会导致成员方法中的this不同,那么this.访问的结果随之不同
所有的成员变量不能重名,在同一区域的局部变量不能重名,但成员变量和局部变量可以重名
在局部变量的作用域之外,变量名代表成员变量,在局部变量的作用域之内代表局部变量
当形参变量和成员变量同名时,在构造方法或成员方法中通常优先使用形参变量,若希望使用成员变量就需要在变量名的前面加上this.进行说明(重中之重)
在构造方法的第一行,使用this(实参)的方式可以调用本类的其他构造(了解)
this
关键字和空值
引用类型变量用于存放对象的地址,可以给引用类型赋值为null,表示不指向任何对象。
当某个引用类型变量为null时无法对对象实施访问(因为它没有指向任何对象),此时,如果通过引用访问成员变量或调用方法,会产生NullPointerException空指针异常
如:
Point p = null;
p.point();
main方法是程序的入口,为main方法中的局部变量开辟内存空间并初始化
调用max方法时为max方法的形参变量开辟内存空间
使用实参变量给形参变量进行赋值操作,执行max方法的方法体
当max方法结束后释放形参变量的内存空间
main方法中的res得到max方法的返回值然后继续向下执行
当main方法结束后释放局部变量的内存空间
当基本数据类型的变量作为方法的参数传递时,形参变量的改变不会影响到实参;
当引用数据类型的变量作为方法的参数传递时,形参变量指向的内容的改变会影响到实参变量指向的内容;
当引用数据类型的变量作为方法的参数传递时,形参变量改变指向后再改变指向的内容时不会影响到实参变量指向的内容;
在一个方法体的内部调用当前方法自身的形式,叫做递归
如:
void show(){
show();
}
(1)必须有规律和退出条件
(2)必须使问题变简单,而不是复杂化
(3)若递归影响到程序的执行性能则使用递推取代之
main方法不是某个类独有的,不应该放在类里,单独放到一个测试类里。在测试类里可以随便改类信息,那不行,得封装
起来!
在Person类实例中国籍都一样,分配不同的内存空间,造成浪费。如果想把国籍都放在一块内存空间就得用到static关键字
static
练习实例在main
方法中能得到且只能得到Singleton
类的一个对象,Singleton
类是单例类
通常情况下测试类可以对封装类中的成员变量进行赋值,若赋值的数据合法但不合理时,无论是编译还是运行都不会报错或者给出提示,此时与现实生活中不符。
为了避免上述错误的发生,就需要对成员变量进行密封包装处理,该机制就叫做封装
封装就是一种保证成员变量值合理性的机制
私有化成员变量,使用private关键字修饰;(有人随便开车犯错误,把车锁了)
提供公有的get和set方法,在方法体中进行合理值的判断;(有人借钥匙,可以开车不可以犯错误)
在构造方法中调用set方法进行合理值判断(借钥匙开车犯错误,不保证不犯了就不借你了)
通常情况下成员变量隶属于对象层级,每创建一个对象就需要申请独立的内存空间来存放该对象独立的成员变量信息,若所有对象的某个成员变量数值完全一样却又单独存放会造成内存空间的浪费
为解决上述问题,则使用static关键字修饰成员变量表达静态的含义,此时该成员变量由对象层级提升到类层级被所有对象共享,该成员变量随着类的加载准备就绪,与是否创建对象无关(放方法区)
static
关键字也可以修饰成员方法,推荐使用类名.
的方式访问(可以体现它是类层级的)
在非静态的成员方法中既能访问非静态的成员也能访问静态的成员
成员:成员变量 + 成员方法,静态成员被所有对象共享
在静态的成员方法中只能访问静态的成员不能访问非静态的成员,必须先创建对象,然后用对象.
调用
成员:成员变量 + 成员方法,调用静态方法时还没有创建对象
static
的属性/方法在类加载时就已经做好准备,因此类名.
就可以调用,与对象存不存在无关
非static
的属性/方法隶属于对象,必须先创建对象,才能使用
只有隶属于类(所有对象共享)的属性才可以加static
,static
不能随便加
在某些特殊场合中一个类对外提供且只提供一个对象,这样的类叫做单例类
设计单例类的思想和模式叫做单例设计模式,主要用于固定的场合
私有化构造方法,使用private关键字修饰;
声明本类类型的引用指向本类类型的对象,使用private static共同修饰;
提供公有的get方法负责将成员变量的数值返回出去,使用static关键字修饰
饿汉式(推荐):一上来就创建对象,有人调用方法的时候直接返回
懒汉式:一上来不创建对象,等到有人调用方法的的时候再创建对象并且返回
执行流程:
TestSingleton
类(方法区)把封装的类中共性的内容提取出来形成一个公共类,其他类吸收这个公共类的机制就叫继承
Worker类继承Person类调用show()方法是Person类里的,只能打印Person类里有的特征,不能打印Worker类里新加的特征,这个时候就要用方法重写
Pet和Dog中的public和private的作用是什么?什么时候用public什么时候用private呢(访问控制
)?
由访问控制引出包
的定义,包是为了方便类的管理,那么包该怎么命名呢?
final关键字
防止滥用继承,不经意方法重写或数值的改变
当多个类之间拥有相同的特征和行为时,可以将相同的内容提取出来组成一个公共类,让多个类分别吸收公共类中已有的特征和行为,而在多个类中只需要编写自己独有特征和行为的机制,就叫做继承
使用继承可以提高代码的复用性、扩展性以及可维护性
在Java语言中使用extends关键字来表达继承关系
如:
public class Worker extends
Person{}表示Worker类继承自Person类
其中Person类叫做基、父类以及超类
其中Worker类叫做派生类、子类以及孩子类
构造类要求与类名相同,而Worker类名Person类名不同
私有方法只能在本类中引用,继承下来也没用
当构造子类对象时会自动构造父类的无参构造方法来初始化从父类中继承下来的成员变量,相当于在子类构造方法的第一行增加代码super();
的效果
Java语言中只支持单继承,也就是一个子类只能有一个父类,一个父类可以有多个子类
使用继承必须满足逻辑关系:子类 is a 父类
this()和super()都必须出现在构造方法的第一行,因此不能同时出现
基本概念
使用方式
要求掌握
this.
的方式可以区分同名的成员变量和形参变量super(实参)
的方式可以调用父类的构造方法super.
的方式可以调用父类中被重写的方法从父类中继承下来的方法不足以满足子类的需求,就需要在子类中重写一个与父类一样的方法来覆盖从父类中继承下来的版本,该方式就叫做方法重写
在子类重写的方法中,可以通过super关键字调用父类的"原始"方法
static的方法重写以后还是可以static的
相同的方法名称,相同的参数列表,相同的返回值类型,从jdk1.5开始可以返回子类类型
访问权限不能变小,可以相同或者变大(你爸钱不够了,你得自己挣也可以维持,但可不能败家)
不能抛出更大的异常(异常机制)
区别点 | 重载方法 | 重写方法 |
---|---|---|
参数列表 | 必须修改 | 一定不能修改 |
返回类型 | 可以修改 | 一定不能修改 |
异常 | 可以修改 | 可以减少或删除,一定不能抛出新的或者更广的异常 |
访问 | 可以修改 | 一定不能做更严格的限制(可以降低限制) |
访问控制符 | 访问权限 | 本类 | 本包中的类 | 子类 | 其他包中的其他类 |
---|---|---|---|---|---|
public | 公有的 | ok | ok | ok | ok |
protected | 保护的 | ok | ok | ok | no |
default | 默认的 | ok | ok | no | no |
private | 私有的 | ok | no | no | no |
public修饰的内容可以在任意位置使用
private修饰的内容只能在本类中使用
通常情况下,成员变量都使用private修饰,成员方法都使用public修饰
package 包名;
package 包名1.包名2…包名n;
目的: 便于管理,避免命名冲突的问题
org.apache.commons.lang.StringUtil
StringUtil是类名
org.apache.commons.lang是多层包名
org.apache表示公司或组织的信息(是这个公司(或组织)域名的反写)
commons表示项目的名称信息
lang表示模块的名称信息
final本意为"最终的,不可更改的",该关键字可以修饰类、成员方法、成员变量等。
为了防止滥用继承带来的危害
如:java.lang.String类等
为了防止不经意间的方法重写
如:java.text.DataFormat类中的format方法等
为了防止不经意间造成的数值的更改
如:java.lang.Thread类中的MAX_PRIORITY等
在以后的开发中很少单独使用static关键字或final关键字修饰成员变量,通常都是使用public static final共同修饰成员变量来表达常量的含义
常量的命名规则是:要求所有的字母大写,不同单词之间采用下划线连接,如:public static final double PI = 3.14;
面向对象的三大特征:在封装的基础上继承,在继承的基础上多态
,那么什么是多态呢
在解决多态性第二条特点的时候,不能直接使用子类中扩展的属性和方法,但能通过强制类型转换
解决,那么引用型类型是如何转换的呢?
多态具体怎么用呢(抽象类
)?
黄金既算金属,又算货币,但是Java中只支持单继承,让黄金类同时实现金属接口
和货币接口,弥补不能多继承的缺陷
如果这个方法只调用一次,为了它在写一个类在方法区占着一块内存空间,有点浪费,所以就创建一个匿名内部类
多态主要指同一事物表现出来的多种形态
饮料:可乐,雪碧,脉动,红牛,…
Person pw = new Worker();
pw.show();
当父类类型的引用指向子类对象时,父类类型的引用可以直接调用父类中独有的方法
当父类类型的引用指向子类对象时,父类类型的引用不可以直接调用子类独有的方法
对于父子类都拥有的非静态成员方法来说,编译阶段调用父类版本,运行阶段调用子类版本。
对于父子类都拥有的静态成员方法来说,编译阶段和运行阶段都调用父类版本。
示例
Person p = new Student(); //p究竟是当父类用,还是子类用呢?
其实是当父类的对象使用
(1)只能使用父类中定义的属性和方法
(2)不能直接使用子类中扩展的属性和方法
(3)如果子类重写了方法,静态方法调父类的,非静态方法调子类的
原因: 编译时,p被认为是Person类型;但在运行时是Student类型,在内存中其实是子类对象
多态的实际意义在于屏蔽不同子类的差异性实现通用的编程带来不同的结果,如既能打印矩形又能打印圆形(父类Shape)。
引用数据类型之间的转换分为:自动类型转换 和 强制类型转换。
其中自动类型转换主要指从小类型到大类型的转换,也就是从子类到父类的转换。
其中强制类型转换主要指从大类型到小类型的转换,也就是从父类到子类的转换。
引用数据类型之间的转换必须发生在父子类之间,否则编译报错。
若目标类型不是引用变量指向的对象类型,则编译通过运行发生类型转换异常。
instanceof就是判断引用指向的对象是否为目标类型,如果是该类型返回true,不是返回false
语法格式: 对象 instanceof 目标类型
instanceof判断支持本类和父类类型
强制类型转换之前都应该判断一下
抽象方法主要指不能具体实现的方法并且使用abstract关键字
修饰
格式如下:访问权限 abstract 返回值类型 成员方法名(形参列表);
如:public abstract void cry();
抽象类主要指不能具体实例化(new 对象)的类并且使用abstract关键字修饰。
抽象类中可以有成员变量、成员方法以及构造方法。
抽象类中可以没有抽象方法,也可以有抽象方法。
拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类是由abstract关键字修饰并且拥有抽象方法的类。(原因:抽象方法没有方法体,因此调用抽象方法是没有意义的,为了防止程序员不小心调用抽象方法,Java官方就规定抽象类不能new对象)
抽象类的实际意义不在于自身创建对象而在于被继承,当一个类继承抽象类之后必须重写抽象方法,否则该类也变成抽象类。
因此抽象类对子类具有强制性和规范性,叫做 模板设计模式。
多态
的方式编写代码,此时抽象类的引用可以直接调用的所有方法一定是抽象类中拥有的方法AbstractTest at = new SubAbstractTest();
当需要更换子类时只需要将new后面的类型修改而其它地方代码不变就立即生效,从而提高了代码的可维护性。
该方式的缺点在于:父类引用若希望访问子类独有方法,则需要强制类型转换。
注意:
接口主要指比抽象类还抽象的类,因此不能实例化对象。
定义类的关键字使用class,而定义接口使用interface关键字。
接口的使用价值和意义
如: 金属接口 货币接口 黄金类
黄金既算金属,又算货币,但是Java中只支持单继承,让黄金类同时实现金属接口和货币接口,弥补不能多继承的缺陷
Metal mt = new Gold();
类和类之间的关系: 使用extends关键字表达继承的关系,支持单继承
类和接口之间的关系: 使用implements关键字表达实现的关系,支持多实现
接口和接口之间的关系: 使用extends关键字表达继承的关系,支持多继承
定义抽象类的关键字是abstract class,而定义接口的关键字是interface。
继承抽象类的关键字是extends,而实现接口的关键字是implements。
继承抽象类支持单继承,而实现接口支持多实现。
抽象类中可以有构造方法,而接口中不可以有构造方法。
抽象类中可以有成员变量,而接口中只可以有常量。
抽象类中可以有成员方法,而接口中只可以有抽象方法。
抽象类中增加方法可以不影响子类,而接口中增加方法通常都会影响实现类。
接口中的方法都是抽象方法,如果增加方法,抽象类方法就会影响实现类,实现类就得重写
抽象类中增加方法可以增加非抽象方法,不影响子类
```
接口中不可以定义成员变量,但可以定义常量
接口中只可以定义没有实现的方法(抽象方法),public abstract
可以省略但不要省略
一个类实现了某个接口后必须实现该接口中定义的所有方法
接口可以作为一种类型声明变量,一个接口类型的变量可以引用实现了该接口的类的对象;通过该变量可以调用该接口中定义的方法(具体的实现类提供了方法的实现)
public class A {
public class B {}
}
接口/父类类型 引用变量名 = new 接口/父类类型(){ 方法的重写 };
A ta = new A() {
@Override
public void show() {
System.out.println("这就是匿名内部类的用法");
}
};
如果一段程序中需要创建一个类的对象(通常这个类需要实现某个接口或者继承某个类),而且对象创建后这个类的价值也就不存在了,这个类可以不必命名,称之为匿名内部类
用匿名内部类所实现的接口或所继承的父类类型声明的引用
SuperType obj = new SuperType(...){
...
}
(1)自定义类实现接口,然后创建该类的对象作为实参传递。
(2)使用匿名内部类得到接口类型的引用作为实参传递。
类中的内容:成员变量、成员方法、构造方法、静态成员、构造块、静态代码块、内部类
语法格式
class 外部类名{
class 内部类名{
内部类体
}
}
实际作用:当一个类存在的价值仅仅是为某一个类单独服务时,那么就可以将这个类定义为所服务类中的内部类,可以隐藏该类的实现细节并且可以方便的访问外部类的私有成员而不再需要提供公有的get和set方法
基本分类