
1.static: 静态的,加载到方法区的
static修饰符可以修饰属性、方法、代码块
静态方法中为什么不能使用this?
答:this是指向一个对象实例的,静态方法中没有实例变量,所以也就不能使用, 可以将该变量改为静态变量,然后直接使用,或者使用类名
1.1 区分:
静态:类名加方法名调用,也可以创建对象调用
- 静态属性是属于类的。
- 静态的属性和方法是在类加载的时候加载到方法区。
- 类所有对象共享的
- 类加载到内存中(方法区)的时候,系统就会给类中的静态属性做初始化赋默认值
非静态:创建对象后可以调用
- 非静态属性,是属于对象的,一定要使用对象来访问,没有其他方式 。
- 是创建对象的时候才会加载到栈区。
- 创建对象栈区开辟空间后,系统会自动给对象中的非静态属性做初始化赋默认值
1.2 静态方法与非静态方法的相互使用:
静态方法中无法加载非静态属性和方法,但可以通过创建对象的方式访问。
非静态的只有在创建对象的时候才会加载
在类加载完成的时候,往往在内存中,还没有创建这个类的对象,没有对象(也就没有this)就不能访问类中的非静态属性和非静态方法。
非静态方法中可以加载静态和非静态的方法和属性
1.3初始化操作:
起因:类中的构造器,可以给非静态属性做初始化,但是不能给静态属性做初始化。
结果:
静态代码块:也叫做静态初始化代码块,它的作用就是给类中的静态属性做初始化的
- 类加载的时候,自动执行
- 静态代码块只会自动被执行一次
匿名代码块:
- 它的作用是给非静态属性做初始化操作
- 它能完成的工作,使用构造器也一样可以完成,使用的场景较少。
- 构造器执行之前,自动执行。并且每次创建对象时,匿名代码块都会被自动执行。
1.4 完整的加载过程实例:静态的是自上而下的加载
jvm首先会扫描所有的静态类并加载到方法区中
之后至上而下给静态属性赋初始值,静态方法分配空间(方法区)
静态属性赋值显示赋值
加载子类前加载父类,因此执行父类的静态代码块,再执行子类的静态代码块
为子类堆区开辟空间,同时初始化子类非静态的属性赋默认值
调用父类的匿名代码块,父类构造器
对子类进行显示赋值(父类因没有创建对象因此没有显式赋值)
执行子类的匿名代码块,子类构造器
创建和初始化对象的过程:
Student s = new Student();
以这句代码为例进行说明:
- 对Student类进行类加载,同时初始化类中静态的属性赋默认值,给静态方法分配内存空间
- 执行类中的静态代码块
- 默认赋值:堆区中分配对象的内存空间,同时初始化对象中的非静态的属性赋默认值
- 调用Student的父类构造器
- 对Student中的属性进行显示赋值,例如 public int age = 20;
- 执行匿名代码块
- 执行构造器代码
- =号赋值操作,把对象的内存地址赋给变量s
public class Person{
private String name = "zs";
public Person() {
System.out.println("Person构造器");
print();
}
public void print(){
System.out.println("Person print方法: name = "+name);
}
}
class Student extends Person{
private String name = "tom";
{
System.out.println("Student匿名代码块");
}
static{
System.out.println("Student静态代码块");
}
public Student(){
System.out.println("Student构造器");
}
public void print(){
System.out.println("student print方法: name = "+name);
}
public static void main(String[] args) {
new Student();
}
}
运行结果:
Student静态代码块
Person构造器
student print方法: name = null
Student匿名代码块
Student构造器
注意注意注意!!!!
注意1,子类重写父类的方法,在创建子类对象的过程中,默认调用的一定是子类中重写后的方法
注意2,非静态属性的
显示赋值:是在父类构造器执行结束之后和子类中的匿名代码块执行之前的时候。
默认赋值:父类构造器执行之前,静态代码块执行之后
注意3,以上代码中,因为方法的重写,会调用子类中重写后的print方法,同时该方法恰好是在父类构造器执行中调用的,而这个时候子类中的name属性还没有进行显示赋值,所以是输出结果为null
注意4,如果此时在子类的匿名代码块中也输出name的值,那么就会显示tom,因为已经完成了属性的显示赋值
1.5 静态导入:JDK1.5及以上版本
在自己的类中,要使用另一个类中的静态属性和静态方法,那么可以进行静态导入,导入完成后,可以直接使用这个类中的静态属性和静态方法,而不用在前面加上类名。
import static java.lang.Math.PI;
import static java.lang.Math.random;
public class Demo {
public void test(){
System.out.println(PI);
System.out.println(random());
}
}
2. final
2.1 修饰类
用final修饰的类不能被继承,也就是说这个类是没有子类的。
2.2 修饰方法
用final修饰的方法可以被子类继承,但是不能被子类的重写 。
2.3 修饰变量
局部变量:
用final修饰的变量就变成了常量,并且它只能被赋一次值,第二次赋值就会报错
非静态成员变量:
对于这个final成员变量a,只有一次赋值的机会。
JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值:
- 声明的同时赋值
- 匿名代码块中赋值
- 构造器中赋值,此时还有额外要求:类中出现的所有构造器都要赋值,否则报错
final修饰静态成员变量:
对于这个final静态成员变量a,只有一次赋值的机会。
JVM不再为其进行默认赋值,我们需要手动在以下地方对其进行赋值:
- 声明的同时赋值
- 静态代码块中赋值
final修饰引用类型变量:
注意,此时final指的是,引用s的指向的对象不能改变,但是可以使用s来操作当前指向的对象属性和方法
3. abstract:修饰符
抽象类中不一定有抽象方法,有抽象方法的一定时抽象类,抽象方法一定是要被继承实现的
抽象类和抽象方法的关系:
- 抽象类中可以没有抽象方法
- 有抽象方法的类,一定要声明为抽象类
存在的必要性:
- 因为抽象类不能实例化对象,所以必须要有子类来实现它之后才能使用。这样就可以把一些具有相同属性和方法的组件进行抽象,这样更有利于代码和程序的维护。
- 当又有一个具有相似的组件产生时,只需要实现该抽象类就可以获得该抽象类的那些属性和方法。
- 有一些方法可以写,但没有意义,因此引入抽象
关于抽象类的一些疑惑?
- 抽象类有构造器,便于子类继承
- 抽象类的抽象方法,让别人使用的时候必须实现
- 抽象方法去实现,普通方法被重写
4. interface:解决类的单继承不好扩展
4.1类型:
引用数据类型:类、数组、接口
接口并不是类,而是另外一种引用数据类型,接口的内部主要就是封装了方法和静态常量。
无构造器
4.2注意点:
abstract不能和final private static 连用:
private:
abstract修饰方法只有方法声明,没有方法实现。抽象方法的意义在于子类实现。而private修饰后为私有,子类不能继承,也就不能使用。所以二者冲突,不能共存。final:
abstract修饰的类,该类中的方法子类继承之后需要重写的,可是final修饰的类不能被继承,也没子类,方法更不能得到重写。所以二者冲突,不能共存。static:
static修饰的是静态方法,可以直接被调用;而abstract修饰的类中只有方法声明,没有方法实现,不能被直接调用。所以二者冲突,不能共存。
4.3 示例:
//使用interface关键字来定义接口
public interface Action {
//接口中的静态常量
public static final String OPS_MODE = "auto";
//接口中的抽象方法
public abstract void start();
//接口的抽象方法
public void stop();
}
接口里面的属性,默认就是public static final修饰的
接口里面的方法,默认就是public abstract修饰的
所以这些都可以省去不写的
4.4 实现
- 类实现一个接口 和 类继承一个父类 的效果类似,格式相仿,只是关键字不同,实现使用 implements 关键字,而继承使用的是extends关键字
- 一个类实现了接口,那么就要实现接口中所有的抽象方法,否则这个类自己就必须声明为抽象类
- 一个类实现了多个接口,那么就需要把这多个接口中的抽象想法全都实现
4.5接口的继承
java中,类和类之间是单继承,接口和接口之间是多继承
示例:
//实现该接口的类,将具有run的功能
public interface Runable {
void run();
}
//实现该接口的类,将具有fly的功能
interface Flyable{
void fly();
}
//实现该接口的类,将具有run的功能,fly的功能,以及Action接口独有的doSomething功能
interface Action extends Runable,Flyable{
void doSomething();
}
//实现类,实现Action接口,就必须要实现Action及其父接口中的所有抽象方法
class Demo implements Action{
@Override
public void run() {
}
@Override
public void fly() {
}
@Override
public void doSomething() {
}
}
4.6 接口的多态
- 多态的前提是继承,必须要先有子父类关系才行,而类和接口之间的实现关系,其实也是继承的一种形式,所以在类和接口的实现关系中,也可以使用多态
- 抽象方法的实现,也算是一种方法的重写,形式、语法完全一致。
子类与父类方法调用时的顺序关系:
当使用多态时子类和父类,调用方法时先查找父类,如果父类有方法,在查找子类是否重写,重写则输出子类,否则输出父类。
在编译的时候,编译器会先检查引用 子类所属的类型父类中,是否存在当前要调用的方法
,如果没有那么就直接编译报错。
5.内部类
类中可以嵌套接口,接口的内部也可以嵌套其他接口。
例如,参考java.util.Map接口中的内部接口Entry
类型:
成员内部类
静态内部类
局部内部类
匿名内部类 (关键)
注意点
注意,成员内部类中,不能编写静态的属性和方法
注意,当前这个代码,编译成功后,会生成俩个class文件,一个对应外部类,一个对应内部类
编译生成的俩个class文件的名字分别为:
MemberOuterClass.class
MemberOuterClass$MemberInnerClass.class
5.1成员内部类和外部类的相互访问 :
成员内部类访问外部的属性和方法 :
//访问当前run方法中的参数name System.out.println(name); //访问内部类自己的属性name System.out.println(this.name); //访问外部类的非静态属性 System.out.println(MemberOuterClass.this.name); //访问外部类的静态属性 System.out.println(MemberOuterClass.age); //访问外部类的非静态方法 MemberOuterClass.this.run(); //访问外部类的静态方法 MemberOuterClass.go();
外部类访问成员内部类的属性和方法 :
//需要创建内部类对象,然后才可以访问 MemberInnerClass t = new MemberInnerClass(); System.out.println(t.name); System.out.println(t.age);
在其他类中使用这个内部类:
如果这个成员内部类不是private修饰的,那么在其他类中就可以访问到这个内部类MemberOuterClass moc = new MemberOuterClass(); MemberInnerClass mic = moc.new MemberInnerClass();
意义:
在对事物进行抽象的时候,若一个事物内部还包含其他事物,就可以考虑使用内部类这种结构。
例如,汽车(Car)中包含发动机(Engine) ,这时, Engine 类就可以考虑(非必须)使用内部类来描述,定义在Car类中的成员位置。这样设计,既可以表示Car和Engine的紧密联系的程度,也可以在Engine类中很方便的使用到Car里面的属性和方法
5.2 静态内部类和外部类的相互访问:
注意的点在其他类中使用这个内部类:
import com.briup.sync.StaticOuterClass.StaticInnerClass; public class Test { public static void main(String[] args) { //不需要通过外部类创建内部类对象,直接创建内部类对象 StaticInnerClass sic = new StaticInnerClass(); sic.run("tom"); } }
- 静态内部类中访问不了外部类中的非静态属性和方法
- 这个内部类需要import导入,并且是 外部类.内部类 的形式导入。
- 在创建对象的时候,直接使用这个静态内部类的名字即可: new 静态内部类对象(); ,不再需要依赖外部类对象了。
5.3 局部内部类和外部类的相互访问:
局部内部类访问外部的属性和方法 :
//将String name 改为别的名字 cride public void sayHello(String name){ /* 局部内部类 声明开始 */ class LocalInnerClass{ private String name; public void test(String name){ //访问当前test方法中的参数name System.out.println(name); //访问内部类自己的属性name System.out.println(this.name); /*注意,sayHello方法的参数name,无法访问,因为实在没有办法表示了,和内部类方法的形参一样了,换成其他名字后,就可以访问了,不要叫name就行*/ System.out.println(cride); //访问外部类的非静态属性 System.out.println(LocalOuterClass.this.name); //访问外部类的非静态方法 LocalOuterClass.this.run(); //访问外部类的静态属性和方法 System.out.println(LocalOuterClass.age); LocalOuterClass.go(); } } /* 局部内部类 声明结束 */ }
局部内部类中,访问当前方法中的变量,这个变量必须是final修饰的 :
在JDK1.8中,一个局部变量在局部内部类中进行访问了,那么这个局部变量自动变为final修饰
public void sayHello(final String name){ final int num = 1; /* 局部内部类 声明开始 */ class LocalInnerClass{ public void test(){ System.out.println(name); System.out.println(num); //编译报错,final修饰的变量,只能赋值一次 //name = "tom"; //num = 2; } } /* 局部内部类 声明结束 */ }
外部类访问局部内部类的属性和方法 :
public void sayHello(String name){ /* 局部内部类 声明开始 */ class LocalInnerClass{ private int num; public void test(){ } } /* 局部内部类 声明结束 */ //创建局部内部类对象 LocalInnerClass lic = new LocalInnerClass(); //对象访问属性 System.out.println(lic.num); //对象调用方法 lic.test(); }
5.4 匿名内部类: 接口和继承
在普通的代码中,使用一个接口的步骤如下:
- 声明一个类,去实现这个接口
- 实现这个接口中的抽象方法(重写)
- 在其他代码中,创建这个类的对象
- 调用类中实现(重写)后的方法
使用匿名内部类,就可以把这个过程给给简化了,让我们更加方便的调用到实现(重写)后的方法!
格式:
父类或者接口类型 变量名 = new 父类或者接口(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
//调用实现(重写)后的方法
变量名.method();
匿名内部类因为没有类名:
- 匿名内部类必须依托于一个父类型或者一个接口
- 匿名内部类在声明的同时,就必须创建出对象,否则后面就没法创建了
- 匿名内部类中无法定义构造器
示例:
public interface Algorithm{
void sort(int[] arr);
}
class Test{
//使用指定算法,对数组arr进行排序
public void sort(int[] arr,Algorithm alg){
alg.sort(arr);
}
}
public static void main(String[] args){
Test t = new Test();
int[] arr = {4,1,6,3,8,5,9};
Algorithm alg = new Algorithm(){
public void sort(int[] arr){
//使用当前需要的排序算法
//例如,这里简单的使用Arrays工具类中的排序方法
java.util.Arrays.sort(arr);
}
};
t.sort(arr,alg);
}
5.5 选择
内部类的选择:
假设现在已经确定了要使用内部类,那么一般情况下,该如何选择?
- 考虑这个内部类,如果需要反复的进行多次使用(必须有名字)
- 在这个内部类中,如果需要定义静态的属性和方法,选择使用静态内部类
- 在这个内部类中,如果需要访问外部类的非静态属性和方法,选择使用成员内部类
- 考虑这个内部类,如果只需要使用一次(可以没有名字)
- 选择使用匿名内部类
- 局部内部类,几乎不会使用