面向对象(一)

方法覆盖(Overriding)和方法重载(Overloading)

方法覆盖(Overriding)

运行时多态
如果在子类中定义一个方法,其名称、返回类型及参数签名正好与父类中某个方法的名称、返回类型及参数签名相匹配,那么可以说,子类的方法覆盖了父类的方法。

  • 子类的方法名称及参数签名,必须与父类的一致(两同)
  • 子类方法不能缩小父类方法的访问权限(一大)
  • 子类方法不能抛出比父类方法更多的异常(两小)(里氏代换原则)
  • 子类方法的返回类型必须要小于或等于父类的返回值类型(两小)
  • 父类的静态方法不能被子类覆盖为非静态方法
  • 子类可以定义与父类的静态方法同名的静态方法,以便在子类中隐藏父类的静态方法(满足覆盖约束)
  • Java虚拟机把静态方法和所属的类绑定,而把实例方法和所属的实例绑定。
  • 父类的非静态方法不能被子类覆盖为静态方法
  • 父类的私有方法不能被子类覆盖
  • 父类的抽象方法可以被子类通过两种途径覆盖(即实现和覆盖)
  • 父类的非抽象方法可以被覆盖为抽象方法
  • Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。

方法重载(Overloading)

编译时多态

  • 在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况
  • 方法返回类型,修饰符可以相同也可以不相同

面向对象的"六原则一法则"

单一职责原则

一个类只做它该做的事情。

单一职责原则想表达的就是"高内聚",写代码最终极的原则只有六个字"高内聚、低耦合",所谓的高内聚就是一个代码模块只完成一项功能,在面向对象中,如果只让一个类完成它该做的事,而不涉及与它无关的领域就是践行了高内聚的原则,这个类就只有单一职责。另一个是模块化,好的自行车是组装车,从减震叉、刹车到变速器,所有的部件都是可以拆卸和重新组装的,好的乒乓球拍也不是成品拍,一定是底板和胶皮可以拆分和自行组装的,一个好的软件系统,它里面的每个功能模块也应该是可以轻易的拿到其他系统中使用的,这样才能实现软件复用的目标。

开闭原则

软件实体应当对扩展开放,对修改关闭。

在理想的状态下,当我们需要为一个软件系统增加新功能时,只需要从原来的系统派生出一些新类就可以,不需要修改原来的任何一行代码。要做到开闭有两个要点:①抽象是关键,一个系统中如果没有抽象类或接口系统就没有扩展点;②封装可变性,将系统中的各种可变因素封装到一个继承结构中,如果多个可变因素混杂在一起,系统将变得复杂而换乱。

依赖倒转原则

面向接口编程。

该原则说得直白和具体一些就是声明方法的参数类型、方法的返回类型、变量的引用类型时,尽可能使用抽象类型而不用具体类型,因为抽象类型可以被它的任何一个子类型所替代,请参考下面的里氏替换原则。

里氏替换原则

任何时候都可以用子类型替换掉父类型。

关于里氏替换原则的描述,Barbara Liskov女士的描述比这个要复杂得多,但简单的说就是能用父类型的地方就一定能使用子类型。里氏替换原则可以检查继承关系是否合理,如果一个继承关系违背了里氏替换原则,那么这个继承关系一定是错误的,需要对代码进行重构。例如让猫继承狗,或者狗继承猫,又或者让正方形继承长方形都是错误的继承关系,因为你很容易找到违反里氏替换原则的场景。需要注意的是:子类一定是增加父类的能力而不是减少父类的能力,因为子类比父类的能力更多,把能力多的对象当成能力少的对象来用当然没有任何问题。

接口隔离原则

接口要小而专,绝不能大而全。

臃肿的接口是对接口的污染,既然接口表示能力,那么一个接口只应该描述一种能力,接口也应该是高度内聚的。例如,琴棋书画就应该分别设计为四个接口,而不应设计成一个接口中的四个方法,因为如果设计成一个接口中的四个方法,那么这个接口很难用,毕竟琴棋书画四样都精通的人还是少数,而如果设计成四个接口,会几项就实现几个接口,这样的话每个接口被复用的可能性是很高的。Java中的接口代表能力、代表约定、代表角色,能否正确的使用接口一定是编程水平高低的重要标识。

合成聚合复用原则

优先使用聚合或合成关系复用代码。

通过继承来复用代码是面向对象程序设计中被滥用得最多的东西,因为所有的教科书都无一例外的对继承进行了鼓吹从而误导了初学者,类与类之间简单的说有三种关系,Is-A关系、Has-A关系、Use-A关系,分别代表继承、关联和依赖。其中,关联关系根据其关联的强度又可以进一步划分为关联、聚合和合成,但说白了都是Has-A关系,合成聚合复用原则想表达的是优先考虑Has-A关系而不是Is-A关系复用代码,即使在Java的API中也有不少滥用继承的例子,例如Properties类继承了Hashtable类,Stack类继承了Vector类,这些继承明显就是错误的,更好的做法是在Properties类中放置一个Hashtable类型的成员并且将其键和值都设置为字符串来存储数据,而Stack类的设计也应该是在Stack类中放一个Vector对象来存储数据。记住:任何时候都不要继承工具类,工具是可以拥有并可以使用的,而不是拿来继承的。

迪米特法则

迪米特法则又叫最少知识原则

一个对象应当对其他对象有尽可能少的了解。再复杂的系统都可以为用户提供一个简单的门面,Java Web开发中作为前端控制器的Servlet或Filter不就是一个门面吗,浏览器对服务器的运作方式一无所知,但是通过前端控制器就能够根据你的请求得到相应的服务。调停者模式也可以举一个简单的例子来说明,例如一台计算机,CPU、内存、硬盘、显卡、声卡各种设备需要相互配合才能很好的工作,但是如果这些东西都直接连接到一起,计算机的布线将异常复杂,在这种情况下,主板作为一个调停者的身份出现,它将各个设备连接在一起而不需要每个设备之间直接交换数据,这样就减小了系统的耦合度和复杂度。

反射

主要参考网址1 网址2

概述

当程序运行时,允许改变程序结构或变量类型,这种语言称为动态语言。我们认为java并不是动态语言,但是它却有一个非常突出的动态相关机制,俗称:反射。

Java 反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为 Java 的反射机制。

所谓反射其实是获取类的字节码文件,也就是.class文件,那么我们就可以通过Class这个对象进行获取。

获取字节码的三种方式

  • 通过 Object 类中的 getClass() 方法,想要用这种方法必须要明确具体的类并且创建该类的对象。
  • 所有数据类型都具备一个静态的属性.class 来获取对应的 Class 对象。但是还是要明确到类,然后才能调用类中的静态成员。
  • 只要通过给定类的字符串名称就可以获取该类的字节码对象,这样做扩展性更强。通过 Class.forName()方法完成,必须要指定类的全限定名,由于前两种方法都是在知道该类的情况下获取该类的字节码对象,因此不会有异常,但是Class.forName() 方法如果写错类的路径会报 ClassNotFoundException 的异常。
package com.jy.reflect;
public class ReflectTest {
    public static void main(String[] args) {
        Fruit fruit = new Fruit();
        
        Class<?> class1 = fruit.getClass();     //方法一,返回一个对象的运行时类,即通过类的实例获取该类的字节码文件对象,该类处于创建对象阶段 
        
        //方法二,当类被加载成.class文件时,此时Person类变成了.class,在获取该字节码文件对象,也就是获取自己, 该类处于字节码阶段。
        Class<?> class2 = Fruit.class;

        Class class3 = null;     
        try {    
            //方法三, 通过Class类中的静态方法forName,直接获取到一个类的字节码文件对象,此时该类还是源文件阶段,并没有变为字节码文件。            
            // 如果这里不指定类所在的包名会报 ClassNotFoundException 异常。
            class3 = Class.forName("com.jy.reflect.Fruit");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        System.out.println(class1 + "  " +class2 + "    " + class3);

    }
}

class Fruit{}

通过反射机制获取类信息

通过反射机制创建对象,在创建对象之前要获得对象的构造函数对象,通过构造函数对象创建对应类的实例。

package com.jy.reflect;
import java.lang.reflect.Constructor;

public class ReflectTest {
    public static void main(String[] args) throws Exception {

        Class clazz = null;
        clazz = Class.forName("com.jy.reflect.Fruit");
        Constructor<Fruit> constructor1 = clazz.getConstructor();
        Constructor<Fruit> constructor2 = clazz.getConstructor(String.class);

        Fruit fruit1 = constructor1.newInstance();
        Fruit fruit2 = constructor2.newInstance("Apple");

    }
}

class Fruit{
    public Fruit(){
        System.out.println("无参构造器 Run...........");
    }
    public Fruit(String type){
        System.out.println("有参构造器 Run..........." + type);
    }

}
输出:
无参构造器 Run……….. 
有参构造器 Run………..Apple

通过反射机制获取 Class 中的属性

package com.jy.reflect;

import java.lang.reflect.Field;

public class ReflectTest {
    public static void main(String[] args) throws Exception {

        Class<?> clazz = null;
        Field field = null;

        clazz = Class.forName("com.jy.reflect.Fruit");
        //field = clazz.getField("num");       getField() 方法不能获取私有的属性
        // field = clazz.getField("type");     访问私有字段时会报 NoSuchFieldException 异常
        field = clazz.getDeclaredField("type");     //获取私有 type 属性
        field.setAccessible(true);  //对私有字段的访问取消检查
        Fruit fruit = (Fruit) clazz.newInstance();  //创建无参对象实例
        field.set(fruit,"Apple");   //为无参对象实例属性赋值
        Object type = field.get(fruit); //通过 fruit 对象获取属性值

        System.out.println(type);
    }
}

class Fruit{
    public int num;
    private String type;

    public Fruit(){
        System.out.println("无参构造器 Run...........");
    }
    public Fruit(String type){
        System.out.println("有参构造器 Run..........." + type);
    }

}
输出:
无参构造器 Run...........
Apple

通过反射机制获取 Class 中的方法并运行。

package com.jy.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class ReflectTest {
    public static void main(String[] args) throws Exception {

        Class clazz = null;
        Method method = null;

        clazz = Class.forName("com.jy.reflect.Fruit");
        Constructor<Fruit> fruitConstructor = clazz.getConstructor(String.class);
        Fruit fruit = fruitConstructor.newInstance("Apple");    //创建有参对象实例

        method = clazz.getMethod("show",null);  //获取空参数 show 方法
        method.invoke(fruit,null);  //执行无参方法

        method = clazz.getMethod("show",int.class); //获取有参 show 方法
        method.invoke(fruit,20);  //执行有参方法

    }
}

class Fruit{
    private String type;

    public Fruit(String type){
        this.type = type;
    }
    public void show(){
        System.out.println("Fruit type = " + type);
    }
    public void show(int num){
        System.out.println("Fruit type = " + type + ".....Fruit num = " + num);
    }
}

反射机制简单应用(使用简单工厂创建对象)

Class.forName() 生成的结果是在编译时不可知的,因此所有的方法特征签名信息都是在执行时被提取出来的。反射机制能过创建一个在编译期完全未知的对象,并调用该对象的方法。

以下是反射机制与泛型的一个应用,通过一个工厂类创建不同类型的实例。

要创建对象的实例类 Apple :

package com.jy.reflect;

public interface Fruit {}
class Apple implements Fruit{}

加载的配置文件 config.properties:

Fruit=com.jy.reflect.Apple

工厂类 BasicFactory :

package com.jas.reflect;

import java.io.FileReader;
import java.util.Properties;

public class BasicFactory {
    private BasicFactory(){}

    private static BasicFactory bf = new BasicFactory();
    private static Properties pro = null;

    static{
        pro = new Properties();
        try{    
            //通过类加载器加载配置文件
            pro.load(new FileReader(BasicFactory.class.getClassLoader().
                    getResource("config.properties").getPath()));
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static BasicFactory getFactory(){
        return bf;
    }

    //使用泛型获得通用的对象
    public  <T> T newInstance(Class<T> clazz){
        String cName = clazz.getSimpleName();   //获得字节码对象的类名
        String clmplName = pro.getProperty(cName);   //根据字节码对象的类名通过配置文件获得类的全限定名

        try{
            return (T)Class.forName(clmplName).newInstance();   //根据类的全限定名创建实例对象
        }catch (Exception e) {
            throw new RuntimeException(e);
        }

    }
}

创建对象实例:

package com.jy.reflect;

public class ReflectTest {
    public static void main(String[] args) throws Exception {
        Fruit fruit = BasicFactory.getFactory().newInstance(Fruit.class);
        System.out.println(fruit);
    }
}

上面这个实例通过一个工厂创建不同对象的实例,通过这种方式可以降低代码的耦合度,代码得到了很大程度的扩展,以前要创建 Apple 对象需要通过 new 关键字创建 Apple 对象,如果我们也要创建 Orange 对象呢?是不是也要通过 new 关键字创建实例并向上转型为 Fruit ,这样做是麻烦的。

现在我们直接有一个工厂,你只要在配置文件中配置你要创建对象的信息,你就可以创建任何类型你想要的对象,是不是简单很多了呢?可见反射机制的价值是很惊人的。

Spring 中的 IOC 的底层实现原理就是反射机制,Spring 的容器会帮我们创建实例,该容器中使用的方法就是反射,通过解析 xml 文件,获取到 id 属性和 class 属性里面的内容,利用反射原理创建配置文件里类的实例对象,存入到 Spring 的 bean 容器中。

内部类(参考网址

成员内部类

成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:

class Circle {
    private double radius = 0;
    public static int count =1;
    public Circle(double radius) {
        this.radius = radius;
    }
     
    class Draw {     //内部类
        public void drawSahpe() {
            System.out.println(radius);  //外部类的private成员
            System.out.println(count);   //外部类的静态成员
        }
    }
}

这样看起来,类Draw像是类Circle的一个成员,Circle称为外部类。

  • 成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。
  • 不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:
外部类.this.成员变量
外部类.this.成员方法
  • 虽然成员内部类可以无条件地访问外部类的成员,而外部类想访问成员内部类的成员却不是这么随心所欲了。在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问:
public class Test {
    public static void main(String[] args)  {
        
        Outter outter = new Outter();
        
        //第一种方式:
        Outter.Inner inner = outter.new Inner();  //必须通过Outter对象来创建
         
        //第二种方式:
        Outter.Inner inner1 = outter.getInnerInstance();
    }
}
 
class Outter {
    private Inner inner = null;
    public Outter() {
         
    }
     
    public Inner getInnerInstance() {
        if(inner == null)
            inner = new Inner();
        return inner;
    }
      
    class Inner {
        public Inner() {
             
        }
    }
}

成员内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。
比如上面的例子,如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;如果是默认访问权限,则只能在同一个包下访问。
这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。我个人是这么理解的,由于成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰。

局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

class People{
    public People() {
         
    }
}
 
class Man{
    public Man(){
         
    }
     
    public People getWoman(){
        class Woman extends People{   //局部内部类
            int age =0;
        }
        return new Woman();
    }
}

匿名内部类

匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护。匿名内部类也是不能有访问修饰符和static修饰符的。下面这段代码是一段Android事件监听代码:

scan_bt.setOnClickListener(new OnClickListener() {
    
    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
         
    }
}); 

匿名内部类是唯一一种没有构造器的类。正因为其没有构造器,所以匿名内部类的使用范围非常有限,大部分匿名内部类用于接口回调。匿名内部类在编译的时候由系统自动起名为Outter$1.class。一般来说,匿名内部类用于继承其他类或是实现接口,并不需要增加额外的方法,只是对继承方法的实现或是重写。

即匿名内部类

  • 匿名内部类是没有名字没有构造方法没有访问修饰符的内部类。
  • 因为没有名字,所以匿名内部类只能使用一次,通常用来简化代码编写。
  • 使用匿名内部类有个前提条件:必须继承一个父类或者实现一个接口。

匿名内部类如何访问在其外面定义的变量:匿名内部类不能访问外部类方法中的局部变量,除非该变量被声明为final类型

静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

public class Test {
    public static void main(String[] args)  {
        Outter.Inner inner = new Outter.Inner();
    }
}
 
class Outter {
    public Outter() {
         
    }
     
    static class Inner {
        public Inner() {
             
        }
    }
}

深入理解

1 为什么成员内部类可以无条件访问外部类的成员?
编译器在进行编译的时候,会将成员内部类单独编译成一个字节码文件。编译器会默认为成员内部类添加了一个指向外部类对象的引用。利用反编译,可以通过字节码看到内部类的构造器
public com.jy.Circle$Draw(com.jy.Circle);
从这里可以看出,虽然我们在定义的内部类的构造器是无参构造器,编译器还是会默认添加一个参数,该参数的类型为指向外部类对象的一个引用,所以成员内部类中的Draw this&0指针便指向了外部类对象,因此可以在成员内部类中随意访问外部类的成员。从这里也间接说明了成员内部类是依赖于外部类的,如果没有创建外部类的对象,则无法对Circle this&0引用进行初始化赋值,也就无法创建成员内部类的对象了。
为什么局部内部类和匿名内部类只能访问局部final变量?

public class Test {
    public static void main(String[] args)  {
         
    }
     
    public void test(final int b) {
        final int a = 10;
        new Thread(){
            public void run() {
                System.out.println(a);
                System.out.println(b);
            };
        }.start();
    }
}

这段代码会被编译成两个class文件:
在这里插入图片描述
  当test方法执行完毕之后,变量a的生命周期就结束了,而此时Thread对象的生命周期很可能还没有结束,那么在Thread的run方法中继续访问变量a就变成不可能了,但是又要实现这样的效果,怎么办呢?Java采用了 复制 的手段来解决这个问题。
其中Test$1用javap工具可以得到如下反汇编代码

class Test01.Test$1 extends java.lang.Thread {
  final int val$b;

  final Test01.Test this$0;

  Test01.Test$1(Test01.Test, int);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LTest01/Test;
       5: aload_0
       6: iload_2
       7: putfield      #2                  // Field val$b:I
      10: aload_0
      11: invokespecial #3                  // Method java/lang/Thread."<init>":()V
      14: return

  public void run();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: bipush        10
       5: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
       8: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_0
      12: getfield      #2                  // Field val$b:I
      15: invokevirtual #5                  // Method java/io/PrintStream.println:(I)V
      18: return
}

final Test01.Test this$0;为指向外部类对象的指针。

final int val$b;为参数b,

Test01.Test$1(Test01.Test, int);我们看到匿名内部类Test$1的构造器含有两个参数,一个是指向外部类对象的引用,一个是int型变量,很显然,这里是将变量test方法中的形参b以参数的形式传进来对匿名内部类中的拷贝(变量a的拷贝)进行赋值初始化。

3: bipush 10这条指令表示将操作数10压栈,表示使用的是一个本地局部变量。这个过程是在编译期间由编译器默认进行,如果这个变量的值在编译期间可以确定,则编译器默认会在匿名内部类(局部内部类)的常量池中添加一个内容相等的字面量或直接将相应的字节码嵌入到执行字节码中。这样一来,匿名内部类使用的变量是另一个局部变量,只不过值和方法中局部变量的值相等,因此和方法中的局部变量完全独立开。

由此可以得出:局部变量的值在编译期间就可以确定,则直接在匿名内部里面创建一个拷贝。如果局部变量的值无法在编译期间确定,则通过构造器传参的方式来对拷贝进行初始化赋值。

从上面可以看出,在run方法中访问的变量a根本就不是test方法中的局部变量a。这样一来就解决了前面所说的 生命周期不一致的问题。但是新的问题又来了,既然在run方法中访问的变量a和test方法中的变量a不是同一个变量,当在run方法中改变变量a的值的话,会出现什么情况?

对,会造成数据不一致性,这样就达不到原本的意图和要求。为了解决这个问题,java编译器就限定必须将变量a限制为final变量,不允许对变量a进行更改(对于引用类型的变量,是不允许指向新的对象),这样数据不一致性的问题就得以解决了。

异常处理(参考网址

异常分类

Java语言设计者将异常划分为两类:Error和Exception,其体系结构大致如下图所示
![在这里插入图片描述](https://img-blog.csdni
Throwable有两个重要的子类:Exception(异常) 和Error(错误),两者都包含了大量的异常处理类。

  • Error:是程序中无法处理的错误,表示运行应用程序中出现了严重的错误。此类错误一般表示代码运行时JVM出现问题。通常有Virtual MachineError(虚拟机运行错误)、NoClassDefFoundError(类定义错误)等。比如说当jvm耗完可用内存时,将出现OutOfMemoryError。此类错误发生时,JVM将终止线程。
  • Exception(异常):程序本身可以捕获并且可以处理的异常。即可以被Java异常处理机制使用,是异常处理的核心。

Exception这种异常又分为两类:运行时异常和编译异常。

  • 运行时异常(不受检异常):RuntimeException类及其子类表示JVM在运行期间可能出现的错误。
    这样的异常发生的原因多半是代码写的有问题。如除0错误ArithmeticException,数组索引越界ArrayIndexOutOfBoundsException,使用了空对象NullPointerException等等。此类异常属于不可查异常,一般是由程序逻辑错误引起的,在程序中可以选择捕获处理,也可以不处理。
  • 检查异常(受检异常):Exception中除RuntimeException及其子类之外的异常。如果程序中出现此类异常,比如说IOException,必须对该异常进行处理,否则编译不通过。

可查异常与不可查异常:java的所有异常可以分为可查异常(checked exception)和不可查异常(unchecked exception)。

  • 可查异常:编译器要求必须处理的异常。正确的程序在运行过程中,经常容易出现的、符合预期的异常情况。一旦发生此类异常,就必须采用某种方式进行处理。除RuntimeException及其子类外,其他的Exception异常都属于可查异常。编译器会检查此类异常,也就是说当编译器检查到应用中的某处可能会此类异常时,将会提示你处理本异常——要么使用try-catch捕获,要么使用throws语句抛出,否则编译不通过。
  • 不可查异常:编译器不会进行检查并且不要求必须处理的异常,也就说当程序中出现此类异常时,即使我们没有try-catch捕获它,也没有使用throws抛出该异常,编译也会正常通过。该类异常包括运行时异常(RuntimeException极其子类)和错误(Error)。

异常处理流程

对于不同的异常,java采用不同的异常处理方式:

  • 运行异常将由系统自动抛出,应用本身可以选择处理或者忽略该异常。
  • 对于方法中产生的Error,该异常一旦发生JVM将自行处理该异常,因此java允许应用不抛出此类异常。
  • 对于所有的可查异常,必须进行捕获或者抛出该方法之外交给上层处理。也就是当一个方法存在异常时,要么使用try-catch捕获,要么使用该方法使用throws将该异常抛调用该方法的上层调用者。

抛出异常:当一个方法出现错误而引发异常时,该方法会将该异常类型以及异常出现时的程序状态信息封装为异常对象,并交给本应用。运行时,该应用将寻找处理异常的代码并执行。任何代码都可以通过throw关键词抛出异常,比如java源代码抛出异常、自己编写的代码抛出异常等。

捕获异常:一旦方法抛出异常,系统自动根据该异常对象寻找合适异常处理器(Exception Handler)来处理该异常。所谓合适类型的异常处理器指的是异常对象类型和异常处理器类型一致。

1 捕获异常
try-catch

		try {

			// 可能产生的异常的代码区,也成为监控区

		} catch (ExceptionType1 e) {

			// 捕获并处理try抛出异常类型为ExceptionType1的异常

		} catch (ExceptionType2 e) {

			// 捕获并处理try抛出异常类型为ExceptionType2的异常

		}

监控区一旦发生异常,则会根据当前运行时的信息创建异常对象,并将该异常对象抛出监控区,同时
系统根据该异常对象依次匹配catch子句,若匹配成功(抛出的异常对象的类型和catch子句的异常类的类型或者是该异常类的子类的类型一致),则运行其中catch代码块中的异常处理代码,一旦处理结束,那就意味着整个try-catch结束。含有多个catch子句,一旦其中一个catch子句与抛出的异常对象类型一致时,其他catch子句将不再有匹配异常对象的机会。

try-catch-finally

 try {

       //可能产生的异常的代码区

   }catch (ExceptionType1 e) {

       //捕获并处理try抛出异常类型为ExceptionType1的异常

   }catch (ExceptionType2 e){

       //捕获并处理try抛出异常类型为ExceptionType2的异常

   }finally{

       //无论是出现异常,finally块中的代码都将被执行

   }

try代码块:用于捕获异常。其后可以接零个或者多个catch块。如果没有catch块,后必须跟finally块,来完成资源释放等操作,另外建议不要在finally中使用return,不要尝试通过catch来控制代码流程。

catch代码块:用于捕获异常,并在其中处理异常。

finally代码块:无论是否捕获异常,finally代码总会被执行。如果try代码块或者catch代码块中有return语句时,finally代码块将在方法返回前被执行。注意以下几种情况,finally代码块不会被执行:
1、 在前边的代码中使用System.exit()退出应用。
2、 程序所在的线程死亡或者cpu关闭
3、 如果在finally代码块中的操作又产生异常,则该finally代码块不能完全执行结束,同时该异常会覆盖前边抛出的异常。

2 抛出异常
throws抛出异常:如果一个方法可能抛出异常,但是没有能力处理该异常或者需要通过该异常向上层汇报处理结果,可以在方法声明时使用throws来抛出异常。

throw抛出异常:在方法内,用throw来抛出一个Throwable类型的异常。一旦遇到到throw语句,后面的代码将不被执行。然后,便是进行异常处理——包含该异常的try-catch最终处理,也可以向上层抛出。注意我们只能抛出Throwable类和其子类的对象。

可以在一个成员函数调用的外面写一个try语句,在这个成员函数内部写另一个try语句保护其他代码。每当遇到一个try语句,”异常“的框架就放到堆栈上面,直到所有的try语句都完成。如果下一级的try语句没有对某种”异常”进行处理,堆栈就会展开,直到遇到有处理这种”异常”的try语句。

abstract class和interface

参数抽象类接口
默认的方法实现它可以有默认的方法实现接口完全是抽象的。它根本不存在方法的实现
实现子类使用 extends 关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。子类使用关键字 implements 来实现接口。它需要提供接口中所有声明的方法的实现
构造器抽象类 可以有构造器接口 不能有构造器
与正常Java类的区别除了你不能实例化抽象类之外,它和普通Java类没有任何区别接口是完全不同的类型
访问修饰符抽象方法可以有 publicprotecteddefault 这些修饰符接口方法默认修饰符是 public 。你不可以使用其它修饰符。
main方法抽象方法可以有main方法并且我们可以运行它接口没有main方法,因此我们不能运行它。
多继承抽象方法可以继承一个类和实现多个接口接口只可以继承一个或多个其它接口
速度它比接口速度要快接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。如果你往接口中添加方法,那么你必须改变实现该接口的类。

接口只可以定义public static final成员变量。方法会被隐式地指定为public abstract方法且只能是public abstract方法

  • public:接口可以被其他接口继承,也可以被类实现,类与接口、接口与接口可能会形成多层级关系,采用public可以满足变量的访问范围;
  • static:如果变量不是static的,那么接口必须实例化才可以访问自己的变量。实现类初始化的时候是不会去初始化所谓的基类(interface)对象的,interface也没有构造函数的实现,故非static的变量是无效的;
  • final:如果变量不是final的,而方法是abstract的,因此接口中的方法又不可以修改变量值,虽然可以直接修改静态成员变量,但所有实现类对应的值都被修改了,此做法等同于抽象类,故需要final修饰成员变量;

两者设计层面上的区别:

  • 抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。抽象类是对整个类整体进行抽象,包括属性、行为,但是接口却是对类局部(行为)进行抽象。举个简单的例子,飞机和鸟是不同类的事物,但是它们都有一个共性,就是都会飞。那么在设计的时候,可以将飞机设计为一个类Airplane,将鸟设计为一个类Bird,但是不能将飞行 这个特性也设计为类,因此它只是一个行为特性,并不是对一类事物的抽象描述。此时可以将 飞行 设计为一个接口Fly,包含方法fly(),然后Airplane和Bird分别根据自己的需要实现Fly这个接口。然后至于有不同种类的飞机,比如战斗机、民用飞机等直接继承Airplane即可,对于鸟也是类似的,不同种类的鸟直接继承Bird类即可。从这里可以看出,继承是一个"是不是"的关系,而 接口 实现则是"有没有"的关系。如果一个类继承了某个抽象类,则子类必定是抽象类的种类,而接口实现则是有没有、具备不具备的关系,比如鸟是否能飞(或者是否具备飞行这个特点),能飞行则可以实现这个接口,不能飞行就不实现这个接口。
  • 设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。什么是模板式设计?最简单例子,大家都用过ppt里面的模板,如果用模板A设计了ppt B和ppt C,ppt B和ppt C公共的部分就是模板A了,如果它们的公共部分需要改动,则只需要改动模板A就可以了,不需要重新对ppt B和ppt C进行改动。而辐射式设计,比如某个电梯都装了某种报警器,一旦要更新报警器,就必须全部更新。也就是说对于抽象类,如果需要添加新的方法,可以直接在抽象类中添加具体的实现,子类可以不进行变更;而对于接口则不行,如果接口进行了变更,则所有实现这个接口的类都必须进行相应的改动
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值