文章目录
继承
概念
继承是面向对象编程(Object-Oriented Programming,
OOP)中的一个核心概念。它允许一个类(称为子类或派生类)从另一个类(称为父类或基类)继承属性和方法
为什么要学继承
在实际生活和开发中,事物与事物中存在一些关联,而用Java语言来描述时,代码就会有重复的
比如说
,学生和老师在下课后都要去吃饭,那么就要在老师和学生类中分别写同样的方法去描述吃饭这个行为
那我们通过继承(共性抽取),就可以提高代码的灵活性和复用性
,只需要再写一个人的类,让学生和老师类继承
extends关键字
通过extends关键字就可以实现继承了
语法格式
修饰符 class 子类 extends 父类{
}
注意事项
注意
:
- 子类会将父类中的成员变量或者成员方法继承到子类中了
- 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必要继承了
访问权限
子类中访问父类的成员变量
子类和父类不存在同名成员变量
遵循就近原则,子类有,优先在子类找,无再去父类找
子类中访问父类的成员方法
通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到
则访问,否则编译报错。
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用
方法适传递的参数选择合适的方法访问,如果没有则报错;
super关键字
如果子类中存在与父类中相同的成员时
那如何在子类中访问父类相同名称的成员呢?
可以通过
super关键字访问父类中的成员
引用形式和this关键字差不多
注意事项
只能在非静态的方法中使用
在子类方法中,访问父类的成员变量和方法
子类构造方法
要记住先有父再有子,在构造子类对象时,先调用父类的构造方法,需要子类把继承下来的成员构造完整,再调用子类的构造方法,将子类自己新增加的成员初始化完整
也就是在子类中需要显性的初始化
继承下来的父类构造方法
public class Dog extends Animal{
private String gendar;
public Dog(String name,int age,String gendar){
super(name,age);
this.gendar=gendar;
}
public Dog() {
}
public String getGendar() {
return gendar;
}
public void setGendar(String gendar) {
this.gendar = gendar;
}
}
public class Animal {
private String name;
private int age;
public Animal(String name,int age){
this.name=name;
this.age=age;
}
public Animal() {
}
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;
}
}
this和super的区别
this | super | |
---|---|---|
引用对象 | 引用当前对象的属性或方法 | 子类引用父类的属性或方法 |
继承关系上的执行顺序
public class Dog extends Animal {
static {
System.out.println("static:dog()....");
}
{
System.out.println("实例化代码dog()...");
}
public Dog(String name,int age){
super(name,age);
System.out.println("Dog:构造方法执行了...");
}
//静态的不能调用非静态的,不依赖对象的不能调用依赖对象的
public void eat(){
System.out.println(getName()+"正在吃饭....");
}
}
public class Animal {
private String name;
private int age;
static {
System.out.println("static:Animal()....");
}
{
System.out.println("Animal:实例化代码块()...");
}
public void eat();
public Animal() {
}
public Animal(String name, int age) {
this.name = name;
this.age = age;
System.out.println("Animal:构造方法执行了");
}
//这里省略了set和get方法
}
public class Test {
public static void main(String[] args) {
Animal animal=new Dog("小狗",18);
animal.eat();
}
}
方法的重写
重写注意事项:
- 父类方法不能用private和static修饰
子类的访问权限要大于父类
- 返回值类型子类必须小于等于父类
- 父类引用调用重写的方法
- 只有
被添加到虚方法表中
的方法才能被重写
重写与重载的区别
区别 | 重写override | 重载overload |
---|---|---|
定义 | 方法名,类型,返回值一致 | 方法名一致,返回值,参数类型不一致 |
权限修饰符 | 无要求 | 子类的访问权限必须比父类大或者一样 |
范围 | 发生在一个类中 | 发生在继承中 |
访问权限范围
public | private | default | protected | |
---|---|---|---|---|
同类 | √ | √ | √ | √ |
同包 | √ | √ | √ | |
不同包的非子类 | √ | |||
不同包子类 | √ | √ |
多态
概念
不同的对象对于某一个方法有不同的表现形式
多态的优势
方法中,使用父类作为参数,可以接收所有子类对象
提高了代码的复用性
实现条件
- 多态是在
继承
的基础之下而谈的- 子类必须对父类中的方法
进行重写
举个例子
写一个花的类,不同的花开花有不同的形态
public class Flower {
public Flower() {
}
public void open() {
System.out.println("我要开花!");
}
}
public class Rose extends Flower {
public Rose() {
}
@Override
public void open() {
System.out.println("我是带刺的玫瑰!!!");
}
}
public class sunFlower extends Flower {
public sunFlower() {
}
@Override
public void open() {
System.out.println("我要向阳而生");
}
}
public class Test {
public Test() {
}
public static void main(String[] args) {
Rose rose = new Rose();
sunFlower sunFlowers = new sunFlower();
//通过数组的方式遍历打印
Flower[] flowers = new Flower[]{rose, sunFlowers};
Flower[] flw = flowers;
int count = flowers.length;
for(int i = 0; i < count; ++i) {
Flower flower1 = flw[i];
flower1.open();
}
}
}
向上转型
创建一个子类对象,把他当成父类对象来使用
格式
父类类型 对象名 =new 子类类型()
动态绑定
动态绑定:也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能够确定具体调用那个类的方法。
也就是当程序编译时,通过父类引用子类重写的方法时,实际上调用了子类的方法
使用方式
直接赋值
Animal animal=new Dog("小狗",18);
作为方法的参数
public class Test {
public static void func(Animal animal){
animal.eat();
}
public static void main(String[] args) {
Dog dog=new Dog("小狗",18);
func(dog);
}
}
作为返回值
public class Test {
public static Animal func(){
Animal animal=new Dog("小狗",18);
return animal;
}
public static void main(String[] args) {
Animal animal=func();
animal.eat();
}
}
多态的弊端
不能调用子类的特有功能
如果一定要调用就要向下转型
向下转型
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法
此时:将父类引用再还原为子类对象即可,即向下转换。
强制性转换(大转小)
public class Test {
public static void main(String[] args) {
Animal animal=new Dog("小狗",18);
Dog dog=(Dog) animal;
dog.eat();
}
}
弊端
父类直接指向的可以向下转型,如果不是不能瞎转,如果转换成其他类的类型,就会报错
public class Test {
public static void main(String[] args) {
Animal animal=new Dog("小狗",18);
Cat cat=(Cat) animal;
cat.eat();
}
}
解决方案
通过instanceof
关键字去判断父类是否直接引用了子类类型
if(animal instanceof Dog){
Dog d=(Dog)animal;
d.eat();
}else if(animal instanceof Cat){
Cat cat=(Cat) animal;
}else {
System.out.println("没有这个类型");
}
可以直接简写成
if(animal instanceof Dog d){
d.eat();
}else if(animal instanceof Cat c){
c.eat();
}else {
System.out.println("没有这个类型,无法转换");
}
static静态
在静态方法中,不能直接调用非静态方法 而在非静态方法中是可以直接调用静态方法的
举个例子,我们在静态的Sing()方法里调用非静态的sleep()方法
public class TestDemo1 {
public static void Sing(){
System.out.println("我爱唱歌!!!");
sleep();
}
public void sleep(){
System.out.println("我想睡觉!!!");
Sing();
}
}
系统会报错
静态的方法是不依赖对象的,可以直接通过类名访问 非静态是依赖对象的,需要通过对象的引用访问
static可以修饰成员变量
static也可以修饰成员方法
代码块
普通代码块
格式是
{
}
主要作用是限定变量生命周期和作用域
出了作用域就销毁了
静态代码块
格式
static{
}
是随着类的加载而加载的,当类被加载到虚拟机执行时,静态代码块就被执行了,而且只加载一次
实例代码块
格式
{
}
位于类的成员变量之后
构造代码块
格式
public 类名(){
}
构造代码块必须定义在所有构造函数的前面
他们之间谁先优先执行呢?
我们可以用代码求证
public class TestDemo1 {
static {
System.out.println("静态代码块执行了!");
}
public TestDemo1(){
System.out.println("构造代码块执行了!");
}
{
System.out.println("实例代码块执行了!");
}
}
public class Test {
public static void main(String[] args) {
TestDemo1 testDemo1=new TestDemo1();
TestDemo1 testDemo2=new TestDemo1();
}
}
结果是:符合我们的结论
总结
通过对类和对象,继承和多态的学习,相信你对java的语法有了一定了解,让我们一起继续学习吧!