1增强的包装类
1JDK1.5以后提供了自动装箱和自动拆箱功能,所谓自动装箱,就是可以把一个基本类型变量直接赋给对应的包装类变量,或者赋给Object变量,自动拆箱则与之相反,允许直接把包装类对象直接赋给一个对应的基本类型变量。
2同样两个int类型的数值包装成Integer实例后,如果两个数其中一个数范围时-128——127之间,则这两个包装后的实例对象不是同一个对象。该Integer用了缓存设计来存储-128——128这些数值对象。
代码例子:
public class WrapperClassCompare { public static void main(String[] args) { Integer a = new Integer(6); //输出true System.out.println("6的包装类实例是否大于5.0" + (a > 5.0)); //输出false System.out.println("比较2个包装类的实例是否相等:" + (new Integer(2) == new Integer(2))); //通过自动装箱,允许把基本类型值赋值给包装类的实例 Integer ina = 2; Integer inb = 2; //输出true System.out.println("两个2自动装箱后是否相等:" + (ina == inb)); Integer biga = 128; Integer bigb = 128; //输出false System.out.println("两个128自动装箱后是否相等:" + (biga == bigb)); } }
1当使用System.out.println方法输出或当java对象和字符串进行连接运算符时,系统都会自动调用Java对象toString方法的返回值。
代码例子:
class Person { private String name; public Person(String name) { this.name = name; } public void info() { System.out.println("此人名为:" + name); } } public class PrintObject { public static void main(String[] args) { //创建一个Person对象,将之赋给p变量 Person p = new Person("孙悟空"); //打印p所引用的Person对象,其实相当于System.out.println(p.toString()) System.out.println(p); //其实相当于String pStr=p.toString()+""; String pStr=p+""; } }2当使用==来判断两个变量是否相等时,对于基本类型变量,且都是数值类型(不一定要求数据类型严格相同),则只要两个变量的值相等,就将返回true.而对于两个引用类型变量,它们必须指向同一个对象时,==判断才会返回true,==不可用于比较类型上没有父子关系的两个对象。
3当Java程序直接使用形如“hello”的字符串直接量(包括可以在编译时就计算出来的字符串值)时,JVM将会使用常量池来管理这些字符串,当使用new String("hello")时,JVM会先使用常量池来管理"hello"直接量,再调用String类的构造器来创建一个新的String对象,新创建的String对象被保存在堆内存中。换句话说,new String("hello")一共产生了两个对象(常量池没有"hello"这个字符串的前提下)。
代码例子:
public class EqualTest { public static void main(String[] args) { int it = 65; float fl = 65.0f; //将输出true System.out.println("65和65.0f是否相等?" + (it == fl)); char ch = 'A'; //将输出true System.out.println("65和'A'是否相等?" + (it == ch)); String str1 = new String("hello"); String str2 = new String("hello"); //将输出false System.out.println("str1和str2是否相等?" + (str1 == str2)); //将输出true System.out.println("str1是否equals str2?" + (str1.equals(str2))); //由于java.lang.String与EqualTest类没有继承关系, //所以下面语句导致编译错误 System.out.println("hello" == new EqualTest()); } }4如果要比较两个字符串的内容是否相同,可以调用String的equals()方法(Object类提供的equals方法比较两个对象相等与==运算符一样,这里因为String已经重写了Object的equals方法,所以可以根据两个对象的字符内容来判断是否相等)
5如果有一个类名是A,对象为obj,obj.getClass()==Person.class和obj instanceof A两者的区别:前者是必须同一个类才返回true,而后者是只要obj是A的实例或子类的实例都会返回true。
3类成员
1访问静态的Field(即类成员),也可以通过实例为null来访问。
代码例子:
public class NullAccessStatic { private static void test() { System.out.println("static修饰的类方法"); } public static void main(String[] args) { //定义一个NullAccessStatic变量,其值为null NullAccessStatic nas = null; //null对象调用所属类的静态方法 nas.test(); } }2单例类(单例设计模式),即允许一个类始终创建一个实例,该类的要求构造器使用private修饰,可以通过类成员变量对象来保存已经创建的对象。
代码例子:
class Singleton { //使用一个变量来缓存曾经创建的实例 private static Singleton instance; //将构造器使用private修饰,隐藏该构造器 private Singleton(){} //提供一个静态方法,用于返回Singleton实例 //该方法可以加入自定义的控制,保证只产生一个Singleton对象 public static Singleton getInstance() { //如果instance为null,表明还不曾创建Singleton对象 //如果instance不为null,则表明已经创建了Singleton对象, //将不会重新创建新的实例 if (instance == null) { //创建一个Singleton对象,并将其缓存起来 instance = new Singleton(); } return instance; } } public class SingletonTest { public static void main(String[] args) { //创建Singleton对象不能通过构造器, //只能通过getInstance方法来得到实例 Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); //将输出true System.out.println(s1 == s2); } }4final修饰符
1类Field:必须在静态初始化快中或声明该Field时指定初始值,实例Field:必须在非静态初始化快,声明该Field或构造器中指定初始值,与普通成员变量不同的是,final成员变量(包括实例Field和类Field)必须由程序员显示初始化,系统不会对final成员进行隐式初始化,final一旦有了初始值,就不能被重新赋值。
代码例子:
public class FinalVariableTest { //定义成员变量时指定默认值,合法。 final int a = 6; //下面变量将在构造器或初始化块中分配初始值 final String str; final int c; final static double d; //既没有指定默认值,又没有在初始化块、构造器中指定初始值, //下面定义char Field是不合法的。 //final char ch; //初始化块,可对没有指定默认值的实例Field指定初始值 { //在初始化块中为实例Field指定初始值,合法 str = "Hello"; //定义a Field时已经指定了默认值, //不能为a重新赋值下面赋值语句非法 //a = 9; } //静态初始化块,可对没有指定默认值的类Field指定初始值 static { //在静态初始化块中为类Field指定初始值,合法 d = 5.6; } //构造器,可对既没有指定默认值、有没有在初始化块中 //指定初始值的实例Field指定初始值 public FinalVariableTest() { //如果初始化块中对str指定了初始化值, //构造器中不能对final变量重新赋值,下面赋值语句非法 //str = "java"; c = 5; } public void changeFinal() { //普通方法不能为final修饰的成员变量赋值 //d = 1.2; //不能在普通方法中为final成员变量指定初始值 //ch = 'a'; } public static void main(String[] args) { FinalVariableTest ft = new FinalVariableTest(); System.out.println(ft.a); System.out.println(ft.c); System.out.println(ft.d); } }2如果打算在构造器,初始化中对final成员变量进行初始化,则不要在初始化之前就访问成员变量的值。
代码例子:
public class FinalErrorTest { //定义一个final修饰的Field, //系统不会对final成员Field进行默认初始化 final int age; { //age没有初始化,所以此处代码将引起错误。 System.out.println(age); age = 6; System.out.println(age); } public static void main(String[] args) { new FinalErrorTest(); } }3对于引用类型变量而言,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。
代码例子:
class Person { private int age; public Person(){} //有参数构造器 public Person(int age) { this.age = age; } //省略age Field的setter和getter方法 //age Field的setter和getter方法 public void setAge(int age) { this.age = age; } public int getAge() { return this.age; } } public class FinalReferenceTest { public static void main(String[] args) { //final修饰数组变量,iArr是一个引用变量 final int[] iArr = {5, 6, 12, 9}; System.out.println(Arrays.toString(iArr)); //对数组元素进行排序,合法 Arrays.sort(iArr); System.out.println(Arrays.toString(iArr)); //对数组元素赋值,合法 iArr[2] = -8; System.out.println(Arrays.toString(iArr)); //下面语句对iArr重新赋值,非法 //iArr = null; //final修饰Person变量,p是一个引用变量 final Person p = new Person(45); //改变Person对象的age Field,合法 p.setAge(23); System.out.println(p.getAge()); //下面语句对p重新赋值,非法 //p = null; } }4"宏替换"即是宏变量(编译器会把程序中所有用到该变量的地方直接替换成该变量的值):使用final修饰符修饰;在定义该final变量时指定了初始值;该初始值可以在编译时被确定下来。
代码例子:
public class FinalReplaceTest { public static void main(String[] args) { //下面定义了4个final“宏变量” final int a = 5 + 2; final double b = 1.2 / 3; final String str = "疯狂" + "Java"; final String book = "疯狂Java讲义:" + 99.0; //下面的book2变量的值因为调用了方法,所以无法在编译时被确定下来 final String book2 = "疯狂Java讲义:" + String.valueOf(99.0); //① System.out.println(book == "疯狂Java讲义:99.0"); System.out.println(book2 == "疯狂Java讲义:99.0"); } }
public class StringJoinTest { public static void main(String[] args) { String s1 = "疯狂Java"; //s2变量引用的字符串可以编译时就确定出来, //因此引用常量池中已有的"疯狂Java"字符串 String s2 = "疯狂" + "Java"; System.out.println(s1 == s2);// true //定义2个字符串直接量 String str1 = "疯狂"; //① String str2 = "Java"; //② //将str1和str2进行连接运算 String s3 = str1 + str2; System.out.println(s1 == s3); //false } }对于s3而言,它的值由str1和str2进行连接运算后得到。由于str1,str2只是两个普通变量,编译器不会执行" 宏替换",因此编译器无法在编译时确定s3的值,也就无法让s3指向字符串池中缓存的"疯狂Java".如果str1和str2加个final修饰,让其在编译时确定下来,那么s1==s3就会输出true了。
5final修饰的方法不可被重写(即不希望子类重写父类的某个方法),不过当使用final修饰一个private访问权限的方法,依然可以在其子类中定义与该方法具有相同方法名,相同形参列表,相同返回值类型的方法(其实相当于这个方法是子类定义的一个方法)。
代码例子:
public class PrivateFinalMethodTest { private final void test(){} } class Sub extends PrivateFinalMethodTest { //下面方法定义将不会出现问题 public void test(){} }6final修饰的类不可以有子类,即不能被继承。
7不可变类的大致意思:创建该类的实例后,该实例的Field是不可改变的,比如Java提供的8个包装类和String类都是不可变类。
使用private和final修饰符来修饰该类的Field。
提供带参数构造器,来初始化类里的Field。
仅为该类的Field提供getter方法,不要为该类Field提供setter方法。
如果有必要,重写Object类的hashCode和equals方法,equals方法以关键Field来作为判断两个对象是否相等的标准。
代码例子:
//没用包含引用变量属性的不可变类 public class Address { private final String detail; private final String postCode; //在构造器里初始化两个实例Field public Address() { this.detail = ""; this.postCode = ""; } public Address(String detail , String postCode) { this.detail = detail; this.postCode = postCode; } //仅为两个实例Field提供getter方法 public String getDetail() { return this.detail; } public String getPostCode() { return this.postCode; } //重写equals方法,判断两个对象是否相等。 public boolean equals(Object obj) { if (this == obj) { return true; } if(obj != null && obj.getClass() == Address.class) { Address ad = (Address)obj; // 当detail和postCode相等时,可认为两个Address对象相等。 if (this.getDetail().equals(ad.getDetail()) && this.getPostCode().equals(ad.getPostCode())) { return true; } } return false; } public int hashCode() { return detail.hashCode() + postCode.hashCode() * 31; } } class Name { private String firstName; private String lastName; public Name(){} public Name(String firstName , String lastName) { this.firstName = firstName; this.lastName = lastName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getFirstName() { return this.firstName; } public void setLastName(String lastName) { this.lastName = lastName; } public String getLastName() { return this.lastName; } } //这个不是真正的不可变类,因为它里面的引用属性值可以被改变 public class Person { private final Name name; public Person(Name name) { this.name = name; } public Name getName() { return name; } public static void main(String[] args) { Name n = new Name("悟空", "孙"); Person p = new Person(n); // Person对象的name的firstName值为"悟空" System.out.println(p.getName().getFirstName()); // 改变Person对象name的firstName值 n.setFirstName("八戒"); // Person对象的name的firstName值被改为"八戒" System.out.println(p.getName().getFirstName()); } } //修改Person的代码,让它成为一个真正的不可变类 public class Person { private final Name name; public Person(Name name) { this.name = new Name(name.getFirstName(),name.getLastName()); } public Name getName() { return new Name(name.getFirstName(),name.getLastName()); } public static void main(String[] args) { Name n = new Name("悟空", "孙"); Person p = new Person(n); // Person对象的name的firstName值为"悟空" System.out.println(p.getName().getFirstName()); // 改变Person对象name的firstName值 n.setFirstName("八戒"); // Person对象的name的firstName还是"悟空" System.out.println(p.getName().getFirstName()); } }5抽象类
1abstract不能用于修饰Field,不能用于修饰局部变量,即没有抽象变量,没有抽象Field等说法,也不能修饰构造器。
2final和abstract不能同时使用,static和abstract不能同时修饰某个方法,即没有所谓的类抽象。abstract方法也不能定义为private访问权限,即private和abstract不能同时使用。
6接口
1一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
2由于接口定义的是一种规范,因此接口里不能包含构造器和初始化快定义。接口里可以包含Field(只能是常量) ,方法( 只能是抽象实例方法),内部类(包括内部接口,枚举)定义。
3在接口中定义Field时,不管是否使用public static final修饰符,接口里的Field总将会使用这三个修饰符来修饰,而接口方法总是使用public abstract 来修饰,不能使用static修饰接口里定义的方法,内部类和枚举类都是public权限(默认也是public)。
4接口的继承和类继承不一样,接口完全支持多继承,即一个接口可以有多个直接父接口,和类继承相似,子接口扩展某个父接口,将会获得父接口里定义的所有抽象方法,常量Field,内部类和枚举定义。
代码例子:
interface interfaceA { int PROP_A = 5; void testA(); } interface interfaceB { int PROP_B = 6; void testB(); } interface interfaceC extends interfaceA, interfaceB { int PROP_C = 7; void testC(); } public class InterfaceExtendsTest { public static void main(String[] args) { System.out.println(interfaceC.PROP_A); System.out.println(interfaceC.PROP_B); System.out.println(interfaceC.PROP_C); } }5接口和抽象的区别
接口里只能包含抽象方法,不包含已经提供实现的方法,抽象类则完全可以包含普通方法。
接口里不能定义静态方法,抽象类里可以定义静态方法。
接口里只能定义静态常量Field,不能定义普通Field,抽象类里则既可以定义普通Field,也可以定义静态常量Field。
接口里不包含构造器,抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
接口里不能包含初始化块,但抽象类则完全可以包含初始化块。
一个类最多只有一个直接父类,包括抽象类,但一个类可以直接实现多个接口。
6内部类
1内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员,同一个类的成员之间可以互相访问,但外部类不能访问内部类的实现细节,例如内部类的成员变量。
2非静态内部类的成员可以访问外部类的private成员,但反过来就不成立了,非静态内部类的成员只在非静态内部类范围内是可知的,并不能被外部类直接使用。如果外部类需要访问非静态内部类的成员,则必须显示创建非静态内部类对象来调用访问其实例成员。(由于非静态内部类对象必须寄存于在外部类对象里,而外部类对象则不必一定有非静态内部类对象寄存其中。)
代码例子:
public class Cow { private double weight; //外部类的两个重载的构造器 public Cow(){} public Cow(double weight) { this.weight = weight; } //定义一个非静态内部类 private class CowLeg { //非静态内部类的两个Field private double length; private String color; //非静态内部类的两个重载的构造器 public CowLeg(){} public CowLeg(double length , String color) { this.length = length; this.color = color; } public void setLength(double length) { this.length = length; } public double getLength() { return this.length; } public void setColor(String color) { this.color = color; } public String getColor() { return this.color; } //非静态内部类的实例方法 public void info() { System.out.println("当前牛腿颜色是:" + color + ", 高:" + length); //直接访问外部类的private修饰的Field System.out.println("本牛腿所在奶牛重:" + weight); //① } } public void test() { CowLeg cl = new CowLeg(1.12 , "黑白相间"); cl.info(); } public static void main(String[] args) { Cow cow = new Cow(378.9); cow.test(); } }其储存图示:
3如果外部类成员变量,内部类成员变量与内部类里方法的局部变量同名,则可通过使用this,外部类类名.this作为限定来区分。
4外部类的静态方法,静态代码不能访问非静态内部类,包括不能使用非静态内部类定义变量,创建实例等;非静态内部类里不能有静态方法,静态Field,静态初始化。
代码例子:
public class StaticTest { //定义一个非静态的内部类,是一个空类 private class In{} //外部类的静态方法 public static void main(String[] args) { //下面代码引发编译异常,因为静态成员(main方法) //无法访问非静态成员(In类) new In(); } } public class InnerNoStatic { private class InnerClass { /* 下面三个静态声明都将引发如下编译错误: 非静态内部类不能有静态声明 */ static { System.out.println("=========="); } private static int inProp; private static void test(){} } }5静态内部类可以包含静态成员,也可以包含非静态成员,根据静态成员不能访问非静态成员的规则,静态内部类不能访问外部类的实例成员,只能访问外部类的成员,即使是静态内部类的实例方法也不能访问外部类的实例成员,只能访问外部类的静态成员。
代码例子:
public class StaticInnerClassTest { private int prop1 = 5; private static int prop2 = 9; static class StaticInnerClass { //静态内部类里可以包含静态成员 private static int age; public void accessOuterProp() { //下面代码出现错误: //静态内部类无法访问外部类的实例成员 System.out.println(prop1); //下面代码正常 System.out.println(prop2); } } }6接口里定义内部类,内部类只能使用public static 修饰(默认也是public static),即相当于是静态内部类,而内部接口只能用public修饰符(默认也是public)。
7在外部类以外的地方定义内部类(包括静态和非静态两种)变量的语法(OuterClass.InnerClass varName),在外部类以外的地方创建非静态内部类实例的语法(OuterInstance.new innerConstructor()),在外部类以外的地方创建静态内部类实例的语法(new OuterClass.InnerConstructor())。
代码例子:
class Out { //定义一个内部类,不使用访问控制符, //即只有同一个包中其他类可访问该内部类 class In { public In(String msg) { System.out.println(msg); } } } public class CreateInnerInstance { public static void main(String[] args) { Out.In in = new Out().new In("测试信息"); /*上面代码可改为如下三行代码:使用OutterClass.InnerClass的形式定义内部类变量Out.In in;创建外部类实例,非静态内部类实例将寄存在该实例中 Out out = new Out(); 通过外部类实例和new来调用内部类构造器创建非静态内部类实例 in = out.new In("测试信息");*/ } } class StaticOut { //定义一个静态内部类,不使用访问控制符,即同一个包中其他类可访问该内部类 static class StaticIn { public StaticIn() { System.out.println("静态内部类的构造器"); } } } public class CreateStaticInnerInstance { public static void main(String[] args) { StaticOut.StaticIn in = new StaticOut.StaticIn(); /*上面代码可改为如下两行代码:使用OutterClass.InnerClass的形式定义内部类变量StaticOut.StaticIn in;通过new来调用内部类构造器创建静态内部类实例 in = new StaticOut.StaticIn();*/ } }8创建非静态内部类的子类时,必须保证让子类构造器可以调用非静态内部类的构造器,调用非静态内部类的构造器时,必须存在一个外部类对象,静态内部类只需使用外部类即可调用构造器,而非静态内部类必须能使用外部类对象来调用构造器。
代码例子:
class Out { //定义一个内部类,不使用访问控制符, //即只有同一个包中其他类可访问该内部类 class In { public In(String msg) { System.out.println(msg); } } } public class SubClass extends Out.In { //显示定义SubClass的构造器 public SubClass(Out out) { //通过传入的Out对象显式调用In的构造器 out.super("hello"); } } class StaticOut { //定义一个静态内部类,不使用访问控制符, //即同一个包中其他类可访问该内部类 static class StaticIn { public StaticIn() { System.out.println("静态内部类的构造器"); } } } public class StaticSubclass extends StaticOut.StaticIn{}9由于局部内部类不能在外部类的方法以外的地方使用,因此局部内部类也不能使用访问控制符和static修饰符修饰。
10匿名内部类不能是抽象类,所以匿名内部类必须实现它的抽象父类或者接口里包含的所有抽象方法,通过继承父类来创建匿名内部类时,匿名内部类将拥有和父类相似的构造器,此处的相似指的是拥有相同的形参列表。
代码例子:
abstract class Device { private String name; public abstract double getPrice(); public Device(){} public Device(String name) { this.name = name; } //此处省略了name的setter和getter方法 public void setName(String name) { this.name = name; } public String getName() { return this.name; } } public class AnonymousInner { public void test(Device d) { System.out.println("购买了一个" + d.getName() + ",花掉了" + d.getPrice()); } public static void main(String[] args) { AnonymousInner ai = new AnonymousInner(); //调用有参数的构造器创建Device匿名实现类的对象 ai.test(new Device("电子示波器") { public double getPrice() { return 67.8; } }); //调用无参数的构造器创建Device匿名实现类的对象 Device d = new Device() { //初始化块 { System.out.println("匿名内部类的初始化块..."); } //实现抽象方法 public double getPrice() { return 56.2; } //重写父类的实例方法 public String getName() { return "键盘"; } }; ai.test(d); } }11如果匿名内部类和局部内部类需要访问外部类的局部变量,则必须使用final修饰符来修饰外部类的局部变量。
代码例子:
interface A { void test(); } public class ATest { public static void main(String[] args) { int age = 0; A a = new A() { public void test() { //下面语句将提示错误: //匿名内部类内访问局部变量必须使用final修饰 System.out.println(age); } }; } }7枚举类
1手动枚举类
通过private将构造器隐藏起来。
把这个类的所有可能实例都使用public static final修饰的类变量来保存。
如果有必要,可以提供一些静态方法,允许其他程序根据特定参数来获取与之匹配的实例。
代码例子:
public class Season { //把Season类定义成不可变的,将其Field也定义成final private final String name; private final String desc; public static final Season SPRING = new Season("春天" , "趁春踏青"); public static final Season SUMMER = new Season("夏天" , "夏日炎炎"); public static final Season FALL = new Season("秋天" , "秋高气爽"); public static final Season WINTER = new Season("冬天" , "围炉赏雪"); public static Season getSeason(int seasonNum) { switch(seasonNum) { case 1 : return SPRING; case 2 : return SUMMER; case 3 : return FALL; case 4 : return WINTER; default : return null; } } //将构造器定义成private访问权限 private Season(String name , String desc) { this.name = name; this.desc = desc; } //只为name和desc提供getter方法 public String getName() { return this.name; } public String getDesc() { return this.desc; } }2用Enum定义的枚举类
枚举类可以实现一个或多个接口,默认继承了java.lang.Enum类,而不是继承Object类,其中java.lang.Enum类实现了java.lang.Serizlizable和java.lang.Comparable两个接口。
使用enum定义,非抽象的枚举类默认会使用final修饰,因此枚举类不能派生子类。
枚举类的构造器只能使用private控制符修饰(默认也是private)。
枚举类的所有实例必须在枚举类的第一行显示列出,否则这个枚举类永远都不能产生实例,列出这些实例时,系统会自动添加public static final修饰。
所有的枚举类提供了一个value方法,该方法可以很方便的遍历所有的枚举值。
代码例子:
public enum SeasonEnum { // 在第一行列出4个枚举实例 SPRING,SUMMER,FALL,WINTER; } public class EnumTest { public void judge(SeasonEnum s) { //switch语句里的表达式可以是枚举值 switch (s) { case SPRING: System.out.println("春暖花开,正好踏青"); break; case SUMMER: System.out.println("夏日炎炎,适合游泳"); break; case FALL: System.out.println("秋高气爽,进补及时"); break; case WINTER: System.out.println("冬日雪飘,围炉赏雪"); break; } } public static void main(String[] args) { //所有枚举类都有一个values方法,返回该枚举类的所有实例 for (SeasonEnum s : SeasonEnum.values()) { System.out.println(s); } //平常使用枚举实例时, //总是通过EnumClass.variable形式来访问 new EnumTest().judge(SeasonEnum.SPRING); } }3枚举类的实例只能是枚举值,不是用new来创建枚举对象的.可以通过Enum.valueOf(Class enumType,String name)来创建指定的枚举值对象。
代码例子:
public enum Gender { MALE,FEMALE; private String name; public void setName(String name) { switch (this) { case MALE: if (name.equals("男")) { this.name = name; } else { System.out.println("参数错误"); return; } break; case FEMALE: if (name.equals("女")) { this.name = name; } else { System.out.println("参数错误"); return; } break; } } public String getName() { return this.name; } } public class GenderTest { public static void main(String[] args) { //通过Enum的valueOf方法来获取指定枚举类的枚举值 Gender g = Enum.valueOf(Gender.class , "FEMALE"); //直接为枚举值的Field赋值 g.name = "女"; //直接访问枚举值的Field值 System.out.println(g + "代表:" + g.name); } }4如果枚举类的构造器有参数的话,可以同枚举值来传入对应的参数来调用对应的构造器。
代码例子:
public enum Gender { //此处的枚举值必须调用对应构造器来创建 MALE("男"),FEMALE("女"); private final String name; //枚举类的构造器只能使用private修饰 private Gender(String name) { this.name = name; } public String getName() { return this.name; } }5如果需要每个枚举值在调用该方法时呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法,每个枚举值提供不同的实现方式,从而让不同的枚举值调用该方法时具有不同的行为方式。
代码例子:
public interface GenderDesc { void info(); } public enum Gender implements GenderDesc { //此处的枚举值必须调用对应构造器来创建 MALE("男") //花括号部分实际上是一个类体部分 { public void info() { System.out.println("这个枚举值代表男性"); } }, FEMALE("女") { public void info() { System.out.println("这个枚举值代表女性"); } }; //其他部分与codes\06\6.8\best\Gender.java中的Gender类完全相同 private final String name; //枚举类的构造器只能使用private修饰 private Gender(String name) { this.name = name; } public String getName() { return this.name; } //增加下面的info方法,实现GenderDesc接口必须实现的方法 public void info() { System.out.println( "这是一个用于用于定义性别Field的枚举类"); } }6枚举类里定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类(因为系统自动会为它添加abstract关键字),但因为枚举类需要显示创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。
代码例子:
public enum Operation2 { PLUS { public double eval(double x , double y) { return x + y; } }, MINUS { public double eval(double x , double y) { return x - y; } }, TIMES { public double eval(double x , double y) { return x * y; } }, DIVIDE { public double eval(double x , double y) { return x / y; } }; //为枚举类定义一个抽象方法, //这个抽象方法由不同枚举值提供不同的实现 public abstract double eval(double x, double y); public static void main(String[] args) { System.out.println(Operation2.PLUS.eval(3, 4)); System.out.println(Operation2.MINUS.eval(5, 4)); System.out.println(Operation2.TIMES.eval(5, 4)); System.out.println(Operation2.DIVIDE.eval(5, 4)); } }8对象与垃圾回收
1垃圾回收机制的特征。
垃圾回收机制只负责回收堆中内存中的对象,不会回收任何物理资源(例如数据库连接,网咯IO等资源)。
程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行,当对象永久性的失去了引用后,系统就会在合适的时候回收它所占的内存。
在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。
2对象在内存中的状态,如图:
3强制系统垃圾回收可以有如下两个方法(即强制系统调用finalize()方法)
调用System类的gc()静态方法:System.gc();
调用Rumtime对象的gc()实例方法:Rumtime.getRumtime().gc();
但是调用这两种方法后,系统不一定调用finalize()方法,进行垃圾回收,这两种方法只是告知系统垃圾回收机制尽快进行垃圾回收。
代码例子:
public class Test { public static void main(String[] args) { for (int i = 0 ; i < 4; i++) { new Test(); //下面两行代码的作用完全相同,强制系统进行垃圾回收 //System.gc(); Runtime.getRuntime().gc(); } } public void finalize() { System.out.println("系统正在清理GcTest对象的资源..."); } }第一次运行结果:
第二次运行结果:
当你运行多次,你会发现结果会出现不一样的(即每次运行,finalize()方法被调用的次数不一样。
4finalize()方法的特点。
永远不要主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用。
finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会被执行的方法。
当JVM执行可恢复的finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态。
当JVM执行finalize ()方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行。
5System和Runtime类里都提供了一个runFinalization方法,可以强制垃圾回收机制调用系统中可恢复对象的finalize()方法。
代码例子:
public class FinalizeTest2 { private static FinalizeTest2 ft = null; public void info() { System.out.println("测试资源清理的finalize方法"); } public static void main(String[] args) throws Exception { //创建TestFinalize对象立即进入可恢复状态 new FinalizeTest2(); //通知系统进行资源回收 System.gc(); //强制垃圾回收机制调用可恢复对象的finalize方法 Runtime.getRuntime().runFinalization(); //① //System.runFinalization(); //② ft.info(); } public void finalize() { //让tf引用到试图回收的可恢复对象,即可恢复对象重新变成可达 ft = this; } }5对象的引用类型。
强引用(StrongReference):当一个对象被一个或一个以上的引用变量所引用时,它处于可达状态,不可能被系统垃圾回收机制回收。(例如我们创建对象或数组赋给一个引用变量,这就是强引用。)
软引用(SoftReference):当一个对象只有软引用时,它有可能被垃圾回收机制回收,系统内存空间足够时,它不会被系统回收,当系统内存空间不足时,系统可能回收它。
弱引用(WeakReference):当系统垃圾回收机制运行时,不管系统内存是否足够,总会回收该对象所占用的内存(不是立即被回收,它类似于失去所有引用的对象一样,必须等到系统垃圾回收机制运行时才会被回收)。
虚引用(PhantomReference):虚引用完全类似于没有引用,对对象本身没有太大影响,对象甚至感觉不到虚引用的存在,主要用于跟踪对象被垃圾回收的状态,虚引用不能单独使用,必须和引用队列联合使用。
弱引用代码例子:
public class Test { public static void main(String[] args) throws Exception { //创建一个字符串对象,这里不能使用String str="beyondboy";系统垃圾回收机制不会回收这个常量池的字符串直接常量。 String str = new String("beyondboy"); //创建一个弱引用,让此弱引用引用到"beyondboy"字符串 WeakReference wr = new WeakReference(str); //① //切断str引用和"beyondboy"字符串之间的引用 str = null; //② //取出弱引用所引用的对象 System.out.println(wr.get()); //③ //强制垃圾回收 System.gc(); System.runFinalization(); //再次取出弱引用所引用的对象 System.out.println(wr.get()); //④ } }运行结果:
系统内存如图所示:
虚引用代码例子:
public class Test { public static void main(String[] args) throws Exception { //创建一个字符串对象 String str = new String("beyondboy"); //创建一个引用队列 ReferenceQueue rq = new ReferenceQueue(); //创建一个虚引用,让此虚引用引用到"beyondboy"字符串 PhantomReference pr = new PhantomReference (str , rq); //切断str引用和"beyondboy"字符串之间的引用 str = null; //取出虚引用所引用的对象, //并不能通过虚引用访问被引用的对象,所以此处输出null System.out.println(pr.get()); //① //强制垃圾回收 System.gc(); System.runFinalization(); //垃圾回收之后,虚引用将被放入引用队列中 //取出引用队列中最先进入队列中的引用与pr进行比较 System.out.println(rq.poll() == pr); //② } }运行结果:
系统内存如图所示:
6下面是两段伪代码(解析这两段伪代码的区别)
第一段伪代码 //取出弱引用所引用的对象 obj=wr.get(); if(obj==null) { //重新创建一个新的对象,再次让弱引用去引用该对象 wr=new WeakReference(recreateIt());//① obj=wr.get();//② } 第二段伪代码 //取出弱引用所引用的对象 obj=wr.get(); if(obj==null) { //重新创建一个新的对象,使用强引用去引用它 obj=recreateIt(); //取出弱引用所引用的对象,将其赋给obj变量 wr=new WeakReference(obj); }其中recreateIt()方法用于生成一个对象,第一段伪代码存在一个问题:当if块执行完之后,obj还是有可能为null,因为垃圾回收的不确定性,假设在①和②行代码之间进行垃圾垃圾回收(new WeakReference(recreateIt());这句话表明recreateIt()创建的对象除了弱引用,就没有其他的变量引用了,因此随时都可能被垃圾回收机制回收),则系统会再次将wr所引用的对象回收,从而导致obj依然为null.而第二段代码则不会有这个问题(obj=recreateIt();这句话表明recreateIt()创建的对象有了一个obj强引用,处于可达状态,就不可能被垃圾回收机制回收)。
10修饰符的使用范围
这篇博客参考资料:
Java疯狂讲义2