这篇文章我们主要学习的是两个知识点,可以来解决文章标题所提出来的三个问题。
目录
1.抽象类
1.1 抽象类概念
1.2 抽象类语法
1.3 抽象类特性
1.4 抽象类的作用
2.内部类
2.1 内部类的分类
2.2 实例内部类
2.3 静态内部类
2.4 匿名内部类
2.5 局部内部类
在多态的学习中,我们曾写过这样的一段代码:
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()的实现部分呢?答案是可以的,抽象类将回来帮我们解决这个问题。
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果 一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。 比如:
说明:
在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由 Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个抽象方法(abstract method), 包含抽象方法的类我们称为抽象类(abstract class)。
在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. 抽象类不能直接实例化对象
2. 抽象方法不能是 private 的
3. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
4. 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰
5. 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
6. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法。
我们可能会想, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验。
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成.。那么此时如果不小心误用成父类 了, 使用普通类编译器是不会报错的.。但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题。
很多语法存在的意义都是为了 "预防出错", 例如我们曾经用过的 final 也是类似. 创建的变量用户不去修改, 不 就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候, 让编译器及时提醒我们。充分利用编译器的校验, 在实际开发中是非常有意义的。
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服 务,那么这个内部的完整结构最好使用内部类。在 Java 中,可以将一个类定义在另一个类或者一个方法的内部, 前者称为内部类,后者称为外部类。内部类也是封装的一种体现。
1. 定义在class 类名{}花括号外部的,即使是在一个文件里,都不能称为内部类
2. 内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
先来看下,内部类都可以在一个类的哪些位置进行定义
class OutClass {
// 成员位置定义:未被static修饰 --->实例内部类
public class InnerClass1 {
}
// 成员位置定义:被static修饰 ---> 静态内部类
static class InnerClass2 {
}
public void method() {
// 方法中也可以定义内部类 ---> 局部内部类:几乎不用
class InnerClass5 {
}
}
}
根据内部类定义的位置不同,一般可以分为以下几种形式:
注意:内部类其实日常开发中使用并不是非常多,大家在看一些库中的代码时候可能会遇到的比较多,日常开始中使用最多的是匿名内部类。
即未被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. 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
被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. 创建静态内部类对象时,不需要先创建外部类对象
学过接口的大家都知道,接口是不能实例化出对象的:
但是我们可以通过接口创建出匿名内部类对象:
输出结果:
定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式。
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. 几乎不会使用