初始化代码块、static / final修饰符、抽象、接口和内部类

静态属性和方法

总则

静态方法中【不可以】直接访问类中的非静态属性和非静态方法,但是返过来,类中的非静态方法中【可以】直接访问类中的静态方法和静态属性。

注意,this和super都属于类中非静态的变量,所以静态方法中是无法使用this和super这俩个关键字的

原理

为什么静态方法中不可以直接访问非静态的属性和访问,而非静态的方法中却可以直接访问静态的属性和方法

类中的属性和方法,都必须分配内存空间,在其中进行初始化操作之后,才能被访问和调用。

静态的属性和方法,在类被加载到内存之后,已经在一个叫静态代码区的内存区域中,专门的做好了初始化相关的操作,这些属性和方法随便可以被类进行调用。

非静态属性和方法,是在使用new关键字创建对象之后,在对象所占据的内存空间中,完成了对这些非静态属性和方法的初始化相关工作,在这之后,我们才可以使用对象来访问这些非静态的属性和方法。

从这个初始化的时间点上来看,可以知道,静态的属性和方法在初始化完成可以被调用的时候,那些非静态的属性和方法还没有完成相关的初始化工作,也就是不能被调用,所以静态方法中无法【直接】访问非静态的属性和方法。

但是非静态属性和方法完成初始化工作,可以被调用的时候,类中的那些静态属性和方法,早就完成了初始化工作,所以非静态方法中,可以【直接】调用类中的静态属性和方法

父类的静态方法可以被继承,但不可以被重写

初始化代码块

调用

匿名代码块会在创建对象的时候自动执行(可多次)且在构造器执行之前

静态代码块只执行一次,在类加载在内存的时候,此后不再执行

作用

匿名代码块的作用,是给对象里面的非静态属性进行初始化赋值的。可被构造器替代,且构造器可被调用,匿名代码块不可

静态代码块的作用,是给类中的静态属性做初始化,也可以在类加载的时候就给这个类本身做一些其他的初始化工作。因为静态代码块执行的时间点比较,例如,可以在静态代码中,早早的对静态属性做初始化赋值工作。

例如:

public class Student{
    {
		System.out.println("匿名代码块执行");
    }
    static{
		System.out.println("静态代码块执行");
	}
public Student(){
	System.out.println("构造器执行");
	}
public static void main(String[] args){
    new Student();
    new Student();
    new Student();
 	}
}

//静态代码块执行
//匿名代码块执行
//构造器执行
//匿名代码块执行
//构造器执行
//匿名代码块执行
//构造器执行

优先级:

例如:怎么能保证静态属性在被访问之前,一定是完成了初始化工作的?可以使用静态代码块来完成

public class Student{
   public static String name;
    static{

         name = "tom";

     }

     public Student(){}

     

     public static void main(String[] args){

         //注意,这个时候这个输出的值为tom

         System.out.println(Student.name);

     }

}

针对这句代码:Student s = new Student(); // 假设Student类之前没有被加载过的执行顺序:

 1. 类加载,初始化形态属性的默认值

 2. 执行静态代码块

 3. 分配内存空间,同时初始化非静态属性的默认值

 4. 如果有继承,则会初始化父类的属性

 5. 对本类中的非静态属性做显示赋值

总结:

  1. 第一梯队 静态属性:类加载 静态代码块

  2. 第二梯队 父类中的非静态属性:super()特殊语法,这里会调用父类中的匿名代码块和构造器

  3. 第三梯队 子类中的非静态属性:匿名代码块 、构造器

final修饰符

  1. final修饰的类不能被子类继承

  2. final修饰的方法,可以被子类继承,但不能被子类重写

  3. final修饰的变量,只可以被赋值一次,再次赋值就会出错,因为此时这个变量已经成为常量了

抽象类和方法

目的是为了配合多态,更好的符合面向对象的设计原则,这样当一个引用到抽象父类的子类实例,就可以不必强制转型成子类实例,也可以使用在父类(因为不知道怎么实现)而抽象的方法(在子类中实现)

Public abstract class //声明一个抽象类

  1. 抽象类中可以有抽象方法,也可以没有,但只要有抽象方法,该类就必须声明为抽象类

  2. 子类继承了抽象父类,必须实现它的所有方法,否则子类也必须为抽象类

  3. 抽象类不可以被实例化,不能使用new来创建对象,但可以被引用

Public abstract void test() //声明一个抽象方法

  1. 抽象方法无法执行,但是可以被调用是因为调用是由编译器决定的(实例调用的方法是哪个类的哪个方法)最后执行时是JVM决定的

  2. Static和final修饰的方法不可以被声明为抽象方法,只要是不能重写的方法都不可以成为抽象方法,因为无法实现(final和static没有冲突)

接口

接口总则

接口在某种程度上与抽象类类似,都是用多个相似类中抽象出来的规范,接口更甚(不关心具体实现,也不提供)体现的是规范和现实分离的设计哲学。接口降低了各个模块之间的耦合,因此他应该是多个类共同的公共行为规范,这些行为是与外部交流的通道(因为接口中定义的通常是一组公共方法)

Pulic interface XX extends XX1, XX2…{
	// 常量定义
	// 抽象方法
	// 内部类
	// 不可包含构造器和初始化块
}
  1. 接口里的所有内容(方法,常量等)都是public的(可省略,若添加则只能是public)

  2. 接口中的静态常量会被默认添加public static final(可省略)同时因为没有构造器和初始化块,所以接口里的成员变量只能在定义时指定默认值。

  3. 接口中的方法必须是抽象方法,默认添加public abstract修饰(可省略)不得实现,类方法和默认方法必须实现

使用接口

一个类可以继承一个类和实现多个接口,并可以获得所实现接口里定义的所有常量和方法,同样继承后就必须实现所有方法,否则需要声明成抽象类,接口不能显示继承任何类(可以继承多个父接口)但所有接口类型的引用变量都可以直接付给Object类型的引用变量(向上转型)

例如:类A可以同时实现接口B、C、D、E…,这时候类A的对象就属于B、C、D、E等类型了,可以使用多态:一个接口的引用,可以指向它的任意一个实现类对象。

B b = new A();
C c = new A();
D d = new A();
E e = new A();

注意,这里使用不同类型的引用B C D E来指向A类的对象,它们区别在于,使用不同类型的引用的时候,我们所能调用到的方法是不一样的。

例如,B b = new A(); 引用b所能调用到的方法,只有B类型中定义的方法 和 Object中定义的方法。

接口和抽象

  1. 接口和抽象都不能被实例化,他们都某种程度上位于继承树的顶点,用于被其他类实现和继承
  2. 接口是系统与外交交互的窗口,是一种规范,它规定了实现者必须向外提供哪些服务,调用者可以调用哪些服务。
  3. 抽象类体现的是一种模板式的设计,接口是大纲,抽象类是中间产品

内部类

成员内部类,静态内部类,局部内部类,匿名内部类

成员内部类

public class Outter{ //外部类
    
    //成员内部类 声明开始
    public class Inner{
        private String name;
        private int age;
        // 错误 private static char sex;
        public void run(){
            //...
            
        public Inner(String name, int age){
            this.name = name;
            this.age = age;
        }
            
        }
    }
}

当前这个情况,编译成功后,会生成俩个class文件,一个对象外部类,一个对应内部类
class文件的名字为:
MemberOutterClass.class
MemberOutterClass$MemberInnerClass.class

编写属性和方法

成员内部类中,private的属性和方法是无法被外部访问的,普通的可以,且【不能】编写静态的属性和方法,可以编写构造器,如果有需要的话,如果不编写的话,那么默认使用无参的构造器。

和外部类的相互访问

public class MemberOutterClass{

	private String name;

	private static int age;

	public void run(){}

	public static void go(){}

	public void test(){
		//在外部类中,任何访问成员内部类中的属性和方法
		//MemberInnerClass mic = this.new MemberInnerClass();
		//在外部类中,可以把 this. 省去
		//这个意思就是成员内部类对象的创建,是需要依托于外部类对象的
		//就像一个类中的成员属性、成员方法,需要依托于这个类的对象才能访问
		MemberInnerClass mic = new MemberInnerClass();
		System.out.println(mic.name);
		System.out.println(mic.age);
		mic.run();
	}

	/* 成员内部类 声明开始 */
	public class MemberInnerClass{
		private String name;
		public  int age;
		
		public void run(){}

		public void test(String name){
			//这个方法的输参数name
			System.out.println(name);

			//访问当前内部类中name属性
			System.out.println(this.name);
			System.out.println(MemberInnerClass.this.name);
			
			//访问外部类中的name属性(非静态)
			System.out.println(MemberOutterClass.this.name);
			
			//访问外部类中的age属性(静态)
			System.out.println(MemberOutterClass.age);
			
			//访问外部类中的run方法(非静态)
			MemberOutterClass.this.run();
			//访问外部类中的go方法(静态)
			MemberOutterClass.go();

			//访问内部类中自己的run方法
			MemberInnerClass.this.run();
			
		}

	}
	/* 成员内部类 声明结束 */

}
  1. 在内部类中,可以使用 类名.this 的形式,当前使用的this指的是哪一个类中的this

  2. 在外部类中,要访问成员内部类中的属性和方法,需要先创建这个成员内部类的对象

  3. 成员内部类对象,是要依托于外部类对象,也就是一定先创建了外部类对象,然后才能创建这个成员内部类对象。

import ...
public class MemberOutterClassTest{
    
    public static void main(String[] args){
        
        //MemberOutterClass moc = new MemberOutterClass();
        //MemberInnerClass mic = moc.new MemberInnerClass();
        
        MemberInnerClass mic = new MemberOutterClass().new;
        MemberInnerClass();
        
        mic.run();
    }
}

静态内部类

public class StaticOutterClass{
	private String name;
	private static int age;

	private void run(){}
	private static void go(){}

	public void test(){
		
		//在外部类中,访问静态内部类中的静态属性和方法
		System.out.println(StaticInnerClass.age);
		StaticInnerClass.go();
		//在外部类中,访问静态内部类中的非静态属性和方法
		//这时候需要先创建静态内部类对象,使用对象来访问
		StaticInnerClass sic = new StaticInnerClass();
		System.out.println(sic.name);
		sic.run();
	}
    
    //在静态内部类中访问不了外部类中的非静态属性和方法

编写属性和方法

与成员内部类相似,【可以】编写静态的属性和方法,另外在四种内部类中,只有静态内部类可以编写静态属性和方法。同样,静态内部类中,可以编写构造器,如果有需要的话,如果不编写的话,那么默认使用无参的构造器。

和外部类的相互访问

静态内部类和成员内部类不同,静态内部类由于是静态的,它可以独立存在,并不会依赖于外部类的对象。而成员内部类就必须依赖于外部类对象,也就是必须先要创建出外部类对象,才能接下来创建成员内部类对象。

public class StaticOutterClassTest{
	
	public static void main(String[] args){
		
		//直接使用静态内部类来创建对象,不需要依赖外部类
		//但是要记得先import导入
		StaticInnerClass sic = new StaticInnerClass();
		sic.run();

	}

}

局部内部类

与前两类相似,不同的是

  1. 局部内部类相比前两类使用较少,不常用
  2. 局部内部类是定义在方法中的一种内部类,旨在方法中起作用
  3. JDK1.8之前,在局部内部类中访问方法体中的局部变量,必须是final修饰的,1.8之后可以访问非final变量,但访问之后就自动变成final的了
  4. 局部内部类不能被该方法外的任何位置访问

匿名内部类

  1. 注意,匿名内部类是将来我们在程序中,使用最多的一种内部类形式。

  2. 匿名内部类可以定义在方法中,也可以在类中属性赋值的时候进行使用。但是绝大多数都会是在方法中进行定义和使用。

  3. 匿名内部类必须依附在一个类或者接口上来进行定义,如果是依附在一个类上,那么这个匿名内部类就默认是这个类的子类。如果是依附在一个接口上,那么这个匿名内部类就默认是这个接口的实现类。

  4. 匿名内部类,必须在声明的同时就创建出这个类的对象,并且只能使用这一次。因为它没有名字。

public class AnonymousOutterClass{

	//在给类中属性private Person person; 赋值的时候
	//=号右边使用了匿名内部类对象,来给=号左边的引用person进行赋值
	private Person person = new Person(){
		public void run(){
		
		}
	};
	
	public void test(){
		//虽然抽象类不能直接new对象,但是我们可以依附在这个抽象类上,声明出一个匿名内部类,并且同时就创建这个匿名内部类的对象。
		//注意,这个里的这个大括号,就是这个匿名内部类的代码实现
		//这个匿名内部类,就相当于继承了Person这个父类型

		//父类型的引用,指向子类对象,只不过这次的这个子类对象,是一个匿名内部类对象。
		Person p = new Person(){
			
			public void run(){
				System.out.println("这里就是匿名内部类的实现");
			}

		};


		//虽然接口不能直接new对象,但是我们可以依附在这个接口上,声明出一个匿名内部类,并且同时就创建这个匿名内部类的对象。
		//这个匿名内部类,默认就是对这个接口的实现
		Action a = new Action(){
			
			public void go(){
				System.out.println("这里就是匿名内部类的实现");
			}

		};

	}

}

abstract class Person{
	public abstract void run();
}

interface Action{
	public void go();
}

编译后的class文件:
当前类中编译后,生成四个class文件,因为在外部类中,定义了三个匿名内部类
AnonymousOutterClass.class
AnonymousOutterClass$1.class
AnonymousOutterClass$2.class
AnonymousOutterClass$3.class
可以使用javap命令反向解析这个class,可以查看到构造器信息以及这个匿名内部类的名字、继承或者实现的信息等

定义属性和方法、构造器 / 和外部类的相互访问

在匿名内部类中,一般情况,我们不会编写单独的属性、方法(如果有需要的话,可以编写)因为在外面不能能直接调用,我们也编写不了构造器,因为没有名字。我们在匿名内部类中,做的最多的事情,就是【重写】父类中的方法,或者【实现】接口中的抽象方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值