02 代码块、内部类

1. 代码块

代码块是指用大括号{}括起来的一段嗲吗,根据位置及声明关键字的不同,代码块可以分为普通代码块、构造代码块、静态代码块、同步代码块四种

代码块的核心问题:

  1. 代码块初始化是在什么时候?
  2. 代码块的执行顺序是什么样的?
  3. 代码块在继承的时候,执行顺序是什么样子的?

1.1 局部代码块(普通代码块)

在方法中出现

若有多个则依次向下执行

可以访问外界的变量,但是代码块内部的变量无法被外界访问

public class Test03 {
    public static void main(String[] args) {
        
        int num = 1;
        System.out.println("第一次");
        {
            num = 11;
            int num2 = 2;
            System.out.println("第二次");
        }

//        num2 = 22; 报错:未声明
        System.out.println("第三次");
    }
}

执行结果:

第一次
第二次
第三次

1.2 构造代码块

在类中方法外出现

若有多个代码块按顺序依次执行,且每次调用构造方法前都会被执行

在构造方法前执行

class CodeBlock {
    //构造代码块,在方法外出现
    {
        int number1 = 10;
        System.out.println("number1: " + number1);
    }

    //构造方法
    public CodeBlock() {
        System.out.println("这是构造方法");
    }

    //在构造代码块在构造方法前后出现,但构造代码块先于构造方法执行
    {
        int number2 = 100;
        System.out.println("number2: " + number2);
    }
}

//构造代码块测试类
public class CodeBlockTest {
    public static void main(String[] args) {
        // 创建对象
        CodeBlock codeBlock = new CodeBlock();
        // 注意:构造代码块通过构造方法自动调用
    }
}

执行结果:

number1: 10
number2: 100
这是构造方法

1.3 静态代码块

在类中方法外,且加上static修饰

在类被加载的时候就会执行,并且静态代码块只会执行一次;若有多个静态代码块按顺序依次执行

静态代码块中不能有非静态的内容

class StatisCodeBlock {
    //静态代码块,在方法外出现
    static {
        int number1 = 20;
        System.out.println("1、静态代码块变量: " + number1);
    }

    //构造代码块,在方法外出现
    {
        int number2 = 100;
        System.out.println("2、构造代码块变量: " + number2);
    }

    public StatisCodeBlock() {
        System.out.println("这是构造方法 StatisCodeBlock()");
    }

    static {
        int number3 = 200;
        System.out.println("3、静态代码块变量: " + number3);
    }

    //在构造代码块在构造方法前后,但构造代码块先于构造方法执行
    {
        int number4 = 1000;
        System.out.println("4、构造代码块变量: " + number4);
    }
}

//静态代码块测试类
public class CodeBlockTest {
    public static void main(String[] args) {
        // 创建对象
        StatisCodeBlock codeBlock = new StatisCodeBlock();
        // 注意:构造代码块通过构造方法自动调用
        System.out.println("======我是分割线======");
        StatisCodeBlock codeBlock2 = new StatisCodeBlock();
    }
}

执行结果:

1、静态代码块变量: 20
3、静态代码块变量: 200
2、构造代码块变量: 100
4、构造代码块变量: 1000
这是构造方法 StatisCodeBlock()
======我是分割线======
2、构造代码块变量: 100
4、构造代码块变量: 1000
这是构造方法 StatisCodeBlock()

1.4 同步代码块

被synchronized关键词修饰的代码块,代码块中的方法执行完成之前,不允许别的线程进入

public class CodeBlock implements Runnable {
    @Override
    public void run() {
        synchronized (CodeBlock.class) {
            System.out.print("同步代码块!");
        }
    }

    public static void main(String[] args) {
        CodeBlock a = new CodeBlock();
        CodeBlock b = new CodeBlock();
        new Thread(a).start();
        new Thread(b).start();
    }
}

1.5 继承中的代码块执行顺序

  1. 父类静态->子类静态->父类构造代码块->父类构造方法->子类构造代码块->子类构造方法;同一等级中若有多个,则按照出现的顺序执行
class Parent {
    // 静态代码块,在方法外出现
    static {
        int number1 = 20;
        System.out.println("1、父类静态代码块变量: " + number1);
    }

    // 构造代码块,在方法外出现
    {
        int number2 = 100;
        System.out.println("2、父类构造代码块变量: " + number2);
    }

    public Parent() {
        System.out.println("父类构造方法 Parent()");
    }

    static {
        int number3 = 200;
        System.out.println("3、父类静态代码块变量: " + number3);
    }

    // 在构造代码块在构造方法前后,但构造代码块先于构造方法执行
    {
        int number4 = 1000;
        System.out.println("4、父类构造代码块变量: " + number4);
    }
}

class Child extends Parent {
    // 静态代码块,在方法外出现
    static {
        int number1 = 2001;
        System.out.println("11、子类静态代码块变量: " + number1);
    }

    // 构造代码块,在方法外出现
    {
        int number2 = 10001;
        System.out.println("22、子类构造代码块变量: " + number2);
    }

    public Child() {
        System.out.println("子类构造方法 Child()");
    }

    static {
        int number3 = 2002;
        System.out.println("33、子类静态代码块变量: " + number3);
    }

    // 在构造代码块在构造方法前后,但构造代码块先于构造方法执行
    {
        int number4 = 100002;
        System.out.println("44、子类构造代码块变量: " + number4);
    }
}

public class Test03 {
    public static void main(String[] args) {
        // 创建对象
        Child child = new Child();
        // 注意:构造代码块通过构造方法自动调用
    }
}

执行顺序:

1、父类静态代码块变量: 20
3、父类静态代码块变量: 200
11、子类静态代码块变量: 2001
33、子类静态代码块变量: 2002
2、父类构造代码块变量: 100
4、父类构造代码块变量: 1000
父类构造方法 Parent()
22、子类构造代码块变量: 10001
44、子类构造代码块变量: 100002
子类构造方法 Child()

1.6 总结

  1. 静态代码块->构造代码块->构造方法->局部代码块

    创建子类对象之前需要先调用父类构造方法

  2. 代码块初始化时机:构造代码块在实例对象创建时进行初始化;静态代码块在类加载时进行初始化。

  3. 代码块执行顺序:静态代码块 ==> main()方法 ==> 构造代码块 ==> 构造方法 ==> 局部代码块 。

  4. 继承中代码块执行顺序:父类静态块 ==> 子类静态块 ==> 父类代码块 ==> 父类构造器 ==> 子类代码块 ==> 子类构造器 。

2. 内部类

类的内部除了属性和方法外,还可以定义类,成为内部类;内部类分为四种:成员内部类、静态内部类、方法内部类、匿名内部类

内部类有以下3点共性:

  1. 内部类和外部类经过编译后会生成两个class文件
  2. 内部类是外部类的一个成员,所以可以访问外部类的任何成员,包括私有的private修饰的,但是外部类不能直接访问内部类的称员
  3. 内部类可以被private、protect和static修饰,但是外部类只能被public和默认的修饰

内部类也可以分为静态内部类和非静态内部类;非静态内部类也可以分为成员内部类、方法内部类、匿名内部类

创建非静态内部类的两种方式:

  1. Outer.Inner inner1 = new Outer().new Inner();
  2. Outer.Inner inner2 = outer.new Inner();

创建静态内部类的方式:

​ Outer.StaticInner staticInner = new Outer().StaticInner();

静态内部类和非静态内部类的区别:

静态内部类非静态内部类
是否可以有静态成员变量
是否可以访问外部类的非静态变量
是否可以访问外部类的静态变量
创建是否依赖于外部类
public class ClassOuter {
    private int noStaticInt = 1;
    private static int STATIC_INT = 2;

    public void fun() {
        System.out.println("外部类方法");
    }

    public class InnerClass {
        //static int num = 1; 此时编辑器会报错 非静态内部类则不能有静态成员
        public void fun(){
            //非静态内部类的非静态成员可以访问外部类的非静态变量。
            System.out.println(STATIC_INT);
            System.out.println(noStaticInt);
        }
    }

    public static class StaticInnerClass {
        static int NUM = 1;//静态内部类可以有静态成员
        public void fun(){
            System.out.println(STATIC_INT);
            //System.out.println(noStaticInt); 此时编辑器会报 不可访问外部类的非静态变量错
        }
    }
}

public class TestInnerClass {
    public static void main(String[] args) {
        //非静态内部类 创建方式1
        ClassOuter.InnerClass innerClass = new ClassOuter().new InnerClass();
        //非静态内部类 创建方式2
        ClassOuter outer = new ClassOuter();
        ClassOuter.InnerClass inner = outer.new InnerClass();
        // 静态内部类的创建方式
        ClassOuter.StaticInnerClass staticInnerClass = new ClassOuter.StaticInnerClass();
    }
}

2.1 成员内部类

class Outer {
    private String name = "outer";
    private String age = "18";
    private int count;

    // 定义成员内部类,和方法平级
    class Inner {
        private String name = "inner";

        private String sex = "女";

        public void say() {
            // 内部类访问外部私有变量
            // Outer.this 表示外部类对象,因为两个类中的成员变量名重复了,使用该关键字即表示外部类的属性
            System.out.println(Outer.this.name);
//            System.out.println(Outer.this.sex); // 错误,因为内部类中没有该变量 
            // 没有重复,所以无需加Outer.this
            System.out.println(age);
            System.out.println(name);
            System.out.println(count);
        }

    }
}

public class Test04 {
    public static void main(String[] args) {
        // 创建内部类对象的两种方式
        // 方式1
        Outer.Inner inner1 = new Outer().new Inner();
        inner1.say();
        // 方式2
        Outer outer = new Outer();
        Outer.Inner inner2 = outer.new Inner();
    }
}

2.2 局部内部类:

如果一个内部类只在一个方法中使用到了,那么我们可以将这个类定义在方法内部,这种内部类被称为局部内部类。其作用域仅限于该方法

  1. 局部内类不允许使用访问权限修饰符 public private protected 均不允许
  2. 局部内部类对外完全隐藏,除了创建这个类的方法可以访问它其他的地方是不允许访问的。
  3. 局部内部类与成员内部类不同之处是:他可以引用外部方法中的变量,但是并内部不允许修改该变量的值,jdk8之前是只能访问final修饰的,之后是不需要用final修饰了,但是只可以引用,不可以修改,变量会自动提升为final修饰的,引用之后就不可以再操作了。(这句话并不准确,因为如果不是基本数据类型的时候,只是不允许修改引用指向的对象(内存地址),而对象本身的属性可以被就修改的)
public class ClassOuter {
    private int noStaticInt = 1;
    private static int STATIC_INT = 2;

    public void fun() {
        System.out.println("外部类方法");
    }
    
    public void testFunctionClass(){
        class FunctionClass{
            private void fun(){
                System.out.println("局部内部类的输出");
                System.out.println(STATIC_INT);
                System.out.println(noStaticInt);
                System.out.println(params);
                //params ++ ; // params 不可变所以这句话编译错误
            }
        }
        FunctionClass functionClass = new FunctionClass();
        functionClass.fun();
    }
}

2.3 匿名内部类

  • 匿名内部类是没有访问修饰符的。
  • 匿名内部类必须继承一个抽象类或者实现一个接口
  • 匿名内部类中不能存在任何静态成员或方法
  • 匿名内部类是没有构造方法的,因为它没有类名。
  • 与局部内部类相同匿名内部类也可以引用局部变量。此变量也必须声明为 final
public class Button {
    public void click(final int params){
        //匿名内部类,实现的是ActionListener接口
        new ActionListener(){
            public void onAction(){
                System.out.println("click action..." + params);
            }
        }.onAction();
    }
    //匿名内部类必须继承或实现一个已有的接口
    public interface ActionListener{
        public void onAction();
    }

    public static void main(String[] args) {
        Button button=new Button();
        button.click();
    }
}

2.4 局部内部类和匿名内部类中,内部类引用的局部变量必须final修饰的原因

为什么局部变量需要final修饰呢

因为局部变量和匿名内部类的生命周期不同。

匿名内部类是创建后是存储在堆中的,而方法中的局部变量是存储在Java栈中,当方法执行完毕后,就进行退栈,同时局部变量也会消失。

那么此时匿名内部类还有可能在堆中存储着,那么匿名内部类要到哪里去找这个局部变量呢?

为了解决这个问题编译器为自动地帮我们在匿名内部类中创建了一个局部变量的备份,也就是说即使方法执结束,匿名内部类中还有一个备份,自然就不怕找不到了。

但是问题又来了。

如果局部变量中的a不停的在变化,那么岂不是也要让备份的a变量无时无刻的变化,为了保持局部变量与匿名内部类中备份域保持一致,编译器不得不规定死这些局部域必须是常量,一旦赋值不能再发生变化了。

例如:

public class Out {  
      
    public void test(final String a) {  
        class In{  
              
            public void function() {  
                System.out.println(a);  
            }  
        }  
        new In().function();  
    }  
      
    public static void main(String[] args) {  
        new Out().test("hi");  
    }  
}  

编译这个类后发现产生了两个class文件

也就是说内部类和外部类各一个class文件,这样就产生了一个问题,调用内部类方法的时候如何访问外部类方法中的局部变量呢?

实际上编译后的内部类的构造方法的里面,传了对应的外部类的引用和所有局部变量的形参。

(由于外部类方法执行完后局部变量会消亡,所以内部类构造函数中的局部变量实际是一份“复制”。而为了访问外部类中的私有成员变量,外部类编译后也产生了访问类似与getXXX的方法。)

这时产生了一个不一致的问题,如果局部变量不设为final,那内部类构造完毕后,外部类的局部变量又改变了那怎么办?

2.5 内部类的作用

我们为什么需要内部类?或者说内部类为啥要存在?

  • 内部类方法可以访问该类定义所在作用域中的数据,包括被 private 修饰的私有数据
  • 内部类可以对同一包中的其他类隐藏起来
  • 内部类可以解决 java 单继承的缺陷
  • 当我们想要定义一个回调函数却不想写大量代码的时候我们可以选择使用匿名内部类来实现

2.5.1 可以无条件地访问外围类的所有元素

为什么可以引用?

内部类虽然和外部类写在同一个文件中, 但是编译完成后, 还是生成各自的class文件,内部类通过this访问外部类的成员。

  1. 编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象(this)的引用
  2. 编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为内部类中添加的成员变量赋值
  3. 在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用
/**
 * 内部类无条件访问外部类元素
 */
public class DataOuterClass {

    private String data = "外部类数据";

    private class InnerClass {

        public InnerClass() {

            System.out.println(data);

        }

    }

    public void getInner() {

        new InnerClass();

    }

    public static void main(String[] args) {

        DataOuterClass outerClass = new DataOuterClass();

        outerClass.getInner();

    }

}

输出结果:

外部类数据

data这是在DataOuterClass定义的私有变量。这个变量在内部类中可以无条件地访问

2.5.2 实现隐藏

关于内部类的第二个好处其实很显而易见,我们都知道外部类即普通的类不能使用 private protected 访问权限符来修饰的,而内部类则可以使用 private 和 protected 来修饰。当我们使用 private 来修饰内部类的时候这个类就对外隐藏了。这看起来没什么作用,但是当内部类实现某个接口的时候,在进行向上转型,对外部来说,就完全隐藏了接口的实现了
从这段代码里面我只知道OuterClass的getInner()方法能返回一个InnerInterface接口实例但我并不知道这个实例是这么实现的。而且由于InnerClass是private的,所以我们如果不看代码的话根本看不到这个具体类的名字,所以说它可以很好的实现隐藏。

接口

public interface InnerInterface {

    void innerMethod();

}

具体类

/**
 * 实现信息隐藏
 */
public class OuterClass {

    /**
     * private修饰内部类,实现信息隐藏
     */
    private class InnerClass implements InnerInterface {

        @Override
        public void innerMethod() {
            System.out.println("实现内部类隐藏");
        }

    }

    public InnerInterface getInner() {

        return new InnerClass();

    }

}

调用程序

public class Test {

    public static void main(String[] args) {

        OuterClass outerClass = new OuterClass();

        InnerInterface inner = outerClass.getInner();

        inner.innerMethod();

    }

}
// 打印结果:实现内部类隐藏

2.5.3 实现多重继承

java 是不允许使用 extends 去继承多个类的。内部类的引入可以很好的解决这个事情。

Java只能继承一个类这个学过基本语法的人都知道,而在有内部类之前它的多重继承方式是用接口来实现的。但使用接口有时候有很多不方便的地方。比如我们实现一个接口就必须实现它里面的所有方法

而有了内部类就不一样了。它可以使我们的类继承多个具体类或抽象类

类一:

public class ExampleOne {

    public String name() {

        return "inner";

    }

}

类二:

public class ExampleTwo {

    public int age() {

        return 25;

    }

}

类三:类三MainExample就拥有了ExampleOne和ExampleTwo的方法和属性

public class MainExample {

   /**
    * 内部类1继承ExampleOne
    */
   private class InnerOne extends ExampleOne {

       public String name() {

           return super.name();

       }

   }

   /**
    * 内部类2继承ExampleTwo
    */
   private class InnerTwo extends ExampleTwo {

       public int age() {

           return super.age();

       }

   }

   public String name() {

       return new InnerOne().name();

   }

   public int age() {

       return new InnerTwo().age();

   }

   public static void main(String[] args) {

       MainExample mi = new MainExample();

       System.out.println("姓名:" + mi.name());

       System.out.println("年龄:" + mi.age());

   }

}

2.5.4 通过匿名内部类来优化简单的接口实现,简化代码

...
    view.setOnClickListener(new View.OnClickListener(){
        @Override
        public void onClick(){
            // ... do XXX...
        }
    })
...

2.6 实际开发中内部类有可能引起的问题

2.6.1 内部类会造成程序的内存泄漏,即改被回收的没有被回收

java 虚拟机会通过内存回收机制来判定引用是否可达,如果不可达就会在某些时刻去回收这些引用

  1. 如果一个匿名内部类没有被任何引用持有,那么匿名内部类对象用完就有机会被回收。
  2. 如果内部类仅仅只是在外部类中被引用,当外部类的不再被引用时,外部类和内部类就可以都被GC回收。
  3. 如果当内部类的引用被外部类以外的其他类引用时,就会造成内部类和外部类无法被GC回收的情况,即使外部类没有被引用,因为内部类持有指向外部类的引用)
public class ClassOuter {

    Object object = new Object() {
        public void finalize() {
            System.out.println("inner Free the occupied memory...");
        }
    };

    public void finalize() {
        System.out.println("Outer Free the occupied memory...");
    }
}

public class TestInnerClass {
    public static void main(String[] args) {
        try {
            Test();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private static void Test() throws InterruptedException {
        System.out.println("Start of program.");

        ClassOuter outer = new ClassOuter();
        Object object = outer.object;
        outer = null;

        System.out.println("Execute GC");
        System.gc();

        Thread.sleep(3000);
        System.out.println("End of program.");
    }
}

运行程序发现 执行内存回收并没回收 object 对象,这是因为即使外部类没有被任何变量引用,只要其内部类被外部类以外的变量持有,外部类就不会被GC回收。我们要尤其注意内部类被外面其他类引用的情况,这点导致外部类无法被释放,极容易导致内存泄漏。

2.6.2 内部类导致内存泄漏的解决办法

以通过声明一个static的内部类来解决问题,从反编译中可以看出,声明为static的类不会持有外部类的引用,如果你想使用外部类的话,可以通过软引用的方式保存外部类的引用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值