继承基本知识详解
当我们在创建一个类的时候, 我们总是在继承, 默认所有的类都继承了 Object 类(java 的标准根类)
继承的关键字: extends, 继承只可以单继承! 切记.
继承的真正含义: 当创建了一个子类时, 其实也隐含的创建了一个基类的对象, 只不过这个对象被包装在了导出类的内部, 我们可以用 super 关键字来引用它.
这也就意味着, 当我们实例化一个导出类对象时, 我们必须要首先实例化它的基类, 通常这个必须要在导出类的构造器中实现.
- public class Demo5 extends Test2{
- Demo5(){
- System.out.println("Demo5 已经初始化");
- }
- public static void main(String[] args) {
- Demo5 demo=new Demo5();
- }
- }
- class Test2 {
- Test2(){
- System.out.println("Test2 已经实例化");
- }
- }
效果:
但是当基类有带参数的构造器时, 就必须要显式的写了.
- public class Demo5 extends Test2{
- Demo5(){
- super(3);
- System.out.println("基类的 a:"+super.getA());
- System.out.println("Demo5 已经初始化");
- }
- public static void main(String[] args) {
- Demo5 demo=new Demo5();
- }
- }
- class Test2 {
- private int a;
- Test2(int a){
- this.a=a;
- System.out.println("Test2 已经实例化");
- }
- int getA(){
- return this.a;
- }
- }
效果:
若是不写的话, 那么会报错, 其实, 早在导出类的构造器可以访问基类之前, 基类就已经实例化了.
使用 super 关键字的注意点
Super 关键字和 this 关键字一样, 我们知道, 实例化一个导出类的同时, 也会实例化他的基类, 并将 它封装在导出类的对象的内部, 而 super 关键字则可以引用这个对象.
1. Super 关键字可以调用基类的方法和变量 (前提是有访问的权限). 采用 super.* 的形式, 但是在调用构造器的时候, 则直接用 super() 的形式调用.
2. 同样的, super 关键字也必须在方法的最前面.
重写
当我们不想对基类的某些方法进行重载时, 便可以采用关键字:@Override
重载和重写的异同:
1. 重载发生在用一个类中具有相同的函数名但是参数列表不同的几个方法,(返回值不影响), 而重写发生在导出类的方法中.
2. 1, 参数列表必须完全与被重写的方法相同, 否则不能称其为重写而是重载.
2, 返回的类型必须一直与被重写的方法的返回类型相同, 否则不能称其为重写而是重载.
3, 访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)
4, 重写方法一定不能抛出新的检查异常或者比被重写方法申明更加宽泛的检查型异常. 例如:
父类的一个方法申明了一个检查异常 IOException, 在重写这个方法是就不能抛出 Exception, 只能抛出 IOException 的子类异常, 可以抛出非检查异常.
向上转型
继承最大的优点其实就是体现在向上转型这一点上, 即新类是基类的一种类型.
导出类是基类的一个超集, 他可能会含有比基类更多的方法, 但是它至少含有基类中所含有的所有方法, 在向上转型的过程中, 我们唯一担心的事情是丢失方法, 而不是获取他们.
简而言之, 就是一个父类的引用可以指向子类, 反过来虽然可以强转, 但是在运行的时候却会报错.
final 关键字
final: 不可改变的, 主要用于: 数据, 方法和类.
Final 数据
当我们想用:
1. 一个永不改变的编译常量
2. 一个在运行是就初始化的值, 而你不希望改变他.
用 final 修饰基本数据时, 保持数值不变, 这一点很好理解, 但是用 final 修饰引用时, 就规定了这个引用指向的对象不可改变, 但是对象本身的内容是可以改变的.
当声明一个变量是 final 时, 如果没有初始化, 那么必须在构造器中进行初始化, 否则会报错.
Final 方法
使用 final 方法的原因:
1. 将方法锁定, 以防任何继承类修改它的含义
2. 想要确保在继承中保持方法行为不变, 不会别重写.
一般, 所有声明为 private 的方法都隐含的被指定为 final.
重写只有时在该方法是接口的一部分时才会生效, 而 private 方法或者 final 方法则会在继承的时候隐藏起来, 就算写一个相同的方法, 也不过是新生成了一个新方法而已, 并没有覆盖.
Final 类
当将一个类声明为 final 时, 也就是说, 你不打算继承该类, 并且也不允许别人这么做, 换句话说, 你对该类的设计用不需要做出任何改动, 你不希望它有子类.
具体的加载顺序:
- public class Demo5 extends Test2 {
- static {
- System.out.println("Demo5 静态加载");
- }
- {
- System.out.println("Demo5 显式加载");
- }
- Demo5() {
- System.out.println("Demo5 已经初始化");
- }
- @Override
- Exception getA() {
- return new NullPointerException();
- }
- public static void main(String[] args) {
- Demo5 demo = new Demo5();
- }
- }
- class Test2 extends Test3 {
- static {
- System.out.println("Test2 静态加载");
- }
- {
- System.out.println("Test2 显式加载");
- }
- Test2() {
- System.out.println("Test2 已经实例化");
- }
- Exception getA() {
- return new Exception();
- }
- }
- class Test3 {
- static {
- System.out.println("Test3 静态加载");
- }
- {
- {
- System.out.println("Test3 显式加载");
- }
- }
- Test3() {
- System.out.println("Test3 已经实例化");
- }
- }
结果:
由此, 可以看出加载的顺序是:
先加载静态代码块(有基类到子类)
再栽在基类的显式代码块以及构造方法
一个原则, 静态优先, 然后从基类到子类以此实例化
多态
什么是多态
在面向对象的语言中, 多态是继抽象, 继承后第三个特点
多态通过分离做什么和怎么做, 从另外一个角度将接口和实现分离开来, 可以改善代码的组织结构和可读性, 还能够创建可扩展的程序.
多态的实质是向上转型, 即对象既可以作为它自己本身的类型使用, 也可以作为他的基类来使用, 即基类的引用可以指向子类.
这样带来的便利就是我们创建一个方法时, 只需要创建基类的引用就可以传递多种不同的子类进去, 这样就实现了多态.
但是, java 是怎么知道如何正确的调用对应的方法的呢?
这就涉及到运行时绑定了.
原来, 出了 final 和 static 方法, 所有的方法都是在后期也就是运行时绑定的, 只有在运行的时候, java 才才会知道到底去调用哪一个方法体.
一句话, 编译时看类型, 运行时看对象.
多态的缺陷
1. 私有方法是无法覆盖的, 有多态的只有接口中可以继承的方法而已.
2. 域和和静态方法.
对于域(成员变量而言, 他的解析是在编译时期进行的, 因此并没有多态)
- public class Demo6 extends hello{
- int a=1;
- public int getA(){
- return this.a;
- }
- public int getSuperA(){
- return super.a;
- }
- public static void main(String[] args) {
- hello hel=new Demo6();
- System.out.println("hello.a="+hel.a+"hel.getA="+hel.getA());
- Demo6 demo =new Demo6();
- System.out.println("Demo6.a="+demo.a+"Demo6.getA="+demo.getA()+"Demo.getSuperA="+demo.getSuperA());
- }
- }
- class hello{
- int a=0;
- public int getA(){
- return this.a;
- }
- }
结果:
3. 静态方法也是没有多态的, 究其原因, 今天方法是属于类的, 而不是属于每个对象实例的.
4. 有时候, 在构造器中调用一个被覆盖的方法时, 会有意想不到的结果.
- public class Deno7 extends test13{
- public void draw(){
- System.out.println("Demo7.draw");
- }
- public static void main(String[] args) {
- Deno7 demo2=new Deno7();
- }
- }
- class test13{
- public void draw(){
- System.out.println("test1.draw");
- }
- test13(){
- draw();
- }
- }
结果是:
明明想调用 test13 的 draw()方法, 但是却返回的是覆盖后的方法避免这类的方法是将 draw()方法设为私有的或者 final 的, 这样就不会不日覆盖了.
接口
抽象类和抽象方法
抽象方法: 仅仅有方法声明, 但是没有方法体.
E:abstract void f();
抽象类: 包含抽象方法的类叫做抽象类.
抽象类不可能被实例化, 否则编译器会报错. 如果继承一个抽象类, 并且想要为该类创建实例, 那么就必须对基类的所有的抽象方法提供方法定义.
(可以这么说, 抽象类不一定包含抽象方法, 但是包含抽象方法一定是抽象类)
接口
1. 接口是一种特殊的抽象类, 它里面所有的方法都是抽象方法, 用 interface 来定义, 没有提供任何具体的实现, 它允许人们通过创建一个能被向上转型为多种基类的类型, 来实现类似多种继承的特性.
2. 接口可以包括成员变量, 但是他们隐式的显示为 static 和 final 的.
3. 接口不可以被类继承, 只可以被类实现,(关键字: implements)且一个类可以同时实现多个接口. 但是接口可以被接口继承, 而且是多种继承.
4. 接口默认定义是 public 的, 其中的方法也默认是 public 的(很简单, 因为接口存在的意义就是用来实现的.)
5. 策略设计模式: 创建一个根据传递参数对象不同而具有不同的行为的方法.
6. 使用接口的核心原因: 可以向上转型为多个基类型以及防止客户端程序员创建该类的对象, 并确保这仅仅是建立一个接口.
7. 在打算组合不同的接口使用相同的方法名通常会造成代码可读性的混乱.
来源: http://www.jianshu.com/p/d434653678ab