访问修饰限定符
java中的字段和方法的四种访问权限
public可以在不同包中的类访问!
protected不同包中继承关系访问!
默认包访问权限,只能在同一包中的类中访问!
privated只能在同一个类中访问
多态
在编程语言和类型论中,多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口。 多态类型(英语:polymorphic type)可以将自身所支持的操作套用到其它类型的值上。多态(百度词条)
向上转型
子类对象赋值给了父类引用
该对象只能访问父类的字段和方法!
直接赋值
子类对象赋值给了父类引用
class Animal{ protected String name; protected int age; public void eat(){ System.out.println("animal eat()!"); } } class Dog extends Animal{ protected int height; public void running(){ System.out.println("dog running()!"); } } public class Test_1 { public static void main(String[] args) { Animal animal = new Animal(); animal = new Dog(); //1.子类对象赋值给了父类引用 Animal animal1 = new Dog(); //2.和 1 等价 animal.eat(); //调用父类中的方法 //error animal只能访问父类中的字段和方法! animal.height=1; animal.running(); } }
方法传参
//方法传参 class Animal{ protected String name; protected int age; public void eat(){ System.out.println("animal eat()!"); } } class Dog extends Animal{ protected int height; public void running(){ System.out.println("dog running()!"); } } public class Test_2 { public static void func(Animal animal){ animal.eat(); } public static void main(String[] args) { Dog dog = new Dog(); func(dog); func(new Dog()); //子类对象赋值给了父类引用! } }
方法返回
//方法返回 class Animal{ protected String name; protected int age; public void eat(){ System.out.println("animal eat()!"); } } class Dog extends Animal{ protected int height; public void running(){ System.out.println("dog running()!"); } } public class Test_2 { public static Animal func(){ return new Dog(); //子类对对象返回给了父类引用! } public static void main(String[] args) { Animal animal = func(); animal.eat(); } }
动态绑定
动态绑定就是,当子类重写了父类的方法时,向上转型后,对象调用与重写的方法,访问的是子类对象中的重写方法!
//运行时绑定 class Animal{ protected String name; protected int age; public void eat(){ System.out.println("animal eat()!"); } } class Dog extends Animal{ protected int height; @Override public void eat() { System.out.println("dog eat()!"); } public void running(){ System.out.println("dog running()!"); } } public class Test_2 { public static void main(String[] args) { Animal animal = new Dog(); animal.eat(); //运行时绑定! } }
为啥要叫运行时绑定呢?
难道说编译的时候没有绑定?
确实如此,当我们查看java的反汇编代码时就会发现,编译期间anmial调用的是自己的eat方法,但是运行时却绑定了子类的eat()!
java反汇编代码步骤:
找到,我们需要反汇编代码类的字节码文件
在命令符的窗口下,输入javap -c 类名代码 回车即可!
理解多态
我们想一想多态可以帮助我们做些什么!
bug郭的理解
运行时绑定使用场景
我们可以重写父类的方法,实现多态。调用重写的方法,一个父类可以有多个子类,不同的子类重写了不同的方法,实现了真正意义上的多态!
eg:打印图形
//类的实现者 class Shape { public void draw() { // 啥都不用干 } } class Cycle extends Shape { @Override public void draw() { System.out.println("○"); } } class Rect extends Shape { @Override public void draw() { System.out.println("□"); } } class Flower extends Shape { @Override public void draw() { System.out.println("♣"); } } //类的调用者 public class Test_1{ public static void main(String[] args) { Shape shape1 = new Flower(); Shape shape2 = new Cycle(); Shape shape3 = new Rect(); drawShape(shape1); drawShape(shape2); drawShape(shape3); } // 打印单个图形 public static void drawShape(Shape shape) { shape.draw(); } }
使用多态的好处是什么?
类调用者对类的使用成本进一步降低。
封装是让类的调用者不需要知道类的实现细节。
多态能让类的调用者连这个类的类型是什么都不必知道, 只需要知道这个对象具有某个方法即可。
因此, 多态可以理解成是封装的更进一步, 让类调用者对类的使用成本进一步降低。
能够降低代码的 “圈复杂度”, 避免使用大量的 if - else。
“圈复杂度” :就是代码中的分支和循环;
可扩展能力更强。
如果要新增一种新的形状,使用多态的方式代码改动成本也比较低。
向下转型
我们知道向上转型是子类对象赋值给了父类引用!
那向下转型莫不就是:父类对象赋值给了子类引用~
并不常见~了解一下即可!
//向下转型 class Animal { protected String name; public Animal(String name) { this.name = name; } public void eat(String food) { System.out.println("我是一只小动物"); System.out.println(this.name + "正在吃" + food); } } class Bird extends Animal { public Bird(String name) { super(name); } public void eat(String food) { System.out.println("我是一只小鸟"); System.out.println(this.name + "正在吃" + food); } public void fly() { System.out.println(this.name + "正在飞"); } } public class Test_2 { public static void main(String[] args) { Animal animal = new Bird("鸽鸽"); //先借助向上转型 animal.eat("脐橙"); //animal.fly; //error Bird bird; bird = (Bird)animal; //向下转型需要强转 bird.fly(); } }
可以看到向下转型步骤比较繁琐,通常要借助向上转型!
而且我们需要确定是否为父子类关系!避免异常!
利用instanceof 关键字可以判定一个引用是否是某个类的实例。
若真返回true,若假返回false!
构造方法中调用一个重写的方法(一个坑!)
Plain Text
自动换行
xxxxxxxxxx
1
//坑
2
class B {
3
public B() {
4
// do nothing
5
func();
6
}
7
public void func() {
8
System.out.println("B.func()");
9
}
10
}
11
class D extends B {
12
private int num = 1;
13
@Override
14
public void func() {
15
System.out.println("D.func() " + num);
16
}
17
}
18
public class Test_3{
19
public static void main(String[] args) {
20
D d = new D();
21
}
22
}
bug郭看了半天愣是没整明白为啥这个代码运行结果是这样!!!
我的理解:创建子类对象d会调用自己的构造方法,子类要先帮助父类构造,而父类中调用了子类重写的方法,动态绑定了;
我们并有执行子类中的 private int num = 1;语句!所以num此时并没有赋值!所以为0!
正解
构造D 对象的同时, 会调用B的构造方法.
B 的构造方法中调用了func方法, 此时会触发动态绑定, 会调用到D 中的func
此时 D对象自身还没有构造, 此时num 处在未初始化的状态, 值为 0.
结论: “用尽量简单的方式使对象进入可工作状态”, 尽量不要在构造器中调用方法(如果这个方法被子类重写, 就会触发动态绑定, 但是此时子类对象还没构造完成), 可能会出现一些隐藏但又及难发现的问题!
抽象类
基本语法
在刚才的打印图形例子中, 我们发现, 父类Shape中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstractmethod), 包含抽象方法的类我们称为 抽象类(abstract class)。
abstract class Shape { abstract public void draw(); }
注意事项
抽象类不能直接实例化。
抽象类可以有一般类一样的字段和方法,语法相同。
abstract class Shape { protected int longth; protected int wide; public int area(){ return longth*wide; } abstract public void draw(); }
抽象类的作用
抽象类存在的最大意义就是为了被继承。
抽象类本身不能被实例化, 要想使用, 只能创建该抽象类的子类。然后让子类重写抽象类中的抽象方法。
有些同学可能会说了, 普通的类也可以被继承呀, 普通的方法也可以被重写呀, 为啥非得用抽象类和抽象方法呢?
确实如此. 但是使用抽象类相当于多了一重编译器的校验。
使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,
使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题!