初始化代码块、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. 对本类中的非静态属性做显示赋值
总结:
-
第一梯队 静态属性:类加载 静态代码块
-
第二梯队 父类中的非静态属性:super()特殊语法,这里会调用父类中的匿名代码块和构造器
-
第三梯队 子类中的非静态属性:匿名代码块 、构造器
final修饰符
-
final修饰的类不能被子类继承
-
final修饰的方法,可以被子类继承,但不能被子类重写
-
final修饰的变量,只可以被赋值一次,再次赋值就会出错,因为此时这个变量已经成为常量了
抽象类和方法
目的是为了配合多态,更好的符合面向对象的设计原则,这样当一个引用到抽象父类的子类实例,就可以不必强制转型成子类实例,也可以使用在父类(因为不知道怎么实现)而抽象的方法(在子类中实现)
Public abstract class //声明一个抽象类
-
抽象类中可以有抽象方法,也可以没有,但只要有抽象方法,该类就必须声明为抽象类
-
子类继承了抽象父类,必须实现它的所有方法,否则子类也必须为抽象类
-
抽象类不可以被实例化,不能使用new来创建对象,但可以被引用
Public abstract void test() //声明一个抽象方法
-
抽象方法无法执行,但是可以被调用是因为调用是由编译器决定的(实例调用的方法是哪个类的哪个方法)最后执行时是JVM决定的
-
Static和final修饰的方法不可以被声明为抽象方法,只要是不能重写的方法都不可以成为抽象方法,因为无法实现(final和static没有冲突)
接口
接口总则
接口在某种程度上与抽象类类似,都是用多个相似类中抽象出来的规范,接口更甚(不关心具体实现,也不提供)体现的是规范和现实分离的设计哲学。接口降低了各个模块之间的耦合,因此他应该是多个类共同的公共行为规范,这些行为是与外部交流的通道(因为接口中定义的通常是一组公共方法)
Pulic interface XX extends XX1, XX2…{
// 常量定义
// 抽象方法
// 内部类
// 不可包含构造器和初始化块
}
-
接口里的所有内容(方法,常量等)都是public的(可省略,若添加则只能是public)
-
接口中的静态常量会被默认添加public static final(可省略)同时因为没有构造器和初始化块,所以接口里的成员变量只能在定义时指定默认值。
-
接口中的方法必须是抽象方法,默认添加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中定义的方法。
接口和抽象
- 接口和抽象都不能被实例化,他们都某种程度上位于继承树的顶点,用于被其他类实现和继承
- 接口是系统与外交交互的窗口,是一种规范,它规定了实现者必须向外提供哪些服务,调用者可以调用哪些服务。
- 抽象类体现的是一种模板式的设计,接口是大纲,抽象类是中间产品
内部类
成员内部类,静态内部类,局部内部类,匿名内部类
成员内部类
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();
}
}
/* 成员内部类 声明结束 */
}
-
在内部类中,可以使用 类名.this 的形式,当前使用的this指的是哪一个类中的this
-
在外部类中,要访问成员内部类中的属性和方法,需要先创建这个成员内部类的对象
-
成员内部类对象,是要依托于外部类对象,也就是一定先创建了外部类对象,然后才能创建这个成员内部类对象。
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();
}
}
局部内部类
与前两类相似,不同的是
- 局部内部类相比前两类使用较少,不常用
- 局部内部类是定义在方法中的一种内部类,旨在方法中起作用
- JDK1.8之前,在局部内部类中访问方法体中的局部变量,必须是final修饰的,1.8之后可以访问非final变量,但访问之后就自动变成final的了
- 局部内部类不能被该方法外的任何位置访问
匿名内部类
-
注意,匿名内部类是将来我们在程序中,使用最多的一种内部类形式。
-
匿名内部类可以定义在方法中,也可以在类中属性赋值的时候进行使用。但是绝大多数都会是在方法中进行定义和使用。
-
匿名内部类必须依附在一个类或者接口上来进行定义,如果是依附在一个类上,那么这个匿名内部类就默认是这个类的子类。如果是依附在一个接口上,那么这个匿名内部类就默认是这个接口的实现类。
-
匿名内部类,必须在声明的同时就创建出这个类的对象,并且只能使用这一次。因为它没有名字。
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,可以查看到构造器信息以及这个匿名内部类的名字、继承或者实现的信息等
定义属性和方法、构造器 / 和外部类的相互访问
在匿名内部类中,一般情况,我们不会编写单独的属性、方法(如果有需要的话,可以编写)因为在外面不能能直接调用,我们也编写不了构造器,因为没有名字。我们在匿名内部类中,做的最多的事情,就是【重写】父类中的方法,或者【实现】接口中的抽象方法。