06_面向对象高级篇_02-接口、多态、内部类、枚举、Java常见类、Java垃圾回收机制
本章章节
> 6.1 继承的基本概念
> 6.2 Object类
> 6.3抽象类
> 6.4 final 关键字
> 6.5接口(interface)
> 6.6对象多态性
> 6.7内部类
> 6.8枚举
> 6.9 Java系统常见类
> 6.10 Java垃圾回收机制
.本章摘要:
(1)先创建父类的变量数组;
(2)利用数组元素创建子类的对象...
6.5 接口(interface)
接口(interface)是Java所提供的另一种重要技术,从本质上讲,接口是一种特殊的抽象类,interface:totally abstract class(完全抽象类)。这种抽象类中只包含全局常量和公共的抽象方法的定义,而没有变量和方法的实现。可以说接口是抽象方法和常量值定义的集合。
如果一个类中完全是由全局变量(static final声明)和抽象方法(abstract声明)组成,就可以将其定义成一个接口。
它的结构和抽象类非常相似,也具有数据成员与抽象方法,但它与抽象类又有以下两点不同:
1、接口里的数据成员必须初始化,且数据成员均为常量。
2、接口里的方法必须全部声明为abstract,也就是说,接口不能像抽象类一样有一般的方法,而必须全部是“抽象方法”。
接口定义的语法如下:
interface 接口名称//定义接口 { public static final 数据类型 成员名称 = 常量;//数据成员必须赋初值 public abstract 返回值的数据类型 方法名称(参数…); //抽象方法,注意在抽象方法里,没有定义方法主体。 }
接口与一般类一样,本身也具有数据成员与方法,但数据成员一定要赋初值,且此值将不能再更改,方法也必须是“抽象方法”。也正因为方法必须是抽象方法,而没有一般的方法,所以抽象方法声明的关键字public abstract是可以省略的。相同的情况也发生在数据成员身上,因数据成员必须赋初值,且此值不能再被更改,所以声明数据成员的关键字public static final也可省略。事实上只要记得:
(1)、接口里的“抽象方法”只要做声明即可,而不用定义其处理的方式;
(2)、数据成员必须赋初值,这样就可以了。
在java中接口是用于实现多继承的一种机制,也是java设计中最重要的一个环节,每一个由接口实现的类必须在类内部复写接口中的抽象方法,且可自由地使用接口中的常量。
既然接口里只有抽象方法,它只要声明而不用定义处理方式,于是自然可以联想到接口也没有办法像一般类一样,再用它来创建对象。利用接口打造新的类的过程,称之为接口的实现(implementation)。
接口实现的语法如下:
class 类名称 implements 接口 A, 接口 B // 接口的实现 { … }
接口的实现与抽象方法相似,当类实现某个接口时,它必须实现这个接口的所有方法才能用该类实例化对象。
范例:TestInterfaceDemo1.java
interface Person { // 声明三个常量 String name = "张三"; int age = 25; String occupation = "学生"; // 声明一个抽象方法talk() public abstract String talk(); } // Student类实现Person接口 class Student implements Person { // 复写talk()方法,public不能少 public String talk() { return "学生——>姓名:" + this.name + ",年龄:" + this.age + ",职业:" + this.occupation + "!"; } } class TestInterfaceDemo1 { public static void main(String[] args) { Student s = new Student(); System.out.println(s.talk()); } }
输出结果:
学生——>姓名:张三,年龄:25,职业:学生!
程序说明:
1、程序1~9行,声明一个Person接口,并在里面声明了三个常量:name、age、occupation,并分别赋值和一个抽象方法。
2、程序10~18行声明一个Student类,此类实现Person接口,并复写Person中的talk()方法。
3、程序第23行实例化一个Student的对象s,并在第24行调用talk()方法,打印信息。
如果没有全部实现接口中的所有方法,则此时的类是一个抽象类。
interface AAA { void print(); void disp(); } // Person类没有全部实现接口中的方法,所以必须声明为abstract abstract class Person implements AAA { public void print() {} public abstract void disp(); } // Student继承自Person,实现了所有的抽象方法,所以可以实例化对象 class Student extends Person { public void print() {}
public void disp() {} }
你可能会觉得这样做与抽象类并没有什么不同,在这里需要再次提醒的是,接口是java实现多继承的一种机制,一个类只能继承一个父类,但却可以实现多个接口。
接口与一般类一样,均可通过扩展的技术来派生出新的接口。原来的接口称为基本接口或父接口,派生出的接口称为派生接口或子接口。通过这种机制,派生接口不仅可以保留父接口的成员,同时也可加入新的成员以满足实际的需要。
Java允许多个类(抽象类、非抽象类)实现同一个接口
Java允许同一个类(抽象类、非抽象类)实现多个接口
接口不能继承抽象类,但是在Java中一个接口可以通过extends关键字同时继承多个接口,实现接口的多继承。
同样的,接口的扩展(或继承)也是通过关键字extends来实现的。有趣的是,一个接口可以继承多个接口,这点与类的继承有所不同。
接口扩展的语法:
interface 子接口名称 extends 父接口1,父接口2,… { … }
范例:TestInterfaceDemo2.java
interface A { int i = 10; public void sayI(); } interface E { int x = 40; public void sayE(); } // B同时继承了A、E两个接口 interface B extends A, E { int j = 20; public void sayJ(); } // C实现接口B,也就意味着要实现A、B、E三个接口的抽象方法 class C implements B { public void sayI() { System.out.println("i = " + i); } public void sayJ() { System.out.println("j = " + j); } public void sayE() { System.out.println("e = " + x); } } class TestInterfaceDemo2 { public static void main(String[] args) { C c = new C(); c.sayI(); c.sayJ(); } }
输出结果:
i = 10
j = 20
程序说明:
1、程序1~5行声明一个接口A,并声明一个常量i和一个抽象方法sayI()。
2、程序6~10行声明一个接口E,并声明一个常量x和一个抽象方法sayE()。
3、程序11~16行声明一个接口B,此接口同时继承A、E接口,同时声明一个常量j 和一个抽象方法sayJ()。
4、程序17~32行声明一个类C,此类实现了B接口,所以此类中要复写接口A、B、E中的全部抽象方法。
从此程序中可以发现与类的继承不同的是,一个接口可以同时继承多个接口,也就是同时继承了多个接口的抽象方法与常量。
6.6 对象多态性
在前面已经对面向对象的封装性、继承性做了详细的解释,下面就来看一下面向对象中最后一个,也是最重要的一个特性——多态性。
那什么叫多态性呢?应该还清楚在之前曾解释过重载的概念,重载的最终效果就是调用同一个方法名称,却可以根据传入参数的不同而得到不同的处理结果,这其实就是多态性的一种体现。只不过这种多态,称为早期绑定或静态绑定。
而对象多态,也叫动态绑定或迟绑定。
动态绑定是指“在执行期间(而非编译期间)”判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。
执行期间:运行 java …
编译期间:运行 javac …
下面用一道范例简单介绍一下对象多态的概念,请看下面的范例:
范例:TestJavaDemo1.java
class Person { public void fun1() { System.out.println("1.Person{fun1()}"); } public void fun2() { System.out.println("2.Person{fun2()}"); } } // Student类扩展自Person类,也就继承了Person类中的fun1()、fun2()方法 class Student extends Person { // 在这里复写了Person类中的fun1()方法 public void fun1() { System.out.println("3.Student{fun1()}"); } public void fun3() { System.out.println("4.Studen{fun3()}"); } } class TestJavaDemo1 { public static void main(String[] args) { // 此处,父类对象由子类实例化 Person p = new Student(); // 调用fun1()方法,观察此处调用的是哪个类里的fun1()方法 p.fun1(); p.fun2(); } }
输出结果:
3.Student{fun1()}
2.Person{fun2()}
程序说明:
1、程序1~11行声明一个Person类,此类中有fun1()、fun2()两个方法。
2、程序14~25行声明一个Student类,此类继承自Person类,并复写了fun1()方法。
3、程序第32行声明一个Person类(父类)的对象,之后由子类对象去实例化此对象。
4、第34行由父类对象调用fun1()方法。
从程序的输出结果中可以发现,p是父类的对象,但调用fun1()方法的时候并没有调用其本身的fun1()方法,而是调用了子类中被复写了的fun1()方法。之所以会产生这样的结果,最根本的原因就是因为父类对象并非由其本身的类实例化,而是通过子类实例化,这就是所谓的对象的多态性,即子类实例化对象可以转换为父类实例化对象。
在这里要着重讲解两个概念,希望大家加以重视:
1、 向上转型:
特点:程序会自动完成。
格式:父类 父类对象 = 子类实例;
在上面范例TestJavaDemo1.java中,父类对象通过子类对象去实例化,实际上就是对象的向上转型。向上转型是不需要进行强制类型转换的,但是向上转型会丢失精度。
2、 向下转型:
特点:必须明确的指明要转型的子类类型。
格式:子类 子类对象 = (子类)父类实例;
与向上转型对应的一个概念就是“向下转型”,所谓向下转型,也就是说父类的对象可以转换为子类对象,但是需要注意的是,这时则必须要进行强制的类型转换。
你可能觉得上面的两个概念有些难以理解,下面举个例子来帮助大家理解:
有个小孩在马路上看见了一辆跑车,他指着跑车说那是汽车。相信大家都会认为这句话没有错,跑车的确是符合汽车的标准,所以把跑车说成汽车并没有错误,只是不准确而已。不管是小轿车也好,货车也好,其实都是汽车,这在现实生活中是说得通的,在这里可以将这些小轿、货车都想象成汽车的子类,它们都是扩展了汽车的功能,都具备了汽车的功能,所以它们都可以叫做汽车,那么这种概念就称为向上转型。而相反,假如说把所有的汽车都当成跑车,那结果肯定是不正确的了,因为汽车有很多种,必须明确的指明是哪辆跑车才可以,需要加一些限制,这个时候就必须明确的指明是哪辆车,所以需要进行强制的说明。
上面的解释可以概括成两句话:
一、向上转型可以自动完成;
二、向下转型必须进行强制类型转换。
小提示:
另外,要提醒大家注意的是,并非全部的父类对象都可以强制转换为子类对象,在父类对象向子类对象的转换的过程中,需要注意的是:如果父类对象是实例化的父类对象而不是用子类对象转换过来的对象,则不能在强制转换为子类对象,否则会出现ClassCastException类转换异常。也就是说,能进行父类对象向子类对象转换的前提是:之前是使用子类对象的实例来初始化得父类对象。请看下面的范例:
范例:TestJavaDemo2.java
class Person { public void fun1() { System.out.println("1.Person{fun1()}"); } public void fun2() { System.out.println("2.Person{fun2()}"); } } // Student类继承Person类,也就继承了Person类的fun1()、fun2()方法 class Student extends Person { // 在这里复写了Person类中的fun1()方法 public void fun1() { System.out.println("3.Student{fun1()}"); } public void fun3() { System.out.println("4.Studen{fun3()}"); } } class TestJavaDemo2 { public static void main(String[] args) { // 此处,父类对象由自身实例化 Person p = new Person(); // 将p对象向下转型 Student s = (Student) p; p.fun1(); p.fun2(); } }
运行结果:
Exception in thread "main" java.lang.ClassCastException
at TestJavaDemo2.main(TestJavaDemo2.java:34)
由程序可以发现,程序32行Person对象p是由Person类本身实例化的,在第34行将Person对象p强制转换为子类对象,这样写在语法上是没有任何错误的,但是在运行时可以发现出了异常,这是为什么呢?为什么父类不可以向子类转换了呢?其实这点并不难理解,可以想一下在现实生活中的例子,假如你今天刚买完一些生活用品,回家的时候在路上碰见一个孩子,这个孩子忽然对你说:“我是你的儿子,你把你的东西给我吧!”,这个时候你肯定不会把你的东西给这个孩子,因为你不确定他跟你是否有关系,怎么能给呢?那么在这程序中也是同样的道理,父类用其本身类实例化自己的对象,但它并不知道谁是自己的子类,那肯定在转换的时候会出现错误,那么这个错误该如何纠正呢?只需要将第32行的代码修改成如下形式即可:
Person p = new Student();
这个时候相当于是由子类去实例化父类对象,也就是说这个时候父类知道有这么一个子类,也就相当于父亲知道了自己有这么一个孩子,所以下面再进行转换的时候就不会再有问题了。
为了使强制转换成功,必须确认对象是属于子类的实例,如果超类对象不是子类的实例,则会发生运行时异常。这就用到了instanceof操作符。
对象在进行向下转型之前一定要先发生向上转型,要使用instanceof关键字判断。
6.6.1 instanceof 关键字的使用
可以用instanceof判断一个类是否实现了某个接口,也可以用它来判断一个实例对象是否属于一个类。instanceof 的语法格式为:
对象 instanceof 类(或接口或抽象类)
它的返回值是布尔型的,或真(true)、或假(false)。
abstract class Animal { public abstract void call(); } class Cat extends Animal { public void call() { System.out.println("喵喵喵"); } public void catchMouse() { System.out.println("猫捉老鼠"); } } class Dog extends Animal { public void call() { System.out.println("汪汪汪"); } public void fill() { System.out.println("狗摇尾巴"); } } class PlayAnimal { PlayAnimal(Animal ani) { ani.call(); if (ani instanceof Cat) ((Cat) ani).catchMouse(); else if (ani instanceof Dog) ((Dog) ani).fill(); } } public class Test { public static void main(String[] args) { new PlayAnimal(new Cat()); new PlayAnimal(new Dog()); } }
6.6.2 抽象类和接口对象的实例化
在Java中可以通过对象的多态性,为抽象类和接口实例化,这样再使用抽象类和接口的时候就可以调用本子类中所覆写过的方法了。之所以抽象类和接口不能直接实例化,是因为其内部包含了各个抽象方法,抽象方法本身都是未实现的方法,所以无法调用。
通过对象多态性可以发现,子类发生了向上转型关系之后,所调用的全部方法都是被覆写过的方法。请看下面的范例:
范例:TestInterfaceObject
interface Person { public void fun1(); } class Student implements Person { public void fun1() { System.out.println("Student fun1()"); } } class TestInterfaceObject { public static void main(String[] args) { Person p = new Student(); p.fun1(); } }
输出结果:
Student fun1()
程序说明:
1、程序第1~4行声明一个Person接口,此接口中只有一个抽象方法fun1()。
2、程序第5~11行声明一个Student类,此类实现Person接口,并复写fun1()方法。
3、程序第16行声明一个Person接口的对象p,并通过子类Student去实例化此对象。
4、程序第17行调用fun1()方法,此时调用的是子类中复写了的fun1()方法。
从上面的程序中可以发现,接口是可以被实例化的,但是不能被直接实例化,只能通过其子类进行实例化,而在这里将Person声明为抽象类道理也是一样的。
那么这样去实例化到底有什么好处呢?
抽象类的应用——定义模板
假设一种场景:“假设动物分为猫和狗,每种动物都可以发出叫声,很明显,猫和狗的叫声是不一样的,也就是说,叫这个功能应该是一个具体功能,而叫的内容就要由猫或狗来决定了”,所以此时就可以使用抽象类实现这种场景。
接口的应用——定义标准
接口在实际中更多的作用是用来制定标准的。比如说:“U盘、打印机和移动硬盘都可以插在电脑上使用,这是因为他们都实现了 USB的接口,对于电脑来说,只要是符合了 USB接口标准的设备就都可以插进来。以此为例编写程序如下所示:
范例:TestInterface.java
interface Usb { public void start(); public void stop(); } class MoveDisk implements Usb { public void start() { System.out.println("MoveDisk start..."); } public void stop() { System.out.println("MoveDisk stop..."); } } class Mp3 implements Usb { public void start() { System.out.println("Mp3 start..."); } public void stop() { System.out.println("Mp3 stop..."); } } class Computer { public void work(Usb u) { u.start(); u.stop(); } } class TestInterface { public static void main(String[] args) { new Computer().work(new MoveDisk()); new Computer().work(new Mp3()); } }
输出结果:
MoveDisk start...
MoveDisk stop...
Mp3 start...
Mp3 stop...
程序说明:
1、程序1~5行声明一个Usb接口,此接口中有两个抽象方法:start()、stop()。
2、程序6~16行声明一个MoveDisk类,此类实现Usb接口,并复写了里面的两个抽象方法。
3、程序17~27行声明一个Mp3类,此类实现Usb接口,并复写了里面的两个抽象方法。
4、程序29~36行声明一个Computer类,在类中有一个work()方法,此方法接收Usb对象的实例,并调用start、stop方法。
5、程序42、43行分别用Computer类的实例化对象调用work()方法,并根据传进对象的不同而产生不同的结果。
从上面的程序中可以发现,使用接口实际上就是定义出了一个统一的标准,这其实是一种接口设计模式,在后面的章节中,还会介绍接口的其它使用方法。
6.6.3 接口和抽象类的关系
在开发中,一个类永远不要去继承一个已经实现好的类。要么继承抽象类,要么实现接口。如果接口和抽象类同时都可以使用的话,那么优先使用接口,避免单继承局限。
6.7 内部类
现在已经知道,在类内部可定义成员变量与方法,有趣的是,在类内部也可以定义另一个类。如果在类Outer的内部再定义一个类Inner,此时类Inner就称为内部类,而类Outer则称为外部类(也叫做嵌套类)。
我们知道,如果将一个类的属性设置为private之后,外部和子类都是不可访问的。内部类的出现,初始思想是JDK1.2版本为解决一个类能访问另外一个类的私有成员。它分为非静态内部类和静态内部类。
内部类可声明成public或private。当内部类声明成public或private时,对其访问的限制与成员变量和成员方法完全相同。
内部类的定义格式:
范例:InnerClassDemo1.java
class Outer { // 定义外部类 private String info = "hello"; // 定义外部类的私有属性 private int age = 24; // 定义外部类的私有属性 class Inner { // 定义内部类 private String name; // 定义内部类的属性 private int age = 23; // 定义内部类的属性,跟外部类同名 public void print() { // 定义内部类的方法 // 直接访问外部类的私有属性 System.out.println(info + ", my name is " + name); // 访问内部类的age System.out.println("我今年" + age + "岁"); System.out.println("我今年" + this.age + "岁"); // 访问外部类的age System.out.println("我今年" + Outer.this.age + "岁"); } } public void fun() { // 定义外部类的方法 Inner in = new Inner(); in.name = "张三"; // 通过内部类的实例化对象调用内部类属性 in.print(); // 通过内部类的实例化对象调用内部类方法 } } public class InnerClassDemo1 { public static void main(String args[]) { new Outer().fun(); // 调用外部类的fun()方法 } }
内部类可以直接访问外部类的属性和方法,包括private的;外部类不能直接访问内部类的成员,哪怕是public的。外部类访问内部类的成员,包括private的,是通过内部类的对象来进行访问的(通过new实例内部类来访问)。外部类属性、内部类属性与内部类里的方法的局部变量同名,则可以通过使用this(内部类属性)、外部类名.this(外部类属性)来区分。
我们可以把内部类看作“方法”一样,在使用的时候调用执行;也可以把内部类看作“属性”一样,在构造内部类对象的时候,也会在堆里为内部类的属性分配存储空间。
前面已经学过了static的用法,用static可以声明属性或方法,也可以用static声明内部类,用static声明的内部类叫静态内部类(外部类),但是用static声明的内部类不能访问非static的外部类属性和方法。非静态内部类不能定义静态成员。
范例:InnerClassDemo2.java
class Outer { // 定义外部类 private static String info = "hello"; // 定义外部类的私有属性 private static int age = 24; static class Inner { // 定义内部类 private String name; // 定义内部类的属性 private int age = 23;
public void print() { // 定义内部类的方法 // 直接访问外部类的私有属性 System.out.println(info + ", my name is " + name); // 访问内部类的age System.out.println("我今年" + age + "岁"); System.out.println("我今年" + this.age + "岁"); // 访问外部类的age System.out.println("我今年" + Outer.age + "岁"); } } public void fun() { // 定义外部类的方法 Inner in = new Inner(); in.name = "张三"; // 通过内部类的实例化对象调用内部类属性 in.print(); // 通过内部类的实例化对象调用内部类方法 } } public class InnerClassDemo2 { public static void main(String args[]) { new Outer().fun(); // 调用外部类的fun()方法 } }
6.7.1 在外部类之外引用非静态内部类
前面的例子内部类的对象总是创建在外部类中。内部类也可以在外部类之外的地方创建对象,从外部类之外调用内部类方法,通常将内部类声明为public。非静态内部类,必须首先创建外部类对象,然后使用下面语法来创建内部类对象:
非静态内部类在类外部实例化格式:
外部类.内部类 内部类对象 = 外部类实例.new 内部类();
请看下面的范例:
范例:InnerClassDemo3.java
class Outer { // 定义外部类 private String info = "hello"; // 定义外部类的私有属性 private int age = 24; // 定义外部类的私有属性 class Inner { // 定义内部类 private String name; // 定义内部类的属性 private int age = 23; // 定义内部类的属性,跟外部类同名 public void print() { // 定义内部类的方法 // 直接访问外部类的私有属性 System.out.println(info + ", my name is " + name); // 访问内部类的age System.out.println("我今年" + age + "岁"); System.out.println("我今年" + this.age + "岁"); // 访问外部类的age System.out.println("我今年" + Outer.this.age + "岁"); } } public void fun() { // 定义外部类的方法 Inner in = new Inner(); in.name = "张三"; // 通过内部类的实例化对象调用内部类属性 in.print(); // 通过内部类的实例化对象调用内部类方法 } } public class InnerClassDemo3 { public static void main(String args[]) { new Outer().fun(); // 调用外部类的fun()方法 //利用new Outer().new Inner()实例化非静态内部类对象 new Outer().new Inner().print(); } }
6.7.2 在外部类之外引用静态内部类
内部类使用static关键字声明,此类内部类就称为静态内部类。可以直接通过“外部类.内部类”的形式进行访问内部类。
静态内部类在类外部实例化格式:
外部类.内部类 内部类对象 = new 外部类.内部类();
范例:InnerClassDemo4.java
class Outer { // 定义外部类 private static String info = "hello"; // 定义外部类的私有属性 private static int age = 24; static class Inner { // 定义内部类 private String name; // 定义内部类的属性 private int age = 23; public void print() { // 定义内部类的方法 // 直接访问外部类的私有属性 System.out.println(info + ", my name is " + name); // 访问内部类的age System.out.println("我今年" + age + "岁"); System.out.println("我今年" + this.age + "岁"); // 访问外部类的age System.out.println("我今年" + Outer.age + "岁"); } } public void fun() { // 定义外部类的方法 Inner in = new Inner(); in.name = "张三"; // 通过内部类的实例化对象调用内部类属性 in.print(); // 通过内部类的实例化对象调用内部类方法 } } public class InnerClassDemo4 { public static void main(String args[]) { new Outer().fun(); // 调用外部类的fun()方法 new Outer.Inner().print();// 利用new Outer.Inner()实例化静态内部类对象 } }
6.7.3 常见的创建内部类对象方式
上面介绍了两种在外部类外部创建内部类(静态或非静态)对象的方式,但用这样的方式来创建内部类对象,阅读费解,且相应违反Java的原则。一般来说,构造内部类对象,调用外部类一个方法,返回内部类对象。如下:
Inner getInnerInstance() { return new Inner(); }
范例:InnerClassDemo5.java
class Outer { // 定义外部类 private static String info = "hello"; static class Inner { // 定义内部类 private String name = "张三"; public void print() { System.out.println(info + ", my name is " + name); } } public static Inner getInnerInstance() { return new Inner(); } } public class InnerClassDemo5 { public static void main(String args[]) { new Outer().getInnerInstance().print(); } }
在这个例子中,将内部类定义为非静态内部类也是可以的。
6.7.4方法内部类
内部类不仅可以在类中定义,也可以在方法中定义内部类。在方法中定义的内部类称为方法内部类。
范例:InnerClassDemo6.java
class Outer { private int score = 95; void inst() { class Inner { void display() { System.out.println("成绩: score = " + score); } } new Inner().display(); } } public class InnerClassDemo6 { public static void main(String[] args) { new Outer().inst(); } }
需要注意的是:在方法中定义的内部类只能访问方法中的final类型的局部变量,因为用final定义的局部变量相当于是一个常量,它的生命周期超出方法运行的生命周期。另外,在方法中定义的内部类不能加上任何的封装操作符(public也不行)。如下面范例所示:
范例:InnerClassDemo7.java
class Outer { int score = 95; void inst(final int s) { int temp = 20; // 变量不是final修饰的 public class Inner { // 不能加上任何的修饰符 void display() { System.out.println("成绩: score = " + (score + s + temp)); } } new Inner().display(); } } public class InnerClassDemo7 { public static void main(String[] args) { new Outer().inst(5); } }
由编译结果可以发现,内部类可以访问用final标记的变量s,却无法访问在方法内声明的变量temp,所以需要在第6行的变量声明时,加上一个final修饰:
final int temp = 20;
另外,要将方法内部类前面的封装修饰符public去掉。
6.7.5 匿名内部类
使用匿名内部类可以更进一步缩短内部类。因为匿名内部类主要用来创建Java事件的适配器,在Android的事件课程会具体讲解。
一个类在整个操作中只使用一次的话,就可以将其定义成匿名内部类,匿名内部类是在抽象类及接口的基础上发展起来的。
下面先来看一个比较简单的范例。
范例:TestNoNameInner1.java
interface A { public void fun1(); } class B { class C implements A { public void fun1() { System.out.println("测试匿名内部类"); } } public void get(A a) { a.fun1(); } public void test() { this.get(new C()); } } class TestNoNameInner1 { public static void main(String[] args) { new B().test(); } }
上面的程序与以前的内部类没有太大的区别,也是在类内部声明了一个类,与原先不同的是多实现了一个接口罢了。当然,这里只是简单的声明了一个内部类,目的并不是要再重新介绍一遍内部类的概念,而是为了引出下面的概念——匿名内部类。
范例:TestNoNameInner2.java
interface A { public void fun1(); } class B { public void get(A a) { a.fun1(); } public void test() { this.get(new A() { public void fun1() { System.out.println("测试匿名内部类"); } } ); } } class TestNoNameInner2 { public static void main(String[] args) { new B().test(); } }
由此程序可以发现,在程序中并没有明确的声明出实现接口A的类,但是在程序实现了接口A中的fun1()方法,并把整个的一个实现类,传递到了方法get中,这就是所谓的匿名内部类。它不用声明实质上的类,就可以使用。
6.8 枚举
在JDK1.5 之前,Java 可以有两种方式定义新类型:类和接口。对于大部分面向对象编程来说,这两种方法看起来似乎足够了。但是在一些特殊情况下,这些方法就不适合。
例如:想定义一个Color类,它只能有Red、Green和Blue三种值,其他的任何值都是非法的。通过private将构造器隐藏起来,把这个类的所有可能实例都使用public static final属性来保存,如果有必要,可以提供一些静态方法,允许其他程序根据特定参数来获取与之匹配实例。代码如下:
class Color { public static final Color RED = new Color("红色"); // 定义第一个对象 public static final Color GREEN = new Color("绿色");// 定义第一个对象 public static final Color BLUE = new Color("蓝色"); // 定义第一个对象 private String name; private Color(String name) { //构造方法私有 this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } public static Color getInstance(int i) { switch (i) { case 1: return RED; case 2: return GREEN; case 3: return BLUE; default: return null; } } } public class Test { public static void main(String[] args) { System.out.println(Color.getInstance(1).getName()); } }
那么JDK1.5之前虽然可以采用上面的形式构造代码,但是要做很多的工作,也有可能带来各种不安全的问题。而JDK1.5 之后引入的枚举类型(enum)就能避免这些问题。所谓的枚举就是规定好了制定的取值范围,所有的内容只能从指定的范围中取得。
·枚举的定义
定义枚举类型,格式如下:
[public] enum 枚举类型名称{
枚举对象 1,
枚举对象 2,
……
枚举对象 n;
}
上例中的3位色可以利用枚举定义如下:
public enum Color{ // 定义三个枚举的类型 RED, GREEN, BLUE; }
·枚举的使用
现在,可以声明这样的枚举变量:
Color c = Color.RED;
可以使用foreach进行全部的输出,使用“枚举.values()”的形式取得全部的枚举内容。
public class TestEnum { public enum Color {// 枚举的定义 RED, GREEN, BLUE; } public static void main(String[] args) { for (Color c : Color.values()) // 枚举的遍历 System.out.println(c); } }
还可以跟switch结合使用,示例如下:
public class Test { public enum Color { RED, GREEN, BLUE; } public static void main(String[] args) { for (Color c : Color.values()) print(c); } public static void print(Color c) { switch (c) { case RED: System.out.println("红颜色"); break; case GREEN: System.out.println("绿颜色"); break; case BLUE: System.out.println("蓝颜色"); break; default: System.out.println("未知颜色"); } } }
6.8.1 枚举类
使用enum声明的枚举类型,就相当于定义一个类,而此类默认继承java.lang.Enum类。
java.lang.Enum类的定义如下:
public abstract class Enum<E extends Enum<E>> extends Object implements Comparable<E>, Serializable
此类定义的时候使用了泛型机制,而且实现了Comparable接口以及Serializable接口,证明此种类型是可以比较,可以被序列化的。
Enum 类的构造方法:
protected Enum(String name, int ordinal);
构造方法中接收两个参数,一个表示枚举的名字,另外一个表示枚举的序号。
常用方法如下所示:
如果希望做一些改进,可以使用一些文字表示颜色的信息,则可以按照最早的Color类的形式,在枚举类中定义属性及自己的构造方法,但是一旦定义有参构造之后,在声明枚举对象的时候就必须明确的调用构造方法,并传递参数。示例如下:
enum Color { RED("红色"), GREEN("绿色"), BLUE("蓝色"); private String name; // 定义name属性 private Color(String name) { this.name = name; } public String getName() { return this.name; } } public class Test { public static void main(String[] args) { for (Color c : Color.values()) System.out.println(c.getName()); } }
6.8.2 枚举的应用
·可以使用枚举实现一个接口
枚举类型可以跟普通的类一样实现一个接口,但是实现接口的时候要求枚举中的每个对象都必须单独覆写好接口中的抽象方法。
实例如下:
interface Print { public String getColor(); } enum Color implements Print { RED { public String getColor() { return "红色"; } }, GREEN { public String getColor() { return "绿色"; } }, BLUE { public String getColor() { return "蓝色"; } }; } public class InterfaceEnumDemo { public static void main(String args[]) { for (Color c : Color.values()) { System.out.println(c.getColor()); } } }
·可以在枚举类中定义抽象方法
在枚举中定义抽象方法,但是要求枚举中的每个对象部分分别实现此抽象方法。
实例如下:
enum Color { RED { public String getColor() { return "红色"; } }, GREEN { public String getColor() { return "绿色"; } }, BLUE { public String getColor() { return "蓝色"; } }; public abstract String getColor(); //抽象方法 } public class AbstractMethodEnum { public static void main(String args[]) { for (Color c : Color.values()) { System.out.println(c.getColor()); } } }
6.9 Java 系统常见类
6.9.1 系统常见类之System类
System类代表虚拟机运行的系统。它不能被实例化,其方法和属性为static。其方法涉及数组拷贝、标准输入输出、系统属性、环境变量、库加载等等。
常用方法:
currentTimeMillis、exit、get系列方法、gc相关方法。
System类里提供了一个identityHashCode(Object x)方法,该方法返回指定对象的精确hashCode值,也就是根据该对象的地址计算得到的hashCode的值,当某个类的hashCode方法被重写之后,该类实例的hashCode方法就不能唯一地标识该对象。但通过identityHashCode方法返回的hashCode依然是根据该对象的地址计算得到的hashCode值。如果两个对象的identityHashCode值相同,则两个对象绝对是一个对象。
6.9.2 系统常见类之Runtime类
Runtime:运行时,是一个封装了jvm进程的类。代表与虚拟机实例关联的运行时“环境”。 每个 Java 应用程序都有一个 Runtime 类实例,使应用程序能够与其运行的环境相连接。每一个java程序实际上都是启动了一个jvm进程。那么每一个jvm进程都是对应这个Runtime实例,此实例是由jvm为其实例化的。应用程序不能创建自己的Runtime类实例,但可以通过Runtime.getRuntime获得当前运行时实例。
实例:TestRuntime.java
public class Test { public static void main(String[] args) { Runtime runTime = Runtime.getRuntime(); System.out.println(runTime.availableProcessors()); System.out.println(runTime.maxMemory()); System.out.println(runTime.totalMemory()); System.out.println(runTime.freeMemory()); try { runTime.exec("notepad"); } catch (Exception e) { } } }
6.9.3 系统常见类之Math类
Math类是用来支持数学计算的,它打包在java.lang包中,包含一组静态方法和两个常数,是终态(final)的,它不能被实例化。它主要包括下列方法:
int ceil(double d):返回不小于d的最小整数。
int floor(double d):返回不大于d的最大整数。
int round(float f):返回最接近f的整数(int)。
long round(double d):返回最接近d的整数(long)。
极值、绝对值(对于float,int,long有类似的函数):
double abs(double d):返回d的绝对值。
double min(double d1, double d2):返回d1与d2中的小者。
double max(double d1, double d2):返回d1与d2中的大者。
三角函数:
double sin(double d):返回d正弦。对于余弦和正切有类似的函数cosine,tangent。
double asin(double d):返回d反正弦。对于反余弦和反正切有类似的函数acos,atan。
double toDegrees(double r):将弧度换算成度。
double toRadians(double d):将度换算成弧度。
对数、指数:
double log(double d):返回d的自然对数。
double exp(double d):返回以e为底的指数。
其它函数
double sqrt(double d):返回d的平方根。
double pow(double d1, double d2):返回d1的d2次幂。
double random():返回0至1的随机数。区间为[0, 1)。例如想要产生随机字符,可以通过:(char)('a'+Math.random()*('z'-'a'+1))
常数
PI:圆周率,double。
E:自然对数的底,double
6.9.4 系统常见类之Random类
java.util.Random类专门用于生产一个伪随机数,它有两个构造器,一个构造器使用默认的种子,另一个构造器需要程序员显示传入一个long型整数的种子。
相对Math的random方法而言,Random类提供了更多方法生成各种伪随机数,可以生成浮点的,也可以生成整数的,还可以生成指定范围的。
其常见的方法如下:
·protected int next(int bits):生成下一个伪随机数。
·boolean nextBoolean():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的boolean值。
·void nextBytes(byte[] bytes):生成随机字节并将其置于用户提供的 byte 数组中。
·double nextDouble():返回下一个伪随机数,它是取自此随机数生成器序列的、在 0.0 和 1.0 之间均匀分布的double值。
·float nextFloat():返回下一个伪随机数,它是取自此随机数生成器序列的、在 0.0 和 1.0 之间均匀分布的float值。
·int nextInt():返回下一个伪随机数,它是此随机数生成器的序列中均匀分布的int值。
·int nextInt(int n):返回一个伪随机数,它是取自此随机数生成器序列的、在0(包括)和指定值(不包括)之间均匀分布的int值。
·long nextLong():返回下一个伪随机数,它是取自此随机数生成器序列的均匀分布的long值。
·void setSeed(long seed):使用单个long种子设置此随机数生成器的种子。
如果两个Random对象的种子相同,而且方法的调用顺序也相同,则会产生相同的数字序列。为了避免这种情况出现,通常推荐使用当前时间作为Random对象种子。
例如:
Random r1=new Random(21L); Random r2=new Random(21L); int i1=r1.nextInt(); int i2=r2.nextInt(); System.out.println(i1); System.out.println(i2); // 此时得到的i1和i2相等。利用下面的语句可以解决: Random r1=new Random(System.nanoTime()); Random r2=new Random(System.nanoTime());
6.9.5 大数操作类(BigIntger、BigDecimal)
·BigInteger
如果在操作的时候一个整型数据已经超过了整数的最大类型长度long的话,则此数据就无法装入,所以此时要使用BigInteger类进行操作。
·BigDecimal
如果在操作的时候一个浮点数据已经超过了浮点的最大类型长度double的话,则此数据就无法装入,所以此时要使用BigDecimal类进行操作。它是不可变的、任意精度的有符号十进制数。提供以下操作:算术、标度操作、舍入、比较、哈希算法和格式转换。toString()方法提供BigDecimal的规范表示形式。
6.10 Java 垃圾回收机制
Java的垃圾回收是Java语言的重要功能之一。当程序创建对象、数组等引用类型实体时,系统都会在堆内存中为之分配一块内存区,对象就保存在这块内存区中,当这块内存不再被任何引用变量引用时,这块内存就变成垃圾,等待垃圾回收机制进行回收。有些面向对象的程序设计语言,特别是C++,有显示的析构函数,其中放置一些当对象不再使用时需要执行的清理操作。在析构函数中,最常见的操作是回收分配给对象的存储空间。由于Java有自动的垃圾回收器,不需要人工回收内存,所以Java不需要析构方法。
垃圾回收机制只负责堆内存中对象,不会回收栈空间和任何物理资源(网络、IO等资源)。垃圾是如何产生的呢?比如创建匿名对象new Person("hello", 26);没有使用的时候,就产生了垃圾。程序员可以通知垃圾回收器进行垃圾回收,但是无法精确控制垃圾回收的运行,垃圾回收会在适当的时候自动进行。
对象整个生命周期中的各种状态:激活状态、去活状态、死亡状态。对象的状态图如下:
当垃圾回收机制回收某个对象所占用的内存之前,通常要求程序调用适当的方法来清理资源,在没有明确指定资源清理的情况下,java提供了默认机制来清理该对象的资源,这个方法就是finalize,该方法是定义在Object类的实例方法,方法原型为:
protected void finalize() throws Throwable
finalize方法特点:永远不要主动调用某个对象的finalize方法,该方法应交给垃圾回收机制调用;finalize方法何时被调用,是否被调用具有不确定性。当jvm执行去活对象的finalize方法时,可以复写该方法使该对象或者系统中其他对象重新变成激活状态;当jvm执行finalize方法时出现了异常,垃圾回收机制不会报告异常,程序继续执行。
通知垃圾回收器进行垃圾回收:
·调用System类的gc()静态方法:System.gc()
·调用Runtime对象gc()实例方法:Runtime.getRuntime().gc();
实例如下:
public class TestFinalize { private static TestFinalize tf = null; public static void main(String[] args) { tf = new TestFinalize(); //激活状态 System.out.println(tf); //打出对象tf tf = null; //去活状态 System.out.println(tf); //打出tf为null System.gc(); //通知垃圾回收 try { //适当的延迟 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(tf); //再次打出tf } public void finalize() throws Throwable { System.out.println("测试资源回收finalize方法"); tf = this; //在finalize方法中,将tf去活状态-->激活状态 } }
使用Java 命令-verbose:gc来运行可以查看每次垃圾回收以后的提示信息,例如:
java –verbose:gc Test
另外,System和Runtime类里都提供了一个runFinalization方法,可以强制垃圾回收机制调用系统中去活对象的finalize方法。
·本章摘要:
1、通过extends关键字,可将父类的成员(包含数据成员与方法)继承到子类。
2、Java在执行子类的构造方法之前,会先调用父类中无参的构造方法,其目的是为了对继承自父类的成员做初始化的操作。
3、父类有数个构造方法时,如要调用特定的构造方法,则可在子类的构造方法中,通过super()这个关键字来完成。
4、this()是在同一类内调用其它的构造方法,而super()则是从子类的构造方法调用其父类的构造方法。
5、this()除了可用来调用同一类内的其它构造方法之外,如果同一类内“实例变量”与局部(local)变量的名称相同时,也可利用它来调用同一类内的“实例变量”。
6、this()与super()其相似之处:(1)当构造方法有重载时,两者均会根据所给予的参数的类型与个数,正确地执行相对应的构造方法。(2)两者均必须编写在构造方法内的第一行,也正是这个原因,this()与super()无法同时存在同一个构造方法内。
7、“重载”(overloading),它是指在相同类内,定义名称相同,但参数个数或类型不同的方法,因此Java便可依据参数的个数或类型调用相应的方法。
8、“复写”(overriding),它是在子类当中,定义名称、参数个数与类型均与父类相同的方法,用以复写父类里的方法。
9、如果父类的方法不希望子类的方法来复写它,可在父类的方法之前加上“final”关键字,如此该方法便不会被复写。
10、final的另一个功用是把它加在数据成员变量前面,如此该变量就变成了一个常量(constant),如此便无法在程序代码中再做修改了。
11、所有的类均继承自Object类。
12、复写Object类中的equals() method可用来比较两个类的对象是否相等。
13、Java可以创建抽象类,专门用来当做父类。抽象类的作用类似于“模板”,其目的是依据其格式来修改并创建新的类。
14、抽象类的方法可分为两种:一种是一般的方法,另一种是以abstract关键字开头的“抽象方法”。“抽象方法”并没有定义方法体,而是要保留给由抽象类派生出的新类来定义。
15、利用父类的变量数组来访问子类的内容的较好的做法是:
(1)先创建父类的变量数组;
(2)利用数组元素创建子类的对象,并以它来访问子类的内容。
16、抽象类不能直接用来产生对象。
17、接口的结构和抽象类非常相似,它也具有数据成员与抽象method,但它与抽象类有两点不同:(1)、接口的数据成员必须初始化。(2)、接口里的方法必须全部都声明成abstract。
18、利用接口的特性来打造一个新的类,称为接口的实现(implementation)。
19、Java并不允许多重继承。
20、接口与一般类一样,均可通过扩展的技术来派生出新的接口。原来的接口称为基本接口或父接口;派生出的接口成为派生接口或子接口。通过这种机制,派生接口不仅可以保留父接口的成员,同时也可以加入新的成员以满足实际的需要。
21、Java对象的多态性分为:向上转型(自动)、向下转型(强制)。
22、通过instanceof关键字,可以判断对象属于那个类。
23、匿名内部类(anonymous inner class)的好处是可利用内部类创建不具有名称的对象,并利用它访问到类里的成员。
感谢阅读。如果感觉此章对您有帮助,却又不想白瞟