目录
1. 继承
1.1 概述
多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
单独的这个类称为父类,基类或者超类;这多个类可以称为子类或者派生类。 有了继承以后,我们定义一个类的时候,可以在一个已经存在的类的基础上,还可以定义自己的新成员。
通过extends关键字可以实现类与类的继承
格式:class 子类名 extends 父类名 {}
案例1:用继承改进学生老师案例
//多个类同属性同行为的一般写法
//学生类
class Student{
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
//老师类
class Teacher{
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
//测试类
class ExtendsDemo{
public static void main(String[] args){
//创建学生类对象
Student s = new Student();
//通过对象调用学生类中的方法
s.eat();
s.sleep();
System.out.println("-----");
//创建老师类对象
Teacher t = new Teacher();
t.eat();
t.sleep();
}
}
//用继承改进
//父类
class Person{
public void eat(){
System.out.println("吃饭");
}
public void sleep(){
System.out.println("睡觉");
}
}
//子类
class Student extends Person{}
class Teacher extends Person{}
//测试类
class ExtendsDemo{
public static void main(String[] args){
//创建学生类对象
Student s = new Student();
//通过对象调用学生类中的方法
s.eat();
s.sleep();
System.out.println("-----");
//创建老师类对象
Teacher t = new Teacher();
t.eat();
t.sleep();
}
}
1.2 继承的优缺点
1.2.1 优点
- 提高了代码的复用性——多个类相同的成员可以放到同一个类中
- 提高了代码的维护性——如果功能的代码需要修改,修改一处即可
- 让类与类之间产生了关系,是多态的前提。
1.2.2 缺点
类与类之间产生了关系,这也是继承的一个弊端:类的耦合性很强
开发原则:低耦合,高内聚
解释:耦合——类与类之间的关系;内聚——自己完成某件事情的能力
1.3 Java中继承的特点
1. Java只支持单继承,不支持多继承
即,一个类只能继承一个父类,不能有多个父类。有的语言是可以多继承的,格式为:extends 父类1,父类2,……
2. Java支持多层继承(继承体系)
//格式:
class A{}
class B extends A{}
class C extends B{}
//在C类中创建对象,可以通过对象直接调用B类和A类中的方法
1.4 Java中继承的注意事项
- 子类只能继承父类所有非私有的成员(成员方法和成员变量)——也体现了另一个弊端:打破了封装性
- 子类不能继承父类的构造方法,但是可以通过super(后面讲)关键字去访问父类构造方法。
- 不要为了部分功能而去继承 ,继承中类之间体现的是:”is a”的关系,即子类是父类的一种
1.5 继承中类的各组成部分关系
1.5.1 继承中成员变量之间的关系
- 子类中的成员变量的名称和父类中成员变量不一样
- 子类中的成员变量的名称和父类中成员变量一样时,其值采用就近原则
名称一样时,在子类方法中访问一个变量的查找顺序:
- 首先在子类方法的局部范围找,有就使用;
- 然后在子类的成员范围找 ,有就使用;
- 最后在父类成员范围找(不能访问到父类局部范围,因为方法不调用不执行) ,有就使用;
- 如果还是没有就报错
super关键字和this关键字的区别
案例2:this和super的应用和输出区别
//this和super的区别
//父类
class Father{
int num = 10;
}
//子类
class Son extends Father{
int num = 20;
//show方法
public void show(){
int num = 30;
System.out.println(num);//输出30
System.out.println(this.num);//输出20
System.out.println(super.num);//输出10
}
}
class ExtendsDemo1{
public static void main(String[] args){
int num = 30;
//创建子类对象
Son s = new Son();
//调用show方法
s.show();
}
}
this:代表本类对应的引用
super:代表父类存储空间的标识(可以理解为父类引用)
this | 解释 | super | 解释 | |
---|---|---|---|---|
访问成员变量 | this.成员变量 | 调用本类的成员方法 | super.成员变量 | 调用父类的成员变量 |
访问构造方法 | this(...) | 调用本类的构造方法,()里为参数 | super(...) | 调用父类的构造方法,()里为参数 |
访问成员方法 | this.成员方法() | 调用本类的成员方法 | super.成员方法() | 调用父类的成员方法 |
1.5.2 继承中构造方法之间的关系
- 子类中所有的构造方法默认都会访问父类中空参数的构造方法
案例3:子类构造方法和父类构造方法的关系
//子类构造方法和父类构造方法的关系
//父类
class Father{
//父类无参构造方法,构造方法的方法名与该类的类名相同
public Father(){
System.out.println("这是父类的无参构造方法");
}
//父类带参构造方法
public Father(String name){
System.out.println("这是父类的带参构造方法");
}
}
//子类
class Son extends Father{
//子类无参构造方法,构造方法的方法名与该类的类名相同
public Son(){
//super();//默认调用父类的无参构造方法
System.out.println("这是子类的无参构造方法");
}
//子类带参构造方法
public Son(String name){
//super();//默认调用父类的无参构造方法
System.out.println("这是子类的带参构造方法");
}
}
class ExtendsDemo2{
public static void main(String[] args){
//创建子类对象
Son s = new Son();
System.out.println("-----------");
//创建带参的子类对象
Son s1 = new Son("卡卡西");
}
}输出:
这是父类的无参构造方法
这是子类的无参构造方法
-----------
这是父类的无参构造方法
这是子类的带参构造方法
原因:
因为子类会继承父类中的数据,可能还会使用父类的数据。所以,子类初始化之前,一定要先完成父类数据的初始化。
注意:
子类每一个构造方法的第一条语句默认都是:super();
注意事项:
1. 子类的构造方法默认会去访问父类的无参构造方法
是为了子类访问父类数据的初始化
2. 父类中如果没有无参构造方法(只给了带参构造方法),怎么办?
a. 子类通过super去明确调用带参构造
b. 子类通过this调用本身的其他构造,但是一定会有一个去访问了父类的构造,让父类提供无参构造
练习题1:
//以下代码的输出结果是?
class Fu{
public int num = 10;
public Fu(){
System.out.println("fu");
}
}
class Zi extends Fu{
public int num = 20;
public Zi(){
System.out.println("zi");
}
public void show(){
int num = 30;
System.out.println(num);
System.out.println(this.num);
System.out.println(super.num);
}
}
class Test {
public static void main(String[] args) {
Zi z = new Zi();
z.show();
}
}
//输出结果:
fu
zi
30
20
10
练习题2:
要点:
- 一个类的静态代码块、构造代码块、构造方法执行流程——静态代码块 > 构造代码块 > 构造方法;
- 静态的内容是随着类的加载而加载;
- 子类初始化之前会进行父类的初始化。
class Fu {
static {
System.out.println("静态代码块Fu");
}
{
System.out.println("构造代码块Fu");
}
public Fu() {
System.out.println("构造方法Fu");
}
}
class Zi extends Fu {
static {
System.out.println("静态代码块Zi");
}
{
System.out.println("构造代码块Zi");
}
public Zi() {
System.out.println("构造方法Zi");
}
}
class Test1 {
public static void main(String[] args) {
Zi z = new Zi();
}
}
//输出结果
静态代码块Fu
静态代码块Zi
构造代码块Fu
构造方法Fu
构造代码块Zi
构造方法Zi
练习题3:
要点:
- 成员变量类型(基本型和引用型)
- 一个类的初始化过程
- 成员变量的初始化(默认初始化→显示初始化→构造方法初始化)
- 子父类的初始化(又称分层初始化)
- 先进行父类初始化,再进行子类初始化
class X {
Y b = new Y();
X() {
System.out.print("X");
}
}
class Y {
Y() {
System.out.print("Y");
}
}
public class Z extends X {
Y y = new Y();
Z() {
System.out.print("Z");
}
public static void main(String[] args) {
new Z();
}
}
//输出结果:
YXYZ
1.5.3 继承中成员方法之间的关系
- 子类中的方法和父类中的方法声明不一样
- 子类中的方法和父类中的方法声明一样时,先看子类中有无,有就使用;没有就在父类中找
案例4:
class Father{
public void show(){
System.out.println("Show Father");
}
}
class Son extends Father{
public void method(){
System.out.println("method Son");
}
public void show(){
System.out.println("Show Son");
}
}
class ExtendsDemo3{
public static void main(String[] args){
Son s = new Son();
s.show();
s.method();
}
}
输出结果:
Show Son
method Son
1.6 方法重写
1.6.1 概述
子类中出现了和父类中一模一样的方法声明,也被称为方法覆盖,方法复写
1.6.2 使用特点
- 如果方法名不同,就调用对应的方法
- 如果方法名相同,最终使用的是子类自己的
1.6.3 注意事项
- 父类中私有方法不能被重写
- 子类重写父类方法时,访问权限不能更低
- 父类静态方法,子类也必须通过静态方法进行重写(这个算不上方法重写)
两个思考题:
1. Override和Overload的区别?Overload是否可以改变返回值类型?答:
- 方法重写——在子类中出现和父类一模一样的方法声明的现象
- 方法重载——同一个类中出现的方法名相同,参数列表不同的现象,能改变返回值,因为与返回值类型无关
2. this和super的区别和各自的作用?
- this——代表当前类的对象的引用
- super——代表父类存储空间的标识
作用见1.5.3节的表格
1.7 final关键字
1.7.1 概述
final关键字是最终的意思,可以修饰类,成员变量,成员方法
1.7.2 特点
- 修饰类——被修饰的类为最终类,最终类不能被继承(不能有子类)
- 修饰方法——被final修饰的方法不能被重写(覆盖)
- 修饰变量——被修饰的变量不能被重新赋值,其实就是常量(为自定义常量,之前学的为字面值常量)
1.7.3 两个问题
1. final修饰局部变量的问题
- 在方法内部,该变量不可以被改变
- 在方法声明上,参数类型
- 为基本类型,值不能被改变,
- 为引用类型,地址值不能被改变
2. final修饰变量的初始化时机
- final修饰的变量只能赋值一次
- 在对象构造完毕前即可
2. 多态
2.1 多态的概述
某一个对象(事物),在不同时刻表现出来的不同状态
举例:水的不同状态
2.2 多态的前提和体现
前提:
- 要有继承关系
- 要有方法重写
- 要有父类引用指向子类对象( 父 f = new 子(); )
2.3 多态中的成员访问特点
2.3.1 成员变量
编译看左边,运行看左边
2.3.2 构造方法
创建子类对象的时候,访问父类的构造方法,对父类的数据进行初始化
2.3.3 成员方法
编译看左边,运行看右边(方法重写覆盖了)
2.3.4 静态方法
编译看左边,运行看左边(静态随着类的加载而加载)
2.4 多态的利弊
利:
- 提高了代码的维护性(由继承保证)
- 提高了代码的扩展性(由多态保证)
弊:
- 不能使用子类中的特有功能
2.5 多态中的转型问题
要想使用子类中的特有功能,有两种方法:
- 创建子类对象调用方法即可(占内存)
- 将父类的引用强制转换为子类的引用(向下转型)——Zi z = (Zi) f;
2.5.1 向上转型
Fu f = new Zi();//左边为大类,右边为小类
2.5.2 向下转型
Zi z = (Zi) f;——要求该f必须是能转换为Zi的
2.6 多态的练习
案例:猫狗案例
//猫狗案例
class Animal{
public void eat(){
System.out.println("吃饭");
}
}
//狗类继承动物类
class Dog extends Animal{
public void eat(){
System.out.println("狗吃肉");
}
public void lookDoor(){
System.out.println("狗看门");
}
}
//猫继承动物类
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void playGame(){
System.out.println("猫玩捉迷藏");
}
}
//测试类
class DuoTaiTest{
public static void main(String[] args){
//Animal类中的引用a指向Dog类,定义为狗
Animal a = new Dog();
a.eat();
System.out.println("-------------");
//创建Dog中的对象d,将a强制转换使用Dog类中的特有功能,还原成狗
Dog d = (Dog) a;
d.eat();
d.lookDoor();
System.out.println("-------------");
//定义为猫
a = new Cat();
a.eat();
System.out.println("-------------");
//还原为猫
Cat c = (Cat)a;
c.eat();
c.playGame();
}
}
输出:
狗吃肉
-------------
狗吃肉
狗看门
-------------
猫吃鱼
-------------
猫吃鱼
猫玩捉迷藏
3. 抽象类
3.1 抽象类的概述
把多个共性的东西提取到一个类中,这是继承的做法。但是,这多个共性的东西,在有些时候,方法声明一样,但是方法体。也就是说,方法声明一样,但是每个具体的对象在具体实现的时候内容不一样。所以,我们在定义这些共性的方法的时候,就不能给出具体的方法体。
而一个没有具体的方法体的方法是抽象的方法(注意没有方法体和空方法体的区别)。
在一个类中如果有抽象方法,该类必须定义为抽象类。
3.2 抽象类的特点
- 抽象类和抽象方法必须用abstract关键字修饰
格式:
abstract class 类名 {}
public abstract void eat();
- 抽象类不一定有抽象方法,有抽象方法的类必须是抽象类
- 抽象类不能实例化,因为它不是具体的。按照多态的方式,由具体的子类实例化。其实这也是多态的一种,抽象类多态
- 抽象类的子类,要么是抽象类(不重写抽象方法),要么重写抽象类中的所有抽象方法(此时子类为具体类)
3.3 抽象类的成员特点
成员变量
既可以是变量,又可以是常量
构造方法
有构造方法,用于子类访问父类数据的初始化
成员方法
既可以是抽象方法,也可以是非抽象方法
抽象方法——强制要求子类做的事
非抽象方法——子类继承的事情,提高代码复用性
4. 接口
4.1 概述
为了体现事物功能的扩展性,Java中就提供了接口来定义这些额外功能,并不给出具体实现
4.2 接口的特点
- 接口用关键字interface修饰
格式:interface 接口名 {}
- 类实现接口用implements修饰
格式:class 类名 implements 接口名 {}
- 接口不能实例化(用多态的方式来实现实例化)
多态的三种方式:
- 具体类多态(几乎没有)
- 抽象类多态
- 接口多态(最常见)
- 接口的子类
- 是一个抽象类,意义不大
- 是一个具体类,这个类必须重写接口中的所有抽象方法
案例1:
/*
接口特点:
接口用关键字interface表示
格式:interface 接口名 {}
类实现接口用implements表示
格式:class 类名 implements 接口名 {}
*/
//猫狗案例
//定义动物培训接口
interface AnimalTrain {
//定义抽象类,无方法体
public abstract void jump();
}
//抽象类实现接口,可以,但是意义不大
abstract class Dog implements AnimalTrain {
}
//具体类实现接口
class Cat implements AnimalTrain {
//重写方法
public void jump(){
System.out.println("小猫钓鱼");
}
}
class InterfaceDemo {
public static void main(String[] args){
/*
在类AnimalTrain中创建对象at报错,因为AnimalTrain是抽象类,无法实例化
AnimalTrain at = new AnimalTrain();
at.jump();
*/
AnimalTrain at = new Cat();
at.jump();
}
}
4.3 接口的成员特点
成员变量
- 接口中的变量默认为常量,且是静态的
- 有默认修饰符:public static final
- 默认修饰符建议自己手动给出
构造方法
- 接口没有构造方法,因为接口主要功能为扩展,没具体存在
- 其实现接口的具体类有构造方法,默认继承于Object类
- 所有的类都默认继承自一个类:Object
成员方法
- 只能是抽象方法
- 默认修饰符:public abstract
- 默认修饰符建议自己手动给出
案例2:
//接口的成员特点
//定义Inter接口
interface Inter {
//定义成员变量
public int num = 10;
public final int num1 = 20;
//给接口写构造方法,编译报错
//因为接口没有构造方法
//public Inter(){}//错误: 需要<标识符>
//写成员方法
//public void show(){}//错误: 接口抽象方法不能带有主体
abstract void show();//默认权限为public
}
/*
//具体类InterImpl实现接口
//开发中,接口名+Impl这种格式是接口的实现类格式
class InterImpl implements Inter {
//给实现类写构造方法,编译不报错。因为所有的类都默认继承自一个类:Object
public InterImpl(){
//访问父类构造方法
super();
}
}
*/
//具体类InterImpl实现接口(默认继承于Object类)
class InterImpl extends Object implements Inter {
//给实现类写构造方法,编译不报错
//因为所有的类都默认继承自一个类:Object
public InterImpl(){
//访问父类构造方法
super();
}
//具体类中的成员方法
public void show(){}//不写public报错:正在尝试分配更低的访问权限; 以前为public
}
//测试类
class InterfaceDemo1 {
public static void main(String[] args) {
//创建对象i
Inter i = new InterImpl();
//多态中访问成员变量是访问的左边(只有成员方法是访问的右边,因为方法重写)
System.out.println(i.num);//输出10
System.out.println(i.num1);//输出20
//i.num = 100;// 错误: 无法为最终变量num分配值
//i.num1 = 200;// 错误: 无法为最终变量num1分配值
System.out.println(Inter.num);//通过接口名字访问变量,输出10
System.out.println(Inter.num1);//通过接口名字访问变量,输出20
}
}
4.2 类与类、类与接口、接口与接口的关系
4.2.1 类与类的关系
继承关系(extends):只能单继承,可以多层继承
4.2.2 类与接口的关系
实现关系(interface定义接口,implements实现接口):
可以单实现,也可以多实现(接口之间用逗号隔开即可,调用的时候注意方法与接口对应)
还可以在继承一个类的同时实现多个接口(联想默认继承类Object)
4.2.3 接口与接口的关系
继承关系,可以单继承,也可以多继承
抽象类和接口的区别:
A:成员区别
抽象类:
成员变量:可以是常量,也可以是变量
构造方法:有
成员方法:可以使抽象,也可以是非抽象
接口:
成员变量:只能是常量
成员方法:只能是抽象的
B:关系区别
类与类:
继承,只能单继承,不能多继承
类与接口:
实现,可以单实现,也可以多实现
接口与接口:
继承,可以单继承,也可以多继承
C:设计理念区别
抽象类:
被继承体现的是is a的关系
抽象类中定义的是该继承体系中的共性功能
接口:
被继承体现的是like a的关系
接口中定义的是该继承体系中的扩展功能
4.3 形式参数和返回值的问题
形式参数:
- 基本类型
- 引用类型(类、抽象类、接口)
案例1:类名作为形参,需要的是该类的对象
class Student{
public void study(){
System.out.println("好好学习,天天向上");
}
}
class StudentDemo{
public void method(Student s){
s.study();
}
}
//测试类
class StudentTest{
public static void main(String[] args){
//测试Student中的study方法,在Student类中创建对象s
Student s = new Student();
//通过该类中的对象s调用study方法
s.study();
//测试StudentDemo中的method方法,在StudentDemo类中创建对象ss
StudentDemo sd = new StudentDemo();
//在Student类中创建对象ss作为调用method方法时的参数
Student ss = new Student();
//调用该类中的method方法,不要忘记传参
sd.method(ss);
//匿名对象用法
new StudentDemo().method(new Student());
}
}
案例2:抽象类作为形参,需要的是该抽象类的子类对象
//抽象类作为形参
//抽象类(抽象类的格式)
abstract class Person{
public abstract void study();//抽象方法没有具体的方法体
}
class PersonDemo{
public void method(Person p){
p.study();
}
}
//定义一个具体的学生类继承自抽象类Person
class Student extends Person{
//重写抽象类的study方法
public void study(){
System.out.println("好好学习,天天向上");
}
}
//测试类
class PersonTest{
public static void main(String[] args){
//使用PersonDemo类中的method方法,在该类中创建对象pd
PersonDemo pd = new PersonDemo();
//抽象类Person实例化
//Person p = new Person();//错误: Person是抽象的; 无法实例化
//间接实例化
Person p = new Student();
pd.method(p);
}
}
案例3:接口作为形参,需要的是该接口的实现类对象
//接口作为形参
//定义一个兴趣的接口
interface Hobby{
//接口的成员方法只能是抽象方法
public abstract void hobby();
}
class HobbyDemo{
public void method(Hobby h){
h.hobby();
}
}
//定义一个具体类来实现接口
class Teacher implements Hobby{
//方法重写
public void hobby(){
System.out.println("卡卡西爱亲热天堂,猫咪老师爱烧酒");
}
}
//测试类
class HobbyTest{
public static void main(String[] args){
//测试HobbyDemo类中的method方法,先创建对象
HobbyDemo ht = new HobbyDemo();
Hobby h = new Teacher();
ht.method(h);
}
}
返回值类型:
- 基本类型
- 引用类型(类、抽象类、接口)
案例1:返回值类型为类的时候,返回的其实是该类的对象
class Student {
public void study() {
System.out.println("Good Good Study,Day Day Up");
}
}
class StudentDemo {
//返回一个Student类型的值,其实是返回Student类的对象
public Student getStudent() {
//创建Student类的对象s并返回s
//Student s = new Student();
//return s;
//用匿名对象简化
return new Student();
}
}
class StudentTest2 {
public static void main(String[] args) {
//调用Student类中的study()方法,不要直接创建Student的对象
//使用StudentDemo创建对象sd
StudentDemo sd = new StudentDemo();
Student s = sd.getStudent();
s.study();
}
}
案例2:返回值为抽象类的时候,返回的其实是该抽象类的具体类的对象
//定义一个抽象类
abstract class Person {
public abstract void study();
}
class PersonDemo {
//返回的是抽象类Person的子类Student的对象
public Person getPerson() {
//Person p = new Student();
//return p;
//匿名对象简化
return new Student();
}
}
//定义抽象类的子类
class Student extends Person {
//方法重写
public void study() {
System.out.println("Good Good Study,Day Day Up");
}
}
class PersonTest2 {
public static void main(String[] args) {
//测试Person类中的study()方法
PersonDemo pd = new PersonDemo();
Person p = pd.getPerson();
p.study();
}
}
案例3:返回值位接口的时候,返回的其实是该接口的实现类的对象
略
链式编程
对象.方法1().方法2().......方法n();
这种用法:其实在方法1()调用完毕后,应该返回一个对象;
方法2()调用完毕后,应该返回一个对象
方法n()调用完毕后,可能是对象,也可以不是对象匿名对象中,对象是作为参数传递
5. 包和导包
5.1 包的概述
5.1.1 概念
其实就是文件夹
5.1.2 作用
- 区分同名的类
- 对类进行分类管理
- 按照功能分(增、删、查、改)
- 按照模块分
5.2 包的定义格式及注意事项
5.2.1 包的定义格式
package 包名;
多级包用.分开即可
举例:
package cn.itcast;
5.2.2 注意事项
- package语句必须是程序的第一条可执行的代码
- package语句在一个java文件中只能有一个
- 如果没有package,默认表示无包名
5.3 带包的类的编译和运行
手动式
- javac编译当前类文件。
- 手动建立包对应的文件夹。
- 把1步骤的class文件放到2步骤的最终文件夹下。
- 通过java命令执行 (eg:需要带包名称的执行 java cn.itcast.HelloWorld )
自动式
- javac编译的时候带上-d即可 javac -d . HelloWorld.java
- 通过java命令执行,和手动式步骤4一样
5.4 导包
5.4.1 概述
每次使用不同包下的类的时候,都需要加包的全路径。java就提供了导包的功能
5.4.2 导包格式
import 包名;
这种方式导入是到类的名称。 虽然可以最后写*,但是不建议,建议用谁导谁
思考题:
package,import,class有没有顺序关系?
答:有,package>import>class
- package只能有一个
- import能有很多个
- class能有多个,以后建议只有一个
6. 权限修饰符
6.1 四种权限修饰符
public | protected | 默认 | private | |
同一类中访问 | √ | √ | √ | √ |
同一包中访问子类和其他类 | √ | √ | √ | |
不同包中访问子类 | √ | √ | ||
不同包中访问其他类 | √ |
6.2 类及其组成常用的修饰符
修饰符的分类:
- 权限修饰符:public、protected、默认权限修饰符、private
- 状态修饰符:static、final
- 抽象修饰符:abstract
类 的常用修饰符:
- 权限修饰符:public(最常用)、默认权限修饰符,protected和private不能修饰类
- 状态修饰符:final
- 抽象修饰符:abstract
成员变量的常用修饰符:
- 权限修饰符:private(最常用),默认的,protected,public
- 状态修饰符:static,final
构造方法的常用修饰符:
- 权限修饰符:private,默认的,protected,public(最常用)
成员方法的常用修饰符:
- 权限修饰符:private,默认的,protected,public(最常用)
- 状态修饰符:static,final
- 抽象修饰符:abstract
除此以外的组合规则:
- 成员变量:public static final
- 成员方法:
- public static
- public abstract
- public final
7. 内部类
7.1 概述
把类定义在其他类的内部,这个类就被称为内部类
7.2 内部类的访问特点
- 内部类可以直接访问外部类的成员,包括私有
- 外部类要访问内部类的成员,必须创建对象
7.3 内部类的分类
根据内部类在类中定义的位置不同,可以分为两大类:
- 成员内部类(内部类在成员位置)
- 局部内部类(内部类在成员方法内部,即局部位置)
7.4 成员内部类
案例1:成员内部类访问
要点:
创建对象格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
eg: Outer.Inner oi = new Outer().new Inner();
class Outer {
private int num = 10;
class Inner {
public void show(){
System.out.println(num);
}
}
}
class InnerClassDemo {
public static void main(String[] args){
//访问成员内部类Inner的show()方法
//创建对象格式:外部类名.内部类名 对象名 = 外部类对象.内部类对象;
Outer.Inner oi = new Outer().new Inner();
oi.show();
}
}
成员内部类的常用修饰符
private 为了保证数据的安全性
static 为了让数据访问更方便 (内部类用静态修饰,此时内部类可以看成外部类的成员)
- 静态的内部类访问的外部类数据必须是静态修饰的
- 内部类被静态修饰后的方法 可以是:
- 静态方法
- 非静态方法
7.5 局部内部类
案例:局部内部类访问
- 可以直接访问外部类的成员
- 可以创建内部类对象,通过对象调用内部类方法,来使用局部内部类功能
class Outer {
//定义成员变量
private int num = 10;
//定义成员方法
public void method(){
//定义局部内部类
class Inner {
public void show(){
System.out.println(num);
}
}
//在局部位置创建内部类对象i调用内部类show方法
Inner i = new Inner();
i.show();
}
}
class InnerClassDemo {
public static void main(String[] args){
//创建外部类对象o,通过o访问外部类成员方法method
Outer o = new Outer();
o.method();
}
}
局部内部类访问局部变量 必须用final修饰
原因:
因为局部变量是随着方法的调用二调用,随着方法调用完毕而消失
这时,局部对象并没有立马从堆内存中消失,还要使用那个变量
为了让数据还能继续被使用,就用fianl修饰,这样,在堆内存里面存储的其实是一个常量值
7.6 匿名内部类
匿名内部类其实就是内部类的简化写法
前提:存在一个类或者接口
- 这里的类可以是具体类也可以是抽象类
格式:
new 类名或者接口名() {
重写方法;
}
本质: 是一个继承了该类或者实现了该接口的子类匿名对象