三大修饰符
关于修饰符的知识,分为访问修饰符和三大修饰符,访问修饰符用于控制方法或者属性的使用范围,三大修饰符各自具有不同的特性,可以修饰类、属性、方法。
static(静态)
在主函数中和学习方法的时候,方法上都添加了static作为修饰符,在学习本章之后,将知道static的作用是什么,什么时候添加static,什么时候不能添加。
静态修饰符可以修饰属性和方法,未来还可以修饰代码块。静态的含义也就是不加入任何一个对象,而是在内存中的一个独立的空间存储。静态修饰符所修饰的内容是写在类里的,但是无论类创建了多少对象,实例属性创建创建了多少个,静态的属性只会存在一个。
- 静态的属性和方法不属于任何一个对象,属于类。
- 静态的成员不能使用this和super,因为不属于任何一个对象。
- 静态的属性之所以多个对象操作的是同一个,是因为静态内容单独存在于一个独立的空间。
修饰的内容 | 修饰的效果 |
---|---|
属性 | 静态的属性可以通过类名直接调用,并且只会存在一个。 |
方法 | 静态的方法可以通过类名直接调用,静态方法中只能使用静态的成员。 |
静态的属性和方法还是会写在一个类中,但并不存在于类的对象中。
静态属性
使用static修饰的属性就叫做静态属性,静态属性可以通过常规创建对象的方式来调用,但更推荐使用类名来直接调用:
class Dog{
static int age;
}
public class Index{
public static void main(String[] args){
Dog d = new Dog();
d.age = 10;//可以通过引用调用方法
Dog.age = 10;//可以通过类名直接调用静态属性
}
}
因为静态属性只会存在一个,所以任何对象对静态属性的操作实际上都是在操作一个变量:
public class Index{
public static void main(String[] args){
Dog d = new Dog();
d.age = 1;
Dog.age = 2;
System.out.print(d.age);//2
}
}
静态属性将在类加载的时候初始化,也就是第一次创建类对象的时候就会初始化。
在学完静态属性之后,就已经学完了所有的变量类型:
- 局部变量:写在方法或者代码块中,只能在局部使用的方法。
- 实例变量:对象在内存中的属性就是实例变量,要通过引用来调用。实例变量在整个实例的任何地方都可以使用。
- 类变量:静态属性就是类变量,因为变量是声明在类中并且通过类名调用的。类变量不属于任何一个对象,并且类变量只会在内存中存在一份,无论此类创建了多少对象。
静态方法
像主函数一样,在public后面加上static,此方法就变成了静态方法。静态方法和静态属性类似,都是通过类名进行直接调用的,虽然可以通过引用调用,但并不推荐:
class Dog{
public static void run(){
System.out.print("hello");
}
}
public class Index{
public static void main(String[] args){
Dog.run();
}
}
在同一个类中,普通的方法可以直接调用普通方法和静态方法,静态方法只能调用静态方法。在类外普通方法要通过对象引用调用,静态方法要使用类名调用:
class Dog{
public static void run(){
System.out.print("hello");
}
public static void run2(){
System.out.print("hello");
run();
}
public void run3(){
System.out.print("hello");
run2();
}
public void run4(){
System.out.print("hello");
run3();
}
}
public class Index{
public static void main(String[] args){
Dog.run();
Dog.run2();
Dog d = new Dog();
d.run3();
d.run4();
}
}
静态方法中不能存在this或者super关键字,这两个关键字是用来指定对象的,但静态内容并不存在于任何一个对象中。并且静态也不能修饰构造函数。
静态方法在继承中是可以被继承的,并且是可以被重写的,但重写后调用的方法仍然是引用类型中的静态方法,而不是子类覆盖父类后的方法:
public class Static{
public static void main(String[] args){
Animal a = new JingBa();
a.eat();//在此处调用的eat方法:因为a的类型是Animal,所以调用的是Animal类中的eat方法!
}
}
class Animal{
public static void eat(){
System.out.print("in Animal");
}
}
class Dog extends Animal{
public static void eat(){
System.out.print("in Dog");
}
}
class JingBa extends Dog{
public static void eat(){
System.out.print("in JingBa");
}
}
final(终极)
终极表示不可篡改的,比如在学习标识符命名规范的时候所用到的常量,就是不可修改的量。final可以修饰属性、方法和类,所修饰的内容都是不可更改的。例如属性值不可更改,方法不可重写,类不可继承等。
修饰的内容 | 特点 |
---|---|
属性或者局部变量 | 称为“常量”,只能赋一次值,属性只能在构造或初始化时赋值,没有默认值。 |
方法 | 终极方法不可被重写,重写会发生编译时(已检查)错误。 |
类 | 终极类不可被继承,继承会发生编译时错误。 |
常量
final修饰属性或者局部变量,则会使此变量变成常量。常量命名需要全部大写,并且每个单词之间用下划线隔开。如果修饰属性,那属性必须要进行初始化或者在构造函数中赋值,当然只能存在一种,如果初始化值之后,也就不能用构造函数再次赋值了。这也就表示属性会在创建对象之初就构建了值,在未来使用的时候就不能再对对象中的值进行篡改。
- 常量一旦赋值无法改变(只读不能写)。
- final修饰的属性(实例变量)没有默认值。
- 对象中的终极属性只能赋予初始值或者在构造方法中赋值,两种赋值方式有且仅有其一。
- 如果类中存在多个构造方法,则每个构造方法都要对其进行赋值。
终极方法
方法的重写实际就是对方法的升级,或者叫做对方法的篡改。被final修饰的方法不能被重写,也就是说子类中不能出现方法名相同,返回值相同,参数表相同,访问修饰符相同或更宽的方法。
即使方法不能被重写,但也是可以被子类所继承来调用的。
终极类
类在被继承后,子类的属性和方法将自动的对父类进行重写,所以为了防备类内容被子类篡改,final所修饰的类不能存在子类,否则将在编译时报错。
abstract(抽象)
在书写实体类来描述一种事物的时候,父类极有可能并不能完整详细的描述某一个功能,但是每一个子类还都要具有这个功能才可以。例如动物都有呼吸的功能,但哺乳动物和两栖动物及水生动物的呼吸方式都不相同,那动物这一类就只能预留一个呼吸的方法,等待让每一个子类去实现自己的呼吸方式。
修饰的内容 | 特点 |
---|---|
方法 | 没有方法体,只有方法的声明(方法签名)。 |
类 | 可以存在非抽象的方法,但有抽象方法一定是抽象类。子类必须实现父类中所有的抽象方法,否则也要变成抽象类。 |
抽象类
在class单词前添加abstract则表示此类为一个抽象类,抽象类中通常存在抽象方法,但也可以不存在抽象方法只存在普通的方法。抽象类并不能创建对象,只能作为父类引用接受子类类型对象来进行多态使用。
- 抽象类是不能通过new关键字创建对象,但是可以作为引用类型。
- 抽象类也存在构造方法。
- 抽象类的子类必须重写父类中所有的抽象方法,否则也要变成抽象类。
- 抽象类中可以存在非抽象的方法。
abstract class Animal{
int age;
public abstract void huxi();
}
抽象方法
和final修饰和static修饰类似,抽象方法只需要在返回值类型前添加abstract就可以。抽象方法并没有实际的方法体,也就是说拥有抽象方法的类并不知道实际此方法的具体流程是什么样的,抽象方法存在的意义就是让所有的子类来继承并重写抽象方法。
抽象方法必定存在于抽象类中,抽象类可以再继承抽象类,最底层的抽象类将获得所有父类中所有的抽象方法,其子类要实现所有的抽象方法才能不变成抽象类,如果子类也无法完全实现所有抽象方法,则也要变成抽象类。
抽象类在继承的过程中可以不断的实现父类的抽象方法,并且声明自己的抽象方法。
- 抽象方法只有方法的声明,没有实现的代码(没有方法体、大括号)。主要用于“被覆盖”。
- 抽象方法必须位于抽象类中!
//以下示例为抽象类的作用及使用:抽象类主要用于对实体类的共性抽取过程中,对不同子类实例概念的抽象。
public class Index{
public static void main(String[] okk){
TaiDi d = new TaiDi();
d.kanMen();
}
}
abstract class Animal{
abstract public void eat();
abstract public void pai();
}
abstract class Dog extends Animal{
public void eat(){
System.out.print("吃骨头或者狗粮");
}
public void pai(){
System.out.print("到点吃到点排");
}
abstract public void kanMen();
}
class DeMu extends Dog{
public void kanMen(){
System.out.print("内有恶犬");
}
}
class TaiDi extends Dog{
public void kanMen(){
System.out.print("一脚十米");
}
}
也能看出,访问修饰符和三大修饰符并没有顺序,主要是共同修饰,则会同时控制方法或者属性。
类加载与代码块
类加载
静态的属性将在类加载时进行初始化,实际上当class文件加载到虚拟机的时候,就会发生类加载。
触发类加载有以下几种途径:
- 使用此类中的静态属性和方法。
- 创建本类的对象。
- 创建本类的子类的对象。
类在被加载到虚拟机之后,就不会再次触发类加载了,也就是说每个类在运行程序时最多只会被类加载一次。
代码块
构造代码块
与构造函数类似,构造代码块用于在使用类创建对象时候触发的代码块。当存在构造代码块的时候,先对属性开辟存储空间并赋默认值,然后调用构造代码块或者对属性进行初始值赋值,具体那一步先运行取决于谁写的位置更考上,再就是调用构造方法:
class Dog{
{//代码块:构造代码块:初始代码块
System.out.println("hello");
}
A a = new A();
public Dog(){
System.out.println("in Dog");
}
}
class A{
public A(){
System.out.println("in A");
}
}
静态代码块
给构造代码块加上静态修饰符就成了静态代码块,静态代码块只会在类加载后执行一次,然后给静态属性开辟空间赋默认值,然后执行静态代码块或者静态属性初始值赋值,这也是取决于谁写的更靠上。静态代码块中也只能使用静态的属性和方法,可以借助静态代码块在类加载时对静态属性进行初始化操作:
class Dog{
static{//静态初始代码块
System.out.println("hello");
}
static A a = new A();
public Dog(){
System.out.println("in Dog");
}
}
class A{
public A(){
System.out.println("in A");
}
}
以下将采用一套程序,测试在继承+静态情况下,程序执行的顺序:
class A{
static T t = new T("A"); //1
static{
System.out.println("static in A"); //2
}
public A(){
System.out.println("in A"); //5
}
}
class B extends A{
static T t = new T("B"); //3
static{
System.out.println("static in B"); //4
}
public B(){
System.out.println("in B"); //6
}
}
class T{
public T(String msg){
System.out.println("T as "+msg);
}
}
public class Chap{
public static void main(String[] args){
new B();
}
}
总结结果为:
-----第一次创建子类对象-----
初始化爷爷辈静态属性
执行爷爷辈静态代码块
初始化父亲辈静态属性
执行父亲辈静态代码块
初始化儿子辈静态属性
执行儿子辈静态代码块
初始化爷爷辈属性
执行爷爷辈构造代码块
执行爷爷辈构造方法
初始化父亲辈属性
执行父亲辈构造代码块
执行父亲辈构造方法
初始化儿子辈属性
执行儿子辈构造代码块
执行儿子辈构造方法
-----第二次创建子类对象-----
初始化爷爷辈属性
执行爷爷辈构造代码块
执行爷爷辈构造方法
初始化父亲辈属性
执行父亲辈构造代码块
执行父亲辈构造方法
初始化儿子辈属性
执行儿子辈构造代码块
执行儿子辈构造方法