第六章 面向对象(下)
①final修饰的变量、方法和类,系统不允许为final变量重新赋值,子类不允许覆盖父类的final方法,final类不允许派生,不可变类让系统更加安全。
②系统把一个 -128~127 之间的整数自动装箱成Integer实例,放入一个cache的数组缓存起来,然后自动装箱成Integer实例。包装类提供parseXXX(String s)静态方法,除了Character之外所有的包装类都提供了该方法,比如:int k=Integer.parseInt("123");Java 7增强了包装类的功能,都提供了一个静态的compare(xxx val1,xxx val2);这样可以比较两个基本类型值的大小,包括比较两个boolean类的值:相等输出0,前者大输出1,后者大输出-1。例如:int a1=Integer.compare(223, 556);输出-1。
③进制之间的转换:String bb=Integer.toHexString(64);//十进制转为十六进制,输出40
int kkk=Integer.parseInt("9F", 16);//十六进制转为十进制,输出159
int iii=Integer.parseInt("011010101", 2);//二进制转为十进制,输出213
④toString()方法是Object一个特殊的方法,是一个“自我描述”方法,p+"";和p.toString()+"";效果一样。
== 和 equals() ,其中“==”如果两个变量是基本类型变量,且都是数值类型,只要两个变量的值相等,就返回true。但是对于两个引用类型变量,只有它们都指向同一个对象时,== 判断才返回true。例如:
String s1="流放深圳";
String s2="流放";
String s3="深圳";
String s4="流放"+"深圳";
String s5=new String("流放深圳");
System.out.println(s1==s4);//输出true
System.out.println(s1==s5);//输出false
System.out.println(s1==(s2+s3));//输出false
System.out.println(s1.equals(s2+s3));//输出true
⑤final修饰的成员变量必须由程序员显示地指定初始值。final修饰的类变量,要么在定义该类变量时指定初始值,要么在静态初始化块中初始化值。实例变量不能在静态初始化块指定初始化值,因为静态初始化块是静态成员,不可访问实例变量——非静态成员。final局部变量在定义时如果没有赋初值,则可以在后面代码中对该final变量赋初始值,但只能一次,不能重复赋值。
⑥Java会使用常量池来管理曾经使用过的字符串变量,例如执行String a="Java";语句后常量池就缓存了一个字符串Java,如果执行String b="Java";系统将会让b直接指向常量池中的Java字符串,因此a==b返回true。因此有时候把字符串定义成final类型,就能输出true。比如:final String s1="疯狂Java";final String s2="疯狂";final String s3="Java";s1==s2+s3;输出为true。
⑦final修饰的方法不能被重写,如果父类的方法不想被子类重写,考虑使用final做修饰。如果父类中定义了一个private final的方法,其子类也定义一个与该方法有相同名、参数、返回值类型的方法,并不是重写,而是一个新方法。
⑧final类不能被继承。重写Object类的hashCode()和equals()两个方法,就能保证对象是否相等了。
public boolean equals(Object obj){
if(this==obj)
return true;
if(obj!=null && obj.getClass()==Address.class){
Address ad=(Address)obj;
if(this.getDetail().equals(ad.getDetail())&& this.getPostCode().equals(ad.getPostCode()))
return true;
}
return false;
}
public int hashCode(){
return detail.hashCode()+postCode.hashCode()*31;
}
⑨
抽象类:抽象方法和抽象类都必须使用abstract修饰符来定义,
有抽象方法的类必须定义为抽象类,
抽象方法不能有具体的方法体,不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例
。抽象类可以包含成员变量、方法(普通的抽象的方法都可以)、构造器、初始化块、内部类、接口、枚举等。抽象类包含的初始化块和构造器,
不是在创建该类的方法时被调用的,而是在创建其子类的实例时被调用
。抽象类不能创建实例,
只能当做父类被其子类继承。
public abstract class TestAbstract {
private String color;
public abstract double cal();
public abstract String getType();
public TestAbstract() {System.out.println("执行了无参构造器。颜色是:"+color);}
public TestAbstract(String color){
System.out.println("执行父类的有参构造器,获得颜色是:"+color); this.color=color;
}
}
注:抽象类的方法不能使用private做修饰符。
public class Triangle extends TestAbstract{
private double a; private double b; private double c;
public Triangle(String color,double a,double b,double c) {
super(color);//获得传递颜this.setSides(a,b,c);
}
private void setSides(double a, double b, double c) {
if(a>b+c || b>a+b || c>a+b) {
System.out.println("三角形两边之和必须大于第三边!");
return;
}
this.a=a;
this.b=b;
this.c=c;
}
public double cal() {
return a+b+c;
}
public String getType() {
return "三角形";
}
public static void main(String[] args) {
TestAbstract tt=new Triangle("蓝色", 3, 4, 5);
System.out.println(tt.getColor());
System.out.println(tt.getType());
System.out.println("周长是:"+tt.cal());
}
}
输出:执行父类的有参构造器,获得颜色是:蓝色 , 蓝色 , 三角形 , 周长是:12.0
理解:凡是子类继承了父类,直接通过 = 号“向上转型”,把对象直接给父类做引用,而父类的对象只能调用父类的方法,如果该方法被子类覆盖(重写),则是调用被覆盖(重写)后的方法。上述例子super(color);直接调用父类的构造器,把“蓝色”通过子类的构造器,去调用父类的构造器(形参只有一个,即color),并且获得该值color;之后子类通过构造器把a、b、c三个数值通过setSides方法赋值给类的a、b、c属性,再通过重写的两个方法,返回需要的类型值。利用抽象类、方法的优势,可以更好的发挥多态的优势,是的程序更加灵活。当使用abstract修饰类时,表明这个类只能被继承;当使用abstract修饰方法时,表明这个方法必须由子类去实现(即重写)。而final修饰的类和方法不能被修改、不能被重写,因此final和abstract永远不能同时使用。
abstract不能用来修饰变量,也不能修饰构造器。还有,使用static修饰的方法说明属于类本身,通过类即可调用,但如果static和abstract两个同时修饰一个方法,能否行?是不行的,类不能调用一个没有方法体的方法,因此static和abstract不能同时修饰方法,但能同时修饰类。还有,abstract修饰的方法最主要是被其子类重写,如果使用private修饰方法,那子类就无法访问,因此private和abstract不能同时修饰方法。
抽象类的作用:从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类设计的随意性。抽象类中父类只需要定义需要使用的某些方法,把不能实现的部分抽象成抽象方法,留给子类去实现。
接口:接口里不能包含普通方法,接口里定义的方法都是抽象方法、类方法或默认方法(Java 8才提供类方法和默认方法),接口定义某一批类所需要遵守的规范,规定这批类里必须提供某些方法,接口不提供这些方法的实现。比如USB接口,只要任何一个USB接口遵守规范,保证能够使用,就不需要关系是哪个厂家生产的。接口不使用class而是使用interface关键字:[修饰符] interface 接口名 extends 父接口1,父接口2...{},接口只能继承接口,不能继承类。接口里定义的常量只能是静态的,不能包含构造器和初始化块,访问权限都是public。一般来说,系统会自动为成员变量增加 public,static和final修饰符,不管程序员是否使用这三个修饰符显示定义。系统也会为方法增加abstract修饰符,普通方法不能有方法体,但类方法、默认方法必须有方法体。Java 8中定义默认方法:
default void test(String msg){System.out.println(msg);},必须有关键字default。
接口支持多继承,一个接口可以继承多个父接口,多个父接口使用逗号“,”隔开。接口的主要用途:①定义变量,也可用于进行强制类型转换;②调用接口中定义的常量;③被其它类实现。一个类,可以实现多个接口,使用implements关键字,多个接口用逗号隔开,格式如下:extends 父类 implements 接口1,接口2...一个类实现了接口后,必须完全实现这些接口里定义全部抽象方法(也就是重写这些抽象方法),否则该类也必须定义成抽象类。实现了接口后,这个类将获得接口中定义的常量、方法等。实现接口方法时,必须使用public访问控制修饰符,因为接口里的方法都是public的,而子类重写父类方法时,访问权限只能更大或相等,接口里的方法都是用public修饰,因此子类重写接口的方法的修饰符必须是public。
接口和抽象类的对比:①接口和抽象类都不能被实例化,用于被其它类实现和继承;②接口和抽象类都可以包含抽象方法,实现接口或继承抽象类的普通子类都必须实现这些抽象方法。不同之处:③接口体现的是一种规范,是多个程序之间的通信标准,类似整个系统的“总纲”,一个系统的接口不应该经常改变,会牵一发而动全身。④抽象类体现系统的部分功能(那些已经提供实现的方法),但不是最终产品,还需要进一步完善,根据实际子类来完善。
用法和差别:⑤接口里只能有抽象方法和默认方法,不能为普通方法提供方法体;抽象类可以完全包含方法体;⑥接口里不能定义静态方法,常量只能是静态,常量不能定义成普通的(系统会默认添加 public static final修饰),而抽象类可以有静态方法,可以有静态成员和普通成员;⑦接口不包含构造器,不能包含初始化块,而抽象类可以包含构造器,可以包含初始化块,但抽象类的构造器不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作;⑧一个类最多能有一个直接父类,包括抽象类;但一个类可以实现多个接口,用于弥补Java单继承的不足。⑨面向接口编程:很多软件架构都提倡“面向接口”编程,而不是面向实现类编程,面向接口编程能降低程序的耦合度。
内部类:①内部类可以隐藏在外部类之内,不允许同一个包的其它类访问;②内部类成员可以直接访问外部类的私有数据,但外部类不能访问内部类的实现细节,如成员变量;③匿名内部类适用于创建那些仅需要一次使用的类。内部类比外部类可以多使用三个修饰符:private、protected、static——外部类不可以使用这三个修饰符;非静态内部类不能拥有静态成员。定义在类的方法里面的类叫做局部内部类。
内部类与外部类生成的class文件一般是:外部类(OutClass.class),内部类(OutClass$InnerClass.class);
在非静态内部类的方法内访问某个变量,会从该方法、该方法所在内部类、该内部类所在的外部类的顺序依次寻找,先找到则使用,否则提示编译错误:找不到该变量。内部类的成员可以使用this访问,外部类的成员可以通过“外部类名.this.变量名”来访问。非静态内部类的成员可以访问外部类的private成员,反过来则不成立,如果外部类一定要访问非静态内部类的成员,则必须“显示地创建非静态内部类对象来调用访问它的实例成员”。
①根据“静态成员不能访问非静态成员”的规则,外部类的静态方法、静态代码块不能访问非静态内部类。总之,不允许在外部类的静态成员中直接使用非静态内部类。②Java不允许在非静态内部类里定义静态成员。意思是:非静态的内部类,里面的所有属性、方法、代码块等都不能是静态的。
静态内部类:使用static修饰一个内部类,则这个内部类就属于外部类本身,而不属于外部类的某个对象,使用static修饰的类叫做“类内部类”,也叫做静态内部类。其实,static关键字的作用就是把类的成员变量变成类本身,而不是某个实例相关。外部类的上一级程序单元是包,所以不可以使用static修饰。静态内部类可以包含普通成员,根据“静态成员不能访问非静态成员”的规则,静态内部类不能访问外部类的实例变量,只能访问外部类的类成员(静态成员,即使用static修饰的成员)。
外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的成员,或者静态内部类对象作为调用者来访问静态内部类成员。
public class MyStaticClass {
static class StaticInnerClass{
private static int innserStatic=9; private int innerNoStatic=5;
}
public void myTest(){
System.out.println(StaticInnerClass.innserStatic);
System.out.println(new StaticInnerClass().innserStatic);
//System.out.println(StaticInnerClass.innerNoStatic);
}
}
注:最后一个编译有误,因为不能访问静态内部类的非静态成员。
另外,Java规定接口里的内部类,只能是静态内部类,默认使用public static修饰。
省略访问控制符的内部类,只能被与外部类的同一个包中的其它类访问;使用protected修饰的内部类,可以被外部类处于同一个包中和外部类的子类所访问;使用public修饰符内部类,可以在任何地方被访问。在外部类之外的地方访问内部类的方式是:OuterClass.InnerClass varName,就是说,外部类以外的地方使用内部类的完整类名应该是:OuterClass.InnerClass,如果有包名,还要加上包名。创建非静态内部类的对象语法:OuterInstance.new InnerConstructor()。
public class OuterClass {
public OuterClass(){
System.out.println("父类构造器");
}
class InnerClass{
public InnerClass(String msg){System.out.println(msg);
}
}
}
public static void main(String[] args) {
OuterClass.InnerClass inn1=new OuterClass().new InnerClass("哈哈");
//等同于下面的三句
OuterClass.InnerClass inner2;
OuterClass outt=new OuterClass();
inner2=outt.new InnerClass("嘛嘛哒");
}
注:非静态内部类的构造器必须使用外部类的对象来调用。子类构造器总会调用父类的构造器。
继承内部类:
public class SuperClass extends OuterClass.InnerClass{
public SuperClass(OuterClass outerClass, String msg) {outerClass.super(msg);}}
注:还有显式的提供内部类的构造器。
②在外部类以外使用静态内部类:因为静态内部类是外部类类相关的,因此创建静态内部类对象时无需创建外部类对象,语法如下:new OuterClass.InnerConstructor()
public class StaticOut {
static class StaticIn{public StaticIn(){System.out.println("静态内部类构造器!");}}}
调用:public static void main(String[] args) {
StaticOut.StaticIn staticIn=new StaticOut.StaticIn();
//上面代码可以改成下面两句
StaticOut.StaticIn staticIn2;
staticIn2=new StaticOut.StaticIn();}
相比之下,使用静态内部类比使用非静态内部类要简单得多,因此使用内部类时,应优先考虑使用静态内部类。
③局部内部类:把一个类放在方法里定义,这个类就是局部内部类,局部内部类仅在方法内有效。局部内部类不能使用访问控制符和static修饰符做修饰。在开发中很少用到局部内部类。
④对象与垃圾回收:当程序创建对象、数组等引用类型实体时,系统都会在堆内存中为之分配一块内存区,对象保存在这块内存区中,当这块内存不再被任何引用变量引用时,这块内存就变成垃圾,等待垃圾回收机制进行回收。有如下特点:☞垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源)☞程序无法控制垃圾回收何时进行;☞垃圾回收机制回收任何对象之前,总会先调用它的finalize方法,该方法可能使对象重新复活,从而导致回收取消。对象在内存中有3个状态:可达状态、可恢复状态、不可达状态。垃圾回收就是在对象不再有任何引用变量引用它,调用它的finalize方法,如果有引用变量重新引用,则回到可达状态,否则进入不可达状态,系统才真正回收该对象所占有的资源。
强制系统垃圾回收有2中方式:①调用System类的gc()静态方法:System.gc()。②调用Runtime对象的gc实例方法:Runtime.getRuntime().gc()。