这篇文章我们主要学习的是两个知识点,可以来解决文章标题所提出来的三个问题。
目录
1.抽象类
1.1 抽象类概念
1.2 抽象类语法
1.3 抽象类特性
1.4 抽象类的作用
2.内部类
2.1 内部类的分类
2.2 实例内部类
2.3 静态内部类
2.4 匿名内部类
2.5 局部内部类
1.抽象类
在多态的学习中,我们曾写过这样的一段代码:
class Shape {
//属性....
public void draw() {
System.out.println("画图形!");
}
}
class Rect extends Shape {
@Override
public void draw() {
System.out.println("♦");
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("●");
}
}
class Triangle extends Shape {
@Override
public void draw() {
System.out.println("▲");
}
}
但是,我们会发现,在父类Shape中的draw()方法似乎不曾被使用过,我们可不可以不写draw()的实现部分呢?答案是可以的,抽象类将回来帮我们解决这个问题。
1.1 抽象类概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果 一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。 比如:
说明:
- 矩形、三角形、圆形都是图形,因此和Shape类的特性应该是继承关系。
- 虽然图形图Shape中也存在draw的方法,但由于Shape类并不是具体的图形,因此其内部的draw方法实际是没有办法实现的。
- 由于Shape类没有办法描述一个具体的图形,导致其draw0方法无法具体实现,因此可以将Shape类设计为“抽象类"。
在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由 Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个抽象方法(abstract method), 包含抽象方法的类我们称为抽象类(abstract class)。
1.2 抽象类语法
在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。
对于上面的shape类,我们可以做出如下修改:
abstract class Shape {
//属性....
public abstract void draw();
}
这样,shape就是一个抽象类。
// 抽象类:被abstract修饰的类
abstract class Shape {
// 抽象方法:被abstract修饰的方法,没有方法体
abstract public void draw();
abstract void calcArea();
// 抽象类也是类,也可以增加普通方法和属性
public double getArea() {
return area;
}
protected double area; // 面积
}
注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法
1.3 抽象类特性
1. 抽象类不能直接实例化对象
2. 抽象方法不能是 private 的
3. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
4. 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
5. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
6. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
1.4 抽象类的作用
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法。
我们可能会想, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验。
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成.。那么此时如果不小心误用成父类 了, 使用普通类编译器是不会报错的.。但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题。
很多语法存在的意义都是为了 "预防出错", 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不 就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们。充分利用编译器的校验, 在实际开发中是非常有意义的。
2.内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服 务,那么这个内部的完整结构最好使用内部类。在 Java 中,可以将一个类定义在另一个类或者一个方法的内部, 前者称为内部类,后者称为外部类。内部类也是封装的一种体现。
【注意事项】
1. 定义在class 类名{}花括号外部的,即使是在一个文件里,都不能称为内部类
2. 内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
2.1 内部类的分类
先来看下,内部类都可以在一个类的哪些位置进行定义
class OutClass {
// 成员位置定义:未被static修饰 --->实例内部类
public class InnerClass1 {
}
// 成员位置定义:被static修饰 ---> 静态内部类
static class InnerClass2 {
}
public void method() {
// 方法中也可以定义内部类 ---> 局部内部类:几乎不用
class InnerClass5 {
}
}
}
根据内部类定义的位置不同,一般可以分为以下几种形式:
- 成员内部类(普通内部类:未被static修饰的成员内部类 和 静态内部类:被static修饰的成员内部类)
- 局部内部类(不谈修饰符)、匿名内部类
注意:内部类其实日常开发中使用并不是非常多,大家在看一些库中的代码时候可能会遇到的比较多,日常开始中使用最多的是匿名内部类。
2.2 实例内部类
即未被static修饰的成员内部类。
实例内部类成员变量的定义:
在实例内部类中,不能定义static修饰的成员变量,除非加final修饰:
创建实例内部类对象时,先将外部类对象先创建出来,然后再创建实例内部类对象:
在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员
如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类自己的
如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字
class OuterClass {
public int data1 = 1;
public static int data2 = 2;
private int data3 = 3;
class InnerClass {
public int data1 = 111;
private int data4 = 4;
public static final int data5 = 5; //data5为常量
protected int data6 = 6;
public void test() {
System.out.println("InnerClass::test()");
System.out.println(data1);//优先访问的是内部类自己的data1
System.out.println(OuterClass.this.data1); //访问外部类的data1
System.out.println(data2);
System.out.println(data3);
System.out.println(data4);
System.out.println(data5);
System.out.println(data6);
}
}
public void test() {
System.out.println("OuterClass::test()");
System.out.println(data1); //访问外部类成员变量
System.out.println(data2);
System.out.println(data3);
//外部类方法访问内部类需要先创建内部类对象来访问
InnerClass innerClass = new InnerClass();
System.out.println(innerClass.data1);
System.out.println(innerClass.data4);
System.out.println(innerClass.data6);
}
}
public class Test3 {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
innerClass.test();
outerClass.test();
}
}
输出结果:
【注意事项】
1. 外部类中的任何成员都可以在实例内部类方法中直接访问。
2. 实例内部类所处的位置与外部类成员位置相同,因此也受public、private等访问限定符的 约束。
3. 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成 员,必须:外部类名称.this.同名成员 来访问。
4. 实例内部类对象必须在先有外部类对象前提下才能创建。
5. 实例内部类的非静态方法中包含了一个指向外部类对象的引用。
6. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
2.3 静态内部类
被static修饰的内部成员类称为静态内部类。
创建静态内部类对象不需要先创建外部类对象:
静态内部类不能直接访问外部类非静态成员变量:
class OuterClass {
public int data1 = 1;
public static int data2 = 2;
private int data3 = 3;
public static class InnerClass {
public int data4 = 4;
public static int data5 = 5;
private int data6 = 6;
void test() {
System.out.println("InnerClass::test()");
OuterClass outerClass = new OuterClass();
//System.out.println(data1); //编译报错
System.out.println(outerClass.data1);
System.out.println(data2);
//System.out.println(data3); //编译报错
System.out.println(outerClass.data3);
System.out.println(data5);
System.out.println(data6);
}
}
}
【注意事项】
1. 在静态内部类中只能访问外部类中的静态成员
2. 创建静态内部类对象时,不需要先创建外部类对象
2.4 匿名内部类
学过接口的大家都知道,接口是不能实例化出对象的:
但是我们可以通过接口创建出匿名内部类对象:
输出结果:
2.5 局部内部类
定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式。
public class Test3 {
public static void func() {
class AA {
public int a = 1;
}
AA aa = new AA();
System.out.println(aa.a);
}
public static void main(String[] args) {
func();
}
}
【注意事项】
1. 局部内部类只能在所定义的方法体内部使用
2. 不能被public、static等修饰符修饰
3. 编译器也有自己独立的字节码文件,命名格式:外部类名字$数字内部类名字.class
4. 几乎不会使用