面向对象
面向对象
2020/9/2 22:58:10
面向对象编程的概念
什么是对象?
万物皆对象。
什么是面向对象?
面向对象就是指以特征和行为的观点分析现实世界中事物的方式。
什么是面向对象编程?
面向对象编程就是指先使用面向对象的观点进行分析再使用面向对象的编程语言进行翻译的过程。
其中C语言是一门面向过程的编程语言。
其中C++语言是一门即面向过程又面向对象的编程语言。
其中Java语言是一门纯面向对象的编程语言。
类和对象以及引用
如:
String name = "侯帅"; int age = 18; ...
String name2 = "刘畅"; int age2 = 17; ...
... ...
人类:
特征:姓名、年龄
行为:吃饭、睡觉
类和对象的概念
对象主要指现实生活中客观存在的事物,在Java语言主要指内存区域的一块存储空间
类就是"分类"的概念,是多个对象共性提取的抽象描述,里面包含描述特征的成员变量以及描述行为的成员方法组成,是创建对象的模板。
类的定义
(1)类定义的语法格式
class 类名 {
类体;
}
如:
class Person {
}
注意:
当类名由多个单词组成时,要求每个单词的首字母都要大写。
(2)成员变量定义的语法格式
class 类名 {
数据类型 成员变量名 = 初始值; - 其中=初始值可以省略,但分号不可以省略
}
如:
class Person {
String name;
int age;
}
注意:
当成员变量名由多个单词组成时,要求从第二个单词起每个单词首字母大写。
注意:
局部变量 - 主要指编写在方法体内部的变量,作用范围从声明开始一直到方法体结束
成员变量 - 主要指编写在方法体外类体内部的变量,从声明开始一直到类体结束
对象的创建
(1)语法格式
new 类名();
如:
new Person(); - 表示创建Person类型的对象,匿名对象
(2)注意事项
a.使用new关键字创建/构造对象的过程叫做 类的实例化;
b.对象创建的本质就是在内存区域的堆区申请一块存储空间,用于记录该对象独有
的特征信息。
引用的定义
(1)基本概念
使用引用数据类型声明的变量 叫做引用型变量,简称为"引用"。
引用变量的定义本质上就是在内存区域的栈区申请一块存储空间,用于记录对象在堆区中的内存地址信息。
(2)语法格式
类名 引用变量名;
如:
Person p; - 表示声明Person类型的引用变量名p
Person p = new Person(); - 表示声明Person类型的引用指向Person类型的对象
引用变量名.成员变量名
如:
p.name = "宇新";
System.out.println("我是" + p.name); // 我是宇新
构造方法和方法重载
如:
Person p = new Person(); - 表示声明Person类型的引用指向Person类型的对象
p.show(); - 表示调用名字为show的方法
构造方法
(1)语法格式
class 类名 {
类名(形参列表) {
构造方法体;
}
}
如:
class Person {
Person() {
System.out.println("构造方法就是我哦!");
}
}
(2)注意事项
a.构造方法的名称与类名完全相同,并且没有返回值类型(连void都不许有)。
b.当使用new关键字创建对象时会自动调用构造方法实现成员变量的初始化工作。
(3)默认构造方法
a.当一个类中没有自定义任何构造方法时,则编译器提供一个无参的空构造方法,
叫做默认构造方法,如:Person() {}
b.若类中出现了自定义构造方法,则编译器不再提供任何构造方法。
方法的重载
(1)基本概念
在Java语言中若两个方法的名称相同但参数列表不同,则构成重载关系(Overload)。
(2)表现形式
方法重载的主要表现形式为:参数的类型不同、参数的个数不同、参数的顺序不同。
与形参变量名以及返回值类型无关,但返回值类型最好相同。
判断方法能否构成重载的核心:调用方法时能否加以区分。
(3)实际意义
方法重载的实际意义在于调用者只需要记住一个方法名就可以调用各种不同的版本实现各种不同的效果。
如:
int i = 10;
System.out.println(i);
char c = 'a';
System.out.println(c);
double d = 3.14;
System.out.println(d);
...
this关键字
基本概念
在构造方法中若出现了this关键字,则表示当前正在构造的对象。
在成员方法中若出现了this关键字,则表示当前正在调用方法的对象。
原理分析:
当构造方法和成员方法中访问成员变量时默认会加上this.的形式,this.相当于汉语中"我的",
当不同的对象调用同一个方法时导致this不同,那么this.访问的结果也就随之不同。
使用方式
(1)当形参变量和成员变量同名时,在方法体内会遵循就近原则优先使用形参变量,
为了明确使用成员变量,则需要在成员变量名的前面加上this.加以说明(重中之重)。
(2)在构造方法的第一行使用this()的方式可以调用本类中的其他构造方法(了解)。
方法的传参
方法的传参过程
(1)main方法是程序的入口,先为main方法中的局部变量申请内存空间并初始化;
(2)调用max方法,为max方法的形参变量申请内存空间;
(3)将实参变量的数值赋值给形参变量;
(4)执行max方法的方法体,当max方法执行完毕后释放形参变量的内存空间;
(5)main方法中的res得到max方法的返回值继续向下执行;
(6)直到main方法执行完毕后,main方法中局部变量的内存空间释放;
a.当基本数据类型的变量作为方法的参数传递时,形参变量数值的改变不会影响实参
b.当引用数据类型的变量作为方法的参数传递时,形参变量指向的数据内容若发生
改变,则会影响到实参变量指向的数据内容。
c.当引用数据类型的变量作为方法的参数传递时,若形参变量改变指向后再改变指向 的数据内容时,则不会影响到实参变量指向的数据内容,因为内存空间独立存在。
方法的递归调用
基本概念
递归主要指在方法体内部调用当前方法自身的形式。
案例:
自定义成员方法实现参数n阶乘的计算并返回。
int show(int n) { // n =5;
// 递推的方式计算阶乘
int num = 1;
for(int i = n; i > 1; i--) {
num *= i; // num = num*i = 5; num = 20; num = 60; num = 120;
// num = 1*5;
// num = 5*4;
// num = 20*3;
// num = 60*2;
}
return num;
}
int show(int n) { // n =5;
// 递归的方式计算阶乘
if(1 == n) {
return 1;
}
return n * show(n-1);
// show(5) = 5 * show(4); = 120
// show(4) = 4 * show(3); = 24
// show(3) = 3 * show(2); = 6
// show(2) = 2 * show(1); = 2
// show(1) = 1;
return num;
}
解析:
5! = 5 * 4 * 3 * 2 * 1;
4! = 4 * 3 * 2 * 1;
3! = 3 * 2 * 1;
2! = 2 * 1;
1! = 1;
n! = n * (n-1) * ... * 1;
5! = 5 * 4!;
4! = 4 * 3!;
3! = 3 * 2!;
2! = 2 * 1!;
1! = 1;
当 n = 1 时,则结果就是 1;
当 n = n 时,则结果就是 n * (n-1)的阶乘;
使用原则
(1)使用递归必须找到规律和退出条件;
(2)使用递归必须使得问题简单化而不是复杂化;
(3)若递归影响程序的执行性能,使用递推取代之;
封装
基本概念
通常情况下可以给成员变量赋值合法但不合理的数值,此时无论是编译阶段还是运行阶段都不会报错或给出提示,因此与现实生活不符。
为了避免上述错误的发生,就需要对成员变量进行密封包装处理,这种处理机制就叫做封装,换句话说,封装就是一种保证成员变量值合理和隐藏的机制。
实现流程
(1)私有化成员变量,使用private关键字修饰;
(2)提供公有的get和set方法,在方法体中进行合理值的判断;
(3)在构造方法中调用set方法进行合理值的判断;
public class Person {
// 1.私有化成员变量,使用private关键字修饰
private String name;
private int age;
// 提升为类层 级,被所有对象共享,使用类名.的方式访问
//private String country;
public static String country;
// 3.在构造方法中调用set方法进行合理值的判断
public Person() {
}
public Person(String name, int age, String country) {
setName(name);
setAge(age);
setCountry(country);
}
// 2.提供公有的get和set方法,并在方法体中进行合理值的判断
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
if(age > 0 && age < 150) {
this.age = age;
} else {
System.out.println("年龄不合理!!!");
}
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
// 自定义成员方法实现所有特征的打印
public void show() {
System.out.println("我是" + getName() + ",今年" + getAge() + "岁了,来自" + getCountry());
}
}
static关键字
基本概念
通常情况下成员变量隶属于对象层级,创建一个新的对象后就会申请独立的存储空间存放该对象独有的成员变量信息,
若所有对象中某个成员变量的数值一定相同时还要在每个对象中单独存储,此时会造成内存空间的浪费。
为了解决上述问题,在Java语言中使用static关键字修饰成员变量表示静态的含义,
此时成员变量由对象层级提升到类层级被所有对象共享,该成员变量随着类的加载准备就绪与是否创建对象无关。
static关键字还可以修饰成员方法,虽然可以使用引用.访问,但推荐使用类名.访问
使用方式
(1)非静态成员方法中既能访问非静态的成员也能访问静态的成员;
(成员:成员变量 + 成员方法, 静态成员被所有对象共享)
(2)静态成员方法中只能访问静态的成员不能访问非静态的成员;
(成员:成员变量 + 成员方法, 非静态成员隶属于对象层级,此时可能没有对象)
(3)只有隶属于类层级被所有对象共享的内容才可以使用static关键字修饰;
(不能滥用static关键字)
public class StaticTest {
private int cnt = 1; // 隶属于对象层级,每个对象都拥有独立的一份
private static int snt = 2; // 隶属于类层级,所有对象共享同一份
// 隶属于对象层级
public void show() {
System.out.println("cnt = " + this.cnt); // ok 1
System.out.println("snt = " + this.snt); // ok 2
}
// 隶属于类层级
public static void test() {
//System.out.println("cnt = " + cnt); // error
System.out.println("snt = " + snt); // ok 2
}
public static void main(String[] args) {
StaticTest.test();
StaticTest st = new StaticTest();
st.show();
}
}
单例设计模式
(1)基本概念
在某些特殊场合中,一个类对外提供且只提供一个对象,这样的类叫做单例类。
而设计单例类的思想和模式叫做单例设计模式。
(2)实现流程
a.私有化构造方法,使用private关键字修饰;
b.声明本类类型的引用指向本类的对象,并使用private static关键字共同修饰;
c.提供公有的get方法负责将对象返回出去,并使用static关键字修饰;
public class Singleton {
private static Singleton sin = new Singleton(); // 饿汉式
private Singleton(){}
public static Singleton getInstance() {
return sin;
}
}
public class Singleton {
private static Singleton sin = null; // 懒汉式
private Singleton(){}
public static Singleton getInstance() {
if(null == sin) {
sin = new Singleton();
}
return sin;
}
}
(3)实现方式
单例设计模式的实现方式有两种:饿汉式 和 懒汉式,以后开发中推荐饿汉式。
继承
基本概念
当多个类之间拥有相同的特征和行为时,可以将相同的内容提取出来组成一个公共类,
让多个类分别吸收公共类中已有的特征和行为,而在多个类中只需要编写自己独有特征和行为的机制,就叫做继承。
使用继承可以提高代码的复用性、扩展性以及可维护性。
在Java语言中使用extends(扩展)关键字来表达继承关系。
如:
public class Worker extends Person{} - 表示Worker类继承自Person类
其中Person类叫做基类、父类以及超类。
其中Worker类叫做派生类、子类以及孩子类。
注意事项
(1)子类不可以继承父类的构造方法和私有方法,私有特征可以继承但不能访问;
(2)当构造子类对象时会自动调用父类的无参构造方法来初始化从父类中继承下来的
成员变量,相当于在子类构造方法的第一行增加代码:super()的效果;
(3)Java语言中只支持单继承,也就是一个子类只能有一个父类,一个父类可以有
多个子类。
(4)使用继承必须满足逻辑关系:子类 is a 父类。
方法的重写
(1)基本概念
从父类中继承下来的方法不足以满足子类的需求时,就需要在子类中重新写一个与父类一样的方法来覆盖从父类中继承下来的版本,该方式就叫做方法的重写。
(2)方法重写的原则
a.要求方法名相同、参数列表相同、返回值类型相同,jdk1.5开始可以返回子类类型
b.要求访问权限不能变小,可以相同或者变大。
c.要求不能抛出更大的异常(异常机制)。
访问控制
常用的访问控制符
访问控制符 | 访问权限 | 本类内部 | 本包中的类 | 子类 | 其它包中类 |
---|---|---|---|---|---|
public | 公有的 | ok | ok | ok | ok |
protected | 保护的 | ok | ok | ok | no |
啥也不写 | 默认的 | ok | ok | no | no |
private | 私有的 | ok | no | no | no |
包的定义
package 包名;
package 包名1.包名2...包名n;
final关键字
基本概念
final本意为"最终的,不可更改的",该关键字可以修饰类、成员方法以及成员变量等
使用方式
final关键字修饰类体现在该类不能被继承。
- 主要用于防止滥用继承,如:java.lang.String类等
final关键字修饰成员方法体现在该方法不能被重写可以被继承。
- 主要用于防止不经意间造成方法的重写。
- 如:java.text.DateFormat类format方法等
final关键字修饰成员变量体现在该变量必须初始化而且不能更改。
- 主要用于防止不经意间造成数值的改变。
- 如:java.lang.Thread类中的MAX_PRIORITY等
扩展:
在以后的开发中很少单独使用static或final关键字修饰成员变量,
通常都是使用public static final共同修饰成员变量来表达常量的含义,
常量的命名规则就是所有字母大写不同单词之间采用下划线连接,
如:
public static final double PI = 3.14;
多态
基本概念
多态主要指同一种事物表现出来的多种形态。
饮料:可乐、雪碧、脉动、红牛、...
宠物:猫、狗、鸟、鱼、小强、...
人: 学生、教师、工人、保安、...
语法格式
父类类型 引用变量名 = new 子类类型();
如:
Person pw = new Worker();
pw.show();
解析:
编译阶段调用Person类中的show方法,运行阶段调用Worker类中的show方法。
多态的效果
(1)当父类类型的引用指向子类对象时,父类类型的引用可以直接调用父类中独有的方法
(2)当父类类型的引用指向子类对象时,父类类型的引用不可以直接调用子类独有的方法
(3)对于父子类都拥有的非静态成员方法来说,编译阶段调用父类版本,运行阶段调用
子类版本。
(4)对于父子类都拥有的静态成员方法来说,编译阶段和运行阶段都调用父类版本。
引用数据类型之间的转换
(1)引用数据类型之间的转换分为:自动类型转换 和 强制类型转换。
其中自动类型转换主要指从小类型到大类型的转换,也就是从子类到父类的转换。
其中强制类型转换主要指从大类型到小类型的转换,也就是从父类到子类的转换。
(2)引用数据类型之间的转换必须发生在父子类之间,否则编译报错。
(3)若目标类型不是引用变量指向的对象类型,则编译通过运行发生类型转换异常。
(4)为了避免上述错误的发生则应该进行判断,具体方式如下:
if(引用变量名 instanceof 目标类型) - 用于判断引用指向的对象是否为目标类型
实际意义
多态的实际意义在于屏蔽不同子类的差异性实现通用的编程带来不同的结果。
抽象类
抽象方法的概念
抽象方法主要指不能具体实现的方法并且使用abstract关键字修饰,格式如下:
访问权限 abstract 返回值类型 成员方法名(形参列表);
如:
public abstract void cry();
抽象类的概念
抽象类主要指不能具体实例化的类并且使用abstract关键字修饰。
注意事项
(1)抽象类中可以有成员变量、成员方法以及构造方法。
(2)抽象类中可以没有抽象方法,也可以有抽象方法。
(3)拥有抽象方法的类必须是抽象类,因此真正意义上的抽象类是由abstract关键字
修饰并且拥有抽象方法的类。
实际意义
抽象类的实际意义不在于自身创建对象而在于被继承,当一个类继承抽象类之后必须重写抽象方法,否则该类也变成抽象类。
因此抽象类对子类具有强制性和规范性,叫做 模板设计模式。
经验分享:
在以后的开发中推荐使用多态的方式编写代码,此时抽象类的引用可以直接调用的所有方法一定是抽象类中拥有的方法,
当需要更换子类时只需要将new后面的类型修改而其它地方代码不变就立即生效,从而提高了代码的可维护性。
该方式的缺点在于:父类引用若希望访问子类独有方法,则需要强制类型转换。
接口
基本概念
接口主要指比抽象类还抽象的类,因此不能实例化对象。
定义类的关键字使用class,而定义接口使用interface关键字。
如:
金属接口 货币接口 黄金类
类和接口之间的关系
类和类之间的关系 使用extends关键字表达继承的关系 支持单继承
类和接口之间的关系 使用implements关键字表达实现的关系 支持多实现
接口和接口之间的关系 使用extends关键字表达继承的关系 支持多继承
抽象类和接口之间的主要区别
(1)定义抽象类的关键字是abstract class,而定义接口的关键字是interface。
(2)继承抽象类的关键字是extends,而实现接口的关键字是implements。
(3)继承抽象类支持单继承,而实现接口支持多实现。
(4)抽象类中可以有构造方法,而接口中不可以有构造方法。
(5)抽象类中可以有成员变量,而接口中只可以有常量。
(6)抽象类中可以有成员方法,而接口中只可以有抽象方法。
(7)抽象类中增加方法可以不影响子类,而接口中增加方法通常都会影响实现类。
(8)从jdk1.8开始允许接口中有非抽象方法,但必须使用default关键字修饰。
匿名内部类
语法格式
接口/父类类型 引用变量名 = new 接口/父类类型(){ 方法的重写 };
public class Main {
public static void main(String[] args) {
Outer o = new Outer();
o.print();
o.method1();
}
}
/*
* 匿名内部类
* 就是内部类的简化写法
* 前提:存在一个类或者接口
* 这里的类可以是具体类也可以是抽象类
* 格式:
* new 类名或者接口名() {
* 重写方法;
* };
*本质是:
* 是一个继承了该类或者是实现了该接口的子类匿名对象
* */
//创建一个接口
interface Inter {
public void method();
}
//先用有名字的内部类实现外部接口
class Outer {
//内部类实现外部接口
class Inner implements Inter {
public void method(){
System.out.println("这是一个内部类实现接口");
}
}
//再整个方法
public void print(){
Inner i = new Inner();
i.method();
}
//匿名内部其实是局部内部类,必须放在方法中
public void method1 (){
//注意匿名内部类的书写格式
//new 后面如果是类名就是继承自父类的名字
//如果是接口名,就是这个类要实现的接口
//整个这个代表子类对象,那么可以调用这个重写后的method方法。要好好理解下
new Inter () {//实现Inter接口
public void method(){
System.out.println("我是一个匿名内部类");
}
}.method();//这后面这个分号不能丢了
}
}
public class Nimingneibulei1 {
public static void main (String[] args){
Test1 t = new Test1();
t.method();
}
}
//匿名内部类中重写多个方法调用
interface Inter1 {
public void show1();
public void show2();
}
class Test1 {
//通过如下例子我们就建议匿名内部类就用在只调用一个方法的情况
public void method(){
//匿名内部类
// new Inter1() {
//
// public void show1(){
//
// System.out.println("这是show1方法");
// }
// public void show2(){
// System.out.println("这是show2方法");
// }
//
// }.show1();
//
// new Inter1() {
//
// public void show1(){
//
// System.out.println("这是show1方法");
// }
// public void show2(){
// System.out.println("这是show2方法");
// }
//
// }.show2();
//换个简单点的写法
Inter1 i = new Inter1() {
public void show1(){
System.out.println("这是show1方法");
}
public void show2(){
System.out.println("这是show2方法");
}
};
i.show1();
i.show2();
}
}
public class Nimingneibulei2 {
public static void main (String[] args){
//我们要求是调用PersonDemo中的method方法
PersonDemo pd = new PersonDemo();
//这里要求我们传一个抽象类对象,但是抽象类对象不能够初始化。
//所以要我们传一个匿名内部类进去,这里不能直接传抽象类对象,我们可以
//传两种,要么传有名的子类对象,要么传匿名内部对象
// Person p = new Student();
// pd.method(p);
//我们用匿名内部类来做一下--匿名内部类当做参数传递,把匿名内部类当做一个对象
pd.method(new Person(){
//重写show()方法
public void show(){
System.out.println("匿名内部类实现");
}
});
}
}
//写个抽象类
abstract class Person{
public abstract void show();
}
//普通类
class PersonDemo {
public void method(Person p){
p.show();
}
}
//再创建个子类对象去继承抽象类
class Student extends Person {
public void show(){
System.out.println("我是一个有名的子类");
}
}
经验的分享
当接口类型的引用作为方法的形参时,实参的传递方式主要有两种:
a.自定义类实现接口,然后创建该类的对象作为实参传递。
b.使用匿名内部类得到接口类型的引用作为实参传递。