1-java安全基础——内部类和代码块

Posted songly_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了1-java安全基础——内部类和代码块相关的知识,希望对你有一定的参考价值。

1. 代码块

代码块又称为初始块,是属于类的一部分。代码块有些类似于方法,将代码逻辑封装在方法体并用“{ }”符号括起来。代码块与方法的区别在于,方法拥有函数名,返回值,参数,方法体。但代码块只有方法体,并且代码块不会通过类或对象来显示调用,而是在加载类或创建对象时隐式调用。

代码块的语法:

[修饰符]{
    //代码逻辑
}

      修饰符的内容是可选的,如果要写的话只能写static,表示这是一个静态代码块,如果不写的话表示这是一个普通代码块。

       代码块可以理解为是另一种形式的构造器,当类加载或创建对象时进行一些初始化操作,使用场景为:当一个类中有多个构造方法,但这些构造方法中有很多重复的代码,那么就可以把这些重复的代码抽取出来放在一个代码块中,并且代码块优先于构造方法调用。

代码块示例程序:

package com.test;

class Student {

    private String name;
    private int age;

    //普通代码块
    {
        System.out.println("代码块被调用了......");
    }

    public Student(){}

    public Student(String name){
        System.out.println("Student(String name)");
        this.name = name;
    }

    public Student(int age){
        System.out.println("Student(int age)");
        this.age = age;
    }

    public Student(String name , int age){
        System.out.println("Student(String name , int age)");
        this.name = name;
        this.age = age;
    }
}

public class CodeBlockTest1 {
    public static void main(String[] args) throws  Exception{
        Student student1 = new Student("zhangsan");
        Student student2 = new Student(123);
        Student student3 = new Student("lisi",10);
    }
}

每次创建Student对象时,调用构造之前都会优先执行代码块的内容,程序执行结果如下:

代码块被调用了......

Student(String name)

代码块被调用了......

Student(int age)

代码块被调用了......

Student(String name , int age)

       静态代码块通常被static关键字修饰,当类加载时就会执行静态代码块,也就是说静态代码块的作用是对类进行初始化,只会被执行一次。而前面的例子中的代码块是普通代码块,每次创建对象都会执行一次。

那么类什么时候被加载?学习反射的时候相信大家对类加载应该比较熟悉了,以下几种情况都会进行类加载

  1. 创建类的实例对象
  2. 创建子类的对象时候会优先加载父类
  3. 使用类的静态变量或静态方法

来看一下这个示例程序

package com.test;

class Person{
    //静态变量
    public static int num = 10;
    //代码块
    static {
        System.out.println("static code block is call");
    }
    //构造
    public Person(){
        System.out.println("Person() is call");
    }
}

class Student extends Person {
    public static int num2 = 20;
    public Student(){
        System.out.println("Student() is call");
    }
}

public class CodeBlockTest1 {
    public static void main(String[] args) throws  Exception{
        //使用类的静态成员
        //System.out.println(Student.num2);
        //创建实例对象
        Student student = new Student();
    }
}

Student类继承了Person类,当创建Student对象时会先调用静态代码块,并且父类的构造优先于子类调用。

静态代码块,普通代码块,构造方法的执行顺序:

package com.test;

class Person{
    //静态变量
    public static int num = 10;
    //代码块
    static {
        System.out.println("Person static code block is call");
    }
    //普通代码块
    {
        System.out.println("Person code block is call");
    }
    //构造
    public Person(){
        System.out.println("Person() is call");
    }
}

class Student extends Person {

    public static int num2 = 20;
    //静态代码块
    static {
        System.out.println("Student static code block is call");
    }
    //普通代码块
    {
        System.out.println("Student code block is call");
    }
    //构造
    public Student(){
        System.out.println("Student() is call");
    }
}

public class CodeBlockTest1 {
    public static void main(String[] args) throws  Exception{
        //使用类的静态成员
        //System.out.println(Student.num2);
        //创建实例对象
        Student student = new Student();
    }
}

程序输出的执行顺序为:静态代码块  -->  普通代码块   -->  构造(父类的构造方法优先于子类的普通代码块)。

2. 内部类

如果一个类定义在另一个类的内部的话,这个类通常称为内部类。

内部类的语法:

public class OuClass {  //外部类

    class InnerClass{  //内部类(也称为成员内部类)

    }
}

        内部类的特点就是可以直接访问OuClass 类的私有成员。内部类分类:定义在外部类的局部位置上的类有:局部内部类和匿名内部类,定义在外部类的成员位置上的类有:成员内部类和静态内部类。

3. 局部内部类

局部内部类就是定义在外部类的局部位置上(例如方法中,并且有类名),例如在method1方法中定义一个名字叫InnerClass的局部内部类

public class OuClass {  //外部类
    private int num1 = 10;
    private void method3(){
        System.out.println("method3");
    }

    public void method1(){
        class InnerClass{  //局部内部类
            public void method2(){
                //可以直接访问外部类OuClass的私有属性和方法
                System.out.println(OuClass.this.num1);
                method3();
            }
        }
        InnerClass innerClass = new InnerClass();
        innerClass.method2();
    }
}

class OuClass2{
    public static void main(String[] args) {
        OuClass ouClass = new OuClass();
        ouClass.method1();
    }
}

局部内部类可以直接访问外部类的私有属性和方法,但num1成员属性不是静态的,因此需要通过OuClass.this.num1方式访问,OuClass.this表示以OuClass对象方式访问num1成员变量

局部内部类还可以使用final关键字修饰,但被final修饰后就不能被其他类继承

局部内部类的作用域只能在定义的method1方法内部,也就是说内部类InnerClass只能在method1方法内部访问。

4. 匿名内部类

匿名内部类定义在外部类的局部位置上,例如方法中,但是没有类名,使用匿名内部类的前提必须先继承一个类或实现一个接口。

定义匿名内部类方式的格式如下:

new 类名或接口名(){
             //重写方法
            @Override
            public void method() {
            }
};

匿名内部类有很多的应用场景,以最为常见的使用方式举例,例如我们想要使用一个接口的方法,通常的做法是我们会先创建一个类实现这个接口的方法

interface ClassA{
    public void method1();
}

class ClassB implements ClassA{
    @Override
    public void method1() {
        System.out.println("call method1");
    }
}

public class ClassTest {
    public static void main(String[] args) {
        ClassB classB = new ClassB();
        classB.method1();
    }
}

但有时候我们并不想创建一个类,因为这个方法只调用一次,那么就可以使用匿名内部类方式,例如我们想要使用接口ClassA中的方法,可以通过创建匿名内部类方式重写接口ClassA中的方法也可以实现调用method1方法。

interface ClassA{
    public void method1();
}


public class ClassTest {
    public static void main(String[] args) {
        //创建匿名内部类
        ClassA classA = new ClassA(){
            @Override
            public void method1() {
                System.out.println("call method1");
            }
        };
        classA.method1();
        classA.method1();
        classA.method1();
        System.out.println(classA.getClass());
    }
}

 new ClassA(){}操作表示创建一个匿名内部类并返回一个匿名内部类对象,classA现在就是匿名内部类对象,调用getClass方法获取匿名内部类对象classA的运行类型,发现匿名内部类在底层的类名实际上叫ClassTest$1

通过程序的执行结果发现匿名内部类在底层还是有一个类名的,既然有类名,那么匿名内部类想要使用一个接口的方法,肯定也要实现这个接口。也就是说匿名内部类底层还是使用了前面我们所说的方式:创建了一个没有名字的类并实现接口ClassA 的方法,匿名内部类的底层伪代码如下:

class ClassTest$1 implements ClassA{
    @Override
    public void method1() {
        System.out.println("call method1");
    }
}

       换句话说,实际上底层还是创建了一个类,并且这个类的名字为ClassTest$1,然后让ClassTest$1实现了ClassA接口并重写method1方法,但是这个匿名内部类是底层“偷偷”创建的,因此这个过程对我们来说是不可见的,所以才叫做匿名内部类(自行体会一下这个过程),通过getClass方法返回的类名可以知道,匿名内部类的类名是以外部类的类名加$1的方式来命名的。

      注意,这个匿名内部类只能用一次,new ClassA(){ }操作创建完匿名内部类的实例对象后,这个匿名内部类就不能再用来创建对象了,但classA对象可以多次调用。

现在我们来看另一个使用场景:如果匿名内部类想要使用某一个类的方法,必须要先继承这个类

class ClassA{
    public int num;
    public ClassA(int num){
        this.num = num;
    }
    
    public void method1(){
        System.out.println("ClassA: call method1");
    }
}

public class ClassTest {
    public static void main(String[] args) {
        ClassA classA = new ClassA(10){
            @Override
            public void method1() {
                System.out.println(super.num);
            }
        };
        classA.method1();
        classA.method1();
        classA.method1();
        System.out.println(classA.getClass());
    }
}

调用getClass方法获取匿名内部类的运行类型,发现匿名内部类在底层的类名叫ClassTest$1

那么我们可以知道实际上基于类的匿名内部类伪代码实现是这样的:

class ClassTest$1 extends ClassA{
    @Override
    public void method1() {
        System.out.println("call method1");
    }
}

       底层还是创建了一个名字为ClassTest$1的类,然后让ClassTest$1继承ClassA类,并重写method1方法,还有一点需要明白:new ClassA(){}操作就是相当于上面这段代码,new ClassA(10)小括号的内容表示参数列表,会调用ClassA类中的单参数构造方法,调用规则一般根据参数的个数和类型调用对应的构造方法。

匿名内部类的调用方式如下

public class ClassTest {
    public static void main(String[] args) {
        //匿名内部类调用方式一
        new ClassA(10){
            @Override
            public void method1() {
                System.out.println(super.num);
            }
        }.method1();
        
        //匿名内部类调用方式二
        ClassA classA = new ClassA(10){
            @Override
            public void method1() {
                super.method1();
            }
        };
        classA.method1();
    }
}

       以上两种匿名内部类的调用方式本质上是一样的,区别在于:方式一直接通过匿名内部类对象来调用method1方法,而方式二先创建一个ClassA 类型的classA变量来接收匿名内部类对象的引用,然后通过对象引用classA来调用method1方法。

什么情况下会使用到匿名内部类?通常的使用场景为:把匿名内部类当做参数传递,来看下面这个示例:

        例如我们想要获取匿名内部类运行阶段的匿名对象的所属类型时,可以通过把匿名内部类当做参数的方式传给println函数,控制台直接输出了匿名内部类对象的虚拟内存地址。

成员内部类

成员内部类是定义在外部类的成员位置,并且没有被static修饰,可以直接访问外部类的所有成员,包括私有的。

class Student{
    private String name;
    protected int age;
    public String country;
    private int num = 10;
    public Student(String name , int age , String country){
        System.out.println("Student Constructor is call");
        this.name = name;
        this.age = age;
        this.country = country;
    }

    public void method2(){
        Student_Inner student_inner = new Student_Inner();
        student_inner.method1();
    }

    //成员内部类,可添加访问权限修饰符
    public class Student_Inner{
        private int num = 20;
        public Student_Inner(){
            System.out.println("Student_Inner Constructor is call");
        }

        public void method1(){
            //内部类可以直接访问外部类的成员
            System.out.println(name);
            System.out.println(age);
            System.out.println(country);
            System.out.println(num);
        }
    }
}

成员内部类跟成员变量和成员方法一样,都可以添加访问权限修饰符,并且还可以使用外部类的成员变量和成员方法,但成员内部类中不能使用静态属性和静态方法。

关于构造方法的调用顺序:外部类的构造的调用优先于内部类的构造。

关于重名的成员变量num的访问顺序:遵循就近原则,先从内部类范围查找,再从外部类查找。

访问成员内部类的方式

public class ClassTest2 {
    public static void main(String[] args) {
        //直接访问成员内部类
        Student.Student_Inner student_inner = new Student("liubei" , 30 , "蜀汉").new Student_Inner();
        student_inner.method1();
        System.out.println("=================分隔线=====================");
        //间接访问成员内部类
        Student student = new Student("sunquan" , 31 , "东吴");
        student.method2();
    }
}

访问成员内部类的方式跟访问外部类的成员变量,成员方法一样,都是要通过外部类的对象来访问

直接访问成员内部类方式:外部内名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();

间接访问成员内部类方式:在外部类的方法中创建内部类,然后main方法中创建外部类对象并调用外部类方法。

静态内部类

静态内部类是定义在外部类的成员位置,并且被static修饰,可以直接访问外部类的所有成员,包括私有的。

class Student{
    private String name;
    protected int age;
    public static String country;

    public void method3(){
        System.out.println("Student: method2");
    }

    public static void method4(){
        System.out.println("Student: method3");
    }

    //静态内部类
    public static class Student_Inner{
        private int num = 20;
        //创建外部类的对象
        Student student = new Student();
        public void method1(){
            method4();
            //不能直接访问method3方法
            //method3();
            student.method3();
            System.out.println(country);
            //不能直接访问非静态属性
            //System.out.println(name);
            System.out.println(student.name);
        }

        public static void method2(){
            System.out.println("Student_Inner:method2");
            //不能直接访问非静态属性
            //System.out.println(name);
            System.out.println(new Student().name);
        }
    }
}

        静态内部类不能直接访问外部类的非静态成员,但是可以通过new 外部类().成员的方式访问,通过创建外部类对象来访问非静态成员属性和成员方法。

以上是关于1-java安全基础——内部类和代码块的主要内容,如果未能解决你的问题,请参考以下文章

大数据必学Java基础(四十六):内部类和面向对象项目实战

片段内部静态类和gradle问题

第三节:Java类和对象之代码块和内部类

深入类和对象

零基础学java-类和对象

Java 基础语法爆肝1W字只为弄懂类和对象