- 关于引用的进一步理解(交换值)
因为Java方法在传递参数的时候都是值传递,那么如何通过方法实现2个数的值交换?
明确:在传引用的时候,到底拿引用干了个啥
class Value {
public int a;
}
public class Test {
public static void swap(Value value1, Value value2) {
int tmp = value1.a;
value1.a = value2.a;
value2.a = tmp;
}
public static void main(String[] args) {
//交换值,如果只是简单的定义a和b不太能做到,因为他们在栈上,我们要想办法把他们放到堆上
Value value1 = new Value();
Value value2 = new Value();
value1.a = 10;
value2.a = 20;
swap(value1, value2);
System.out.println(value1.a);
System.out.println(value2.a);
}
将属性a的权限改成private,此时在类外a是被封装起来的,不能够直接赋值了,可以提供get和set方法来实现交换值
class Value {
private int a;
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
}
public class Test {
public static void swap(Value value1, Value value2) {
// int tmp = value1.a;
int tmp = value1.getA();
// value1.a = value2.a;
value1.setA(value2.getA());
// value2.a = tmp;
value2.setA(tmp);
}
public static void main(String[] args) {
//交换值,如果只是简单的定义a和b不太能做到,因为他们在栈上,我们要想办法把他们放到堆上
Value value1 = new Value();
Value value2 = new Value();
//value1.a = 10;
value1.setA(10);
//value2.a = 20;
value2.setA(20);
swap(value1, value2);
System.out.println(value1.getA());
System.out.println(value2.getA());
}
public static void main1(String[] args) {
Derived derived = new Derived();
derived.fun();
}
}
运行结果:
-
什么是多态?
发生多态的3个条件①在继承的条件下②发生向上转型③方法重写
多态是一种思想,父类引用引用不同对象的时候,表现出来的行为是不一样的。【这就叫做多态】
多态的前提是:动态绑定
【一个父类引用 指向的对象不一样,调用重写的方法,会表现出不同的行为。】 -
向上转型
分别定义动物类、狗类、鸟类
class Aniaml {
public String name;
public int age;
public void eat() {
System.out.println(name + "吃东西");
}
}
class Dog extends Aniaml {
public void wangwnag() {
System.out.println(name + "汪汪汪");
}
}
class Bird extends Aniaml {
public String wing;
public void miaomiao() {
System.out.println(name + "喵喵喵");
}
}
public class Test1 {
public static void main(String[] args) {
Dog dog = new Dog();
dog.name = "小狗";
dog.eat();
dog.wangwnag();
System.out.println("===分割===");
Bird bird = new Bird();
bird.name = "小鸟";
bird.eat();
bird.miaomiao();
}
}
运行结果:
如果修改main方法中的实例化对象语句为:
Aniaml aniaml1 = new Dog();
aniaml1.name = "小狗";
aniaml1.eat();
// aniaml1.wangwang(); //报错,因为只能访问Aniaml类中有的成员
System.out.println("===分割===");
Aniaml aniaml2 = new Bird();
aniaml2.name = "小鸟";
aniaml2.eat();
此时 Aniaml aniaml1 = new Dog();
,左边是动物类,右边是狗类,发生了向上转型,父类引用指向了子类对象。
理论上:等号两边的数据类型必须一致,否则赋值出错。
当发生向上转型之后,此时通过父类的引用只能访问父类自己的成员,不能访问到子类特有的成员,也就是说,只animal能调用Animal类中的方法和属性,不能再访问Dog类和Bird类子类中独有的方法和属性了。
- 向上转型的3种场景
①直接赋值
Aniaml aniaml1 = new Dog();
aniaml1.name = "小狗";
aniaml1.eat();
②方法传参
public static void fun(Aniaml aniaml) {
aniaml.eat();
}
public static void main(String[] args) {
Dog dog = new Dog("小狗");
fun(dog);
}
③方法返回值
public static Aniaml func() {
return new Dog();
}
向上转型的优点:让代码实现更简单灵活。(animal能够引用它,说明它一定是个animal的子类,它一定是一个动物)
向上转型的缺点:不能访问子类特有的方法。【除非发生了动态绑定,可以调用子类重写的方法】(animal只能调用自己特有的属性和方法)
- 方法重写的引入
在上述例子种,狗和鸟都是吃饭,按照常理,狗应该吃狗粮,鸟应该吃鸟粮,此时就应该在狗类和鸟类种重写父类的eat方法。
public static void main(String[] args) {
Aniaml aniaml1 = new Dog("小狗");
aniaml1.eat();
Aniaml aniaml2 = new Bird("小鸟");
aniaml2.eat();
}
运行结果:
分析:在这个过程中,【父类不是只能调用父类的方法吗?这里怎么调用了重写的方法呢?】因为这里发生了动态绑定,编译的时候确实调用的是Animal的eat方法,但是在运行的时候发生了动态绑定,调用了重写的方法。
-
动态绑定需要满足的3个条件
①向上转型②重写③通过父类引用调用这个父类和子类重写的方法
【动态绑定是多态的基础】
动态绑定:也称为后期绑定(晚绑定),在编译的时候不能确定方法的行为,需要等到程序运行的时候,才能确定具体调用哪个类的方法
静态绑定:也称为前期绑定(早绑定),在编译的时候,根据传入的参数,就能够确定调用哪个方法,典型代表方法重载。 -
方法重写的注意点
重写:是子类对父类非private修饰、非static修饰、非final修饰、非构造方法等进行重新编写。【重写的好处:子类可以根据需要,定义特定于自己的行为,子类可以根据需要实现父类的方法。】
①被private修饰的方法不能被重写:因为private权限范围只能在当前类内使用
②被static修饰的方法不能被重写:因为static属于类方法,如果被重写,则一个属于动物类,一个属于狗类,和对象毫无关系,分别属于各自所在的类。
③被final修饰的方法不能被重写:也叫密封方法
④构造方法不能被重写:因为子类的构造方法名字不可能和父类的构造方法的名字一样。
⑤子类的访问修饰限定权限要大于等于父类的权限
private<默认<protected<public -
重写和重载的对比
重写:①方法名相同 ②参数列表相同③返回值类型相同【当返回值类型不同的时候必须是 父子关系】
重载:①方法名相同②参数列表不同③与返回值类型无关
【重写的参数列表 一定不能修改,重载的参数列表 必须修改】 -
向下转型【不安全】
将一个子类对象经过向上转型之后当成父类方法使用,这也再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时需要将父类引用再还原为子类对象。
例如,将Dog和Bird向上转型成Animal后,无法调用Dog中特有的wangwang方法以及Bird中的zhazha方法,此时需要将Animal再次向下转型成Dog和Bird进行调用子类特有的方法。
public static void main(String[] args) {
Aniaml aniaml1 = new Dog("小狗");
Dog dog = (Dog) aniaml1;
dog.eat();
Aniaml aniaml2 = new Bird("小鸟");
Bird bird = (Bird) aniaml2;
bird.eat();
}
运行结果:
分析:animal1向下转型:本来是狗,还原为狗,安全;animal2向下转型:本来是鸟,还原为鸟,安全。
如果本来是狗,向下转型为鸟,则不安全:
public static void main(String[] args) {
Aniaml aniaml = new Dog("小狗");
Bird bird = (Bird) aniaml;
bird.eat();
}
编译没有报错,但是在运行的时候出现类型转换异常:
分析:程序可以通过编程,但是运行时抛出异常,本来是狗,现在要强制还原为猫,无法正常还原,不安全。
Java中为了提高向下转型的安全性,引入 instanceof关键字,如果表达式为true,则可以安全转换。
public static void main(String[] args) {
Aniaml aniaml = new Dog("小狗");
if (aniaml instanceof Bird) {
Bird bird = (Bird) aniaml;
bird.eat();
}else {
Dog dog = (Dog) aniaml;
dog.eat();
}
}
运行结果:
分析:aniaml instanceof Bird
,判断animal是不是引用了Bird对象,如果引用了就可以向下转型。
- 多态的优缺点
例:定义类,完成画图(圆,矩形,花等)
使用多态完成:
class Shape {
public void draw() {
System.out.println("画图形");
}
}
class Cir extends Shape {
@Override
public void draw() {
System.out.println("画圆");
}
}
class Rex extends Shape {
@Override
public void draw() {
System.out.println("画矩形");
}
}
class Flow extends Shape {
@Override
public void draw() {
System.out.println("画花");
}
}
public class TestDemo {
public static void func(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Cir cir = new Cir();
Rex rex = new Rex();
Flow flow = new Flow();
func(cir);
func(rex);
func(flow);
}
}
运行结果:
圈复杂度:是一种描述一段代码复杂程度的方式,可以简单粗暴的一段代码中条件语句和循环语句出现的个数,这个个数就称为“圈复杂度”。如果一个方法圈复杂度太高,就需要考虑重构,不同公司对于代码的圈复杂度的规范不一样,一般不会超过10。
假如不使用多态,则要使用到if-else语句等进行判断(此时圈复杂度会很高)。
public static void main(String[] args) {
Cir cir = new Cir();
Rex rex = new Rex();
Flow flow = new Flow();
String[] shapes = {"cir", "rex", "flow","cir", "rex"};
for (String x: shapes) {
if (x.equals("cir")) {
cir.draw();
}else if (x.equals("rex")) {
rex.draw();
}else if (x.equals("flow")) {
flow.draw();
}
}
}
如果使用多态,则不必写那么多if-else分支语句,代码更简单。
public static void func(Shape shape) {
shape.draw();
}
public static void main(String[] args) {
Shape[] shapes = {new Cir(), new Rex(), new Flow(),new Rex(), new Rex()};
for (Shape x: shapes) {
func(x);
}
}
分析: Shape数组:意味着每个元素都是Shope子类,父类类型的数组可以放子类类型变量。
在func方法中,shape引用 引用饿子类对象不一样,调用的方法表现出来的行为也就不一样——这种思想叫做多态。
如果要增加一个打印三角形。
对于类的实现者:定义一个三角形的类,并重写方法。
class Tri extends Shape {
@Override
public void draw() {
System.out.println("画三角形");
}
}
对于类的调用者:创建一个三角形的实例。
Tri tri = new Tri();
func(tri);
改动成本很低
而对于不用多态的情况,就要把if-else进行一定的改动,改动成本更高。
因此多态的优势:
①能够降低代码的“圈复杂度”,避免使用大量的if-else。
②可扩展能力强,如果要新增一种新的形状,使用多态的方式代码改动成本也比较低。
多态的缺陷:
①属性没有多态性,只有方法有多态性:当父类和子类都有同名的属性的时候,通过父类的引用,只能引用父类自己的成员属性。
②构造方法没有多态性,构造方法也不能被重写
③多态代码运行效率降低
- 避免在构造方法中调用重写的方法
有坑的代码,创建两个类,B是父类,D是子类,D中重写func方法。并且在父类B的构造方法中调用func。
class B {
public B(){
func();
}
public void func() {
System.out.println("B的方法");
}
}
class D extends B {
int num;
public D() {
num = 1;
}
@Override
public void func() {
System.out.println(num + " C重写B的方法");
}
}
public class TestDemo2 {
public static void main(String[] args) {
//B是父类,D是子类,D中重写func方法。并且在父类B的构造方法中调用func。
D d = new D();
}
}
运行结果:
分析:在B的构造方法中调func,刻板印象会认为调用的是B的func方法,但是通过实际的运行结果发现调用的是D中的func方法,并且num的值为0,因为此时父类B的构造方法都还没执行完成,更别说子类D的构造了。
注意:当在父类的构造方法当中,去调用父类和子类重写的方法的时候,此时会调用子类的重写方法。