导图社区 Java类的高级特性
这是一篇关于Java类的高级特性的思维导图,主要内容有一、Java类包二、final变量三、final方法四、 final类五、内部类。
编辑于2022-12-08 17:38:50 贵州微信小程序开发知识整理,系统地介绍了微信小程序开发的基础知识,帮助开发者更好地理解和掌握小程序开发的要点和流程。
SpringFramework技术的介绍,SpringFramework,通常简称为Spring,是一个开源的企业级Java应用程序框架,由Rod Johnson创建,并于2004年首次发布。Spring Framework的主要目标是简化企业级Java开发,提高开发效率和应用程序的可维护性。
Maven的特性以及使用部署方法,Maven介绍:Maven是一款为Java项目构建管理、依赖管理的工具(软件),使用Maven可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。
社区模板帮助中心,点此进入>>
微信小程序开发知识整理,系统地介绍了微信小程序开发的基础知识,帮助开发者更好地理解和掌握小程序开发的要点和流程。
SpringFramework技术的介绍,SpringFramework,通常简称为Spring,是一个开源的企业级Java应用程序框架,由Rod Johnson创建,并于2004年首次发布。Spring Framework的主要目标是简化企业级Java开发,提高开发效率和应用程序的可维护性。
Maven的特性以及使用部署方法,Maven介绍:Maven是一款为Java项目构建管理、依赖管理的工具(软件),使用Maven可以自动化构建、测试、打包和发布项目,大大提高了开发效率和质量。
Java类的高级特性
一、 Java类包
在Java中每定义好一个类,通过Java编译器进行编译之后,都会生成一个扩展名为.class的文件。当程序的规模逐渐扩大时,就很容易发生类名称冲突的现象。JDK API中提供了成千上万具有各种功能的类,系统又是如何管理的呢?Java中提供了一种管理类文件的机制,就是类包。
1. 类名冲突
Java中每个接口或类都来自不同的类包,无论是Java API中的类与接口还是自定义的类与接口,都需要隶属于某一个类包,这个类包包含了一些类和接口。如果没有包的存在,管理程序中的类名称将是一件非常麻烦的事情。如果程序只由一个类定义组成,并不会给程序带来什么影响,但是随着程序代码的增多,难免会出现类同名的问题。例如,在程序中定义一个Login类,因业务需要,还要定义一个名称为Login的类,但是这两个类所实现的功能完全不同,于是问题就产生了—编译器不会允许存在同名的类文件。解决这类问题的办法是将这两个类放置在不同的类包中。
2. 完整的类路径
一个完整的类名需要包名与类名的组合,每个类都隶属于一个类包,只要保证同一类包中的类不同名,就可以有效地避免同名类冲突的情况。例如,一个程序中同时使用到java.util.Date类与java.sql.Date类,如果在程序中不指定完整类路径,编译器不会知道这段代码使用的是java.util类包中的Date类还是java.sql类包中的Date类,所以需要在指定代码中给出完整的类路径。在程序中使用两个不同Date类的完整路径,可以使用如下代码: java.util.Date date=new java.util.Date(); java.sql.Date date1=new java.sql.Date(2333);
在Java中采用类包机制非常重要,类包不仅可以解决类名冲突问题,还可以在开发庞大的应用程序时,帮助开发人员管理庞大的应用程序组件,方便软件复用。
同一个包中的类相互访问时,可以不指定包名。同一个包中的类可以不存放在同一个位置,如com.cxk.class1和com.cxk.class2可以一个放置在C盘,另一个放置在D盘,只要将CLASSPATH分别指向这两个位置即可
3. 创建包
在Java中包名设计应与文件系统结构相对应,如一个包名为com.cxk,那么该包中的类位于com文件夹下的cxk子文件夹下。没有定义包的类会被归纳在预设包(默认包)中。在实际开发中,应该为所有类设置包名,这是良好的编程习惯。
在类中定义包名的语法如下: package 包名;
在类中指定包名时,需要将package表达式放置在程序的第一行,它必须是文件中的第一行非注释代码。使用package关键字为类指定包名之后,包名将会成为类名中的一部分,预示着这个类必须指定全名。例如,在使用位于com.cxk包下的Zy.java类时,需要使用形如com.cxk.Zy这样的表达式。
Java包的命名规则是全部使用小写字母
如此之多的包不会产生包名冲突现象吗?这是有可能的。为了避免这样的问题,在Java中定义包名时通常使用创建者的Internet域名的反序,由于Internet域名是独一无二的,包名自然不会发生冲突。
实例代码: package com.cxk; //指定包名 public class Math { public static void main(String[] args) { System.out.println("这不是java.lang.Math类,而是com.cxk.Math类"); } }
运行结果: 这不是java.lang.Math类,而是com.cxk.Math类
4. 导入包
使用import关键字导入包:如果某个类中需要使用Math类,那么如何告诉编译器当前应该使用哪一个包中的Math类,是java.lang.Math还是com.cxk.Math类?这时可以使用import关键字指定。
import关键字语法: import com.cxk.*; //指定包中的所有类在程序中都可用 import com.cxk.Math; //指定包中的Math类在程序中可用
如果类定义中已经导入com.lzw.Math类,在类体中再使用其他包中的Math类时就必须指定完整的带有包格式的类名,如这种情况再使用java.lang包的Math类时就要使用全名格式java.lang.Math。 在程序中添加import关键字时,就开始在CLASSPATH指定的目录中进行查找,查找子目录com.cxk,然后从这个目录下编译完成的文件中查找是否有名称符合者,最后寻找到Math.class文件。另外,当使用import指定了一个包中的所有类时,并不会指定这个包的子包中的类,如果用到这个包中的子类,需要再次对子包作单独引用。
5. 使用import导入静态成员
import关键字除了导入包以外,还可以导入静态成员。导入静态成员可以使编程更为方便
语法如下: import static 静态成员
实例代码: package com.cxk; import static java.lang.Math.max; //导入静态成员方法 import static java.lang.System.out; //导入静态成员变量 public class importTest { public static void main(String[] args) { out.println("6和8较大的数为:"+max(6,8)); } }
导入静态成员后,在主方法中可以直接使用这些静态成员
运行结果: 6和8较大的数为:8
二、 final变量
inal关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final定义的变量为常量。例如,在类中定义PI值,可以使用如下语句: final double PI=3.14; 当在程序中使用到PI这个常量时,它的值就是3.14。如果在程序中再次对定义为final的常量赋值,编译器将不会接受。final关键字定义的变量必须在声明时对其进行赋值操作。final除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组也可以被看作一个对象来引用,所以final可以修饰数组。一旦一个对象引用被修饰为final后,它只能恒定指向一个对象,无法将其改变以指向另一个对象。一个既是static又是final的字段只占据一段不能改变的存储空间。
三、 final方法
将方法定义为final类型,可以防止子类修改该类的定义与实现方式,同时定义为final的方法的执行效率要高于非final的方法。在修饰权限中曾经提到过private修饰符,如果一个父类的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法。也就是说一个定义为private的方法隐式被指定为final类型,因此无需将一个定义为private的方法再定义为final类型。 但是在父类中被定义为private final的方法似乎可以被子类覆盖。
实例代码: package com.cxk; import static java.lang.System.out; class Parents { private final void doit() { out.println("父类.doit()"); } final void doit1() { out.println("父类.doit1()"); } public void doit2() { out.println("父类.doit2()"); } } class Sub extends Parents { public final void doit() { out.println("子类.doit()"); } //final void doit1() //final方法不能覆盖 // { // out.println("子类.doit1()"); // } public void doit2() { out.println("子类.doit2()"); } } public class FinalMethod { public static void main(String[] args) { Sub s=new Sub(); //实例化子类 s.doit(); Parents p=s; //子类向上转型 //p.doit(): //不能调用priva方法 p.doit2(); p.doit1(); } }
运行结果: 子类.doit() 子类.doit2() 父类.doit1()
从本实例中可以看出,final方法不能被覆盖,例如,doit2()方法不能在子类中被重写,但是在父类中定义了一个private final的doit()方法,同时在子类中也定义了一个doit()方法,从表面上来看,子类中的doit()方法覆盖了父类的doit()方法,但是覆盖必须满足一个对象向上转型为它的基本类型并调用相同方法这样一个条件。例如,在主方法中使用“Parents p=s;”语句执行向上转型操作,对象p只能调用正常覆盖的doit3()方法,却不能调用doit()方法,可见子类中的doit()方法并不是被正常覆盖,而是生成了一个新的方法。
四、 final类
定义为final的类不能被继承。如果希望一个类不被任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为final形式。
final类的语法如下: final 类名{}
实例代码: package com.cxk; import static java.lang.System.out; final class FinalClass { int a=6; void doit() { } public static void main(String[] args) { FinalClass f=new FinalClass(); f.a++; out.println(f.a); } }
运行结果: 7
五、 内部类
在一个文件中定义两个类,则其中任何一个类都不在另一个类的内部;如果在类中再定义一个类,则将在类中再定义的那个类称为内部类。内部类可分为成员内部类、局部内部类以及匿名类。
1. 成员内部类
在一个类中使用内部类,可以在内部类中直接存取其所在类的私有成员变量。
语法如下: public class OutClass {//外部类 private class innerClass {//内部类 } }
在内部类中可以随意使用外部类的成员方法以及成员变量,尽管这些类成员被修饰为private。
实例代码: package com.cxk; public class OuterClass { innerClass in=new innerClass(); //在外部类实例化内部类对象引用 public void out() { in.inf(); //在外部类方法中调用内部类方法 } class innerClass { innerClass(){ System.out.println("这是内部类构造方法"); } public void inf() { System.out.println("这是内部类成员方法"); } int y=8; //定义内部类成员变量 } public innerClass doit() //外部类方法,返回值为内部类引用 { //y=8; //这行代码会报错,外部类不可以直接访问内部类成员变量 System.out.println(in.y); return new innerClass(); //返回内部类引用 } public static void main(String[] args) { OuterClass out=new OuterClass(); //内部类的对象实例化操作必须在外部类或外部类的非静态方法中实现 OuterClass.innerClass in=out.doit(); OuterClass.innerClass in1=out.new innerClass(); } }
如果在外部类和非静态方法之外实例化内部类对象,需要使用外部类.内部类的形式指定该对象的类型。
运行结果: 这是内部类构造方法 8 这是内部类构造方法 这是内部类构造方法
2. 内部类向上转型为接口
如果将一个权限修饰符为private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的具体实现过程。可以在外部提供一个接口,在接口中声明一个方法。如果在实现该接口的内部类中实现该接口的方法,就可以定义多个内部类以不同的方式实现接口中的同一个方法,而在一般的类中是不能多次实现接口中同一个方法的,这种技巧经常被应用在Swing编程中,可以在一个类中做出多个不同的响应事件
package com.cxk; interface OutInterface { //定义一个接口 public void function(); } class OuterClass2 //外部类 {//定义一个内部类实现接口 private class InnerClass implements OutInterface { InnerClass(String s) { System.out.println(s);//内部类构造方法 } public void function() { //实现接口的方法 System.out.println("访问内部类中的方法"); } } public OutInterface doit() { return new InnerClass("访问内部类构造方法"); } } public class InterfaceInner { public static void main(String[] args) { OuterClass2 out=new OuterClass2(); //调用doit方法返回一个OutInterface接口 OutInterface outinter=out.doit(); outinter.function(); } }
OuterClass2类中定义了一个修饰权限为private的内部类,这个内部类实现了OutInterface接口,然后修改doit()方法,使该方法返回一个OutInterface接口。由于内部类InnerClass修饰权限为private,所以除了OuterClass2类可以访问该内部类之外,其他类都不能访问,而可以访问doit()方法。由于该方法返回一个外部接口类型,这个接口可以作为外部使用的接口。它包含一个function()方法,在继承此接口的内部类中实现了该方法,如果某个类继承了外部类,由于内部的权限不可以向下转型为内部类InnerClass,同时也不能访问function()方法,但是却可以访问接口中的function()方法。例如,InterfaceInner类中最后一条语句,接口引用调用function()方法,从执行结果可以看出,这条语句执行的是内部类中的function()方法,很好地对继承该类的子类隐藏了实现细节,仅为编写子类的人留下一个接口和一个外部类,同时也可以调用function()方法,但是f()方法的具体实现过程却被很好地隐藏了,这就是内部类最基本的用途。
运行结果: 访问内部类构造方法 访问内部类中的方法
3. 使用this关键字获取内部类与外部类的引用
如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用this关键字
实例代码: package com.cxk; public class TheSameName { private int x=6; private class Inner { private int x=9; public void doit(int x) { x++; //调用的是形参x this.x++; //调用的是内部类x TheSameName.this.x++; //调用外部类的变量x System.out.println(x+"\t"+this.x+"\t"+TheSameName.this.x); } } public static void main(String[] args) { TheSameName.Inner in=new TheSameName().new Inner(); in.doit(8); } }
在类中,如果遇到内部类与外部类的成员变量重名的情况,可以使用this关键字进行处理。例如,在内部类中使用this.x语句可以调用内部类的成员变量x,而使用TheSameName.this.x语句可以调用外部类的成员变量x,即使用外部类名称后跟一个点操作符和this关键字便可获取外部类的一个引用。
运行结果: 9 10 7
4. 局部内部类
内部类不仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或任意的作用域中均可以定义内部类
实例代码: package com.cxk; import static java.lang.System.out; interface OutInterface { //定义一个接口 } public class OutClass { public OutInterface doit(final String x) //doti方法参数为final类型 { //在doit方法中定义一个内部类 class InnerClass implements OutInterface { InnerClass(String s) { s=x; out.println(s); } } return new InnerClass(""); } public static void main(String[] args) { OutInterface inter=new OutClass().doit("Just Do It!"); } }
内部类被定义在了doit()方法内部。但是有一点值得注意,内部类InnerClass是doit()方法的一部分,并非OuterClass类中的一部分,所以在doit()方法的外部不能访问该内部类,但是该内部类可以访问当前代码块的常量以及此外部类的所有成员。
运行结果: Just Do It!
如果需要在方法体中使用局部变量,该局部变量需要被设置为final类型,换句话说,在方法中定义的内部类只能访问方法中final类型的局部变量,这是因为在方法中定义的局部变量相当于一个常量,它的生命周期超出方法运行的生命周期,由于该局部变量被设置为final,所以不能在内部类中改变该局部变量的值。
5. 匿名内部类
匿名类的所有实现代码都需要在大括号之间进行编写。语法如下: return new A() {//内部类体 }
实例代码: package com.cxk; interface OutInterface2 { } public class OuterClass3 { public OutInterface2 doit() { return new OutInterface2() { //声明匿名内部类 private int i=0; public int getVaule() { return i; } }; } }
在doit()方法内部首先返回一个OutInterface2的引用,然后在return语句中插入一个定义内部类的代码,由于这个类没有名称,所以这里将该内部类称为匿名内部类。实质上这种内部类的作用就是创建一个实现于OutInterface2接口的匿名类的对象。
匿名内部类没有名称,所以匿名内部类使用默认构造方法来生成OutInterface2对象。在匿名内部类定义结束后,需要加分号进行标识,这个分号并不代表内部类的结束,而是代表OutInterface2引用表达式的创建。
匿名内部类编译以后,会产生以“外部类名$序号”为名称的.class文件,序号以1~n排列,分别代表1~n个匿名内部类。
6. 静态内部类
在内部类前添加修饰符static,这个内部类就变为静态内部类了。一个静态内部类中可以声明static成员,但是在非静态内部类中不可以声明静态成员。静态内部类有一个最大的特点,就是不可以使用外部类的非静态成员,所以静态内部类在程序开发中比较少见。 可以这样认为,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部类对象,但如果内部类被定义为static,就会有更多的限制。静态内部类具有以下两个特点:
1. 如果创建静态内部类的对象,不需要其外部类的对象
2. 不能从静态内部类的对象中访问非静态外部类的对象
package com.cxk; public class StaticInnerClass { int x=100; //外部类成员变量 static class inner { void doitInner() { System.out.println("外部类" + x); //代码会报错,静态内部类不能直接调用外部类的非静态成员变量 } public static void main(String[] args) { System.out.println("cxk"); } } }
在内部类的doitInner()方法中调用成员变量x,由于Inner被修饰为static形式,而成员变量x却是非static类型的,所以在doitInner()方法中不能调用x变量。
进行程序测试时,如果在每一个Java文件中都设置一个主方法,将出现很多额外代码,而程序本身并不需要这些主方法,为了解决这个问题,可以将主方法写入静态内部类中。 编译该类,将生成一个名称为StaticInnerClass$Inner的独立类和一个StaticInnerClass类,只要使用java StaticInnerClass$Inner,就可以运行主方法中的内容,这样当完成测试,需要将所有.class文件打包时,只要删除StaticInnerClass$Inner独立类即可。
7. 内部类的继承
内部类和其他普通类一样,可以被继承,但是继承内部类比继承普通类复杂,需要设置专门的语法来完成
实例代码: package com.cxk; class A { class B { } } public class OutputInnerClass extends A.B{ public OutputInnerClass(A a) { a.super(); } }
在某个类继承内部类时,必须硬性给予这个类一个带参数的构造方法,并且该构造方法的参数为需要继承内部类的外部类的引用,同时在构造方法体中使用a.super()语句,这样才为继承提供了必要的对象引用。