面向对象的三大特征:封装、继承、多态
1.1 封装
1.1.1 封装简介
封装,英文单词Encapsulation。
从广义的角度来说,将一块经常要使用的代码片段,定义到方法中,是封装。将多个方法和多个状态数据定义到类体中,也是一种封装。
从狭义的角度来说,java的封装,就是把类的属性私有化(private修饰),再通过公有方法(public)进行访问和修改;
1.1.2 属性封装
1)为什么封装?
一个类中的某一些属性,不希望直接暴露给外界,让外界直接操作。因为如果让外界直接操作的话,对 这个属性进行的值的设置,可能不是我们想要的(可能不符合逻辑)。此时就需要将这个属性封装起来,不让外界直接访问。
下面使用案例来演示一下不合逻辑的地方:
class Person { String name; int age; } class Program { public static void main(String[] args) { // 实例化一个Person对象 Person xiaoming = new Person(); xiaoming.name = "xiaoming"; xiaoming.age = -10000; } }
2)如何封装
1、为了不让外界直接访问某些属性,用关键字private修饰这些属性。
2 、提供共有的getter/setter的方法,用来操作这个被私有化的属性。
public class Person { String name; private int age; // 1、将属性私有化起来,不让外界直接访问 // 2、给要访问的属性,设置对应的 setter/getter 方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } }
3)疑问:为什么私有化起来的属性,不希望外界访问,还需要再提供setter和getter
答:可以通过指定的方式访问属性,在这些方法中,可以添加一些数据处理操作
public void setAge(int age) { if (age >= 0 && age <= 120) { this.age = age; } }
2.2 单例设计模式
2.2.1 简介
==设计模式==(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的代码设计的经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。
毫无疑问,设计模式于己于他人于系统都是多赢的,设计模式使代码编制真正工程化,设计模式是软件工程的基石,如同大厦的一块块砖石一样。项目中合理的运用设计模式可以完美的解决很多问题,每种模式在现在中都有相应的原理来与之对应,每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是它能被广泛应用的原因。
总体来说设计模式分为三大类:
-
创建型模式(5种):工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。
-
结构型模式(7种):适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
-
行为型模式(11种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。
在这里,我们只介绍和学习其中的单例模式:
==单例模式==(Singleton Pattern)是一种常用的软件设计模式,属于创建型模式之一。它的目的是确保一个类只有一个实例,并提供一个全局访问点。
使用场景:
-
频繁创建和销毁的对象:如果对象创建和销毁的成本较高,且在程序运行期间需要频繁访问,使用单例模式可以提高效率。
-
控制资源访问:例如,数据库连接、日志对象、配置管理器等,这些资源通常希望在整个应用中只有一份实例。
-
工具类:对于一些工具类,如缓存、对话框、注册表设置等,使用单例模式可以简化代码,避免重复实例化。
2.2.2 单例的饿汉模式
-
提供一个private权限的、静态的、当前类的属性,并在静态代码段中进行实例化。
-
构造方法私有化,杜绝从外界通过new的方式实例化对象的可能性。
-
提供一个public权限的静态方法,获取一个当前类的对象。
public class Boss { // 1、设计一个私有的、静态的、当前类的对象 private static Boss instance; static { // 对instance静态对象进行实例化 instance = new Boss(); } // 2、将单例类的构造方法私有化,杜绝从外界通过new的方式实例化对象的可能性。 private Boss() { System.out.println("一个Boss对象出现了 "); } // 3、需要提供一个public权限的静态方法,可以获取一个当前类的对象。 public static Boss getCurrentBoss() { return instance; } }
2.2.3 单例的懒汉模式
-
提供一个private权限的、静态的、当前类的属性
-
构造方法私有化,杜绝从外界通过new的方式实例化对象的可能性。
-
提供一个public权限的静态方法,负责创建单例对象,第一次调用时会初始化单例,后续调用则直接返回已存在的单例。
public class Master { private static Master instance; private Master() { System.out.println("一个Chairman对象被实例化了 "); } public static Master getMaster() { // 使用到instance对象的时候,判断是不是null if (instance == null) { // 实例化 instance = new Master(); } return instance; } }
2.2.4 两者比较
基本都是一样的,都可以获取到一个类的唯一的对象。
1 、在没有使用获取当前类对象之前,懒汉式单例比饿汉式单例在内存上有较少的资源占用。
2 、懒汉式单例在多线程的环境下有问题。需要考虑线程安全(学到线程再说)
3.3 继承
3.3.1 继承简介
是面向对象最显著的一个特征。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。
这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。比如可以先定义一个类叫车,车有以下属性:车体大小,颜色,方向盘,轮胎,而又由车这个类派生出轿车和卡车两个类,为轿车添加一个小后备箱,而为卡车添加一个大货箱。
已有的类,叫==父类==,又叫基类,超类。 派生出来的新类,叫==子类==,也叫派生类。
使用关键字extends 来表示子类继承了父类,语法如下:
修饰词 class 子类名 extends 父类名{ //子类的类体 }
例如:
class A{} //父类 class B extends A{} //B是A的子类 class C extends B{} //C是B的子类
3.3.2 继承特点
1、Java只支持单继承,即一个类只能有一个父类;但是一个类可以有多个子类。
2 、java支持多重继承,即一个类在继承自一个父类的同时,还可以被其他类继承。 可见继承具有传递性
3、子类继承了父类的所有成员变量和方法,包括私有的(只不过没有访问权限),当然也包括静态成员。
4、子类不会继承父类的构造方法,只能调用父类里的构造方法,并且一定至少有一个子类构造器调用了父类的构造器。
5 、子类在拥有父类的成员的基础上,还可以添加新成员。
3.3.3 继承中的构造器
一个对象在实例化的时候,需要在堆上开辟空间,堆中的空间分为两部分,分别是从父类继承到的属性,子类特有的属性。而实例化父类部分的时候,需要调用父类中的构造方法。
值得注意的是,默认调用的是父类中的无参构造器。如果父类中没有无参构造器,那么子类需要显式调用父类中的某一个有参构造器。
在子类的构造方法中,使用==super(有参传参)==调用父类中存在的构造方法,而且==super(有参传参)==必须放在首行首句的位置上。因此可知,==super(有参传参)==和==this(有参传参)==不能在一个构造器中共存。
3.3.4 继承中的方法重写
重写,叫做override。在子类中,对从父类继承到的方法进行重新的实现。 这个过程中,子类重写该方法,会覆盖掉继承自父类中的实现方法,因此,重写又叫做覆写。
为什么重写呢?
因为父类的方法逻辑不能满足子类的需求了,因此子类需要修改逻辑(重写)
重写的特点:
-- 子类只能重写父类中存在的方法。 -- 重写时,子类中的方法名和参数要与父类保持一致。(区别于重载overload) -- 返回值类型:必须和父类方法的返回值类型相同,或者是其子类型。 -- 访问权限:子类重写方法的访问权限必须大于等于父类方法的访问权限。
注解@Override
这个注解,用在重写的方法之前,表示验证这个方法是否是一个重写的方法。如果是,程序没有问题。如果 不是,程序会报错。 因为我们在进行方法重写的时候,没有什么提示的,因此,在进行重写之前,最好加 上去这个注解。 误区:加了@Override就是重写,没有加@Override就不是重写。 这种说法是错误的! @Override只是进行的一个语法校验,与是不是重写无关。
面试题 : 简述 Override 和 Overload 的区别
Override: 是重写,是子类对父类的方法进行重新实现。
Overload: 是重载,是对同一个类中的同名、不同参数方法的描述。
3.3.5 Object类型
Object类,是Java中的根类。所有的类都直接或者间接的继承自Object类。因为,所有的类都直接或者间接的继承自Object类,因此在Object类中定义的属性、方法,在所有的类中都有包含。 比如常用的方法 hashCode(),equals(),toString(),getClass(),wait(),notify(),notifyAll()等
1)toString()方法
源码如下:
public String toString() { return getClass().getName() + "@" + Integer.toHexString(hashCode()); }
该方法的作用是将对象的信息变成字符串。 Object里返回的是 “类名@16进制” 。这个返回结果意义不大。看不到对象的成员变量的信息。因次一般都需要重写。
该方法。在使用输出语句打印对象的变量时,会自动调用。
@Override public String toString() { return "Teacher{" +"name='" + name + '\'' +", age=" + age +", gender=" + gender +'}'; }
2)equals()方法
源码如下:
public boolean equals(Object obj){ return this==obj; // == 比较的是地址值。 }
上述的源码的意义所在:比较this和传进来的obj 是不是同一个对象,如果是,则返回true,不是的话,返回false。
而我们大多时候的需求,并不是比较是不是同一个对象,而是比较两个对象的属性值是否相同。 因此:自定义类型时,应该重写eqauls方法。
重写要遵循一些原则:
1、如果 obj = null,一定要返回false。 2、如果 obj = this,一定要返回true。 3、如果两个对象的类型不同,一定要返回false。 4、如果 a.equals(b) 成立,则 b.equals(a) 也必须成立。 5、如果 a.equals(b), b.equals(c) 成立,则 a.equals(c) 也必须成立。
3)hashCode()方法
该方法返回的是一个int类型的值。 表示对象在内存堆中的一个算法值。 在自定义类型时,一般都需要重写该方法
4)getClass()方法
方法原型 : public final native Class<?> getClass(); 方法作用 : 获取一个用来描述指定类型的Class类对象。获取一个指定的对象的类型。 这个方法,不能被重写。
3.3.6 final修饰词
String s1 = “你好”
String s2 = s1 + “中国”
String s3 = s1+s2
final是java语法中的修饰词,可以用来修饰类,属性,方法,局部变量。 final是“最后,最终”的含义
--1. 修饰类时,不能再有子类,即不能被继承, 比如String, 八大基本数据类型的包装类 --2. 修饰属性: 只能赋值一次,即要么直接初始化,要么在构造器初始化。 --3. 修饰方法: final修饰的方法,不能被重写, --4. 修饰局部变量, 只能赋值1次,不能第二次赋值。
3.3.7 static修饰词
static修饰的内容,都是属于公有资源,不属于对象,而是属于类的,因此都是类名调用
1) static修饰属性
-- 属于类的,应该使用类名调用, 可以使用对象变量.调用,但是不合理。 因为对象一旦对该属性进行了修改,势必会影响以后其他对象的使用。 因此,static修饰的属性,迎合配合 final 一起修饰,来表示该属性是一个常量。 常量: 命名时,字母都大写,多个单词使用下划线连接。应该使用 public static final修饰。
2) static修饰代码块
在类加载期间执行,只执行一次,适用于加载一些静态资源,比如图片,音频,视频等
3)static可以修饰方法
-- static修饰的方法,应该使用类名调用。 -- static修饰的方法,不能再被重写
package com.day11.static0; public class Test03 extends A{ public static void main(String[] args) { Test03.f1(); // 使用子类名调用父类里的静态方法不合理 //应该使用f1所在类的类名调用 A.f1(); } //不是重写A里的f1 public static void f1(){ System.out.println("------Test03------"); } } class A{ public static void f1(){ System.out.println("------A------"); } }
4)static修饰类
static可以修饰类,但必须是内部类,(类体中可以再定义类结构)
4.4 多态
4.4.1 简介
多态:从字面上理解,就是多种形态,多种状态的含义,在这里,指的是一个对象具有多种形态的特点。说的再简单点,就是一个对象可以从一种类型转换为另外一种类型。有向上转型和向下转型两种形式
4.4.2 向上造型(向上转型)
-
父类型的变量引用子类型的对象。
-
向上转型肯定会成功,是一个隐式转换。
-
向上转型后的对象,将只能够访问父类中的成员(编译期间,看变量类型)
-
如果调用的是重写过的方法,那么调用的一定是重写方法(运行期间,看对象)
-
应用场景:在定义方法时,形式参数是父类型的变量。这样更加灵活,可以传任意子类型的对象
父类型 变量 = new 子类型(); //"Is-a"的关系 Animal a = new Cat() a.noise(); noise(){ ....... } noise(){ "汪汪汪" }
4.4.3 向下转型
-
父类型变量赋值给子类型的变量,需要强制转换,是一个显式转换。
-
可能会失败,失败的话,会报类造型异常ClassCastException
-
为了避免ClassCastException ,可以使用instanceof 来判断:变量指向的对象是否属于某一个类型。
子类型 变量名 = (子类型)父类型变量
// 1、实例化一个Dog对象,并且向上转型 Animal animal = new Dog(); // 2、判断类型,判断animal指向的对象是不是一个Cat类型 if (animal instanceof Cat) { System.out.println("animal 的确是一个Cat对象,可以进行向下转型 "); } else { System.out.println("animal不是一个 Cat对象,不能进行向下转型 "); }