final关键字
final 关键字,用于修饰不可改变内容。
final: 不可改变。可以用于修饰类、方法和成员变量和局部变量。
类:被修饰的类,不能被继承。
方法:被修饰的方法,不能被重写。
成员变量或局部变量:被修饰的变量,不能被重新赋值。
修饰类
final class 类名 {
}
查询API发现像 public final class String 、 public final class Math 、 public final class Scanner
等,很多我们学习过的类,都是被final修饰的,目的就是供我们使用,而不让我们所以改变其内容。
/*
当final关键字用来修饰一个类的时候,格式:
public final class 类名称 {
// ...
}
含义:当前这个类不能有任何的子类。(太监类)
注意:一个类如果是final的,那么其中所有的成员方法都无法进行覆盖重写(因为没儿子。)
*/
public final class MyClass /*extends Object*/ {
public void method() {
System.out.println("方法执行!");
}
}
// 不能使用一个final类来作为父类
public class MySubClass /*extends MyClass*/ {
}
修饰成员方法
修饰符 final 返回值类型 方法名(参数列表){
//方法体
}
重写被 final 修饰的方法,编译时就会报错。
/*
当final关键字用来修饰一个方法的时候,这个方法就是最终方法,也就是不能被覆盖重写。
格式:
修饰符 final 返回值类型 方法名称(参数列表) {
// 方法体
}
注意事项:
对于类、方法来说,abstract关键字和final关键字不能同时使用,因为矛盾。
*/
public abstract class Fu {
public final void method() {
System.out.println("父类方法执行!");
}
public abstract /*final*/ void methodAbs() ;
}
public class Zi extends Fu {
@Override
public void methodAbs() {
}
// 错误写法!不能覆盖重写父类当中final的方法
// @Override
// public void method() {
// System.out.println("子类覆盖重写父类的方法!");
// }
}
修饰成员变量
成员变量涉及到初始化的问题,初始化方式有两种,只能二选一:
显示初始化
public class User {
final String USERNAME = "张三";
private int age;
}
构造方法初始化
public class User {
final String USERNAME ;
private int age;
public User(String username, int age) {
this.USERNAME = username;
this.age = age;
}
}
被final修饰的常量名称,一般都有书写规范,所有字母都大写。
/*
对于成员变量来说,如果使用final关键字修饰,那么这个变量也照样是不可变。
1. 由于成员变量具有默认值,所以用了final之后必须手动赋值,不会再给默认值了。
2. 对于final的成员变量,要么使用直接赋值,要么通过构造方法赋值。二者选其一。
3. 必须保证类当中所有重载的构造方法,都最终会对final的成员变量进行赋值。
*/
public class Person {
private final String name/* = "鹿晗"*/;
public Person() {
name = "关晓彤";
}
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
// public void setName(String name) {
// this.name = name;
// }
}
对于final的成员变量,如果同时也使用static关键字,那么就必须在定义的时候进行初始化,不能够放到构造方法中进行初始化了。
由于现在定义为静态的常量(成员变量),初始化放在构造函数中是不行的,对于静态的常量(成员变量),它也是只属于某个类,并不属于某个具体的对象。而当使用类名.静态常量名,这样调用的时候,不会导致构造函数的调用,因此,将静态常量这个成员变量放到构造方法中初始化,结果就是没有初始化。所以不行。
因此,当一个常量(成员变量)定义为static的时候,就要在声明的同时对其进行初始化。
修饰局部变量
局部变量——基本类型
基本类型的局部变量,被final修饰后,只能赋值一次,不能再更改。
public class FinalDemo1 {
public static void main(String[] args) {
// 声明变量,使用final修饰
final int a;
// 第一次赋值
a = 10;
// 第二次赋值
a = 20; // 报错,不可重新赋值
// 声明变量,直接赋值,使用final修饰
final int b = 10;
// 第二次赋值
b = 20; // 报错,不可重新赋值
}
}
如下两种写法,哪种可以通过编译
写法1:
final int c = 0;
for (int i = 0; i < 10; i++) {
c = i;
System.out.println(c);
}
编译错误!!
写法2:
for (int i = 0; i < 10; i++) {
final int c = i;
System.out.println(c);
}
编译通过!!
根据 final 的定义,写法1报错!写法2,为什么通过编译呢?因为每次循环,都是一次新的变量c。
局部变量——引用类型
引用类型的局部变量,被final修饰后,只能指向一个对象,地址不能再更改。但是不影响对象内部的成员变量值的修改。
public class FinalDemo2 {
public static void main(String[] args) {
// 创建 User 对象
final User u = new User();
// 创建 另一个 User对象
u = new User(); // 报错,指向了新的对象,地址值改变。
// 调用setName方法
u.setName("张三"); // 可以修改
}
}
public class Student {
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/*
final关键字代表最终、不可改变的。
常见四种用法:
1. 可以用来修饰一个类
2. 可以用来修饰一个方法
3. 还可以用来修饰一个局部变量
4. 还可以用来修饰一个成员变量
*/
public class Demo01Final {
public static void main(String[] args) {
int num1 = 10;
System.out.println(num1); // 10
num1 = 20;
System.out.println(num1); // 20
// 一旦使用final用来修饰局部变量,那么这个变量就不能进行更改。
// “一次赋值,终生不变”
final int num2 = 200;
System.out.println(num2); // 200
// num2 = 250; // 错误写法!不能改变!
// num2 = 200; // 错误写法!
// 正确写法!只要保证有唯一一次赋值即可
final int num3;
num3 = 30;
// 对于基本类型来说,不可变说的是变量当中的数据不可改变
// 对于引用类型来说,不可变说的是变量当中的地址值不可改变
Student stu1 = new Student("赵丽颖");
System.out.println(stu1);
System.out.println(stu1.getName()); // 赵丽颖
stu1 = new Student("霍建华");
System.out.println(stu1);
System.out.println(stu1.getName()); // 霍建华
System.out.println("===============");
final Student stu2 = new Student("高圆圆");
// 错误写法!final的引用类型变量,其中的地址不可改变
// stu2 = new Student("赵又廷");
System.out.println(stu2.getName()); // 高圆圆
stu2.setName("高圆圆圆圆圆圆");
System.out.println(stu2.getName()); // 高圆圆圆圆圆圆
}
}
类的修饰符
类的访问修饰符:
public
default:一个缺省的类。要想这个类在包外边被访问,那么需要声明为public。公有的类。缺省(default)的类只能在包内部被访问,或者说在同一个包中被访问。或者说,缺省的类只能在包内使用。
类的其它修饰符:
final:当在一个类前面加上final,表明这个类是一个最终的类。不能由这个类派生其它的子类。
一般标准的类,不想被修改的,那么声明为final的。
java类库中,String类就是一个final的类。
abstract:抽象类,含有抽象方法的类就是抽象类。如果一个子类没有实现抽象基类中的所有的抽象方法,那么这个子类也是一个抽象类。我们可以将一个没有任何抽象方法的类声明为abstract,从而避免由这个类产生任何的对象。抽象类不能够实例化的。
java中就提供了这样的类,里面的所有方法都是实现了的,但是都是空实现,然后将这个类定义为抽象类,然后我们要去继承这个类,然后至少重写其中的一个方法,然后用这个类去实例化对象,这样实例化出的对象才有意义。
抽象类通常是将共性的东西抽象出来,例如,Animal类,其中的sleep方法定义为抽象方法,然后Animal自然就是抽象类,然后子类中去实现这个sleep方法,站着睡觉的就站着,躺着睡觉的就躺着实现的。
方法的访问权限修饰符
在Java中提供了四种访问权限,使用不同的访问权限修饰符修饰时,被修饰的内容会有不同的访问权限,
public:公共的。
protected:受保护的
default:默认的
private:私有的
public,任意地方都可以。
protected,有继承关系的两个类,子类中方法可以访问父类的protected方法,即使两个类不在同一个包中。
default,在同一个包中被访问。
private,只能在同一个类中被访问。注意,私有方法是不能够被继承的。
可见,public具有最大权限。private则是最小权限。
编写代码时,如果没有特殊的考虑,建议这样使用权限:
成员变量使用 private ,隐藏细节。
构造方法使用 public ,方便创建对象。
成员方法使用 public ,方便调用方法。
提示:不加权限修饰符,其访问能力与default修饰符相同
Java中有四种权限修饰符:
注意事项:(default)并不是关键字“default”,而是根本不写。
方法的其它修饰符
方法的其它修饰符:
static,静态方法,前面已讲
final,在定义某个类的时候,如果不希望其中的某个方法在这个类被继承的时候被覆盖,那么可以将这个方法定义为final。private和static方法自然就是final。private方法和static方法,都是不能够被继承的。因为final方法是不能被改写的方法,因此,编译器会对final方法进行相应的优化。如果final的方法代码量很小,那么编译器就会优化。
abstract,在类中没有方法体的方法,就是抽象方法。
native,native方法在java中可以使用,但是用户不能够去编写的方法。
JNI(java native interface),它允许JVM内部运行的java代码能够与其它编程语言(例如,c、c++、汇编)编写的应用程序或者库进行互操作。JNI最大的好处就是它没有对底层JVM的实现加以限制,因此,JVM厂商可以在不影响虚拟机其它部分的情况下添加对JNI的支持,程序员只需要编写一种版本的本地(native)应用程序或者库,就能够与所有支持JNI的JVM协同工作。JNI可以理解为Java和本地应用程序之间的中介。
如果想访问os的特有功能,想要和特殊的硬件设备进行沟通,那么就使用jni技术,通过jni接口去调用其它的语言,然后利用其它的语言去访问os或者硬件设备的特有功能。
如果想重复使用过去编写的非java代码,那么也可以使用jni技术。
如果想实现实时性很强的,那么可以用其它的语言实现,然后利用jni在java中去调用这种实现。
synchronized,多线程并发同步修饰符。
内部类
内部类:将一个类A定义在另一个类B里面,里面的那个类A就称为内部类,B则称为外部类。
成员内部类
成员内部类:定义在类中方法外的类。
class 外部类 {
class 内部类{
}
}
class Car { //外部类
class Engine { //内部类
}
}
访问特点
内部类可以直接访问外部类的成员,包括私有成员。在内部类中,可以随意的访问外部类中的成员方法和成员变量。
外部类要访问内部类的成员,必须要建立内部类的对象。
创建内部类对象格式:
外部类名.内部类名 对象名 = new 外部类型().new 内部类型();
public class Person {
private boolean live = true;
class Heart {
public void jump() {
// 直接访问外部类成员
if (live) {
System.out.println("心脏在跳动");
} else {
System.out.println("心脏不跳了");
}
}
}
public boolean isLive() {
return live;
}
public void setLive(boolean live) {
this.live = live;
}
}
public class InnerDemo {
public static void main(String[] args) {
// 创建外部类对象
Person p = new Person();
// 创建内部类对象
Heart heart = p.new Heart();
// 调用内部类方法
heart.jump();
// 调用外部类方法
p.setLive(false);
// 调用内部类方法
heart.jump();
}
}
输出结果:
心脏在跳动
心脏不跳了
内部类仍然是一个独立的类,在编译之后会内部类会被编译成独立的.class文件,但是前面冠以外部类的类名和$符号 。例如:Person$Heart.class
class Outer{
private int index = 100;
class Inner{
void print(){
System.out.println(index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
}
Inner类就是一个内部类,Outer类就是Inner类的一个外部类。
虽然Inner这个类是在Outer类的内部定义的,但是它仍然是一个独立的个体,它和将这个Inner类放到Outer类的外部去定义是一样的,当产生一个Inner类对象和Outer类对象,这两个对象,它们分属于不同的内存空间,它们都有自己的成员变量和成员方法的。因此,在内部类中定义的成员变量和成员方法与在外部类中定义的同名的成员变量与成员方法,是不会有冲突的。内部类仍然是一个独立的类。
class Outer{
private int index = 100;
class Inner{
void print(){
System.out.println(index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
}
class Test{
public static void main(String[] args){
Outer outer = new Outer();
outer.print();
}
}
outer.print()的时候,就会调用内部类来产生一个内部类对象。
但是我们注意到内部类中打印的index是在外部类中定义的,且是private的,如果Inner这个类在Outer类外部定义,那么是无法访问Outer的private的变量的。
内部类产生的class文件的名字:Outer$Inner.class。
内部类中可以访问外部类中private的成员变量。
既然private可以访问,那么public,protected,default,都是可以在内部类中被访问的。
也就是说,在内部类中,可以随意的访问外部类中的成员方法和成员变量。
当o.print(),调用外部类中的print方法的时候,会有一个this变量指向当前对象,也就是外部类对象的。
注意,在内部类对象中,还有一个Outer.this,它保存了外部类对象的一个引用。
在内部类中,能够访问到外部类中所有的成员,就是通过Outer.this来访问的。有了Outer.this,那么就可以随意的访问外部类中的成员了。
class Outer{
private int index = 100;
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
}
class Test{
public static void main(String[] args){
Outer outer = new Outer();
outer.print();
}
}
结果:
30
50
100
现在要在main中直接使用内部类
class Outer{
private int index = 100;
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
}
class Test{
public static void main(String[] args){
Outer outer = new Outer();
Inner inner = Outer.getInner();//报错。
inner.print();
}
}
由于Inner是在Outer类内部定义的,因此,在main中是看不到这个Inner的。因此,需要这样写,Outer.Inner
class Outer{
private int index = 100;
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
}
class Test{
public static void main(String[] args){
Outer outer = new Outer();
Outer.Inner inner = Outer.getInner();//正确
inner.print();
}
}
结果:
30
50
100
class Outer{
private int index = 100;
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
public static void main(String[] args){
Outer outer = new Outer();
//如果将main方法放在Outer类内部,那么就不需要Outer.Inner这样写了,直接Inner即可。
Inner inner = outer.getInner();
inner.print();
}
}
class Outer{
private int index = 100;
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
public static void main(String[] args){
Outer outer = new Outer();
Inner inner = new Inner();//报错
inner.print();
}
}
编译报错,提示静态不能够访问非静态成员。
因为main是一个static的,我们可以将Inner看成是Outer的一个成员,非静态的成员,因此,就不能够在静态的main中被访问了。
从另一角度来说,Inner这个对象也是不能直接用new来产生的。之前是通过getInner中的new Inner()间接的来获取内部类对象,这个是可以的。
class Outer{
private int index = 100;
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
}
class Test{
public static void main(String[] args){
Outer outer = new Outer();
Outer.Inner inner = new Inner();//报错
}
}
编译错误。
这里直接用new Inner是不行的,因为,当使用new Inner的时候,它需要有一个引用指向外部类的一个对象,因为在Inner类中,它可以随意的访问外部类中的数据成员,那么如果我们还没有产生外部类Outer的对象,那么它的数据成员也就不存在,而对于内部类Inner,如果我们能直接new产生一个对象,然后它去访问外部类对象的成员变量,而这个成员变量又不存在,那么应该如何去访问呢?
因此,如果想在内部类中访问外部类中的成员,那么首先要先产生外部类对象才行。
因此,当我们想直接new一个内部类对象,那么这个new要有一个引用去指向外部类对象。因此,应该outer.new Inner()这样写。也就是说要先有一个外部类对象,然后才能够创建内部类对象。
class Outer{
private int index = 100;
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
}
class Test{
public static void main(String[] args){
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();//正确
}
}
因此,在main中如果想直接new一个内部类对象,那么它肯定是和一个外部类对象相关联的,因为内部类对象中访问的外部类对象的数据成员,对于每个外部类对象来说,都有一份拷贝,那么内部类对象访问的外部类的index,到底是哪个外部类对象的index呢?因此,就需要将内部类对象跟具体的外部累对象相关联,outer.new Inner()
方法内部类
内部类可以将其定义在方法内部。
class Outer{
private int index = 100;
void fn(){
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
}
void print(){
Inner inner = new Inner();//报错
inner.print();
}
Inner getInner(){
return new Inner();//报错
}
}
当将一个类放到一个方法内定义的时候,那么也就限定了这个类只能在这个方法内来使用。一旦超出这个方法的范围,其它地方就看不到这个类了。使用范围只能限定在这个方法的内部了。
class Outer{
private int index = 100;
void fn(){
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
}
}
class Test{
public static void main(String[] args){
Outer outer = new Outer();
}
}
内部类还可以定义在语句块中。
class Outer{
private int index = 100;
void fn(){
if(true){
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
}
}
}
class Outer{
private int index = 100;
void fn(){
if(true){
{
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
}
}
}
}
我们注意,不管内部类嵌套的有多深,它都可以访问外部类的成员变量,Outer.this.index。
因为内部类对象具有一个指向外部类对象的引用。
class Outer{
private int index = 100;
void fn(){
if(true){
{
class Middle{
private int index = 60;
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Middle.this.index);//注意这里
System.out.println(Outer.this.index);
}
}
}
}
}
}
}
class Outer{
private int index = 100;
void fn(int a){
int b;
if(true){
{
class Middle{
private int index = 60;
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Middle.this.index);//注意这里
System.out.println(Outer.this.index);
a = 5;//报错
b = 6;//报错
}
}
}
}
}
}
}
报错提示:可以看到,说本地变量(局部变量)不能在内部类中被访问,需要声明为final。
class Outer{
private int index = 100;
void fn(final int a){
final int b = 0;
if(true){
{
class Middle{
private int index = 60;
class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Middle.this.index);//注意这里
System.out.println(Outer.this.index);
//a = 5;//报错
//b = 6;//报错
System.out.println(a);
System.out.println(b);
}
}
}
}
}
}
}
既然a,b被声明为final,那么就不能对其进行修改了。另外,由于b是final,因此,需要进行初始化的。
此时编译通过。
因此,当在方法中定义一个类,然后在这个类中去访问方法的参数或者方法中定义的局部变量,那么必须将这个参数或者局部变量声明为final,然后才能访问。而如果方法参数或者局部变量不会被内部定义的类访问,那么可以不用声明为final。
注意,fn中的a是final的,那么调用方法的时候,肯定会传入值的,因此,此时就初始化了。
成员内部类使用访问修饰符
对于一个正常的类,它的访问权限,只有public或者缺省的。
对于一个内部类,我们可以将其声明为public,protected或者private。或者缺省的。也就是说,对于内部类,它类似类中的方法。可以是public,protected,缺省,private的。
//protected
class Outer{
private int index = 100;
protected class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
}
//Inner对于在同一个类中,肯定可以访问。
//Inner对于在同一个包中的其它类,例如,Test,也是可以访问的。
class Test{
public static void main(String[] args){
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();//正确
}
}
//private
class Outer{
private int index = 100;
private class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
}
class Test{
public static void main(String[] args){
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();//报错,private只能在类内部使用。
}
}
//abstract
//对于内部类来说,也可以声明为abstract。这个时候,就不能直接用内部类来实例化一个对象了。
class Outer{
private int index = 100;
abstract class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
}
//我们可以在Outer中再定义一个内部类,它继承Inner。然后再实例化这个类。
//我们也可以在Outer外部,从Inner内部类进行继承。然后利用这个派生类产生对象。
//final
//表示Inner,就不能被继承了。
class Outer{
private int index = 100;
final class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
}
//static
//内部类还可以声明为static的。
class Outer{
private int index = 100;
static class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.this.index);//报错
}
}
void print(){
Inner inner = new Inner();
inner.print();
}
Inner getInner(){
return new Inner();
}
}
静态内部类,那么在产生一个内部类对象的时候,就不需要同时再存在一个外部类的对象了。
那么在静态内部类中也就不能再访问外部类中定义的非静态的成员变量了和非静态的成员方法。
在静态内部类中只能访问外部类中的静态成员变量或者静态成员方法。当外部类中成员变量或者成员方法声明为静态的时候,静态内部类中访问就不是用this了。而是直接用类名去引用了。
class Outer{
private int index = 100;
static class Inner{
private int index = 50;
void print(){
int index = 30;
System.out.println(index);
System.out.println(this.index);
System.out.println(Outer.index);//正确
}
}
}
当将一个内部类声明为静态内部类了,相当于切断了它和外部类对象之间的关系,当产生一个内部类对象的时候,就不再需要同时存在一个外部类对象了。但是,这个时候,对于静态内部类来说,它只能访问外部类中定义的静态变量,静态方法。
对于一个非静态的内部类,是不能够在其内部定义一个静态的方法的。
class Outer{
private int index = 100;
class Inner{
private int index = 50;
static void print(){//编译报错
int index = 30;
System.out.println(index);
System.out.println(this.index);
//System.out.println(Outer.index);//正确
}
}
}
非静态内部类中不能有静态方法或者变量。另外一个错误是静态方法中访问了非静态的变量。
在静态的内部类中,可以有静态的成员和静态的方法。
非静态的内部类中,不能有静态的成员和静态的方法。因为非静态内部类不是在外部类加载的时候,这个内部类就加载了,而是需要去调用的时候,这个内部类才会被加载,而普通时刻是不会被加载,那么里面的静态成员就不会被初始化。
class Car{
class Wheel{
}
}
class PlaneWheel extends Car.Wheel{
public static void main(String[] args){
PlaneWheel pw = new PlaneWheel();//报错
}
}
编译报错
当产生子类的对象的时候,首先会调用基类的构造函数,产生一个基类的对象,而现在的基类是一个内部类,前面说了,要产生一个内部类对象首先需要产生一个外部累对象,然后才能产生内部类对象,从而建立起内部类对象到外部类对象的一个引用关系。
而上面在产生PlaneWheel对象的时候,Car这个类的对象没有产生。因此,内部类对象也就无法产生了。
class Car{
class Wheel{
}
}
class PlaneWheel extends Car.Wheel{
PlaneWheel(Car car){
car.super();
}
public static void main(String[] args){
Car car = new Car();
PlaneWheel pw = new PlaneWheel(car);//正确
}
}
//通过car.super()这种特殊的语法,建立起从内部类对象到一个指定的外部类对象的一个引用关系。
public class Body { // 外部类
public class Heart { // 成员内部类
// 内部类的方法
public void beat() {
System.out.println("心脏跳动:蹦蹦蹦!");
System.out.println("我叫:" + name); // 正确写法!
}
}
// 外部类的成员变量
private String name;
// 外部类的方法
public void methodBody() {
System.out.println("外部类的方法");
new Heart().beat();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/*
如果一个事物的内部包含另一个事物,那么这就是一个类内部包含另一个类。
例如:身体和心脏的关系。又如:汽车和发动机的关系。
分类:
1. 成员内部类
2. 局部内部类(包含匿名内部类)
成员内部类的定义格式:
修饰符 class 外部类名称 {
修饰符 class 内部类名称 {
// ...
}
// ...
}
注意:内用外,随意访问;外用内,需要内部类对象。
==========================
如何使用成员内部类?有两种方式:
1. 间接方式:在外部类的方法当中,使用内部类;然后main只是调用外部类的方法。
2. 直接方式,公式:
类名称 对象名 = new 类名称();
【外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();】
*/
public class Demo01InnerClass {
public static void main(String[] args) {
Body body = new Body(); // 外部类的对象
// 通过外部类的对象,调用外部类的方法,里面间接在使用内部类Heart
body.methodBody();
System.out.println("=====================");
// 按照公式写:
Body.Heart heart = new Body().new Heart();
heart.beat();
}
}
// 如果出现了重名现象,那么格式是:外部类名称.this.外部类成员变量名
public class Outer {
int num = 10; // 外部类的成员变量
public class Inner /*extends Object*/ {
int num = 20; // 内部类的成员变量
public void methodInner() {
int num = 30; // 内部类方法的局部变量
System.out.println(num); // 局部变量,就近原则
System.out.println(this.num); // 内部类的成员变量
System.out.println(Outer.this.num); // 外部类的成员变量
}
}
}
public class Demo02InnerClass {
public static void main(String[] args) {
// 外部类名称.内部类名称 对象名 = new 外部类名称().new 内部类名称();
Outer.Inner obj = new Outer().new Inner();
obj.methodInner();
}
}
/*
如果一个类是定义在一个方法内部的,那么这就是一个局部内部类。
“局部”:只有当前所属的方法才能使用它,出了这个方法外面就不能用了。
定义格式:
修饰符 class 外部类名称 {
修饰符 返回值类型 外部类方法名称(参数列表) {
class 局部内部类名称 {
// ...
}
}
}
小节一下类的权限修饰符:
public > protected > (default) > private
定义一个类的时候,权限修饰符规则:
1. 外部类:public / (default),最外层的外部类,只能是这两种访问修饰符,不能是private,protected的。
2. 成员内部类:public / protected / (default) / private
3. 局部内部类:什么都不能写,不等同于default的。不是default的意思。因为default的类,同一个包中能访问的,这局部内部类的作用域就是方法中。
*/
class Outer {
public void methodOuter() {
class Inner { // 局部内部类,只能在methodOuter局部作用域中使用的。
int num = 10;
public void methodInner() {
System.out.println(num); // 10
}
}
Inner inner = new Inner();
inner.methodInner();
}
}
public class DemoMain {
public static void main(String[] args) {
Outer obj = new Outer();
obj.methodOuter();
}
}
/*
局部内部类,如果希望访问所在方法的局部变量,那么这个局部变量必须是【有效final的】。
备注:从Java 8+开始,只要局部变量事实不变,那么final关键字可以省略。如果不写final,然后后面又将这个变量值修改了,那么就会报错了。
原因:两者的生命周期不同。
1. new出来的对象在堆内存当中。
2. 局部变量是跟着方法走的,在栈内存当中。
3. 方法运行结束之后,立刻出栈,局部变量就会立刻消失。
4. 但是new出来的对象会在堆当中持续存在,直到垃圾回收消失。这就说明了MyInner对象活的更久,那么它想用外部的局部变量,而这个局部变量已经释放了就不行了,因此,需要将其设置为final常量,放到常量池(堆中)中了,一直存在了,或者不写final,那么实际上就会将这个局部变量cp一份到常量池中来,供内部类来使用。
*/
public class MyOuter {
public void methodOuter() {
int num = 10; // 所在方法的局部变量
class MyInner {
public void methodInner() {
System.out.println(num);
}
}
}
}
匿名内部类
匿名内部类 :是内部类的简化写法。它的本质是一个 带具体实现的 父类或者父接口的 匿名的 子类对象。
开发中,最常用到的内部类就是匿名内部类了。以接口举例,当你使用一个接口时,似乎得做如下几步操作,
- 定义子类
- 重写接口中的方法
- 创建子类对象
- 调用重写后的方法
能不能简化一下,把以上四步合成一步呢?匿名内部类就是做这样的快捷方式。
前提:匿名内部类必须继承一个父类或者实现一个父接口。
new 父类名或者接口名(){
// 方法重写
@Override
public void method() {
// 执行语句
}
};
//不使用匿名内部类时
interface Animal{
void eat();
void sleep();
}
class Zoo{
class Tiger implements Animal{
public void eat(){
System.out.println("tiger eat");
}
public void sleep(){
System.out.println("tiger sleep");
}
}
Animal getAnimal(){
return new Tiger();
}
}
class Test{
public static void main(String[] args){
Zoo z = new Zoo();
Animal an = z.getAnimal();
an.eat();
an.sleep();
}
}
结果:
tiger eat
tiger sleep
//使用匿名内部类
interface Animal{
void eat();
void sleep();
}
class Zoo{
Animal getAnimal(){
return new Tiger(){
public void eat(){
System.out.println("tiger eat");
}
public void sleep(){
System.out.println("tiger sleep");
}
};
}
}
class Test{
public static void main(String[] args){
Zoo z = new Zoo();
Animal an = z.getAnimal();
an.eat();
an.sleep();
}
}
//通过内部类去实现接口,隐藏实现细节。声明为private,那么就不能在Zoo的外部被访问了
interface Animal{
void eat();
void sleep();
}
class Zoo{
private class Tiger implements Animal{
public void eat(){
System.out.println("tiger eat");
}
public void sleep(){
System.out.println("tiger sleep");
}
}
Animal getAnimal(){
return new Tiger();
}
}
class Test{
public static void main(String[] args){
Zoo z = new Zoo();
Animal an = z.getAnimal();
an.eat();
an.sleep();
}
}
//因为Tiger是私有的,因此,不能在外部通过Zoo.Tiger tiger这样来定义变量了。
//在接口中定义的方法和类中的方法重名,但是表示的意义不同,那么这个时候,子类就可以通过内部类来实现接口解决。
interface Machine{
void run();
}
class Person{
void run(){
System.out.println("run");
}
}
class Robot extends Person{
private class MachineHeart implements Machine{
public void run(){
System.out.println("heart run");
}
}
Machine getMachine(){
return new MachineHeart();
}
}
class Test{
public static void main(String[] args){
Robot robot = new Robot();
Machine m = robot.getMachine();
m.run();
robot.run();
}
}
结果:
heart run
run
java中不允许多继承,但是可以通过内部类和接口来解决多继承的问题。
c++中的多继承容易产生二义性等问题,但是,java中的内部类和接口的方式能避免这种问题。
class A{
void fn1(){
}
}
abstract class B{
abstract void fn2();
}
class C extends A{
B getB(){
return new B(){
public void fn2(){
}
};
}
}
class Test{
static void method1(A a){
a.fn1();
}
static void method2(B b){
b.fn2();
}
public static void main(String[] args){
C c = new C();
method1(c);
method2(c.getB());
}
}
public interface MyInterface {
void method1(); // 抽象方法
void method2();
}
public class MyInterfaceImpl implements MyInterface {
@Override
public void method1() {
System.out.println("实现类覆盖重写了方法!111");
}
@Override
public void method2() {
System.out.println("实现类覆盖重写了方法!222");
}
}
/*
如果接口的实现类(或者是父类的子类)只需要使用唯一的一次,
那么这种情况下就可以省略掉该类的定义,而改为使用【匿名内部类】。
匿名内部类的定义格式:
接口名称 对象名 = new 接口名称() {
// 覆盖重写所有抽象方法
};
对格式“new 接口名称() {...}”进行解析:
1. new代表创建对象的动作
2. 接口名称就是匿名内部类需要实现哪个接口
3. {...}这才是匿名内部类的内容
另外还要注意几点问题:
1. 匿名内部类,在【创建对象】的时候,只能使用唯一一次。
如果希望多次创建对象,而且类的内容一样的话,那么就需要使用单独定义的实现类了。
2. 匿名对象,在【调用方法】的时候,只能调用唯一一次。
如果希望同一个对象,调用多次方法,那么必须给对象起个名字。
3. 匿名内部类是省略了【实现类/子类名称】,但是匿名对象是省略了【对象名称】
强调:匿名内部类和匿名对象不是一回事!!!
*/
public class DemoMain {
public static void main(String[] args) {
// MyInterface obj = new MyInterfaceImpl();
// obj.method();
// MyInterface some = new MyInterface(); // 错误写法!
// 使用匿名内部类,但不是匿名对象,对象名称就叫objA
MyInterface objA = new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-A");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-A");
}
};
objA.method1();
objA.method2();
System.out.println("=================");
// 使用了匿名内部类,而且省略了对象名称,也是匿名对象
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method1();
// 因为匿名对象无法调用第二次方法,所以需要再创建一个匿名内部类的匿名对象
new MyInterface() {
@Override
public void method1() {
System.out.println("匿名内部类实现了方法!111-B");
}
@Override
public void method2() {
System.out.println("匿名内部类实现了方法!222-B");
}
}.method2();
}
}
//通常在方法的形式参数是接口或者抽象类时,也可以将匿名内部类作为参数传递。
public abstract class FlyAble{
public abstract void fly();
}
public class InnerDemo2 {
public static void main(String[] args) {
/*
1.等号右边:定义并创建该接口的子类对象
2.等号左边:是多态,接口类型引用指向子类对象
*/
FlyAble f = new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
};
// 将f传递给showFly方法中
showFly(f);
}
public static void showFly(FlyAble f) {
f.fly();
}
}
//两步,也可以简化为一步
public class InnerDemo3 {
public static void main(String[] args) {
/*
创建匿名内部类,直接传递给showFly(FlyAble f)
*/
showFly( new FlyAble(){
public void fly() {
System.out.println("我飞了~~~");
}
});
}
public static void showFly(FlyAble f) {
f.fly();
}
}
引用类型作为成员变量
public class Weapon {
private String code; // 武器的代号
public Weapon() {
}
public Weapon(String code) {
this.code = code;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
// 游戏当中的英雄角色类
public class Hero {
private String name; // 英雄的名字
private int age; // 英雄的年龄
private Weapon weapon; // 英雄的武器,引用类型作为成员变量
public Hero() {
}
public Hero(String name, int age, Weapon weapon) {
this.name = name;
this.age = age;
this.weapon = weapon;
}
public void attack() {
System.out.println("年龄为" + age + "的" + name + "用" + weapon.getCode() + "攻击敌方。");
}
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 Weapon getWeapon() {
return weapon;
}
public void setWeapon(Weapon weapon) {
this.weapon = weapon;
}
}
public class DemoMain {
public static void main(String[] args) {
// 创建一个英雄角色
Hero hero = new Hero();
// 为英雄起一个名字,并且设置年龄
hero.setName("盖伦");
hero.setAge(20);
// 创建一个武器对象
Weapon weapon = new Weapon("AK-47");
// 为英雄配备武器
hero.setWeapon(weapon);
// 年龄为20的盖伦用多兰剑攻击敌方。
hero.attack();
}
}
引用类型作为成员变量时,对它进行赋值的操作,实际上,是赋给它该引用类型的一个对象。
接口类型作为成员变量
public interface Skill {
void use(); // 释放技能的抽象方法
}
public class SkillImpl implements Skill {
@Override
public void use() {
System.out.println("Biu~biu~biu~");
}
}
public class Hero {
private String name; // 英雄的名称
private Skill skill; // 英雄的技能
public Hero() {
}
public Hero(String name, Skill skill) {
this.name = name;
this.skill = skill;
}
public void attack() {
System.out.println("我叫" + name + ",开始施放技能:");
skill.use(); // 调用接口中的抽象方法
System.out.println("施放技能完成。");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Skill getSkill() {
return skill;
}
public void setSkill(Skill skill) {
this.skill = skill;
}
}
public class DemoGame {
public static void main(String[] args) {
Hero hero = new Hero();
hero.setName("艾希"); // 设置英雄的名称
// 设置英雄技能
// hero.setSkill(new SkillImpl()); // 使用单独定义的实现类
// 还可以改成使用匿名内部类
// Skill skill = new Skill() {
// @Override
// public void use() {
// System.out.println("Pia~pia~pia~");
// }
// };
// hero.setSkill(skill);
// 进一步简化,同时使用匿名内部类和匿名对象
hero.setSkill(new Skill() {
@Override
public void use() {
System.out.println("Biu~Pia~Biu~Pia~");
}
});
hero.attack();
}
}
接口作为成员变量时,对它进行赋值的操作,实际上,是赋给它该接口的一个子类对象。
接口类型作为参数和返回值类型
import java.util.ArrayList;
import java.util.List;
/*
java.util.List正是ArrayList所实现的接口。
*/
public class DemoInterface {
public static void main(String[] args) {
// 左边是接口名称,右边是实现类名称,这就是多态写法
List<String> list = new ArrayList<>();
List<String> result = addNames(list);
for (int i = 0; i < result.size(); i++) {
System.out.println(result.get(i));
}
}
public static List<String> addNames(List<String> list) {
list.add("迪丽热巴");
list.add("古力娜扎");
list.add("玛尔扎哈");
list.add("沙扬娜拉");
return list;
}
}
接口作为参数时,传递它的子类对象。
接口作为返回值类型时,返回它的子类对象。