一 继承
1.1 什么是继承?
继承的思想:
所谓继承在生活中有继承衣钵,继承遗产的说法,
java中引入了继承的思想,继承是类与类之前的关系,一个类继承另一个类的成员与方法
被继承的类称为父类 /基类 / 超类
继承的类成为 子类/派生类
子类可以使用父类的成员变量与方法,也可以定义自己的成员变量与方法!
public class Test {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.age = 10;
dog1.name = "小黑";
dog1.func1();
}
}
class Animal{
String name;
int age ;
public void shout(){
System.out.println("动物发出叫声!");
}
}
class Dog extends Animal {
String sex ;
public void eat(){
System.out.println("狗在吃骨头!");
}
public void func1(){
System.out.println("这只狗的名字叫" +this.name+ " ,它已经"+this.age+" 岁了");
}
}
代码分析:
注:关键字extends :extends后面用于标识本类所继承的父类。
在代码中,定义了一个Animal类与一个Dog类,Dog类继承Animal类,
因为Dog类是子类,所以继承了Animal中定义的成员变量与方法。
在测试类的主方法中为Dog对象继承的age与name属性赋值,最后调用了Dog对象的func1方法。
1.2 继承的特性
特性一:理论上讲继承可以无限地进行下去,也就是说子类也可以有其自身的子类,而子类的子类也可以有其自身的子类,但是在实际应用中,继承的层数太多极不利于编码,所以继承关系正常情况下最多只有三层。
特性二:一个类只能有一个父类,不能有多个父类!
final关键字
final关键字修饰类
如果一个类不想被其他的类继承,则可以用final关键字修饰,则此类则被称为密封类。
final关键字要放在class关键字之前!
如图所示,继承被final修饰的类会报错!
final关键字修饰成员方法
当使用final关键字修饰成员方法后,则该方法不能够被子类重写(下面会讲到重写的问题)。
final要放在返回值类型之前,注意我并没有讲不能放在限定修饰符之前
final关键字修饰成员变量
通过final修饰的成员变量则变为常量,这与c语言中的常变量不同,是彻底地变成了常量。
常量的值在编译阶段就被确定,是不能够被修改的。
1.3 this与super关键字的使用
1.3.1 this 在子类中使用
注:
当使用在子类中使用this调用成员变量与方法时, 如果父类中成员变量或方法与子类中的成员变量或方法相冲突时,
应当采用【就近原则】,即调用子类本身的成员变量或方法!
举例:
public class Test {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.age = 10;
dog1.func1();
dog1.shout();
}
}
class Animal{
String name = "小黑";
public void shout(){
System.out.println("动物发出叫声!");
}
}
class Dog extends Animal {
String name = "旺财";
public void func1(){
System.out.println("这只狗的名字叫" +this.name);
}
public void shout(){
System.out.println("狗发出叫声!");
}
}
代码分析:
代码中子类Dog类与父类Animal类均定义了成员变量name,并进行了本地初始化。
在子类func1方法中使用this调用name属性,而问题在于此时子类与父类中均有name属性,此时该调用哪一个?
根据就近原则,调用Dog类中的name属性。
在上述例子中,实例化子类对象时,内存中的存储如图所示:
1.3.2 super关键字
当子类的某一属性与继承的父类的某一属性相同时,使用this关键字只能调用子类的属性,那当当子类的某一属性与继承的父类的某一属性相同时,如何调用属于父类的那一个?使用super关键字
super关键字用于调用继承于父类的成员属性与方法
public class Test {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.age = 10;
dog1.func1();
dog1.shout();
}
}
class Animal{
String name = "小黑";
public void shout(){
System.out.println("动物发出叫声!");
}
}
class Dog extends Animal {
String name = "旺财";
public void func1(){
System.out.println("这只狗的名字叫" +super.name);
}
public void shout(){
super.shout();
}
}
结果表明,通过使用了super关键字,调用了继承于父类的name属性与shout方法!
此时this与super在内存中的作用域如图所示:
super关键字的三个作用
作用一:
用于调用父类中的成员属性与方法,上面已经举例,这里不再赘述。
作用二:
用于调用父类被重写的方法。(方法重写的概念在下方会阐述到,因为本人的失误,博客的脉络别扭了一些,现在实在是不太好调了,望大家见谅!)
class Animal{
String name;
public void shout(){
System.out.println("动物发出吼声");
}
}
class Cat extends Animal{
public void shout(String name){
System.out.println( name+"发出吼叫");
super.shout();
}
}
public class Test {
public static void main(String[] args) {
Cat cat1 = new Cat();
cat1.shout("猫");
}
}
结果表明:通过super访问了父类中的方法。
作用三:
用于调用父类中的构造方法。
知识点:
在子类的构造方法实现时,需要先实现父类的构造方法,因为先有父,后有子
举例一:
此时父类中的构造方法有参且方法体不为空,此时在子类的构造方法如果没有super调用父类的构造方法就会报错。
加上super关键字调用父类构造方法后,警告解除。
知识点:
在子类的构造方法中,super语句必须是构造方法的第一条语句。
而我们之前学到的通过this关键字调用本类中的其他构造方法时,this语句也必须是构造方法的第一条语句。
这就意味着在【子类】中,构造方法不允许调用本类中的其他构造方法!
举例二 :
问题:为啥此时的代码不报错呢,此时代码并没有构造方法与super语句啊。
构造方法总结:
-
当父类与子类均并未定义构造方法时,系统会为父类与子类创建构造方法, 对于父类则创建无参无方法体的构造方法,对于子类则在创建无参的构造方法后,自动在第一行加上super();语句
-
当父类的构造方法为显式时(即有参数),则子类中必须手动在构造方法中第一行加入对应的super语句
-
当父类的构造方法为隐式时(即无参数,有无方法体不重要),则不需要手动在子类的构造方法中加入super语句,系统会自动添加。
1.4 方法的重写与重载
1.4.1 方法的重写
方法的重写是指在继承关系中,子类中的创建的某一方法与父类中的某一方法名相同,返回值类型,
参数列表均相同,但方法体的实现不同。
举例:
class Animal{
String name;
public void shout(){
System.out.println("动物发出吼声");
}
}
class Cat extends Animal{
public void shout(){
System.out.println("猫发出喵喵叫");
}
String sex;
}
public class Test {
public static void main(String[] args) {
Cat cat1 = new Cat();
cat1.shout();
}
}
如图所示,当子类的对象调用shout方法时,所调用的是重写后的方法!
方法重写的4个注意事项:
-
进行重写的方法不能被static与final关键字修饰
package demo1;
public class Animal {
public static void shout(){
System.out.println("动物发出吼声");
}
}
//另一个java文件
package demo1;
public class Dog extends Animal{
public void shout(){
System.out.println("狗发出叫声");
}
}
//测试类所在的java文件
package demo1;
public class Test {
public static void main(String[] args) {
Dog dog1 =new Dog();
//尝试调用重写后的方法
dog1.shout();
}
}
原因:
1) static修饰的方法不能够重写是因为 静态方法属于类,
而方法重写是依赖于不同的对象对同一种行为产生不同的效果,
本质上是依赖于对象,所以不能够进行重写,
2) final 修饰的方法不能够重写是因为被final修饰的东西带有最终的特性,
不能够再改变
注:若在子类中定义了一个与父类返回值类型,方法名,参数列表相同的静态方法,这并不是方法的重写,而仅仅是又重新定义了一个静态方法!
-
重写后的方法的访问权限必须大于等于被重写的方法的访问权限。
在我们之前讲到的访问修饰限定符中,权限大小关系为:
public > protected > default > private
这就意味着如果被重写的方法的权限为public ,那么子类中的重写后的方法只能是public,如果被重写的方法是protected,那么子类中可以是public,也可以是protected。
举例:
如图:编译器报警告,结果表明,子类中重写后的方法比父类中重写前的方法的访问权限小。
-
被private修饰的方法不能够被重写。 (因为private修饰的方法只能在本类中使用)
-
方法重写时,一般情况下返回值类型,方法名,参数列表不能够发生变化, 在特殊情况下,返回值类型可以在重写后发生变化,但仅限于父子关系。
举例:
注意:重写的方法的返回值类型是父类,重写后的返回值类型是子类,不可以颠倒!
1.4.2 在继承中方法的重载
方法的重载问题在之前的博客中已经讲过,这里阐述的是在继承关系中,方法依然可以重载,
即父类中的某一方法与子类中的某一方法,方法名相同,但是参数列表不同。
class Animal{
String name;
public void shout(){
System.out.println("动物发出吼声");
}
}
class Cat extends Animal{
public void shout(String name){
System.out.println( name+"发出吼叫");
}
}
public class Test {
public static void main(String[] args) {
Cat cat1 = new Cat();
cat1.shout();
cat1.shout("猫");
}
}
1.4.3 方法重写与重载的区别?
1.5 在继承关系中,各个代码块与构造方法的执行顺序?
举例:
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("母");
}
}
class Animal{
{
System.out.println("调用了父类的构造代码块");
}
static {
System.out.println("调用了父类的静态代码块");
}
String name;
int age;
public Animal(String name,int age){
this.name = name;
this.age = age;
System.out.println("调用了父类的构造方法");
}
public void shout(){
System.out.println("动物发出吼叫声!");
}
}
class Cat extends Animal{
String sex;
public Cat(String sex){
super("阿黄",10);
this.sex = sex;
System.out.println("调用了子类的构造方法");
}
static {
System.out.println("调用了子类的静态代码块");
}
{
System.out.println("调用了子类的构造代码块");
}
}
从结果中得知:
在继承关系中,实例化子类对象时,各代码块与构造方法的执行顺序是:
先执行父类的静态方法
然后执行子类的静态方法
再执行父类的构造代码块与父类的构造方法
最后执行子类的构造代码块与子类的构造方法
1.6 访问修饰限定符
学到现在,我们已经可以完全看懂这张图了,
对于private修饰的成员方法与变量只能本类自己使用
对于default修饰的成员变量与方法是所在的包均可使用,但是此关键字是不显示的。
对于protected是本包包括自己的子类课
当修饰成员属性时,能用private就最好不要用public ,尽可能地将类封装到最好。
1.7 继承与组合
类与类之间的关系除了继承之外,还有另一种方式:组合
所谓组合是指:
一个类的对象作为另一个类的成员,相当于车轮与汽车的关系,
而继承关系就好像父母与子女之间的关系。‘
举例:
public class Test {
public static void main(String[] args) {
}
}
class Car{
Chelun chelun1; //前面我们讲过,类相当于一个自定义类型,自然可以作为一个成员变量!
Wheel wheel ;
}
class Chelun{
int size;
String type;
}
class Wheel{
int size;
String type;
}
代码分析:汽车这个类由车轮类与方向盘类作为成员变量。
二 多态
2.1 什么是多态?
多态的思想通俗点来讲:就是多种形态,具体来讲就是同样一种行为,不同对象的实施就会产生不同的效果。
比如:
同样一句话,在不同的人嘴里说出来,分量就是不同。
同样一句话,在不同的人听到,听到的东西就是不一样。
要掌握多态需要掌握三点
-
熟悉向上转型
-
熟悉方法的重写
-
理解多态
2.2 向上转型
所谓向上转型是指子类转换成父类
如图:
向上转型的三种方式:
-
直接赋值
-
方法的传参
- 返回值类型
向上转型的缺陷:
向上转型的本质是父类的引用引用了子类的对象。
它只能调用父类自身的成员方法与变量。
举例:
package demo1;
public class Animal {
String name;
public void shout() {
System.out.println("动物发出吼叫");
}
}
//在另一个java文件中
package demo1;
public class Dog extends Animal {
int age;
public void shout2(){
System.out.println("狗发出汪汪叫......");
}
}
//在另一个java文件中
package demo1;
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.shout();
// animal.age;
}
}
结果表明:animal无法访问Dog类中的成员与方法。
但是:
在下面的代码中却打破了这一结论:
package demo1;
public class Animal {
String name;
public void shout() {
System.out.println("动物发出吼叫");
}
}
//在另一个java文件中
package demo1;
public class Dog extends Animal {
int age;
public void shout(){
System.out.println("狗发出汪汪叫......");
}
}
//另一个java文件
//测试类
package demo1;
public class Test {
public static void main(String[] args) {
Animal animal = new Dog();
animal.shout();
}
}
结果表明,通过父类引用调用了子类中的shout方法。
但是这与之前的知识点相矛盾,这里涉及了一个知识点即动态绑定!
2.3 多态
2.3.1 动态绑定
所谓动态绑定是指:在父类引用引用了子类对象之后,(即子类向上转型),调用父类重写的方法,
此时调用的结果是子类中被重写的方法,这称作动态绑定。上面的例子即是动态绑定
可以理解成在动态绑定使得父类引用指向了属于子类的重写后的方法,而不再指向父类自己的重写方法
动态绑定是理解多态的基础
2.3.2 多态的实现
举例:
//第一个java文件
package demo1;
//定义Animal类
public class Animal {
String name;
public void shout() {
System.out.println("动物发出吼叫");
}
}
//第二个java文件
package demo1;
//定义了一个Dog类,继承Animal类
public class Dog extends Animal {
String name;
public Dog(String name) {
this.name = name;
}
public void shout(){
System.out.println( this.name+ "发出汪汪叫......");
}
}
//第三个java文件
package demo1;
//定义了一个Bird类,继承Animal类
public class Bird extends Animal {
String name;
public Bird(String name) {
this.name = name;
}
public void shout(){
System.out.println( this.name+ "发出唧唧叫");
}
}
//第四个java文件
//继承了一个测试类:
package demo1;
public class Test {
public static void main(String[] args) {
Dog dog = new Dog("旺财");
func1(dog);
Bird bird =new Bird("小鸟");
func1(bird);
}
public static void func1(Animal animal){
animal.shout();
}
}
代码解析:
在上述的代码中,我们在测试类中,对于func1方法,输入一个Dog对象,就输出其对应的行为,
输入一个Bird对象,就输出一个与Dog对象不同的对应的行为。这就是多态的具体实现!
对于同一个行为,不同对象去实现,产生不同的效果!
另外:
向下转型:
向下转型的前提是先有向上转型,然后再将转型后的对象再向下转型:
向下转型需要强制类型转换
Dog dog1 = new Dog("旺财");
Animal animal = dog1;
Dog dog = (Dog) animal;
dog.shout();
结果表明:
转型后,还是调用子类自己的方法
但是 ,不是所有的向下转型都能成功,比如:
Dog dog1 = new Dog("旺财");
Animal animal = dog1;
Bird bird = (Bird) animal;
bird.shout();
instanceof关键字
instanceof关键字用于判断左边的变量引用是否引用了右边的对象
Dog dog1 = new Dog("旺财");
Animal animal = dog1;
if(animal instanceof Bird) {
Bird bird = (Bird) animal;
bird.shout();
} else {
System.out.printf(" animal instanceof Bird Not!!!!!");
}
静态绑定
静态绑定(Static Binding)是指在编译时就已经确定调用哪一个方法。
在静态绑定中,方法的调用是根据变量的数据类型来确定的,而不是根据变量实际指向的对象的类型来确定的。
如果一个子类对象被赋值给了一个父类类型的变量,那么通过该变量调用方法时,将只会调用父类的方法,而不是子类的方法。(重写的方法除外,因为它是动态绑定!)
动态绑定的另一场景
在父类的构造方法中,如果调用父类中被重写的方法,
且创建的对象是子类对象,则会发生动态绑定,
结果调用子类中重写后的方法。
package demo1;
public class Dog extends Animal {
String name;
public void shout(){
System.out.println( "发出汪汪叫......");
}
}
package demo1;
public class Animal {
String name;
public Animal() {
shout();
}
public void shout() {
System.out.println("动物发出吼叫");
}
}
package demo1;
public class Test {
public static void main(String[] args) {
Dog dog1 = new Dog();
}
}
代码分析:程序执行Dog对象的构造方法时,先执行父类的构造方法,此时父类调用shout方法,结果表明调用的是子类中重写后的方法!
如果创建的是Animal对象,则只会调用父类中被重写的方法。