概述
Java SE基础(十一)面向对象2
- 概述
- 类的继承
- 好处与弊端
- Java中继承的特点
- Java继承中成员变量与成员方法的特点
- 方法重写
- 类的多态
- 好处与弊端
- 向上转型与向下转型
- 判断是否为同一类
- 对象中的成员访问特点
- 尾言
概述
之前有写过类的封装。
类的封装
今天再写一篇类的继承与多态,类的三大特性就记录完毕了。。。
类的继承
先举个栗子。。。
public class Student {
public void study() {
System.out.println("学生要学习");
}
private String name;
private int age;
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Teacher {
public void teach(){
System.out.println("老师要教学");
}
private String name;
private int age;
public Teacher() {
}
public Teacher(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Run {
public static void main(String[] args) {
Teacher teacher = new Teacher();
teacher.sleep();
teacher.eat();
teacher.teach();
System.out.println("--*************--");
Student student = new Student();
student.sleep();
student.eat();
student.study();
}
}
显然出现了非常多的重复代码。复制粘贴的方式表面上可行,但代码的复用性与可维护性都很差。。。为此,当多个类中存在相同的属性和行为时,需要将共同的属性和行为单独抽取到一个类,多个类便无需重复照搬代码来定义属性及行为,只需要继承父类即可获得很多特性,这种关系就是继承。
public class 类A extends 类B{
}
使用到关键字extends。
其中,类A叫子类(派生类),类B叫父类(基类,超类)。
对上述代码进行抽取。
public class Person {
private String name;
private int age;
public Person() {
}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void eat(){
System.out.println("人要吃饭");
}
public void sleep(){
System.out.println("人要睡觉");
}
}
子类中只需要:
public class Student extends Person {
public void study() {
System.out.println("学生要学习");
}
}
public class Teacher extends Person{
public void teach(){
System.out.println("老师要教学");
}
}
瞬间清爽了很多。
好处与弊端
好处:
提高了代码的复用性。
提高 了代码的可维护性。
让类与类直接产生关系,是多态的前提。
弊端:
类与类之间之间产生关系,就会让类与类的耦合度提高。
开发中追求的是高内聚(类自己独立完成某些事情的能力)、低耦合(类与类之间的关系)。
Java中继承的特点
先看一段代码。
public class Grandfather {
public Grandfather() {
System.out.println("这是爷爷");
}
public void GrandFatherSay(){
System.out.println("我是爷爷");
}
public void Say(){
System.out.println("爷爷说:");
}
}
public class Father extends Grandfather {
public Father() {
System.out.println("这是爸爸");
}
public void FatherSay(){
System.out.println("我是爸爸");
}
public void Say(){
System.out.println("爸爸说:");
}
}
public class Son extends Father{
public Son() {
System.out.println("这是儿子");
}
public void SonSay(){
System.out.println("我是儿子");
}
public void Say(){
System.out.println("儿子说:");
}
}
public class Run {
public static void main(String[] args) {
Grandfather grandfather = new Grandfather();
Father father = new Father();
Son son = new Son();
System.out.println("--*********--");
grandfather.GrandFatherSay();
father.FatherSay();
son.SonSay();
System.out.println("--*********--");
grandfather.Say();
father.Say();
son.Say();
}
}
运行后:
这是爷爷
这是爷爷
这是爸爸
这是爷爷
这是爸爸
这是儿子
--*********--
我是爷爷
我是爸爸
我是儿子
--*********--
爷爷说:
爸爸说:
儿子说:
Process finished with exit code 0
这段代码可以说明很多问题。
首先,通过继承,类的构造方法中的所有方法显然都被继承下来了。子类中可以写与父类不同的成员方法。当然也可以写同名但是功能不同的方法(方法的重写,实现多态的前提)。
Java不像C++那样可以多继承,Java的类与类之间只能单继承(但是可以通过接口实现多继承的功能)。Java的类与类之间可以多层继承。。。
Java继承中成员变量与成员方法的特点
先看代码。
public class Father {
public int age ;
public Father(){
this.age = 40;
}
public Father(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void show(){
System.out.println("Father~age = " + age);
}
}
public class Son extends Father {
public int age = 10;
public Son(){
super();//无参访问可以什么都不写,第一行会默认有super();
}
public Son(int age){
super(age);
}
public void show(){
int age = 20;
System.out.println("Son~age = " + age);//不修饰时优先就近访问局部变量/形参
System.out.println("this.age = " + this.age);//有this修饰时访问本类的属性
System.out.println("super.age = " + super.age);//使用super可以访问父类的属性
super.show();//子类中可以使用super来调用父类的方法
}
// public void Age(int age){
// super(age);
// } //必须在构造函数中才能使用super调用父类的构造方法
}
public class Run {
public static void main(String[] args) {
Son son1 = new Son();
son1.show();
//如果子类中没有该方法,就去父类中找相同名称的方法
son1.setAge(50);
System.out.println("son1.getAge() = " + son1.getAge());
Son son2 = new Son(60);
System.out.println("son2.getAge() = " + son2.getAge());
}
}
执行后:
Son~age = 20
this.age = 10
super.age = 40
Father~age = 40
son1.getAge() = 50
son2.getAge() = 60
Process finished with exit code 0
从中可以看出,Java中变量遵循就近原则,未指明时优先使用局部变量,使用this后指向当前对象的变量,使用super可以指向父类的变量。
通过super.父类的成员变量名
可以访问父类的成员变量,使用 super(有参或无参)
可以调用父类的构造方法(调用父类构造方法时,super()
必须在子类构造方法的第一行,构造方法不写内容时默认调用的也是这个),使用 super.成员方法名
可以调用父类的普通方法。
子类的构造方法中必须第一句是super(有参或无参)
,目的是子类对象访问父类数据前,确保父类的数据已经初始化完毕。如果父类没有写构造方法或漏写,会导致后续子类调用比较繁琐,最好是使用idea自动生成有参、无参构造方法及所有参数的get与set方法。
子类的构造方法只能选父类的。。。这样可以避免出错。
同理,继承中成员方法也遵循就近原则。子类没有就去父类查找,找不到则编译不过(报错)。
方法重写
重写和重载有本质区别!!!
重载是在一个类里面,方法名字相同,而参数不同。返回类型可以相同也可以不同。每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
方法重写是父类与子类有一模一样的方法,外壳相同(参数列表、返回值、名称等),功能略有不同。
子类重写父类方法时,访问权限不能更低。。。
Public 访问权限最高,公共的访问权限
Protected 受保护的访问。
默认的访问权限 (不写修饰符时)
Private 访问权限最低,只能本类访问。
可以使用@Override
来确认重写时方法名称是否正确。
子类中重写时方法名写错会报错。。。
类的多态
多态是指同一事物(对象)在不同时刻表现出不同的状态。
类的多态实现起来需要父类与子类之间有继承关系,要有方法的重载,还需要父类引用指向子类对象。
举个栗子:
public class Animal {
public void eat(){
System.out.println("动物需要吃饭");
}
}
public class Cat extends Animal {
@Override
public void eat(){
System.out.println("猫会吃鱼");
}
public void play(){
System.out.println("猫会玩毛线球");
}
}
public class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗会吃肉");
}
}
public class Run {
public static void main(String[] args) {
//向上转型任何时候都没有问题
Animal animal1 = new Cat();
Animal animal2 = new Dog();
System.out.println("animal1 = " + animal1);
System.out.println("animal2 = " + animal2);
animal1.eat();
animal2.eat();
//animal1.play();//报错。。。向上转型时不能使用子类的特有方法
Cat cat1 = (Cat)animal1;
Dog dog1 = (Dog)animal2;
System.out.println("cat1 = " + cat1);
System.out.println("dog1 = " + dog1);//指向相同地址,说明内存中的数据没变
//Dog dog2 = (Cat)animal1;//这种情况下会报错。。。向下转型只能转回原来的子类
cat1.play();//此时可以使用Cat类特有的方法
}
}
运行后:
animal1 = Demo6in2021_3_29.Cat@1b6d3586
animal2 = Demo6in2021_3_29.Dog@4554617c
猫会吃鱼
狗会吃肉
cat1 = Demo6in2021_3_29.Cat@1b6d3586
dog1 = Demo6in2021_3_29.Dog@4554617c
猫会玩毛线球
Process finished with exit code 0
向下转型时对象类型不对会报错。
这段代码能说明很多问题。
向上转型与向下转型时对象指向的堆内存地址是相同的!!!说明在底层数据量是相同的。。。
好处与弊端
好处:
提高了程序的扩展性。同时,开发时也可以少记很多方法名称(这点与方法重载很像)。
弊端:
父类只能使用自己已有的方法(这样已经实现了多态),不再能够使用子类的特有方法。但是这个问题可以通过向下转型来解决。
向上转型与向下转型
使用 父类 对象名 = new 子类的构造方法;
的格式即可使用向上转型,用于实现类的多态。
需要使用类的特有方法时,使用 子类 对象名 = (子类) 对象名;
的格式即可向下转型。
这一点很像基本数据类型中的强制类型转换。
当向下转换的时候,类与原对象的类不同时,会报错且编译不过。。。为了防止将对象作为形参传入方法造成报错的这种情况,需要在向下转型前先判断是否为同一类。
判断是否为同一类
public class Animal {
public void eat(){
System.out.println("动物要吃饭");
}
}
public class Cat extends Animal {
public void play(){
System.out.println("猫会玩毛线球");
}
@Override
public void eat(){
System.out.println("猫要吃鱼");
}
}
public class Dog extends Animal {
public void swim(){
System.out.println("狗会游泳");
}
@Override
public void eat(){
System.out.println("狗要吃肉");
}
}
public class Run {
public static void main(String[] args) {
Animal animal1 = new Cat();
Animal animal2 = new Dog();
animal1.eat();
animal2.eat();
System.out.println("--*****************--");
actor(animal1);
actor(animal2);
}
public static void actor(Animal animal) {
animal.eat();
if (animal instanceof Cat) {//利用instanceof判断是否为同一个类,防止出错
((Cat) animal).play();
}
if (animal instanceof Dog) {//利用instanceof判断是否为同一个类,防止出错
((Dog) animal).swim();
}
}
}
运行后:
猫要吃鱼
狗要吃肉
--*****************--
猫要吃鱼
猫会玩毛线球
狗要吃肉
狗会游泳
Process finished with exit code 0
利用instanceof判断是否为同一个类,防止出错。
对象中的成员访问特点
先看段代码:
public class Person {
public int age = 30;
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public Person() {
//this.age = 30;
}
public Person(int age) {
this.age = age;
}
public void eat(){
System.out.println("人要吃饭");
}
}
public class Student extends Person {
public int age = 20;
public Student() {
//this.age = 20;
super();
}
public Student(int age) {
super(age);
}
public void eat(){
System.out.println("学生爱吃饭");
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
public class Run {
public static void main(String[] args) {
Person person1 = new Student();
Student student1 = new Student();
System.out.println("student1 = " + student1);
System.out.println("person1 = " + person1);
System.out.println("System.identityHashCode(person1.age) = " + System.identityHashCode(person1.age));
System.out.println("System.identityHashCode(person1.getAge()) = " + System.identityHashCode(person1.getAge()));
System.out.println("person1.getAge() = " + person1.getAge());//=20?????如果子类不重写get函数,对象年龄也是30
System.out.println("student1.getAge() = " + student1.getAge());//如果子类不重写get函数,学生年龄也是30
System.out.println("person1.age = " + person1.age);//=30?????
System.out.println("student1.age = " + student1.age);//=20
System.out.println("--**************--");
Student student2 = (Student)person1;
System.out.println("student2 = " + student2);
System.out.println("student2.getAge() = " + student2.getAge());
person1.eat();
student1.eat();
}
}
运行后:
student1 = Demo7in2021_3_29.Student@1b6d3586
person1 = Demo7in2021_3_29.Student@4554617c
System.identityHashCode(person1.age) = 1956725890
System.identityHashCode(person1.getAge()) = 356573597
person1.getAge() = 20
student1.getAge() = 20
person1.age = 30
student1.age = 20
--**************--
student2 = Demo7in2021_3_29.Student@4554617c
student2.getAge() = 20
学生爱吃饭
学生爱吃饭
Process finished with exit code 0
笔者很惊喜地发现,
person1.getAge() = 20,
person1.age = 30
他们居然不一样!!!
利用System.identityHashCode()
看到,person1.age
与person1.getAge()
指向了不同的堆内存地址。这是因为子类中重写了父类的getAge方法,导致了异常。。。
将子类中的
public int getAge() {
return this.age;
}
屏蔽掉以后运行:
student1 = Demo7in2021_3_29.Student@1b6d3586
person1 = Demo7in2021_3_29.Student@4554617c
System.identityHashCode(person1.age) = 1956725890
System.identityHashCode(person1.getAge()) = 1956725890
person1.getAge() = 30
student1.getAge() = 30
person1.age = 30
student1.age = 20
--**************--
student2 = Demo7in2021_3_29.Student@4554617c
student2.getAge() = 30
学生爱吃饭
学生爱吃饭
Process finished with exit code 0
这时利用System.identityHashCode()
看到,person1.age
与person1.getAge()
指向了相同的堆内存地址,表明此时输出的30即为person1对象的age数值,而20为student1的age数值。
从中可以看出,
成员变量:编译看左边,运行看左边(按左边的类型来赋初值及访问变量值)。
成员方法:编译看左边,运行看右边(按右边的类型来调用类的成员方法)。
尾言
类的三大特性就暂时告一段落了。。。
最后
以上就是俏皮盼望为你收集整理的Java SE基础(十一)面向对象2 类的继承与多态概述类的继承类的多态尾言的全部内容,希望文章能够帮你解决Java SE基础(十一)面向对象2 类的继承与多态概述类的继承类的多态尾言所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复