目录
1、final(最终的)
1.1 final修饰变量
1.1.1 final修饰成员变量
1.1.2 final修饰局部变量
1.1.3 final修饰的基本类型变量和引用类型变量的区别
1.2 final方法
1.3 final类
2、abstract(抽象的)
2.1 抽象方法和抽象类
2.2 抽象类的作用
2.3 接口与抽象类
Java中各个关键字都有其不同的含义和用处,一下总结几个关键字用法以及示例:
final意思是最终的,可以用于修饰变量、方法和类。final修饰的变量、方法和类是不可该变的。
final可以用于修饰成员变量、局部变量以及形参(形参也属于局部变量),final修饰的变量一旦获得了初始值就不可以被改变。
注意:final修饰的变量是不可改变的,也就是说不能被重新赋值,不是不可以被赋值的。
成员变量是随类的初始化或对象初始而初始化的。当类初始化时,系统会为该类的类变量分配内存,并分配默认值;当创建对象时,系统会为改对象的实例变量分配内存,并分配默认值。如果即没有在定义成员变量是赋值,也没有在初始化块、构造器中为成员变量指定初始值,那么这些成员变量的值将一直是系统默认分配的 0、'\u0000'、false、或null,这些成员变量就失去了意义。所以,Java语法规定:final修饰的成员变量必须由程序员显示地指定初始值。
final修饰的成员变量(类变量和实例变量)能指定初始值的地方如下:
a) 类变量:必须在静态初始化块中指定初始值或在声明改类变量时就指定初始值,而且只能在两个地方的其中之一指定。
b) 实例变量:必须在非静态初始化块、声明改实例变量或构造器中指定初始值,而且只能在这三个地方的其中之一指定。
下面代码示范了成员变量的各种指定初始值的方法:
/**
* @CalssName: FinalVariableTest
* @Describe:
* @DATE: 2019/10/12 0012 10:16
* @Author:Hrzhi
*/
public class FinalVariableTest {
//定义实例变量时指定默认值,合法
final int a = 123;
//定义类变量时指定默认值,合法
final static String b = "love me, love my dog.";
final int c;
final String str;
// final char ch;
final static double d;
//普通初始化块可以对没有指定默认值的实例变量指定初始值
{
// a变量在定义的时候已经指定初始值,不能再重新赋值,非法
// a = 123;
// 普通初始化块,为实例变量赋值,合法
c = 456;
}
// 静态初始化块可以对没有指定初始值的类变量指定初始值
static {
// 在静态初始化块中为类变量指定初始值,合法
d = 1.2;
}
// 构造器中为没有指定初始值的实例变量指定初始值
public FinalVariableTest(){
// b变量已经指定初始值,不能重新赋值,非法
// b = "To choose time is to save time.";
// 在构造器中为尚未指定初始值的实例变量指定初始值,合法
str = "海上生明月,天涯共此时!";
}
// 普通方法
public void setFinalVariable(){
// 普通方法中,不能为final变量指定初始值,非法
// ch = 'T';
}
public static void main(String[] args) {
FinalVariableTest fvt = new FinalVariableTest();
System.out.println(fvt.a); // 123
System.out.println(FinalVariableTest.b); // love me, love my dog.
System.out.println(fvt.c); // 456
System.out.println(fvt.str); // 海上生明月,天涯共此时!
System.out.println(FinalVariableTest.d); // 1.2
}
}
系统不会对局部变量进行初始化,局部变量必须由程序员显式的初始化。因此使用final修饰局部变量时,既可以在定义时指定初始值,也可以不指定初始值。如果在定义时没有指定初始值,则可以在后面的代码中指定对该final变量赋初始值,但只能进行赋值一次,不能重复赋值;如果final修饰的变量在定义时已经赋值,则后面代码中不能再对该变量进行赋值。示例如下:
public class FinalLocalVariableTest {
public void test(final int a){
// 不能对final修饰的形参赋值,只能在调用方法的时候赋值,所以下面语句非法
// a = 10;
System.out.println("形参获得的值是:" + a);
}
public static void main(String[] args) {
// 定义并初始化final修饰的局部变量
final String str = "定义并初始化了final修饰的局部变量";
// 对final修饰的局部变量进行先定义后赋值,但是只能赋值一次,再次赋值则报错
final int a;
a = 5;
// final修饰的局部变量只能赋值一次,不可重复赋值
// a = 6;
new FinalLocalVariableTest().test(12);
}
}
我们已经知道final修饰的变量不能重复赋值,而且只能显式的指定初始值,因此,可见final修饰的基本类型的变量是不能被改变的,但是对于引用类型的变量而言,变量保存的是一个对象的引用,final只能保证这个引用类型变量所引用的地址不会改变,也就是一直引用同一个对象,但是对象本身是可以改变的。
class Student{
private String name;
private int age;
private String addr;
public Student(){};
public Student(String name, int age, String addr) {
this.name = name;
this.age = age;
this.addr = addr;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", addr='" + addr + '\'' +
'}';
}
}
public class FinalReferenceTest {
public static void main(String[] args) {
final Student student = new Student("张三", 24, "北京海淀");
System.out.println(student); // Student{name='张三', age=24, addr='北京海淀'}
// 不能对final修饰的变量(包括基本类型变量和引用类型变量)进行重复赋值
// student = new Student();
// 对final修饰的引用类型变量本身进行修改(即重新赋值)
student.setName("李四");
student.setAge(20);
student.setAddr("上海徐汇");
System.out.println(student); // Student{name='李四', age=20, addr='上海徐汇'}
}
}
final修饰的方法不能被重写,如果处于某些原因,不希望子类重写父类的某个方法,则可以使用final修饰该方法。例如Java提供的Object类里的getClass()方法,因为Java不希望任何类重写这个方法,所以使用final把这个方法密封起来。如果子类试图重写这个final方法,则会发生编译错误。
对于private修饰的方法,因为是仅当前类可见,子类无法访问该方法,所以子类无法重写该方法,如果子类定义一个与父类的private方法有相同的方法名、相同的形参列表、相同的返回值类型的方法,也不是方法的重写,只是在子类李重新定义了一个新方法。因此,即使是private final同时修饰一个方法,子类同样可以“重写”该方法,即可以定义同名、同参、同返回值的新方法,而不构成覆盖或重写。
final修饰的类不可以有子类。因为当子类继承父类时,将可以访问到父类内部的数据,并通过重写父类方法,来改变父类方法的实现细节,这可能导致一些不安全因素。为了保证某个类不可被继承,则可以使用final修饰这个类。如果试图继承一个final类,则会发生编译错误。
abstract修饰的类叫抽象类,abstract修饰的方法叫抽象方法。
抽象方法和抽象类必须使用abstract修饰符来修饰,有抽象方法的类只能被定义成抽象类,抽象类里可以没有抽象方法。抽象方法和抽象类的规则如下:
☛ 抽象类和抽象方法必须使用abstract来修饰,抽象方法不能有方法体。
☛ 抽象类你能被实例化,无法使用new关键字来调用抽象类的构造器来创建抽象类的对象,即使抽象类里不包含抽象方法,这个抽象类也不能被实例化。
☛ 抽象类可以包含成员变量、成员方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接口、枚举)5种成分。抽象类的构造器不能用于创建实例,主要用于被其子类调用。
☛ 含有抽象方法的类(包括直接定义一个抽象方法;或者继承一个抽象类,但是没有完全实现其父类的抽象方法;或实现一个接口,但没有完全实现接口包含的抽象方法三种情况),只能被定义成抽象类。
综上:抽象类可用“一得一失”来描述。“一得”是指:抽象类多了一个可以有抽象方法的能力;“一失”是指:失去了一个创建实例的能力。
示例如下:
public abstract class Person {
private String name;
private String job;
// 定义一个无参构造器
public Person() {
System.out.println("执行了抽象父类的无参构造器!");
}
// 定义一个有参构造器
public Person(String name, String job) {
this.name = name;
this.job = job;
System.out.println("执行了抽象父类的有参构造器!");
}
/**
* 定义一种模板,即父类定义方法执行的流程work(),
* 以及一个抽象的方法sayWork(),
* 而具体的实现细节交给子类实现
* 这种设计方法被称为模板设计方法
*/
public void work(){
System.out.println("我是" + getName() + ",我的工种是" + getJob() + "," + sayWork());
}
// 定义一个sayWork方法,用于不同工种的person介绍自己的工作
public abstract String sayWork();
/*
* 为私有变量提供getter和setter方法
*/
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
}
/**
* 教师类
*/
class Teacher extends Person {
@Override
public String sayWork() {
return "我的工作是教学生学习知识以及做人的道理!";
}
}
/**
* 医生类
*/
class Doctor extends Person{
public Doctor() {super();}
public Doctor(String name, String job) {
super(name, job);
}
@Override
public String sayWork() {
return "我的工作是为病人提供帮助,治病救人!";
}
}
class TestAbstract{
public static void main(String[] args) {
// 创建子类对象的时候,会首先调用父类的对应的构造器(无参构造器)
Person teacher = new Teacher();
teacher.setName("张教员");
teacher.setJob("教师");
teacher.work(); //我是张教员,我的工种是教师,我的工作是教学生学习知识以及做人的道理!
System.out.println();
// 创建子类对象的时候,会首先调用父类的对应的构造器(对应的有参构造器)
Person doctor = new Doctor("李医生","医生");
doctor.work(); //我是李医生,我的工种是医生,我的工作是为病人提供帮助,治病救人!
}
}
当abstract修饰类时,表明这个类只能被继承,当abstract修饰方法时,表明这个方法必须由子类提供实现(即重写)。而final修饰的类不能被继承,final修饰的方法不能被重写。因此final和abstract永远不能同时使用。
当static修饰一个方法时,表示这个方法属于该类本身,即通过类就可调用该方法,但如果该方法被定义成抽象方法,则将导致通过该类来调用该方法时出现错误(调用了一个没有方法体的方法肯定会引起错误)。因此static和abstract不能同时修饰某个方法,即没有类抽象方法。但是static和abstract并不是绝对互斥的,static和abstract虽然不能同时修改某个方法,但是它们可以同时修饰内部类。
abstract修饰的方法必须被继承才有意义,否则这个方法将永远没有方法体,因此abstract修饰的方法不能定义成private访问权限的,即private不能和abstract同时修饰方法。
抽象类不能创建实例,只能当成父类来被继承。抽象类是从多个具体类中抽象出来的父类,具有更高层次的抽象。从多个具有相同特征的类中出一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类设计的随意性。
抽象类体现的是一种模板设计模式,抽象类作为多个子类的通用模板,子类在抽象类的基础上继续扩展、改造,但子类总体上会保留抽象类的行为方式。
接口和抽象类很像,它们都具有如下特征:
☛ 接口和抽象类都不能被实例化,它们都位于继承树的顶端,用于被其他类实现和继承。
☛ 接口和抽象类都可以包含抽象方法,实现接口或者实现抽象类的普通子类都必须实现这些抽象方法。
但是,接口和抽象类之间的差别非常大,这种差别只要体现在二者的设计目的上。下面具体分析二者的差别。
接口作为系统与外界交互的窗口,接口体现的是一种规范。对于接口的实现者而言,接口规定了实现者必须向外提供哪些服务(以方法的形式来提供);对于接口调用者而言,接口规定了调用者可以调用哪些服务,以及如何调用这些服务(就是调用这些方法)。当在一个程序中使用接口时,接口是多个模块间的耦合标准;在多个程序间使用接口时,接口是多个程序之间的通信标准。
从某种程度上来看,接口类似于整个系统的“总纲”,它制定了系统中各模块应该遵循的标准,因此一个系统的接口不应该经常改变。一旦接口被改变,对整个系统甚至其他系统的影响将是辐射式的,导致系统中大部分类都需要重写。
抽象类则不一样,抽象类作为系统中多个子类的共同父类,它所体现的是一种模板设计模式。抽象类作为系统中多个子类的抽象父类,可以被当成系统实现过程中的中间产品,这个中间产品已经实现了系统的部分功能(那些已经提供实现的方法),但这个产品依然不能当成最终产品,必须有更进一步的完善,这种完善可能有几种不同的实现方式。
除此之外,接口和抽象类在用法上也存在如下差异:
☛ 接口里只能包含抽象方法和默认方法,不能为普通方法提供实现;抽象类里则完全可以包含普通方法。
☛ 接口里不能定义静态方法;抽象类里可以定义静态方法。
☛ 接口里只能定义静态常量,不能定义普通成员变量;抽象类里则既可以定义普通成员变量,也可以定义静态常量。
☛ 接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不能用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
☛ 接口里不能包含初始化块;但抽象类里可以包含初始化块。
☛ 一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补Java单继承的不足。