5. 面向对象
5.1. 面向对象的简介
面向过程
是一种看待问题、解决问题的思维方式。着眼点在于,问题是怎样一步步的解决的,然后亲力亲为的去解决这个问题。
面向对象
是一种看待问题、解决问题的思维方式。着眼点在于,找到一个能够帮助解决问题的实体,然后委托这个实体帮助解决问题。
Java是一种面向对象的语言。
可以使用Java这门编程语言,更容易的写出具有面向对象编程思想的代码。
Java是一种面向对象的语言,因此用Java写的程序一定是面向对象的程序。
5.2. 类的设计与对象的实例化
对象: 可以帮助解决问题的实体,就是一个对象。
万物皆对象
类: 是由若干个具有相同的特征和行为的对象组成的一个集合。
类和对象的关系: 类是对象的集合 , 对象是类的个体
备注: 在程序设计中,一定是现有的类,再有的对象。
5.2.1. 类的设计
语法
[访问权限修饰符] class 类名 {
// 类体
// 类的所有的对象共有的特征
// 类的所有的对象共有的行为
}
语法说明:
1. 类名: 是一个标识符, 遵循大驼峰命名法。
2. 特征: 又叫属性, 在类中的体现是一个全局变量。
3. 行为: 在类中的体现,是一个方法。
类是一种自定义的数据类型
5.2.2. 对象的实例化
对象的实例化,需要使用到关键字new,也就是说,类是一种自定义的引用数据类型。
5.3. 构造方法
是一个方法,这个方法和普通方法相比,有不一样的地方。
- 构造方法没有除了访问权限修饰符以外的其他修饰符。
- 构造方法没有返回值。并不是说,返回值类型是void。而是不要写返回值类型部分。
- 构造方法的名字必须和类名相同。
- 构造方法不能被显式调用。构造方法是在实例化一个对象的时候被自动调用。
构造方法默认的提供原则
- 如果一个类中没有写任何的构造方法,此时,这个类中会包含一个系统自动提供的,public权限的无参的构造方法。
- 如果一个类中写构造方法了,此时,系统将不再自动提供任何的构造方法。
构造方法是可以重载的,如何区分调用重载的构造方法?
通过参数区分。
构造方法的实际使用场景
构造方法,作为一个对象生命周期中的第一个方法,一般会在构造方法中,对对象的某一些属性进行初始化的赋值
5.4. 成员访问
访问类中的成员(属性、方法)
5.4.1. 成员分类
类中的成员,大致可以分为两种:静态成员、非静态成员。
5.4.2. 非静态成员
没有使用关键字 static 修饰的成员,就是非静态成员。也叫作实例成员。
非静态的成员,是属于对象的。在访问的时候需要使用对象来访问。而对象也叫作一个类的实例(instance),因此非静态成员,也叫作–实例成员。
5.4.3. 静态成员
使用关键字 static 修饰的成员,就是静态成员。也叫作 类成员。
静态的成员是属于类的。在访问的时候,需要用类来访问。
其实,静态成员也可以用对象访问。只是不推荐。
5.4.5. 静态成员与非静态成员的内存分析
-
非静态属性:空间的开辟,是发生在对象的实例化的时候。
非静态属性的空间,在堆空间中,随着对象的实例化开辟,随着对象的销毁而销毁。
-
静态属性:空间的开辟,是发生在类第一次被加载到jvm内存。(当程序第一次使用到这个类的时候)。
静态属性的空间,是在静态空间中,随着类的加载被开辟,程序运行的过程中,不会销毁。常驻内存。
5.4.6. 总结
- 静态成员,需要用类来访问。
- 非静态成员,需要用对象来访问。
- 在非静态方法中,可以直接访问非静态成员和静态成员。
- 在静态方法中,只能直接访问静态成员。
5.5. this关键字
this在程序中,可以用在非静态的方法中,和构造方法中。
在一个类的方法中,允许出现参数的名字和属性的名字重复。并且这样是合法的。
因为属性的空间开辟在堆上,而参数作为局部变量,空间开辟在栈中。他们在不同的空间中。
在这个方法中,直接写变量的名字,使用的是 参数,而不是属性。
this表示对当前对象的引用
1. 如果用在一个非静态方法中,谁调用这个方法,this就代表谁。
2. 在构造方法中,this表示刚刚被实例化的对象。
在一个类中,访问当前类的属性和方法的时候,在某些情况下,this是可以省略的。
如果省略掉this之后,对程序没有任何的影响,没有任何的歧义,此时的this就是可以省略的。
杀手锏:如果不知道哪些this可以省略,哪些this不能省略。那么全部不要省略。
this()调用当前类中的其他构造方法
在构造方法中,可以使用this()
调用当前类中的其他的构造方法,具体调用哪一个构造方法,由小括号中的实参区分
注意事项:
- 在构造方法中,使用this关键字调用其他的构造方法,这一句话必须写在构造方法中的第一行。也就是说,在构造方法调用的代码之前,不能有任何的语句,包括输出语句。
- 不要循环调用。
5.6. 封装
是面向对象三大特性之一【封装、继承、多态】
封装,是一个比较抽象的概念。可以将一个代码段、一个功能包装起来,方便维护、方便在其他的地方去使用这部分功能。
封装可以分为 广义的封装 和 狭义的封装。
广义的封装:将一个功能封装成一个方法、将一个大的功能集封装成一个模块、…
将某些具有固定格式的数据,封装成一个类的对象…
狭义的封装:将一个类中的某些属性私有化起来,不让外界直接访问。同时提供对应的set和get方法。
某一些属性,如果直接让外界访问。外界对其赋的值可能不是我们想要的。可能这些值从语法讲,没有问题;但是从逻辑上讲,是不可以的。这种情况下,我们就需要将属性私有化起来,不让外界直接访问。
从JavaBean规范出发,所有的属性,都需要私有化。
5.6.1. 单例设计模式
设计模式:由前人总结的,用来解决特定问题的一种解题思路。
单例设计模式:在项目的任意模块、任意位置,某个类的对象只能是唯一的一个。
如果每次需要获取到的类的对象需要时同一个对象,此时,对象的获取不能使用new的方式。因为new代表了开辟一块新的空间。两次new出来的空间一定是不相同的。
实现单例设计模式的步骤
-
把类的构造方法私有化,杜绝从类外通过new的方式实例化对象的可能性。
-
给类添加一个方法,返回一个当前类的对象。
-
单例分为懒汉式单例 和 饿汉式单例
-
声明一个私有的、静态的、当前类的对象
-
饿汉式:直接在声明的同时,对这个静态属性进行实例化
public class Chairman { private static Chairman Instance = new Chairman(); private Chairman() { System.out.println("一个新的Chairman对象被实例化了"); } public static Chairman getChairman() { return Instance; } }
-
懒汉式:声明好静态的当前类对象后,不去实例化。当第一次调用getChairman方法的时候实例化。
public class Chairman { private static Chairman Instance; private Chairman() { System.out.println("一个新的Chairman对象被实例化了"); } public static Chairman getChairman() { if (Instance == null) { Instance = new Chairman(); } return Instance; } }
-
5.7. 包 Package
包,起到了组织代码、组织文件。类似于文件夹。
package: 声明当前文件属于哪一个包,为了给编译后的.class文件找路径。
在程序中,如果需要在一个类中,使用到另外一个包里面的类,有两种方式可以实现:
- 使用类的全限定名:从这个类最外层的包开始,一层一层的往里面找。
- 导包:使用关键字 import 导入指定的包(类)。
5.8. 继承
是面向对象三大特性之一【封装、继承、多态】
5.8.1. 什么是继承
如果有多个相关联的类,具有相同的属性和方法,那么,可以将这些相同的部分提取出来,单独做一个类。
这个被提取出来的,具有公共部分的类,叫做 – 父类,基类,超类(Super Class)
被提取的具有相同的属性方法的类,叫做 – 子类,派生类
他们之间的关系,叫做 – 继承。
子类 继承自 父类
5.8.2. 继承的语法
extends:用来描述继承
class 子类类名 extends 父类类名 {}
5.8.3. 继承的特点
-
Java是单继承的。一个类只能有一个父类,但是一个类可以有多个子类。
-
一个类在继承了父类的同时,还可以被其他的类继承。
-
子类可以访问父类中看得到的成员。
所谓的"看得到的",跟成员的访问权限有关。
父类可以将属性、方法继承给子类。
-
子类在拥有父类成员的基础之上,还可以有自己的成员,而且自己特有的成员,只能被自己和自己的子类访问。
5.8.4. 访问权限修饰符
访问权限:指的是某一个类、属性、方法 可以被访问的范围。
访问权限 | 修饰符 | 可以修饰 | 访问级别 |
---|---|---|---|
公共权限 | public | 类、属性、方法 | 在当前项目的任意位置都可以访问 |
保护权限 | protected | 属性、方法 | 在当前包中,和跨包的子类中可以访问 |
包权限 | - | 类、属性、方法 | 在当前包中可以访问 |
私有权限 | private | 属性、方法 | 在当前类中可以访问 |
5.8.5. 继承中的构造方法
一个对象在实例化的时候,会自动的调用构造方法。
一个对象,在实例化,在堆上分配内存空间的时候,会先实例化父类部分。会优先给从父类继承的属性分配空间。在实例化父类部分的时候,会自动的调用父类中的无参构造方法。
因此,如果父类中没有无参构造,会对子类造成影响,导致子类对象无法正常实例化。
关键字super: 代表对父类对象的引用,类似于this关键字
但是,对于super,一般情况下,我们只在两种场景下使用:
- 在构造方法中,使用super(),调用父类中的构造方法。
- 在非静态方法中、构造方法中,使用super调用父类中的方法。
如何解决这个问题:
- 给父类添加一个无参构造。
- 在子类的构造方法中,手动使用super()调用父类中可见的构造方法。
5.8.6. 方法的重写
方法重写Override:
在子类中,对从父类继承到的方法进行重新实现。
重写,又叫做覆写。在子类中,用子类的实现方式覆盖掉父类的实现方式。
子类从父类中继承到某一个方法,将父类的实现方式抛弃,改成自己的实现方式。
@Override
是系统内置的一个注解,用于方法前。
作用:检测下面的一个方法,是不是一个重写的方法。
注意:这个注解只是起到一个验证的作用,并不意味着没有添加这个注解,就不是一个重写方法了。但是出于规范,一般情况下,重写的方法都需要添加一个@Override
重写对方法的返回值、访问权限的要求
-
重写的方法,访问权限要大于等于父类中方法的访问权限。
public > protected > 包权限 > private
-
重写的方法,返回值类型要小于等于父类方法的返回值类型。
final关键字
可以修饰 | 含义 |
---|---|
变量 | 表示值不可以发生改变,是一个常量 |
类 | 表示最终类,这个类无法被继承 |
方法 | 表示最终方法,这个方法无法被重写 |
5.8.7. Object类
Object是Java的根类,在Java中,所有的类都直接或者间接的继承自Object类。
Object类中的几个方法:
-
getClass
- 获取用来描述当前类的Class对象,可以获取一个类型。
-
equals
-
由于
==
比较,对于对象来说,只能比较地址。因此,在进行比较的时候,很多时候不符合逻辑需求。 -
equals就是一个用来比较两个对象的方法。默认实现,比较的是地址。如果需要自定义比较的规则,可以重写这个方法。
-
注意事项:
虽然我们可以在equals方法中,进行自定义的规则比较。但是,在制定比较规则的时候,还是要遵循一定的规范的。 1. 如果obj是null,一定要返回false 2. 如果this == obj,一定要返回true 3. 如果两个对象的类型不同,一定要返回false 4. a.equals(b)如果是true,则 b.equals(a) 也一定要是true 5. a.equals(b) == true && b.equals(c) == true, 那么 a.equals(c) 一定也得是true
-
-
hashCode
- 获取一个对象的在一个散列集合中的索引。(哈希表)(HashSet、HashMap)
-
toString
- 可以通过重写这个方法,实现自定义的对象的字符串表示形式。
5.9. 多态
是面向对象三大特性之一【封装、继承、多态】
5.9.1. 对象的转型
和数据类型转换比较像。
父类的引用可以指向子类的对象。
- 向上转型
- 由子类类型转型为父类类型。
- 向上转型一定会成功,是一个隐式转换。
- 向上转型后的对象,将只能访问父类中公有的方法和属性。
- 向下转型
- 由父类类型转型为子类类型。
- 向下转型可能会失败,需要显式转换。
- 向下转型后的对象,将可以访问子类中特有的方法和属性。
向下转型失败:
如果在做向下转型的时候,这个对象正好是要转型的类型,则此时会转型成功。但是如果这个对象不是要转型的类型,此时会转型失败。如果转型失败,会出现 ClassCastException。
5.9.2. instanceof关键字
向下转型可能会失败,如果转型失败,就会出现类型转化异常 ClassCastException,因此,在做向下转型的时候,一定要判断要转型的对象是不是指定的类型。
可以使用关键字 instanceof 判断对象是否是要转型的类型。
对象 instanceof 类
判断一个对象是否是指定的类型,判断结果是一个boolean。
5.9.3. 多态
多态:向上转型后的对象,调用父类中的方法,最终的实现是子类的重写实现。
5.10. 抽象类
5.10.1. 抽象类和抽象方法
- 抽象方法:使用关键字abstract修饰的方法,叫做抽象方法
- 抽象方法,只有方法声明,没有方法实现。
- 抽象方法,只能被定义在抽象类中。不允许在一个非抽象类中定义抽象方法。
- 抽象类:使用关键字abstract修饰的类,叫做抽象类
- 抽象类除了可以包含以前所有的成员之外,还可以包含抽象方法。
- 抽象类不能实例化对象。
- 非抽象子类,在继承一个抽象父类的时候,必须实现父类中的抽象方法。(通过重写)
5.10.2. 抽象类的使用场景
结合抽象类和抽象方法的特点:抽象方法,必须写在抽象类中。
得出抽象类的使用场景:
可以通过抽象类和抽象方法,实现某些简单规则的制定
可以在抽象类中,定义若干抽象方法,让所有的非抽象子类必须实现。这些抽象方法,其实都是可以被当做规则。
5.10.2. 抽象类
可以使用抽象类,进行简单的规则制定。约束所有的子类。约束一个规范,让所有的子类都实现这个规范,方便对子类的统一管理。
但是,抽象类的规则制定,是通过继承实现的。而在Java中,继承是单继承,父类只能有一个。因此,如果使用抽象类进行规则的制定,可能会对一个类原有的继承结构造成影响。
另外,因为单继承的存在,一个类最多只能遵守一种规则约束。
为了让一个类可以遵守多个规则约束,引入接口的概念。
5.11. 接口
接口,也是一种自定义的引用数据类型,跟类比较相似。可以使用接口进行规则的指定,对所有的实现类进行统一的约束,使其具有相同的对外的方法和功能。
Java是单继承的。其实很多面向对象的语言,都是单继承的。很多语言在抛弃了多继承的同时,会使用其他的方法间接的实现多继承。而Java就是通过接口间接的实现多继承的。
5.11.1. 接口的定义
接口的定义,需要使用关键字 interface
接口中成员的定义:
- 可以写方法:
- 接口中的方法,默认都是用 public abstract 修饰的。
- 即便接口中的方法没有任何的修饰符,他们依然是 public abstract。
- 可以写属性:
- 接口中的属性,默认的修饰符是 public static final。
- 接口中的属性定义,必须同时赋初始值。
5.11.2. 接口的实现
接口不是类,不能实例化对象。接口写完之后,是要给某一个类去实现的。
类,在实现接口的时候,需要使用关键字 implements
- 接口的实现,对于继承没有影响。一个类可以在继承父类的同时,实现接口。
- 如果一个类既有继承,又有实现。在类的定义部分,先写继承,再写实现。
- 一个类可以实现多个接口,一个接口也可以被多个类实现。
如果一个类实现了一个接口。那么这个类被称为这个接口的 实现类
- 如果一个实现类实现了接口,那么这个实现类中可以 “继承” 到接口中的成员。
实现接口注意事项
- 如果接口方法,在父类中已经被实现了。子类是可以继承到这些方法的,因此,在子类中,可以不去重写实现
- 如果一个类实现的多个接口中,有相同的方法。实现类中只需要实现一次即可。
Java8的新特性
1. static
在Java8中,可以使用static修饰一个接口中的方法。接口中静态的方法,需要添加实现。
接口中的静态方法,只能用接口自己调用。
2. default
在Java8中,可以使用default修饰一个接口中的方法。default方法,也需要添加实现。
使用default修饰的方法,需要用接口的实现类对象来调用。
其实,default就是给接口方法添加一个默认的实现方式,实现类在实现这个接口的时候,可以重写这个方法,也可以不重写这个方法。
5.11.3. 接口的继承
接口之间是有继承的。并且接口之间的继承是多继承。
子接口可以继承到父接口中所有的方法。
5.11.4. 接口的多态
对象转型
接口的引用,可以指向实现类的对象。
- 实现类类型转型为接口类型,是一个向上转型。
- 接口类型转型为实现类类型,是一个向下转型。
多态
向上转型后的对象,去调用接口中的方法,最终的实现是实现类中的实现方式。
5.11.5. 接口在案例
5.11.6. 函数式接口
如果一个接口中,有且只有一个方法,是实现类必须要实现的。这样的接口就叫做函数式接口。
@FunctionalInterface 是一个用来修饰接口的注解。可以去验证一个接口是不是一个函数式接口。
5.12. 内部类
5.12.1. 成员内部类
5.12.2. 静态内部类
5.13.3. 局部内部类
5.13.4. 匿名内部类
匿名内部类,作为内部类部分的重点,也是必须要掌握的。
没有名字的内部类。
匿名内部类,一般是配合其他类一块使用的。表示这个类的子类。
实际应用中,匿名内部类,一般情况下,是配合抽象类或者接口使用的
5.13. lambda表达式
5.13.1. 什么是lambda表达式
lambda表达式是Java8的新特性之一。其实本质上来讲,lambda表达式是一个匿名函数。
实际应用中,lambda表达式一般是配合抽象类或者接口使用的。配合接口的使用场景更多。
可以使用lambda表达式对一个接口进行非常简洁的实现。
Lambda表达式可以简化接口的实现。但是并不是所有的接口都可以使用lambda表达式实现,lambda表达式对接口是有要求的。要求接口是一个函数式接口。
5.13.2. lambda表达式的基础语法
lambda表达式是对接口中的方法进行简单的实现,本质是一个匿名函数。
对于lambda表达式的语法部分,需要重点关注的是:方法的参数列表、方法体
关键的符号: ->
lambda运算符,读作 “goes to”
也就是说,一个完整的lambda表达式,由三部分组成:参数列表、lambda运算符 和 方法体
其中,lambda运算符的作用是分隔参数列表 和 方法体
使用lambda表达式来实现接口中的抽象方法,因此,在使用接口引用调用接口中的方法的时候,实际执行的是lambda表达式中的实现方式。
5.13.3. lambda表达式的语法进阶
- 方法体部分的精简:
- 如果一个lambda表达式的方法体中,只有一句代码,则大括号可以省略。
- 如果lambda表达式方法体中唯一的一句代码,是一个返回语句。在省略了大括号的同时,必须省略return。
- 参数部分精简:
- 可以省略掉参数的类型,因为参数的类型在接口的方法声明中已经明确了。
- 如果有多个参数,那么要么都省略,要么都不省略。不要出现有的省略了,有的没有省略。
- 如果参数列表中有且只有一个参数,则小括号可以省略。
- 可以省略掉参数的类型,因为参数的类型在接口的方法声明中已经明确了。
5.13.4. 函数引用
如果一个lambda表达式的方法体中,只是在调用其他的方法。这种情况下,对于方法的参数、方法体、lambda运算符。都可以省略。直接引用这个要调用的方法即可。
方法引用,分为两部分:
第一部分:引用的方法调用方。
第二部分:引用方方法。
中间使用两个冒号分隔。
方法引用,其实就是用一个已经存在的方法来实现指定的接口中的抽象方法。因此:引用的方法参数和返回值必须和接口中的方法保持一致。
来源:https://blog.csdn.net/a1265262132/article/details/99459821