进阶day08——访问修饰符、继承、spuer、static、main、代码块、单利模式

一、访问修饰符

1.基本介绍

java提供四种访问控制修饰符号,权限修饰符是用来限制类的成员(成员变量、成员方法、构造器…)能够被访问的范围。

1.public:公共访问权限。
如果一个类中的成员(属性和方法)使用public关键字修饰,则该成员可以在所有类中被访问,不管是否在同一个包中。

2.protected:受保护的访问权限。
如果一个类的成员使用了protected关键字修饰符,则只能被本包其他类及不同包的子类访问(继承)。

3.default:默认访问权限
就是没有写访问权限的,称为默认。如果一个类中的属性或方法没有任何访问权限声明,则该属性或方法就是默认访问权限,可以被本包中的其他类访问,但不能被其他包的类访问。

4.private:私有访问权限。
用于修饰类的属性和方法,也可以修饰内部类。类的成员一旦使用了private关键字修饰,则该成员只能在本类中访问。

2.访问控制权限

在这里插入图片描述

二、继承

1.介绍

继承是指在一个现有类的基础上构建一个新的类,构建新的类被称作子类,现有类被称作父类。子类会自动继承父类的属性和方法,使得子类具有父类的特征和行为。

2.继承示意图

在这里插入图片描述

加入B类和C类有多个共同的属性和方法,此时创造一个A类,A类里面有B类和C类共用的属性和方法,而B类和C类里面包含各自特有的属性和方法,然后让B类和C类继承A类(extends),然后B类和C类自动拥有A类的属性和方法。接着B类和C类也可以被别的类继承。

3.语法

class 子类 extends 父类{
     执行语句;
}
/*
1.子类就会自动拥有父类定义的属性和方法
2.父类又叫超类、基类
3.子类又叫派生类
*/

4.继承的好处

(1)代码的复用性提高了

(2)代码的扩展性和维护性提高了

5.super关键字

5.1介绍

super代表父类的引用,用于访问父类的属性、方法、构造器。

super关键字是在子类对象内部代指其父类空间的引用

5.2基本语法
1.访问父类的属性
super.属性
//访问父类的属性,但不能访问父类的私有属性。

2.访问父类的方法
super.方法(参数1,参数2;
//访问父类的方法,但不能访问父类的私有方法。

3.访问父类构造器
super(参数1,参数2);
只能在构造器中使用,并且只能放在构造器的第一句,只能出现一句,而且与this()不能共存
5.3调用父类构造器的好处

分工明确,父类属性由父类进行初始化,子类属性由子类进行初始化。

6.细节

  • 细节1

子类继承父类的所有属性和方法,非私有属性和方法可以在子类直接访问,但是私有属性和方法不能再子类直接访问,要通过父类提供的公共方法去访问。

测试类

public class Test {
    public static void main(String[] args) {
        Zi zi = new Zi();
        zi.print();
        
    }
}

父类

public class Fu {
    //结合四种访问权限,来看看能否访问
    public int n1 = 100;//公共的
    protected int n2 = 200;//受保护的
     int n3 = 300;//默认的
     private int n4 =400;//私有的

    public Fu() {//无参构造器
    }
    //通过四种不同范围的方法,来看能否访问
    public void test100(){
        System.out.println("test100");
    }
    protected void test200(){
        System.out.println("test200");
    }
    void test300(){
        System.out.println("test300");
    }
    private void test400(){
        System.out.println("test400");
    }

    //提供公共方法访问私有属性值
    public int  getN4() {
        return n4;
    }
    //提供公共方法访问私有方法
    public void callTest400(){
        test400();
    }
}

子类

public class Zi extends Fu {
    //构造器
    public Zi(){
    }

    public void print(){
        //非私有属性和方法可以在子类调用
        System.out.println("n1 = " + n1+" n2 = "+n2+" n3 = "+n3);
        test100();
        test200();
        test300();
        //私有属性通过公共方法调用
        System.out.println(getN4());
        callTest400();
    }
}

  • 细节2

子类的全部构造器,都必须先调用父类的构造器,在执行自己.

执行顺序,如下图按照① ② ③ 步骤执行:

在这里插入图片描述

课外知识

为什么要先调用父类的构造器?

在面向对象的编程中,子类通过继承父类来获取父类的属性和行为。当创建子类的对象时,需要先调用父类的构造函数,以确保父类的初始化工作得到完成。这样可以保证子类实例化时,它所依赖的父类对象已经被正确地初始化。

以下是几个原因:

1.初始化父类状态:通过调用父类的构造函数,可以确保父类的成员变量被正确地初始化。如果子类直接初始化自己的成员变量,可能会导致父类的一些必要初始化被忽略,从而导致程序运行出现错误。

2.继承关系的完整性:通过先调用父类的构造函数,可以确保子类对象具有完整的继承关系。父类的构造函数负责初始化父类的状态,子类的构造函数则负责初始化子类自身的特有状态。这样可以保持继承关系的完整性,确保子类对象拥有父类的所有属性和方法。

3.遵循最小知识原则:调用父类构造函数首先初始化父类,符合面向对象设计中的最小知识原则。按照最小知识原则,一个对象应该尽可能少地了解其他对象的内部细节。通过先调用父类构造函数,子类只需要关注自己特有的初始化逻辑,而不需要了解父类的具体实现细节。

总之,先调用父类构造函数再调用子类构造函数是为了保证继承关系的完整性、遵循最小知识原则,并确保父类和子类的状态都得到正确初始化
  • 细节3

当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器.

public class Test {
    public static void main(String[] args) {
        Sub sub1 = new Sub();
        Sub sub2 = new Sub("jack");
    }
}

class Base{//父类
    public Base(){
        System.out.println("父类无参构造器被调用");
    }
}
class Sub extends Base{
    String name;

    public Sub() {
        //这里有个隐式的super();
        System.out.println("Sub的无参构造器被调用");
    }

    public Sub(String name) {
          //这里有个隐式的spuer(),也可以显示调用
        super();
        this.name = name;
        System.out.println("子类Sub(String name)构造器被调用");
    }
}
/*
输出:
父类无参构造器被调用
Sub的无参构造器被调用
父类无参构造器被调用
子类Sub(String name)构造器被调用
*/

注意事项

1.如果父类没有提供无参构造器,则必须在子类的每一个构造器中用super(参数,参数)去指定使用父类的哪个构造器,完成对父类的初始化工作,否则编译不会通过。
2.如果父类有无参构造器,希望指定去调用父类的某个构造器,则显示调用一下:super(参数列表)
  • 细节4

super()在使用时,必须放在构造器第一行,super()只能在构造器中使用,因为super()和this()都只能放在第一行,所以super()和this()不能出现在同一构造器中。

this()是在构造器中调用另一个构造器,但是语句this.成员和super()不冲突。

  • 细节5

Java所有类都是Object类的子类,Object是顶级父类

  • 细节6

父类构造器的调用不限于直接父类,将一直往上追溯到Object类

  • 细节7

(1)子类最多只能继续一个父类(指直接继承,直接父类)

(2)Java支持单继承和多层继承,不允许多继承

7.this和super的比较

在这里插入图片描述

访问本类成员:
	this.成员变量	//访问本类成员变量
	this.成员方法	//调用本类成员方法
	this()		   //调用本类空参数构造器
    this(参数)	  //调用本类有参数构造器
	
访问父类成员:
	super.成员变量	//访问父类成员变量
	super.成员方法	//调用父类成员方法
	super()		   //调用父类空参数构造器
    super(参数)	  //调用父类有参数构造器
    
注意:thissuper访问构造方法,只能用到构造方法的第一句,否则会报错。
  • 案例

当子类中有父类成员(属性和方法)重名时,为了访问父类的成员变量,必须先调用super。如果没有重名时,使用super、this、直接调用直接访问是一样效果

继承的机制->如果有同名的属性和方法时,查找顺序是从本类开始寻找,如果有并且可以访问,则调用,如果没有就往上一级的父类查找,如果有并且可以访问,则调用,否则以此类推,直到Object类

(1)super是从父类开始寻找,即使本类有,也不会进行访问

(2)查找过程中,找到了,但不能访问(遵循访问修饰权限),会报错

//父类-A
public class A {
    public int i =1;
    public A() {
        System.out.println("父类无参构造器");
    }

    public A(int i) {
        System.out.println("父类有参构造器");
        this.i = i;
    }

    public void sum(){
        System.out.println("父类sum方法");
    }
    public void cal(){
        System.out.println("这是父类cal方法");
    }
}

//子类-B
public class B extends A{
    public B() {
 /*  (1) 这里有一个隐藏的super()
   (2) 需要注意的是,父类如果没有显示出构造器就会有一个隐藏的无参构造器。所以语法一样
   (3)但如果父类添加了有参构造器,无参构造器就会被覆盖,除非父类再次显示的写出无参构造器,否则没有super()
   (4) 这次就需要我们去调用父类的有参构造器->子类的构造器第一行都必须用super()去调用父类构造器*/
    }

    public B(int i) {
        super(i);
    }

   
    public void sum() {
        System.out.println("子类sum方法");
        super.sum();//和父类方法重名时,需要通过super.方法名()
       --------------没有重名时-------------------------
        super.cal();//通过super.方法名()调用父类的方法
        this.cal();//没有重名时,可以使用this.方法名()调用父类的方法
        cal();//没有重名时,可以通过方法名直接调用父类的方法
    }

}


//测试类-主方法
public class Test {
    public static void main(String[] args) {
        B b1 = new B();
         b1.sum();
       System.out.println("——————————————————————————");
        B b2 = new B(1);
         b2.sum();
    }
}
/*
父类无参构造器
子类sum方法
父类sum方法
这是父类cal方法
这是父类cal方法
这是父类cal方法
——————————————————————————
父类有参构造器
子类sum方法
父类sum方法
这是父类cal方法
这是父类cal方法
这是父类cal方法
*/

8.方法的重写(overvide)

8.1介绍

在继承关系中,子类会自动继承父类中定义的方法,但有时在子类中需要对继承的方法进行一些修改,即对父类的方法进行重写。简单来说,方法覆盖(重写)就是子类有一个方法和父类的某个方法名称、返回类型、参数一样,那么我们就说这个方法覆盖(重写)了父类的方法

8.2快速入门
//父类
public class Animal {
    public void cry(){
        System.out.println("动物叫唤。。。");
    }
}

//子类-Dog-继承Animal
public class Dog extends Animal{
    @Override
    public void cry() {//重写(覆盖)父类的cey()方法
        System.out.println("小狗汪汪叫。。。");
    }
}

//测试类-主方法
public class Overvide {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.cry();//输出:小狗汪汪叫。。。
    }
}

/*说明:
(1)因为Dog是Animal的子类,在继承关系中,子类会自动继承父类中定义的方法
(2)Dog的cry方法和Animal的cry定义形式一样(方法名、形参、参数)
(3)这时我们就说Dog的cry方法,重写了Animal的cry方法
*/
8.3细节
  • 细节1

子类的方法名、形参、参数要和父类的方法完全一致

  • 细节2

子类方法的放回类型和父类返回类型一样,或是父类的返回类型的子类

比如:父类返回值类型是object、子类返回值类型是String
//父类
public object getinfo(){
}
//子类
public String getinfo(){
}
  • 细节3

子类不能缩小父类方法的访问权限

扩展
扩展1:子类方法返放回类型和父类返回类型一样,或是父类的返回类型的子类

协变关系是指在继承关系中,子类重写父类的方法时,返回类型可以是父类方法返回类型的子类。这种协变关系允许我们在使用继承关系的同时,对返回结果进行更具体的描述。

在Java中,协变关系适用于返回类型是引用类型的情况(即非基本数据类型)。

子类的方法可以具有与父类相同的返回类型,或者是父类返回类型的子类。这种特性被称为方法的返回类型协变。

在Java中,当子类重写(覆盖)父类的方法时,子类方法的返回类型可以是与父类方法返回类型兼容的任何类型。具体来说:

1.如果子类方法的返回类型与父类方法的返回类型完全一致,那么它们具有相同的返回类型。

在这里插入图片描述

2.如果子类方法的返回类型是父类方法返回类型的子类,那么它们也是满足协变关系的。

在这里插入图片描述

扩展2:子类不能缩小父类方法的访问权限

(1)理论篇

  • 多态性:

Java中通过继承实现了多态性,子类对象可以作为父类对象使用。当我们使用父类引用指向子类对象时,编译器只会识别该引用为父类类型,而不知道实际引用的是子类对象。因此,编译器只允许调用父类中已经声明的方法。

如果子类重写父类的方法并缩小了访问权限,例如将父类的方法从public变为private,那么在使用父类引用调用该方法时,编译器仍然会按照父类的方法进行调用。这就导致了一个问题:编译器允许了调用一个在子类中是不可见的方法,这样在运行时会出现错误。

为了避免这种可能的错误和不一致性,Java语言规定子类重写父类方法时不能缩小访问修饰符。子类重写父类方法时只允许拓宽访问权限或保持相同的访问权限,例如从protected变为public,或者保持为public。

这样做有助于确保使用父类引用指向子类对象时的代码安全和正确性。它强调了面向对象设计中的一致性原则和Liskov替换原则,使得代码更加可靠和稳定。

  • 继承性

Liskov替换原则是面向对象设计中的一个重要原则,它指出子类对象必须能够替换父类对象而不破坏程序的正确性。即,对于任何父类定义的方法,在使用父类引用指向子类对象时,应该能够正确地调用父类定义的方法,并且返回结果是符合预期的。

如果子类能够缩小父类方法的访问权限,那么在使用父类引用指向子类对象并调用该方法时,子类对象无法完全替代父类对象的行为,可能导致出现编译时错误或运行时错误。这违反了Liskov替换原则,破坏了代码的可靠性和一致性。

因此,Java语言规定子类重写父类方法时不能缩小访问权限,以保证继承关系的正确性和一致性。

  • 总结

​ Java中规定子类重写父类方法不能缩小访问权限,目的是为了保持代码的安全性和一致性。这样做可以避免使用父类引用调用子类中不可见的方法导致的错误,同时也符合面向对象设计的原则,使代码更可靠和稳定。


(2)理解篇(背)

  • 从继承性的角度来看

子类继承了父类的方法,意味着子类对象可以被当作父类对象使用。在使用父类引用指向子类对象时,编译器只知道该引用是父类类型,因此只能调用父类中已经声明的方法。如果子类重写了父类方法并且缩小了访问权限,那么在使用父类引用调用该方法时会出现不可见的方法,导致错误的发生,违反了继承性的原则。

  • 从多态性的角度来看

Java中通过继承实现了多态性,子类对象可以作为父类对象使用。当使用父类引用指向子类对象时,编译器只识别该引用为父类类型。如果子类重写了父类方法并且缩小了访问权限,那么在使用父类引用调用该方法时,编译器仍然会按照父类的方法进行调用,这就导致了一个问题:编译器允许调用一个在子类中是不可见的方法,这样在运行时会出现错误,违反了多态性的原则。

扩展3:为什么不能继承构造器

构造器(Constructor)在面向对象编程中用于创建对象实例的特殊方法。它的主要目的是初始化对象的状态。

  1. 构造器的作用是创建对象,并初始化对象的状态。它通常包含了一些特定于该类的初始化逻辑。如果构造器可以被继承,那么子类也会继承父类的构造器,这意味着子类创建对象时会执行父类的构造逻辑,而这可能与子类自身的逻辑不匹配,导致对象创建过程出现问题。
  2. 子类在继承父类时,可以通过调用父类的构造器来初始化从父类继承的成员变量。这可以通过使用 super() 关键字来实现。子类需要显式地调用适当的父类构造器来初始化父类的成员变量,以保证继承的正确性。
8.4重载和重写的比较

在这里插入图片描述

三、static(静态)

static 是Java中的关键字,用于声明静态成员。静态成员属于类本身,而不是类的实例。这意味着无论创建多少个类的实例,静态成员只有一份拷贝。

在Java中,可以使用static修饰以下内容:

  1. 静态变量(类变量):被所有类实例共享的变量。可以通过类名直接访问,也可以通过实例对象访问。静态变量在内存中只有一份拷贝。
  2. 静态方法:属于类而不属于实例的方法。可以通过类名直接调用,无需实例化对象。静态方法只能访问静态成员(包括静态变量和静态方法),而不能访问非静态成员。
  3. 静态代码块:用于在类加载时执行一次的代码块。静态代码块在类第一次被加载时会被执行,常用于进行静态成员的初始化操作。

1.类变量

1.1类变量快速入门

思考:

如果幼儿园活动课老师组织学生老鹰抓小鸡游戏,计算出有多少个人参加活动。设计:count表示总人数,我们创建了一个小孩时,就把count+1,并且count是所有对象共享就OK了


//定义Child类
public class Child {
    private String name;
    //定义一个变量count,是一个类变量(静态变量)static静态
    //该变量最大的特点就是会被Child类的所有对象实例共享
    public static int count = 0;

    public Child(String name) {
        this.name = name;
    }
    public void join(){
        System.out.println("有人参加");
    }
}

//测试类-主方法
public class Test {
    public static void main(String[] args) {
        Child c1 = new Child("小明");
        c1.join();
        c1.count++;
         System.out.println("——————————————————————————");
        Child c2 = new Child("李华");
        c2.join();
        c2.count++;
        System.out.println("——————————————————————————");
        Child c3 = new Child("小红");
        c3.join();
        c3.count++;

   System.out.println("共有"+Child.count+"个小孩参加老鹰抓小鸡");
        /*输出
        有人参加
        ——————————————————————————
        有人参加
        ——————————————————————————
        有人参加
        共有3个小孩参加老鹰抓小鸡
        */
    }
}
/*思考
 c1.count;
 c2.count;
 c2.count;
 会输出什么?
 答案:都输出2-》因为静态变量被对象共享
        */
1.2什么是类变量(静态变量)

类变量也叫静态变量、静态属性,是该类的所有对象共享的变量,任何一个该类对象去访问它时,取到的都是相同的值,同样任何一个该类对象去修改它时,修改的也是同一个变量。

1.3类变量的执行
执行类变量的过程如下:

(1)类加载:当程序第一次使用某个类时,Java 虚拟机(JVM)会进行类加载操作。类加载是将类的字节码文件加载到内存中,并创建一个与该类相关联的 Class 对象。

(2)静态变量分配内存空间:在类加载的过程中,类变量所需的内存空间会被分配。这个内存空间是在方法区中分配的。

(3)初始化赋值:类变量会根据其声明时的默认值或显式赋予的初始值进行初始化。静态变量可以在声明时直接赋值,也可以在静态代码块中赋值。如果没有显示赋值,则会使用默认值(例如数值类型默认为0,引用类型默认为null)。

(4)访问类变量:可以通过类名直接访问类变量。由于类变量属于类本身,而不是实例,因此无需创建类的实例即可访问类变量。

需要注意的是,类变量在整个程序的生命周期内只会被初始化一次,且只有一份内存空间。无论创建多少个类的实例,它们都共享同一个类变量。

此外,类变量的生命周期与类的生命周期一致。当类被卸载时,类变量所占用的内存空间会被回收。
1.4如何定义类变量(静态变量)
访问修饰符 static 数据类型 变量名;(推荐)
或
static 访问修饰符 数据类型 变量名;
1.5如何访问类变量(静态变量)
类名.类变量名(推荐)
对象名.类变量名
//静态变量的访问修饰符放访问权限和范围与普通变量一致
//类变量是随着类的加载而创建,所以即使没有创建对象实例也可以访问
1.6类变量细节
  • 细节1

什么时候需要使用类变量?

当我们需要让某个类的所有对象共享一个变量时,就可以考虑使用类变量(静态变量)

  • 细节2

类变量与实例变量(普通变量)的区别

类变量是该类所有对象共享的,而实例变量是每一个变量独享的

  • 细节3

加上static的变量就是类变量否则为实例变量(普通变量、非静态变量)

  • 细节4

实例变量不能通过类名.变量名的方法访问

实例变量是属于类的实例(对象)的,而不是类本身。因此,无法直接通过类名去调用实例变量。

实例变量必须通过创建类的实例(对象)后,再通过该对象来访问。每个对象都有自己独立的实例变量副本,它们的值可以相互独立地进行修改和访问。

  • 细节5

类变量是在类加载的时候就初始化了,也就是说,即使你没有创建对象,只要类加载了就可以使用类变量。

  • 细节6

类变量的生命周期是随着类的加载而加载,随着类消亡而消亡

  • 扩展

实例变量的副本

在Java中,每个对象都有自己独立的实例变量副本。

当创建一个对象时,会为该对象分配一块内存,用于存储该对象的实例变量。每个对象根据其所属的类定义的实例变量,在内存中占用独立的空间。

因此,对于同一个类的不同对象而言,它们的实例变量是相互独立的,互不影响。修改一个对象的实例变量的值不会影响其他对象的实例变量的值。

对象名.类变量名

在 Java 中,类变量(静态变量)属于类本身,不是属于对象的实例。虽然使用对象名来调用类变量是合法的,但实际上它是通过类名来访问的。

当我们使用对象名来访问类变量时,Java 编译器会将其转换为对应的类名进行访问。这是因为类变量在类加载时就已经被分配内存,并且只会有一份副本存在。无论通过类名还是对象名访问,实际上都是在访问相同的类变量

2.类方法

类方法也叫静态方法

2.1语法
1)访问修饰符 static 返回值类型 方法名(){}2static 访问修饰符 返回值类型 方法名(){}
2.2类方法的调用
1)类名.类方法名
(2)对象名.类方法名

在这里插入图片描述

当方法中不涉及到任何对象相关的成员,则可以将方法设计成类方法,提高开发效率

比如:工具类中的 Math类

2.3类方法的细节
  • 细节1

类方法和普通方法都是随着类的加载而加载,随着类的消亡而消亡,将结构信息存储到方法区中

  • 细节2

类方法中无法使用this的参数

普通方法中隐藏this的参数

  • 细节3

类方法可以通过类名调用,也可以通过对象名调用(与类变量一样)

  • 细节4

普通方法和对象有关,需要通过对象名调用

  • 细节5

类方法中不允许使用和对象相关的关键字(this、super)

  • 细节6

(1)类方法(静态方法)中只能访问静态方法或静态变量。

(2)普通成员方法,可以访问非静态成员,也可以访问静态成员

  • 扩展

类方法中不允许使用和对象相关的关键字(this、super)

类方法是属于类本身的方法,它们在没有具体对象实例的情况下被调用。因此,类方法无法使用与对象实例相关的关键字,如thissuper等。

  1. this关键字:this表示当前对象实例,在类方法中没有具体的对象实例,因此无法使用this关键字引用对象自身。this关键字只能在实例方法(非静态方法)中使用。
  2. super关键字:super用于在子类中调用父类的成员。类方法是属于类本身的,与特定的对象实例无关,所以无法使用super关键字调用父类的成员。

由于类方法是通过类名直接调用的,不依赖于具体的对象实例,因此不能使用与对象实例相关的关键字。类方法通常用于执行与类属性或静态方法相关的操作,而不涉及对象实例的状态。

类方法(静态方法)中只能访问静态方法或静态变量

静态方法是属于类本身的方法,它们在没有具体对象实例的情况下被调用。因为静态方法不依赖于对象实例,所以无法直接访问非静态成员(非静态变量和非静态方法)。

静态方法与类相关,而不是与特定的对象实例相关。这意味着在静态方法中,没有this引用可以用于访问当前对象实例的非静态成员。

一方面,静态方法没有隐式的对象实例,无法使用this关键字引用当前对象,因此无法访问任何非静态成员。

另一方面,非静态成员是属于对象实例的,只有通过具体的对象实例才能访问它们。而静态方法在调用时并不需要对象实例,因此无法直接访问非静态成员。

如果在静态方法中需要使用非静态成员,可以通过创建对象实例来访问非静态成员,或者将非静态成员转换为静态成员,使其能够在静态方法中使用。但是需要根据具体情况谨慎使用,并确保遵循面向对象的设计原则。

普通成员方法,可以访问非静态成员,也可以访问静态成员

非静态方法是属于对象实例的方法,它们依赖于具体的对象实例才能被调用。因此,非静态方法可以直接访问静态成员(包括静态变量和静态方法)。

当非静态方法被调用时,它们会以特定的对象实例为上下文,这意味着它们可以使用关键字this引用当前对象实例。通过this关键字,非静态方法可以直接访问对象实例中包含的静态成员。

静态成员(静态变量和静态方法)是属于类本身的,与特定的对象实例无关。因此,非静态方法可以在访问自身对象实例的同时,直接访问类的静态成员。

需要注意的是,虽然非静态方法可以访问静态成员,但静态成员只有一份,属于整个类而不是对象实例。因此,在非静态方法中对静态变量进行修改时,会影响到所有的对象实例。这是因为所有的对象实例共享同一个静态变量。

总结起来,普通成员方法既可以访问非静态成员(通过关键字 “this” 引用当前对象),也可以访问静态成员(通过类名直接访问)。这种灵活性使得普通成员方法在处理对象级别的操作时很有用,并且可以访问类级别的共享资源。

3.工具类

学习完static修饰方法之后,我们讲一个有关类方法的应用知识,叫做工具类。

如果一个类中的方法全都是静态的,那么这个类中的方法就全都可以被类名直接调用,由于调用起来非常方便,就像一个工具一下,所以把这样的类就叫做工具类

在这里插入图片描述

//使用语法实现一个求最大最小值的工具类

//定义一个工具类utility
public class utility {

    private utility(){

    }
//返回最大值
    public static int max(int x , int y){
         int max = x>y?x:y;
         return max;
    }
//返回最小值
    public static int min(int x , int y){
        int min = x>y?x:y;
        return min;
    }
}

//测试类-主方法
public class static02 {
    public static void main(String[] args) {
        System.out.println(utility.max(1, 5));
        System.out.println(utility.min(99, 7));
    }
}

四、mian方法介绍

public static void main(String[] args)

1.介绍

(1)main方法由虚拟机调用

(2)Java虚拟机需要调用类的main方法,所以该方法的访问权限必须是public

(3)Java虚拟机在执行main()方法时,不必创建对象,所以该方法必须是static

(4)该方法接收String类行的字符串数组,该数组中保存执行Java命令时传递给所运行类的参数

在这里插入图片描述

2.main方法细节

  • 细节1

在main方法中,我们可以直接调用main方法所在类的静态方法或静态属性

  • 细节2

不能直接调用该类中的非静态成员,必须创建该类的实例对象后,才能通过这个对象去访问类中的非静态成员。

public class Test {
    public static String name ="java";
    public int age = 20;
    public static void main(String[] args) {
        System.out.println(Test.name);
        Say();
        Test test = new Test();
        System.out.println(test.age);
    }
    public static void Say(){
        System.out.println("调用方法");
    }
}

五、代码块

1.介绍

代码块又称为初始化块,属于类中的成员【即是类中的一部分】,类似方法,将逻辑语句封装在方法体中,通过{}包围起来,但是和方法不同,代码块没有方法名、没有参数、只有方法体,而且不同的对象或类显示调用,而是加载类时,或创建对象时隐式调用。

2.语法

(1)类别

使用static修饰的叫静态代码块

没有static修饰的叫普通代码块/非静态代码块

(2)功能

可以为任何的逻辑语句(输入、输出、方法调用、循环判断)

在这里插入图片描述

应用场景:初始化静态资源

在这里插入图片描述

应用场景:对成员变量初始化

3.代码块的好处

(1)理解

相当于另外一种形式的构造器(对构造器的补充机制),可以做初始化的操作

(2)应用

如果多个构造器中都有重复语句,可以抽取到代码块中,提高代码的复用性

4.代码块使用细节

  • 细节1

static代码块也叫静态代码块,作用就是对类的操作进行初始化,而且它随着类的加载而执行,并且只会执行一次。如果是普通代码块,每次创建一个对象就会执行一次。

  • 细节2

类什么时候被加载?

(1)创建对象实例时(new),普通代码块,在创建实例对象时,会被隐式的调用,被创建一次,就会调用一次。

(2)创建子类对象实例,父类也会被加载。(静态与非静态均会被调用)

(3)使用类的静态成员时(静态属性、静态方法),普通代码块不会执行,而静态代码块一定会被执行。

定义A类-并创建2个A类对象

测试new对象时,普通代码块隐式调用

测试类加载的时候代码块会调用,且执行一次

public class A {
     static int  count1 =0;
     static int  count2 =0;

    //定义普通代码块
   //普通代码块,在创建实例对象时,会被隐式的调用,被创建一次,就会调用一次。
    {
        System.out.println("A类普通代码块被执行"+(++count1)+"次");
    }
    //定义静态代码块
    //类加载时候,静态代码块就会被调用了,且只会调用一次
    static {
        System.out.println("A类静态代码块被执行"+(++count2)+"次");
    }
}

定义B类-不创建对象

测试代码块创建子类对象实例,父类也会被加载。

public class B {
    static int count1 = 0;
    static int count2 =0;

    {
        System.out.println("B类普通代码块被执行"+(++count1)+"次");
    }
    static {
        System.out.println("B类的静态代码块被调用"+(++count2)+"次");
    }
}

定义C类,继承B类,并创建C类对象

public class C extends B{

}

定义D类-不创建对象,调用两次静态属性

测试使用类的静态成员时(静态属性、静态方法),普通代码块不会执行,而静态代码块一定会被执行。

public class D {
    static String name ="D类";
    {
        System.out.println("D类的普通方法被调用");
    }
    static {
        System.out.println("D类的静态方法被调用");
    }
}

定义测试类,并创建A、C类对象和调用D类静态属性

public class ABC {
    public static void main(String[] args) {

        A a1 = new A();
         System.out.println("——————————————————————————");
        A a2 = new A();
         System.out.println("——————————————————————————");
        C c = new C();
        System.out.println("——————————————————————————");
        System.out.println(D.name);
        System.out.println(D.name);
    }
}
/*输出
A类静态代码块被执行1次
A类普通代码块被执行1次
——————————————————————————
A类普通代码块被执行2次
new对象时,普通代码块隐式调用
——————————————————————————
B类的静态代码块被调用1次
B类普通代码块被执行1次
——————————————————————————
D类的静态方法被调用
D类
D类
*/
  • 细节3

创建一个对象时,在一个类调用顺序是

(1)先调用静态代码块

静态代码块和静态属性初始化调用的优先级一样,如果有多个静态代码块和多个静态属性初始化,则按他们的定义顺序调用

(2)其次是调用普通代码块和普通属性初始化

普通代码块和普通属性初始化调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按他们的定义顺序调用

(3)然后是调用构造器

构造器是没有静态的

代码演示

public class A {
     static int  count1 =0;
     static int  count2 =0;

    //定义普通代码块
    /*创建对象实例时(new),普通代码块,在创建实例对象时,会被隐式的调用,被创建一次,就会调用一次。
    创建子类对象实例,父类也会被加载。*/
    {
        System.out.println("A类普通代码块被执行"+(++count1)+"次");
    }
    //定义静态代码块
    //类加载时候,静态代码块就会被调用了,且只会调用一次
    static {
        System.out.println("A类静态代码块被执行"+(++count2)+"次");
    }
}

public class Test {
    public static void main(String[] args) {
        A a = new A();
    }
}

/*
静态代码块被调用
普通代码块被调用
构造器被调用
*/
  • 细节4

构造器的最前面其实隐含了super()和调用普通构造器代码块

静态相关的代码块、属性初始化在类加载时,就执行完毕,因此优先于构造器和普通方法代码块的执行

代码演示

定义父类——A

public class A {
    public A() {
        System.out.println("父类构造器被调用");
    }
    {
        System.out.println("父类代码块被调用");
    }
}

定义子类——B

public class B extends A {
    public B(){

    }
}

定义测试类

public class Test{
    public static void main(String[] args) {
        B b = new B();
    }
}
/*
父类代码块被调用
父类构造器被调用
*/
  • 细节5

    静态代码块只能直接调用静态成员,普通代码块可以调用任意成员

5.总结

创建一个子类对象时(继承关系),他们的静态代码块属性初始化,普通代码块初始化,普通属性初始化,构造器方法调用顺序如下:

(1)父类静态代码块和静态属性初始化(优先级一样按定义顺序执行)

(2)子类静态代码块和静态属性初始化(优先级一样按定义顺序执行)

(3)父类的普通代码块和普通属性初始化(优先级一样按定义顺序执行)

(4)父类的构造器

(5)子类普通代码块和普通属性初始化(优先级一样按定义顺序执行)

(6)子类的构造器

六、单例模式

1.什么是设计模式

在这里插入图片描述

2.什么是单例模式

在这里插入图片描述

3.单例模式实现

在这里插入图片描述

3.1.饿汉式
  • 步骤

(1)构造器私有化

(2)类的内部创建对象-直接创建-且修饰为静态的

(3)向外暴露一个静态方法

  • 代码演示

定义一个饿汉式的妻子(wife)类,因为妻子只能有一个,然后类的内部创建两个妻子对象,最后在测试类中查看是否为同一对象

定义Wife类

public class Wife {
    private String name;

    //为了能够在静态方法中,返回w对象,需要将其修饰为static
    //直接创建对象
    private static Wife w = new Wife("红太狼");

   //将构造器私有化
    private Wife(String name) {
        this.name = name;
    }
    public static Wife getInstance(){
        return w;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                '}';
    }
}

定义测试类

public class Test {
    public static void main(String[] args) {
//在主方法中可以直接通过访问饿汉式类的静态方法或静态变量来获取对象实例
       Wife wife1 =Wife.getInstance();
// Wife w = new Wife(); ->报错,因为构造器已经私有化
//在Java中,当您将一个对象作为参数传递给打印语句时,Java会自动调用该对象的 toString() 方法以获取其字符串表示形式。
        System.out.println(wife1);
      System.out.println("——————————————————————————");
        Wife wife2 =Wife.getInstance();
        System.out.println(wife2);
        //判断两个是否为同一对象
        System.out.println(wife1==wife2);
    }
}
输出:
    Wife{name='红太狼'}
    ——————————————————————————
    Wife{name='红太狼'}
    true

  • 扩展
private static Wife w = new Wife("红太狼");

private:,确保只有一个实例存在。将内部实例变量私有化(private)是常见且推荐的做法,这样可以防止外部直接访问和修改该实例。当将内部实例变量声明为私有(private)时,外部类无法直接访问和修改该变量,只能通过公共的访问方法来获取该实例。这样可以有效地封装实例,控制其访问权限,避免了实例的不恰当使用。

static:将内部实例变量声明为静态(static)是为了确保该实例在类加载时就被创建,并且可以在整个程序的生命周期内被访问。
当将内部实例变量声明为静态变量时,它会随着类的加载而被初始化,并且在内存中只会存在一份。这样可以保证无论创建多少个该类的对象,都只会使用同一个实例。通过使用静态变量,我们可以在任何地方通过类名来访问该实例。
3.2懒汉式

懒汉式是一种延迟加载的单例模式,它在第一次使用时才创建对象。

  • 步骤

(1)构造器私有化

(2)类的内部创建对象-判断创建

(3)向外暴露一个静态方法

  • 代码演示

定义一个懒汉式的妻子(wife)类,因为妻子只能有一个,然后创建两个妻子对象,查看是否是同一对象

定义Wife类

public class Wife {
    private String name;
    private Wife w;// 声明对象但不实例化
   //将构造器私有化
    private Wife(String name) {
        this.name = name;
    }
    public static Wife getInstance(){
        if(w==null){
            w = new Wife("红太狼");
        }
        return w;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                '}';
    }
}

System.out.println(wife1==wife2);
}
}
输出:
Wife{name=‘红太狼’}
——————————————————————————
Wife{name=‘红太狼’}
true


- **扩展**

```java
private static Wife w = new Wife("红太狼");

private:,确保只有一个实例存在。将内部实例变量私有化(private)是常见且推荐的做法,这样可以防止外部直接访问和修改该实例。当将内部实例变量声明为私有(private)时,外部类无法直接访问和修改该变量,只能通过公共的访问方法来获取该实例。这样可以有效地封装实例,控制其访问权限,避免了实例的不恰当使用。

static:将内部实例变量声明为静态(static)是为了确保该实例在类加载时就被创建,并且可以在整个程序的生命周期内被访问。
当将内部实例变量声明为静态变量时,它会随着类的加载而被初始化,并且在内存中只会存在一份。这样可以保证无论创建多少个该类的对象,都只会使用同一个实例。通过使用静态变量,我们可以在任何地方通过类名来访问该实例。
3.2懒汉式

懒汉式是一种延迟加载的单例模式,它在第一次使用时才创建对象。

  • 步骤

(1)构造器私有化

(2)类的内部创建对象-判断创建

(3)向外暴露一个静态方法

  • 代码演示

定义一个懒汉式的妻子(wife)类,因为妻子只能有一个,然后创建两个妻子对象,查看是否是同一对象

定义Wife类

public class Wife {
    private String name;
    private Wife w;// 声明对象但不实例化
   //将构造器私有化
    private Wife(String name) {
        this.name = name;
    }
    public static Wife getInstance(){
        if(w==null){
            w = new Wife("红太狼");
        }
        return w;
    }

    @Override
    public String toString() {
        return "Wife{" +
                "name='" + name + '\'' +
                '}';
    }
}

测试类和饿汉式一样,不同的只是创建方式,调用方式一至

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值