一篇文章给你Java中的'重载''递归''封装''覆盖''多态'最详细的讲解


在java中有很多专业的名词,我们时常会把他们弄混或记错,下面一篇文章让你明白它们到底是什么?


一. 关于重载(overload)

1.什么时候使用重载机制?
  1. 功能相似的时候,建议将方法名定义为一致的。
  2. 但是如果功能不相似,一定要让方法名不一致
2.什么时候构成了重载机制?
  1. 在同一个类中。
  2. 方法名相同。
  3. 形式参数列表不同。(它们的类型个数顺序。
    如:
public class  Demo01{
   
public static void main(String []args){
   
Demo1.m(10);
Demo1.m("abc");
Demo1.m('a');
Demo1.m(1,2);
Demo1.m(1,2,3);
Demo1.m(1,2000L);
Demo1.m(2000L,1);
}
public static void m(int a){
   
System.out.println(a);
}
public static void m(String a){
   
System.out.println(a);
}
public static void m(char a){
   
System.out.println(a);
}
public static void m(int a,int b){
   
System.out.println(a+b);
}
public static void m(int a,int b,int c){
   
System.out.println(a+b+c);
}
public static void m(int a,long b){
   
System.out.println(a+b);
}
public static void m(long a,int b){
   
System.out.println(a+b);
}
}
输出结果:
10
abc
a
3
6
2001
2001

在同一个类中,方法名都是m();但是形式参数列表不同,构成了重载,不必将它们的方法名逐个定义,在调用时都调用m方法即可,根据填入的实参不同就可得出不同的输出结果。

3.使用重载机制的优点?
  1. 代码美观
  2. 编写代码方便

二. 关于递归

1.什么是方法递归?

当执行方法时,方法自身调用了这个方法。
但是,如果方法调用了方法自身,没有结束条件,相当于"死循环",一直在调用,无法停止,导致"StackOverflowError"栈内存溢出错误,因为栈的空间是有限制的(如果发生错误,可以检查结束条件是否正确或者调整JVM的栈内存大小)。

2.关于循环和递归的比较?

能使用循环尽量使用循环来代替递归,循环消耗内存少一点,而且递归使用不当会导致内存溢出

3.一个递归的例子?
public class Demo01{
   
public static void main(String[]main){
   
Demo01.num(1);
}
public static void num(int i){
   
if(i>10){
   
return;
}System.out.println(i);
i++
num(i);
}
}
输出结果:
1
2
3
4
5
6
7
8
10

三. 关于封装

1.简单理解封装?

一个电视机,我们不需要了解电视机的内部结构,我们只需要用一个遥控器,来对电视机进行操作,无论如何对遥控器操作,电视机都不会因为人的行为而坏掉,这就是因为电视机对于人来说,内部结构是封装起来的,人们看到的只是屏幕和外壳。
所以我们可以说封装的好处就是安全方便。

2.在代码级别上理解封装?

在代码级别上,一个类体中的代码,通过封装之后,无论如何复杂,外部人员调用的时候,只需要一个简单的入口进行操作。
不可以让任何人随意访问和改动,保障了安全。

3.如何封装?
  1. 属性私有化,也就是用private修饰。
  2. 提供简单的操作入口
4.操作入口如何定义?
  1. 读操作:[修饰符] 属性类型 get+属性名(){
    return 返回值;
    }
  2. 改操作:[修饰符] void set+属性名(形参){
    通过形参对属性赋值
    }

例子:

public class Demo01{
   
private int age =10;
public int getAge(){
   
return age;
}
public void setAge(int nianling){
   
if(nianling<0){
   
System.out.println("输入有错误");    //在这里有一个条件判断,如果输入小于0,会报错
return;
}
age=nianling;
}
}

public class Demo02{
   
public static void main(String[]args){
   
Demo01 d=new Demo01();
System.out.println(d.getAge());
d.setAge(20);
System.out.println(d.getAge());
d.setAge(-100);
System.out.println(d.getAge());
}
}
输出结果:
10
20
输入有错误
20

四. 关于覆盖(重写)(override)

1.使用覆盖的作用?

“覆盖"的另一个名字叫做"重写”,我们一定要把它和"重载"(overload)区分好,上面对重载讲解的很细致了,下面我们了解一下覆盖(重写)。
当我们运用了继承机制,如:


public class Demo03{
   
public static void main(String[] args){
   
Dog dog=new Dog();
dog.eat();}}
class Animal{
   
String name;
public void eat(){
   
System.out.println("小动物吃东西");
}
}
class Dog extends Animal{
   
public void eat(){
   
System.out.println("小狗狗啃骨头");}
}

输出结果:小狗狗啃骨头

我们定义了父类"动物类",又定义了子类"狗狗类",可是父类里的方法是"小动物吃东西",子类继承父类,此方法不再那么严谨,因为是"狗狗类",所以我们希望"狗狗类"中的方法为"小狗狗啃骨头",这样一来更加严谨。

当我们创建了新的小狗狗对象时,小狗狗dog调用eat方法,就一定会执行覆盖后的方法(理解为子类的方法把父类的方法覆盖了,子类再调用就会调用新的那个了),输出的就是"小狗狗啃骨头",而不是输出原来没有覆盖(重写)之前的"小动物吃东西",这样就达到了我们的目的。

所以当我们使用继承时,父类的方法被子类继承,但是继承已经无法达到子类的目的时,我们就再子类中对父类的方法进行覆盖(重写),把我们想要的目的编写进去。这样我们创建新的对象,就能输出我们想要的结果。

2.覆盖(重写)的语法?
  1. 两个类必须有继承关系。
  2. 重写的方法和之前的方法方法名同名
  3. 重写的方法和之前的方法形参列表相同
  4. 重写的方法和之前的方法返回值类型相同
  5. 新方法对于原来的方法访问权限可以更高不能更低
    (例:上面的代码,子类Dog中的方法如果定义为private的,就会报错。)
  6. 新方法对于原来的方法不能抛出更多的异常。
  7. 覆盖的是方法,与属性无关
  8. 构造方法不能被继承,构造方法也不能被覆盖。
  9. 针对于实例方法,静态方法覆盖没有意义。

五. 关于多态

1.向上转型和向下转型?
  1. 子类型对象———》父类型对象:向上转型。
  2. 父类型对象———》子类型对象:向下转型。
  3. 无论是向下转型还是向上转型,都必须拥有继承关系。
public class Demo04{
   
public static void main (String [] args){
   
Dog d1=new Dog();
Animal d2=new Dog();   //向上转型,父类型引用指向子类型对象。
d2.eat();

Animal d3=new Dog();    
Dog d=(Dog)d3;      //向下转型,d3转换成Dog类型。
d.move();
}
}
class Animal{
   
public void eat(){
   
System.out.println("小动物吃东西");}
}
class Dog extends Animal{
   
public void eat(){
   
System.out.println("小狗狗啃骨头");}
public void move(){
   
System.out.println("小狗狗跑了");}
}  
class Cat extends Animal{
   
public void eat(){
   
System.out.println("小猫咪喝奶奶");
}}
 

d2在调用eat方法时,java程序分为两个阶段:

  1. 编译阶段:编译阶段还没有执行new操作,看等号左面,此时认为d2是Animal类型的,去Animal.class 找eat方法绑定,编译通过,静态绑定成功。
  2. 运行阶段:在堆内存中创建的对象是Dog类型的,真正参与eat方法的是Dog,所以执行Dog对象的eat方法,动态绑定成功。
    多态的含义:编译和运行时形态不同,表示多种形态。

当调用子类对象特有的方法(Dog中的move方法),父类对象没有,就必须进行向下转型。

2.instanceof的用法?

向下转型时,有可能会产生风险:
例如上述代码中加入,将d4强转成其他类型,编译正常,但是运行会出错,这就是"类型强制转换错误"(ClassCastExpection),为了防止这种错误发生,我们要用到"instanceof"。

Animal d4=new Dog();
Cat c=(Cat)d4;
c.eat();

用法:

  1. instanceof用于动态阶段判断,判断引用指向对象的类型。
  2. 格式:引用 instanceof 对象的类型。如 d4 instanceof Cat
  3. 运算结果只能是"true"或者"false"。
  4. true代表:d4引用指向堆内存的java对象代表的Cat类型。
  5. false代表:d4引用指向堆内存的java对象代表的不是Cat类型。

所以我们可以用if进行判断,如果true就执行,false就不执行。

3.多态在开发中的作用?

举例:当我们想写一个程序,一个主人类,他喜欢小动物,所以有一个动物类。他养了小狗狗和小猫咪,所以小狗狗类和小猫咪类继承了动物类。小狗狗和小猫咪有各自吃的方法,主人有喂养小动物的方法,喂小狗狗,小狗狗就调用自己的吃方法。为小猫咪,小猫咪就调用自己的吃方法。
可是,如果我们不采用多态,没有动物类,而是一个个"小狗狗类"“小猫咪类”…这样扩展性太差,如果有一天主人有了新的宠物,将要改写的代码将会很多很复杂,就像客户的需求变更一样,不利于代码开发。
当我们创建了主人对象,利用多态父类型引用a1指向子类型对象Dog,主人再调用feed方法,那么就是调用了a1的吃方法,输出小狗狗的吃方法。小猫咪同理。十分便捷,条理清晰。
所以由此看出多态的重要性:降低程序耦合性,提高程序扩展性


public class Demo05{
   
public static void main (String []args){
   
Person mc=new Person();
Animal a1=new Dog();
mc.feed(a1);
Animal a2=new Cat();
mc.feed(a2);
}
} 
class Person{
   
public void feed(Animal a){
   
a.eat();
}
} 

class Animal{
   
public void eat(){
   
System.out.println("小动物吃东西");
}
}
class Dog extends Animal{
   
public void eat(){
   
System.out.println("小狗狗啃骨头");}
}
class Cat extends Animal{
   
public void eat(){
   
System.out.println("小猫咪吃小鱼干");}
}


输出结果:
小狗狗啃骨头
小猫咪吃小鱼干

全部评论

相关推荐

点赞 收藏 评论
分享
牛客网
牛客企业服务