class xiaoming {
String name;
int age;
int height;
String play;
}
class zhangsan {
String name;
int age;
int height;
String study;
}
上述代码,两个类有相同的部分,面向对象思想提出继承的概念,将共同点共性抽取,实现代码复用
继承机制: 是面向对象程序设计使代码可以复用的最重要的手段,他允许程序员在保持类原有类特性的基础上进行拓展,增加新功能,这样产生的新的类,叫派生类.继承主要解决的问题是:共性抽取,实现代码复用
利用继承的思想,将上述代码共性部分进行抽取,达到共用
在java中如果要表示类之间的继承关系,需要借助extends关键字
利用继承,可以将刚才的代码改写为
class stu {
String name;
int age;
int height;
}
class xiaoming extends stu{
String play;
}
class zhangsan extends stu{
String study;
}
具体使用extends关键字如下:
修饰符 class 子类 extends 父类 {
// …
}
注意
子类会将父类中的成员变量或者成员方法继承到子类中了
在继承体系中,子类将父类中的方法和字段继承下来了
在子类方法中 或者 通过子类对象访问成员时:
如果访问的成员变量子类中有,优先访问自己的成员变量。
如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找。
class stu {
public String name;
public int age;
public int height;
}
class xiaoming extends stu{
public String play;
public void show(){
//没有同名的情况
System.out.println(name);
System.out.println(age);
System.out.println(height);
System.out.println(play);
}
}
如下代码:
class stu {
private String name;
public int age;
public int height;
}
class xiaoming extends stu{
public String play;
}
子类仍然可以继承这个父类的name变量,并获得该变量的存储空间。这并不是说子类可以直接访问该变量,但它在内部维护了对这个私有变量的引用。这意味着如果父类的这个私有变量发生改变,子类中的相应变量也会发生改变。
class big{
public int a = 1;
}
class small extends big{
public int a = 3;
public void show(){
System.out.println(a);
}
}
public class test {
public static void main(String[] args) {
new small().show();
}
}
照应上文,访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时 再到父类中找,如果父类中也没有则报错
通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到 则访问,否则编译报错。
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用
方法适传递的参数选择合适的方法访问,如果没有则报错;
class big{
public int a = 1;
}
class small extends big{
public int a = 3;
public void show(){
System.out.println(a);
}
}
子类方法或变量与父类同名时,直接访问只会访问子类,要想访问父类就要用到super关键字,该关键字主要作用:在子类方法中访问父类的成员。
class small extends big{
public int a = 3;
public void show(){
System.out.println(super.a);//访问父类
}
}
public class test {
public static void main(String[] args) {
new small().show();
}
}
class big{
public int a = 1;
}
class small extends big{
public int a = 3;
public void show(){
System.out.println(a);
System.out.println(this.a);
System.out.println(super.a);
}
}
public class test {
public static void main(String[] args) {
new small().show();
}
}
this包括父类和子类,依旧是先找子类再找父类
子类对象构造时,需要先调用基类(父类)构造方法,然后执行子类的构造方法
初始化父类要么就地初始化,要么默认初始化,或者调用构造方法
class cola{
int c;
public cola(int c) {
this.c = c;
}
}
class water extends cola{
int a;
int b;
}
如上代码,父类调用构造方法出现报错,为什么呢?在不创建构造方法的时候,会调用默认构造方法
(此处是默认构造方法的原代码)
-class cola{
int c;
}
class water extends cola{
int a;
int b;
}
默认构造方法显式化如下:
-class cola{
int c;
public cola(){
}
}
class water extends cola{
int a;
int b;
public water(){
super();
}
}
或许会有宝子说子类构造方法不加super不是没有报异常吗,像这样
class cola{
int c;
public cola(){
}
}
class water extends cola{
int a;
int b;
public water(){
}
}
这里还是把子类调用父类的构造方法给隐藏了,而非没有
class cola{
int c;
public cola(){
System.out.println("父类构造方法");
}
}
class water extends cola{
int a;
int b;
public water(){
super();
System.out.println("子类构造方法");
}
}
上述代码的结果可知,子类仍然会调用父类的构造方法
所以,现在我们可以解决一下这个报错的问题了
class cola{
int c;
public cola(int c) {
this.c = c;
}
}
class water extends cola{
int a;
int b;
}
父类的构造方法已经有了,不会调用默认构造方法但是子类的仍然会调用默认构造方法,如上代码的子类默认构造方法如下:
public water(){
super();
}
}
此处super调用父类的默认构造方法就与已经实现的父类的构造方法不同,父类的构造方法如上上次代码可知是这样的
public cola(int c) {
this.c = c;
}
}
默认构造方法有参数int c,但是super()并没有传参,所以不符,会报错异常
解决刚才的问题,如下解决:
class cola{
int c;
public cola(int c) {
this.c = c;
}
}
class water extends cola{
int a;
int b;
public water(){
super(1);//传参就ok了
}
}
class cola{
int c;
public cola(int c) {
this.c = c;
}
}
class water extends cola{
int a;
int b;
public water(int c){
//调用父类的构造方法,帮助初始化子类从父类继承的成员,而不会去实例化父类对象
super(c);
}
}
子类对象中成员是由两部分组成的,父类继承下来的以及子类新增加的部分.构造子类对象时候,先调用父类的构造方法,先将父类继承下来的成员构造完整,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整
注意
相同点
不同点
4. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于子类对象中从父类继承下来部分成员的引用
5. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
6. 在构造方法中:this()用于调用本类构造方法,super()用于调用父类构造方法,两种调用不能同时在构造方法中出现
7. 构造方法中一定会存在super()的调用,用户没有谢编译器也会自己000,但是this()用户不写则没有
class things{
static {
System.out.println("父类静态代码块");
}
{
System.out.println("父类实例代码块");
}
things(){
System.out.println("父类构造方法");
}
String name;
}
class building extends things{
static {
System.out.println("子类静态代码块");
}
{
System.out.println("子类实例代码块");
}
building(){
System.out.println("子类构造方法");
}
int height;
}
public class test {
public static void main(String[] args) {
building a = new building();
System.out.println("=============");
building b = new building();
}
}
如上代码可得.静态代码块先执行且只执行一次,在类加载阶段执行,当对象创建时,才会执行实例代码块,实例代码块执行完后,最后调用构造方法,父类优先于子类,父类实例代码块和父类构造方法紧接执行,子类同理
package AA;
public class A {
protected int a = 1;
}
package BB;
import AA.A;
public class B extends A{
public static void main(String[] args) {
A tem1 = new A();
B tem2 = new B();
System.out.println(tem2.a);
}
}
A类实例化出对象tem1,tem1无法访问是因为tem1是A的对象而非子类,protected修饰要满足之前图中的三个条件之一,tem2是B类的实例化对象,B类继承父类的成员,tem2实例化包括父类的成员,可直接访问
super不能在静态方法里使用,所以不能直接在main方法里使用super
利用如下代码来访问a
public class B extends A{
public void show(){
System.out.println(super.a);
}
public static void main(String[] args) {
B tem = new B();
tem.show();
}
}
单继承
多层继承
不同类继承同一个类
多继承(不支持)
java不支持多继承,不要出现超过三层的继承关系,如果想从语法上进行限制继承, 就可以使用 final 关键字
和继承类似,组合也是一种表达类之间关系的方式,也是能够达到代码重用的效果。组合并没有涉及到特殊的语法,仅仅是将一个类的实例作为另一个类的字段
public class animal {
public dog d;
public cat c;
}
class dog{
}
class cat{
}
多态体现:在代码运行时,当传递不同类对象时,会调用对应类中的方法
public class test extends test_big{
public int b = 2;
public test(int a, int b) {
super(a);
this.b = b;
}
public void func02(){
System.out.println(b);
}
public static void main(String[] args) {
test t01 = new test(1,2);
test_big t02 = t01;
}
}
class test_big{
public int a = 1;
public test_big(int a) {
this.a = a;
}
public void func01(){
System.out.println(a);
}
}
如上代码,子类test和父类test_big,其中代码
public static void main(String[] args) {
test t01 = new test(1,2);
test_big t02 = t01;
}
实例化子类创建一个t01的对象,把t01的引用给父类对象,可以简化为
public static void main(String[] args) {
test_big t02 = new test(1,2);
}
采用直接赋值的方式实现向上转型
向上转型:实际就是创建一个子类对象,将其当成父类对象来使用
语法格式:父类类型 对象名 = new 子类类型()
public static void fun04(test_big t) {
return;
}
public static void main(String[] args) {
test t = new test(1,2);
fun04(t);
}
如上:方法参数,传参的时候进行向上转型
如下返回值 向上转型
public static test_big func03(test t){
return t;
}
再来看
test_big t = new test(1,2);
子类对象给到了父类对象的引用
t是父类对象,如果你用t调用子类成员还是不行,因为那是子类特有的
如果子类和父类同时共有一个方法,方法名,参数列表,返回值都相同的情况下,如下
public class test extends test_big{
public void show(){
System.out.println("子类show");
}
public static void main(String[] args) {
test_big t = new test();
t.show();
}
}
class test_big{
public void show(){
System.out.println("父类show");
}
}
子类和父类都有一样的show方法
子类:
public void show(){
System.out.println("子类show");
}
父类:
public void show(){
System.out.println("父类show");
}
可是t刚才不是说是父类对象吗,不能有子类的特性,这里却调用方法时,显示的是子类的方法
这里就要提到重写了,如果在继承关系上,满足方法的返回值一样,方法名一样,方法的参数列表一样,那就可以说这两个方法之间的关系是重写,而程序运行的时候绑定到了子类的show方法,这个过程叫作动态绑定
//注解,提示
@Override
实现重写:
返回值,参数列表,方法名必须一样
被重写的方法的访问修饰限定符在子类中必须大于等于父类的
private 默认值 protected public
被private修饰的方法是不可以被重写的
被static修饰的方法是不可以被重写的
被final修饰的方法是不可以被重写
final修饰是常性的
被重写的方法的返回类型可以不同,但是必须是具有父子关系的,如下,又被称为协变类型:
public class Animal {
public Animal stay(){
return null;
}
}
class Dog extends Animal{
public Dog stay(){
return null;
}
}
重写和重载的区别
参数列表:
重写一定不能修改,重载必须修改
返回类型:
重写一定不能修改,除非构成父子类关系,重载可以修改
访问修饰限定符:
一定不能做更严格的限制,可以降低限制,重载可以修改
public class Animal {
@Override
public String toString() {
return "Animal{}";
}
public static void main(String[] args) {
Dog dog = new Dog();
System.out.println(dog);
}
}
class Dog extends Animal{
}
在Java中,Object 是所有类的直接或间接父类。每个类在Java都直接或间接地继承自Object类。这意味着,如果你创建一个类,而不明确指定它的父类,那么它将默认继承自Object类。
dog继承父类Animal,形参Dog类,实参Object类,向上转型,传参到valueOf方法,在valueOf方法里调用toString方法,因为子类Dog继承父类的toString方法,toString重写,obj调用子类的toString,打印Animal{}
public class Animal {
public void fun1(){
System.out.println("父类");
}
public static void main(String[] args) {
Animal animal = new Dog();
animal.fun1();
}
}
class Dog extends Animal{
public void fun1(){
System.out.println("子类");
}
}
程序编译过程中,调用的是Animal的func1()
程序在运行过程中,调用Dog的func1方法
动态绑定->运行时绑定
静态绑定可以理解为在编译的时候已经确定要调用谁了
**静态绑定:**也称为前期绑定(早绑定),即在编译时,根据用户所传递实参类型确定了具体调用哪个方法.典型代表函数重载
**动态绑定:**也称为后期绑定(晚绑定),即在编译时,不能确定方法的行为,需要等到程序运行时,才能确定具体调用哪个类的方法
public class Animal {
public String name;
public void play(){
System.out.println("动物正在玩");
}
public Animal(String name) {
this.name = name;
}
public static void playAnimal(Animal animal){
animal.play();
}
public static void main(String[] args) {
Dog dog = new Dog("小狗");
playAnimal(dog);
Cat cat = new Cat("小猫");
playAnimal(cat);
}
}
class Dog extends Animal{
public Dog(String name) {
super(name);
}
@Override
public void play() {
System.out.println(name+"正在玩");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
@Override
public void play() {
System.out.println(name+"正在玩");
}
}
当父类引用引用的子类对象不一样的时候,调用这个重写的方法,所表现出来的行为是不一样的,我们把这种思想叫作多态
将一个子类对象经过向上转型之后当成父类方法使用,再无法调用子类的方法,但有时候可能需要调用子类特有的方法,此时:将父类引用再还原为子类对象即可,即向下转换。
Animal animal = new Dog("小狗");
这段代码向上转型可以理解为狗是一个动物,但是倒过来,动物不一定就是狗
Animal animal = new Dog("小狗");
Dog dog = animal;
所以如上代码会报错,需要通过强转来实现
Animal animal = new Dog("小狗");
Dog dog = (Dog) animal;
向下转型并不安全
public class Animal {
public String name;
public void play(){
System.out.println("动物正在玩");
}
public Animal(String name) {
this.name = name;
}
public static void playAnimal(Animal animal){
animal.play();
}
public static void main(String[] args) {
Animal animal = new Dog("小狗");
Cat cat = (Cat)animal;
cat.speak();
}
}
class Dog extends Animal{
public Dog(String name) {
super(name);
}
@Override
public void play() {
System.out.println(name+"正在玩");
}
}
class Cat extends Animal{
public Cat(String name) {
super(name);
}
public void speak(){
System.out.println("喵喵");
}
@Override
public void play() {
System.out.println(name+"正在玩");
}
}
在main方法里
public static void main(String[] args) {
Animal animal = new Dog("小狗");
Cat cat = (Cat)animal;
cat.speak();
}
当你运行会发现运行失败
可以加一个限制防止出现这种情况
public static void main(String[] args) {
Animal animal = new Dog("小狗");
//如果animal引用的对象是Cat对象的实例
if(animal instanceof Cat){
Cat cat = (Cat)animal;
cat.speak();
} else {
System.out.println("here");
}
}
再次强调一下,向下转型非常不安全
代码运行效率降低
public class test {
public static void main(String[] args) {
Dog dog = new Dog();
}
}
class Dog extends Animal{
public void eat(){
System.out.println("dog");
}
}
class Animal{
public Animal(){
eat();
}
public void eat(){
System.out.println("Aniaml");
}
}
如上代码,实例化Dog类创建对象dog,先调用父类的构造方法,父类Animal的构造方法里调用eat方法,eat方法在子类和父类都有的情况下,调用子类的eat方法,发生了动态绑定
稍微改动一下
class Dog extends Animal{
private int d = 1;
public void eat(){
System.out.println("dog "+d);
}
}
对Dog类作出以前的改动,打印出来是
打印出来是0,而不是1,因为现在实例化子类的时候先调用父类的构造方法,构造方法里可以调用子类和父类重写的方法,调用子类的eat方法,而此时的d变量还未被子类所初始化,属于默认值,int的默认值是0,所以打印出0
所以在构造方法里,尽量避免使用实例方法,除了final和private方法,因为final和private方法无法被初始化
总结:用尽量简单的方式使对象进入工作状态,尽量不要在构造器中调用方法,如果这个方法被子类重写,就会触发动态绑定,但是此时子类对象还没构造完成,可能会出现一些隐藏的极难发现的问题