一、方法的重载
首先来介绍一下什么是方法的重载。
方法的重载(overload):是指一个类中可以定义有相同的名字,但参数不同的多个方法。调用时,会根据不同的参数表选择对应的方法。
假设有这样的一个需求,我们要写输出两个整数中比较大的整数的方法,根据之前的知识不难写出
public class Test {
public void max(int a, int b) {
System.out.println(a > b ? a : b);
}
}
现在我们又收到这样的消息,要求还要有一个比较浮点数大小的方法,那我们在这个方法的下面再添加一个maxFloat()
public class Test {
public void max(int a, int b) {
System.out.println(a > b ? a : b);
}
public void maxFloat(float a, float b) {
System.out.println(a > b ? a : b);
}
}
以此类推,我们的这个类里面就会有maxDouble、maxByte等等一系列的方法,那么其他人在调用这些方法时要先弄清楚自己想要比较的是什么样的数,才可以调用对应的方法,这样显然是不合理的。
所以,我在Test这个类里面这样写:
public class Test {
public void max(int a, int b) {
System.out.println(a > b ? a : b);
}
public void max(float a, float b) {
System.out.println(a > b ? a : b);
}
public void max(double a, double b) {
System.out.println(a > b ? a : b);
}
}
我们看在这个类里面,三个方法的名字是一样的,但是参数是不同的。所以我在调用max方法时,Java会根据传递的参数值得类型,去调用相应的方法,比如我传入两个int类型的值,那么Java会自动调用第一个方法,如果我传入的是两个double类型的值,那么就会自动去调用第三个方法。
那么现在我们再来看方法重载的定义,他说,一个类中可以定义有相同的名字,但参数不同的多个方法。调用时,会根据不同的参数表选择对应的方法。
那么问题来了,什么叫参数不同?
参数不同包括两方面的内容:一是参数的类型不同,二是参数的个数不同。
总而言之,只要我在调用这个方法时,能够区分出我应该调用哪个方法,就算是合格的方法重载
现在我给出一段代码:
public class Test {
public void max(int a, int b) {
System.out.println(a > b ? a : b);
}
public int max(int a, int b) {
return a > b ? a : b;
}
}
这两个max方法算是方法的重载吗?
我们已经说过,参数不同包括两个方面:参数类型不同和参数个数不同,其中不包括返回值不同,所以这段代码会报错!
其实也很好理解,第二个方法虽然有返回值,但是在调用的时候我可以不去采用它的返回值,所以当我调用max方法的时候还是分不清到底应该调用哪个方法
下面介绍一种比较特殊的方法重载:构造方法的重载,这里给出一段代码
class Person {
private int id;
private int age;
Person() {
System.out.println("1");
id = 1;
age = 20;
}
Person(int age) {
System.out.println("2");
id = 2;
this.age = age;
}
Person(int id, int age) {
System.out.println("3");
this.id = id;
this.age = age;
}
public void info() {
System.out.println("my id = " + id);
}
public void info(String str) {
System.out.println(str + " id = " + id);
}
}
public class TestOverload {
public static void main(String[] args) {
Person p1 = new Person();
Person p2 = new Person(50);
Person p3 = new Person(3, 50);
p1.info();
p2.info("ok");
}
}
相应的输出
具体就不解释了,有一点Java基础的同学应该可以看懂
二、继承
继承是一种类与类之间的关系,比如现在我们写了这样的一个类叫Person,还有这样的一个类叫做Student,那么很明显可以看到学生也是一种人,所以只要能够说通X是一种Y,那么我们就说X继承于Y,Y叫做父类(基类),X叫子类,Java中使用extends关键字实现类的继承机制。
那么为什么要继承呢?继承有什么好处?
通过继承,子类就自动拥有了父类的所有成员(成员变量和方法),我们给出一段代码
class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Student extends Person {
String school;
public String getSchool() {
return school;
}
public void setSchool(String school) {
this.school = school;
}
}
public class TestPerson {
public static void main(String[] args) {
Student student = new Student();
student.setName("张三");
student.setAge(18);
student.setSchool("nenu");
System.out.println("name = " + student.getName());
System.out.println("age = " + student.getAge());
System.out.println("school = " + student.getSchool());
}
}
这里可以看到,Student类从Person继承,虽然我们没有在Student类里面定义成员变量name和age,但是由于Person是Student 的父类,所以Student也自动拥有了这两个成员变量。同时,Student还在这个基础上新增了一个成员变量school并实现了其get和set方法
这个程序的执行结果
这里需要提到的是,Java只支持单继承,不支持多继承,就是说,一个类只能从另一个类继承,不能同时继承多个类(C++是支持多继承的),但是在现实生活中,是存在多继承的情况的。比如,学生,他是人,能从“人”这个类继承,但他也是一种“能直立行走的生物”,所以也可以从这个类继承。关于这个问题,在接口部分会有所介绍
那么现在问一个问题:子类和父类到底哪个更大一点?
我们之前已经说过,子类从父类继承,那么子类中就包含了父类中所有的成员变量和方法,与此同时,子类还可以定义属于自己的成员变量,所以我们说,子类比父类大,在子类中已经包含了一个父类。
在这里提一点Java的访问权限的问题
Java的权限修饰符有四种:private, default, protected, public,这四种修饰符可以修饰两方面内容,第一种是类里面的成员变量和方法,第二种,它可以修饰class,可以修饰类。我们接触到的主要是第一种,用private和protected修饰class我们在内部类里面会接触到
用private修饰的变量或方法只有类的内部才可以访问,default(可以不写)叫做“包权限”,只有同一个包内可以访问,protected中的变量子类可以访问,public中的变量任何地方都可以访问
给一段代码
public class Parents {
private int i = 1;
int j = 2;
protected int m = 3;
public int n = 4;
}
class Child extends Parents {
public void test() {
//下面这句话是错误的,所以我注释掉了
// i = 2;
}
}
这段程序的意思是:Child这个类从Parents继承,也就继承了Parents的所有成员变量和方法,当然也包括private int i,但是,Child只是拥有这个变量,不能去访问、使用甚至去更改。
三、重写
在子类中可以根据需要对从父类继承来的方法进行重写,举个例子,比如有这样的一个类:Animal,里面定义了一个方法run()
public class Animal {
public void run() {
System.out.println("Animal is running");
}
}
还有一个类Dog,从Animal继承,所以Dog这个类也就自动继承了Animal的run()方法。
可是有这样的一个问题:Dog这个类对run()这个方法的实现比较不满意,Dog在实现“跑”这个方法的时候为什么要输出Animal在跑呢?所以在这样的情况下,Dog就可以对父类中的run()方法进行重写。
重写有这样的两条规则:
1、重写方法必须和被重写方法具有相同方法名称、参数列表和返回值类型
也就是说,当Dog这个类在重写run()方法的时候,必须和Animal中的run()方法 名字、参数列表、返回值类型都相同。
2、重写方法不能使用比被重写方法更严格的访问权限
当然一般来讲我们重写的方法使用的都是和原方法相同的访问权限
好了,说了这么多,其实在我们真正开发的过程中用处都不是很大,因为我们在真正的开发过程中都会使用IDE开发,也就是所说的集成开发环境(Integrated Development Environment),这些IDE都会帮助我们去直接生成重写的方法。
在IDEA中重写方法的快捷键是ALT + Insert,Eclipse和MyEclipse的快捷键是ALT + Shift + S,道理都是相通的,我们用IDEA举例
可以看到,就像我们举出的例子一样,现在我要在Dog类中重写Animal的run方法,此时,我在光标处鼠标右键-Generate
或者直接Alt + Insert
点Override Methods
选择自己想要重写的方法即可
然后,你就可以在生成的方法体中开始写你重写的方法了
Eclipse和MyEclipse过程类似,不再复述
这里可以看到,在自动生成的方法上面,有一个@Override ,这个叫做注解,是IDE自动给我们生成出来的。注意这个和注释是不同的,有着本质的区别。当然在这里,@Override这个注解有没有都是可以的,没有也不会报错,但是在其他地方,注解往往是有着他自己存在的价值的。这个在我们之后学习servlet,特别是框架的时候会大量用到,所以这里大家不用在意
解释一下super。在Java类中使用super来引用父类的成分。我们在继承里面说过,子类对象比父类对象要大,子类对象里面包含了一个父类对象,而使用super关键字,就是指向了子类对象里面的那个父类对象。所以说,super.run()这个语句,其实就是调用了父类Animal里面的那个run()方法
贴一下最终的代码
class Animal {
public void run() {
System.out.println("Animal is running");
}
}
public class Dog extends Animal{
@Override
public void run() {
System.out.println("Dog is running");
}
public static void main(String[] args) {
Animal animal = new Animal();
animal.run();
System.out.println("-----------我是分割线------------");
Dog dog = new Dog();
dog.run();
}
}
输出结果
四、多态
多态,又叫动态绑定,又叫池绑定,可以说是Java面向对象中最核心的机制。不过…
在介绍多态之前,我们要先介绍一下对象转型的相关知识
我们知道,Java的数据类型分为两种,一种是基本数据类型,四类八种不再复述,除此之外全部成为引用数据类型。基本数据类型的转换我们不再介绍,我们来介绍一下引用数据类型的转型
对象转型有这样的几条规则:
1、一个父类的引用类型变量可以指向其子类的对象。
比如,我现在要new一个动物,但是我new出来了一只狗,可以吗?当然可以,因为狗就是一种动物。这也就是我们所说的“父类引用指向子类对象”
2、一个父类的引用不可以访问其子类对象新增加的成员(属性和方法)
比如这行代码
Animal a = new Dog();
像我们第一条所说的,我需要一只动物,但是传过来的是一条狗,当然可以,但是此时a不能看成是一只狗,a只能访问它作为动物所具有的属性和方法,访问不到作为一只狗所独有的属性和方法
3、可以使用“引用变量”instanceof “类名”来判断该引用型变量所指向的对象是否属于该类或该类的子类
4、子类的对象可以当做父类的对象来使用称作向上转型,反之称为向下转型
我们使用代码来说明一下
class Animal {
public String name;
Animal(String name) {
this.name = name;
}
}
class Cat extends Animal {
public String eyesColor;
Cat(String name, String eyesColor) {
super(name);
this.eyesColor = eyesColor;
}
}
class Dog extends Animal {
public String furColor;
Dog(String name, String furColor) {
super(name);
this.furColor = furColor;
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Animal("animal");
Cat cat = new Cat("cat", "blue");
Dog dog = new Dog("dog", "yellow");
System.out.println(animal instanceof Animal);//true
System.out.println(cat instanceof Animal);//true
System.out.println(dog instanceof Animal);//true
System.out.println(animal instanceof Dog);//false
animal = new Dog("dd", "black");
System.out.println(animal.name);//dd
System.out.println(animal.furColor);//error!!!
System.out.println(animal instanceof Dog);//true
Dog dd = (Dog)animal;
System.out.println(dd.furColor);//black
}
}
有一个Animal的类,Cat和Dog从Animal继承,然后我们来看主方法
首先new出来一个animal,然后new出来一只蓝猫、一只黄狗
第一个输出:animal是一种动物吗? 输出true
第二个输出:cat是一种动物吗? 输出true
第三个输出:dog是一种动物吗? 输出true
第四个输出:animal是一种Dog吗? 当然false
然后重点来了
animal现在指向了新new出来的一个名字为”dd”的黑色Dog
第一个输出:animal的名字:dd
第二个输出:animal的furColor,由于furColor是Dog自己所独有的一种属性,所以animal是无法访问furColor这种属性的,所以这一行会报错
第三个输出:animal是一种Dog吗? instanceof 关键字看的是你真正new出来的到底是什么,animal实际上是一个Dog,只不过这个Dog有一点特殊,他只能看到自己的name看不到自己的furColor,但它实际上还是一种Dog,所以返回true
那么问题来了,如果才能让animal看到自己的furColor这种属性呢?答案是强制转换。通过强制转换,将animal强制转换成Dog并复制给dd,这样就可以访问自己的furColor属性了
这就是对象转型,再次强调,Animal animal = new Dog()叫做“父类引用指向子类对象”
下面介绍多态,首先看一段程序
class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public void run() {
System.out.println("Animal is running");
}
}
class Dog extends Animal {
String furColor;
public Dog(String name, String furColor) {
super(name);
this.furColor = furColor;
}
@Override
public void run() {
System.out.println("Dog is running");
}
}
class Cat extends Animal {
String eyesColor;
public Cat(String name, String eyesColor) {
super(name);
this.eyesColor = eyesColor;
}
@Override
public void run() {
System.out.println("Cat is running");
}
}
class Girl {
String name;
Animal pet;
public Girl(String name, Animal pet) {
this.name = name;
this.pet = pet;
}
public void myPetRun() {
pet.run();
}
}
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("dog","yellow");
Cat cat = new Cat("cat","black");
Girl g1 = new Girl("haha", dog);
Girl g2 = new Girl("hehe", cat);
g1.myPetRun();
g2.myPetRun();
}
}
老样子,Dog、Cat分别从Animal继承,他们都重写了父类Animal里面的run方法,然后有一个Gilr类,女孩,她有一个名字(String name),她还养了一只宠物(Animal pet),请注意这里的宠物是Animal类型,因为女孩可能会养猫、也可能会养狗,所以这里放Animal是最合适的。然后里面有一个方法叫myPerRun,里面调用了pet的run方法
然后看主程序,new出来一只Dog,一只Cat,然后new出来两个Girl,我们先把目光聚集在g1上面,看到她叫做”haha”,养了一只dog,但是大家注意,这里的dog是按照Animal的类型传入进去的
然后g1调用了myPetRun()方法,也就是调用了dog的run()方法
按照我们刚刚讲的对象转型想法来看的话,dog是作为Animal传入进去的,所以只能看到Animal类里面的run()方法看不到Dog里面重写的run方法,所以应该执行Animal的run(),也就是说,控制台会输出”Animal is running”
可是我们看输出
它输出的却是Dog is running,也就是说,它执行的是Dog类里面的run方法
这就叫做多态,多态指的是:在执行期间(而非编译期间)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法
在这个程序里面,虽然它是按照Animal的类型传入进去的,但是它实际上的类型却是一个Dog,所以会调用Dog里面的run()方法
多态的三个必要条件:
1、继承
2、重写
3、父类引用指向子类对象