Java里类是单继承的,接口是可以多继承的,用关键字extends。
子类能继承父类的所有成员
子类是继承了父类的私有方法的(不管是否是final),只是直接调用父类的私有方法是不可以的,但是利用反射的方式可以调用。
接口
接口就像是一种约定,我们约定某些英雄是物理系英雄,那么他们就一定能够进行物理攻击
实现某个接口,就相当于承诺了某种约定
判别子转父(向上转型)能否成功的办法
所谓的转型,是指当引用类型和对象类型不一致的时候,才需要进行类型转换
Hero h = new Hero();
ADHero ad = new ADHero();
h = ad;
所有的子类转换为父类,都是说得通的。比如你身边的例子
苹果手机 继承了 手机,把苹果手机当做普通手机使用
怡宝纯净水 继承了 饮品, 把怡宝纯净水 当做饮品来使用
父类转子类(向下转型)
父类转子类,有的时候行,有的时候不行,所以必须进行强制转换。
强制转换的意思就是 转换有风险,风险自担。
什么时候行呢?
-
Hero h =new Hero();
-
ADHero ad = new ADHero();
-
h = ad;
-
ad = (ADHero) h;
第3行,是子类转父类,一定可以的
第4行,就是父类转子类,所以要进行强转。
h这个引用,所指向的对象是ADHero, 所以第4行,就会把ADHero转换为ADHero,就能转换成功。
什么时候转换不行呢?
-
Hero h =new Hero();
-
ADHero ad = new ADHero();
-
Support s =new Support();
-
h = s;
-
ad = (ADHero)h;
第4行,是子类转父类,是可以转换成功的
第5行,是把h引用所指向的对象 Support,转换为ad引用的类型ADHero。 从语义上讲,把物理攻击英雄,当成辅助英雄来用,说不通,所以会强制转换失败,并且抛出异常
没有继承关系的两个类,互相转换
没有继承关系的两个类,互相转换,一定会失败
虽然ADHero和APHero都继承了Hero,但是彼此没有互相继承关系
“把魔法英雄当做物理英雄来用”,在语义上也是说不通的
实现类转换成接口(向上转型)
引用ad指向的对象是ADHero类型,这个类型实现了AD接口
10行: 把一个ADHero类型转换为AD接口
从语义上来讲,把一个ADHero当做AD来使用,而AD接口只有一个physicAttack方法,这就意味着转换后就有可能要调用physicAttack方法,而ADHero一定是有physicAttack方法的,所以转换是能成功的。
接口转换成实现类(向下转型)
10行: ad引用指向ADHero, 而adi引用是接口类型:AD,实现类转换为接口,是向上转型,所以无需强制转换,并且一定能成功
12行: adi实际上是指向一个ADHero的,所以能够转换成功
14行: adi引用所指向的对象是一个ADHero,要转换为ADAPHero就会失败。
假设能够转换成功,那么就可以使用magicAttack方法,而adi引用所指向的对象ADHero是没有magicAttack方法的
public static void main(String[] args) {
ADHero ad = new ADHero();
AD adi = ad;//10
ADHero adHero = (ADHero) adi;//12
ADAPHero adapHero = (ADAPHero) adi;//14
adapHero.magicAttack();
}
重写
重写Overriding是父类与子类之间多态性的一种表现,重载Overloading是一个类中多态性的一种表现。
方法重写应遵循两同两小一大原则:
两同:方法名、形参列表相同;
两小:返回值类型、抛出异常更小
一大:访问修饰符。
这里强调一下返回值类型小:
重写返回值类型如果是基本数据类型,必须相同,如果是引用数据类型,那么复写方法必须小于等于父类,比如父类方法返回值类型是object 那么子类就任意了
再强调一下 "两小一大"是为了加强记忆的方便,并不是非要 “小大”,相同也可以。
多态
操作符的多态
- 可以作为算数运算,也可以作为字符串连接
多态问题中,无论向上或向下转型,都记住一句话就可以了。
编译看左边,运行看右边。意思编译时候,看左边有没有该方法,运行的时候结果看 new 的对象是谁,就调用的谁。
举个例子
Base base = new Son();
编译看左边,调用的方法是否是Base类型有的,如果没有,就报编译错误
类的多态
观察类的多态现象:
- i1和i2都是Item类型
- 都调用effect方法
- 输出不同的结果
多态: 都是同一个类型,调用同一个方法,却能呈现不同的状态
类的多态条件
要实现类的多态,需要如下条件
- 父类(接口)引用指向子类对象
- 调用的方法有重写
隐藏
就是子类覆盖父类的类方法
实例化子类,父类的构造方法一定会被调用
实例化一个ADHero(), 其构造方法会被调用
其父类的构造方法也会被调用
并且是父类构造方法先调用
子类构造方法会默认调用父类的 无参的构造方法
子类显式调用父类带参构造方法
使用关键字super 显式调用父类带参的构造方法
super(name);
调用父类属性
public int getMoveSpeed2(){
return super.moveSpeed;
}
调用父类方法
// 重写useItem,并在其中调用父类的userItem方法
public void useItem(Item i) {
System.out.println("adhero use item");
super.useItem(i);
}
Object类是所有类的父类
声明一个类的时候,默认是继承了Object
toString()
Object类提供一个toString方法,所以所有的类都有toString方法
toString()的意思是返回当前对象的字符串表达
finalize()
当一个对象没有任何引用指向的时候,它就满足垃圾回收的条件
当它被垃圾回收的时候,它的finalize() 方法就会被调用。
finalize() 不是开发人员主动调用的方法,而是由虚拟机JVM调用的
equals()
Object类的equals方法的实现代码如下:
boolean equals(Object o){
return this==o;
}
==
这不是Object的方法,但是用于判断两个对象是否相同
更准确的讲,用于判断两个引用,是否指向了同一个对象(比较的是地址)
hashCode()
不同的对象可能会生成相同的hashcode值。虽然不能根据hashcode值判断两个对象是否相等,但是可以直接根据hashcode值判断两个对象不等
在有些情况下,程序设计者在设计一个类的时候为需要重写equals方法,比如String类,但是千万要注意,在重写equals方法的同时,必须重写hashCode方法。因为默认情况下,hashCode方法是将对象的存储地址进行映射,和我要判断相等的条件本意冲突了。
线程同步相关方法
Object还提供线程同步相关方法
wait()
notify()
notifyAll()
这部分内容的理解需要建立在对线程安全有足够的理解的基础之上,所以会放在线程交互 的章节讲解
getClass()
getClass()会返回一个对象的类对象,属于高级内容,不适合初学者过早接触,关于类对象的详细内容请参考反射机制
接口与继承之final
final修饰类
不能够被继承
其子类会出现编译错误
final修饰方法
不能够被重写
final修饰基本类型变量
表示该变量只有一次赋值机会
final修饰引用
h引用被修饰成final,表示该引用只有1次指向对象的机会
常量
常量指的是可以公开,直接访问,不会变化的值
抽象类的基本特性
1.当一个类有抽象方法的时候,该类必须被声明为抽象类
2.抽象类可以没有抽象方法
3.抽象类可以定义public,protected,package,private,静态和非静态属性final和非final属性
4.抽象方法中不能用private,static, synchronized,native等修饰词修饰.(细节要交给子类)
内部类分为四种
内部类其实和类的属性没什么区别,只是在声明的时候必须是Outer.Inner a,就像int a 一样,至于静态内部类和非静态内部类new的时候有点区别,Outer.Inner a=new Outer().new Inner()(非静态,先有Outer对象才能有属性) Outer.Inner a=new Outer.Inner()要把Outer.Inner看成一部分,就像类变量一样
注意:Outer和Inner是类名
非静态内部类
非静态内部类可以直接在一个类里面定义
语法: new 外部类().new 内部类()
作为外部类的非静态内部类,是可以直接访问外部类的private实例属性name的
静态内部类
static class EnemyCrystal{}
在一个类里面声明一个静态内部类
比如敌方水晶,当敌方水晶没有血的时候,己方所有英雄都取得胜利,而不只是某一个具体的英雄取得胜利。
与非静态内部类不同,静态内部类水晶类的实例化 不需要一个外部类的实例为基础,可以直接实例化
语法:new 外部类.静态内部类();
因为没有一个外部类的实例,所以在静态内部类里面不可以访问外部类的实例属性和方法
除了可以访问外部类的私有静态成员外,静态内部类和普通类没什么大的区别
匿名类
匿名类指的是在声明一个类的同时实例化它,使代码更加简洁精练
通常情况下,要使用一个接口或者抽象类,都必须创建一个子类
有的时候,为了快速使用,直接实例化一个抽象类,并“当场”实现其抽象方法。
既然实现了抽象方法,那么就是一个新的类,只是这个类,没有命名。
这样的类,叫做匿名类
Hero h = new Hero(){
//当场实现attack方法
public void attack() {
System.out.println("新的进攻手段");
}
};
本地类
本地类可以理解为有名字的匿名类
内部类与匿名类不一样的是,内部类必须声明在成员的位置,即与属性和方法平等的位置。
本地类和匿名类一样,直接声明在代码块里面,可以是主方法,for循环里等等地方
在匿名类中使用外部的局部变量
在匿名类中使用外部的局部变量,外部的局部变量必须修饰为final
为什么要声明为final,其机制比较复杂
注:在jdk8中,已经不需要强制修饰成final了,如果没有写final,不会报错,因为编译器偷偷的帮你加上了看不见的final
什么是默认方法
默认方法是JDK8新特性,指的是接口也可以提供具体方法了,而不像以前,只能提供抽象方法
Mortal 这个接口,增加了一个默认方法 revive,这个方法有实现体,并且被声明为了default
default public void revive() {
System.out.println("本英雄复活了");
}
为什么会有默认方法
假设没有默认方法这种机制,那么如果要为Mortal增加一个新的方法revive,那么所有实现了Mortal接口的类,都需要做改动。
但是引入了默认方法后,原来的类,不需要做任何改动,并且还能得到这个默认方法
通过这种手段,就能够很好的扩展新的类,并且做到不影响原来的类
面向对象的五大基本原则
单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化。
开放封闭原则(Open-Closed principle):软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
Liskov替换原则(Liskov-Substituion Principle):子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
依赖倒置原则(Dependecy-Inversion Principle):依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
接口隔离原则(Interface-Segregation Principle):使用多个小的专门的接口,而不要使用一个大的总接口