三个修饰符
一、abstract
1.1 什么是抽象
似是而非,像却又不是;具备某种对象的特征,但又不完整。
第一个图有的人说是旋涡,有的人说是龙卷风;第二个图有的人说是盘子,有的人说是一群鱼;第三个图有的人说是鲤鱼,有的人说不是。这种似是而非的,具备某种特征就是抽象。
1.2 生活中的抽象
在百度上面搜索动物的图片,搜索结果都是动物的子类对象,比如猫、鹿、斑马等等,而没有动物这个对象。所以说动物这个对象是不存在的,它是抽象的。
1.3 不该被创建的对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class TestAbstract { public static void main(String[] args) {
Animal a = new Animal(); } } class Animal{ String breed; int age; String sex;
public Animal() { } public void eat(){ System.out.println("吃..."); } public void sleep(){ System.out.println("睡..."); } }
|
Animal只是一种会吃会睡的对象,再无其他行为,现实中也无动物这个具体的对象,存在的是它的子类,狗、老虎、猫等,所以它不应该被独立创建成对象。
那如何限制这种对象的创建呢?
答:使用抽象类。
1.4 抽象类
使用abstract
修饰类,此类不能new对象。但是可以声明此类的变量。
Animal是抽象的,无法实例化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class TestAbstract { public static void main(String[] args) { Animal a; } } abstract class Animal{ String breed; int age; String sex;
public Animal() { } public void eat(){ System.out.println("吃..."); } public void sleep(){ System.out.println("睡..."); } }
|
被abstract
修饰的类,被称为抽象类。抽象类意为不够完整的类、不够具体的类,抽象类对象无法独立存在,即不能new对象。
1.5 抽象类的作用
- 可被子类继承,提供共性属性和方法
- 可声明为引用,更自然的使用多态。例,
Animal a;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| public class TestAbstract { public static void main(String[] args) { Animal a1 = new Dog(); Animal a2 = new Cat(); } }
abstract class Animal{ String breed; int age; String sex;
public Animal() { } public void eat(){ System.out.println("吃..."); } public void sleep(){ System.out.println("睡..."); } }
class Dog extends Animal{ @Override public void eat() { System.out.println("狗狗吃饭..."); } } class Cat extends Animal{ @Override public void eat() { System.out.println("猫猫吃饭..."); } }
|
1.6 不该被实现的方法
Dog中的eat应该输出“狗狗在吃饭”,Cat中的eat应该输出“猫猫在吃饭”,但是父类中有方法eat输出的是“吃”,子类都会重写这个eat方法,父类实现这个方法就是多余的,但是声明是必要的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| abstract class Animal{ String breed; int age; String sex;
public Animal() { } public void eat(){ System.out.println("吃..."); } public void sleep(){ System.out.println("睡..."); } }
class Dog extends Animal{ @Override public void eat() { System.out.println("狗狗吃饭..."); } } class Cat extends Animal{ @Override public void eat() { System.out.println("猫猫吃饭..."); } }
|
1.7 抽象方法
被abstract
修饰的方法,称为抽象方法,只有方法声明,没有方法实现,必须包含在抽象类中。
产生继承关系后,子类必须重写父类中所有的抽象方法,否则子类必须被定义为抽象类。这是因为抽象方法被继承了,而未被重写,由于抽象方法必须存在于抽象类中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| public class TestAbstract { public static void main(String[] args) { Animal a1 = new Dog(); Animal a2 = new Cat(); a1.eat(); a2.eat(); } }
abstract class Animal{ String breed; int age; String sex;
public Animal() { } public abstract void eat(); public void sleep(){ System.out.println("睡..."); } }
class Dog extends Animal{ @Override public void eat() { System.out.println("狗狗吃饭..."); } }
class Cat extends Animal{ @Override public void eat() { System.out.println("猫猫吃饭..."); } }
|
运行结果:
如果Dog
类中为重写eat方法,且Dog
类未定义为抽象类。
- abstract修饰类:不能new对象,但是可以声明引用
- abstract修饰方法:只用方法声明,没有方法实现(需包含在抽象类中)
- 抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类。
- 子类继承抽象类后,必须抽象父类中所有的抽象方法,否则子类还是抽象类。
二、static
什么是静态:
- 静态(static)可以修饰属性和方法
- 静态成员时全类所有对象共享的成员
- 在全类中只有一份,不因创建多个对象而产生多份
- 不必创建对象,也可通过类名直接访问
2.1 静态属性
静态属性是整个类共同持有的共享空间,任何对象对其修改,都会影响到其它对象。
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class TestStaticField { public static void main(String[] args) { MyClass c1 = new MyClass(); c1.a = 10; MyClass c2 = new MyClass(); c2.a = 20; System.out.println("c1.a="+c1.a+" c2.a="+c2.a); } }
class MyClass{ public static int a; }
|
运行结果:
c1.a
赋值为10,c2.a
赋值为20,最后c1.a
与c2.a
的值都变为了20。
案例1:统计一个类的对象被创建过多少次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| public class TestCase1 { public static void main(String[] args) { Student s1 = new Student(); Student s2 = new Student("小明"); Student s3 = new Student("小红",18); System.out.println("学生类被创建了"+Student.count+"次"); } } class Student{ private String name; private int age; public static int count=0; public Student(){ count++; } public Student(String name){ this(); this.name = name; } public Student(String name,int age){ this(name); this.age = age; } }
|
运行结果:
在每次创建中,都会执行到count++
,变量count
是静态属性,只有一份,所有对象共享这个属性。
2.2 静态方法
可直接通过类名.静态方法名
进行访问。
已知的静态方法有:Array.copyOf() Array.sort() Math.random() Math.sqrt() 等
,均可只用类名直接调用。
1 2 3 4 5 6 7 8 9 10
| public class TestStaticMethod { public static void main(String[] args) { MyClass1.testMedthod(); } } class MyClass1{ public static void testMedthod(){ System.out.println("我是静态方法"); } }
|
运行结果:
静态方法testMedthod
可直接通过类名进行访问调用。
静态方法的特点:
- 静态方法允许直接访问静态成员。
- 静态方法不能直接访问非静态成员。
- 静态方法中不允许使用
this
或super
关键字。
- 静态方法可以继承,不能重写、没有多态。
案例1:静态方法允许直接访问静态成员。(非静态方法也可以直接访问静态成员)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public class TestCase1 { public static void main(String[] args) { Student s1 = new Student(); Student s2 = new Student("小明"); Student s3 = new Student("小红",18); System.out.println("学生类被创建了"+Student.getCount()+"次"); } } class Student{ private String name; private int age; private static int count=0; public Student(){ count++; } public Student(String name){ this(); this.name = name; } public Student(String name,int age){ this(name); this.age = age; } public static int getCount(){ return count; } }
|
运行结果:
获取count
被定为私有属性,通过静态方法进行访问。
案例2:静态方法不能直接访问非静态成员。
1 2 3 4 5 6 7
| class Student{ private String name; private int age; public static void testName(String name){ this.name = name; } }
|
运行结果:
案例3:静态方法中不允许使用this或super关键字。
因为静态方法是可以通过类名进行调用,调用的时候对象有可能没有被创建,而this
关键字是本对象的引用,super
是父类的引用,所以都不可用。
案例4:静态方法可以继承,不能重写、没有多态。
TestDog.java
1 2 3 4 5 6 7 8
| public class TestDog { public static void main(String[] args) { Dog.testAnimal(); Dog.testAnimal1(); } }
|
Animal.java
1 2 3 4 5 6 7 8 9 10
| public class Animal { private int age; private String sex; public static void testAnimal(){ System.out.println("Animal的静态方法"); } public static void testAnimal1(){ System.out.println("Animal的静态方法1"); } }
|
Dog.java
1 2 3 4 5 6 7
| public class Dog extends Animal{ private String breed; public static void testAnimal1(){ System.out.println("Dog中的静态方法"); } }
|
运行结果:
2.3 动态代码块
创建对象时,触发动态代码块的执行。
执行地位:初始化属性之后、构造方法之前。
作用:可为实例属性赋值,或必要的初始行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class TestDynamicBlock { public static void main(String[] args) { MyClass c1 = new MyClass(); } } class MyClass{ String field="字段"; { System.out.println("动态代码块"); System.out.println(field); System.out.println(this.field); field = "动态代码块中给field赋值"; System.out.println(field); } public MyClass(){ System.out.println("**构造方法**"); } }
|
运行结果:
2.4 类加载
- JVM首次使用某个类时,需要通过
CLASSPATH
查找该类的.class
文件
- 将
.class
文件中对类的描述信息加载到内存中,进行保存,如包名、类名、父类、属性、方法、构造方法等..
- 加载时机:以下操作都会执行类加载
- 创建对象
- 创建子类对象
- 访问静态属性
- 调用静态方法
- 主动加载,
Class.forName("全限定名");
2.5 静态代码块
类加载时,触发静态代码块的执行(仅一次)。
执行地位:静态属性初始化之后。
作用:可为静态属性赋值,或必要的初始行为。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class TestStaticBlock { public static void main(String[] args) { MyClass c1 = new MyClass(); MyClass c2 = new MyClass(); } } class MyClass{ static String field = "字段"; String field1 = "非静态字段"; static { System.out.println("静态代码块"); System.out.println(field); field = "静态方法中为field赋值"; System.out.println(field); } }
|
运行结果:
只执行了一次
案例1:访问静态方法导致类加载从而执行静态代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class TestStaticBlock { public static void main(String[] args) { System.out.println("访问静态属性:"+MyClass.field); System.out.println("访问静态属性:"+MyClass.field); System.out.println("访问静态属性:"+MyClass.field); } } class MyClass{ static String field = "字段"; String field1 = "非静态字段"; static { System.out.println("静态代码块"); System.out.println(field); field = "静态方法中为field赋值"; System.out.println(field); } }
|
运行结果:
访问多次仅执行一次静态代码块
案例2:调用静态方法导致类加载从而执行静态代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class TestStaticBlock { public static void main(String[] args) { MyClass.testMethod(); MyClass.testMethod(); MyClass.testMethod(); } } class MyClass{ static String field = "字段"; String field1 = "非静态字段"; static { System.out.println("静态代码块"); System.out.println(field); field = "静态方法中为field赋值"; System.out.println(field); } public static void testMethod(){
} }
|
运行结果:
只会执行一次。
案例3:主动加载类导致类加载从而执行静态代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class TestStaticBlock { public static void main(String[] args) throws ClassNotFoundException { Class.forName("demo9.MyClass"); Class.forName("demo9.MyClass"); Class.forName("demo9.MyClass"); } } class MyClass{ static String field = "字段"; String field1 = "非静态字段"; static { System.out.println("静态代码块"); System.out.println(field); field = "静态方法中为field赋值"; System.out.println(field); } }
|
运行结果:
2.6 对象的创建过程
静态属性初始化 --> 静态代码块 --> 实例属性初始化 --> 动态代码块 --> 构造方法
静态的属性以及代码块仅初始化执行一次。
TestCreateObject.java
1 2 3 4 5 6 7
| public class TestCreateObject { public static void main(String[] args) { new Super(); System.out.println("**********"); new Super(); } }
|
Super.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Super { static String superField = "父类静态属性"; static { System.out.println("父类静态代码块"); System.out.println(superField); }
String superField1 = "父类属性"; { System.out.println("父类动态代码块"); System.out.println(superField1); }
public Super(){ System.out.println("父类构造方法"); } }
|
运行结果:
2.7 带有继承的对象创建的过程
父类静态属性初始化 --> 父类静态代码块 --> 子类静态属性初始化 --> 子类静态代码块--> 父类实例属性初始化 --> 父类动态代码块 --> 父类构造方法 --> 子类实例属性初始化 --> 子类动态代码块 --> 子类构造方法
静态的属性以及代码块仅初始化执行一次。
TestCreateObject.java
1 2 3 4 5 6 7
| public class TestCreateObject { public static void main(String[] args) { new Sub(); System.out.println("**********"); new Sub(); } }
|
Super.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Super { static String superField = "父类静态属性"; static { System.out.println("父类静态代码块"); System.out.println(superField); }
String superField1 = "父类属性"; { System.out.println("父类动态代码块"); System.out.println(superField1); }
public Super(){ System.out.println("父类构造方法"); } }
|
Sub.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class Sub extends Super{ static String subField = "子类静态属性"; static { System.out.println("子类静态代码块"); System.out.println(subField); }
String subField1 = "子类实例属性"; { System.out.println("子类动态代码块"); System.out.println(subField1); }
public Sub(){ System.out.println("子类构造方法"); } }
|
运行结果:
2.8 总结
- static修饰的成员为静态成员,无需创建对象,可直接通过类名访问。
- static修饰的成员,虽然可通过对象进行访问,但是不推荐,这个就好比脱裤子放屁,还得创建一个对象。
- 静态方法不能直接访问非静态成员。
- 静态方法中不能使用this和super。
- 静态方法可以被继承,但不能重写,没有多态。
- 静态代码块在类加载时被执行,且只执行一次。
三、final
3.1 什么是最终
- 概念:最后的,不可更改的
- final可修饰的内容
- 类 –> 最终类
- 方法 –> 最终方法
- 变量 –> 最终变量
3.2 final修饰类
被final
修饰的类不能被继承。
String Math System
均为final
修饰的类,不能被继承。
1 2 3 4 5 6 7
| public final class Student { String name; }
class MyClass extends Student{
}
|
运行结果:
报错,无法被继承。
3.3 final修饰类中方法
此方法不能被子类重写。可以被继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class Animal { public final void sleep(){ System.out.println("睡觉..."); } }
class Dog extends Animal{ @Override public final void sleep() { super.sleep(); }
public static void main(String[] args) { Dog dog1 = new Dog(); dog1.sleep(); } }
|
运行结果:
3.4 final修饰变量
1. 修饰局部变量
此变量不能被更改(常量)
1 2 3 4 5 6
| public class TestFinal { public static void main(String[] args) { final int num = 10; num = 20; } }
|
运行结果:
2. 修饰类属性
被修饰的类属性称为实例常量,不能被更改。
实例常量不再提供默认值,必须手动赋予初始值。否则报错:变量 name 未在默认构造器中初始化
赋值时机:显示初始化 动态代码块 构造方法
注意:如果在构造方法里进行初始化,必须保证所有的构造方法都能对其正确赋值,且不能重复赋值。
案例1:显示初始化
1 2 3
| public class Student { final String name = "小明"; }
|
案例2:在动态代码块中初始化
1 2 3 4 5 6
| public class Student { final String name; { name = "小明"; } }
|
如果已经初始化了,重复初始化将会报错
1 2 3 4 5 6
| public class Student { final String name = "小明"; { name = "小明"; } }
|
运行结果:
案例3:在构造方法中初始化
1 2 3 4 5 6
| public class Student { final String name; public Student() { name = "小明"; } }
|
使用构造方法进行初始化,如果存在多个构造方法,需要保证每个构造方法都正确的对实例常量进行初始化
1 2 3 4 5 6 7 8 9 10 11 12
| public class Student { final String name; public Student() { name = "小明"; } public Student(String s){
} public Student(int a){ } }
|
运行结果:
修改后:
1 2 3 4 5 6 7 8 9 10 11 12
| public class Student { final String name; public Student() { name = "小明"; } public Student(String s){ this(); } public Student(int a,String s){ name = s; } }
|
3. 静态常量(static+final)
静态常量不再提供默认值,与实例常量一样需要手动赋予初始值,二种初始化方式:显示初始化 静态代码块
。
可通过类名直接访问。
静态常量一般使用大写字母命名。
案例1:显示初始化
1 2 3 4 5 6 7 8
| public class TestFinal { public static void main(String[] args) { System.out.println(Student.SCHOOL_NAME); } } class Student{ static final String SCHOOL_NAME = "第一中学"; }
|
运行结果:
案例2:静态代码块初始化
1 2 3 4 5 6 7 8 9 10 11
| public class TestFinal { public static void main(String[] args) { System.out.println(Student.SCHOOL_NAME); } } class Student{ static final String SCHOOL_NAME; static { SCHOOL_NAME = "第一中学"; } }
|
运行结果:
不能重复初始化,将报错。
1 2 3 4 5 6
| class Student{ static final String SCHOOL_NAME = "第一中学"; static { SCHOOL_NAME = "第一中学"; } }
|
运行结果:
4. 修饰引用类型变量
对象常量,被final
修饰的引用类型变量,其引用地址不能被改变,引用指向的对象值可被改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class TestFinal { public static void main(String[] args) { final Student s1 = new Student(); s1.name = "小明"; s1.name = "小红"; s1.setAge(18); s1.setAge(19); s1.show(); } } class Student{ String name; private int age; public void setAge(int age){ this.age = age; } public int getAge(){ return this.age; } public void show(){ System.out.println("name:"+name+" age:"+age); } }
|
运行结果:
如果引用被改变将报错。
1 2 3 4 5 6
| public class TestFinal { public static void main(String[] args) { final Student s1 = new Student(); s1 = new Student(); } }
|
运行结果:
3.5 总结
- final修饰类:此类不能被继承
- final修饰方法:此方法不能被覆盖
- final修饰变量:此变量不能被更改(无默认初始值,需要手动初始化,只允许赋值一次)
- 修饰局部变量:显示初始化
- 修饰实例常量:显示初始化、动态代码块、构造方法
- 修饰静态常量:显示初始化、静态代码块
- 基本类型常量:值不能变
- 引用类型变量:值(引用)不可变,引用对象的值可变