java基础-类的高级属性(包、final、内部类)
女神镇楼
类除了具有普通的特性之外,还有一些高级特性,比如包、内部类等。包在整个管理过程中起到非常积极的作用,使用包可以有效的管理繁杂的类文件,解决类重名的问题,当在类中配合包与权限修饰符使用时,可以控制其他人对类成员的访问。在java中一个更为有效的实现隐藏实现细节的技巧是使用内部类,通过使用内部类机制可以向上转型为被内部类实现的公共接口。由于在类中可以定义多个内部类,实现接口的方式也不止一个,只要将内部类中的方法设置为类最小范围的修饰符权限,就可以将内部类的实现细节有效的隐藏。
1、完整的类路径
在程序中使用两个不同的Date类的完整的类路径,可以使用如下代码:
java.util.Date date = new java.util.Date();
java.sql.Date date2 = new java.sql.Date(123);
在java中采用类包机制非常重要,类包不仅可以解决类名冲突的问题,还可以在开发庞大的应用程序时,帮助开发人员管理庞大的应用程序组件,方便软件复用。
在同一个包中,类相互访问时,可以不指定包名。
同一个包中的类,不必放在同一个位置,只要将CLASSPATH指向两个位置即可。
2、创建包
在IntelliJ IDEA中创建包:
在类的定义中,包名用法如下:
package 包名
在类中指定包名时,需要将package表达式放在程序的第一行,必须是文件的非注释代码第一行,使用package关键字为类指定包名后,包名会成为类名中的一部分,预示着这个类不许指定全名。例如,在使用位于com.lzw下的Dog.java类时,需要使用形如com.lzw.Dog的表达式。
3、导入包
(1)使用import关键字导入包
语法如下:
import com.lzw.*;
import com.lzw.Math;
使用import关键字时,可以指定类的完整描述,如果为了使用包中更多的类,可以在使用import关键字指定时在包的指定后加上*,表示可以在程序中使用包中的所有类。
4、使用import导入静态成员
语法如下:
import static 静态成员
代码示例:
package com.lzw; import static java.lang.Math.max; // 导入静态成员方法 import static java.lang.System.out; //导入静态成员变量 public class ImportTest { public static void main(String[] args) { // 主方法中可以直接使用这些静态成员 out.println("1和4之间较大的值是:"+max(1,4)); } }
使用import static 导入了java.lang.Math类中的静态方法max()和java.lang.System类中的out成员变量,就可以在程序中直接引用这些静态成员。
5、final变量
final关键字可用于变量声明,一旦该变量被设定,就不可以再改变该变量的值,通常,final定义的变量为常量,例如:
final double PI = 3.14;
final关键字定义的变量在声明的时候,必须进行赋值操作,final除了可以修饰基本数据类型的常量,还可以修饰对象的引用。由于数组也可以看做是对象的引用,所以final可以修饰数组。一旦一个对象的引用被final修饰后,他只能恒定的指向一个对象,无法改变指向另一个对象。
代码示例:
package com.lzw; import static java.lang.System.out; import java.util.Random; 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 final Test test = new Test();//声明一个final引用 private Test test2 = new Test(); // 声明一个不是final的引用 private final int[] a = {1,2,3,4,5,6}; // 声明一个定义为final的数组 private final int i4 = rand.nextInt(20); private static final int i5 = rand.nextInt(20); public String toString(){ return i4 + " " + i5 + " "; } }
实例中,定义为final的常量定义时,需要使用大写字母命名,并且中间使用下划线链接,这是java的编码规则。同时,定义为final的数据,无论是常量、引用还是数组,在主函数中都不能被改变。
一个被定义为final的对象引用只能指向唯一一个对象,不可能将它再指向其他对象,但是一个对象本身的值却可以改变,那么使一个常量真正做到不可更改,可以将常量声明为static final 。
6、final方法
定义的final方法不能被重写,方法定义为final类型,可以防止子类修改该类的定义和实现方式,同时定义为final的方法执行效率要高于非final的方法。在权限修饰符中,private修饰符,如果一个父类的某个方法被定义为private修饰符,子类将无法访问这个方法,自然也就无法覆盖该方法,所以定义一个private方法隐式的被指定为final类型。
但是在父类中被定义的private final类型似乎可以被子类覆盖
代码示例:
class Parent { 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 Parent { public final void doit(){ System.out.println("子类中的都doit方法"); } /* final void doit2(){ // final方法不能被覆盖 System.out.println("子类企图覆盖doit2方法,但是编辑器不允许"); }*/ public void doit3(){ System.out.println("子类的doit3方法"); } }
public class FInalMrthod { public static void main(String[] args) { Sub s = new Sub(); // 实例化子类 s.doit();// 调用doit()方法 Parent p = s; // 执行向上转型操作 //p.doit(); //会报错,不能执行private方法 p.doit2(); p.doit3(); } }
示例分析:
final方法不能被覆盖,doit2()方法不能再子类中被重写,但是在父类中定义了一个private final 的doit()方法,在子类中也定义了一个doit()方法,表面上看子类的doit()方法覆盖了父类的doit()方法,但是覆盖必须满足一个对象向上转型为他的基本类型,并调用相同方法这样一个条件。在主方法中"Parent P = s"是向上转型的操作,对象p只能调用正常覆盖的doit3方法,却不能调用doit()方法,可见子类中的doit()不是正常的覆盖,而是生成了一个新的方法。
7、final类
final类不能被继承,如果希望一个类不允许被任何类继承,不允许其他人对这个类进行任何修改,这个类可以设置为final形式。
语法如下:
final 类名{}
如果类设置为final形式,则类中所有的方法都被隐式的设置为final形式,但是final中的成员变量可以设置为final或者非final形式。
代码示例:
final class FianlClass { int a = 3; void doit(){ } public static void main(String[] args) { FinalClass f = new FinalClass(); f.a++; System.out.println(f.a); } }
8、成员内部类
(1)简介
语法:
public class OuterClass { //外部类 private class innerClass{ //内部类 } }
内部类可以随意使用外部类定义的成员变量和成员方法,即使类成员被定义为private方法。内部类的实例一定要绑定外部类的实例上,如果外部类中初始化一个内部类对象,那么内部类对象就会绑定在外部类对象上。
// >> TODO 成员内部类,是在类中直接定义类 // >> TODO 成员内部类,不可以包含任何静态的成分,比如静态方法,静态变量,静态内部类。否则会造成内外部类初始化问题。 // >> TODO 成员内部类,可以有访问控制符。成员内部类和成员方法,成员变量一样,都是类的组成部分 public class CPU { // >> TODO 可以有final static的基本数据类型变量 final static int abc = 999; private String producer; public CPU(String producer) { this.producer = producer; } public double getSpeed() { // >> TODO 成员内部类,代码和这个类本身的访问权限一样,可以访问外部(Phone)的private属性 // >> TODO 成员内部类中有一个外部类的引用,其访问外部类的对象的成员属性就是使用这个引用,完整写法是:类名.this.属性/方法 return Phone.this.speed; } public String getProducer() { return producer; } public void setProducer(String producer) { this.producer = producer; } @Override public String toString() { return "CPU{" + "speed=" + getSpeed() + ", producer='" + producer + '\'' + '}'; } // >> TODO 成员内部类,里面可以有任意合法的类的组成部分,包括成员内部类,但是不可以有静态内部类 // public class ABC{ // public void test(){ // // } // } }
代码示例:
public class OuterClass { //外部类 innerClass in = new innerClass(); // 在外部类实例化内部类对象引用 public void ouf(){ in.inf(); // 在外部类方法中调用内部类方法 } private class innerClass{ //内部类 innerClass(){ // 内部类的构造方法 } public void inf(){ // 内部类的成员方法 } int y = 0; //定义内部类成员变量 } public innerClass doit(){ //外部类方法,返回值为内部类引用 in.y = 4; return new innerClass(); // 返回内部类引用 } public static void main(String[] args) { OuterClass out = new OuterClass(); // 内部类对象实例化操作必须在外部类或者外部类的非静态方法中实现 OuterClass.innerClass in = out.doit(); OuterClass.innerClass in2 = out.new innerClass(); } }
示例分析:
外部类创建内部类实例,与其他类创建对象引用时相同。内部类可以访问他的外部类成员,但是内部类成员只能在内部类范围内是可知的,不能呗被外部类使用。在实例化内部类对象时,不能再new操作符之前使用外部类名称实例化内部类对象,而是应该使用外部类对象来创建其内部类对象。
(2)内部类向上转型为接口
如果讲一个权限修饰符为private的内部类向上转型为其父类对象,或者直接向上转型为一个接口,在程序中就可以完全隐藏内部类的实现过程。可以在外部提供一个接口,在接口中声明一个方法。如果在实现该接口的内部类中实现了给接口的方法,就可以定义多个内部类以不同的方式实现接口中的同一个方法,而一般的类中是不能多次实现接口中同一个方法的,这个技巧经常被应用在Swing编程中,可以在一个类中做出多个不同的响应事件。
代码示例:
package com.lzw; public interface OutInterFace { public void f(); }
import com.lzw.OutInterFace; public class InterfaceInner { public static void main(String[] args) { OutClass2 out = new OutClass2(); // 实例化一个OutClass2对象 // 调用doit方法,返回一个OutInterface接口 OutInterFace outInte = out.doit(); outInte.f(); } }
import com.lzw.OutInterFace; public class OutClass2 { // 定义一个内部类实现OutInterFace接口 private class InnerClass implements OutInterFace { InnerClass(String s){ // 内部类构造方法 System.out.println(s); } @Override public void f() { // 实现接口中的f()方法 System.out.println("访问内部类中的f()方法"); } } public OutInterFace doit(){ //定义一个方法,返回值类型为OutinterFace接口 return new InnerClass("访问内部类构造方法"); } }
示例分析:
OutClass2类中定义了一个权限修饰符为private的内部类,这个内部类实现了OutInterFace接口,然后修改了doit()方法,使得该方法返回一个OutInterFace接口。由于内部类InterClass的修饰符为private,所以只能被OutClass2的类访问,其他的类可以访问doit()方法。由于该方法返回的是一个外部接口类型,这个接口可以作为外部使用的接口。他包含一个f()方法,在继承此接口的内部类中实现了此方法,如果某个类继承了外部类,由于内部的权限不可以向下转型为内部类InterClass,同时不能方位f()方法,但是可以访问接口中的f()方法。例如InterFaceInner类中的最后一条语句,接口引用调用f()方法,从执行结果可以看出,这条语句执行的是内部类中的f()方法,很好的对继承该类的子类隐藏了实现细节,仅为编写子类的人留下了一个接口和一个外部类,同时也可以调用f()方法,但f()方法的实现过程却很好的隐藏了,这就是内部类最基本的用途。
(3)使用this关键字获取内部类与外部类的引
如果外部类定义的成员变量与内部类中的成员变量名称相同,可以使用this关键字
代码示例:
public class TheSameName { private int x ; private class Inner{ private int x = 8; public void doit(int x){ x++; //调用的是形参 x this.x++; //调用的是内部类的变量x TheSameName.this.x++; //调用的是外部类的变量x } } }
示例分析:
在内部类中使用this,x可以调用内部类的成员变量x,使用ThsSameName.this.x语句可以调用外部类成员变量x,即:
外部类名称.this.变量名 =====可获取外部类引用
9、局部内部类
内部类不仅可以在类中进行定义,也可以在类的局部位置定义,如在类的方法或者任意作用域中都可以定义内部类。
// >> TODO 接口也可以定义为静态内部接口,但是一般不这么做。接口的目的是为了让更多人实现,所以一般会是单独一个文件作为公共接口 public static interface UnitSpec { public double getNumSpec(); public String getProducer(); } public Phone( String name, String id, int count, double soldPrice, double purchasePrice, double screenSize, double cpuHZ, int memoryG, int storageG, String brand, String os ) { double localCPUHZ = cpuHZ; // localCPUHZ = Math.random(); // >> TODO 局部内部类,是在类中直接定义类 // >> TODO 局部内部类,不可以包含任何静态的成分,比如静态方法,静态变量,静态内部类。否则会造成内外部类初始化问题。 // >> TODO 局部内部类,不可以有访问控制符。局部内部类和成员方法,成员变量一样,都是类的组成部分 class CPU implements UnitSpec { // >> TODO 可以有final static的基本数据类型变量 final static int abc = 999; private String producer; public CPU(String producer) { this.producer = producer; } public double getNumSpec() { // >> TODO 局部内部类,代码和这个类本身的访问权限一样,可以访问外部(Phone)的private属性 // >> TODO 局部内部类中有一个外部类的引用 // >> TODO 局部内部类访问外部类的对象的成员属性的完整写法如下,类名.this.属性/方法 // >> TODO 以上都和成员内部类一样。除此之外,局部内部类还可以访问参数和局部变量,但是它俩必须是实际final的 // 仅做访问数据的演示,没有实际意义 return Math.max(Phone.this.speed, Math.max(cpuHZ, localCPUHZ)); } public String getProducer() { return producer; } public void setProducer(String producer) { this.producer = producer; } @Override public String toString() { return "CPU{" + "speed=" + getNumSpec() + ", producer='" + producer + '\'' + '}'; } // >> TODO 局部内部类,就好像局部变量一样,方法内部的东西出了代码就不可被访问, // >> TODO 所以可以再定义类,但是不能有访问控制符,也不能是static,就好像成员变量没有访问控制符没有static一样 // >> TODO 但是可以有final。记忆要点:和局部变量一样 // final class ABC { // public void test() { // // } // } } class Memory implements UnitSpec { private String producer; public Memory(String producer) { this.producer = producer; } @Override public double getNumSpec() { return memoryG; } public String getProducer() { return producer; } public String toString() { return "Memory{" + "storage=" + getNumSpec() + ", producer='" + producer + '\'' + '}'; } } this.screenSize = screenSize; // >> TODO 可以像平常的类一样使用局部内部类 this.speed = cpuHZ; this.cpu = new CPU("Default"); this.memoryG = new Memory("Default"); this.storageG = storageG; this.brand = brand; this.os = os; this.setName(name); this.setId(id); this.setCount(count); this.setSoldPrice(soldPrice); this.setPurchasePrice(purchasePrice); }
代码示例:
package com.lzw; public interface OutInterFace2 { }
import com.lzw.OutInterFace2; public class OutClass3 { public OutInterFace2 doit(final String x){ // doit()方法的参数为final类型 //在doit()方法中定义一个内部类 class InnerClass2 implements OutInterFace2{ InnerClass2(String s ){ s = x; System.out.println(s); } } return new InnerClass2("doit"); } }
示例分析:
内部类定义在了方法doit()内部,内部类InterClass2是doit()方法的一部分,并非是OutClass3类的一部分,所以在doit()方法的外部不能访问该内部类,但是内部类可以访问该代码块的常量以及此外部类的所有成员。
doit()方法的参数设置为final类型,如果需要在方法体中使用局部变量,该局部变量需要设置为final类型,在方法中定义的内部类只能访问方法中final类型的局部变量,这是因为方法中定义的局部变量相当于一个常量,他的生命周期超出了方法运行的生命周期,由于该局部变量被设置为final,所以不能再内部类中改变局部变量的值。
10、匿名内部类
匿名类的所有代码需要在大括号之间编写,语法如下:
return new A(){ //内部类体 };
代码示例:
import com.lzw.OutInterFace2; public class OuterClass4 { public OutInterFace2 doit(){//定义doit()方法 return new OutInterFace2() { private int i = 0; public int getValue(){ return i; } } } }
示例分析:
在doit()方法内部首先返回一个OutInterFace2的引用,然后在return语句中插入一个定义内部类的代码,由于这个类没有名称,所以将该内部类成为匿名内部类。实际上这种内部类的作用就是创建一个实现于OutInterFace2接口的匿名类的对象。
11、静态内部类
在内部类前添加static修饰符,这个内部类就变成静态内部类了。一个静态内部类中可以声明static成员,但是非静态内部类中不可以声明静态成员。静态内部类一个最大的特点:就是不可以使用外部类的非静态成员,所以静态内部类在程序开发中比较少见。
// >> TODO 静态内部类,是在类中使用static修饰的类 // >> TODO 静态内部类,可以有访问控制符。静态内部类和静态方法,静态变量一样,都是类的静态组成部分 // >> TODO 静态内部类也是类,在继承,实现接口方面,都是一样的。以后我们讲的类,不特殊说明,在这方面都是一样的 public static class CPU { private double speed; private String producer; public CPU(double speed, String producer) { this.speed = speed; this.producer = producer; } public double getSpeed() { // >> TODO 静态内部类,代码和这个类本身的访问权限一样,可以访问外部(Phone)的private属性 // >> TODO 注意,这并不少说它可以访问private变量, // >> TODO 静态内部类是静态的,就好像静态方法一样,没有this自引用,可以通过引用访问Phone对象的private属性 // 仅作演示访问性,不具有实际意义 Phone phone = null; phone.memoryG = 99; return speed; } public void setSpeed(double speed) { this.speed = speed; } public String getProducer() { return producer; } public void setProducer(String producer) { this.producer = producer; } @Override public String toString() { return "CPU{" + "speed=" + speed + ", producer='" + producer + '\'' + '}'; } // >> TODO 静态内部类,里面可以有任意合法的类的组成部分,包括静态内部类 // public static class ABC{ // // } } public void accessStaticClass(){ // >> TODO 同样,外部类也可以访问静态内部类(CPU)的private属性 // 仅作演示访问性,不具有实际意义 this.cpu.producer = ""; }
12、内部类的继承
内部类和其他普通类一样可以被继承,但继承内部类比继承普通类要复杂,需要设置专门的语法完成
public class OutputInnerClass extends ClassA.ClassB { public OutputInnerClass(ClassA a){ a.super(); } } class ClassA{ class ClassB{} }
在某个类继承内部类时,必须硬性的给予这个类一个带参数的构造方法,并且该构造方法的参数为需要继承内部类的外部类引用,同时在构造方法体中使用s.super()语句,这样才能为继承提供了必要的对象引用。