目录
1. 抽象类
1.1 抽象类概念
在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。
即:抽象类不是一个具体的类,不能描述具体的对象。
比如:
说明:
1、矩形、三角形、圆形都是图形,因此和Shape类的惯性应该是继承关系
2、虽然图形图Shape中也存在draw的方法,但由于Shape类并不是具体的图形,因此其内部的draw方法实际是没有办法实现的
3、由于Shape类没有办法描述一个县体的图形,导致其draw()方法无法具体实现,因此可以将Shape类设计为“抽象类”
在打印图形例子中,我们发现,父类 Shape 中的 draw 方法好像并没有什么实际工作,主要的绘制图形都是由 Shape的各种子类的 draw 方法来完成的。像这种没有实际工作的方法,我们可以把它设计成一个 抽象方法(abstract method),包含抽象方法的类我们称为 抽象类(abstract class)
1.2 抽象类语法:abstract关键字
在Java中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。
// 抽象类:被abstract修饰的类
public abstract class Shape {
// 抽象方法:被abstract修饰的方法,没有方法体
public abstract void draw();
// 抽象类也是类,也可以增加普通方法和属性
public String name;
public static int a;
public void eat() {
}
}
注意:抽象类也是类,内部可以包含普通方法和属性,构造方法
抽象类的形状就跟棒球一样。
1.3 抽象类特性
- 使用 abstract 修饰的方法称为抽象方法。
- 类中包含抽象方法,这个类也必须由 abstract 修饰,称为抽象类。
- 抽象类不能直接实例化对象
Shape shape = new Shape(); // 编译出错 Error:(30, 23) java: Shape是抽象的; 无法实例化
抽象类当中可以和普通类一样,定义成员变量和成员方法- 当一个普通的类继承了抽象类,需要重写抽象类当中的所有的抽象方法。
- 抽象类的出现,就是为了被继承。被继承后能发生向上转型。
- 抽象方法不能被 private 修饰
- 抽象方法不能被 final 和 static 修饰,因为抽象方法要被子类重写。
- 抽象类中不一定包含抽象方法,但是有抽象方法的类一定是抽象类
- 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量
抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修饰。
出来混迟早是要还的,最终继承的子类要重写前面全部的抽象方法。
abstract class Shape {
public abstract void draw();
}
abstract class A extends Shape {
public abstract void testA();
}
class B extends A {
@Override
public void draw() {
}
@Override
public void testA() {
}
}
代码演示:
abstract class Shape {
public abstract void draw();
/*public String name;
public static int a;
public void eat() {
}*/
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("●!");
}
}
public class TestDemo {
public static void main(String[] args) {
//Shape shape = new Shape(); //编译报错,Shape是抽象类,不能被实例化
Shape shape = new Cycle();
shape.draw();
}
}
//运行结果
●!
1.4 抽象类的作用
抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类,然后让子类重写抽象类中的抽象方法。
- 普通的类也可以被继承,普通的方法也可以被重写,为啥非得用抽象类和抽象方法呢?
因为使用抽象类相当于多了一重编译器的校验。
- 使用抽象类的场景就如上面的代码,实际工作不应该由父类完成,而应由子类完成。那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误,让我们尽早发现问题。
- 很多语法存在的意义都是为了 "预防出错",例如我们曾经用过的 final 也是类似。创建的变量用户不去修改,不就相当于常量嘛? 但是加上 final 能够在不小心误修改的时候,让编译器及时提醒我们。
- 充分利用编译器的校验,在实际开发中是非常有意义的。
2. 接口
2.1 接口的概念
在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。
电脑的USB口上,可以插:U盘、鼠标、键盘...所有符合USB协议的设备
电源插座插孔上,可以插:电脑、电视机、电饭煲...所有符合规范的设备
通过上述例子可以看出:
- 接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。
- 在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。
2.2 语法规则:interface关键字
接口的定义格式与定义类的格式基本相同,将 class 关键字换成 interface 关键字,就定义了一个接口。
public interface 接口名称{
// 抽象方法
public abstract void method1(); // public abstract 是固定搭配,可以不写
public void method2();
abstract void method3();
void method4();
// 注意:在接口中上述写法都是抽象方法,跟推荐方式4,代码更简洁
}
提示:
- 创建接口时,接口的命名一般以大写字母 I 开头
- 接口的命名一般使用 "形容词" 词性的单词
- 阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性。
2.3 接口使用与特性:implements关键字
接口不能直接使用,必须要有一个“实现类”来“实现”该接口,实现接口中的所有抽象方法。
public class 类名称 implements 接口名称{ // ... }
子类和父类之间是 extends 继承关系,类与接口之间是 implements 实现关系。
接口特性:
1、接口是使用 interface 关键字来修饰的
2、接口当中不能有被实现的方法,意味着只能有抽象方法。但是两个方法除外(jdk8中引入的):一个是static修饰的方法,一个是被default修饰的方法。 static修饰的方法不能被重写,直接可以通过接口名调用,default修饰的方法 可重写也可不重写(下面代码介绍)。接口可以认为比抽象类更抽象。
3、接口当中的抽象方法默认都是public abstract 修饰的(只能是,其他修饰符是错误的;两个关键字可以随意排序)
4、接口当中的成员变量默认都是 public static final 修饰的(三个关键字可以随意排序)
interface Shape {
//接口当中的成员变量都是 public static final 修饰的
public int a = 1;
public static int b = 2;
public static final int c = 3;
//意味着为常量,大写
int A = 1;
int B = 2;
int C = 3;
//接口当中的抽象方法默认都是public abstract修饰的
void draw();
//public void draw();
//public abstract void draw();
// 不能有方法的实现
public void test() { //编译报错
System.out.println("123");
}
//有static 或者 define关键字修饰的方法可以实现,JDK8 新引入的
default public void test1(){
System.out.println("test1");
}
public static void test2() {
System.out.println("test2");
}
}
5、接口不能进行实例化,但在被实现后能发生向上转型
6、类和接口之间的关系,可以使用 implements 来进行关联,叫做这个类实现了接口。
7、如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类。但是出来混迟早是要还的
8、接口中不能有静态代码块和构造代码块,也没有构造方法
9、接口虽然不是类,但是接口编译完成后也有对应的字节码文件,后缀格式也是.class
10、重写接口中方法时,不能使用默认的访问权限。重写的子类的访问权限要大于等于父类。
接口中方法默认被public abstract修饰的,所以子类的这个重写的方法的访问权限只能是public
interface Shape {
/*
{}
static {}
public Shape () {
}
*/
//接口当中的抽象方法默认都是public abstract修饰的
void draw();
}
class Rect implements Shape {
public void draw() {
System.out.println("矩形!");
}
}
class Flower implements Shape {
@Override
public void draw() {
System.out.println("❀!");
}
}
接口可以去引用实现了该接口的具体的类型。 接口也能发生向上转型和动态绑定,来实现多态。
一个类实现了一个接口,通过这个接口引用可以指向这个类的具体对象。
public class Test {
public static void drawMap(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
//Shape shape = new Shape();//不能被实例化
Shape shape1 = new Rect();
Shape shape2 = new Flower();
drawMap(shape1);
drawMap(shape2);
System.out.println("====================");
Shape[] shapes = {new Rect(),new Flower(),new Rect(),new Flower()};
for (Shape shape: shapes) {
shape.draw();
}
}
}
//运行结果
矩形!
❀!
====================
矩形!
❀!
矩形!
❀!
代码示例:
实现笔记本电脑使用USB鼠标、USB键盘的例子
1. USB接口:包含打开设备、关闭设备功能
2. 笔记本类:包含开机功能、关机功能、使用USB设备功能
3. 鼠标类:实现USB接口,并具备点击功能
4. 键盘类:实现USB接口,并具备输入功能
//USB接口
public interface USB {
void openDevice();
void closeDevice();
}
//鼠标类:实现USB接口
public class Mouse implements USB {
@Override
public void openDevice() {
System.out.println("鼠标开始工作!");
}
public void click() {
System.out.println("疯狂点击鼠标!");
}
@Override
public void closeDevice() {
System.out.println("鼠标关闭工作!");
}
}
//键盘类:实现USB接口
public class KeyBoard implements USB {
public void openDevice() {
System.out.println("键盘开始工作!");
}
public void input() {
System.out.println("疯狂敲击键盘!");
}
public void closeDevice() {
System.out.println("键盘结束工作!");
}
}
//笔记本类:使用USB设备
public class Computer {
public void powerOn() {
System.out.println("打开电脑!");
}
public void powerOff() {
System.out.println("关闭电脑");
}
public void useService(USB usb) {
usb.openDevice();
if (usb instanceof Mouse) {
Mouse mouse = (Mouse) usb;//向下转型,调用子类特有的方法
mouse.click();
} else if (usb instanceof KeyBoard) {
KeyBoard keyBoard = (KeyBoard) usb;
}
usb.closeDevice();
}
}
测试类:
public class Test {
public static void main(String[] args) {
Computer computer = new Computer();
computer.powerOn();
Mouse mouse = new Mouse();
computer.useService(mouse);//向上转型
System.out.println("=====================");
KeyBoard keyBoard = new KeyBoard();
computer.useService(keyBoard);//向上转型
computer.powerOff();
}
}
//运行结果
打开电脑!
鼠标开始工作!
疯狂点击鼠标!
鼠标关闭工作!
=====================
键盘开始工作!
键盘结束工作!
关闭电脑
接口中被static修饰的方法不能被重写,直接可以通过接口名调用。
被default修饰的方法,可重写也可不重写。
不重写默认调用的是接口的方法;重写调用重写后的方法。
代码演示:
public interface ITest {
//必须被重写
void testA();
//一定不能重写
public static void staticMethod () {
System.out.println("这是staticMathe()");
}
//可重写可不重写
default public void defaultMethod() {
System.out.println("这是defaultMethod()");
}
}
测试类:
public class TestDemo implements ITest{
@Override
public void testA() {
System.out.println("重写test()");
}
@Override
public void defaultMethod() {
System.out.println("重写的defaultMethod()");
}
}
class Test {
public static void main(String[] args) {
ITest.staticMethod();
//TestDemo testDemo = new TestDemo();
//testDemo.defaultMethod();//被继承下来的部分,子类访问父类成员方法
ITest iTest = new TestDemo();//发生向上转型
iTest.defaultMethod();//没有重写default修饰的方法,父类引用调用自己独有的方法
//重写后调用重写的方法
}
}
//运行结果
这是staticMathe()
重写的defaultMethod()
2.5 实现多个接口
在Java中,类和类之间是单继承的,一个类只能有一个父类,即Java中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物。
bstract class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public abstract void eat();
/*public void fly(){}
//不建议在抽象类中写,不是所有动物都会飞,会跑,会游泳的
public void run() {}
public void swim() {}*/
}
另外我们再提供一组接口, 分别表示 "会飞的", "会跑的", "会游泳的"
interface IFly {
void fly();
}
interface IRun {
void run();
}
interface ISwim {
void swim();
}
接下来我们创建几个具体的动物
狗会跑;青蛙会跑,游泳;鸭子会跑,游泳,飞。
注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。
提示, IDEA 中使用 ctrl + i 快速实现接口
class Dog extends Animal implements IRun {
public Dog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(this.name + " 正在吃狗粮!");
}
@Override
public void run() {
System.out.println(this.name + " 四条腿猛猛跑!");
}
}
class Frog extends Animal implements IRun, ISwim {
public Frog(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(this.name + " 正在吃蛙粮!");
}
@Override
public void run() {
System.out.println(this.name + " 两条腿跳着跑!");
}
@Override
public void swim() {
System.out.println(this.name + " 蛙泳唰唰游!");
}
}
class Duck extends Animal implements IRun, ISwim, IFly {
public Duck(String name, int age) {
super(name, age);
}
@Override
public void eat() {
System.out.println(this.name + " 正在吃鸭粮!");
}
@Override
public void fly() {
System.out.println(this.name + " 酷酷飞!");
}
@Override
public void run() {
System.out.println(this.name + " 两条腿迈着跑!");
}
@Override
public void swim() {
System.out.println(this.name + " 滑着游泳!");
}
}
上面的代码展示了 Java 面向对象编程中最常见的用法:一个类继承一个父类,同时实现多种接口。
继承表达的含义是 is - a 语义,而接口表达的含义是 具有 xxx 特性。
- 狗是一种动物,具有会跑的特性(功能)
- 青蛙也是一种动物,具有跑,游泳的特性
- 鸭子也是一种动物,具有跑,游泳和飞的特性
测试类:
public class Test {
public static void func1(Animal animal) {
animal.eat();
}
public static void running (IRun iRun) {
iRun.run();
}
public static void swimming (ISwim iSwim) {
iSwim.swim();
}
public static void flying (IFly iFly) {
iFly.fly();
}
public static void main(String[] args) {
func1(new Dog("六六", 3));
func1(new Frog("蛙蛙", 2));
func1(new Duck("唐老鸭", 5));
System.out.println("=========================");
running(new Dog("六六", 3));
running(new Frog("蛙蛙", 2));
running(new Duck("唐老鸭", 5));
System.out.println("=========================");
swimming(new Frog("蛙蛙", 2));
swimming(new Duck("唐老鸭", 5));
System.out.println("=========================");
flying(new Duck("唐老鸭", 5));
}
}
//运行结果
六六 正在吃狗粮!
蛙蛙 正在吃蛙粮!
唐老鸭 正在吃鸭粮!
=========================
六六 四条腿猛猛跑!
蛙蛙 两条腿跳着跑!
唐老鸭 两条腿迈着跑!
=========================
蛙蛙 蛙泳唰唰游!
唐老鸭 滑着游泳!
=========================
唐老鸭 酷酷飞!
- 这样设计有什么好处呢? 时刻牢记多态的好处,让程序猿忘记类型。有了接口之后,类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力。
- 在这个 runing 方法内部,我们并不关注到底是不是动物,只要参数是会跑的就行。例如,写一个机器人类,具备跑的特性。
class Robot implements IRun {
public String name;
public Robot(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(this.name + "机器人会跑!");
}
}
running(new Robot("大黄蜂"));
//执行结果
大黄蜂机器人会跑!
为什么说接口解决了Java的多继承问题:
- 不是所有的动物都具备这些功能,需要把这些功能(行为分开),让这个动物具有指定的特性。不能当甩手掌柜。
- 接口是行为的规范,标准的规范;
- 让程序员忘记类型。有了接口之后,类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力。
2.6 接口间的继承
在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。
接口可以继承一个接口,达到复用的效果。使用 extends 关键字。
- 类 与 类 之间的关系 -》 extends 继承
- 类 和 接口 之间的关系 -》implements 实现
- 接口 与 接口 之间的关系 -》 extends 拓展,接口间的继承相当于把多个接口合并在一起。
interface A {
void testA();
}
interface B {
void testB();
}
//接口 extends 接口 —》 拓展
interface C extends A, B {
void testC();
}
// 类 implements 接口 -》 实现
class TestDemo implements C {
@Override
public void testA() {
}
@Override
public void testB() {
}
@Override
public void testC() {
}
}
2.7 抽象类和接口的区别
抽象类和接口都是 Java 中多态的常见使用方式。都需要重点掌握,同时又要认清两者的区别(重要!!!常见面试题)
核心区别:
- 抽象类中可以包含普通方法和普通属性,这样的普通方法和属性可以被子类直接使用(不必重写)
- 接口中不能包含普通方法,子类必须重写所有的抽象方法。因此接口比抽象类更加抽象。
比如之前写的 Animal 例子。此处的 Animal 中包含两个属性name和age,这个两个属性在任何子类中都是存在的,因此此处的 Animal 只能作为一个抽象类, 而不应该成为一个接口
class Animal {
public String name;
public int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
}
再次提醒:
抽象类存在的意义是为了让编译器更好的校验,像Animal这样的类我们并不会直接使用,而是使用它的子类。万一不小心创建了Animal的实例,编译器会及时提醒我们。
3. Object 类
Object是Java默认提供的一个类。Java里面除了Object类,所有的类都是存在继承关系的。默认会继承Object父类。
即所有类的对象都可以使用Object的引用进行接收。
- 现阶段先学习使用:clone(),equals(),hashCode(),toString(),这四种方法。
- 线程阶段学习使用:notify(),notifyAll(),wait(),finalize(),这几种方法。
- 反射阶段学习使用:getClass()。
范例:使用Object接收所有类的对象
class Person{}
class Student{}
public class Test {
public static void main(String[] args) {
function(new Person());
function(new Student());
}
public static void function(Object obj) {
System.out.println(obj);
}
}
//执行结果:
Person@1b6d3586
Student@4554617c
所以在开发之中,Object类是参数的最高统一类型。但是Object类也存在有定义好的一些方法。如下:
对于整个Object类中的方法需要实现全部掌握。
本篇博客当中,我们主要来熟悉这几个方法:toString()方法,equals()方法,hashcode()方法。clone()方法会在下一篇博客中介绍到。
3.1 获取对象信息
如果要打印对象中的内容,可以直接重写Object类中的toString()方法,之前已经讲过了,此处不再累赘。
Object类中的toString()方法实现:
3.2 对象比较equals方法
在Java中,== 进行比较时:
- 如果 == 左右两侧是基本类型变量,比较的是变量中值是否相同
- 如果 == 左右两侧是引用类型变量,比较的是引用变量地址是否相同
- 如果要比较对象中内容,必须重写Object中的equals方法,因为equals方法默认也是按照地址比较的.
Object类中的equals方法:
class Person {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
Person类重写equals方法后,然后比较:
class Person implements Cloneable {
public String name;
public int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
//重写 equals
@Override
public boolean equals(Object obj) {
Person tmp = (Person) obj;
return this.name.equals(tmp.name)
&& this.age == tmp.age;
}
}
也可以不自己重写equals方法,由编译器生成(Alt + Insert )
这里的hashCode()方法,定义对象在内存中的位置。一般与equals方法配合使用,后续会讲到。
结论:比较对象中内容是否相同的时候,一定要重写equals方法。
3.3 hashcode方法(简单介绍)
hashCode()方法,帮我算了一个具体的对象位置。后续在数据结构中会详细讲解,这里了解即可。
hashcode方法源码:
该方法是一个native方法,底层是由C/C++代码写的,特点是:快。我们看不到。
4. 内部类
当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么这个内部的完整结构最好使用内部类。
在 Java 中,可以将一个类定义在另一个类或者一个方法的内部,前者称为内部类,后者称为外部类。内部类也是封装的一种体现。
比如:一串糖葫芦,是由一个个山楂构成的,而这里的内部类就是山楂,一串糖葫芦就是外部类。
public class OutClass {
class InnerClass{
}
}// OutClass是外部类
// InnerClass是内部类
1、定义在class 类名{}花括号外部的,即使是在一个文件里,都不能称为内部类
public class A{
}
class B{
}
// A 和 B是两个独立的类,彼此之前没有关系
2、内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
4.1 内部类的分类
先来看下,内部类都可以在一个类的哪些位置进行定义:
public class OutClass {
// 成员位置定义:未被static修饰 --->实例内部类
public class InnerClass1{
}
// 成员位置定义:被static修饰 ---> 静态内部类
static class InnerClass2{
}
public void method(){
// 方法中也可以定义内部类 ---> 局部内部类:几乎不用
class InnerClass5{
}
}
}
根据内部类定义的位置不同,一般可以分为以下几种形式:
- 实例内部类:未被static修饰的成员内部类
- 静态内部类:被static修饰的成员内部类
- 匿名内部类:没有名字的内部类。
- 局部内部类:定义在方法体内部。不谈修饰符,几乎不用。
注意:内部类其实日常开发中使用并不是非常多,大家在看一些库中的代码时候可能会遇到的比较多,日常开发中使用最多的是匿名内部类。
- 在外部类中,内部类定义位置与外部类成员所处的位置相同,因此称为成员内部类。成员内部类包含:普通(实例)内部类 和 静态内部类。
- 说内部类一定要加上前缀,每种内部类特点不一样。
4.1.1 实例内部类
即未被static修饰的成员内部类。
实例内部类在创建对象时需要先创建外部类的对象,通过外部类对象的引用来实例化成员。
- 实例内部类是定义在另一个类的实例内部的类。它依赖于外部类的实例存在,因此每次创建外部类的实例时,实例内部类也会被创建。
- 要访问实例内部类中成员,必须要创建实例内部类的对象。而普通内部类定义与外部类成员定义位置相同,因此创建实例内部类对象时必须借助外部类。
【注意事项】
- 实例内部类对象必须在先有外部类对象前提下才能创建
- 内部类中不能定义被static修饰的成员。加final 关键字修饰的成员变量可以定义(改为常量,编译时确认的),成员方法不行。(下方有详细讲解)
- 实例内部类的方法中可以直接访问外部类中的任何成员。
- 实例内部类包含两个this:一个是自己的this,另一个是外部类的this。在实例内部类中,可以通过外部类类名.this来访问外部类的this
- 在实例内部类方法中访问同名的成员时,优先访问自己的,如果要访问外部类同名的成员,必须:外部类名称.this.同名成员 来访问
- 实例内部类的非静态方法中包含了一个指向外部类对象的引用,this
- 外部类中,不能直接访问实例内部类中的成员,如果要访问必须先要创建内部类的对象。
- 实例内部类所处的位置与外部类成员变量位置相同,因此也受public、private等访问限定符的约束
代码演示:
class OuterClass {
public int data1 = 1;
public static int data2 = 2;
private int data3 = 3;
//实例内部类
class InnerClass {
public int data1 = 111;
public int data4 = 4;
public static final int data5 = 5;//编译的时候就确认
private int data6 = 6;
public void test() {
System.out.println("这是实例内部类InnerClass::test()" + data5);//"InnerClass::test()" + 5
//如果外部类和实例内部类中具有相同名称成员时,优先访问的是内部类自己的
System.out.println(data1);
System.out.println(this.data1);
//如果要访问外部类同名成员时候,必须:外部类名称.this.同名成员名字
System.out.println(OuterClass.this.data1);
System.out.println(data2);
System.out.println(data3);
//在实例内部类中可以直接访问外部类中:任意访问限定符修饰的成员
System.out.println(data4);
System.out.println(data5);
System.out.println(data6);
System.out.println("=====================================");
}
}
public void test() {
System.out.println("这是外部类OuterClass::test()");
// 要访问实例内部类中成员,必须要创建实例内部类的对象
InnerClass innerClass = new InnerClass();
System.out.println(data1);
System.out.println(innerClass.data1);
System.out.println(data2);
System.out.println(data3);
System.out.println(innerClass.data4);
System.out.println(innerClass.data5);
System.out.println(innerClass.data6);
}
}
测试类:
public class Test2 {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
OuterClass.InnerClass innerClass = outerClass.new InnerClass();
//OuterClass.InnerClass innerClass = new OuterClass().new InnerClass();
innerClass.test();
outerClass.test();
}
}
//执行结果
这是实例内部类InnerClass::test()5
111
111
1
2
3
4
5
6
=====================================
这是外部类OuterClass::test()
1
111
2
3
4
5
6
注意:内部类和外部类共用同一个java源文件,但是经过编译之后,内部类会形成单独的字节码文件
内部类生成的字节码文件,命名格式: 外部类类名+ $ + 内部类类名.class
【详细讲解】:内部类中不能定义被static修饰的成员。
- 原因是,实例内部类在创建对象时,依赖外部类的对象引用。(需要外部类先实例化对象,然后通过这个对象引用调用去创建对象)而static修饰的成员在使用的时候不需要依赖于实例化对象。所以二者矛盾。
- 解决方法:在static修饰的变量上,加一个关键字 final,把该成员变量定义为不可修改的变量,也就是常量(在编译时就确定了值)。此方法谨对内部类static修饰的成员变量有效,对static修饰的成员方法无效,所以内部类中一定不能定义被static修饰的成员方法。
4.1.2 静态内部类
被static修饰的内部成员类称为静态内部类。
实例静态内部类对象。new 外部类类名.静态内部类
【注意事项】
- 在静态内部类中只能访问外部类中的静态成员,不能直接访问外部类普通成员。因为普通成员依赖于对象。
- 非要访问,在静态内部类中,实例一个外部类对象。通过这个引用,来获得外部类的普通成员。
- 创建静态内部类对象时,不需要先创建外部类对象
代码演示:
class OuterClass {
public int data1 = 1;
public static int data2 = 2;
private int data3 = 3;
//静态内部类:被static修饰的成员内部类
static class InnerClass {
public int data4 = 4;
public static int data5 = 5;
private int data6 = 6;
public void test() {
System.out.println("这是静态内部类InnerClass::test()");
// 在内部类中只能访问外部类的静态成员,不能直接访问外部类普通成员
// 静态内部类对象创建 & 成员访问
OuterClass outerClass = new OuterClass();
System.out.println(outerClass.data1);
System.out.println(data2);
System.out.println(outerClass.data3);
System.out.println(data4);
System.out.println(data5);
System.out.println(data6);
}
}
public void test() {
System.out.println("这是外部类InnerClass::test()");
}
}
测试类:
public class Test2 {
public static void main(String[] args) {
OuterClass.InnerClass innerClass = new OuterClass.InnerClass();
innerClass.test();
}
}
//执行结果
这是静态内部类InnerClass::test()
1
2
3
4
5
6
静态内部类相较于实例内部类的区别在于,不需要外部类对象引用。
4.1.3 局部内部类
定义在外部类的方法体或者{}中,该种内部类只能在其定义的位置使用,一般使用的非常少,此处简单了解下语法格式。
class OuterClass {
public int data1 = 1;
public static int data2 = 2;
private int data3 = 3;
public void func() {
int data4 = 4;
//局部内部类:定义在方法体内部
//不能被public、static等访问修饰限定符修饰
class InnerClass {
int data5 = 5;
public void test() {
System.out.println(data1);
System.out.println(data2);
System.out.println(data3);
System.out.println(data4);
System.out.println(data5);
}
}
//只能在该方法体内使用,其他位置都不能用
InnerClass innerClass = new InnerClass();
innerClass.test();
System.out.println(innerClass.data5);
}
}
public class Test2 {
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
outerClass.func();
}
}
//执行结果
1
2
3
4
5
5
【注意事项】
- 局部内部类只能在所定义的方法体内部使用
- 不能被public、static等修饰符修饰
- 几乎不会使用
- 编译器也有自己独立的字节码文件,命名格式:外部类名字 + $ + 数字 + 内部类名字.class
4.1.4 匿名内部类 — 日常开发中用的最多
匿名内部类就是没有名字的内部类 。
上面这个过程相当于 有一个类,实现了IA接口,重写了test方法,然后new了一下,就变成了匿名内部类对象。这个类叫什么,不清楚,也不用了解,知道有这个类就可以了。
通过两种方式可以调用匿名内部类的方法:
1、. 内部类重写的方法
2、接收一下匿名内部类对象(已经new过了),通过对象的引用去调用。
好啦Y(^o^)Y,本节内容就到此结束了,感谢大家的阅读浏览,期望大家的一键三连哟!