类的高级特性
一、 Java类包
在Java中定义好一个类,通过Java编译器进行编译之后,都会生成一个扩展名为.class的文件,当着个程序的规模逐渐庞大时,就会很容易发生类名冲突的现象。而JDK API中提供了成千上万具有各种功能的类,所以Java提供了一种管理类文件的机制,就是类包。
1.1类名冲突
Java中每一个接口或者类都来自于不同的类包,无论是Java API中的类与接口还是自定义的类与接口都需要隶属于某一个类包,在这个类包中包含了一些类和接口。如果没有包的存在,管理程序中的类名称将会很麻烦。如果程序只是由一个类定义或者组成,并不会给程序带来什么影响,但是随着程序变得庞大,代码量不断的增多难免会出现类名冲突的问题。编译器不会允许存在同名的类文件,而解决这个问题的唯一办法就是将两个类放在不同的包中。
1.2完整的类路径
举个栗子吧,比如Scanner类。但是其实Math类并不是一个完整的名称。一个完整的类名称需要包名与类名的组合,每个类都隶属于一个类包,只要保证同一类包中的类不同名,就可以有效的避免类名冲突的情况。
再比如,一个程序中同时适应java.util.Date类和java.sql.Date,如果在程序中不指定完整的类路径,编译器就不会知道这段代码使用的究竟是哪一个Date类。
在Java中采用的类包机制非常重要,类包不仅可以解决类名冲突问题,还可以在开发庞大的应用程序时帮助开发人员管理庞大的应用组件,以此方便软件复用。
1.3创建包
在Java中包名设计应与文件系统结构相对应,如一个包名为com.bing,name该包中的类就位于com文件夹下的bing子文件夹下。没有定义包的类会被归纳在魔刃豹中。在实际的开发中应该为所有的类设置包名,这是良好的编程习惯。
在类中定义包的语法如下:
package 包名
在类中指定包名时候需要将package表达式防治在程序的第一行,它必须是文件中第一行非注释的代码,当适应package关键字为类指定包名后,包名将会成为类名的一部分,预示着这个类必须指定全名。
注意:
Java包的命名规则是全部使用小写字母。
举个栗子:
在IDEA中创建一个包:
1.首先打开IDEA开发工具,鼠标右击src目录,然后将鼠标放在new上,在右边弹出的框中找到Package,然后鼠标左键单击
2.在弹出的对话框中输入包名,以下图为例,输入完成后点击OK按钮。
3.IDEA左侧可以找到我们刚刚创建好的包(由于我的工具里还创建了其他包,所以看上去比较乱,请自动忽略,谢谢合作!!!)
4.鼠标右击test,然后将鼠标放在new上,人后点击Java Class,创建一个类文件。
5.在弹出的对话框中输入类名点击OK按钮吗,这里以HelloWorld为例。
6.创建好该类后效果如下,红色的框中就是包名。
导入包可以使用import关键字指定,例如在程序中需要适应Scanner类时,就可以用import关键字用来导入包。
语法:
import java.util.Scanner; //指定在com.se.test包中的HelloWorld类在程序中可以使用
在使用import关键字时候,可以指定类的完整描述,如果为了使用包中更多的类,可以在使用import关键字指定时在包指定后面加上*,这表示可以在程序中使用包中的所有类。
1.4导入包
1.使用import关键字导入包
如果某个类中需要使用Scanner类,那么此时可以使用Java中的关键字import关键字指定。
import 关键字的语法如下:
//两种方法均可以导入Sacnner包
import java.util.Scanner;
import java.util.*;
2.使用import导入静态成员
import关键字除了导入包之外,还可以导入静态成员,这是JDK5.0以上版本提供的新功能。导入静态成员可以使程序员在繁重的开发工作中变得更加方便。
使用import关键字导入静态成员的与法如下:
import static 静态成员;
举个栗子:
package com.se.test;
import static java.lang.Math.max;
import static java.lang.System.out;
/**
* 描述:
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
public class ImportTest {
public static void main(String[] args) {
//在main()方法中可以直接使用这些静态成员
out.println("1和4的较大值为:" + max(1,4));
}
}
运行结果:
二、final变量
final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值。通常,由final定义的变量为常量。
例如:
final int num = 1;
当在程序中使用num这个变量时,他的值就是1,如果在程序中再次对定义final的常量赋值,编译器将不会接受。
final关键字定义的变量必选在声明时对其进行赋值操作。它除了可以修饰基本数据类型的常量,还可以修饰对象引用。由于数组也可以被看做一个对象来引用,所以final可以修饰数组。一旦一个对象引用被修饰为final后,它只能恒定指向一个对象,无法改变将其指向另一个对象。而一个既是static又是final的字段只占据一段不能改变的存储空间。
举个栗子:
package com.se.test;
import java.util.Random;
import static java.lang.System.out;
/**
* 描述:
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
class Test{
int i = 0;
}
public class FinalData {
static Random rand = new Random();
private final int VALUE_1 = 9; //声明一个final常量
private static final int VALUE_2 = 10;//声明一个final、static常量
private Test test = new Test(); //声明一个final引用
private Test test2 = new Test(); //声明一个不是final的引用
private static final int[] a = {1,2,3,4,5,6}; //声明一个定义为final/static的数组
private final int num1 = rand.nextInt(20);
private static final int num2 = rand.nextInt(20);
public String toString(){
return num1 + "" + num2 + "";
}
public static void main(String[] args) {
FinalData data = new FinalData();
data.test = new Test();
//可以对指定weifinal的引用中的成员变量赋值,但是不能将定义为final的引用指向其他引用
//data.VALUE_2++;
//不能改变定义为final的常量值
data.test2 = new Test(); //可以将没有定义为final的引用指向其他引用
for (int i = 0; i < data.a.length; i++) {
a[i] = 9;
//不能对定义为final的数组赋值
}
out.println(data);
out.println("data2");
out.println(new FinalData());
out.println(data);
}
}
定义为final的数据无论是常量、对象引用还是数组,在主函数中都不可以被改变。
一个被定义为final的对象引用只能指向唯一一个对象,不可以将它再指向其他对象,但是一个对象本身的值却是可以改变的,那么为了使一个常量真正做到不可更改,可以将常量生命为static final。
验证一下:
package com.se.test;
import java.util.Random;
import static java.lang.System.out;
/**
* 描述:
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
public class FinalStaticData {
private static Random rand = new Random(); //实例化一个Random类对象
//随机产生0-10之间的随机数赋予定义为final的num1
private final int num1 = rand.nextInt(10);
//随机产生0-10之间的随机数赋予定义为static final的num2
private static final int num2 = rand.nextInt(10);
public static void main(String[] args) {
FinalStaticData finalStaticData = new FinalStaticData();//实例化对象
//调用定义为final的num1
out.println("重新实例化对象调用num1的值:" + finalStaticData.num1);
//调用定义为static final的a2
out.println("重新实例化对象调用num2的值:" + finalStaticData.num2);
//实例化另一个对象
FinalStaticData finalStaticData2 = new FinalStaticData();
out.println("重新实例化对象调用num1的值:" + finalStaticData2.num1);
out.println("重新实例化对象调用num1的值:" + finalStaticData2.num2);
}
}
运行结果:
定义为final的常量不是恒定不变的,将随机数赋予定义为final的常量,可以做到每次运行程序时改变num1的值。但是num1和num2不同,由于它被声明为static final形式,所以在内存中为a2开辟了一个恒定不变的区域,当再次实例化一个FinalStaticData对象时,仍然指向num2这块内存区域,所以num2的值保持不变。num2在装载时被初始化,而不是每次创建新对象时都被初始化,而num1会在重新实例化时被更改。
小技巧:
在Java中定义全局常量,通常使用public static final修饰,这样的常量只能在定义时被赋值。
可以将方法的参数定义为final类型,这预示着无法在方法中更该参数引用所指向的对象。
三、final方法
将方法定义为final类型可以防止子类修改类的定义和实现方式,同时定义为final的方法执行效率要高于非final方法。如果一个夫列为的某个方法被设置为private修饰符,子类将无法访问该方法,自然无法覆盖该方法,所以一个定义为private的方法隐式被指定为final类型,这样无需将一个定义为private的方法再定义为final类型。
举个栗子:
private final void test(){
//省略一些程序代码
}
但是在父类中被定义为private final的方法似乎被子类覆盖,那么,再举个栗子:
package com.se.test;
/**
* 描述:创建FinalMethod类,在该类中创建Parents类和继承该类
* 的Sub类,在主方法中调用这两个类中的方法,并查看final类型方
* 法能否被覆盖
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
class Parents{
private final void doit(){
System.out.println("父类.doit()");
}
final void doit2(){
System.out.println("父类.doit2()");
}
public void doit3(){
System.out.println("父类.doit3()");
}
}
class Sub extends Parents{
public final void doit(){//在子类中定义一个diit()方法
System.out.println("子类.doit()");
}
// final void doit2(){ //final方法不能覆盖
// System.out.println("子类.doit2");
// }
public void doit3(){
System.out.println("子类.doit3");
}
}
public class FinalMethod {
public static void main(String[] args) {
Sub sub = new Sub();//实例化
sub.doit();//调用doit方法
Parents p = sub;//执行向上转型操作
//p.doit();//不能调用private方法
p.doit2();
p.doit3();
}
}
运行结果:
四、final类
首先,定义为final的类不能被继承
如果希望一个类不允许任何类继承,并且不允许其他人对这个类进行任何改动,可以将这个类设置为final形式
final类的语法:
final 类名{}
如果将某个类设置为final形式,则类中的所有方法都被隐式设置为final形式,但是final类中的成员变量可以被定义为final或非final形式。
举个栗子:
package com.se.test;
/**
* 描述:在这个类先定义diit()方法和变量num,实现在主方法中操作变量自增
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
final class FinalClass {
int num = 5;
void doit() {
}
public static void main(String[] args) {
FinalClass finalClass = new FinalClass();
finalClass.num++;
System.out.println(finalClass.num);
}
}
运行结果:
五、内部类
在前面的例子中,在一个文件中定义两个类,但其中任何一个类都不再另一个类的内部,而如果在类中再定义一个类,则将在类中再定义的那个类称为内部类。内部类可以分为成员内部类、局部内部类和匿名类。
5.1成员内部类
5.1.1什么是成员内部类
在一个类中使用内部类,可以在内部类中直接存取其所在类的私有成员变量。
语法:
public class OuterClass {
private class InnerClass{
//省略部分代码
}
}
在内部类中可以随意使用外部类的成员方法以及成员变量,即使这些类成员被修饰为private,但是在内部类中可以直接使用外部类中的类成员。
内部类的实例一定要绑定在外部类的实例上,如果从外部类中初始化一个内部类对象,那么内部类对象就会绑定在外部类对象上。内部类初始化方式与其他类初始化方式相同,都是使用new关键字。
举个栗子:
package com.se.test;
/**
* 描述:在OuterClass类中定义innerClass内部类和doit()方法,在主方法中创建OuterClass类
* 的实例对象和diit()方法
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
public class OuterClass {
innerClass innerClass = new innerClass();//在外部类实例化内部类对象引用
public void ouf() {
innerClass.inf();
}
class innerClass {
innerClass() {//内部类构造方法
}
public void inf() {//内部成员方法
}
int y = 0; //内部成员变量
}
public innerClass doit() {//外部类方法,返回值为内部类引用
// y =4; //外部类不可以直接访问内部类成员变量
innerClass.y = 4;
return new innerClass();//返回内部类引用
}
public static void main(String[] args) {
OuterClass outerClass = new OuterClass();
//内部类的对象实例化操作必须在外部类或者外部类的非静态方法中实现
OuterClass.innerClass innerClass = outerClass.doit();
OuterClass.innerClass innerClass2 = outerClass.new innerClass();
}
}
上面的例子中的外部类创建内部类实例与其他类创建对象引用时相同。内部类可以访问它的外部类成员,但内部类的成员只有在内部类的范围内是可知的,不能被外部类使用。内部类对象与外部类对象关系非常紧密,内外可以交互使用彼此类中定义的变量。
注意:
-
如果在外部类和非静态方法之外实例化内部类对象,需要使用外部类。内部类的形式指定该对象的类型。
-
在实例化内部类对象时,崩在new操作符之前使用外部类名称实例化内部类对象,而是应该使用外部类的对象创建其内部类的对象。
-
内部类的对象会依赖外部类对象,除非已经穿在一个外部类对象,否则类中不会出现内部类对象。
5.1.2内部类向上转型为接口
如果将一个权限修饰符为private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的具体实现过程。可以在外部提供一个接口,在接口中声明一个方法,如果在实现接口的内部类中实现该接口的方法,就可以定义多个内部类以不同的方式实现接口中的同一个方法,而在一般的类中是不能多次实现接口中同一个方法的,这种技巧经常经常被应用在Swing编程中,可以在一个类中做出多个不同的响应事件(Swing编程后续会更新)
举个栗子:
package com.se.test;
/**
* 描述:创建了一个InterfaceInner类,并定义接口OutInterface,使内部类InterClass实现
* 这个接口,左后使doit()方法返回值类型为该接口
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
interface OutInterface{ //定义一个接口
public void f();
}
public class InterfaceInner{
public static void main(String[] args) {
OuterClass2 outerClass2 = new OuterClass2();//实例化一个OuterClass2对象
//调用doit()方法,返回一个OutInterface接口
OutInterface outInterface = outerClass2.doit();
outInterface.f();
}
}
class OuterClass2 {
//定义一个内部类实现OutInterface接口
private class InnerClass implements OutInterface{
InnerClass(String s){//内部类构造方法
System.out.println(s);
}
public void f(){//实现接口中的f()方法
System.out.println("访问内部类中的f()方法");
}
}
public OutInterface doit(){//定义一个方法,返回值类型为OutInterface接口
return new InnerClass("访问内部类构造方法");
}
}
运行结果:
分析一下:
上述代码中定义了一个权限修饰符为private的内部类,这个内部类实现了OutInterface接口,然后修改doit()方法,使该方法返回一个OutInterface接口。由于内部类InnerClass修饰权限为private,所以除了OuterClass2类可以访问该内部类之外,其他类都不能访问,而可以访问doit()方法。由于该方法返回一个外部接口类型,这个接口可以作为外部使用的接口。它包含一个f()方法,在继承该接口的内部类实现了该方法,如果某个类继承了外部类,由于内部的权限不可以向下转型为内部类InnerClass,同时也不能访问f()方法,但是却可以访问接口中的f()方法。例如,InterfaceInner类中最后一条语句,接口引用调用f()方法,从执行结果可以看出,这条语句执行的是内部类中的f()方法,很好地对继承该类的子类隐藏了实现细节,仅为编写子类的人留下一个接口和一个外部类,同时也可以调用f()方法,但是f()方法的具体实现过程却被很好的隐藏了,这是内部类的根本用途。
注意:
非内部类不能被声明为private或protected访问类型
5.1.3使用this关键字获取内部类与外部类的引用
如果在外部类中定义的成员变量与内部类的成员变量名称相同,可以使用this关键字。
举个栗子:
/**
* 描述:创建类TheSameName,在类中定义成员变量num,再定义一个内部类Inner,在
* 内部类中也创建num变量,并在内部类的doit()方法中分别对两个num变量操作
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
public class TheSameName {
private int num;
private class Inner{
private int num = 9;
public void doit(){
num++; //调用形参num
this.num++; //调用内部类的变量num
TheSameName.this.num++; //调用外部类的变量num
}
}
}
如果遇到内部类中定义的成员变量重名的情况,可以使用this关键字进行处理。在内部类中使用this.num语句可以调用内部类的成员变量num,而使用TheSameName.this.num语句可以调用外部类的成员变量num,即使用外部类名称后跟一个点操作符和this关键字就可以获取外部类的一个引用。
5.2局部内部类
内部类不仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或任意的作用域中均可以定义内部类。
举个栗子:
package com.se.test;
/**
* 描述:jiang InnerClass放在doit()方法内部
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
interface OutInterface2{//定义一个接口
}
class OuterClass3 {
public OutInterface2 doit(final String s){//doit()方法参数为final类型
//在doit()方法中定义一个内部类
class InnerClass2 implements OutInterface2{
InnerClass2(String str){
str = s;
System.out.println(str);
}
}
return new InnerClass2("doit");
}
}
分析一下:
上面的代码块中,内部类被定义在doit()方法内部。但是需要注意的是,内部类InnerClass2是doit()方法的一部分,并非OuterClass3类中的一部分,所以在doit()方法的外部不能访问该内部类,但是该内部类可以访问当前代码块的常量以及此外部类的所有成员。
5.3匿名内部类
不说了,直接安排个栗子吧:
/**
* 描述:在doit()方法中将return语句和内部类定义语句合并在一起
* 在return语句中编写返回值为1个匿名内部类
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
public class OuterClass4 {//定义doit()方法
public OutInterface2 doit(){//定义匿名内部类
return new OutInterface2() {
private int i = 0;
public int getValue(){
return i;
}
};
}
}
这种莫名其妙的写法是个什么鬼?,但是,但是,这种写法确实被Java编译器认可,在doit()方法内部首先返回一个OutInterface2的引用,然后在return语句中插入一个定义内部类的代码,由于这个类没有名称,所以这里将该内部类称为匿名内部类。实质上这种内部类的作用就是创建一个实现于OutInterface2接口的匿名类的对象。
匿名内部类的所有实现代码都需要在大括号之间进行编写,语法如下:
return new A() {
//内部类体
};
由于匿名内部类没有名称,所以匿名内部类使用默认构造方法来生成OutInterface2对象。在匿名内部类定义结束后,需要加分号标识,这个分号并不是代表定义额你不累结束的标识,而是代表创建OutInterfadce2引用表达式的标识。
注意:
匿名内部类编译以后会产生以“外部类名$序号”为名称的.class文件,序号以1-n排列,分别代表1-n个匿名内部类。
5.4静态内部类
在内部类前天机修饰符static,这个内部类就变为静态内部类了。一个静态内部类中可以声明static成员,但是在非静态内部类中不可以声明静态成员。静态内部类有一个最大的特点,就是不可以使用外部类的非静态成员,所以静态内部类在程序开发中比较少见。虽然比较少见,还是了解一下下的好,万一呢?
可以这样理解,普通的内部类对象隐式地在外部保存了一个引用,指向创建它的外部类对象,但是如果内部类被定义为static,就会有更多的限制。
静态内部类具有以下两个特点:
- 如果创建静态内部类的对象,不需要其他外部类的对象。
- 不能从静态内部类的对象中访问非静态外部类的对象
举个栗子:
/**
* 描述:定义一个静态内部类StaticInnerClass
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
public class StaticInnerClass {
int num = 100;
static class Inner{
void doitInner(){
// System.out.println("外部类" + num);//调用外部类的成员变量num
}
}
}
在内部类的doitInner()方法中调用成员变量num,由于Inner被修饰为static形式,而成员变量x却是非static类型的,所以在doitInner()方法中不能调用num变量。
在进行程序测试时,如果在每一个Java文件中都设置一个主方法将会出现很多额外的代码,而程序本身并不需要这些主方法,为了解决这个问题,可以将主方法写入静态内部类中。
比如:(修改一下上面的代码块)
/**
* 描述:在静态额你不累中定义主方法
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
public class StaticInnerClass {
int num = 100;
static class Inner{
void doitInner(){
// System.out.println("外部类" + num);//调用外部类的成员变量num
}
public static void main(String[] args) {
System.out.println("Hello");
}
}
}
如果编译这一段代码,将生成一个名称为StaticInnerClass C l a s s Class ClassInner的独立类和一个StaticInnerClass类,只要使用java.StaticInnerClass I n n e r , 就 可 以 运 行 主 方 法 中 的 内 容 , 这 样 当 完 成 测 试 , 需 要 将 所 有 的 . c l a s s 文 件 打 包 时 , 只 要 删 除 S t a t i c I n n e r C l a s s Inner,就可以运行主方法中的内容,这样当完成测试,需要将所有的.class文件打包时,只要删除StaticInnerClass Inner,就可以运行主方法中的内容,这样当完成测试,需要将所有的.class文件打包时,只要删除StaticInnerClassClass$Inner独立类即可。
5.5内部类继承
内部类和其他普通类一样可以被继承,但是继承内部类比继承普通类复杂,需要设置专门的语法来完成
举个栗子:
/**
* 描述:创建OutputInnerClass类,使OutputInnerClass类继
* 承ClassA类的内部类ClassB
*
* @author: yanqi
* CSDN:7旅病娘
* url:sev7lvbingniang.blog.csdn.net
*/
public class OutputInnerClass extends ClassA.ClassB{//继承内部类ClassB
public OutputInnerClass(ClassA a){
a.super();
}
}
class ClassA{
class ClassB{
}
}
当某个类继承内部类时,必须硬性给予这个类一个带参数的构造方法,并且该构造方法的参数为需要继续继承内部类的外部类的引用,同时在构造方法体中使用a.super()语句,这样才能为继承提供了必要的对象引用。
关注我,持续更新!!!