JavaSE(三)之static、final、abstract修饰符
一、static修饰符
1.1、static变量
在类中,使用static修饰的成员变量,就是静态变量,反之为非静态变量。
静态变量和非静态变量的区别
静态变量属于类的,"可以"使用类名来访问,非静态变量是属于对象的,"必须"使用对象来访问.
public class Student{ private static int age; private double score; public static void main(String[] args) { Student s = new Student(); //推荐使用类名访问静态成员 System.out.println(Student.age); System.out.println(s.age); System.out.println(s.score); } }
静态变量对于类而言在内存中只有一个,能被类的所有实例所共享。实例变量对于类的每个实例都有一份,它们之间互不影响.
例如:
public class Student{ private static int count; private int num; public Student() { count++; num++; } public static void main(String[] args) { Student s1 = new Student(); Student s2 = new Student(); Student s3 = new Student(); Student s4 = new Student(); //因为还是在类中,所以可以直接访问私有属性 System.out.println(s1.num); System.out.println(s2.num); System.out.println(s3.num); System.out.println(s4.num); System.out.println(Student.count); System.out.println(s1.count); System.out.println(s2.count); System.out.println(s3.count); System.out.println(s4.count); } }
在加载类的过程中为静态变量分配内存,实例变量在创建对象时分配内存
所以静态变量可以使用类名来直接访问,而不需要使用对象来访问.
1.2、static方法
在类中,使用static修饰的成员方法,就是静态方法,反之为非静态方法。
静态方法和非静态方法的区别
静态方法数属于类的,"可以"使用类名来调用,非静态方法是属于对象的,"必须"使用对象来调用.
静态方法"不可以"直接访问类中的非静态变量和非静态方法,但是"可以"直接访问类中的静态变量和静态方法
注意:this和super在类中属于非静态的变量.(静态方法中不能使用)
例如:
public class Student{ private static int count; private int num; public void run(){} public static void go(){} public static void test(){ //编译通过 System.out.println(count); go(); //编译报错 System.out.println(num); run(); } }
非静态方法"可以"直接访问类中的非静态变量和非静态方法,也"可以"直接访问类中的静态变量和静态方法
例如:
public class Student{ private static int count; private int num; public void run(){} public static void go(){} public void test(){ //编译通过 System.out.println(count); go(); //编译通过 System.out.println(num); run(); } }
思考:为什么静态方法和非静态方法不能直接相互访问?
父类的静态方法可以被子类继承,但是不能被子类重写
例如:
public class Person { public static void method() {} } //编译报错 public class Student extends Person { public void method(){} } 例如: public class Person { public static void test() { System.out.println("Person"); } } //编译通过,但不是重写 public class Student extends Person { public static void test(){ System.out.println("Student"); } } main: Perosn p = new Student(); p.test();//输出Person p = new Person(); p.test();//输出Perosn 和非静态方法重写后的效果不一样
父类的非静态方法不能被子类重写为静态方法
例如:
public class Person { public void test() { System.out.println("Person"); } } //编译报错 public class Student extends Person { public static void test(){ System.out.println("Student"); } }
1.3、代码块和静态代码块
1)类中可以编写代码块和静态代码块
例如:
public class Person {
{
//代码块(匿名代码块)
}
static{
//静态代码块
}
}
2)匿名代码块和静态代码块的执行
因为没有名字,在程序并不能主动调用这些代码块。
匿名代码块是在创建对象的时候自动执行的,并且在构造器执行之前。同时匿名代码块在每次创建对象的时候都会自动执行.
静态代码块是在类加载完成之后就自动执行,并且只执行一次.
注:每个类在第一次被使用的时候就会被加载,并且一般只会加载一次.
例如:
1 public class Person { 2 { 3 System.out.println("匿名代码块"); 4 } 5 6 static{ 7 System.out.println("静态代码块"); 8 } 9 public Person(){ 10 System.out.println("构造器"); 11 } 12 } 13 main: 14 Student s1 = new Student(); 15 Student s2 = new Student(); 16 Student s3 = new Student();
//输出结果:
静态代码块
匿名代码块
构造器
匿名代码块
构造器
匿名代码块
构造器
3)匿名代码块和静态代码块的作用
匿名代码块的作用是给对象的成员变量初始化赋值,但是因为构造器也能完成这项工作,所以匿名代码块使用的并不多。
静态代码块的作用是给类中的静态成员变量初始化赋值。
例如:
1 public class Person { 2 public static String name; 3 static{ 4 name = "tom"; 5 } 6 public Person(){ 7 name = "zs"; 8 } 9 } 10 main: 11 System.out.println(Person.name);//tom
注:在构造器中给静态变量赋值,并不能保证能赋值成功,因为构造器是在创建对象的时候才指向,但是静态变量可以不创建对象而直接使用类名来访问.
1.4、创建和初始化对象的过程
Student s = new Student();
4.1)Student类之前没有进行类加载
1、类加载,同时初始化类中静态的属性
2、执行静态代码块
3、分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
4、调用Student的父类构造器
5、对Student中的属性进行显示赋值(如果有的话)
6、执行匿名代码块
7、执行构造器
8、返回内存地址
注:子类中非静态属性的显示赋值是在父类构造器执行完之后和子类中的匿名代码块执行之前的时候
例如:
1 public class Person{ 2 private String name = "zs"; 3 public Person() { 4 System.out.println("Person构造器"); 5 print(); 6 } 7 public void print(){ 8 System.out.println("Person print方法: name = "+name); 9 } 10 } 11 12 public class Student extends Person{ 13 private String name = "tom"; 14 { 15 System.out.println("Student匿名代码块"); 16 } 17 18 static{ 19 System.out.println("Student静态代码块"); 20 } 21 public Student(){ 22 System.out.println("Student构造器"); 23 } 24 public void print(){ 25 System.out.println("student print方法: name = "+name); 26 } 27 public static void main(String[] args) { 28 new Student(); 29 } 30 }
//输出结果:
Student静态代码块
Person构造器
student print方法: name = null
Student匿名代码块
Student构造器
Student s = new Student();
4. 2)Student类之前已经进行了类加载
1.分配内存空间,同时初始化非静态的属性(赋默认值,0/false/null)
2.调用Student的父类构造器
3.对Student中的属性进行显示赋值(如果有的话)
4.执行匿名代码块
5.执行构造器
6.返回内存地址
5)静态导入
静态导入是JDK5.0引入的新特性。
例如:
1 import static java.lang.Math.random; 2 import static java.lang.Math.PI; 3 4 public class Test { 5 public static void main(String[] args) { 6 //之前是需要Math.random()调用的 7 System.out.println(random()); 8 System.out.println(PI); 9 } 10 }
二、final修饰符
2.1、修饰类
用final修饰的类不能被继承,没有子类。
例如:我们是无法写一个类去继承String类,然后对String类型扩展的,因为API中已经被String类定义为final的了.
我们也可以定义final修饰的类:
1 public final class Action{ 2 3 } 4 5 //编译报错 6 public class Go extends Action{ 7 8 }
2.2、修饰方法
用final修饰的方法可以被继承,但是不能被子类的重写。
例如:每个类都是Object类的子类,继承了Object中的众多方法,在子类中可以重写toString方法、equals方法等,但是不能重写getClass方法 wait方法等,因为这些方法都是使用fianl修饰的。
我们也可以定义final修饰的方法:
1 public class Person{ 2 public final void print(){} 3 } 4 5 //编译报错 6 public class Student extends Person{ 7 public void print(){ 8 9 } 10 }
2.3、修饰变量
用final修饰的变量表示常量,只能被赋一次值.其实使用final修饰的变量也就成了常量了,因为值不会再变了。
1)修饰局部变量:
例如:
1 public class Person{ 2 public void print(final int a){ 3 //编译报错,不能再次赋值,传参的时候已经赋过了 4 a = 1; 5 } 6 } 7 8 public class Person{ 9 public void print(){ 10 final int a; 11 a = 1; 12 //编译报错,不能再次赋值 13 a = 2; 14 } 15 }
2)修饰成员变量:
非静态成员变量:
public class Person{
private final int a;
}
只有一次机会,可以给此变量a赋值的位置:
声明的同时赋值
匿名代码块中赋值
构造器中赋值(类中出现的所有构造器都要写)
静态成员变量:
public class Person{
private static final int a;
}
只有一次机会,可以给此变量a赋值的位置:
声明的同时赋值
静态代码块中赋值
3)修饰引用变量
1 main: 2 final Student s = new Student(); 3 //编译通过 4 s.setName("tom"); 5 s.setName("zs"); 6 7 //编译报错,不能修改引用s指向的内存地址 8 s = new Student();
三、abstract修饰符
abstract修饰符可以用来修饰方法也可以修饰类,如果修饰方法,那么该方法就是抽象方法;如果修饰类,那么该类就是抽象类。
3.1、抽象类和抽象方法的关系
抽象类中可以没有抽象方法,但是有抽象方法的类一定要声明为抽象类。
3.2、语法
public abstract class Action{
public abstract void doSomething();
}
public void doSomething(){...}
对于这个普通方法来讲:
"public void doSomething()"这部分是方法的声明
"{...}"这部分是方法的实现,如果大括号中什么都没写,就叫方法的空实现
声明类的同时,加上abstract修饰符就是抽象类
声明方法的时候,加上abstract修饰符,并且去掉方法的大口号,同时结尾加上分号,该方法就是抽象方法。
3.3、特点及作用
抽象类,不能使用new关键在来创建对象,它是用来让子类继承的。
抽象方法,只有方法的声明,没有方法的实现,它是用来让子类实现的。
注:子类继承抽象类后,需要实现抽象类中没有实现的抽象方法,否则这个子类也要声明为抽象类。
例如:
public abstract class Action{
public abstract void doSomething();
}
main:
//编译报错,抽象类不能new对象
Action a = new Action();
//子类继承抽象类
public class Eat extends Action{
//实现父类中没有实现的抽象方法
public void doSomething(){
//code
}
}
main:
Action a = new Eat();
a.doSomething();
注:子类继承抽象类,那么就必须要实现抽象类没有实现的抽象方法,否则该子类也要声明为抽象类。
3.4、思考
思考1:抽象类不能new对象,那么抽象类中有没有构造器?
思考2:抽象类和抽象方法意义(为什么要编写抽象类、抽象方法)
有抽象方法的抽象类
无抽象方法的抽象类