目录
一、继承
1、继承的概念
概念:一个类继承了父类(基类)的属性和方法,这个类叫做子类(派生类/超类),继承时子类会将父类的属性和方法全部继承,所以私有成员可以被继承但不能被访问非要访问就用get和set。
作用:共性的抽取,实现代码复用
格式:修饰词+class+子类+extends+父类{}
代码如下:
//父类
class Animal {
public String name = "动物";
public void sleep() {
System.out.println(name+"正在睡觉");
}
}
//Dog子类
class Dog extends Animal {
//子类独有的属性和方法
public boolean loyal = true;
public void judge() {
System.out.println("dog " + name + " 忠诚:" + loyal);//Cat子类继承了父类Animal的属性name
}
}
//Cat子类
class Cat extends Animal {
//子类独有的方法
public void catchMouse() {
System.out.println("cat " + name + " 在抓老鼠");//Cat子类继承了父类Animal的属性name
}
}
public class test {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();
//子类单独有的方法
dog.judge();
cat.catchMouse();
//子类从父类继承的方法
dog.sleep();
cat.sleep();
}
}
结果演示:
dog 动物 忠诚:true
cat 动物 在抓老鼠
动物正在睡觉
动物正在睡觉
注意:
一、访问属性:
- 父类对象可以访问父类属性
- 子类对象访问父类同名属性,优先访问子类自己属性。
二、访问方法:
- 父类对象可以调用父类方法
- 子类对象调用父类同名方法,优先调用子类自己方法。
2、访问父类成员
2.1、同名子类优先访问
一、访问父类成员方式:
- 用子类对象访问
- 用子类方法访问
二、子类对象(方法)访问父类成员变量或方法时
- 如果不同名,可以正常访问
- 如果同名,优先访问子类的同名成员变量(成员方法找同名的方法中合适参数的那一个方法,如果找不到,就报错)。
注:如果成员方法同名,传参也相同(或成员变量同名),要访问父类成员方法(成员变量),需要用到下面的super、。即同名时要指定访问父类成员时用super。
2.2、同名指定访问父类成员(super)
1、super作用:在子类方法中访问父类成员
- super.data; 访问父类非静态成员变量
- super.func(); 调用父类非静态成员方法
- super(); 调用父类的构造方法
2、被static修饰成员:用类名+变量(方法)访问
注意:
- super只能在非静态方法中使用
- super在子类方法中,访问父类的成员变量和方法。
super访问成员变量和方法:
//父类
class Animal {
public String name;
public void sleep() {
System.out.println(name+"正在睡觉");
}
}
//Dog子类
class Dog extends Animal {
public String name="旺财"; //如果无super,结果中name="旺财"
//子类独有的属性和方法
public boolean loyal;
public void func() {
System.out.println(super.name);//super访问父类成员变量
super.sleep(); //super访问父类成员方法
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
dog.func();
}
}
结果演示:
null
null正在睡觉
2.3、super和this
一、访问成员原则
在继承中
- super访问父类成员变量或成员方法(super范围是父类继承的成员)
- this优先访问子类成员,子类没有才会访问父类成员(this范围是父类继承的+子类独有的)
二、相同点
- 都是Java中的关键字
- 只能在类的非静态方法中使用,访问非静态成员方法和变量
- 构造方法中调用时,必须是构造方法中的第一条语句,且不能同时存在
三、不同点
- this是当前对象的引用,当前对象即调用实例方法的对象。
- super则是专门访问从父类继承的成员的访问,不是父类的引用。
3、子类构造方法
3.1、先父后子构造方法
组成:子类对象中成员是由两部分组成的,父类继承下来的以及子类新增加的部分 。原则:在构造子类对象时候 ,先要调用父类的构造方法,将从父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整 。先父后子
先父后子构造方法:
//构造方法
//父类
class Animal {
public String name;
public void sleep() {
System.out.println(name+"正在睡觉");
}
//默认提供的父类构造方法
// public Animal() {
//
// }
//父类构造方法
public Animal(String name) {
this.name = name;
System.out.println("Animal(String )");
}
}
//Dog子类
class Dog extends Animal {
//子类独有的属性和方法
public String name;
public boolean loyal;
public void func() {
System.out.println(super.name + "是忠诚的: " + loyal);
System.out.println(this.name + "是忠诚的: " + loyal);
}
//默认提供的子类构造方法
// public Dog() {
// super();
// }
//子类构造方法
public Dog(String name1, boolean loyal) {
super("旺财"); //1、帮助父类进行初始化,如果父类有name和age两个值可以:super(name,age)
this.name = name1; //2、子类自己初始化
this.loyal = loyal;
System.out.println("Dog(String , boolean )");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("hello",true);
dog.func();
}
}
结果演示:
Animal(String ) //父类的构造方法
Dog(String , boolean ) //子类的构造方法
旺财是忠诚的: true //子类调用func,用super访问父类时打印
hello是忠诚的: true //子类调用func,用this访问子类时打印
注意:
- 若父类构造方法无参或为默认(没有人为创建的)构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用父类构造方法
- 若父类构造方法带有参数,这时需要在子类中人为创建构造方法,并在子类构造方法第一行选择相应的父类构造方法调用,否则编译失败。
- 在子类构造方法中,super();调用父类构造方法时必须是子类构造方法中的第一句
- super();后并不生成父类对象,只是将子类从父类继承的内容初始化了。
- super();只能在子类构造方法中出现一次,且不能和this();同时出现
3.2、静态代码块、实例代码块、构造方法执行顺序
//父类
class Animal {
public String name;
static {
System.out.println("static Animal()!");
}
{
System.out.println("example Animal()!");
}
public Animal() {
System.out.println("public Animal()!");
}
}
//Dog子类
class Dog extends Animal {
//子类独有的属性
public boolean loyal;
static {
System.out.println("static Dog()!");
}
{
System.out.println("example Dog()!");
}
public Dog() {
System.out.println("public Dog()!");
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println("--------------------------");
Dog dog2 = new Dog();
}
}
结果演示:
static Animal()!
static Dog()!
example Animal()!
public Animal()!
example Dog()!
public Dog()!
--------------------------
example Animal()! //第二次实例化没有静态代码块了
public Animal()!
example Dog()!
public Dog()!
注:
- 静态代码块先执行,并且只执行一次,在类加载阶段执行
- 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行
- 顺序为:父类静态块->子类静态块->父类实例块->父类构造方法->子类实例块->子类构造方法。总之:父类总是先于子类执行,静态块先于实例块先于构造方法。
4、继承方式 继承和组合
4.1、继承方式
种类:单继承,多层继承,不同类继承同一个类,多继承(Java不支持)
注:一般来说不会超过三层继承关系,可以用final修饰类来限制
4.2、继承和组合
一、组合概念
组合和继承一样,也是表达类之间的关系的方式,是将一个类的实例作为另一个类的字段。
作用:达到代码复用
二、形式
继承:如狗是动物(狗继承动物共性)
组合: 如汽车(汽车有轮胎、方向盘、发动机等等)
三、组合代码
//学校(学生和老师组合)
class Student {
private String name;
public Student(String name) {
this.name=name;
}
}
class Teacher {
private String name;
public Teacher(String name) {
this.name=name;
}
}
class School {
//字段(属性)
public Student[] students;
public Teacher[] teachers;
public static void main(String[] args) {
}
}
四、继承和组合代码
//车(轮胎等共性的组合)
package inherit;
//轮胎类
class Tire {
}
//发动机类
class Engine {
}
//组合了Tire和Engine类
public class Car {
private Tire tire; //可以复用轮胎中的属性和方法
private Engine engine; //可以复用发动机中的属性和方法
}
//奔驰是汽车
class Benz extends Car {
//继承了汽车的共性,如轮胎,发动机等全部继承
}
5、protected限定符和final关键字
5.1、protected限定符
注:限定符属于关键字。
1、protected的最大权限为:访问不同包中的子类(子类访问protected修饰的不同包父类成员变量)
2、类的修饰符只能是public或者默认修饰符,即公开权限和包权限。
包一:
package inherit;
//包一中的类作为父类
public class Animal {
protected String name="旺财";
public void func() {
System.out.println(name);
}
}
包二:
package inherit_2;
import inherit.Animal;
//包二中的类作为子类
public class Dog extends Animal {
public void func() {
System.out.println(super.name); //用super访问父类成员
}
public static void main(String[] args) {
Dog dog = new Dog();
dog.func();
}
}
结果演示:
旺财
5.2、final关键字
作用:
- 修饰变量或成员变量,表示常量
- 修饰类,表示该类不能被继承
- 修饰方法,表示该方法不能重写
二、多态
1、多态的实现
1.1、多态的概念
多种形态,具体来说就是:同一个引用调用同一个方法,结果不一样,(因为引用对象不同)
1.2、多态实现条件
Java中要实现多态,必须满足以下条件:
- 必须在继承体系下
- 子类必须对父类中的方法进行重写
- 通过父类的引用调用重写的子类方法
1.3、例子
代码如下:
package multiple_forms;
//父类
class Animal {
public String name;
public Animal(String name) {
this.name=name;
}
//重写
public void eat() {
System.out.println(name + "吃饭");
}
}
//Dog子类
class Dog extends Animal {
public Dog(String name) {
super(name); //调用父类的构造方法进行初始化
}
@Override
public void eat() {
System.out.println(super.name + "吃狗粮");
}
}
//Cat子类
class Cat extends Animal {
public Cat(String name) {
super(name); //调用父类的构造方法
}
@Override
public void eat() {
System.out.println(super.name + "吃猫粮");
}
}
//Mulf类
public class Mulf {
//向上转型法一
public static void func(Animal animal) {
animal.eat();
}
public static void main(String[] args) {
//dog对象和cat对象调用同一个方法,结果不同
Dog dog = new Dog("旺财2.0");
func(dog);
Cat cat = new Cat("咪咪2.0");
func(cat);
}
//向上转型法二
// public static void main(String[] args) {
// Animal animal = new Dog("旺财"); //向上转型
// Animal animal1 = new Cat("咪咪"); //向上转型
// animal.eat(); //
// animal1.eat();
// }
}
结果演示:
旺财2.0吃狗粮
咪咪2.0吃猫粮
理解:
dog对象和cat对象调用同一个方法func,func方法用Animal类型(父类)的animal接收,打印结果不同。即同一个引用调用同一方法产生不同结果,因为对象不同,所以调用方法执行的行为不一样。
2、重载和重写
2.1、重载(overload)
同一个类中或具有继承关系的两个类(父子类)中,满足:
- 成员方法名相同
- 参数(数据类型,个数,顺序)不同
- 返回值无要求
- 必须是同一类或者是父子类。
2.2、重写(override)
一定发生在继承上,子类对父类非static,非final,非private方法的重新实现。满足:
- 成员方法名,参数都相同
- 返回值类型相同或为父子关系
- 该方法访问权限必须子类大于等于父类
- 父类被static,final,private修饰的方法不能重写。
- 给子类+@Override检查重写是否正确
3、向上转型和向下转型
3.1、向上转型
一、概念
1、核心:创建一个子类对象,把它当成父类对象来使用。
2、格式:父类类型 对象名 = new 子类类型();如:
Animal animal = new Dog(),此时animal这个引用指向的是一个Dog类。从右往左理解:狗是动物,所以,Animal类型的animal可以接收Dog类型的对象。
3、向上转型优缺点:优:让代码更灵活简便。缺:不能调用到子类特有的方法和属性
二、向上转型的三种方式
法一:直接接收(animal接收Dog对象)
//直接接收(animal接收Dog对象)
//Mulf类
public class Mulf {
public static void main(String[] args) {
Animal animal = new Dog("旺财2.0"); //向上转型
animal.eat();
}
}
法二:方法传参
//利用方法传参
//Mulf类
public class Mulf {
public static void func(Animal animal) { //用Animal类型的animal接收Dog类型的dog对象
animal.eat();
}
public static void main(String[] args) {
Dog dog = new Dog("旺财2.0");
func(dog);
}
}
法三:方法返回
注意:方法必须加static,因为传进去时还没有建立对象
//利用返回值 返回子类,再转换为父类
public static Animal func(String var) { //注意方法必须加static,因为传进来时还没有对象
if(var.equals("狗")) {
return new Dog("旺财3.0");
}else if(var.equals("猫")) {
return new Cat("咪咪3.0");
}else {
return null;
}
}
public static void main(String[] args) {
func("狗").eat();
}
}
3.2、向下转型
目的:在向上转型完成后能够访问子类的方法和属性理解:将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为(强转)子类对象即可,即向下转换。建议:很少使用向下转型,可以理解为动物是狗说不通。
Animal animal= new Dog();
//确认animal是否引用了Dog对象
if(animal isstanceof Dog) { //返回值是Boolean类型
Dog dog = (Dog)animal;//向下转型
dog.//特有属性或方法
}
4、静态绑定和动态绑定
4.1、静态绑定
也叫前期绑定,在编译时,根据传入参数(类型,个数,顺序)确定调用的方法。
4.2、动态绑定
一、概念
也叫后期绑定,在编译时,不能确定方法,运行时才能确定。即:
Animal animal = new Dog();animal.eat();
animal对象编译时用Animal.eat,但运行时animal对象调用Dog.eat打印。
简单来说就是:满足重写和向上转型,父类类型引用调用重写方法时,调用的是子类的。
动态绑定的条件是:
- 有重写
- 发生向上转型
二、例子:
package multiple_forms;
//父类
class Animal {
public String name;
public Animal(String name) {
this.name=name;
}
//重写
public void eat() {
System.out.println(name + "吃饭");
}
}
//Dog子类
class Dog extends Animal {
public Dog(String name) {
super(name); //调用父类的构造方法进行初始化
}
@Override
public void eat() {
System.out.println(super.name + "吃狗粮");
}
}
//Cat子类
class Cat extends Animal {
public Cat(String name) {
super(name); //调用父类的构造方法
}
@Override
public void eat() {
System.out.println(super.name + "吃猫粮");
}
}
//Mulf类
public class Mulf {
public static void main(String[] args) {
Animal animal = new Dog("旺财"); //向上转型
Animal animal1 = new Cat("咪咪"); //向上转型
animal.eat();
animal1.eat();
}
}
结果演示:
旺财吃狗粮
咪咪吃猫粮
三、Mulf解析
Mulf代码:
//Mulf类
public class Mulf {
public static void main(String[] args) {
Animal animal = new Dog("旺财"); //向上转型
Animal animal1 = new Cat("咪咪"); //向上转型
animal.eat();
animal1.eat();
}
}
Mulf反汇编:
理解:
- 当子类没有eat时(即没有重写):编译和运行都是父类Animal的eat方法。
- 当子类有eat时(重写):编译的时候还是父类Animal的eat()方法,但是程序运行的时候变成了子类的,所以叫做动态绑定。
四、扩展:反汇编调用方法
- 右键Open in ->Explorer->找到out路径->production路径->工程名->包名
- 点项目名.class->点上面的路径->输入cmd调出命令指示符->输入javap -c 项目名
5、多态的优缺点
5.1、多态优点:
- 能够降低代码的 "圈复杂度", 避免使用大量的 if - else
- 可扩展能力更强
优点1:降低代码的 "圈复杂度"
多态实现代码:
//多态
class Shape {
//属性...
public void draw() {
System.out.println("画图形");
}
}
class Rate extends Shape {
@Override
public void draw() {
System.out.println("画矩形");
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("画圆");
}
}
class Flower extends Shape {
@Override
public void draw() {
System.out.println("画花");
}
}
public class Mulf {
public static void drawShapes() {
Shape[] shapes = {new Cycle(),new Rate(),new Flower()};
for (Shape shape:shapes) {
shape.draw();
}
}
public static void main(String[] args) {
drawShapes();
}
}
不用多态: 例如我们现在需要打印 多个形状图形 . 如果不基于多态 , 实现代码如下:
public static void drawShapes() {
Rect rect = new Rect();
Cycle cycle = new Cycle();
Flower flower = new Flower();
String[] shapes = {"cycle", "rect", "cycle", "rect", "flower"};
for (String shape : shapes) {
if (shape.equals("cycle")) {
cycle.draw();
} else if (shape.equals("rect")) {
rect.draw();
} else if (shape.equals("flower")) {
flower.draw();
}
}
}
用多态 , 则不必写这么多的 if - else 分支语句 , 代码更简单:
public static void drawShapes() { // 我们创建了一个 Shape 对象的数组.
Shape[] shapes = {new Cycle(), new Rect(), new Cycle(), new Rect(), new Flower()};
for (Shape shape : shapes) {
shape.draw();
}
}
优点2:可扩展能力更强
如:Shape[] shapes = { new Cycle(), new Rect() },里面可以继续追加加对象,很方便,不像if-else还需要改方法内部。
5.2、多态缺点
代码的运行效率降低 。(效率影响不太大)
5.3、注意
- 属性没有多态性 ,当父类和子类都有同名属性的时候,通过父类引用,只能引用父类自己的成员属性
构造方法没有多态性
6、避免在构造方法中调用重写的方法
例子代码如下:
class B {
public B() { // do nothing func(); }
public void func () {
System.out.println("B.func()");
}
}
}
class D extends B {
private int num = 1;
@Override
public void func() { System.out.println("D.func() " + num);
}
}
public class Test {
public static void main(String[] args) {
D d = new D();
}
}// 执行结果 D.func() 0
- 构造 D 对象的同时, 会调用 B 的构造方法
- B 的构造方法中调用了 func 方法, 此时会触发动态绑定, 会调用到 D 中的 func
- 此时 D 对象自身还没有构造, 此时 num 处在未初始化的状态, 值为 0. 如果具备多态性,num的值应该是1
- 所以在构造函数内,尽量避免使用实例方法,除了final、static、private方法。
注意:"用尽量简单的方式使对象进入可工作状态", 尽量不要在构造方法中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏的但是又极难发现的问题.