第十四天

可以将一个类的定义放在另一个类的定义内部,这就是内部类。

内部类是一个非常有用的特性但又比较难理解和难使用的特性

1、什么是内部类:

 内部类我们从外面看是非常容易理解的,无非就是在一个类的内部在定义一个类。

public class OutClass {   //内部类和外部类虽然是写在一个文件里,编译的时候会分别生成两个class文件
    private String name ;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    class InnerClass{  //内部类
        public InnerClass() {  //内部类里的构造器
            //在内部类里边可以直接访问外部类里的任何成员,name,age 他们是private修饰的属性
            name = "张三";
            age = 18;
            System.out.println(getName());
            System.out.println(getAge());
        }
    }
}

在这里InnerClass就是内部类,对于初学者来说内部类实在是使用的不多,但是随着编程能力的提高,我们会领悟到它的魅力所在,它能够更加优雅的设计我们的程序结构。学习使用内部类,我们需要明白为什么要使用内部类,内部类能够为我们带来什么样的好处。

2、为什么要使用内部类?

在《Java编程思想》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响

在我们程序设计中有时候会存在一些使用一个接口很难解决的问题,这个时候我们可以利用内部类来继承多个接口或类来解决这些程序设计问题。可以这样说,一个接口只是解决了部分问题,而内部类使得可以多继承很多接口或父类,从而能解决全部问题

public interface Father {
}
public interface Mather {
}
  //同时需要继承两个接口
//一种写法 public class Son implements Father,Mather{
    public class Son implements Father{
        class Mather_ implements Mather{    
        }
}

其实对于这个实例我们确实是看不出来使用内部类存在何种优点,但是如果FatherMother不是接口,而是抽象类或者具体类呢?这个时候我们就只能体会到内部类实现多重继承的好处,因为每个类只能有一个父类。

 

public class Father {
}
public class Mather {
}
    public class Son extends Father{ //类与类之间的继承都是单继承
     class Mather_ extends Mather{
         //在这个内部类里边,我们相当于同时继承了Father和Mather
     }
     //。。。。。定义很多个内部类
}

其实使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,但是如果我们不需要解决多重继承问题,那么我们自然可以使用其他的编码方式,但是使用内部类还能够为我们带来如下特性(摘自《Java的编程思想》):

      1内部类可以用多个实例,每个实例都有自己的状态信息,并且与其外围对象的信息相互独立。

      2在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类

      3内部类提供了更好的封装,除了该外围类,其他类都不能访问。

 

3、内部类的基本语法,java代码中怎么实例化内部类,它和外围类怎么联系?

在这个部分主要介绍内部类如何使用外部类的属性和方法,以及使用.this.new

当我们在创建一个内部类的时候,它无形中就与外围类有了一种联系,依赖于这种联系,它可以无限制地访问外围类的元素,哪怕是private修饰的元素。

public class OutClass {   //内部类和外部类虽然是写在一个文件里,编译的时候会分别生成两个class文件
    private String name ;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
    class InnerClass{  //内部类
        public InnerClass() {  //内部类里的构造器
            //在内部类里边可以直接访问外部类里的任何成员,name,age 他们是private修饰的属性
            name = "张三";
            age = 18;
        }
        
        public void innerMethod() {
            System.out.println(getName());
            System.out.println(getAge());
        }
        public OutClass getOuterClass() {
            return OutClass.this;//OutClass.this;表示一个外围类的对象的引用
        }
    }
    
    public static void main(String[] args) {
        OutClass out = new OutClass();//实例化了一个外部类
        OutClass.InnerClass inner = out.new InnerClass();//实例化/访问内部类,必须通过外部类的对象!
        inner.innerMethod();
        OutClass out1 = inner.getOuterClass();
        System.out.println(out == out1);//结果为true
    }
}

在这个应用程序中,我们可以看到内部了InnerClass可以对外围类OuterClass的属性进行无缝的访问,尽管它是private修饰的。这是因为当我们在创建某个外围类的内部类对象时,此时内部类对象必定会捕获一个指向那个外围类对象的引用,只要我们在访问外围类的成员时,就会用这个引用来选择外围类的成员。

其实在这个应用程序中我们还看到了如何来引用内部类:OuterClasName.InnerClassNam引用内部类我们需要指明这个对象的类型:e。同时如果我们需要创建某个内部类对象,必须要利用外部类的对象通过.new来创建内部类: OuterClass.InnerClass innerClass = outerClass.new InnerClass();

同时如果我们需要生成对外部类对象的引用,内部类会自动创建一个隐式的外部类对象的引用,可以使用OuterClassName.this,这样就能够产生一个正确引用外部类的引用了。

到这里了我们需要明确一点,内部类是个编译时的概念,一旦编译成功后,它就与外围类属于两个完全不同的类(当然他们之间还是有联系的)。对于一个名为OuterClass的外围类和一个名为InnerClass的内部类,在编译成功后,会出现这样两个class文件:OuterClass.classOuterClass$InnerClass.class

 

Java中内部类具体的又可以分为成员内部类、局部内部类、匿名内部类、静态内部类。

1、成员内部类

成员内部类也是最普通的内部类,它是外围类的一个成员,所以他是可以无限制的访问外围类的所有成员属性和方法,尽管是private的,但是外围类要访问内部类的成员属性和方法则需要通过内部类实例来访问。

 

在成员内部类中要注意两点

第一:成员内部类中不能存在任何static的变量和方法;

第二:成员内部类是依附于外围类的,所以只有先创建了外围类才能够创建内部类。

 

public class OutClass {   //内部类和外部类虽然是写在一个文件里,编译的时候会分别生成两个class文件
    private String name ;
    private int id;
    
    public class InnerClass{//内部类InnerClass,注意定义的的位置,OuterClass的类体里边,成员内部类
        //public static int iidd;  成员内部类里边不允许定义静态的成员
        public void innermm() {
            id=11;
            name = "张三";
            System.out.println(id);
            System.out.println(name);//成员内部类可以无障碍访问外部类的成员,不管成员用什么修饰
        }
    }
    public InnerClass getInnerClass() { //第二种方法获取内部类
        return new InnerClass();
    }
} 
public class TestMain {
     public static void main(String[] args) {
        OutClass out = new OutClass();
        OutClass.InnerClass innerClass = out.new InnerClass();
        OutClass.InnerClass innerClass1 = out.getInnerClass();//第二种方法
        
         //        在项目开发中,我们是可以这样拿内部类的对象,但是我们不推荐
         //        推荐用getxxx()方法来获取这个内部类的对象
         innerClass.innermm();
         // InnerClass innerClass2 = new InnerClass();//不能直接new内部类
         //需要先实例一个外部类对象
    }
}

推荐使用getxxx()来获取成员内部类,尤其是该内部类的构造函数无参数时 。

2、局部内部类

有这样一种内部类,它是嵌套在方法的方法体内的,对于这个类的使用主要是应用与解决比较复杂的问题,想创建一个类来辅助我们的解决方案,到那时又不希望这个类是公共可用的,所以就产生了局部内部类,局部内部类和成员内部类一样被编译,只是它的作用域发生了改变,它只能在该方法中被使用,出了该方法就会失效。

public class OutClass {   //内部类和外部类虽然是写在一个文件里,编译的时候会分别生成两个class文件
    private String name ;
    private int id;
    public void outermm() {
        class InnerClass implements InnerInterface{//这个时候的InnerClass就是一个局部的内部类
            @Override
            public String limm() {
                // TODO Auto-generated method stub
                return "这里是InnerClass这个局部内部类里的现实InnerInterface这个接口定义的抽象方法iimm";
            }
        }
        //使用一下这个局部的内部类
        InnerClass innerClass = new InnerClass();
        String str = innerClass.limm();
        System.out.println(str);
    }
} 
public interface InnerInterface {
       public String limm();
}
public class TestMain {
     public static void main(String[] args) {
        OutClass out = new OutClass();
        out.outermm();
    }
}

3、匿名内部类

在做Android手机程序中,我们经常使用这种方式来绑定事件

写一个例子:

public class OutClass {   //内部类和外部类虽然是写在一个文件里,编译的时候会分别生成两个class文件
    private String name ;
    private int id;
    public void outermm() {
//        class InnerClass implements InnerInterface{//这个时候的InnerClass就是一个局部的内部类
//            @Override
//            public String limm() {
//                // TODO Auto-generated method stub
//                return "这里是InnerClass这个局部内部类里的现实InnerInterface这个接口定义的抽象方法iimm";
//            }
//        }
//        //使用一下这个局部的内部类
//        InnerClass innerClass = new InnerClass();
//        String str = innerClass.limm();
//        System.out.println(str);
        
        //通过接口,来写一个匿名的内部类,   下面这个类体就是一个匿名的内部类
        InnerInterface  innerClass =  new  InnerInterface() {//new关键字去new一个接口,接口后面必须跟上一个实现这个接口的,实现类的类体,这个类体,没有看到对应的修饰词,也没有看到这个类体对应类名
            @Override
            public String limm() {
                return "这里是InnerClass这个局部内部类里的现实InnerInterface这个接口定义的抽象方法iimm";
            }
        };//java多态
        String str = innerClass.limm();
        System.out.println(str);
    }
} 
public interface InnerInterface { //抽象类接口,都包含有抽象方法,不是一个完整的类型,我们前面讲过
    //不能用new关键字,创建对象
       public String limm();//抽象的方法,只有方法的定义,没有方法的实现,没有方法体
}
public class TestMain {
     public static void main(String[] args) {
        OutClass out = new OutClass();
        out.outermm();
    }
}

这里我们就需要看清几个地方

1 匿名内部类是没有访问修饰符的。

2 new 匿名内部类,写法上new匿名内部类实现的接口。如果没又InnerClass接口,就会出现编译出错。

严格注意第三条

3 注意outmm()方法的形参,第一个形参是用final修饰的,而第二个却没有。同时我们也发现第二个形参在匿名内部类中没有使用过,所以当所在方法的形参需要被匿名内部类使用,那么这个形参就必须为final

 

为什么呢?内部类里面使用外部类的局部变量时,其实就是内部类的对象在使用它,内部类对象生命周期中都可能调用它,而内部类试图访问外部方法中的局部变量时,外部方法的局部变量很可能已经不存在了,那么就得延续其生命,拷贝到内部类中,而拷贝会带来不一致性,从而需要使用final声明保证一致性。说白了,内部类会自动拷贝外部变量的引用,为了避免:

a. 外部方法修改引用,而导致内部类得到的引用值不一致

b.内部类修改引用,而导致外部方法的参数值在修改前和修改后不一致。于是就用 final 来让该引用不可改变。

 

4 匿名内部类是没有构造方法的。因为它连名字都没有何来构造方法。

 

4、静态内部类

关键字static中提到static可以修饰成员变量、方法、代码块,其实它还可以修饰内部类,使用static修饰的内部类我们称之为静态内部类,不过我们更喜欢称之为嵌套内部类。静态内部类与非静态内部类之间存在一个最大的区别,我们知道非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:

      a 它的创建是不需要依赖于外围类的。

      b 它不能使用任何外围类的非static成员变量和方法。

public class OutClass {   //内部类和外部类虽然是写在一个文件里,编译的时候会分别生成两个class文件
    private static String name ;
    private int id;
    public class InnerClass{
        //成员内部类,不能有任何static修饰的静态成员
        //public static int iidd;
        public void innermm1() {
            //成员内部类是可以访问到外围类的静态和非静态的成员
            id = 11;
            name = "aaa";
            System.out.println(id);
            System.out.println(name);
        }
    }
    public  static class InnerClass2 {//有static关键字修饰的内部类,就是静态类的内部类
        //普通的非静态成员内部类,不能有任何static修饰的静态成员
        //静态内布类是可以有static修饰的静态成员
        public static int iidd;
        public static void indermm2() {
            System.out.println("静态内部类是可以有静态方法");
        }
        public void innermm3() {
            System.out.println("静态的内部类也可以有非静态的普通成员方法");
        }    
    }
} 
package coursetest;

import coursetest.OutClass.InnerClass2;

public class TestMain {
     public static void main(String[] args) {
        //普通的非静态的成员内部类,用这种内部类里的方法或属性,必须依附外围类的对象实例
         OutClass outClass = new OutClass();
         outClass.InnerClass1  innerClass1 = outClass.new InnerClass1();
         innerClass1.innermm1();
         //对静态的内布类,在使用的时候
         InnerClass2.indermm2();//直接的用静态的内部类的类名来调用里面的静态成员
         InnerClass2 innerClass2 = new InnerClass2();
         innerClass2.innermm3();
    }
}

上面这个例子充分展现了静态内部类和非静态内部类的区别。课后一定要自己写来理解一下!

 

 

 

 

 

 

 

一、理解枚举类型

枚举类型是Java 5中新增特性的一部分,它是一种特殊的数据类型,之所以特殊是因为它既是一种(class)类型却又比类类型多了些特殊的约束,但是这些约束的存在也造就了枚举类型的简洁性、安全性以及便捷性。下面先来看看如何写一个枚举?

 

二、枚举的定义语法

在没有枚举类型时定义常量常见的方式

public class DayDemo {
     //周一到周日
    public static final int MONDAY = 1;
    public static final int TUESDAY = 2;
    public static final int WEDNESDAY = 3;
    public static final int THURSDAY = 4;
    public static final int FRIDAY = 5;
    public static final int SATURDAY = 6;
    public static final int SUNDAY = 7;
    public int age = 1; 
}

上述的常量定义常量的方式称为int枚举模式,这样的定义方式并没有什么错,但它存在许多不足,如在类型安全和使用方便性上并没有多少好处,如果存在定义int值相同的变量,容易混淆,因此这种方式在枚举出现后并不提倡,现在我们利用枚举类型来重新定义上述的常量,定义周一到周日的常量

public enum Day {
     MONDAY,TUESDAY,WEDNESDAY,
     THURSDAY,FARIDAY,SATURDAY,SUNDAY
}

相当简洁,在定义枚举类型时我们使用的关键字是enum,与class关键字类似,只不过前者是定义枚举类型,后者是定义类类型。

 

枚举类型Day中分别定义了从周一到周日的值,这里要注意,值一般是大写的字母,多个值之间以逗号分隔。同时我们应该知道的是枚举类型可以像类(class)类型一样,定义为一个单独的文件,当然也可以定义在其他类内部,更重要的是枚举常量在类型安全性和便捷性都很有保证,如果出现类型问题编译器也会提示我们改进,但务必记住枚举表示的类型其取值是必须有限的,也就是说每个值都是可以枚举出来的,比如上述描述的一周共有七天。

 

以上是写法,写好后该如何使用呢?如下:

 

public class TestMain {
     public static void main(String[] args) {
       int day = DayDemo.age;
       Day day2 = Day.MONDAY;//直接引用
    }
}

 

就像上述代码那样,直接引用枚举的值即可,这便是枚举类型的最简单模型。

 

 

三、为什么可以这样用,看看枚举实现原理就知道了:

我们大概了解了枚举类型的定义与简单使用后,现在有必要来了解一下枚举类型的基本实现原理。实际上在使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承了Java API中的java.lang.Enum类,也就是说通过关键字enum创建枚举类型在编译后事实上也是一个类类型而且该类继承自java.lang.Enum类。我们可以看看反编译的结果!

 

结论:从反编译的代码可以看出编译器确实帮助我们生成了一个Day类而且该类继承自java.lang.Enum类,该类是一个抽象类,除此之外,编译器还帮助我们生成了7Day类型的实例对象分别对应枚举中定义的7个日期。还为我们生成了两个静态方法,分别是values() valueOf()到此我们也就明白了,使用关键字enum定义的枚举类型,在编译期后,也将转换成为一个实实在在的类,而在该类中,会存在每个在枚举类型中定义好常量的对应实例对象,如上述的MONDAY枚举类型对应public static final Day MONDAY;

 

四、编译器生成的Values方法与ValueOf方法

values()方法和valueOf(String name)方法是编译器生成的static方法,后面我们自己定义的枚举类的父类Enum的分析中,在Enum类中并没出现values()方法,但valueOf()方法还是有出现的,只不过编译器生成的valueOf()方法需传递一个name参数,而Enum自带的静态方法valueOf()则需要传递两个方法,从前面反编译后的代码可以看出,编译器生成的valueOf方法最终还是调用了Enum类的valueOf方法,下面通过代码来演示这两个方法的作用:

 

public enum Day {
     MONDAY,TUESDAY,WEDNESDAY,
     THURSDAY,FARIDAY,SATURDAY,SUNDAY;
}

 

public class TestMain {
     public static void main(String[] args) {
       Day[] days = Day.values();//需要用到数组
       for(Day day:days) {
           System.out.println(day);
           //System.out.println(day.toString());
       }
       Day monDay = Day.valueOf("MONDAY");//valueOf方法返回的值:monday对象
      System.out.println(monDay);//强调:控制台输出的内容“MONDAY”文字描述
     }//   MONDAY
}   //     TUESDAY
    //     WEDNESDAY
    //     THURSDAY
    //     FARIDAY
    //     SATURDAY
    //     SUNDAY
    //     MONDAY

从结果可知道,values()方法的作用就是获取枚举类中的所有变量,并作为数组返回,而valueOf(String name)方法与Enum类中的valueOf方法的作用类似根据名称获取枚举变量,只不过编译器生成的valueOf方法更简洁些只需传递一个参数。

 

五、Enum抽象类常见方法

Enum是所有 Java 语言枚举类型的公共基本类(注意Enum是抽象类),以下是它的常见方法:

ordinal()方法,该方法获取的是枚举变量在枚举类中声明的顺序,下标从0开始,如日期中的MONDAY在第一个位置,那么MONDAYordinal值就是0,如果MONDAY的声明位置发生变化,那么ordinal方法获取到的值也随之变化,注意在大多数情况下我们都不应该首先使用该方法,毕竟它总是变幻莫测的

compareTo(E o)方法则是比较枚举的大小,注意其内部实现是根据每个枚举的ordinal值大小进行比较的。

name()方法与toString()几乎是等同的,都是输出变量的字符串形式。

valueOf(Class<T> enumType, String name)方法则是根据枚举类的Class对象和枚举名称获取枚举常量,注意该方法是静态的。

 

    public class TestMain {
         public static void main(String[] args) {
           Day[] days = Day.values();//需要用到数组
           for(Day day:days) {
               System.out.println(day);
               //System.out.println(day.toString());
           }
           Day monDay = Day.valueOf("MONDAY");//valueOf方法返回的值:monday对象
           Day tuesDay = Day.valueOf("TUESDAY");
          System.out.println(monDay);//强调:控制台输出的内容“MONDAY”文字描述
          System.out.println(monDay.ordinal());
          System.out.println(monDay.compareTo(tuesDay));//compareTo方法的返回值:1,-1
          System.out.println(monDay.toString());//效果和下面这一行一样
          System.out.println(monDay);//会默认加上toString 和上面一样
          System.out.println(monDay.name());
         }
    }

六、枚举的进阶用法

enum类添加方法与自定义属性和构造函数

重新定义一个日期枚举类,带有desc成员变量描述该日期的对于中文描述,同时定义一个getDesc方法,返回中文描述内容,自定义私有构造函数,在声明枚举实例时传入对应的中文描述,代码如下:

 

public enum Day {
     MONDAY("星期一",1),TUESDAY("星期二",2),WEDNESDAY("星期三",3),
     THURSDAY("星期四",4),FARIDAY("星期五",5),SATURDAY("星期六",6),SUNDAY("星期七",7);
    //先自定义两个枚举的属性
    //此枚举类中包含的枚举对象的对应的文字描述
    private  String desc;
    //此枚举类中包含的枚举对象的对应的代码
    private  int code;
    public String getDesc() {//因为枚举是常量,常量不能改所以不用添加set方法
        return desc;
    }

    public int getCode() {
        return code;
    }
    //此枚举类自定义一个构造器
    private Day(String desc,int code) {
        this.desc = desc;
        this.code = code;
    }
   //通过自定义的code属性,来获取整合枚举对象,静态方法
    public static Day getDayByCode(int code) {
        Day day = null;
        switch(code) {
        case 1 :
            day = day.MONDAY;
            break;
        case 2 :
            day = day.TUESDAY;
            break;
        case 3 :
            day = day.WEDNESDAY;
            break;
        case 4 :
            day = day.THURSDAY;
            break;
        case 5 :
            day = day.FARIDAY;
            break;
        case 6 :
            day = day.SATURDAY;
            break;
        case 7 :
            day = day.SUNDAY;
            break;
        }
        return day;
    }
}

 

一、什么是JAR文件:

JAR文件的全称是Java Archive File,意思就是Java档案文件。通常JAR文件是一种压缩文件,与常见的ZIP压缩文件兼容。JAR文件与zip文件的区别就是在JAR文件中默认包含了一个名为META-INF/MANIFEST.MF的清单文件,这个清单文件是在生成JAR文件时系统自动创建的。

 

当开发了一个应用程序后,这个应用程序包含了很多类,如果需要把这个应用程序提供给别人使用,通常会将这些类文件打包成一个JAR文件,把这个JAR文件提供给别人使用。只要别人在系统的CLASSPATH环境变量中添加这个JAR文件,就可以想在本地自己写的代码一样地使用Jar包里的代码

 

二、使用JAR文件有以下好处:

安全。能够对JAR文件进行数字签名,只让能够识别数字签名的用户使用里面的东西。

加快下载速度。在网上使用applet时,如果存在多个文件而不打包,为了能够把每个文件都下载到客户端,需要为每个文件单独建立一个HTTP连接,这是非常耗时的工作。将这些文件压缩成一个JAR包,只要建立一个http连接就能够一次下载所有的文件。

压缩。使文件变小,JAR的压缩机制和zip完全相同

包封装。能够让JAR包里面的文件依赖于统一版本的类文件。

可一致性。JAR包作为内嵌在Java平台内部处理的标准,能够在各种平台上直接使用。

 

三、如何开发jar

开发jar包的人员开发流程:

步骤1:编写类

//GetSum.java

public class GetSum{

      public static getSum(int a,int b){

            return a+b;

      }

}

步骤2:打包成jar

      在要导出的类上,右键->Export->java -> JAR file,然后选择路径,D:sum.jar ->导出。

打开jar包可以看到jar包中含有class文件。

 

 

四、如何使用jar

当别人发过来一个jar包后,比如上面的sum.jar包,如何使用呢?

使用方法:

步骤1:在工程目录下新建文件夹lib,将需要使用的jar包复制进去(ctrl cctrl v)。在工程上右键刷新就可以看到jar包了

步骤2:在需要使用的jar包上,右键->build path ->Add to build Path

步骤3:编写测试类Test.java

 

 

 

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注