6、面向对象三大特性
6.1、封装
6.1.1、封装性
封装是一个比较大的概念
把一段功能提取出来,做成一个方法:功能封装
把若干具有相同特征的数据,提取出一个类:类的封装、数据封装
把若干个相关功能的类提取出来:模块封装
6.1.2、属性的封装
如果一个类中的某些属性,不希望外界直接访问,可以将这些属性封装起来。
Q:为什么不希望外界直接访问?
A:因为外界直接访问,可能赋值会出乎我们的逻辑要求。例如:设计一个Person类,有一个属性age,希望这个属性来表示年龄。如果直接让外界访问,外界可以赋值为-10,从语法上讲不错,但是从逻辑上讲,有问题。
关键字 private
private是一个访问权限修饰符,代表私有的。被private修饰的属性、方法,只能在当前的类中访问。
属性的封装
1、使用private,将不希望外界直接访问的属性包装起来,这样外界将不能直接访问。
2、提供相关的访问给外界,用来访问这个属性。
6.1.3、setter、getter方法
可以让外界通过setter方法,对属性进行赋值。通过getter方法,获取属性的值。
这样做的好处:
提供了唯一的用来访问属性的方式,我们就可以在这个方法中添加一些值的过滤。
public class Person {
private int age;
// 对属性的访问:赋值、读取值
public void setAge(int age) {
// 对参数的值做过滤。
if (age >= 0 && age <= 120) {
this.age = age;
}
}
public int getAge() {
return this.age;
}
}
6.1.4、单例设计模式
设计模式
设计模式,是由前人总结出来的,用来解决特定问题的一种解决方案。
单例
Singleton,解决的问题是需要在一个项目的不同模块中,可以访问到同一个类的同一个对象。
单例类的对象,全局唯一。
饿汉式单例
/**
* 把这个类设计为单例类,这个类的对象全局唯一,有且只有一个。
*/
public class Chairman {
// 1、私有化构造方法,杜绝外界通过new的方式来实例化对象的可能性。
private Chairman() {
System.out.println("无参构造方法执行了");
}
// 2、定义一个静态的、私有的、当前类的对象,作为全局唯一的对象,并实例化。
private static Chairman instance = new Chairman();
// 3、提供一个方法,这个方法,可以返回一个全局唯一的当前类的对象。
public static Chairman getInstance() {
return instance;
}
}
懒汉式单例
/**
* 把这个类设计为单例类,这个类的对象全局唯一,有且只有一个。
*/
public class Chairman {
// 1、私有化构造方法,杜绝外界通过new的方式来实例化对象的可能性。
private Chairman() {
System.out.println("无参构造方法执行了");
}
// 2、定义一个静态的、私有的、当前类的对象,作为全局唯一的对象。
private static Chairman instance = null;
// 3、提供一个方法,这个方法,可以返回一个全局唯一的当前类的对象。
public static Chairman getInstance() {
// 逻辑:
// 当外界需要一个单例对象的时候,再对单例对象进行判断
// 如果是null,立即实例化
if (instance == null) {
instance = new Chairman();
}
return instance;
}
}
懒汉式单例,比饿汉式单例,在没有使用到单例对象的时候,有一定的空间优势。
懒汉式单例,在多线程的环境中,会有问题。
6.1.5、包 package
包,起到了一个组织文件、组织代码的作用,类似于文件夹。
文件的第一句话
package com.qianfeng.fsingleton;
这句话的作用,是来表示这个文件应该在哪一个包里面。确定文件中的类,编译之后的.class字节码文件所在的路径。
不同的包中的类该如何访问
1、可以通过类的全限定名来访问。
全限定名:从最外层的包开始,一层层的向里查询,一直到这个类。组成了一个完整的查找路径,这个就是类的全限定名。例如:com.qianfeng.gpackage.subpackage1.Person
2、通过导包的形式。
// 导入了这个类,在这个文件中使用这个类的时候,就不用再使用权限定名。
// import com.qianfeng.gpackage.subpackage1.Person;
// import com.qianfeng.gpackage.subpackage1.Dog;
// 导入这个包下的所有的类(但是不包括子包中的类)
import com.qianfeng.gpackage.subpackage1.*;
注意事项:
- 如果当前包中,和导入的包中有同名的类
- 如果导入的多个包中有同名的类
以上两种情况下,再使用到这个同名的类的时候,需要使用全限定名。
6.2、继承
6.2.1、什么是继承?
如果多个相关联的类中,有相同的属性和方法,那么可以把相同的部分,单独的提取出来,做成一个类。
这个被提取出来的,具有相同部分的类,称为 – 父类。
被提取出来的,具有相同的属性方法的类,称为 – 子类。
他们之间的关系,就是 – 继承。(子类继承自父类)
父类,又叫做 基类。与之对应的,子类叫做 派生类。
由A类派生出B类:A类是基类,B类是派生类。
在某些书籍中,父类,又被称为 – 超类。(super class)
6.2.2、继承的基本语法
描述继承关系,需要使用到关键字 – extends
class SubClass exetnds SuperClass {
// 此时,SubClass是子类,SuperClass是父类
// SubClass 继承自 SuperClass
}
6.2.3、继承的特点
1、Java语言是单继承的语言,一个类只能有一个父类,但是一个类可以有多个子类。
2、一个类在继承了父类的同时,还可以被其他类继承。
3、子类可以访问父类中看得到的成员。
这里所谓的“看得到的”,是由访问权限修饰符来决定的。
4、子类除了拥有父类中的成员之外,还可以定义其他的成员。
6.2.4、继承的使用场景
1、如果多个逻辑上相关联的类之间有相同的成员(属性、方法)。
强调:一定是要在逻辑上关联的类。
2、如果某一个类提供的功能,已经不能满足我们的需求了。需要给这个类拓展功能,可以派生一个子类出来。
因为子类除了拥有父类的所有的成员之外,还可以添加成员。
6.2.5、访问权限修饰符
访问权限:决定了一个类、属性、方法,可以被访问的范围。
访问权限修饰符:使用指定的修饰符,来修饰类、属性、方法,限定这些内容被访问的范围。
访问权限 | 修饰符 | 可以修饰 | 访问范围 |
---|---|---|---|
公共权限 | public | 类、属性、方法 | 在项目的任意位置都可以访问 |
保护权限 | protected | 属性、方法 | 在当前的包中,和跨包的子类中 |
包权限 | - | 类、属性、方法 | 在当前的包中 |
私有权限 | private | 属性、方法 | 在当前的类中 |
访问权限的大小: public > protected > package > private
6.2.6、继承中的构造方法(重点)
子类对象的实例化
子类对象实例化,在堆上开辟空间。其实,在堆上开辟的空间分为两部分:从父类继承到的属性、自己特有的属性。而这两部分的空间开辟是有先后顺序的,先开辟父类的属性空间,再开辟子类特有的属性空间。在给从父类继承到的属性分配空间的时候(以后就称为实例化父类部分),会默认的调用父类中的无参构造方法来实例化。
如果父类没有无参构造
如果父类没有无参构造,则子类对象的实例化会受到影响,将无法进行实例化。
如何解决
1、给父类添加一个无参构造。
2、在子类的构造方法中,手动调用父类中存在的构造方法。
调用父类的构造方法,使用 super() 来调用。
6.2.7、继承中的方法重写(重点)
Override:子类对从父类继承到的方法进行重新实现。
重写,又叫做 覆写。用子类的实现覆盖掉父类的实现。
重写,其实不单单是重写的父类中的方法,还可以重写接口中的方法。
为什么要重写
- 父类提供的方法,已经不能满足子类的需求。
- 对于同样的功能,子类的实现方式和父类不同。
重写的注意事项
1、子类只能重写父类中存在的方法。
2、子类在重写方法的时候,要求方法的名字和参数必须要和父类保持一致。
@Override
用在重写的方法前面。对重写的方法进行验证,验证这个方法是不是重写的方法。
@Override
public void bark() {}
注意事项:
@Override这个注解仅仅起到一个验证的作用。如果一个方法前面不加@Override也可以。
并不是说不加@Override的方法就不是重写的方法。
虽然说这个注解可加可不加,但是习惯上都会加上去。
重写对访问权限、返回值的要求
- 重写的方法,访问权限要大于等于父类方法的访问权限。
- 重写的方法,返回值类型要小于等于父类方法的返回值类型。
重写除了对访问权限、返回值类型有要求,还对异常抛出的类型有要求。
6.2.8、Object类
Object是所有类的根类,Java中所有的类都直接或者间接的继承自Object。
toString()
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
返回一个对象的字符串表示形式。将一个对象转成一个字符串表示形式,会自动的调用toString()。
class Person {
private String name;
private int age;
private char gender;
public Person(String name, int age, char gender) {
super();
this.name = name;
this.age = age;
this.gender = gender;
}
@Override
public String toString() {
return this.name + ", " + this.age + ", " + this.gender;
}
}
如果需要定制一个对象转成字符串之后的格式,则需要重写toString()
getClass()
public final native Class<?> getClass();
获取一个对象所对应的类的描述。
hashCode()
public native int hashCode();
返回的当前对象的哈希表中存储的位置索引。
equals()
判断两个对象是否相同。
public boolean equals(Object obj) {
return (this == obj);
}
如果需要自定义两个对象的比较规则,可以重写equals实现。
equals的重写规范:
1、如果obj是null,一定要返回false;
2、如果this和obj的指向的空间是相同的,一定要返回true;
3、如果this和obj的类型不同,一定要返回false;
4、如果a.equals(b)成立,则b.equals(a)也一定要成立;
5、如果a.equals(b), b.equals(c)都成立,则a.equals(c)也必须要成立;
// 需求:希望如果两个对象的姓名、年龄、性别都是相同的,那么视为是同一个对象
@Override
public boolean equals(Object obj) {
// 1、判断obj是否是null
if (obj == null) {
return false;
}
// 2、比较是否是同一块空间
if (this == obj) {
return true;
}
// 3、判断类型
if (this.getClass() != obj.getClass()) {
return false;
}
// 4、比较属性值
Person other = (Person)obj;
return this.name.equals(other.name) && this.age == other.age && this.gender == other.gender;
}
6.2.9、关键字final
final表示最终,可以修饰类、方法、变量
- final修饰的类:最终类,无法被继承。
- final修饰的方法:最终方法,无法被重写。
- final修饰的变量:最终变量,无法修改值,就是常量。
6.3、多态
6.3.1、对象的转型
对象的转型,其实就是将一个对象转成其他的类型。类似于数据类型转换。
向上转型
- 由子类类型转型为父类类型。
- 向上转型一定会成功,不需要任何额外的操作,是一个隐式转换。
- 向上转型后的对象,将只能够访问父类中存在的成员,子类特有的成员将无法访问。
向下转型
- 由父类类型转型为子类类型。
- 向下转型存在失败的可能性,需要额外进行强制操作,是一个显式转换。
- 向下转型后的对象,将可以访问子类中特有的成员。
6.3.2、instanceof关键字
向下转型可能会失败,如果转型失败,会抛出 ClassCastException 异常。
因为向下转型存在失败的可能性,因此,在向下转型之前,最好先判断一下要转型的对象是不是指定的类型。
判断对象是否是指定的类型,可以使用 instanceof 关键字。
boolean ret = obj instanceof Type;
Dog dog = new Dog();
Animal animal = dog; // 向上转型
if (animal instanceof Dog) { // 类型判断
Dog d = (Dog)animal; // 向下转型
}
6.3.3、多态的体现
1、父类的引用,可以指向子类的对象。
向上转型后的对象,调用父类中的方法,最终的实现,是子类中的实现。这就是多态。
向上转型后的对象,调用接口中的方法,最终的实现,是实现类中的实现。