此博客为看B老师Java视频的部分记录
- 面向对象三大特征之继承
-
好处: 提高了代码的复用性。让类和类产生了关系,给另一个特征 多态 提供了前提
-
弊端:打破封装性
-
使用 extends 关键字,如 class Student extends Person
学生继承了Person, Person是父类(基类,超类),学生是子类 -
Java中继承的体现:
Java允许单继承,不直接支持多继承,将多继承进行其他方式的体现
单继承:一个子类只能有一个父类
多继承:一个子类可以有多个父类
Java还支持多重继承,形成继承体系,如:
C也可以调用A中东西 -
补充
- 子类不可以直接访问父类中private内容
- 若子父类中定义了一模一样的成员变量,都存在于子类对象中,直接调用的是子类的变量。要在子类中直接访问同名的父类中的变量,通过super关键字来完成
(开发一般用不到,父类定义好属性子类可以直接使用) - super与this用法相似
super : 代表队是父类的内存空间
this : 代表的是本类的对象的引用
示例代码
class Fu
{
void show1()
{
System.out.println("Fu show1 run");
}
}
class Zi extends Fu
{
void show2()
{
System.out.println("zi show2 run");
}
}
class ExtendsDemo1
{
public static void main(String[] args)
{
Zi z = new Zi();
z.show1();
z.show2();
}
}
运行结果
- 字符类中定义了一模一样的函数,运行结果是子类函数在运行。这种情况在子父类中,是函数的另一种特性:over日的(重写、覆盖、复写)
- 示例
需求:原来的电话只能显示号码,新电话在显示号码基础上可以显示名称和头像
分析:新电话继承原来的电话,新电话不需要重新定义来显功能,如果子类来显
功能内容不同,则需要保留来显功能,定义子类的内容即可(重写的应用)
代码
class Phone
{
// 打电话
void call(){}
// 来电显示
void show()
{
System.out.println("电话号码");
}
}
class NewPhone extends Phone
{
void show()
{
super.show(); // 如果还需要父类中原有的部分功能,可以通过super调用
System.out.println("姓名");
System.out.println("头像");
}
}
class OverridePhone
{
public static void main(String[] args)
{
NewPhone np = new NewPhone();
np.show();
}
}
运行结果
- 注意事项
- 子类覆盖父类。必须要保证子类权限大于或等于父类权限(如父类中为public,则子类必须为public才能保证子类覆盖父类)
- 静态覆盖静态。
- 写法注意必须一模一样:函数的返回值类型、函数名、参数列表都要一样
- 子父类中构造函数
- 特点:先执行了父类的构造函数,再执行子类中的构造函数。因为子类中的所有构造函数中的第一行都有一句隐式语句 super(); //默认调用的是父类中的空参数的构造函数
- 子类构造函数中为什么有一句隐式的super()?
因为子类会继承父类中的内容,所以子类在初始化时,必须先到父类中去执行父 类的初始化动作,才可以更方便的使用父类中的内容 - 当父类中没有空参数构造函数时,子类的构造函数必须同构显示的super语句指定要访问的父类中的构造函数
示例代码
class Fu
{
Fu()
{ // 显示初始化
System.out.println("fu constructor run A");
}
Fu(int x)
{
System.out.println("run constructor run B..."+x);
}
}
class Zi extends Fu
{
Zi()
{
System.out.println("zi constructor run C...");
}
Zi(int x)
{
System.out.println("zi constructor run D..."+x);
}
}
class ExtendsDemo
{
public static void main(String[] args)
{
new Zi();
new Zi(6);
}
}
运行结果
-
补充
- 如果子类的构造函数第一行写了this调用了本类其他构造函数,那么super调用父类的语句没有了,因为this()或者super()只能定义在构造函数的第一行,因为初始化动作要先执行
- 任何构造函数默认第一行都有隐式的super()。父类构造函数中也有super(),父类的super()调用的是所有对象的父类 : Object
- 总结:类中的构造函数默认第一行都有隐式的super()语句,访问父类的构造函数。所以父类的构造函数既可以给自己的对象初始化,也可以给自己的子类对象初始化。如果默认的隐式super的语句没有对应的构造函数,必须在构造函数中通过this或super的形式明确调用的构造函数
- this和super语句不可以在同一个构造函数中出现,因为它们都必须定义在第一行(因为初始化动作要先执行)
-
子类实例化过程的应用(也是super调用的应用)
只要使用父类的指定初始化动作,就在子类中通过super(参数列表)格式进行调用
class Person
{
private String name;
private int age;
public Person(String name, int age)
{
this.name = name;
this.age = age;
}
public void setName(String name)
{
this.name=name;
}
public String getName()
{
return name;
}
public void setAge(int age)
{
this.age = age;
}
public int getAge()
{
return age;
}
}
class Student extends Person
{
public Student(String name, int age)
{
super(name, age);
}
public void study()
{
System.out.println("study...");
}
}
class Worker extends Person
{
public Worker(String name, int age)
{
// 调用父类。使用父类的初始化动作
super(name, age);
}
}
class SuperApp
{
public static void main(String[] args)
{
new Student("张三", 18);
}
}
- final关键字
- 是一个修饰符,可以修饰类、方法、变量(成员变量、局部变量、静态变量)
- 特点:
- final修饰的类是一个最终类,不能再派生类
- final修饰的方法是最终方法,不可以重写(如果类中出现部分可以重写,部分不可以重写,可以通过让指定的方法最终化(final)即可)
- final修饰的变量是一个常量,只能被赋值一次
- 什么时候定义final常量?
当程序中一个数据使用时是固定不变的,这时为了增加阅读性,可以给该数据起个名字,这就是变量。为了保证这个变量的值不被修改,加上final修饰,这就是一个阅读性很强的常量。书写规范:被final修饰的常量名所有的字母都是大写的,如果由多个单词组成,单词之间通过 _ 连接
/*final*/ class Fu
{
/*final*/ void show()
{
}
}
class Zi extends Fu
{
// 重写
void show()
{
final int count=21;
System.out.println("xxx。。。"+count);
}
}
- 抽象类
- 在描述事物时,没有足够的信息描述一个事物,这时该事物就是抽象事物(如描述犬科时,发现了有些功能不具体,这些不具体的功能,需要在类中标识出来,通过Java中的关键字abstract(抽象))
- 特点
- 抽象类和抽象方法都需要被abstract修饰,抽象方法一定要定义在抽象类中
- 抽象类不可以创建实例,原因:调用抽象方法没有意义
- 只有覆盖了抽象类中所有的抽象方法后,其子类才可以实例化,否则该子类还是一个抽象类
- 抽象函数,需要abstract修饰,并以分号 ; 结束
abstract class Canine
{
abstract void roar(); // 抽象函数。需要abstract修饰,并以分号结束
}
- 补充
- 抽象类一定是父类,因为不断抽取而来
- 抽象类有构造函数,虽然不能给自己的对象初始化,但可以给子类对象初始化
- 抽象类和一般类的异同点
- 相同:
- 它们都是用来描述事物的
- 它们之中都可以定义属性和行为
- 不同:
- 一般类可以具体的描述事务,抽象类描述事物的信息不具体
- 抽象类中可以多定义一个成员:抽象函数
- 一般类能创建对象,抽象类不能创建对象
- 相同:
- 抽象类中可以不定义抽象方法,意义是不让该类创建对象
- abstract不可以与final、private、static共存
示例
/*
需求:公司中程序员有姓名、工号、薪水、工作内容,项目经理在此基础上多出一项奖金
对需求进行数据建模
*/
abstract class Employee
{
private String name;
private String id;
private double pay;
// 构造一个员工对象,一初始化就具备三个属性
public Employee(String name, String id, double pay)
{
this.name=name;
this.id=id;
this.pay=pay;
}
// 工作行为
public abstract void work();
}
// 具体的子类:程序员
class Programmer extends Employee
{
Programmer(String name, String id, double pay)
{
super(name, id, pay);
}
public void work()
{
System.out.println("code...");
}
}
// 具体的子类:经理
class Manage extends Employee
{
private double bonus;
Manage(String name, String id, double pay, double bonus)
{
super(name, id, pay);
this.bonus=bonus;
}
public void work()
{
System.out.println("manage...");
}
}
- 接口(interface)
- 当一个抽象类中的方法全是抽象的,可以通过接口来表示,通过interface定义
- 接口中的成员已被限定为固定的几种,如
- 定义变量:变量必须有固定的修饰符修饰,public static final,所以接口中的变量也称为常量
- 定义方法:public abstract ,接口中的成员都是公共的
interface Demo
{
// 定义变量
public static final int NUM=3;
// 定义方法
public abstract void show1();
public abstract void show2();
}
- 特点:
- 接口不可以创建对象
- 子类必须覆盖掉接口中所有的抽象方法后,子类才可以实例化。否则子类是一个抽象类
// 定义子类去覆盖接口中的方法。子类必须和接口产生关系,
//类与类的关系是继承,类与接口之间的关系是<实现>。通过关键字implements
class DemoImpl implements Demo //子类实现接口
{
// 重写接口中的方法
public void show1(){}
public void show2(){}
}
- 最重要体现:解决多继承的弊端(当多个父类中有相同功能时,子类调用会产生不确定性,核心原因在于多继承父类中功能有主体,而导致调用运行时,不确定运行哪个内容),将多继承这种机制在Java中通过多实现完成(因为接口中的功能都没有方法体,由子类来明确)
interface A
{
void show();
}
interface B
{
void show();
}
class C implements A,B //多实现。同时实现多个接口
{
public void show();
}
...
C c = new C();
c.show();
- 基于接口的扩展
接口的出现避免了单继承的局限性
父类中定义的事物的基本功能
接口中定义的事物的拓展功能
class Fu
{
public void show(){}
}
// 子类通过继承父类扩展功能,通过继承扩展的功能是子类应该具备的基础功能
// 如果子类想要继续扩展其它类中的功能,通过接口来完成
interface Inter
{
public void show1();
}
class Zi extends Fu implements Inter
{
public void show1(){}
}
- 扩展
- 类与类之间是继承关系(is a),类与接口之间是实现关系(like a), 接口与接口之间是继承关系,而且可以多继承
interface InterA
{
void show1();
}
interface InterAA
{
void show11();
}
interface InterB extends InterA, InterAA //接口的多继承
{
void show2();
}
class Test implements InterB
{
public void show1(){}
public void show2(){}
public void show11(){}
}
- 接口思想:
- 接口的出现对功能实现了扩展
- 接口的出现定义了规则
- 接口的出现降低了耦合性(解耦)
- 接口的出现,完成了解耦,说明有两方,一方在使用此规则,一方在实现此规则。
- 抽象类和接口简单区别
- 抽象类是描述事物的基本功能,可以定义非抽象的方法。接口中定义只能是抽象方法,负责功能的扩展。
- 类与类之间是继承关系(is a),类与接口之间是实现(like a)关系
- 多态
- 前提
- 必须有关系 :继承或实现
- 通常有重写操作
- 体现:
父类的引用或者接口的引用指向了自己的子类对象
Dog d = new Dog();// Dog对象的类型是Dog类型
Animal a = new Dog(); // Dog对象的类型右边是Dog类型,左边是Animal类型 - 好处
- 提高了程序的扩展性
- 局限
- 通过父类引用操作子类对象时,只能使用父类中已有的方法,不能操作子类特有的方法
-
但是父类型应用指向子对象时,这就是让子类对象进行了类型的提升(向上转型)Animal a = new Dog(); //Animal是父类型,new Dog()是子对象
- 向上转型好处:提高了扩展性,隐藏了子类型
- 向上转型弊端:不能使用子类型的特有方法
如果要想使用子类的特有方法,只有子类型可以用。可以向下转型,强制转换。
Animal a = new Dog();
a.eat();
Dog d = (Dog)a;//将a转型为Dog类型,向下转型
d.lookhome();
-
无论是向上还是向下转型,最终都是子类对象做着类型的变化
-
向下转型的注意事项:
Animal a = new Dog();
//Cat c = (Cat)a; 向下转型因为不明确具体子类对象类型,所以容易引发ClassCastException异常。所以为了避免这个问题,需要在向下转型前,使用instanceof关键字做类型的判断。if(a instanceof Cat) //a指向的对象的类型是Cat类型 { // 将a转型Cat类型 Cat c = (Cat)a; c.catchMouse(); } else if (a instanceof Dog) { Dog d = (Dog)a; d.lookHome(); }
-
转型总结
- 什么时候使用向上转型?
- 提高程序的扩展性,不管行子类型(子类型被隐藏)
- 判断方法:如果不需要用子类的特定方法,则使用向上转型,反之使用向下转型
- 什么时候需要向下转型?
- 需要使用子类型的特有方法时
- 但是一定要使用instanceof进行类型的判断,避免发生 ClassCastException
- 什么时候使用向上转型?
-
多态中成员变量:
- 当子父类中出现同名的成员变量时,多态调用该变量时:
- 编译时期:参考引用型变量所属类中是否有被调用的成员变量,若没有,则编译失败
- 运行时期:也是调用引用型变量所属的类中的成员变量
- 简记为:编译和运行参考等号左边所属类
- 当子父类中出现同名的成员变量时,多态调用该变量时:
-
多态中成员函数:
- 编译时期:参考左边,如果没有,编译失败
- 运行时期:参考右边对象所属的类
- 简记为:编译看左边,运行看右边
- 成员函数是动态绑定到对象上
-
多态中的静态函数:
- 编译和运行都看左边
- 静态函数是静态的绑定到类上
- 真正开发中静态方法是不会被多态调用的,因为静态方法不所属于对象,而是所属于类
-
结论:对于成员变量和静态函数,编译和运行都看左边;对于成员函数,编译看左边,运行看右边
- Object
- Object类是所有类的根类,定义了所有对象都具备的功能
- Object equals方法 , [! 以后判断对象是否相同,就需要覆盖equals方法 ]
class Person extends Object
{
private int age;
Person(int age)
{
this.age=age;
}
// 判断是否是同龄人,这个方法也是在比较两个person对象是否相等
// 因为Person类继承Object,所以它本身就具备着equals方法
// 但是equals方法判断的是地址,不是我们所需要的内容,需要对equals进行方法重写
public boolean equals(Object obj)
{
//建立Person自己的判断相同的依据,判断年龄是否相同
// return this.age==obj.age; // 所属类型object,object没有定义age,所以编译失败
//如果调用该方法的对象和传递进来的对象是同一个,就不需要转型,直接返回true,提高效率
if(this==obj)
return true;
// age是Person类型的属性,既然要用到子类型的内容,需要向下转型
if(!(obj instanceof Person))
throw new ClassCastException("类型错误!请改正");
Person p = (Person)obj;
return this.age==p.age;
}
}
class ObjectDemo
{
public static void main(String[] args)
{
Person p1 = new Person(12);
Person p2 = new Person(15);
System.out.println(p1.equals(p2));
}
}
- Object toString方法,重写toString方法,建立新的字符串表现形式
public String toString() { return "xxx" }
- 内部类
- 当A类中的内容要被B类直接访问,而且A类还需要创建B的对象,访问B的内容时,可以将B类定义到A类的内部,此时将B称之为内部类(内置类、嵌套类)
- 当描述事物时,事物的内部还有事物,这个内部的事物还在访问外部事物中的内容,这时就将这个事物通过内部类来描述
- 访问方式:内部类可以直接访问外部类中的所有成员,包含私有的;而外部类要想访问内部类中的成员,必须创建内部类的对象
示例代码
class Outer // 外部类
{
private int num=6;
class Inner // 内部类
{
void show()
{
System.out.println("num+"+num);
}
}
void method()
{
Inner in = new Inner();
in.show();
}
}
class InnerClassDemo
{
public static void main(String[] args)
{
Outer out = new Outer();
out.method();
}
}
运行结果
-
内部类在成员位置上的被访问方式
- 成员可以被指定的修饰符所修饰
- public:不多见,因为更多的时候内部类已经被封装到了外部类中,不对外直接提供
- static:非静态内部类中不允许定义静态成员,仅允许在非静态内部类中定义静态常量 static final。如果想在内部类中定义静态成员,必须内部类也要被静态修饰
- 成员可以被指定的修饰符所修饰
-
内部类被静态修饰后,随着外部类加载而加载,可以把一个静态内部类理解成一个外部类
-
直接访问Outer中的Inner内部类的非静态成员:
创建内部类的对象。内部类作为成员,应该先有外部类对象再有内部类对象Outer.Inner in = new Outer().new Inner(); in.show();
-
对静态内部类中的非静态成员进行调用:
因内部类是静态,所以不需要创建Outer的对象,直接创建内部类对象
Outer.Inner2 in = new Outer.Inner2();
in.show2();
- 访问静态内部类的静态成员:
因静态内部类已随外部类加载,且静态成员随着类的加载而加载,就不需要对象,直接使用类名调用即可
Outer.Inner2 .staticShow();
- 当静态方法访问内部类时,内部类必须是静态的
- 内部类能直接访问外部类中成员的原因:
因为内部类中持有了外部类的引用 <外部类.this> ;对于静态内部类不持有 <外部类.this>,而是直接使用 <外部类名.> - 内部类定义在局部时,只能访问被final修饰的局部变量,因为编译产生的class直接操作被修饰的局部变量代表的数值了。不能访问非最终局部变量,因为非最终局部变量生命周期太短
内部类定义在外部类局部位置示例:
class Outer
{
int num=3;
void method()
{
class Inner//局部内部类,不能被成员修饰符修饰(如public等)
{
void show()
{
System.out.println("Inner show run..."+num);
}
}
}
}
- 看API,发现类名或者接口名称中有 . 说明是内部类或者内部接口
- 内部类可以继承或者实现外部其他的类或者接口,好处:通过内部类的方式对类进行继承重写或者接口进行实现,通过公共的方式对其内部类对象进行访问,因为通常内部类很有可能被外部类封装其中,我们可以通过父类或者接口的方式访问到内部类对象
- 匿名内部类:其实就是一个带有内容的子类对象
- 格式: new 父类/接口()(子类的内容)
- 匿名内部类就是内部类的简化形式,但有前提:内部类必须要继承父类或者实现接口
- 匿名内部类中一般方法不要过多,阅读性会变差