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;//返回对象
    }
}

由此可以知道,懒汉式是线程不安全的,因为如果有多个线程调用getInstance()时,并且已经有一个线程在执行if中语句,但是另一个线程也来到了,那么这时候第二线程同样执行if方法体的语句,从而导致线程不安全。

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)、默认(default)的,它的子类仍具有相应的这些特性,并且子类不能获得父类的构造方法,同时如果父类的成员变量是私有的,那么子类则不可以继承,即使是在子类中给使用了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修饰的方法、变量可以被它的子类所继承,但是不可以这个方法被重写,变量不可以再被赋值更改(道理就像常量不可以再被赋值更改是一样的)。如果方法被重写,那么会发生报错,提示make Student show not final。从而表明了如果父类中的方法被final修饰,那么这个方法就不可以再被重写了,注意是不能被重写,但是可以被继承

public class Student {
    private int age;
//final修饰的变量,可以被继承,但是在子、父类中都不可以再被赋值更改,否则会发生报错
    final String NAME = "李四";
    public Student(int age){
        this.age =age;
        //输出fu-----李四------20
        System.out.println("fu"+"-----"+NAME+"-----"+this.age+"岁");
    }
//被final修饰的方法,可以被它的子类继承,但是不可以被重写,一旦被重写,就会发生报错。(道理应该类似于常量一旦被赋值之后就不可以再赋值更改)   
    public final void show(){
        System.out.println("fu");
    }
}

//子类
public class Stu1 extends Student {
    Stu1(int age){
        super(20);//调用父类的构造方法
        //输出zi-----李四-----5
        System.out.println("zi-----"+NAME+"-----"+age);
    }
}

//测试
public class StudentApp {
    public static void main(String[] args){
        Stu1 str3 = new Stu1(5);
        str3.show();//调用子类中的show方法,输出fu
    }
}

结果:
在这里插入图片描述

被fianl修饰的变量会被子类继承,不过父类、子类的这个变量已经是一个常量了,是不可以更改的了

//父类
public class Student {
    private int age;//私有变量,不被子类继承
    final String NAME= "李四";//被final修饰,是一个常量,为了和变量区分,要将所有的字母都大写
    public Student(int age){
        this.age =age;
        //输出fu-----李四-----20
        System.out.println("fu"+"-----"+NAME+"-----"+this.age+"岁");
    }
    public final String getName(){
        return NAME;
    }
}

//子类
public class Stu1 extends Student {
    Stu1(int age){
        super(20);
        //输出zi-----李四-----5
        System.out.println("zi-----"+NAME+"-----"+age);
    }
}

//测试
public class StudentApp {
    public static void main(String[] args){
        Stu1 str3 = new Stu1(5);
        System.out.println(str3.getName());//调用方法,从而输出李四
    }
}

结果:
在这里插入图片描述
从而表明了被fianl修饰的变量会被它的子类继承,同时在子类中不可以再给这个变量进行赋值,因为这个变量是一个常量。

👍总结:被fianl修饰的类不会被继承,但是被fianl修饰的变量、方法会被继承,只是此时的方法不可以再被重写,变量不可以再被赋值更改,道理就和常量不能再被赋值是一样的道理

2.2继承中构造方法的相关注意事项

在看注意事项之前,请先明白这个:
如果编写一个javaBean时没有添加构造方法,那么编译器会自动添加无参构造方法;
但是如果编写时添加了构造方法,不管这时候添加的构造方法是否有参,编译器不会再自动添加无参构造方法了。

所以,如果需要使用无参构造方法,一定要在类里面添加
在子类的每一个构造方法中, 第一行(一定是第一行,因为要对父类进行初始化)必须写其父类的构造方法,如果不写,那么会默认为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 {
    public Stu1(){
    //第一行中并没有使用super明确调用父类的哪一个调用方法,那么就会默认调用无参数的构造方法super(),因此先输出fu,然后在输出zi
        System.out.println("zi");
    }
    public Stu1(int age){
    //第一行中使用super明确调用父类的哪一个构造方法,因此先输出fu-----6,然后在输出zi-----0
        super(6);
        System.out.println("zi-----"+age);
    }
    public 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);
    }
}

结果:
在这里插入图片描述

注意:如果在父类中存在构造方法时,那么编译器不会自动添加无参的构造方法,此时,子类构造方法中,super()默认会调用父类中无参的构造方法,这时候就会发生报错,否则,如果super调用的时父类的有参构造方法时,就会调用父类对应的有参构造方法。
如下面的代码:

//父类中没有定义无参的构造方法
public class Student {
    public int age;
    public int name;
    /*
    由于有定义了构造方法,那么这时候编译器不会自动添加无参构造方法了。这时候如果子类要调用父类
    的无参构造方法,需要自己写一个无参构造方法,否则如果不写的话就会发生报错。
    public Student(){
       ........
    }
    */
    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()
    但是由于在父类中自己已经定义了构造方法,并且自己定义的构造方法中没有无参的构造方法,此时父类
    中没有无参构造方法(因为自己定义构造方法之后系统不会添加无参构造方法)从而导致错误
    */
        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,从而验证了我们的结论是正确的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值