在 Java 中声明类、属性和方法时,可使用关键字 final 来修饰。
-
final 标记的类不能被继承;
-
final 标记的方法不能被子类复写;
-
final 标记的变量(成员变量或局部变量)即为常量,只能赋值一次。
final 关键字修饰类、成员变量和成员方法
1.final 修饰类;
final 用来修饰一个类,意味着该类成为不能被继承的最终类。出于安全性的原因和效率上的考虑,有时候需要防止一个类被继承。例如,Java 类库中的 String类,它对编译器和解释器的正常运行有着很重要的作用,不能轻易改变它,因此把它修饰为 final 类,使它不能被继承,这就保证了 String 类的惟一性。同时,如果你认为一个类的定义己经很完美,不需要再生成它的子类,这时也应把它修饰为 final 类。 定义一个 final 类的格式如下:
final class ClassName{
...
}
注意:声明为 final 的类隐含地声明了该类的所有方法为 final 方法。
2.final 修饰成员变量:
变量被声明为 final 后,成为常值变量(即常量),一旦被通过某种方式初始化或赋值,即不能再被修改。通常 static 与 final 一起使用来指定一个类常量。例如:
static final int SUNDAY=0;
final 变量一般用大写字母和下划线来表示,这是一种编码规定。
3.final 修饰成员方法:
用 final 修饰的方法为最终方法,不能再被子类重写,可以被重载。 尽管方法重写是 Java 非常有力的特征,但有时却需要避免这种情况的发生。为了不允许一个方法被重写,在方法的声明中指定 final 属性即可。例如:
class A{
final void method(){}
}
class B extends A{// 定义A类的一个子类B
void method(){}// 错误,method()不能被重写
}
该例中,因为 method() 方法在 A 中被声明为 final ,所以不能在子类 B 中被重写。如果这样做,将导致编译错误。 方法被声明为 final 有时可以提高性能:编译器可以自由地内联调用它们,因为它“知道”它们不会被子类重写。
4.final修饰数组:
当final
修饰引用类型的变量时,这个变量的引用(即它指向的对象或数组的内存地址)是不能被改变的。但是,这个引用所指向的对象或数组的内容本身是可以被修改的。final int[] a = {1, 2, 3};
声明了一个final
类型的数组,这意味着你不能将a
重新指向另一个数组,但是你可以修改a
数组中的元素值。
final int[] a = {1, 2, 3};
这行代码创建了一个final
的整型数组a
,并初始化为包含三个元素的数组。由于a
是final
的,你不能将a
指向另一个数组,比如a = new int[]{4, 5, 6};
这样的操作是不允许的。但是,你可以修改a
数组中的元素,如a[0] = 9;
,因为这只是改变了数组内部的值,而没有改变a
这个引用本身。
总结
final
修饰基本数据类型时,表示这个变量的值不可变。final
修饰引用类型时,表示这个引用的指向不可变,但引用指向的对象或数组的内容本身是可以变的。
static
是静态的意思,是一个修饰符,就像是一个形容词,是用来形容类,变量,方法的。
static
修饰变量,这个变量就变成了静态变量,修饰方法这个方法就成了静态方法,
static
关键字方便在没有创建对象的情况下来进行调用(方法/变量)。
static关键字的作用
static
关键字你可以理解为是一个形容词,一般是用来形容类、方法、变量,代码块,还有一个作用是用来静态导包,本关我们只讨论它的三个用法。
1.修饰变量 不使用static
关键字访问对象的属性:
使用static
关键字访问对象的属性:
注意:如果一个类的成员变量被static
修饰了,那么所有该类的对象都共享这个变量。无论这个类实例化多少对象,它的静态变量只有一份拷贝。 如:
2.修饰方法
用static
关键字修饰的方法叫做静态方法。静态方法我们已经用过,它有一个特点相信你已经很熟悉,那就是不需要创建对象就可以直接使用。 如:
注意:
- 静态方法不能使用类的非静态变量;
- 静态方法可以直接调用静态方法,但是调用普通方法只能通过对象的实例才能调用。
3.静态代码块
我们先来看一段静态代码块的运行效果:
上图中static{ }
就是一个静态代码块。
我们在main
方法中没有编写任何代码,可是运行的时候,程序还是会输出我被调用了
,由此我们可以发现静态代码块是不需要依赖main
方法就可以独立运行的。
关于静态代码块你只需要记住一句话:在类被加载的时候运行且只运行一次。
静态代码块中变量和方法的调用也遵守我们之前所说的规则,即只能直接调用静态的属性和方法。
静态代码块》构造代码块》构造方法
构造代码块类似于构造器,实例化对象的时候会调用构造代码块
package task07;
/*
* ˵Ã÷£º¸ù¾Ý TODO Ìáʾ£¬²¹³äÍêÕû¶ÔÓ¦¹¦ÄÜ´úÂë¡£
* £¨ÆäËû´úÂë¿ÉÒÔ²»Óøģ©
*/
public class Task07 {
static {
System.out.println("静态代码块");
}
{
System.out.println("构造代码块");
}
public Task07() {
System.out.println("构造方法");
}
void localBlock() {
{
System.out.println("局部代码块");
}
}
public static void main(String[] args) {
System.out.println("---------");
Task07 t=new Task07();
System.out.println("---------");
Task07 t2=new Task07();
System.out.println("---------");
t.localBlock();
System.out.println("---------");
t2.localBlock();
}
}
静态代码块
---------
构造代码块
构造方法
---------
构造代码块
构造方法
---------
局部代码块
---------
局部代码块
4.匿名对象:
匿名对象就是没有名字的对象。通常我们使用new
关键字声明一个对象实例,然后将对象赋给变量,代码如下:
Animal animal = new Animal();
而匿名对象就是声明后不赋给变量,匿名对象可以直接调用方法,也可以作为方法中的参数或返回值。代码如下:
// 直接使用
new Animal.eat();
// 参数
show(new Animal());
// 返回值
public static Animal find () {
return new Animal(); //匿名对象作为方法的fan 'huil
}
匿名对象优点
JVM
会把内存划分为不同的数据区域,包括方法区、堆、虚拟机栈、本地方法栈、程序计数器。其中方法区用于存储已被加载的类信息、常量、静态变量、编译后的代码等数据,堆内存区域用于存放对象实例,几乎所有的对象都是分配这部分的内存,而栈中的内存会用来保存对象的内存地址。因此,匿名对象可以节约内存空间,同时在使用完后,就可以立马被当作垃圾回收。
5.成员内部类:
内部类
为了表示出类和类之间的包含关系,将类A写在一个类B的成员位置或局部位置,类A就叫内部类,类B叫外部类。比如在描述电脑时,电脑中包含CPU,那么CPU就可以用内部类来描述。内部类分为成员内部类与局部内部类。
成员内部类
定义在一个类的成员变量位置的类叫成员内部类。内部类A可以使用任意访问修饰符,并且内部类A可以访问外部类B的方法,而不受修饰符的影响。相比较正常的类,内部类A不能通过new A()
的方法创建对象,而需要先创建外部类Bb = new B()
,然后再通过new b.new A()
的方法创建对象。
class 外部类 {
修饰符 Class 内部类 {
//其它代码
}
}
下面是一个例子,展示了成员内部类的使用,以及如何访问外部类的成员:
public class OuterClass {
private String outerField = "外部类的字段";
// 定义一个成员内部类
class InnerClass {
public void display() {
// 内部类访问外部类的私有字段
System.out.println(outerField);
// 调用外部类的方法
methodInOuter();
}
// 内部类可以定义自己的方法
private void internalMethod() {
System.out.println("这是内部类的方法");
}
}
// 外部类的方法
public void methodInOuter() {
System.out.println("这是外部类的方法");
}
// 一个方法来创建和使用内部类
public void useInnerClass() {
// 通过外部类的实例来创建内部类的实例
InnerClass inner = new InnerClass();
inner.display();
}
public static void main(String[] args) {
OuterClass outer = new OuterClass();
outer.useInnerClass(); // 输出:外部类的字段 和 这是外部类的方法
}
}
6.局部内部类:
局部内部类
定义在一个类的局部变量位置的类就是局部内部类,局部内部类可以在方法中访问。定义格式为:
class 外部类 {
修饰符 返回值类型 方法名(参数) {
class 内部类 {
}
}
}
局部内部类需要在外部方法中创建内部对象进行访问。局部内部类类似方法中的局部变量,类外的方法和类中不包含内部类的方法都不能访问这个内部类,但这并不代表局部内部类的实例和定义了它的方法中的局部变量具有相同的生命周期。并且由于局部类只能在方法内部使用,所以不存在外部可见性问题,因此内部类没有访问修饰符,局部类中也可以访问到外围类的成员变量。
如何理解加粗字:
-
类外的方法不能访问这个内部类:
这意味着,如果你有一个名为OuterClass
的类,它里面有一个方法outerMethod
,而outerMethod
内部定义了一个局部内部类LocalInnerClass
,那么OuterClass
的其他方法(即除了outerMethod
之外的方法)或者任何其他类(即OuterClass
外部的类)都不能直接访问LocalInnerClass
。这是因为LocalInnerClass
的作用域被限制在了outerMethod
的方法体内,对于外部世界来说,它是不可见的。 -
类中不包含内部类的方法不能访问这个内部类:
这进一步强调了局部内部类的作用域限制。即使在同一个OuterClass
中,如果某个方法(比如anotherMethod
)没有包含LocalInnerClass
的定义,那么这个方法也不能直接访问LocalInnerClass
。这是因为LocalInnerClass
只在outerMethod
的上下文中存在,对于OuterClass
的其他方法来说,它就像是一个完全不存在的类。
总结来说,这句话强调的是局部内部类的作用域限制,即它只能在定义它的方法内部被访问和使用,对于外部世界(包括同一个类的其他方法和外部类)来说,它是不可见的。
7.匿名内部类:
public abstract class Animal {
/****************** begin **************/
// 申明抽象方法eat()
public abstract void eat();
/****************** end ***************/
}
public class Main {
public static void main(String[] args) {
/*************** begin ******************/
//匿名内部类
//继承了Animal这个类并且创建该类的对象
Animal a=new Animal(){
public void eat(){
System.out.print("吃东西!");
}
};
//调用eat()方法
a.eat();
}
/**************** end ********************/
}
匿名内部类(Anonymous Inner Class)是Java中一种特殊的类,它允许你在需要类实例的地方直接定义并实例化一个类。这种方式非常适用于那些只需要使用一次类的场合,可以使得代码更加简洁。在提供的例子中,匿名内部类被用来创建一个Animal
类的实例,而Animal
类是一个抽象类,包含一个抽象的eat()
方法。
匿名内部类的使用方式
-
继承抽象类或实现接口:匿名内部类通常用于继承一个抽象类或者实现一个或多个接口。在这个例子中,它继承了
Animal
抽象类。 -
定义方法:在匿名内部类中,你需要实现或重写所有抽象方法(如果继承自抽象类)或接口中的方法。在这个例子中,
eat()
方法被重写以输出"吃东西! "。 -
实例化:通过new关键字后跟抽象类名(或接口名)和一对大括号来创建匿名内部类的实例。在大括号中,你可以定义类的成员变量、方法实现等。
-
new Animal() {...}
:这里创建了一个Animal
类的匿名子类实例。注意,因为Animal
是抽象类,所以不能直接实例化它;但通过匿名内部类,我们可以在不显式定义新类的情况下,提供eat()
方法的具体实现。 -
在大括号
{...}
内部,我们重写了eat()
方法,使其打印出"吃东西! "。 -
Animal animal = ...;
:这行代码将匿名内部类的实例赋值给了一个Animal
类型的变量animal
。由于匿名内部类继承了Animal
,因此这个赋值是合法的。 -
animal.eat();
:通过animal
变量调用eat()
方法,输出"吃东西! "。
在main方法中创建了一个匿名内部类,这个匿名内部类做了以下几件事情:
-
继承了
Animal
类:这里的new Animal() {...}
语法看起来像是在尝试实例化一个Animal
对象,但由于Animal
是一个抽象类,你不能直接实例化它。然而,这里的new Animal() {...}
实际上是在告诉编译器:“我要创建一个新的类,这个类继承自Animal
,并且我会在这个匿名类体中提供所有抽象方法的具体实现。” -
提供了
eat()
方法的具体实现:在匿名内部类的大括号{...}
中,你重写了从Animal
继承来的抽象方法eat()
,给出了它的具体实现。 -
创建了这个匿名内部类的一个实例:整个
new Animal() {...}
表达式的结果就是这个匿名内部类的一个实例,这个实例被赋值给了Animal
类型的变量animal
。