Bootstrap

Java 复习 【知识改变命运】第九章

代码块

1:代码块的基本介绍和基本语法

  • 代码块又被称为初始块,属于类中的成员,类似于方法把逻辑语句放在{}中,但是没有名字返回值,没有参数,也不能被调用,在加载类或者创建对象的时候被隐式调用。
  • 语法:
[修饰符] {
代码
};
  • 修饰符可选且只能是static
  • 代码块分两种一种是静态代码块,一种是实例代码块/普通代码/非静态代码块
  • ;可以写上也可以省略

2:体会代码块的用处

  • 相当于另外一种形式的构造体,可以进行初始化此操作
  • 当多个构造器中都有重复的语句,就可以抽取到初始代码块中,提高代码重用性
class CodeBlock01 {
    public static void main(String[] args) {
        Movie movie = new Movie("你好,李焕英");
        System.out.println("===============");
        Movie movie2 = new Movie("唐探 3", 100, "陈思诚");
    }
}
class Movie {
    private String name;
    private double price;
    private String director;
//3 个构造器-》重载
//老韩解读
//(1) 下面的三个构造器都有相同的语句
//(2) 这样代码看起来比较冗余
//(3) 这时我们可以把相同的语句,放入到一个代码块中,即可
//(4) 这样当我们不管调用哪个构造器,创建对象,都会先调用代码块的内容
//(5) 代码块调用的顺序优先于构造器.. 
{
System.out.println("电影屏幕打开...");
System.out.println("广告开始...");
System.out.println("电影正是开始...");
};
public Movie(String name) {
        System.out.println("Movie(String name) 被调用...");
        this.name = name;
        }
public Movie(String name, double price) {
        this.name = name;
        this.price = price;
        }
public Movie(String name, double price, String director) {
        System.out.println("Movie(String name, double price, String director) 被调用...");
        this.name = name;
        this.price = price;
        this.director = director;
        }
}

3:代码块的细节

  • static代码块也叫静态代码块,作用是对类进行初始化,而且它之随着类的加载而执行,且只会执行一次,如果是普通代块,每创建一个对象,执行一次
  • 类加载的几种情况:创建对象实例的时候,创建子对象的实例,父类类也会被加载,使用静态成员时(静态属性,静态方法)
  • 普通代码块:在创建对象实例的时候,会被隐形调用,被创建一次就会调用一次,如果使用类的静态成员,普通代码块并不会执行。
  • static的=代码是类加载的时候,且只会被执行一次,普通代码块是创建对象的时候,创建一次对象执行一次
public class Code {
    public static void main(String[] args) {
//类被加载的情况举例
//1. 创建对象实例时(new)
// AA aa = new AA();
//2. 创建子类对象实例,父类也会被加载, 而且,父类先被加载,子类后被加载
//AA aa2 = new AA();
//3. 使用类的静态成员时(静态属性,静态方法)
// System.out.println(Cat.n1);
//static 代码块,是在类加载时,执行的,而且只会执行一次.
// DD dd = new DD();
 //DD dd1 = new DD();
//普通的代码块,在创建对象实例时,会被隐式的调用。
// 被创建一次,就会调用一次。
// 如果只是使用类的静态成员时,普通代码块并不会执行
        System.out.println(DD.n1);//8888, 静态模块块一定会执行
    }
}
class DD {
    public static int n1 = 8888;//静态属性
    //静态代码块
    static {
        System.out.println("DD 的静态代码 1 被执行...");//
    }
//普通代码块, 在 new 对象时,被调用,而且是每创建一个对象,就调用一次
//可以这样简单的,理解 普通代码块是构造器的补充
{
    System.out.println("DD 的普通代码块...");
}
}
class Animal {
    //静态代码块
    static {
        System.out.println("Animal 的静态代码 1 被执行...");//
    }
}
class Cat extends Animal {
    public static int n1 = 999;//静态属性
    //静态代码块
    static {
        System.out.println("Cat 的静态代码 1 被执行...");//
    }
}
class BB {
    //静态代码块
    static {
        System.out.println("BB 的静态代码 1 被执行...");//1
    }
}
class AA extends BB {
//静态代码块
static {
    System.out.println("AA 的静态代码 1 被执行...");//2
}
}

4:创建一个对象时,在一个类中的调用顺序(难点)

  • 调用静态代码块和静态属性初始化:静态代码块和静态属性的调用优先级一样,如果有多个代码块和多个静态变量初始化,则按他们的顺序调用
  • 调用普通代码块和普通属性的初始化:两者调用的优先级一样,如果有多个普通代码块和多个普通属性初始化,则按他们定义的顺序来调用
class A {
    { //普通代码块
        System.out.println("A 普通代码块 01");
    }
    private int n2 = getN2();//普通属性的初始化
    static { //静态代码块
        System.out.println("A 静态代码块 01");
    }
    //静态属性的初始化
    private static int n1 = getN1();
    public static int getN1() {
        System.out.println("getN1 被调用...");
        return 100;
    }
    public int getN2() { //普通方法/非静态方法
        System.out.println("getN2 被调用...");
        return 200;
    }
    //无参构造器
    public A() {
        System.out.println("A() 构造器被调用");
    }
}

在这里插入图片描述

  • 构造器的最前面其实隐含了super()和调用普通代码块,静态代码块和静态属性初始化,在类的加载时候,就执行完成,因此优于构造器和普通代码块执行的
class A {
	public A() {//构造器
	//这里有隐藏的执行要求
	//(1)super();
	// (2)调用普通代码块
	System.out.println("ok");
	}
}
public class Code{
    public static void main(String[] args) {
        new BBB();//(1)AAA 的普通代码块(2)AAA() 构造器被调用(3)BBB 的普通代码块(4)BBB() 构造器被调用
    }
}
class AAA { //父类 Object
    {
        System.out.println("AAA 的普通代码块");
    }
    public AAA() {
//(1)super()
//(2)调用本类的普通代码块
        System.out.println("AAA() 构造器被调用....");
    }
}
class BBB extends AAA {
    {
        System.out.println("BBB 的普通代码块...");
    }
    public BBB() {
//(1)super()
//(2)调用本类的普通代码块
        System.out.println("BBB() 构造器被调用....");
    }
}
  • 总结:创建一个子类父类对象时候(继承关系),他们的静态代码,静态属性初始化,普通代码块,普通属性初始化,构造方法的调用顺序如下:
  • 1:父类的静态方法和的静态属性(优先级一样,按定义的顺序)
  • 2:子类的静态代码块和静态属性(优先级一样,按定义的顺序)
  • 3:父类的普通代码块和普通属性初始化(优先级一样,按定义的顺序执行)
  • 4:父类的构造方法
  • 5:子类的普通代码块和普通属性初始化(优先级一样,按定义的顺序执行)
  • 6:子类的构造方法
class Sample
{
    Sample(String s)
    {
        System.out.println(s);
    }
    Sample()
    {
        System.out.println("Sample 默认构造函数被调用");
    }
}
//====
public class Test{
    Sample sam1=new Sample("sam1 成员初始化");//
    static Sample sam=new Sample("静态成员 sam 初始化 ");//
    static{
        System.out.println("static 块执行");//
        if(sam==null)System.out.println("sam is null");
    }
    Test()//构造器
    {
        System.out.println("Test 默认构造函数被调用");//
    }
    //主方法
    public static void main(String str[])
    {
        Test a=new Test();//无参构造器
    }
}

//运行结果, 输出什么内容,并写出. 2min 看看
//1. 静态成员 sam 初始化
//        2. static 块执行
//        3. sam1 成员初始化
//        4. Test 默认构造函数被调用

单例设计模式

1:介绍单例模式

  • 所谓的单机设计模式,就是采用一定的方法保证在整个软件系统中,对某个类只存在一个对象实例,并且该类只提供一个取得对象实例的方法
  • 单例模式有两种方法:饿汉式和懒汉式

2:单例模式应用的实现


public class Test {
    public static void main(String[] args) {
        GirlFriend xh = new GirlFriend("小红");
        GirlFriend xb = new GirlFriend("小白");
    }
}
class GirlFriend {
    private String name;

    public GirlFriend(String name) {
        this.name = name;
    }
}
问题抛出,我们只要有一个女朋友对象,怎么去实现呢?
  • 创建方法:构造器私有化(防止直接new),类的内部创建对象,向外暴露一个静态公共方法。

1:饿汉式

public class Test {
    public static void main(String[] args) {
        GirlFriend instance1 = GirlFriend.getInstance();
        System.out.println(instance1);
        GirlFriend instance2 = GirlFriend.getInstance();
        System.out.println(instance2);
        System.out.println(instance1 == instance2);
        System.out.println(GirlFriend.n1);
    }
}
class GirlFriend {
    public static int n1 = 100;
    private String name;

    //第一步将构造器私有化
    private GirlFriend(String name) {
    	System.out.println("构造方法被调用。。。");
        this.name = name;
        
    }

    //第二部在类的内部创建一个静态的对象
    private static GirlFriend gf = new GirlFriend("小红");

    //第三步提提供一个方法,返回一个对象的引用
    public static GirlFriend getInstance() {
        return gf;
    }

    @Override
    public String toString() {
        return "GirlFriend{" +
                "name='" + name + " " +"age:"+n1+""+
                '}';
    }
}

饿汉式的缺点:饿汉式,一般创造重量级的对象,但是有可能创建了对象没有使用,造成资源的浪费

2:懒汉式

class Cat{
    public static int n1 = 999;
    private String name;
    private static Cat cat;
    //构造器私有化
    private Cat(String name) {
        this.name = name;
        System.out.println("构造方法被调用");
    }
    //提供一个静态的方法,返回一个对象的引用
    public static Cat getInstance() {
        if(cat==null) {
            //创建一个对象
            cat = new Cat("小白");
        }
        return cat;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}
public class Test {
    public static void main(String[] args) {
//        Cat cat1 = Cat.getInstance();
//        System.out.println(cat1);
//        Cat cat2 = Cat.getInstance();
//        System.out.println(cat2);
//        System.out.println(cat1 == cat2);
        System.out.println(Cat.n1);
    }
}

懒汉式缺陷:会存在线程安全问题,当多个线程同时调用getInstance()时候,会产生线程安全问题。

3:小结

  • 二者的区别在于创建对象的时机不同,饿汉式在类加载时候创建实例对象,懒汉式是在使用时候才创建
  • 饿汉式不存在线程问题,懒汉式存在线程安全问题
  • 饿汉式存在浪费资源的可能(程序员创建后不使用),懒汉式不存在浪费资源的问题
  • 在我们javaSE标准类中,java.lang.Runtime就是经典的单例模式
    在这里插入图片描述

内部类

  • 前言:内部类是在一个类的内部又听到的另一个类的结构,被嵌套的类成为内部类,嵌套其他类的对象成为外部类

1:内部类的分类

  • 按位置可以分为两类:在属性位置上和在方法里面的内部类
  • 定义在类的局部位置(方法/代码块)(1)局部内部类(有类名),(2)匿名内部类(没有类名)
  • 定义在成员位置,(1)成员内部类(没有static修饰),(2)静态内部类(有static修饰)

2:内部类的基本语法

2.1:局部内部类

2.1.1基本语法
  • 说明:局部内部类是定义在外部类的局部位置,比如方法中(方法块中),并且有类名
  • 1:可以直接防蚊外部类的成员和方法,包括私有的
  • 2:不能添加访问修饰符,因为它的地位就是一个局部变量,局部变量是不能使用修饰符的,但是可以使用final和abstract修饰,因为局部变量也可以使用final
  • 3:作用域仅仅是它的方法体内或者是代码块内
  • 4:局部内部类----访问------->外部类成员【直接访问】
  • 5:外部类----访问----->局部内部类的成员,访问方式:创建对象,在访问(必须在作用域内)
  • 6:外部其他类-----不能访问----->局部内部类(因为局部内部类的地位是一个局部变量)
  • 6:如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果访问外部类的成员,则可以使用(外部类名.this.成员)
  • 记住:(局部内部类定义在方法中/代码块),作用域在方法体或者代码块,本质还是一个类
class Outer{ //外部类
	方法{
		class Inner//	
	}
}
public class LocalInnerClass {//
    public static void main(String[] args) {
//演示一遍
        Outer02 outer02 = new Outer02();
        outer02.m1();
        System.out.println("outer02 的 hashcode=" + outer02);
    }
}
class Outer02 {//外部类
    private int n1 = 100;
    private void m2() {
        System.out.println("Outer02 m2()");
    }//私有方法
    public void m1() {//方法
//1.局部内部类是定义在外部类的局部位置,通常在方法
//3.不能添加访问修饰符,但是可以使用 final 修饰
//4.作用域 : 仅仅在定义它的方法或代码块中
        final class Inner02 {//局部内部类(本质仍然是一个类)
//2.可以直接访问外部类的所有成员,包含私有的
private int n1 = 800;
            public void f1() {
//5. 局部内部类可以直接访问外部类的成员,比如下面 外部类 n1 和 m2()
//7. 如果外部类和局部内部类的成员重名时,默认遵循就近原则,如果想访问外部类的成员,
// 使用 外部类名.this.成员)去访问
// 老韩解读 Outer02.this 本质就是外部类的对象, 即哪个对象调用了 m1, Outer02.this 就是哪个对象
                System.out.println("n1=" + n1 + " 外部类的 n1=" + Outer02.this.n1);
                System.out.println("Outer02.this hashcode=" + Outer02.this);
                m2();
            }
        }
//6. 外部类在方法中,可以创建 Inner02 对象,然后调用方法即可
        Inner02 inner02 = new Inner02();
        inner02.f1();
    }
}

2.2:匿名内部类

2.2.1:基本语法
  • 1:本质还是类,内部类,该类没有名字,同时还是一个对象
  • 2:匿名内部类定义在外部类的局部位置,比如方法和方法体
  • 3:内部类的基本语法
new (抽象)类或者接口(参数列表) {
	类体
};
public class AnonymousInnerClass {
    public static void main(String[] args) {
        Outer04 outer04 = new Outer04();
        outer04.method();
    }
}
class Outer04 { //外部类
    private int n1 = 10;//属性
    public void method() {//方法
//基于接口的匿名内部类
//1.需求: 想使用 IA 接口,并创建对象
//2.传统方式,是写一个类,实现该接口,并创建对象
//3.需求是 Tiger/Dog 类只是使用一次,后面再不使用
//4. 可以使用匿名内部类来简化开发
//5. tiger 的编译类型 ? IA
//6. tiger 的运行类型 ? 就是匿名内部类 Outer04$1
/*
我们看底层 会分配 类名 Outer04$1
class Outer04$1 implements IA {
@Override
public void cry() {
System.out.println("老虎叫唤...");
}
}
*/
//7. jdk 底层在创建匿名内部类 Outer04$1,立即马上就创建了 Outer04$1 实例,并且把地址
// 返回给 tiger
//8. 匿名内部类使用一次,就不能再使用
        IA tiger = new IA() {
            @Override
            public void cry() {
                System.out.println("老虎叫唤...");
            }
        };
        System.out.println("tiger 的运行类型=" + tiger.getClass());
        tiger.cry();
        tiger.cry();
        tiger.cry();
// IA tiger = new Tiger();
// tiger.cry();
//演示基于类的匿名内部类
//分析
//1. father 编译类型 Father
//2. father 运行类型 Outer04$2
//3. 底层会创建匿名内部类
/*
class Outer04$2 extends Father{
@Override
public void test() {
System.out.println("匿名内部类重写了 test 方法");
}
}
*/
//4. 同时也直接返回了 匿名内部类 Outer04$2 的对象
//5. 注意("jack") 参数列表会传递给 构造器
        Father father = new Father("jack"){
            @Override
            public void test() {
                System.out.println("匿名内部类重写了 test 方法");
            }
        };
        System.out.println("father 对象的运行类型=" + father.getClass());//Outer04$2
        father.test();
//基于抽象类的匿名内部类
        Animal animal = new Animal(){
            @Override
            void eat() {
                System.out.println("小狗吃骨头...");
            }
        };
        animal.eat();
    }
}
interface IA {//接口
    public void cry();
}
//class Tiger implements IA {
//
// @Override
// public void cry() {
// System.out.println("老虎叫唤...");
// }
//}
//class Dog implements IA{
// @Override
// public void cry() {
// System.out.println("小狗汪汪...");
// }
//}
class Father {//类
    public Father(String name) {//构造器
        System.out.println("接收到 name=" + name);
    }
    public void test() {//方法
    }
}
abstract class Animal { //抽象类
    abstract void eat();
}
  • 2:匿名类的语法比较特殊,匿名类即使一个类的定义,也是一个对象,从语法层面上看,它既有定义类的特征,也有创建对象的特征,因此可以调用匿名内部类的方法
  • 3:可以直接访问外部所有类的成员,包括私有的
  • 4:不能添加访问修饰符,因为它的地位是一个局部变量
  • 5:作用域:仅仅是它这个方法内,或者代码块内
  • 6:匿名内部类-----访问-----外部类成员(直接访问)
  • 7:外部其他类不能访问匿名内部类(因为匿名内部类是一个局部变量)
  • 8:如果外部类和匿名内部类的成员重名时,匿名内部类访问,,默认执行就近原则,想要访问外部类成员,则可以使用(外部类名.this.成员)去访问
public class AnonymousInnerClass {
        public static void main(String[] args) {
        Outer05 outer05 = new Outer05();
        outer05.f1();
    //外部其他类---不能访问----->匿名内部类
        System.out.println("main outer05 hashcode=" + outer05);
    }
}
class Outer05 {
    private int n1 = 99;
    public void f1() {
//创建一个基于类的匿名内部类
//不能添加访问修饰符,因为它的地位就是一个局部变量
//作用域 : 仅仅在定义它的方法或代码块中
        Person p = new Person(){
            private int n1 = 88;
            @Override
            public void hi() {
//可以直接访问外部类的所有成员,包含私有的
//如果外部类和匿名内部类的成员重名时,匿名内部类访问的话,
//默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.this.成员)去访问
                System.out.println("匿名内部类重写了 hi 方法 n1=" + n1 +
                        " 外部内的 n1=" + Outer05.this.n1 );
//Outer05.this 就是调用 f1 的 对象
                System.out.println("Outer05.this hashcode=" + Outer05.this);
            }
        };
        p.hi();//动态绑定, 运行类型是 Outer05$1
//也可以直接调用, 匿名内部类本身也是返回对象
// class 匿名内部类 extends Person {}
// new Person(){
// @Override
// public void hi() {
// System.out.println("匿名内部类重写了 hi 方法,哈哈...");
// }
// @Override
// public void ok(String str) {
// super.ok(str);
// }
// }.ok("jack");
    }
}
class Person {//类
    public void hi() {
        System.out.println("Person hi()");
    }
    public void ok(String str) {
        System.out.println("Person ok() " + str);
    }
}
//抽象类/接口...

2.2.2:最佳用法
public class InnerClassExercise01 {
    public static void main(String[] args) {
//当做实参直接传递,简洁高效
        f1(new IL() {
            @Override
            public void show() {
                System.out.println("这是一副名画~~...");
            }
        });
//传统方法
        f1(new Picture());
    }
    //静态方法,形参是接口类型
    public static void f1(IL il) {
        il.show();
    }
}
//接口
interface IL {
    void show();
}
//类->实现 IL => 编程领域 (硬编码)
class Picture implements IL {
    @Override
    public void show() {
        System.out.println("这是一副名画 XX...");
    }
}

3.1:成员内部类

3.1.1:基本语法
  • 说明:成员内部类是定义在外部类的成员位置,并且没有static修饰
  • 1:可以直接访问外部类的所有成员,包括私有的
  • 2:可以添加任意的访问修饰符,因为它的地位就是一个成员
  • 3:作用域:和外部类的其他成员一样,为整个类体
  • 4:成员内部类-----访问----->外部类成员(直接访问)、
  • 5:外部类------访问----->成员内部类(先创造对象,访问)
  • 6:外部其他类-----访问-----成员内部类(创造外部类对象)
  • 7:如果外部类和内部类成员重名时,内部类访问的话,默认遵循就近原则,如果像访问外部类的成员,则可以使用(外部类.this.成员)去访问
public class MemberInnerClass01 {
    public static void main(String[] args) {
        Outer08 outer08 = new Outer08();
        outer08.t1();
//外部其他类,使用成员内部类的三种方式
//老韩解读
// 第一种方式
// outer08.new Inner08(); 相当于把 new Inner08()当做是 outer08 成员
// 第二种这就是一个语法,不要特别的纠结. 
Outer08.Inner08 inner08 = outer08.new Inner08();
        inner08.say();
// 第二方式 在外部类中,编写一个方法,可以返回 Inner08 对象
        Outer08.Inner08 inner08Instance = outer08.getInner08Instance();
        inner08Instance.say();
}
}
}
class Outer08 { //外部类
    private int n1 = 10;
    public String name = "张三";
    private void hi() {
        System.out.println("hi()方法...");
    }
    //1.注意: 成员内部类,是定义在外部内的成员位置上
//2.可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
    public class Inner08 {//成员内部类
        private double sal = 99.8;
        private int n1 = 66;
        public void say() {
//可以直接访问外部类的所有成员,包含私有的
//如果成员内部类的成员和外部类的成员重名,会遵守就近原则. //,可以通过 外部类名.this.属性 来访问外部类的成员
            System.out.println("n1 = " + n1 + " name = " + name + " 外部类的 n1=" + Outer08.this.n1);
            hi();
        }
    }
    //方法,返回一个 Inner08 实例
    public Inner08 getInner08Instance(){
        return new Inner08();
    }
    //写方法
    public void t1() {
//使用成员内部类
//创建成员内部类的对象,然后使用相关的方法
        Inner08 inner08 = new Inner08();
        inner08.say();
        System.out.println(inner08.sal);
    }
}

4:静态内部类的使用

4.1:基本语法
  • 说明静态内部类是定义在外部类的成员位置,并且有static修饰
  • 1:可以最直接访问外部类所有静态成员,包含私有的,但不能直接访问非静态成员
  • 2:可以添加任意访问修饰符,因为它的地位是一个成员
  • 3:作用域:同其他成员,是整个类体
  • 4:静态内部类----访问---->外部类(比如静态属性)(直接访问所有的静态属性成员)
  • 5:外部类—访问------静态内部类 访问方式:创建对象,再访问
  • 6:其他外部其他类—访问—静态内部类
  • 7:如果外部类和静态内部类的成员重名时,静态内部类访问时,默认遵循就近原则,如果访问外部类成员,则可以使用(类名,成员)
public class StaticInnerClass01 {
    public static void main(String[] args) {
        Outer10 outer10 = new Outer10();
        outer10.m1();
//外部其他类 使用静态内部类
//方式 1
//因为静态内部类,是可以通过类名直接访问(前提是满足访问权限)
        Outer10.Inner10 inner10 = new Outer10.Inner10();
        inner10.say();
//方式 2
//编写一个方法,可以返回静态内部类的对象实例. Outer10.Inner10 inner101 = outer10.getInner10();
        System.out.println("============");
        inner101.say();
        Outer10.Inner10 inner10_ = Outer10.getInner10_();
        System.out.println("************");
        inner10_.say();
    }
}
class Outer10 { //外部类
    private int n1 = 10;
    private static String name = "张三";
    private static void cry() {}
//Inner10 就是静态内部类
//1. 放在外部类的成员位置
//2. 使用 static 修饰
//3. 可以直接访问外部类的所有静态成员,包含私有的,但不能直接访问非静态成员
//4. 可以添加任意访问修饰符(public、protected 、默认、private),因为它的地位就是一个成员
//5. 作用域 :同其他的成员,为整个类体
static class Inner10 {
    private static String name = "代阳";
    public void say() {
//如果外部类和静态内部类的成员重名时,静态内部类访问的时,
//默认遵循就近原则,如果想访问外部类的成员,则可以使用 (外部类名.成员)
        System.out.println(name + " 外部类 name= " + Outer10.name);
        cry();
    }
}
    public void m1() { //外部类---访问------>静态内部类 访问方式:创建对象,再访问
        Inner10 inner10 = new Inner10();
        inner10.say();
    }
    public Inner10 getInner10() {
        return new Inner10();
    }
    public static Inner10 getInner10_() {
        return new Inner10();
    }
}
;