代码块,内部类
代码块
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();
}
}