Java基础知识(抽象类,接口,内部类)笔记三

抽象类,接口,内部类笔记

抽象类:
1.    将一个类通过abstract修饰 那么这个类称之为抽象类
2.    抽象类可以定义:1、成员变量 2、构造器 3、普通方法 4、抽象方法
3.    思考:抽象类不能实例化,那为什么抽象类中可以定义构造器?

       答:抽象类中的构造器不是给自身使用的,而是用来给子类使用,当子类实例化的时候会先加载父类的构造器再加载自身的构造器。
4.   抽象类不能够实例化,只能被继承,普通类也可以被继承,那为什么还需要抽象类呢?
     答:抽象类的存在就是为了让子类继承的,抽象类可以避免子类设计的随意性,因为子类必须要实现抽象类中的所有抽象方法。同时因为抽象类的存在就是为了继承,所以它也维护了is-a的关系。另外,抽象类也可以使用多态。
5.  当一个类继承自抽象,那么1、它要么实现抽象类中的所有抽象方法。2、要么它自身定义为一个抽象类。
6.  将一个方法通过abstract修饰,那么这个方法称为抽象方法,抽象方法只能存在方法的声明,不能存在方法体。
- 注意:含有抽象方法的类一定是抽象类,但是抽象类不是只含有抽象方法,抽象方法也可能不含有抽象函数。

 抽象方法的小案例(模板方法):
没有使用抽象方法代码:

/*
 * 
 * 喝茶:    烧水        冲泡        等待        干了
 * 喝咖啡:    烧水        搅拌        等待        干了
 */
public class Test2 {
    public static void main(String[] args) {
        
        Tea01 tea=new Tea01();
        tea.fW();
        tea.cp();
        tea.waitting();
        tea.drink();
        
        Cof01 cof=new Cof01();
        cof.fW();
        cof.cp();
        cof.waitting();
        cof.drink();
    }

}
class Tea01{
    public void fW(){
        System.out.println("烧水");
    }
    
    public void cp(){
        System.out.println("冲泡");
    }
    
    public void waitting(){
        System.out.println("等待");
    }
    
    public void drink(){
        System.out.println("干了");
    }
}
class Cof01{
    public void fW(){
        System.out.println("烧水");
    }
    
    public void cp(){
        System.out.println("搅拌");
    }
    
    public void waitting(){
        System.out.println("等待");
    }
    
    public void drink(){
        System.out.println("干了");
    }
}


分析以上的代码存在什么问题:
1. Tea01类与Cof类重复代码太多,不利于维护。
2. 用户在使用Teal01类或者Cof类时,调用函数的顺序可以随意,这是不符合逻辑的。

Tea01 tea=new Tea01();
tea.fW();     //烧水
tea.drink();  //干了
tea.cp();      //冲泡
tea.waitting();   //等待

修改过后的代码如下:

/*
 * 编写模板方法
 * 
 * 喝茶:    烧水        冲泡        等待        干了
 * 喝咖啡:    烧水        搅拌        等待        干了
 */
public class Test02 {
    public static void main(String[] args) {
        
        Drink drink=new Tea();
        drink.flow();
        
        drink=new Cof();
        drink.flow();
    }
}

abstract class Drink{
    //注意此处烧水方法定义为private,外界不能对其进行访问 
    private void fW(){
        System.out.println("烧水");
    }
    //定义一个抽象方法
    protected abstract void cp();
    
    //注意此处等待方法定义为private,外界不能对其进行访问 
    private void waitting(){
        System.out.println("等待");
    }
    
    //注意此处方法定义为private,外界不能对其进行访问 
    private void drink(){
        System.out.println("干了");
    }
    //此方法供外界调用,固定了泡茶的过程
    public void flow(){
        this.fW();
        this.cp();
        this.waitting();
        this.waitting();
    }
}

class Tea extends Drink{
    //实现抽象类中的方法
    public void cp(){
        System.out.println("冲泡。。");
    }
}
class Cof extends Drink{
    //实现抽象类中的方法
    public void cp(){
        System.out.println("搅拌。。");
    }
}

分析修改过后程序的好处:
1. 使用抽象类,使代码得到重用。
2. 屏蔽了底层泡茶与冲咖啡的具体实现,用户不再能随意的调用方法,对用户而言,使用Tea与Cof类更加简单。
3. 使用抽象类可以实现多态。

    注:当然此处代码也还存在问题,我们只是初步的学习模板方法,所以只是写一个简单的例子。

接口:
1. 什么是接口?
    答:接口是一套标准,没有实现。谁遵守谁实现,维护的是have-a的关系。接口比抽象类还要抽象
2. 如何定义接口?
   答: 通过 interface + 接口名
3. 类和接口如何产生关系?

   答:1、通过implements 实现类 implements 接口。2、实现类必须重写接口中的(所有)抽象方法。3、 一个类可以实现多个接口,类 implements 接口1,接口2 ....
4. 接口中的注意事项:
    1. 接口中只能定义public static final修饰的成员变量 
    2.  接口中不允许出现构造器(因为接口是不能创建实例的)
    3.  接口中只能存在抽象方法
    4.  接口中可以定义静态方法(可以包含方法体)
    5.  jdk1.8之后,支持接口中定义default方法(可以拥有方法体)
    6.  接口不能被实例化
    7.  接口是允许多继承的(interface 接口名 extends Serializable,Cloneable,Comparable)
5. 接口好处:
    1.  规范了实现类编码,避免实现类的随意设计(实现了这个接口,就必须要实现接口中所有的抽象方法,实现类不能随意编码。)
    2.  维护了类与类之间的关系(have-a关系)
    3.  抽象类当中可以写静态方法,可以包含方法体。提高了代码的复用性。
    4.  java中只支持单继承,但是通过implements提高了单继承情况下的复用性不强的问题。
    5.  接口变量指向实现类对象,可以实现多态

 内部类:(定义在类里面的类,称之为内部类)
 1. 普通内部类
    注意:1、内部类是外部类的一个成员,同时它也是一个类
               2、普通内部类中不能定义静态的内容,只能定义静态常量,且常量要赋字面值,即编译期常量。
           3、普通内部类在实际运用中不是很多,一般用法为将普通内部类私有化。(源码中private class Itr implements Iterator<E>就是典型的运用)。

常规概念:
1. 外部类访问内部类的成员:创建内部类实例。
2. 其他类访问内部类:

    创建内部类对象:创建时要求必须保证外部类对象存在。
    外部类.内部类 变量名= new 外部类().new 内部类();

3. 内部类访问外部类成员:直接使用(内部类持有外部类的引用;外部类.this)

内部类的小案例:
 

class Outer{
    int num=10;
    class Inner{
        int num=20;
        
        //下面进行分析
        //static int result0=10;错误
        static final int result1=10;正确
        //static final int result2=(int)(Math.random());错误
        
        //外部类、内部类和局部方法中都有num,应该怎么访问。
        public void method(int num){
            //就近原则:此num为局部的num
            System.out.println("Outer01.Inner.enclosing_method()"+num);
            //this.num为当前类的num
            System.out.println("Outer01.Inner.enclosing_method()"+this.num);
            //内部类持有外部类的引用使用Outer.this.num访问外部类的num
            System.out.println("Outer01.Inner.enclosing_method()"+Outer.this.num);
        }
    }
    
}

代码分析:

    1. static int result0=10;错误;
    结论:非静态的内部类不能定义静态的成员。
    分析:
    第一:java虚拟机要求所有的静态变量在类加载过程中的初始阶段将符号引用变成直接引用,也就意味着内部类中的静态内容要先于内部类对象存在,因为静态内容是属于类的,是类层面上的。
    第二:由于内部类是依附于外部类对象的,那么就意味着内部类中的静态内容是依附于外部类对象。但是内部类中的静态内容是不需要实例的。这两者相互矛盾。也就意味着内部类无法做到在没有外部类实例的情况下而直接使用,所以非静态的内部类不能定义静态的成员。
    2.static final int result1=10;正确;
    结论:我们可以在非静态的内部类中定义静态常量,静态常量是编译期常量。
    分析:
    第一:变量被final static修饰后,由于所赋的值是字面常量,而此时字面常量在编译阶段就确定,我们把这样的常量称之为编译期常量。而编译期常量是不需要加载的类字节码文件的,很多书上将这一步称之为编译期常量折叠【编译器在编译阶段通过语法分析计算出常量表达式的具体值】
    第二:编译期常量不需要加载字节码文件,也就是说编译期常量不会导致类加载。那么基于这点,静态常量在非静态内部类中的定义是合法的。
    3.static final int result2=(int)(Math.random());错误;
    结论:在非静态的内部类中不能定义非编译期的静态常量。
    分析:
    此处赋值,不再是编译期常量,而是非编译期常量,对于这样的值而言编译期无法折叠,编译器只会做一些语法的检查。既然无法确定值,那么就会导致该常量需要类加载时确定。与上面static int result0=10;一样,非静态的内部类不能定义静态的非编译期常量。
    拓展:有些类中定义字符串会通过staticfinal修饰的原因:通过static final修饰,无需导致类加载,在一定程度上降低了内存的消耗。
2. 方法内部类
    方法内部类可以减少类的生命周期,缩减内存开销,外界不能访问
    思考:为什么外部不能访问呢?
    答: 因为该类为方法内部类,属于局部的,出了方法就无效

3.静态内部类
    注意:静态内部类是外部类的静态成员,同时也是一个类
    1、外部类访问内部类的成员:
        1. 创建内部类实例 (访问非静态)  
        2.类名. (访问静态)
    2、其他类访问内部类:
        1.访问内部类的静态内容:外部类.内部类.静态内容
        2.访问内部类的非静态内容:外部类.内部类 变量名 = new 外部类.内部类();
    3、内部类访问外部类成员:
        1.创建外部类实例(访问非静态)
        2.直接访问(访问静态)
 4.匿名内部类
    可以用来创建接口的实现类对象和抽象类的子类对象,而这些对象只能使用一次。
    new 接口/抽象类(){
        实现所有的抽象方法;
    };

内部类思考:
    思考:java中为什么需要内部类?
    答:为了更好的面向对象。当一个类中含有的内容很多时,假如在它当中存在着一些站在面向对象的逻辑角度而言不应该属于他的内容,那么此时为了更好的面向对象,在这个类当中将本不属于它的内容抽象成一个内部类。
    
使用静态内部类实现单例模式小案例:
单例类:

public class SingltonStaticInner {
    //静态内部类
    private static class SingleHolder{
        private static SingltonStaticInner single=new SingltonStaticInner();
    }
    
    //将构造器进行私有化
    private SingltonStaticInner() {
        System.out.println("构造器");
    }
    
    //提供外界获取实例的方法
    public static  SingltonStaticInner getInstance(){
        return SingleHolder.single;
    }
    //此方法用于验证是否可以延迟加载
    public static void add(){
        System.out.println("如果调用此方法,没有加载构造器说明会延迟加载!");
    }

}

验证类:

public static void main(String[] args) {
        /*SingltonStaticInner s1=SingltonStaticInner.getInstance();
        SingltonStaticInner s2=SingltonStaticInner.getInstance();
        //可以验证s1与s2是同一个对象
        System.out.println(s1);
        System.out.println(s2);*/
        
        SingltonStaticInner.add();
    }

结果:

如果调用此方法,没有加载构造器说明会延迟加载!

代码分析:

    首先通过静态内部类可以实现单例,此种单例的优点为1、天然线程安全;2、可以实现延迟加载。那么是为什么呢?
    1.  因为创建的对象的静态的,在类还未加载就已经创建,不存在多线程创建对象实例的线程安全问题。
    2.  为什么可以延迟加载呢?如果外界通过类名.add()方法进行调用,因为add()为静态方法,对它进行调用就会导致类加载。那么类加载就会导致类中的静态内容被加载,那么private static class SingleHolder也应该被加载,那么就会创建实例对象吗?我开始就是这么认为的。因为我们忘记了一个很重要的地方private static class SingleHolder这是一个类。我们并没有主动的使用SingleHolder内部类,所以SingleHolder内部类并不会被加载,自然就不会创建实例了。
    

猜你喜欢

转载自blog.csdn.net/ToBe_Coder/article/details/81416991