一篇文章给你Java中的'重载''递归''封装''覆盖''多态'最详细的讲解
在java中有很多专业的名词,我们时常会把他们弄混或记错,下面一篇文章让你明白它们到底是什么?
一. 关于重载(overload)
1.什么时候使用重载机制?
- 当功能相似的时候,建议将方法名定义为一致的。
- 但是如果功能不相似,一定要让方法名不一致。
2.什么时候构成了重载机制?
- 在同一个类中。
- 方法名相同。
- 形式参数列表不同。(它们的类型,个数,顺序。)
如:
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.什么是方法递归?
当执行方法时,方法自身调用了这个方法。
但是,如果方法调用了方法自身,没有结束条件,相当于"死循环",一直在调用,无法停止,导致"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.如何封装?
- 属性私有化,也就是用private修饰。
- 提供简单的操作入口。
4.操作入口如何定义?
- 读操作:[修饰符] 属性类型 get+属性名(){
return 返回值;
} - 改操作:[修饰符] 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.覆盖(重写)的语法?
- 两个类必须有继承关系。
- 重写的方法和之前的方法方法名同名。
- 重写的方法和之前的方法形参列表相同。
- 重写的方法和之前的方法返回值类型相同。
- 新方法对于原来的方法访问权限可以更高不能更低。
(例:上面的代码,子类Dog中的方法如果定义为private的,就会报错。) - 新方法对于原来的方法不能抛出更多的异常。
- 覆盖的是方法,与属性无关。
- 构造方法不能被继承,构造方法也不能被覆盖。
- 针对于实例方法,静态方法覆盖没有意义。
五. 关于多态
1.向上转型和向下转型?
- 子类型对象———》父类型对象:向上转型。
- 父类型对象———》子类型对象:向下转型。
- 无论是向下转型还是向上转型,都必须拥有继承关系。
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程序分为两个阶段:
- 编译阶段:编译阶段还没有执行new操作,看等号左面,此时认为d2是Animal类型的,去Animal.class 找eat方法绑定,编译通过,静态绑定成功。
- 运行阶段:在堆内存中创建的对象是Dog类型的,真正参与eat方法的是Dog,所以执行Dog对象的eat方法,动态绑定成功。
多态的含义:编译和运行时形态不同,表示多种形态。
当调用子类对象特有的方法(Dog中的move方法),父类对象没有,就必须进行向下转型。
2.instanceof的用法?
在向下转型时,有可能会产生风险:
例如上述代码中加入,将d4强转成其他类型,编译正常,但是运行会出错,这就是"类型强制转换错误"(ClassCastExpection),为了防止这种错误发生,我们要用到"instanceof"。
Animal d4=new Dog();
Cat c=(Cat)d4;
c.eat();
用法:
- instanceof用于动态阶段判断,判断引用指向对象的类型。
- 格式:引用 instanceof 对象的类型。如 d4 instanceof Cat
- 运算结果只能是"true"或者"false"。
- true代表:d4引用指向堆内存的java对象代表的是Cat类型。
- 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("小猫咪吃小鱼干");}
}
输出结果:
小狗狗啃骨头
小猫咪吃小鱼干