Java 8增强的包装类:
8中基本数据类型——8中包装类:
byte Byte
short Short
int Integer
long Long
char Character
float Float
double Double
boolean Boolean
除了int和char例外,其余基本数据类型对应的包装类都是将其首字母大写
Java1.5提供了自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)功能
自动装箱:可以把一个基本类型变量直接赋值给对应的包装类变量,或赋值给Object变量。
自动拆箱:允许直接把包装类对象直接赋值给一个对应的基本类型变量。
1 public class AutoBoxingUnboxing{ 2 public static void main(String[] args){ 3 //直接把一个基本类型变量赋给Integer对象 4 Integer inObj = 5; 5 //直接把一个boolean类型变量赋给一个Object类型的变量 6 Object boolObj = true; 7 //直接把一个Integer对象赋给int类型的变量 8 int it = inObj; 9 if(boolObj instanceof Boolean){ 10 //先把Object对象强制类型转换为Boolean类型,再赋值给boolean变量 11 boolean b = (Boolean) boolObj; 12 System.out.println(b); 13 } 14 } 15 }
Java还提供了基本类型变量的包装类和字符串之间的转换:
包装类(除了Character之外)提供了 parseXxx(String s)静态方法,可将字符串转换为基本数据类型
String类提供了多个重载valueOf()方法,用于将基本类型转变为字符串
1 public class Primitive2String{ 2 public static void main(String[] args){ 3 String intStr = "123"; 4 //把一个特定字符串转换成int变量 5 int it1 = Integer.parseInt(intStr); 6 int it2 = new Integer(intStr); 7 System.out.println(it2); 8 String floatStr = "4.56"; 9 //把一个特定字符串转换成float变量 10 float ft1 = Float.parseFloat(floatStr); 11 float ft2 = new Float(floatStr); 12 System.out.println(ft2); 13 //把一个float变量转换成String变量 14 String ftStr = String.valueOf(2.345f); 15 System.out.println(ftStr); 16 //把一个double变量变成String变量 17 String dbStr = String.valueOf(3.344); 18 System.out.println(dbStr); 19 //把一个boolean变量转换为String变量 20 String boolStr = String.valueOf(true); 21 System.out.println(boolStr.toUpperCase()); 22 } 23 }
把基本类型转换为String类型,更简便的方法是:
String intStr = 5 + "";
包装类的实例可以与数值类型进行比较:
Integer a = new Integer(6);
//输出true
System.out.println("6的包装类实例是否大于5.0:" + (a > 5.0));
处理对象:
Object类中的方法:
1.toString():
java中System.out.println(p);//p是Person类的一个引用,该语句等价于System.out.println(p.toString());
java中所有对象都可以和字符串进行连接运算:String pStr = p + "";//等价于String pStr = p.toString() + “”;
Object类提供的toString()方法总是返回该对象实现类的“类名 + @ + hashCode”,用户要自己重写Object类中的toString()方法来实现类的”自我描述”功能。
2.==和equals()方法
测试两个变量是否相等的两种方式:== 和 equals()
若两个变量是基本类型变量,且都是数值类型(不一定要求数据类型严格相同),则只要两个变量的值相等,就将返回true;
若两个变量是引用类型变量,只有它们指向同一个对象时,==判断才会返回true。==不可用于比较类型上没有父子关系的两个对象。
1 public class EqualTest{ 2 public static void main(String[] args){ 3 System.out.println("hello" == new EqualTest()); 4 } 5 }
Object类中的equals()方法和==没有任何区别,要求两个引用变量指向同一个对象才会返回true。若希望采用自定义的相等标准,需要重写。
String类重写了equals()方法,判断两个字符串相等的标准是:只要两个字符串包含的字符序列相同,通过equals()比较将返回true,否则将返回false。
下面是一个重写了equals()方法的Person类:
1 class Person{ 2 private String name; 3 private String idStr; 4 public Person(){} 5 public Person(String name, String idStr){ 6 this.name = name; 7 this.idStr = idStr; 8 } 9 10 public String getIdStr(){ 11 return idStr; 12 } 13 14 //重写equals()方法 15 public boolean equals(Object obj){ 16 //若两个对象为同一个对象 17 if(this == obj) 18 return true; 19 //只有当obj是Person对象 20 if(obj != null && obj.getClass() == Person.class){ 21 Person personObj = (Person) obj; 22 //并且当前对象的idStr与obj对象的idStr相等时才可判断两个对象相等 23 if(this.getIdStr().equals(personObj.getIdStr())) 24 return true; 25 } 26 return false; 27 } 28 } 29 30 public class OverrideEqualsRight{ 31 public static void main(String[] args){ 32 Person p1 = new Person("孙悟空", "1234"); 33 Person p2 = new Person("孙悟空", "1234"); 34 Person p3 = new Person("孙悟饭", "4321"); 35 //p1和自身相比 36 System.out.println("p1和p1是否相等?" + p1.equals(p1)); 37 //p1和p2的idStr相等,所以输出true 38 System.out.println("p1和p2是否相等?" + p1.equals(p2)); 39 //p2和p3的idStr不相等,所以输出false 40 System.out.println("p2和p3是否相等?" + p2.equals(p3)); 41 } 42 }
这里判断obj是否为Person类的实例使用的是obj.getClass() == Person.class//使用了反射基础
不适用instanceof运算符,是因为对于instanceof运算符而言,前操作数只要是后操作数的实例或其子类的实例时,都将返回true;而我们要判断两个对象是否是同一个类的
实例,所以不能使用instanceof运算符。
单例(Singleton)类:
一个类始终只能创建一个实例,则这个类被称为单例类:
1 class Singleton{ 2 //使用一个类变量来缓存曾经创建的实例 3 private static Singleton instance; 4 5 //对构造器使用private修饰,隐藏该构造器 6 private Singleton(){} 7 8 //提供一个静态方法,用于返回Singleton实例 9 //该方法可以加入自定义控制,保证只产生一个Singleton对象 10 public static Singleton getInstance(){ 11 //若instance为null,则表明还不曾创建Singleton对象 12 //若instance不为null,则表明已经创建了Singleton对象 13 if(instance == null){ 14 //创建一个Singleton对象,并将其缓存起来 15 instance= new Singleton(); 16 } 17 18 return instance; 19 } 20 } 21 22 public class SingletonTest{ 23 public static void main(String[] args){ 24 //创建Singleton对象不能通过构造器 25 //只能通过getInstance()方法来得到实例 26 Singleton s1 = Singleton.getInstance(); 27 Singleton s2 = Singleton.getInstance(); 28 29 System.out.println(s1 == s2);//将输出true 30 } 31 }
final修饰符:
final关键字可用于修饰类、变量和方法,表示它修饰的类、方法和变量不可改变。
final修饰变量,一旦该变量获得了初始值就不可被改变
java程序规定:final修饰的成员变量必须有程序员显式地指定初始值,系统不会对final成员进行隐式初始化,不要再未初始化final成员前,访问final成员
1 public class FinalErrorTest{ 2 //定义一个final修饰的实例变量 3 //系统不会对final成员变量进行默认初始化 4 final int age; 5 { 6 //age没有初始化,所以此处代码将引起错误 7 System.out.println(age); 8 age = 6; 9 System.out.println(age); 10 } 11 12 public static void main(String[] args){ 13 new FinalErrorTest(); 14 } 15 }
final修饰的类变量、实例变量:
类变量:必须在静态初始化块中指定初始值或生命该类变量时指定初始值,二选一
实例变量:必须在非静态初始化块、声明该实例变量或构造器中指定初始值,三选一
1 public class FinalVariableTest{ 2 //定义成员变量是指定初始值,合法 3 final int a = 6; 4 //下面变量将在构造器或初始化块中分配初始值 5 final String str; 6 final int c; 7 final static double d; 8 //既没有指定初始值,有没有在初始化块、构造器中指定初始值 9 //下面定义的ch实例变量是不合法的 10 final char ch; 11 //初始化块,可对没有指定初始值的实例变量指定初始值 12 { 13 //在初始化块中为实例变量指定初始值,合法 14 str = "hello"; 15 //定义a实例变量时已经指定了默认值 16 //不能为a重新赋值,因此下面赋值语句非法 17 a = 9; 18 } 19 //静态初始化块,可对没有指定初始值的类变量指定初始值 20 static{ 21 //在静态初始化块中为类变量指定初始值,合法 22 d = 5.6; 23 } 24 //构造器中,可对没有指定初始值,有没有在初始化块中 25 //指定初始值的实例变量指定初始值 26 public FinalVariableTest(){ 27 //若在初始化块中已经对str指定了初始值 28 //那么在构造器中不能对final变量重新赋值,下面的赋值语句非法 29 str = "java"; 30 c = 5; 31 } 32 public void changeFinal(){ 33 //普通方法不能为final修饰的成员变量赋值 34 d = 1.2; 35 //不能在普通方法中为final成员变量指定初始值 36 ch = 'a'; 37 } 38 public static void main(String[] args){ 39 FinalVariableTest ft = new FinalVariableTest(); 40 System.out.println(ft.a); 41 System.out.println(ft.c); 42 System.out.println(ft.d); 43 } 44 }
final修饰的方法:
不可被重写
final修饰的类:
不可被继承
不可变类:创建该类的实例后,该实例的实例变量是不可改变的。Java提供的8个包装类和Java.lang.String类都是不可变类。
final修饰引用变量时,仅表示这个引用变量类型是不可被重新赋值的,但引用类型变量所指向的实例依然可以改变。若不可变类中包含的成员变量是引用变量,那么
这个引用变量的对象是可变的,则这个不可变类是失败的不可变类:
1 //这是一个可变类,因为Person类中的name引用的对象是可以更改的 2 class Name{ 3 private String firstName; 4 private String lastName; 5 public Name(){} 6 public Name(String firstName, String lastName){ 7 this.firstName = firstName; 8 this.lastName = lastName; 9 } 10 11 public String getFirstName(){ 12 return firstName; 13 } 14 public void setFirstName(String firstName){ 15 this.firstName = firstName; 16 } 17 public String getLastName(){ 18 return lastName; 19 } 20 public void setLastName(String lastName){ 21 this.lastName = lastName; 22 } 23 } 24 25 public class Person{ 26 private final Name name; 27 public Person(Name name){ 28 this.name = name; 29 } 30 public Name getName(){ 31 return name; 32 } 33 public static void main(String[] args){ 34 Name n = new Name("悟空", "孙"); 35 Person p = new Person(n); 36 //Person对象的name得firstName值为“悟空” 37 System.out.println(p.getName().getFirstName()); 38 //改变Person对象的name的firstName值 39 n.setFirstName("八戒"); 40 //Person对象的name的firstName值被改为“八戒” 41 System.out.println(p.getName().getFirstName()); 42 } 43 }
将上面的可变类改为不可变类:
1 //这是一个可变类,因为Person类中的name引用的对象是可以更改的 2 class Name{ 3 private String firstName; 4 private String lastName; 5 public Name(){} 6 public Name(String firstName, String lastName){ 7 this.firstName = firstName; 8 this.lastName = lastName; 9 } 10 11 public String getFirstName(){ 12 return firstName; 13 } 14 public void setFirstName(String firstName){ 15 this.firstName = firstName; 16 } 17 public String getLastName(){ 18 return lastName; 19 } 20 public void setLastName(String lastName){ 21 this.lastName = lastName; 22 } 23 } 24 25 public class Person{ 26 private final Name name; 27 public Person(Name name){ 28 //设置name实例变量为临时创建的Name对象,该对象的firstName和lastName 29 //与传入的name参数的firstName和lastname相同 30 this.name = new Name(name.getFirstName(), name.getLastName()); 31 } 32 public Name getName(){ 33 //返回一个匿名对象,该对象的firstName和lastName 34 //与该对象里的name的firstName和lastName相同 35 return new Name(name.getFirstName(), name.getLastName()); 36 } 37 public static void main(String[] args){ 38 Name n = new Name("悟空", "孙"); 39 Person p = new Person(n); 40 //Person对象的name得firstName值为“悟空” 41 System.out.println(p.getName().getFirstName()); 42 //改变Person对象的name的firstName值 43 n.setFirstName("八戒"); 44 //Person对象的name的firstName值被改为“八戒” 45 System.out.println(p.getName().getFirstName()); 46 } 47 }
上面的Person类就是一个不可变类,即使再怎么更改n.setFirstName()和n.setLastName(),也不会改变Person类中name引用的对象的值。
缓存实例的不可变类:
1 class CacheImmutale{ 2 private static int MAX_SIZE = 10; 3 //使用数组来缓存已有的实例 4 private static CacheImmutale[] cache = new CacheImmutale[MAX_SIZE]; 5 //记录缓存实例在缓存中的位置,cache[pos - 1]是最新缓存的实例 6 private static int pos = 0; 7 private final String name; 8 private CacheImmutale(String name){ 9 this.name = name; 10 } 11 public String getName(){ 12 return name; 13 } 14 public static CacheImmutale valueOf(String name){ 15 //遍历已缓存的对象 16 for(int i = 0; i < MAX_SIZE; i++){ 17 //如果已有相同实例,则直接返回该缓存的实例 18 if(cache[i] != null && cache[i].getName().equals(name)){ 19 return cache[i]; 20 } 21 } 22 //若缓存池已满 23 if(pos == MAX_SIZE){ 24 //把缓存的第一个对象覆盖,即把刚刚生成的对象放在缓存池的最开始位置 25 cache[0] = new CacheImmutale(name); 26 //把pos设为1 27 pos = 1; 28 }else{ 29 //把新创建的对象缓存起来,pos加1 30 cache[pos++] = new CacheImmutale(name); 31 } 32 return cache[pos - 1]; 33 } 34 public boolean equals(Object obj){ 35 if(this == obj){ 36 return true; 37 } 38 if(obj != null && obj.getClass() == CacheImmutale.class){ 39 CacheImmutale ci = (CacheImmutale) obj; 40 return name.equals(ci.getName()); 41 } 42 return false; 43 } 44 public int hashCode(){ 45 return name.hashCode(); 46 } 47 } 48 49 public class CacheImmutaleTest{ 50 public static void main(String[] args){ 51 CacheImmutale c1 = CacheImmutale.valueOf("hello"); 52 CacheImmutale c2 = CacheImmutale.valueOf("hello"); 53 //下面代码将输出true 54 System.out.println(c1 == c2); 55 } 56 }
再来看看java.lang.Integer中Integer类构造器和valueOf方法的差异:
1 public class IntegerCacheTest{ 2 public static void main(String[] args){ 3 //生成新的Integer对象 4 Integer in1 = new Integer(5); 5 //生成新的Integer对象,并缓存该对象 6 Integer in2 = Integer.valueOf(5); 7 //直接从缓存中取出Integer对象 8 Integer in3 = Integer.valueOf(5); 9 System.out.println(in1 == in2);//输出false 10 System.out.println(in2 == in3);//输出true 11 //由于Integer只缓存-128~127之间的值 12 //因此200对应的Integer对象没有被缓存 13 Integer in4 = Integer.valueOf(200); 14 Integer in5 = Integer.valueOf(200); 15 System.out.println(in4 == in5);//输出false 16 } 17 }
抽象类:
关键字abstract
不可被实例化,即使抽象类中不包含抽象方法
抽象类包含成员变量、方法(普通方法和抽象方法)、构造器(不用于创建实例,而是被其子类调用)、初始化块、内部类(接口、枚举)5钟成分。
只能被当做父类被子类继承
只能定义为抽象类的三种情况:
1.直接定义了一个抽象方法
2.继承了一个抽象父类,但是没有完全实现父类中的抽象方法
3.实现了一个接口,但是没有完全实现接口中的抽象方法
抽象方法:
public abstract double abstractClass();//记得最后加上分号
必须由不是抽象类的子类提供实现。
final修饰符不能和abstract修饰符一起使用
static修饰符也不能和abstract修饰符一起使用去修饰某个方法(静态方法属于类本身,可由类名调用,若为abstract则会调用错误,所以不能一起使用)
static和abstract可以一起修饰内部类
abstract修饰的方法必须被其子类重写才有意义,否则方法永远不会有方法体,所以abstract方法不能定义为private访问权限,即private和abstract不能同时修饰方法。
模板模式:设计模式之一
1 abstract class SpeedMeter{ 2 //转速 3 private double turnRate; 4 public SpeedMeter(){} 5 //把返回车轮半径的方法定义成抽象方法 6 public abstract double getRadius(); 7 public void setTurnRate(double turnRate){ 8 this.turnRate = turnRate; 9 } 10 //定义计算速度的通用算法 11 public double getSpeed(){ 12 //速度等于 车轮半径 * 2 * PI * 转速 13 return java.lang.Math.PI * 2 * getRadius() * turnRate; 14 } 15 } 16 17 public class CarSpeedMeter extends SpeedMeter{ 18 public double getRadius(){ 19 return 0.28; 20 } 21 22 public static void main(String[] args){ 23 CarSpeedMeter csm = new CarSpeedMeter(); 24 csm.setTurnRate(15); 25 System.out.println(csm.getSpeed()); 26 } 27 }
抽象父类的普通方法getSpeed()依赖于一个抽象方法getRadius()获得车轮半径,从而计算出速度。
模板模式在面向对象的软件中经常使用,简单规则:
1.抽象父类可以只定义需要使用的某些方法,把不能实现部分抽象成抽象方法,留给其子类去实现
2.父类中可能包含需要调用其他系列方法的方法,这些被调用方法既可以有父类实现,也可以有其子类实现。
接口:
接口中不能包含普通方法,所有方法都是抽象方法
Java 8对接口进行改进,允许在接口中定义默认方法,默认方法可以提供方法实现。
一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
修饰符可以是public或省略,若省略public访问控制符,则默认采用包权限访问控制符,即只有在相同包结构下才可以访问该接口。
接口不能包含构造器和初始化块定义。
接口可以包含成员变量(只能是静态变量)、方法(只能是抽象实例方法、类方法或默认方法)、内部类(包括内部接口、枚举)定义。
接口中定义成员变量时,不管是否使用public static final修饰符,接口的成员变量总是使用这三个修饰符来修饰,且接口中没有构造器和初始化块,因此接口中成员变量只能
在定义时指定默认值:
//系统自动为接口里定义的成员变量增加public static final修饰符,下面两行代码定义在接口中:
int MAX_SIZE = 50;
public static final int MAX_SIZE = 50;
上面两行代码结果是完全一样的
在接口中系统自动为普通方法增加public abstract修饰符,接口中普通方法不能有方法实现,但是类方法、默认方法可以有方法实现。
接口的继承:
1 interface interfaceA{ 2 public static final int PROP_A = 5; 3 public abstract void testA(); 4 } 5 6 interface interfaceB{ 7 public static final int PROP_B = 6; 8 public abstract void testB(); 9 } 10 11 interface interfaceC extends interfaceA, interfaceB{ 12 public static final int PROP_C = 7; 13 public abstract void testC(); 14 } 15 16 public class InterfaceExtendsTest{ 17 public static void main(String[] args){ 18 System.out.println(interfaceC.PROP_A); 19 System.out.println(interfaceC.PROP_B); 20 System.out.println(interfaceC.PROP_C); 21 } 22 }
实现接口类:
1 public interface Output{ 2 //接口里定义的成员变量只能是常量 3 public static final int MAX_CACHE_LINE = 50; 4 //接口里定义的普通方法只能是public的抽象方法 5 public abstract void out(); 6 public abstract void getData(String msg); 7 //在接口中定义默认方法,需要使用default修饰 8 default void print(String... msgs){ 9 for(String msg : msgs){ 10 System.out.println(msg); 11 } 12 } 13 //在接口中定义默认方法要使用default修饰 14 default void test(){ 15 System.out.println("默认的test()方法"); 16 } 17 //在接口中定义类方法,需要使用static修饰 18 static String staticTest(){ 19 return "接口里的类方法"; 20 } 21 }
1 //定义一个Product接口 2 interface Product{ 3 int getProduceTime(); 4 } 5 6 //让Printer类实现Output和Product接口 7 public class Printer implements Output, Product{ 8 private String[] printData = new String[MAX_CACHE_LINE]; 9 //用以记录当前需打印的作业数 10 private int dataNum = 0; 11 12 public void out(){ 13 //只要还有作业,就继续打印 14 while(dataNum > 0){ 15 System.out.println("打印机打印:" + printData[0]); 16 //把作业队列整体前移一位,并将剩下的作业数减1 17 System.arraycopy(printData, 1 , printData, 0, --dataNum); 18 } 19 } 20 21 public void getData(String msg){ 22 if(dataNum >= MAX_CACHE_LINE){ 23 System.out.println("输出队列已满,添加失败"); 24 }else{ 25 //把打印数据添加到队列里,已保存数据的数量加1 26 printData[dataNum++] = msg; 27 } 28 } 29 30 public int getProduceTime(){ 31 return 45; 32 } 33 34 public static void main(String[] args){ 35 //创建一个Printer对象,当成Output使用 36 Output o = new Printer(); 37 o.getData("轻量级Java EE企业应用实战"); 38 o.getData("疯狂Java讲义"); 39 o.out(); 40 o.getData("疯狂Android讲义"); 41 o.getData("疯狂Ajax讲义"); 42 o.out(); 43 //调用Output接口中定义的默认方法 44 o.print("孙悟空", "猪八戒", "白骨精"); 45 o.test(); 46 //创建一个Printer对象,当成Product使用 47 Product p = new Printer(); 48 System.out.println(p.getProduceTime()); 49 //所有接口类型的引用变量都可直接赋给Object类型的变量 50 Object obj = p; 51 } 52 }
实现接口方法时,必须使用public访问控制符。
接口不能显示继承任何类,但所有接口类型的引用变量都可以直接赋给Object类型的引用变量
抽象类和接口都不能被实例化
接口和抽象类的区别:
接口里只能包含抽象方法、静态方法和默认方法,不能为普通方法提供方法实现;抽象类则完全可以包含普通方法
接口里只能定义静态常量不能定义普通成员变量;抽象类里则既可以定义普通成员变量,也可以定义静态常量
接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作
接口中不能包含初始化块;但抽象类则完全可以包含初始化块
一个类只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口弥补Java单继承的不足。
简单工厂模式:
Computer类组合一个输出设备,有两个选择:
1.直接让Computer类组合一个Printer实现类
2.让Computer类组合一个Output接口
简单工厂模式建议采用Output接口,有什么好处看代码:
Output.java接口:
1 public interface Output{ 2 //接口里定义的成员变量只能是常量 3 public static final int MAX_CACHE_LINE = 50; 4 //接口里定义的普通方法只能是public的抽象方法 5 public abstract void out(); 6 public abstract void getData(String msg); 7 //在接口中定义默认方法,需要使用default修饰 8 default void print(String... msgs){ 9 for(String msg : msgs){ 10 System.out.println(msg); 11 } 12 } 13 //在接口中定义默认方法要使用default修饰 14 default void test(){ 15 System.out.println("默认的test()方法"); 16 } 17 //在接口中定义类方法,需要使用static修饰 18 static String staticTest(){ 19 return "接口里的类方法"; 20 } 21 }
Printer.java打印类:
1 //定义一个Product接口 2 interface Product{ 3 int getProduceTime(); 4 } 5 6 //让Printer类实现Output和Product接口 7 public class Printer implements Output, Product{ 8 private String[] printData = new String[MAX_CACHE_LINE]; 9 //用以记录当前需打印的作业数 10 private int dataNum = 0; 11 12 public void out(){ 13 //只要还有作业,就继续打印 14 while(dataNum > 0){ 15 System.out.println("打印机打印:" + printData[0]); 16 //把作业队列整体前移一位,并将剩下的作业数减1 17 System.arraycopy(printData, 1 , printData, 0, --dataNum); 18 } 19 } 20 21 public void getData(String msg){ 22 if(dataNum >= MAX_CACHE_LINE){ 23 System.out.println("输出队列已满,添加失败"); 24 }else{ 25 //把打印数据添加到队列里,已保存数据的数量加1 26 printData[dataNum++] = msg; 27 } 28 } 29 30 public int getProduceTime(){ 31 return 45; 32 } 33 34 public static void main(String[] args){ 35 //创建一个Printer对象,当成Output使用 36 Output o = new Printer(); 37 o.getData("轻量级Java EE企业应用实战"); 38 o.getData("疯狂Java讲义"); 39 o.out(); 40 o.getData("疯狂Android讲义"); 41 o.getData("疯狂Ajax讲义"); 42 o.out(); 43 //调用Output接口中定义的默认方法 44 o.print("孙悟空", "猪八戒", "白骨精"); 45 o.test(); 46 //创建一个Printer对象,当成Product使用 47 Product p = new Printer(); 48 System.out.println(p.getProduceTime()); 49 //所有接口类型的引用变量都可直接赋给Object类型的变量 50 Object obj = p; 51 } 52 }
Computer.java电脑类:
1 public class Computer{ 2 private Output out; 3 public Computer(Output out){ 4 this.out = out; 5 } 6 7 //定义一个模拟获取字符串输入的方法 8 public void keyIn(String msg){ 9 out.getData(msg); 10 } 11 12 //定义一个模拟打印的方法 13 public void print(){ 14 out.out(); 15 } 16 }
OutputFactory.java打印工场类:
1 public class OutputFactory{ 2 public Output getOutput(){ 3 return new Printer(); 4 //return new BetterPrinter(); 5 } 6 7 public static void main(String[] args){ 8 OutputFactory of = new OutputFactory(); 9 Computer c = new Computer(of.getOutput()); 10 c.keyIn("轻量级Java EE企业应用实战"); 11 c.keyIn("疯狂Java讲义"); 12 c.print(); 13 } 14 }
将Computer电脑类的打印类换成BetterPrinter:将OutputFactory工厂类的getOutput()方法中return new Printer();换为return new BetterPrinter();
1 public class BetterPrinter implements Output{ 2 private String[] printData = new String[MAX_CACHE_LINE * 2]; 3 //用以记录当前需打印的作业数 4 private int dataNum = 0; 5 public void out(){ 6 //只要还有作业,就继续打印 7 while(dataNum > 0){ 8 System.out.println("高速打印机正在打印:" + printData[0]); 9 //把作业队列整体前移一位,并将剩下的作业数减1 10 System.arraycopy(printData, 1, printData, 0, --dataNum); 11 } 12 } 13 14 public void getData(String msg){ 15 if(dataNum >= MAX_CACHE_LINE * 2){ 16 System.out.println("输出队列已满,添加失败"); 17 }else{ 18 //把打印数据添加到队列里,以保存数据的数量加1 19 printData[dataNum++] = msg; 20 } 21 } 22 }
按以上代码来看,简单工厂模式可以随时将实现了Output接口的打印类替换Computer类中的引用out,相当于Computer随时替换打印机。
通过上面方式,即可把所有生成Output对象的逻辑集中在OutputFactory工厂类中管理,而所有需要使用Output对象的类只需要与Output接口耦合,而不是与具体的实现类耦合。
即使系统中有很多类使用了Printer对象,只要OutputFactory类的getOutput()方法生成的Output对象是BetterPrinter对象,则它们全部都会改为使用BetterPrinter对象,而所有程序无需修改,只需要修改OutputFactory工厂类的getOutput()方法实现即可。
命令模式:
某个方法需要完成某一个行为,但这个行为的具体实现无法确定,必须等到执行该方法时才可以确定。如:某方法要遍历一个数组的全部元素,但是对这些元素要进行什么
操作,需要在调用该方法的时候指定具体的处理行为。即数组中的元素要加、减、乘、除只有在调用遍历数组方法后才能确定。
考虑使用一个Command接口定义处理数组元素的方法,用这个方法来封装“处理行为”:
Command.java:
1 public interface Command{ 2 //接口里定义的process方法用于封装“处理行为” 3 void process(int[] target); 4 }
PrintCommand.java:
1 public class PrintCommand implements Command{ 2 public void process(int[] target){ 3 for(int tmp : target){ 4 System.out.println("迭代输出目标数组的元素 :" + tmp); 5 } 6 } 7 }
AddCommand.java:
1 public class AddCommand implements Command{ 2 public void process(int[] target){ 3 int sum = 0; 4 for(int tmp : target){ 5 sum += tmp; 6 } 7 System.out.println("数组元素的总和是:" + sum); 8 } 9 }
Command.java:
1 public class CommandTest{ 2 public static void main(String[] args){ 3 ProcessArray pa = new ProcessArray(); 4 int[] target = {3, -4, 6, 4}; 5 //第一次处理数组,具体处理行为取决于PrintCommand 6 pa.process(target, new PrintCommand()); 7 System.out.println("------------------"); 8 //第二次处理数组,具体处理行为取决于AddCommand 9 pa.process(target, new AddCommand()); 10 } 11 }
运行结果:
上述命令模式,通过一个Command接口,定义一个process()方法,该方法接收两个参数,一个
是数组,一个是对数组的操作方法对象,这样就实现了处理行为在调用方法时确定,而我们可以随时可以改变处理行为,因为我们只要把想要的处理行为实现Command接口,并创建一个对象传入process方法就可以了。
内部类:
定义在其他类内部的类被称为内部类。
内部类提供了良好的封装,不允许同一个包中的其他类访问该类。
内部类成员可以直接访问外部类的私有数据,因为内部类被当成其外部类成员。但外部类不能访问内部类的实现细节。
匿名内部类适合用于创建仅需要一次使用的类。对于上面介绍的命令模式,当需要传入一个Command对象时,重新专门定义PrintCommand和AddCommand两个实现类可能
没有太大的意义,因为这两个实现类可能仅需要使用一次。所以我们可以使用匿名类。
内部类比外部类多了三个修饰符:private、protected、static
非静态内部类不能拥有静态成员
非静态内部类:
内部类都被作为成员内部类定义,而不是作为局部内部类。成员内部类是一种与成员变量、方法、构造器和初始化块相似的类成员;局部内部类和匿名内部类不是类成员
成员内部类分为:静态内部类和非静态内部类
1 public class Cow{ 2 private double weight; 3 //外部类的两个重载的构造器 4 public Cow(){} 5 public Cow(double weight){ 6 this.weight = weight; 7 } 8 9 //定义一个非静态内部类 10 private class CowLeg{ 11 //非静态内部类的两个实例变量 12 private double length; 13 private String color; 14 //非静态内部类的两个重载的构造器 15 public CowLeg(){} 16 public CowLeg(double length, String color){ 17 this.length = length; 18 this.color = color; 19 } 20 21 //非静态内部类的实例方法 22 public void info(){ 23 System.out.println("当前牛的颜色是:" + color + ",高:" + length); 24 //直接访问外部类的private修饰的成员变量 25 System.out.println("本牛腿所在奶牛中:" + weight); 26 } 27 } 28 29 public void test(){ 30 CowLeg cl = new CowLeg(1.12, "黑白相同"); 31 cl.info(); 32 } 33 34 public static void main(String[] args){ 35 Cow cow = new Cow(378.9); 36 cow.test(); 37 } 38 }
当外部类成员变量、内部类成员变量与内部类里方法的局部变量同名:
1 public class DiscernVariable{ 2 private String prop = "外部类的实例变量"; 3 private class InClass{ 4 private String prop = "内部类的实例变量"; 5 6 public void info(){ 7 String prop = "局部变量"; 8 //通过“外部类类名.this.varName”访问外部类实例变量 9 System.out.println(DiscernVariable.this.prop); 10 11 //通过“this.varName”访问内部类实例的变量 12 System.out.println(this.prop); 13 14 //直接访问局部变量 15 System.out.println(prop); 16 } 17 } 18 19 public void test(){ 20 InClass in = new InClass(); 21 in.info(); 22 } 23 24 public static void main(String[] args){ 25 new DiscernVariable().test(); 26 } 27 }
外部类访问非静态内部类的成员,必须显示创建非静态内部类对象来调用访问实例成员:
1 public class Outer{ 2 private int outProp = 9; 3 class Inner{ 4 private int inProp = 5; 5 public void accessOuterProp(){ 6 //非静态内部类可以直接访问外部类的private成员变量 7 System.out.println("外部类的outProp值:" + outProp); 8 } 9 } 10 11 public void accessInnerProp(){ 12 //外部类不能直接访问非静态内部类的实例变量 13 //下面代码出现编辑错误 14 //System.out.println("内部类的inProp值:" + inProp); 15 //若访问内部类的实例变量,必须显示创建内部类对象 16 System.out.println("内部类的inProp值:" + new Inner().inProp); 17 } 18 19 public static void main(String[] args){ 20 //执行下面代码,之创建了外部类对象,还未创建内部类对象 21 Outer out = new Outer(); 22 out.accessInnerProp(); 23 } 24 }
非静态内部类对象必须寄生在外部类对象里,所以,若存在一个非静态内部类对象,则一定存在一个被它寄生的外部类对象,但外部类对象存在,非静态内部类对象不一定
存在。
静态成员不能访问非静态成员,外部类静态方法、静态代码块不能访问非静态内部类。
Java不允许在非静态内部类中定义静态成员:
1、既然是非静态内部类,说明该类只被包含它的那个外部类使用,是专用的,所以定义成静态的没有意义
2、Java的装载过程是先加载类,然后初始化静态元素(Java编译器会使所有的静态元素在使用前被分配好内存空间)然后非静态的只有在使用的时候才分配内存
空间,而内部类是作为外部类的成员存在的,所以非静态的内部类必须等到外部类实例化后才会存在,所以非静态内部类的静态成员(如果合法的话)在类加载
时并没有被初始化,这与Java的编译原则相违背,但如果内部类是静态内部类,则其中可以定义静态元素。
静态内部类:
静态内部类可包含静态成员、非静态成员。不可访问外部类的实例成员,只能访问外部类成员。
静态内部类是外部类的类相关,不是外部类的对象相关。当静态内部类存在时,并不存在一个被它寄生的外部类对象。
外部类依然不能直接访问静态内部类的成员,但可以使用静态内部类的类名作为调用者来访问静态内部类的类成员。
Java允许在接口中定义内部类,默认使用public static修饰符,即接口中只能是静态内部类。
使用内部类:
定义类的作用就是定义变量、创建实例、作为父类被继承。
但使用内部类定义变量和创建实例与外部类有些差异:
1.在外部类中使用内部类:
不要在外部类的静态成员(包括静态方法和静态初始化块)中使用非静态内部类,因为静态成员不能访问非静态成员
2.在外部类以外使用非静态内部类:
注意修饰符private、protected、默认、public的作用
1 class Out{ 2 //定义一个内部类,不使用访问控制符 3 //即只有同一个包中的其他类可访问该内部类 4 class In{ 5 public In(String msg){ 6 System.out.println(msg); 7 } 8 } 9 } 10 11 public class CreateInnerInstance{ 12 public static void main(String[] args){ 13 Out.In in = new Out().new In("测试信息"); 14 /* 15 上面代码可以改为如下三行代码 16 使用“OutterClass.InnerClass”的形式定义内部类变量 17 Out.In in; 18 创建外部类实例,非静态内部类实例将寄生在该事例中 19 Out out = new Out(); 20 通过外部类实例和new来调用内部类构造器创建非静态内部类实例 21 in = out.new In("测试信息"); 22 */ 23 } 24 }
上面代码可以看出非静态内部类的构造器必须使用外部类对象来调用。若需要在外部类以外的地方创建非静态内部类的子类,则需注意上面的规则:非静态内部类的
构造器必须通过其外部类对象来调用。
当创建一个子类时,子类构造器总会调用父类的构造器,因此在创建非静态内部类的子类时,必须保证让子类构造器可以调用非静态内部类的构造器,调用非静态内
部类的构造器时,必须存在一个外部类对象:
1 public class SubClass extends Out.In{ 2 //显示定义SubClass的构造器 3 public SubClass(Out out){ 4 //通过传入的Out对象显示调用In的构造器 5 out.super("hello"); 6 } 7 }
3.在外部类以外使用静态内部类:
1 class StaticOut{ 2 //定义一个静态内部类,不使用访问控制符 3 //即同一个包中的其他类可以访问该内部类 4 static class StaticIn{ 5 public StaticIn(){ 6 System.out.println("静态内部类的构造器"); 7 } 8 } 9 } 10 11 public class CreateStaticInnerInstance{ 12 public static void main(String[] args){ 13 StaticOut.StaticIn in = new StaticOut.StaticIn(); 14 /* 15 上面代码可以改为如下两行代码 16 使用OuterClass.InnerClass的形式定义内部类变量 17 StaticOut.StaticIn in; 18 通过new来调用内部类构造器创建静态内部类变量 19 in = new StaticOut.StaticIn(); 20 */ 21 } 22 }
在外部类以外创建内部类的子类:
1 public class StaticSubClass extends StaticOut.StaticIn{ 2 public StaticSubClass(){ 3 super(); 4 } 5 }
局部内部类:
若把内部类放在方法中定义,则这个内部类就是一个局部内部类
局部内部类不能在外部类方法以外的地方使用,因此局部内部类也不能使用访问控制符和static修饰符修饰。
1 public class LocalInnerClass{ 2 public static void main(String[] args){ 3 //定义局部内部类 4 class InnerBase{ 5 int a; 6 } 7 8 //定义局部内部类的子类 9 class InnerSub extends InnerBase{ 10 int b; 11 } 12 13 //创建局部内部类的对象 14 InnerSub is = new InnerSub(); 15 is.a = 5; 16 is.b = 6; 17 System.out.println("InnerSub对象的a 和 b 实例变量是:" + is.a + "," + is.b); 18 } 19 }
Java8改进的匿名内部类:
匿名内部类适合创建那种只需要一次使用的类。
创建匿名内部类时会立即创建一个该类的实例,匿名内部类不能重复使用
匿名内部类的两条规则:
1.匿名内部类不能是抽象类,因为系统创建匿名内部类时,会立即创建匿名内部类的对象
2.匿名内部类不能定义构造器。由于匿名内部类没有类名,所以无法定义构造器,但是匿名内部类可以定义初始化块,可通过实例初始化块完成构造器需要完成的事情。
最常用的匿名内部类的方式是需要创建某个接口类型的对象:
1 interface Product{ 2 public double getPrice(); 3 public String getName(); 4 } 5 6 public class AnonymousTest{ 7 public void test(Product p){ 8 System.out.println("购买了一个" + p.getName() + ",花掉了" + p.getPrice()); 9 } 10 11 public static void main(String[] args){ 12 AnonymousTest ta = new AnonymousTest(); 13 //调用test()方法时,需要传入一个Product参数 14 //此处传入其匿名实现类的实例 15 ta.test(new Product(){ 16 public double getPrice(){ 17 return 567.8; 18 } 19 20 public String getName(){ 21 return "AGP显卡"; 22 } 23 }); 24 } 25 }
上面的匿名内部类代码等价于下面的代码:
1 class AnonymousProduct implements Product{ 2 public double getPrice(){ 3 return 567.8; 4 } 5 6 public String getName(){ 7 return "AGP显卡"; 8 } 9 } 10 ta.test(new AnonymousProduct());
匿名内部类通过继承父类来实现:
1 abstract class Device{ 2 private String name; 3 public abstract double getPrice(); 4 public Device(){} 5 public Device(String name){ 6 this.name = name; 7 } 8 9 public String getName(){ 10 return name; 11 } 12 } 13 14 public class AnonymousInner{ 15 public void test(Device d){ 16 System.out.println("购买了一个" + d.getName() + ",花掉了" + d.getPrice()); 17 } 18 19 public static void main(String[] args){ 20 AnonymousInner ai = new AnonymousInner(); 21 //调用有参数的构造器创建Device匿名实现类的对象 22 ai.test(new Device("电子示波器"){ 23 public double getPrice(){ 24 return 67.8; 25 } 26 }); 27 //调用无参数的构造器创建Device匿名实现类的对象 28 Device d = new Device(){ 29 //初始化块 30 { 31 System.out.println("匿名内部类的初始化块..."); 32 } 33 //实现抽象方法 34 public double getPrice(){ 35 return 56.2; 36 } 37 //重写父类的实例方法 38 public String getName(){ 39 return "键盘"; 40 } 41 }; 42 ai.test(d); 43 } 44 }
当创建匿名内部类时,必须实现接口或抽象父类里的所有抽象方法。若有需要也可以重写父类中的普通方法,如上面代码中的getName。
Java8之前,要求被局部内部类、匿名内部类访问的局部变量必须使用final修饰,从Java 8开始该限制被取消,但是若局部变量被匿名类访问,那·么该局部变量相当于自
动使用了final修饰。
Java8 新增的Lambda表达式:
Lambda表达式:
1 interface Eatable{ 2 void taste(); 3 } 4 interface Flyable{ 5 void fly(String weather); 6 } 7 interface Addable{ 8 int add(int a, int b); 9 } 10 11 public class LambdaQs{ 12 //调用该方法需要Eatable对象 13 public void eat(Eatable e){ 14 System.out.println(e); 15 e.taste(); 16 } 17 18 //调用该方法需要Flyable对象 19 public void drive(Flyable f){ 20 System.out.println("我正在驾驶" + f); 21 f.fly("【碧空如洗的晴日】"); 22 } 23 24 //调用该方法需要Addable对象 25 public void test(Addable add){ 26 System.out.println("5与3的和为:" + add.add(5, 3)); 27 } 28 29 public static void main(String[] args){ 30 LambdaQs lq = new LambdaQs(); 31 //Lambda表达式的diamante块只有一条语句,可以省略花括号 32 lq.eat(()->System.out.println("苹果的味道不错!")); 33 //Lambda表达式的形参列表只有一个形参,可以省略圆括号 34 lq.drive(weather -> { 35 System.out.println("今天天气是:" + weather); 36 System.out.println("直升机飞行平稳"); 37 }); 38 //Lambda表达式的代码块只有一条语句,可以省略花括号 39 //代码块中只有一条语句,即使该表达式需要返回值,也可以省略return关键字 40 lq.test((a, b) -> a + b); 41 } 42 }
从上面语法格式可以看出,Lambda表达式主要作用就是代替匿名内部类的繁琐语法,它由三部分组成:
1.形参列表:形参列表允许省略形参类型,若形参列表中只有一个参数,甚至连形参列表中的圆括号也可以省略。
2.箭头(->):必须通过英文中划线和大于符号组成
3.代码块:若代码块中只包含一条语句,Lambda表达式允许省略代码块的花括号。Lambda代码块只有一条return语句,甚至可以省略return关键字。Lambda表达式
需要返回值,而它的代码块中仅有一条省略了return的语句,Lambda表达式会自动返回这条语句的值。代码块返回一个目标对象。
Lambda表达式与函数式接口:
Lambda表达式的类型,也被称为“目标类型(target type)”,Lambda表达式的目标类型必须是“函数式接口(functional interface)”。
函数式接口:只包含一个抽象方法的接口。函数式接口可以包含多个默认方法,类方法,但只能声明一个抽象方法。
Java 8专门提供了@FunctionalInterface注解,用于告诉编译器——检查该接口必须是函数式接口,否则编译器会报错。
Lambda表达式的两个限制:
1.Lambda表达式的目标类型必须是明确的函数式接口
2.Lambda表达式只能为函数式接口创建对象。Lambda表达式只能实现一个方法,因此只能为只有一个抽象方法的接口(函数式接口)创建对象。
为了保证Lambda表达式的目标类型是一个明确的函数式接口,有如下三种方式:
1.将Lambda表达式赋值给函数式接口类型的变量
2.将Lambda表达式作为函数式接口类型的参数传给某个方法中的形参
3.使用函数式接口对Lambda表达式进行强制类型转换
需要说明的是,同样的Lambda表达式的目标类型完全可能是变化的——唯一的要求是:Lambda表达式实现的匿名方法与目标类型(函数式接口)中唯一的抽象方法有相同的
形参列表。
Java8 在 java.util.function包下定义了大量的函数式接口,典型有以下4类接口:
1.XxxFunction
2.XxxConsumer
3.XxxxPredicate
4.XxxSupplier
枚举类:
Java 5新增了一个关键字enum(与class、interface关键字地位相同),用来定义枚举类。
枚举类是特殊的类,可以有自己的成员变量、方法、可有一个或多个接口,可有自己的构造器。
枚举类和普通类的区别:
1.枚举类可实现一个或多个接口,使用enum定义的枚举类默认继承java.lang.Enum类,而不是Object类,因此枚举类不能显示继承其他父类。其中java.lang.Enum类实现
了java.lang.Serializable和java.lang.Comparable两个接口
2.使用enum定义、非抽象的枚举类默认使用final修饰,因此枚举类不能派生子类。
3.枚举类的构造器只能使用private修饰,若省略了控制器的访问控制符,则默认使用private修饰。
4.枚举类的所有实例必须在枚举类的第一行显式列出,否则该枚举类永远不能产生实例。列出的这些实例系统会自动添加public static final修饰。
枚举类默认提供values()方法,可很方便的遍历所有的枚举值。
1 public class EnumTest{ 2 public void judge(SeasonEnum s){ 3 //switch语句里的表达式可以是枚举值 4 switch(s){ 5 case SPRING: 6 System.out.println("春暖花开,正好踏青"); 7 break; 8 case SUMMER: 9 System.out.println("夏日炎炎,适合游泳"); 10 break; 11 case FALL: 12 System.out.println("秋高气爽,进步及时"); 13 break; 14 case WINTER: 15 System.out.println("冬日雪飘,围炉赏雪"); 16 break; 17 } 18 } 19 20 public static void main(String[] args){ 21 //枚举类默认有一个values()方法,返回该枚举类的所有实例 22 for(SeasonEnum s : SeasonEnum.values()){ 23 System.out.println(s); 24 } 25 //使用枚举实例时,可通过EnumClass.variable形式来访问 26 new EnumTest().judge(SeasonEnum.SPRING); 27 } 28 }
java.lang.Enum类中的方法:
1.int compareTo(E o):比较顺序,同一个枚举实例只能与相同类型的枚举实例进行比较。若该枚举对象位于指定枚举对象之后,返回正整数;若该枚举对象位于指定枚举对象
之前,则返回负整数,否则返回零
2.String name():返回此实例的名称。
3.int ordinal():返回枚举值在枚举类中的索引值(就是枚举值在枚举声明中的位置,第一个枚举值的索引值为零)
4.String toString():返回枚举常量的名称,与name()方法相似,但toString()方法更常用。
5.public static<T extends Enum<T>> T valueOf(Class<T> enumType, String name):这是一个静态方法,用于返回指定枚举类中指定名称的枚举值。名称必须与在该枚举类中
声明枚举值时所用的标识符完全匹配,不允许使用额外的空白字符。
枚举类的成员变量、方法和构造器:
1 public enum Gender{ 2 MALE,FEMALE; 3 //定义一个public修饰的实例变量 4 public String name; 5 }
1 public class GenderTest{ 2 public static void main(String[] args){ 3 //通过Enum的valueOf()方法来获取指定枚举类的枚举值 4 Gender g = Enum.valueOf(Gender.class, "FEMALE"); 5 //直接为枚举值的name实例变量赋值 6 g.name = "女"; 7 //直接访问枚举值的name实例变量 8 System.out.println(g + "代表:" + g.name ); 9 } 10 }
1 public enum Gender{ 2 MALE,FEMALE; 3 //定义一个public修饰的实例变量 4 private String name; 5 6 public void setName(String name){ 7 switch(this){ 8 case MALE: 9 if(name.equals("男")){ 10 this.name = name; 11 }else{ 12 System.out.println("参数错误"); 13 return; 14 } 15 break; 16 17 case FEMALE: 18 if(name.equals("女")){ 19 this.name = name; 20 }else{ 21 System.out.println("参数错误"); 22 return; 23 } 24 break; 25 } 26 } 27 28 public String getName(){ 29 return this.name; 30 } 31 }
1 public class GenderTest{ 2 public static void main(String[] args){ 3 //通过Enum的valueOf()方法来获取指定枚举类的枚举值 4 Gender g = Gender.valueOf("FEMALE"); 5 //直接为枚举值的name实例变量赋值 6 g.setName("女"); 7 //直接访问枚举值的name实例变量 8 System.out.println(g + "代表:" + g.getName()); 9 //此时设置name值时会提示参数错误 10 g.setName("男"); 11 System.out.println(g + "代表:" + g.getName()); 12 } 13 }
1 public enum Gender{ 2 //此处的枚举值必须调用对应的构造器来创建 3 MALE("男"),FEMALE("女"); 4 //定义一个public修饰的实例变量 5 private final String name; 6 //枚举类的构造器只能使用private修饰 7 private Gender(String name){ 8 this.name = name; 9 } 10 11 public String getName(){ 12 return this.name; 13 } 14 }
1 public class GenderTest{ 2 public static void main(String[] args){ 3 //通过Enum的valueOf()方法来获取指定枚举类的枚举值 4 Gender nv = Gender.valueOf("FEMALE"); 5 System.out.println(nv + "代表:" + nv.getName()); 6 7 Gender nan = Gender.valueOf("MALE"); 8 System.out.println(nan + "代表:" + nan.getName()); 9 } 10 }
最后一次的代码设计才合理,这样将枚举类设计成不可变类。
实现接口的枚举类:
1 public enum Gender implements GenderDesc{ 2 //此处的枚举值必须调用对应的构造器来创建 3 MALE("男"),FEMALE("女"); 4 //定义一个public修饰的实例变量 5 private final String name; 6 //枚举类的构造器只能使用private修饰 7 private Gender(String name){ 8 this.name = name; 9 } 10 11 public String getName(){ 12 return this.name; 13 } 14 15 //增加下面的info()方法,实现GenderDesc接口必须实现的方法 16 17 public void info(){ 18 System.out.println("这是一个用于定义性别的枚举类"); 19 } 20 }
1 public interface GenderDesc{ 2 void info(); 3 }
1 public class GenderTest{ 2 public static void main(String[] args){ 3 //通过Enum的valueOf()方法来获取指定枚举类的枚举值 4 Gender nv = Gender.valueOf("FEMALE"); 5 System.out.println(nv + "代表:" + nv.getName()); 6 nv.info(); 7 8 Gender nan = Gender.valueOf("MALE"); 9 System.out.println(nan + "代表:" + nan.getName()); 10 nan.info(); 11 } 12 }
这样每个枚举值在调用info()方法都有相同的行为方式,若想要不同的枚举值在调用该方法呈现不同的行为方式:
1 public enum Gender implements GenderDesc{ 2 //此处的枚举值必须调用对应的构造器来创建 3 MALE("男") 4 //花括号部分实际上是一个类体部分 5 { 6 public void info(){ 7 System.out.println("这个枚举值代表男性"); 8 } 9 },//这里有个逗号 10 FEMALE("女") 11 { 12 public void info(){ 13 System.out.println("这个枚举值代表女性"); 14 } 15 };//这里有个分号 16 //定义一个public修饰的实例变量 17 private final String name; 18 //枚举类的构造器只能使用private修饰 19 private Gender(String name){ 20 this.name = name; 21 } 22 23 public String getName(){ 24 return this.name; 25 } 26 }
1 public interface GenderDesc{ 2 void info(); 3 }
1 public class GenderTest{ 2 public static void main(String[] args){ 3 //通过Enum的valueOf()方法来获取指定枚举类的枚举值 4 Gender nv = Gender.valueOf("FEMALE"); 5 System.out.println(nv + "代表:" + nv.getName()); 6 nv.info(); 7 8 Gender nan = Gender.valueOf("MALE"); 9 System.out.println(nan + "代表:" + nan.getName()); 10 nan.info(); 11 } 12 }
上面代码中MALE和FEMALE两个枚举值后紧跟一对花括号,这两个枚举值时Gender枚举类的两个匿名子类实例。因为在编译的时候会生成Gender.class、Gender$1.class和
Gender$2.class三个文件。证明,MALE和FEMALE实际上使Gender匿名子类的实例,而不是Gender类的实例。
包含抽象方法的枚举类:
1 public enum Operation{ 2 PLUS{ 3 public double eval(double x, double y){ 4 return x + y; 5 } 6 },//这里有逗号 7 8 MINUS{ 9 public double eval(double x, double y){ 10 return x - y; 11 } 12 },//这里有逗号 13 14 TIMES{ 15 public double eval(double x, double y){ 16 return x * y; 17 } 18 },//这里有逗号 19 20 DIVIDE{ 21 public double eval(double x, double y){ 22 return x / y; 23 } 24 }; 25 26 //为枚举类定义一个抽象方法 27 //这个抽象方法由不同的枚举值提供不同的实现 28 public abstract double eval(double x, double y); 29 public static void main(String[] args){ 30 System.out.println(Operation.PLUS.eval(3, 4)); 31 System.out.println(Operation.MINUS.eval(3, 4)); 32 System.out.println(Operation.TIMES.eval(3, 4)); 33 System.out.println(Operation.DIVIDE.eval(3, 4)); 34 } 35 }
枚举类定义抽象方法时不能使用abstract关键字将枚举类定义成抽象类(因为系统自动会为它添加abstract关键字),但因为枚举类需要显示创建枚举值,而不是作为父类,所以定义每个枚举值时必须为抽象方法提供实现,否则将出现编译错误。
对象与垃圾回收:
在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。
强制垃圾回收:
1.调用System类的gc()静态方法:System.gc()
2.调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc()
1 public class GcTest{ 2 public static void main(String[] args){ 3 for(int i = 0; i < 4; i++){ 4 new GcTest(); 5 //下面两行diamante的作用完全相同,强制系统进行垃圾回收 6 //System.gc(); 7 Runtime.getRuntime().gc(); 8 } 9 } 10 11 public void finalize(){ 12 System.out.println("系统正在清理GcTest对象的资源..."); 13 } 14 }
finalize()方法:Java提供的默认机制清理对象资源方法
该方法定义在Object类中的实例方法,方法原型:
protected void finalize() throws Throwable//Throwable可抛出任何异常
finalize()方法四个特点:
1.永远不要主动调用某个对象的finalize()方法,该方法应交给垃圾回收机制调用
2.finalize()方法何时被调用,是否被调用具有不确定性,不要把finalize()方法当成一定会被执行的方法
3.当JVM执行可恢复对象的finalize()方法时,可能使该对象或系统中其他对象重新变成可达状态
4.当JVM执行finalize()方法时出现异常时,垃圾回收机制不会报告异常,程序继续执行。
GitHub网站代码链接:https://github.com/lanshanxiao/-Java-/tree/master/%E7%AC%AC%E5%85%AD%E7%AB%A0