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关键字修饰,当类加载时就会执行静态代码块,也就是说静态代码块的作用是对类进行初始化,只会被执行一次。而前面的例子中的代码块是普通代码块,每次创建对象都会执行一次。
那么类什么时候被加载?学习反射的时候相信大家对类加载应该比较熟悉了,以下几种情况都会进行类加载
- 创建类的实例对象
- 创建子类的对象时候会优先加载父类
- 使用类的静态变量或静态方法
来看一下这个示例程序
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安全基础——内部类和代码块的主要内容,如果未能解决你的问题,请参考以下文章