chapter5 面向对象之单例设计模式(懒汉、饿汉)及继承
1.单例设计模式
在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,即一个类只能有一个对象(对象唯一性),其他程序不能创建,例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误,这就是所谓的单例模式。
单例模式有 3 个特点:
①单例类只有一个实例对象;
②该单例对象必须由单例类自行创建;
③单例类对外提供一个访问该单例的全局访问点;
实现步骤:
①在本类中新创建一个对象s,并且将其定义成private类型,从而保证了外部类无法直接访问这个对象。
②为了保证对象唯一性,即其他类不可以新建当前这个类的对象,将其构造方法定义成private型。
③新建一个方法(public型,使得外部类可以访问),可以返回这个类中的对象。注意的是,因为构造方法是私有型的了,那么就不可以通过新建一个对象, 然后调用这个方法,那么应该怎么办呢?我们知道被static修饰的成员变量随着类的加载而加载,那么就可直接被类直接调用,因此这个方法要定义成静态类型,即要有static,又因为静态方法只能访问静态成员,所以①中的对象s也要定义成为静态变量,因此也要有static。
1.1懒汉式
特点:该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
代码:
public class LazySingleton
{
private static LazySingleton instance=null; //注意private型的静态变量,因为在下面的静态方法中要访问这个对象,所以要将这个对象定义成静态变量
private LazySingleton(){} //private型的构造方法,从而避免类在外部被实例化
/**
*public型的静态方法,从而对外提供一个方法,可以返回在当前这个类的对 像,因此可以在外部类中实现这个类的相关操作。之所以是静态,是因为构造方法是私有化了,无法在外部类中新建一个对象,然后调用这个方法,因此通过将其定义成静态变量,从而可以通过类直接调用
*/
public static LazySingleton getInstance()
{
if(instance==null)
{
instance=new LazySingleton();//新建对象
}
return instance;//返回对象
}
}
1.2饿汉式
特点:该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了。
代码:
public class LazySingleton
{
//注意private型的静态变量,因为在下面的静态方法中要访问这个对象,所以要将这个对象定义成静态变量
private static LazySingleton instance= new LazySingleton(); //新建对象
private LazySingleton(){} //private型的构造方法,从而避免类在外部被实例化
/**
*public型的静态方法,从而对外提供一个方法,可以返回在当前这个类的对
*像,因此可以在外部类中实现这个类的相关操作。之所以是静态,是因为构造
*方法是私有化了,无法在外部类中新建一个对象,然后调用这个方法,因此通
*过将其定义成静态变量,从而可以通过类直接调用
*/
public static LazySingleton getInstance()
{
return instance;//返回对象
}
}
1.3单例模式的应用实例(懒汉式)
用懒汉式单例模式模拟产生美国当今总统对象。
public class SingletonLazy
{
public static void main(String[] args)
{
President zt1=President.getInstance();//直接通过类访问getInstance这个方法
zt1.getName(); //调用getName方法,从而得到总统的名字
President zt2=President.getInstance();
zt2.getName(); //调用getName方法,从而得到总统的名字
//由于类是一个引用类型,那么这里可以用==进行比较,从而比较他们的地址是否 相同,如果相同,就说明没有新建对象,从而表明是同一个人,否则不是
if(zt1==zt2)
{
System.out.println("他们是同一人!");
}
else
{
System.out.println("他们不是同一人!");
}
}
}
class President
{
private static President instance=null; //保证instance在所有线程中同步
//private避免类在外部被实例化
private President()
{
System.out.println("产生一个总统!");
}
public static synchronized President getInstance()
{
//在getInstance方法上加同步
if(instance==null)//如果为null,说明还没有产生总统,那么就新建对象
{
instance=new President();//在新建对象的时候,返回到了构造方法,从而对对象进行初始化,这里输出----产生一个总统
}
else
{
System.out.println("已经有一个总统,不能产生新总统!");
}
return instance;
}
public void getName()
{
System.out.println("我是美国总统:特朗普。");
}
}
结果:
2.简述继承
继承是面向对象的三大特征之一。继承和现实生活中的“继承”的相似之处是保留一些父辈的特性,从而减少代码冗余,提高程序运行效率。
Java 中的继承就是在已经存在类的基础上进行扩展,从而产生新的类。已经存在的类称为父类、基类或超类,而新产生的类称为子类或派生类。在子类中,不仅包含父类的属性和方法,还可以增加新的属性和方法。继承所用的关键字为extends,子类要调用父类的相关成员,那么需要用到关键字super。
继承的格式:
2.1继承的相关注意事项
①java中只允许单继承,即一个子类只能有一个直接父类,不可以有多个。为什么呢?因为如果有多个直接父类,并且这些直接父类中有方法名是相同,那么在调用子类的这个方法时,就会发生报错,因为不知道应该调用哪个父类的方法。
演示:
java虽然不支持多继承,但是支持多重继承,即构建一个继承体系。如图示:
②如果父类的成员是公有的(public)、被保护的(protected)或默认的,它的子类仍具有相应的这些特性,并且子类不能获得父类的构造方法,同时如果父类的成员变量是私有的,那么子类则不可以继承,即使是在子类中给使用了super这个关键字,还是会报错的。
子类继承了父类的所有公开成员,但是父类这个age变量是私有,此时会发现有个错误提示,即使是在子类中给使用了super这个关键字,还是会有一个错误提示,还是会报错的,提示要求make Person.age public,从而表明了子类只能继承父类的所有公开变量。
下面是一个错误的代码示范:
class Person {
private int age = 12;//父类变量,私有类型
}
class Student extends Person {
void display() {
System.out.println("学生年龄:" + age);//子类继承了父类的所有公开成员,但是这个age变量是私有,会报错,即使是使用了super这个关键字,依然会报错,改正方法就是将age在父类中改为public,而不是private
}
}
class Test {
public static void main(String[] args) {
Student stu = new Student();
stu.display();
}
}
③如果子类中的成员变量和父类中的成员变量同名的时候,在没有使用关键字super的情况下,那么在子类的方法中输出这个变量时,就是子类的成员变量,而不是父类的变量,因此,如果想要输出父类的变量,就要调用super,从而将两者区分的,否则在输出的就是子类的变量了。
子父类变量同名:
class Person {
int age = 12;//父类变量
}
class Student extends Person {
int age = 18;//子类中的变量
void display() {
System.out.println("学生年龄:" + age);//父类和子类同名,那么这时候没有使用关键字super,那么输出的是子类中的变量,输出18
System.out.println("学生年龄:"+super.age);//使用了关键字super,那么输出父类中的变量,输出12
}
}
class Test {
public static void main(String[] args) {
Student stu = new Student();
stu.display();
}
}
子父类的方法名同名:
子父类方法名同名,参数列表相同,返回值类型相同,但是方法体不同,这就是方法的重写。子类调用这个方法时,输出的就是子类这个方法实现的数据,如果要输出父类这个方法实现的数据,那么需要使用关键字super。
class Person {
void message() {
System.out.println("This is person class");
}
}
class Student extends Person {
void message() {
System.out.println("This is student class");
}
void display() {
message();//没有使用关键字super,从而表示是子类的方法,输出This is student class
super.message();//使用了关键字super,从而表示是父类的方法,输出This is person class
}
}
class Test {
public static void main(String args[]) {
Student s = new Student();
s.display();
}
}
④如果一个类被final修饰,那么就没有子类。同样的,如果一个父类的方法被final修饰,那么这个方法不可以被他的子类所继承。
一个类被final修饰,没有子类。
父类中的被final修饰的方法不被其子类继承。
2.2继承中构造方法的相关注意事项
①在子类的每一个构造方法中, 第一行(一定是第一行,因为要对父类进行初始化)必须写其父类的构造方法,如果不写,那么会默认为super(),从而对父类进行初始化动作。
如下面的代码:
//父类
public class Student {
public int age;
public int name;
public Student(){
System.out.println("fu");
}
public Student(int x){
System.out.println("fu------"+ x);
}
public Student(String name,int age){
System.out.println("fu"+"-----"+name+"-----"+age+"岁");
}
}
//子类
public class Stu1 extends Student {
Stu1(){
//第一行中并没有使用super明确调用父类的哪一个调用方法,那么就会默认调用无参数的构造方法super(),因此先输出fu,然后在输出zi
System.out.println("zi");
}
Stu1(int age){
//第一行中使用super明确调用父类的哪一个构造方法,因此先输出fu-----6,然后在输出zi-----0
super(6);
System.out.println("zi-----"+age);
}
Stu1(String name,int age){
//第一行中使用super明确调用父类的哪一个构造方法,因此先输出fu-----李四-----9,然后在输出zi-----张三-----5
super("李四",9);
System.out.println("zi-----"+name+"-----"+age);
}
}
//测试
public class StudentApp {
public static void main(String[] args){
Stu1 stu1 = new Stu1();
Stu1 str2 = new Stu1(10);
Stu1 str3 = new Stu1("张三",5);
}
}
结果:
注意:如果在父类中存在有参的构造方法而并没有重载无参的构造方法,那么在子类中必须含有有参的构造方法,因为如果在子类中不含有构造方法,默认会调用父类中无参的构造方法,而在父类中并没有无参的构造方法,因此会出错,即如果父类中没有说明无参的构造方法,但是在子类的构造方法中调用父类无参的构造方法会报错,其他的同理。
如下面的代码:
//父类中没有定义无参的构造方法
public class Student {
public int age;
public int name;
public Student(int x){
System.out.println("fu------"+ x);
}
public Student(String name,int age){
System.out.println("fu"+"-----"+name+"-----"+age+"岁");
}
}
//子类中调用了父类的无参的构造方法,从而发生了报错
public class Stu1 extends Student {
Stu1(){
//第一行中并没有使用super明确调用父类的哪一个调用方法,那么就会默认调用无参数的构造方法super(),因此先输出fu,然后在输出zi
System.out.println("zi");
}
Stu1(int age){
//第一行中使用super明确调用父类的哪一个构造方法,因此先输出fu-----6,然后在输出zi-----0
super(6);
System.out.println("zi-----"+age);
}
Stu1(String name,int age){
//第一行中使用super明确调用父类的哪一个构造方法,因此先输出fu-----李四-----9,然后在输出zi-----张三-----5
super("李四",9);
System.out.println("zi-----"+name+"-----"+age);
}
}
//测试
public class StudentApp {
public static void main(String[] args){
Stu1 stu1 = new Stu1();
Stu1 str2 = new Stu1(10);
Stu1 str3 = new Stu1("张三",5);
}
}
结果及提示(发生报错了):
②子类的构造方法中含有this的情况:先看下列的代码,然后进行分析:
//父类
public class Student {
public int age;
public int name;
public Student(){
System.out.println("fu");
}
public Student(int x){
System.out.println("fu------"+ x);
}
public Student(String name,int age){
System.out.println("fu"+"-----"+name+"-----"+age+"岁");
}
}
//子类
public class Stu1 extends Student {
Stu1(){
this(6);//调用当前对象的构造方法,同样要将其放在代码的第一行,因为要对对象进行初始化,这时候这个调用方法中并没有调用super()(父类的无参的构造方法),因为他们同样是为了先将对象进行初始化,因此要放在第一行中
System.out.println("zi");
}
Stu1(int age){
//第一行中使用super明确调用父类的哪一个构造方法,因此先输出fu-----6,然后在输出zi-----0
super(6);
System.out.println("zi-----"+age);
}
Stu1(String name,int age){
//第一行中使用super明确调用父类的哪一个构造方法,因此先输出fu-----李四-----9,然后在输出zi-----张三-----5
super("李四",9);
System.out.println("zi-----"+name+"-----"+age);
}
}
//测试
public class StudentApp {
public static void main(String[] args){
Stu1 stu1 = new Stu1();
Stu1 str2 = new Stu1(10);
Stu1 str3 = new Stu1("张三",5);
}
}
结果:
可以看见在子类无参的构造方法使用了this,那么没有再调用其父类的无参构造方法,因为super和this调用他们的构造方法都必须将其放在第一行,因为要先对其进行初始化,所以如果有了super,就不可以有this,有了this,就不能有super,但是有一点可以保证的是子类的肯定会有其他的构造方法调用其父类的构造方法,因此上面的代码中子类调用无参的构造方法,只输出了zi,而原先没有this时,还会输出fu,从而验证了我们的结论是正确的。
上一篇: java虚拟机深入理解