Java的编程逻辑(Java基础)(SE传)【三】
继承 与 多态 与 单例设计模式
一、类的继承
1.类的继承的基本概念
计算机程序经常用类之间的继承关系来表示对象之间的分类关系。
在继承关系中,有父类也有子类,比如person类和student类,person类是父类,student类是子类继承了person人的属性和方法。父类也叫做基类,子类也叫做派生类。
继承是指子类继承了父类的属性和行为,父类有的属性和行为子类都有。
但是子类也可以增加特有的属性和行为,比如说student类有个行为叫readBook(),是子类特有的行为。
父类
public class Person { //父类
private static final String DEFAULT_RACE = "Asian"; //默认属性
private String race; //人种
//调了个空参构造器和一个带参构造器
public Person(){
this(DEFAULT_RACE);
}
public Person(String race) {
this.race = race;
}
//race的get和set方法
public String getRace() {
return race;
}
public void setRace(String race) {
this.race = race;
}
//人都会的公共行为
public void speak(){
System.out.println("人会说话");
}
public void walk(){
System.out.println("人会走路");
}
}
子类
public class Student extends Person { //继承了Person类的属性和行为
private static final String DEFAULT_FAVORITE_BOOK = "Journey to the West";//西游记
private String favorite_book;//子类特有的属性
public Student(){ //空参构造器
this(DEFAULT_FAVORITE_BOOK);
}
public Student(String favorite_book) { //带参构造器
this.favorite_book = favorite_book;
}
public void readBook() { //Student类特有的行为
System.out.println("学生会读书");
}
//get和set特有属性的方法
public String getFavorite_book() {
return favorite_book;
}
public void setFavorite_book(String favorite_book) {
this.favorite_book = favorite_book;
}
@Override //重写了父类的walk()方法
public void walk(){
System.out.println("学生不仅仅会走路还会跑步");
}
}
Java中支持类的单继承和多层继承关系,但不支持多继承,
即一个类只能继承一个类,而不支持同时继承多个类。
class xxx (子类名)extends (父类名){}
extends是Java的关键字,Java继承只能直接继承父类的公有属性和公有方法,而隐含地(不可见的)继承了私有属性,被final修饰的方法不能被子类覆写。只有非static才考虑重写。
2.根父类Object
在Java中,如果没有显示的声明父类,也有一个隐含的父类,继承于java.lang.Object类。
所有的Java类直接或者间接继承Object类,所有的Java类都具备Object类的功能。
Object没有定义属性,但定义了一些静态方法,可以直接调用。
2.1 关于equals()方法
equals()方法是Object类中使用频率较高的一种方法。
在equals()方法使用中有几点事项:
- equals()是一个方法,而非运算符。
- 只适用于引用数据类型
- 像String、Data、File、包装类都重写了equals()方法,比较的不再是地址而是内中值。
equals()方法的重写
重写equals方法的目的是判断两个对象的内容 (内容可以有很多,比如同时比较姓名和年龄,同时相同的才是用一个对象)是否相同。 如果不重写equals,那么比较的将是对象的引用是否指向同一块内存地址,重写之后目的是为了比较两个对象的value值是否相等。
Java重写equals方法(重点讲解).
2.2 关于toString()方法
toString方法也是Object类中使用频率较高的一种方法。
在Object类中toString()方法使用中也有几点事项:
- 当我们输入一个对象的引用时,实际上就是调用了当前对象的toSring()方法。
- 像String、Data、File、包装类等都重写了Object类中的toString()方法,使得在调用toSring()时,返回的是“实体内实体本身”的信息。
- 每个Java类型都会从Object中继承tostring()方法,因此在任何对象调用中都能够调用任意对象的toSring()方法。
- 如果一个对象没有实现toString()方法,那么就会调用Object类中的toString()方法默认实现。
- 默认实现的大多都没有实用价值,因为它只会返还一个含有该对象内存地址的字符串。因此我们通常会重写tiString()方法,来达到我们需要的效果。
二、类的多态
多态性是面向对象编程的又一个重要特征,它是指在父类中定义的属性和方法被子类继承之后,可以具有不同的数据类型或表现出不同的行为,这使得同一个属性或方法在父类及其各个子类中具有不同的含义。
多态性:一种事物的多种形态。
对象的多态性:父类的引用指向子类的对象。
当调用子父同名同参方法的时候,实际调用执行的是子类重写过的父类的方法。
我们看代码的一些实现,除了我们上述中创建的两个类,Person类和Student类。
我们还实现两个类,Grade_leader类和Teach类
Grade_leader 类
public class Grade_leader extends Person{
private String department; //特有属性部门
//不存在无参构造器,必须有一个部门
public Grade_leader(String department) {
this.department = department;
}
//使用到super关键字,表示调用父类带race参数的构造方法。调用父类构造方法时,必须放在第一位。
public Grade_leader(String race, String department) {
super(race);
this.department = department;
}
//Department部门的get和set方法
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
//重写tool()方法
@Override
public void tool(){
System.out.println(department+"校领导会使用办公软件");
}
}
Teacher类
//这里的Teacher的父类是Grade_leader类不是person类,展现的是继承的层级性
public class Teacher extends Grade_leader{
private static final String DEFAULT_SUBJECT = "Humanities";//有个默认值
private String Subject ;
public Teacher(String department,String subject) {
super(department);
this.Subject = subject;
}
public Teacher(String race, String department, String subject) {
super(race, department);
this.Subject = subject;
}
public String getSubject() {
return Subject;
}
public void setSubject(String subject) {
Subject = subject;
}
@Override
public void tool(){
System.out.println(getSubject()+"老师会使用教具,是"+getDepartment()+"部门");
}
}
使用继承的好处就是可以统一处理不同子类型的对象。
比如说,我们看一个人物身份信息的控制面板,它负责了管理所有不同身份的人,在调用他们的tool()方法的时候,
只需要当作person对象调用tool()方法就行,系统会自动执行子类的tool()方法。
PeopleManagement使用一个数组保存所有的person,在tool方法调用中每个person的tool方法。 PeopleManagement并不知道每个person具体类型,也不关心,但可以调用到子类的tool方法。
比如我们看下述代码:
public class PeopleManagement {
private static final int MAX_NUM = 100; //最大处理人数
private Person[] persons = new Person[MAX_NUM]; //创建一个存放Person类的数组
private int personsNum = 0;//初始值为0
public void addperson(Person person) { //添加对象
if (personsNum < MAX_NUM) {//如果没超最大人口
persons[personsNum++] = person; //添加进来
}
}
public void tool() { //添加进数组里的所有对象都调用他们各自的tool方法
for (int i = 0; i < personsNum; i++) {
persons[i].tool();
}
}
//PeopleManagement使用一个数组保存所有的person,在tool方法调用中每个person的tool方法。
//PeopleManagement并不知道每个person具体类型,也不关心,但可以调用到子类的tool方法。
public static void main(String[] args) {
PeopleManagement management = new PeopleManagement();
//设置好初始值
management.addperson(new Student());
management.addperson(new Student("Water_Margin"));
management.addperson(new Grade_leader("Finance Department"));
management.addperson(new Teacher("Teaching Department"));
management.addperson(new Teacher("Teaching Department", "math"));
management.addperson(new Teacher("American", "Teaching Department", "English"));
management.tool();
}
}
需要特别说的是在addperson()方法中,参数Person person,
声明类型是Person的,而实际的类型可以是Student、Grade_leader、Teacher 类。子类对象赋值给父类引用变量,这叫向上转型。
转型就是转换类型,向上转型就是转换父类类型。
变量person可以任意引用任何Person子类类型对象,这叫多态,即一种类型变量,可以引用各种多种实际类型对象。
这样,对于变量person,它就有两个类型:Person类型,称之为person的静态类型;类型Student、Grade_leader、Teacher ,我们称之为动态类型。
访问绑定到变量的静态类型,称之为静态绑定。
实例变量、静态变量、静态方法、private方法、都是静态绑定的
PeopleManagement 类中的 tool() 方法中,persons[ i ].tool()调用的是其对应动态类型的tool()方法,这称之为方法的动态绑定
之所以,是因为Java中创建对象的代码和操作对象的代码,经常不在一块,操作对象的代码往往只知道对象是某种父类型,也往往只需要知道它是某种父类型就可以了。
2.1 static关键字的使用:(静态的)(类变量)
static可以修饰:属性,方法,代码块,内部类。
2.1.1 static修饰属性:(静态变量)
- 属性:按是否是使用static分为静态属性和非静态属性(实例变量)。
修改非静态属性值是 不会影响 到其他 同类对象 的值,但是静态变量,static修饰的属性是多个 对象共享的属性,若修改就都会改变。 - 其他说明:1.静态变量随着类的 加载而加载。2.静态变量的加载 早于对象的创建,可以通过“类.静态变量” 的方式进行调用。3.由于类只会加载一次,则静态变量再内存中也只会存在 一份,存在方法域中(静态域中)。
- 静态变量:System.out.Math.PI;
堆:new出来的结构都再堆当中。
栈:存放局部变量。
方法区:类的加载信息,静态域,常量池存放再方法区中。(可以去看看第一篇章)
2.1.2 静态方法和非静态方法
- 静态方法在编译的时候就会调用。
- 静态方法中只能调用静态的方法和属性(生命周期一致才能调用)。
- 非静态方法中,既可以调用非 静态放法 或者 非静态属性,又可以调用 静态方法 或者 静态的属性。
静态方法和静态数据成员会随着类的定义而被分配和装载入内存中;
而非静态方法和非静态数据成员只有在类的对象创建时在对象的内存中才有这个方法的代码段。
2.1.3 static 一些注意
- 在静态方法内,不能使用this、super关键字。
- 在开发中如何确认一个属性或者方法是否需要声明为static属性,可以考虑被多个对象所共享的,不会随着对象的不同而不同的,操作静态属性的方法,以上这些通常设置static的。
- 工具类方法,习惯上也设置为static的,因为没有必要造对象了,只有早对象才能调用非静态的方法。
2.2 多态的使用
有了对象的多态性以后,我们在编译期间,只能调用父类中声明的方法,但在运行期间,我们实际执行的时子类重写父类的方法(编译看左执行看右)。
静态绑定在程序编译阶段即可决定,而动态绑定则要等到程序运行时。
多态的使用前提:
1.要有类的继承关系(有子父类)。
2.要有方法的重写。对象的多态性只适用于方法,不使用于属性,只有方法体现多态性(编译看左边,执行看右边)
2.3 重载和重写
重载:是指方法名称相同 但 参数签名不同(参数个数、类型、顺序不同)。
重写:是指与父类同名同参数签名的方法。
当有多个重名参数的时候,在决定要调用哪个参数过程中,首先是按照参数类型进行匹配的,换句话来说,寻找咋所有重载版本中最匹配的,然后再看变量的动态类型,进行动态绑定。
2.4 父子类型转换
之前例子中,子类型的对象可以赋值给父类型的引用变量,这叫向上转型,那么父类型的变量是否可以赋值给子类型的变量,或者是说可以向下转型吗?
从语法上是可以的,但是不一定成功。
一个父类的变量能不能转换为一个子类的变量,取决于这个父类的动态类型(即引用的对象类型)是不是这个子类或者这个子类的子类。
给定一个父类的变量能不能判断是不是某个子类的对象的父类,从而安全的转换,这里要使用关键字instanceof。
2.5 关键字 instanceof
如何判断是不是子父类关系,Java提供了一个关键字 instanceof
instanceof前面是变量,后面是类,返回值类型是boolean值的,表示变量引用的对象是不是该类或其子类的对象。
a instanceof b:从本质上判断对象a是否是b的实例,如果是?返回true:不是返回false;
即 a instanceof b?true:false;
顺便一句:instanceof的使用可以是为了避免向下转型时的异常classCastException。
小结:
多态和动态绑定时计算机程序的一种重要思维方式,使得操作对象的程序不需要关注对象的实际类型,从而可以统一处理不同的对象,但又能实现每个对象的特有行为。
三、两种单例设计模式:
java中单例模式是一种常见的设计模式,单例模式的写法有好几种,
这里主要介绍三种:懒汉式单例、饿汉式单例。
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
注意:
- 构造器必须私有化。
- 对外必须获得一个公有的访问方式来获得实例。
1. 饿汉式:
public class SingletonTestHungry_Man_style {
public static void main(String[] args) {
//用对象
boolean a;
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank2==bank1);
}
}
class Bank{
//1.私有化类的构造器
private Bank(){
//不让它在外面造对象
}
//2.内部创建类的对象
private static Bank instance = new Bank();
//3.提供一个获得对象的静态办法
public static Bank getInstance(){
return instance;
}
//4.要求对象也是静态的
}
2. 懒汉式:
public class Singleton1 {
private static Singleton1 instance = null;
private Singleton1() {
}
/**
* 1、适用于单线程环境(不推荐)
*/
public static Singleton1 getInstanceA() {
if (null == instance) {
instance = new Singleton1();
}
return instance;
}
/**
* 2、适用于多线程环境,但效率不高(不推荐)
*/
public static synchronized Singleton1 getInstanceB() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
/**
* 3、双重检查加锁(推荐)
*/
public static Singleton1 getInstanceC() {
// 先判断实例是否存在,若不存在再对类对象进行加锁处理
if (instance == null) {
synchronized (Singleton1.class) {
if (instance == null) {
instance = new Singleton1();
}
}
}
return instance;
}
}
懒汉式源代码链接
对于线程安全问题,懒汉式有几种写法,上置链接有详细关于懒汉式的一些方法的实现。
总结
以上是关于Java的继承和多态特性,其中涉及到的概念很多,
静态方法和非静态方法,静态属性和非静态属性,
在继承中子类重写父类的方法体现子类所独有的特征,又通过向上转型,只通过父类不需要知道子类具体是谁,从而统一管理调用不同子类同一个父类同样的方法,体现了多态性和继承性的一些特点与存在意义。
上述中有列举两种单例设计模式,以及其特点,
有懒汉式和饿汉式,在创建单例的方法上有所区别,在这区别上相关着单例模式的线程安全问题。