abstract和interface两个关键字分别用于定义抽象类和接口,抽象类和接口都是从多个子类中抽象出来的共同特征。
一.Java8增强的包装类
所有引用类型的变量都继承了Object类,都可当成Object类型变量使用。但基本数据类型的变量就不可以,如果有个方法需要Object类型的参数,但实际需要的值却是2,3等数值,此时Java提供了包装类的概念。
public class AutoBoxingUnboxing
{
public static void main(String[] args)
{
// 直接把一个基本类型变量赋给Integer对象
Integer inObj = 5;
// 直接把一个boolean类型变量赋给一个Object类型的变量
Object boolObj = true;
// 直接把一个Integer对象赋给int类型的变量
int it = inObj;
if (boolObj instanceof Boolean)
{
// 先把Object对象强制类型转换为Boolean类型,再赋给boolean变量
boolean b = (Boolean)boolObj;
System.out.println(b);
}
}
}
开发者可以把基本类型的变量近似当成对象使用(所有装箱和拆箱过程都由系统自动完成,无须程序员理会)。
此外包装类还可以实现基本类型变量和字符串之间的转换,把字符串类型的值转换为基本类型的值有两种方式:
1.利用包装类提供的parseXXX(String s)静态方法;
2.利用包装类提供的Xxx(String s)构造器;
public class Primitive2String
{
//包装类海可以实现基本类型变量和字符串之间的转换
//把字符串类型的值转换为基本类型
//1.方法一:
//利用包装类提供的parsexxx(String)
//2.方法二:
//利用包装类提供的xxx(String)构造器
public static void main(String[] args)
{
String intStr = "123";
// 把一个特定字符串转换成int变量
int it1 = Integer.parseInt(intStr);
int it2 = new Integer(intStr);
System.out.println(it2);
String floatStr = "4.56";
// 把一个特定字符串转换成float变量
float ft1 = Float.parseFloat(floatStr);
float ft2 = new Float(floatStr);
System.out.println(ft2);
// 把一个float变量转换成String变量
String ftStr = String.valueOf(2.345f);
System.out.println(ftStr);
// 把一个double变量转换成String变量
String dbStr = String.valueOf(3.344);
System.out.println(dbStr);
// 把一个boolean变量转换成String变量
String boolStr = String.valueOf(true);
System.out.println(boolStr.toUpperCase());
}
}
结果:
123
4.56
2.345
3.344
TRUE
Java8为整型包装类增加了支持无符号运算的方法。
二.处理对象
Java对象都是Object类的实例,都可直接调用该类中定义的方法。这些方法提供了处理Java对象的通用方法。
1.“==”和equal方法
对于两个引用类型变量,只有它们指向同一个对象时,判断才会返回true,不可用于比较类型上没有父子关系的两个对象。
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());
}
}
“hello”直接量和new String(“hello”)有什么区别呢?
当Java程序直接使用形如“hello”的字符串直接量(包括可以在编译时就计算出来的字符串值)时,JVM将会使用常量池来管理这些字符串;
当使用new String(“hello”)时,JVM会先使用常量池来管理“hello”直接量,再调用String类的构造器来创建一个新的String对象,新创建的Strign对象被保存在堆内存中,换句话,new String(“hello”)一共产生了两个字符串对象。
public class StringCompareTest
{
public static void main(String[] args)
{
// s1直接引用常量池中的"疯狂Java"
String s1 = "疯狂Java";
String s2 = "疯狂";
String s3 = "Java";
// s4后面的字符串值可以在编译时就确定下来
// s4直接引用常量池中的"疯狂Java"
String s4 = "疯狂" + "Java";
// s5后面的字符串值可以在编译时就确定下来
// s5直接引用常量池中的"疯狂Java"
String s5 = "疯" + "狂" + "Java";
// s6后面的字符串值不能在编译时就确定下来,
// 不能引用常量池中的字符串
String s6 = s2 + s3;
// 使用new调用构造器将会创建一个新的String对象,
// s7引用堆内存中新创建的String对象
String s7 = new String("疯狂Java");
System.out.println(s1 == s4); // 输出true
System.out.println(s1 == s5); // 输出true
System.out.println(s1 == s6); // 输出false
System.out.println(s1 == s7); // 输出false
}
}
equals()方法是Object类提供的一个实例方法,因此所有引用变量都可调用该方法来判断是否与其他引用变量相等。但使用这个方法判断两个对象相等的标准与使用==运算符没有区别,同样要求两个引用变量指向同一个对象才会返回true。如果希望采用自定义的相等标准,则可以采用重写equals方法来实现。
tips:String已经重写了Object的equals方法,String的equals方法判断两个字符串相等的标准是:只要两个字符串所包含的字符序列相同,通过equals比较将返回true。否则将返回false。
class Person
{
private String name;
private String idStr;
public Person(){}
public Person(String name , String idStr)
{
this.name = name;
this.idStr = idStr;
}
// 此处省略name和idStr的setter和getter方法。
// name的setter和getter方法
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return this.name;
}
// idStr的setter和getter方法
public void setIdStr(String idStr)
{
this.idStr = idStr;
}
public String getIdStr()
{
return this.idStr;
}
// 重写equals()方法,提供自定义的相等标准
public boolean equals(Object obj)
{
// 如果两个对象为同一个对象
if (this == obj)
return true;
// 只有当obj是Person对象
if (obj != null && obj.getClass() == Person.class)
{
Person personObj = (Person)obj;
// 并且当前对象的idStr与obj对象的idStr相等才可判断两个对象相等
if (this.getIdStr().equals(personObj.getIdStr()))
{
return true;
}
}
return false;
}
}
public class OverrideEqualsRight
{
public static void main(String[] args)
{
Person p1 = new Person("孙悟空" , "12343433433");
Person p2 = new Person("孙行者" , "12343433433");
Person p3 = new Person("孙悟饭" , "99933433");
// p1和p2的idStr相等,所以输出true
System.out.println("p1和p2是否相等?"
+ p1.equals(p2));
// p2和p3的idStr不相等,所以输出false
System.out.println("p2和p3是否相等?"
+ p2.equals(p3));
}
}
三.抽象类
1.抽象方法和抽象类
1.1 抽象方法不能有方法体;
1.2 抽象类不能被实例化,无法使用new关键字来调用抽象类的构造器创建抽象类的实例。即使抽象类里不包含抽象方法,这个抽象类也不能创建实例。
1.3 抽象类可以包含成员变量,方法(普通方法和抽象方法都可以),构造器,初始化块,内部类(接口,枚举)5种成分。抽象类的构造器不能用于创建实例,主要用于被其子类调用。
1.4 含有抽象方法的类(包括直接定义了一个抽象方法;或继承了一个抽象父类,但没有完全实现父类包含的抽象方法;或实现了一个接口,但没有完全实现接口包含的抽象方法三种情况)只能被定义成抽象类。
public abstract class Shape
{
{
System.out.println("执行Shape的初始化块...");
}
private String color;
// 定义一个计算周长的抽象方法
public abstract double calPerimeter();
// 定义一个返回形状的抽象方法
public abstract String getType();
// 定义Shape的构造器,该构造器并不是用于创建Shape对象,
// 而是用于被子类调用
public Shape(){}
public Shape(String color)
{
System.out.println("执行Shape的构造器...");
this.color = color;
}
// 省略color的setter和getter方法
public void setColor(String color)
{
this.color = color;
}
public String getColor()
{
return this.color;
}
}
抽象类不能用于创建实例,只能当作父类被其他子类继承。Shape类里既包含了初始化块,也包含了构造器,这些都不是在创建Shape对象时被调用的,而是在创建其子类的实例时被调用。
public class Triangle extends Shape
{
// 定义三角形的三边
private double a;
private double b;
private double c;
public Triangle() {}
public Triangle(String color , double a
, double b , double c)
{
super(color);
this.setSides(a , b , c);
}
public void setSides(double a , double b , double c)
{
if (a >= b + c || b >= a + c || c >= a + b)
{
System.out.println("三角形两边之和必须大于第三边");
return;
}
this.a = a;
this.b = b;
this.c = c;
}
// 重写Shape类的的计算周长的抽象方法
public double calPerimeter()
{
return a + b + c;
}
// 重写Shape类的的返回形状的抽象方法
public String getType()
{
return "三角形";
}
}
public class Circle extends Shape
{
private double radius;
public Circle(String color , double radius)
{
super(color);
this.radius = radius;
}
public void setRadius(double radius)
{
this.radius = radius;
}
// 重写Shape类的的计算周长的抽象方法
public double calPerimeter()
{
return 2 * Math.PI * radius;
}
// 重写Shape类的的返回形状的抽象方法
public String getType()
{
return getColor() + "圆形";
}
public static void main(String[] args)
{
Shape s1 = new Triangle("黑色" , 3 , 4, 5);
Shape s2 = new Circle("黄色" , 3);
System.out.println(s1.getType());
System.out.println(s1.calPerimeter());
System.out.println(s2.getType());
System.out.println(s2.calPerimeter());
}
}
结果:
执行Shape的初始化块...
执行Shape的构造器...
执行Shape的初始化块...
执行Shape的构造器...
三角形
12.0
黄色圆形
18.84955592153876
当使用abstract修饰类时,表明这个类只能被继承;当使用abstract修饰方法时,表明这个方法必须由子类提供实现(即重写)。而final修饰的类不能被继承,final修饰的方法不能被重写。因此final和abstract永远不能同时使用。
使用static修饰一个方法时,表明这个方法属于该类本身,即通过类就可调用该方法,但如果该方法被定义成抽象方法,则将导致通过该类来调用该方法时出现错误(调用了一个没有方法体的方法肯定会引起错误)。因此static和abstract不能同时修饰某个方法,即没有所谓的类抽象方法。但他们可以同时修饰内部类。
abstract修饰的方法必须被子类重写才有意义,因此abstract方法不能定义为private访问权限,即private和abstract不能同时修饰方法。
二.接口
1. interface,接口里不能包含普通方法,接口里的所有方法都是抽象方法。
1.1 一个接口可以有多个直接父接口,但接口只能继承接口,不能继承类。
1.2 接口里不能包含构造器和初始化块定义,接口里可以包含成员变量(只能是静态常量),方法(只能是抽象实例方法,类方法或默认方法),内部类(包括内部接口,枚举)定义。
1.3 在接口中定义成员变量时,不管是否使用public static final修饰符,接口里的成员变量总是使用这三个修饰符来修饰。而且接口里没有构造器和初始化块,因此接口里定义的成员变量只能在定义时指定默认值。
package lee;
public interface Output
{
// 接口里定义的成员变量只能是常量
int MAX_CACHE_LINE = 50;
// 接口里定义的普通方法只能是public的抽象方法
void out();
void getData(String msg);
// 在接口中定义默认方法,需要使用default修饰
default void print(String... msgs)
{
for (String msg : msgs)
{
System.out.println(msg);
}
}
// 在接口中定义默认方法,需要使用default修饰
default void test()
{
System.out.println("默认的test()方法");
}
// 在接口中定义类方法,需要使用static修饰
static String staticTest()
{
return "接口里的类方法";
}
}
接口里的成员变量默认是使用public static final修饰的,因此即使另一个类处于不同包下,也可以通过接口来访问接口里的成员变量。
package yeeku;
import lee.Output;
public class OutputTest implements Output {
@Override
public void out() {
System.out.println("OutputTest out");
}
@Override
public void getData(String msg) {
System.out.println("OutputTest getData: " + msg);
}
}
package yeeku;
public class OutputFieldTest
{
public static void main(String[] args)
{
// 访问另一个包中的Output接口的MAX_CACHE_LINE
System.out.println(lee.Output.MAX_CACHE_LINE);
// 下面语句将引起"为final变量赋值"的编译异常
// lee.Output.MAX_CACHE_LINE = 20;
// 使用接口来调用类方法
System.out.println(lee.Output.staticTest());
OutputTest outputtest = new OutputTest();
outputtest.out();
outputtest.getData("hello world");
outputtest.test();
}
}
结果:
50
接口里的类方法
OutputTest out
OutputTest getData: hello world
默认的test()方法
2.接口的继承
接口的继承和类继承不一样,接口完全支持多继承,即一个接口可以有多个直接父接口,和类继承相似,子接口扩展某个父接口,将会获得父接口里定义的所有抽象方法,常量。
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);
}
}
3.接口的使用
一个类可以实现一个或多个接口,继承使用extends关键字,实现则使用implements关键字。因为一个类可以实现多个接口。
一个类可以继承一个父类,并同时实现多个接口,implements部分必须放在extends部分之后。
类实现接口的语法格式如下:
[修饰符] class 类名 extends 父类 implements 接口1,接口2...
{
类体部分
}
一个类实现了一个或多个接口之后,这个类必须完全实现这些接口里所定义的全部抽象方法(也就是重写这些抽象方法);否则,该类将保留从父接口那里继承到的抽象方法。
把实现接口理解为一种特殊的继承,相当于实现类继承了一个彻底抽象的类(相当于除了默认方法外,所有方法都是抽象方法的类).
import lee.Output;
// 定义一个Product接口
interface Product
{
int getProduceTime();
}
// 让Printer类实现Output和Product接口
public class Printer implements Output , Product
{
private String[] printData
= new String[MAX_CACHE_LINE];
// 用以记录当前需打印的作业数
private int dataNum = 0;
public void out()
{
// 只要还有作业,继续打印
while(dataNum > 0)
{
System.out.println("打印机打印:" + printData[0]);
// 把作业队列整体前移一位,并将剩下的作业数减1
System.arraycopy(printData , 1
, printData, 0, --dataNum);
}
}
public void getData(String msg)
{
if (dataNum >= MAX_CACHE_LINE)
{
System.out.println("输出队列已满,添加失败");
}
else
{
// 把打印数据添加到队列里,已保存数据的数量加1。
printData[dataNum++] = msg;
}
}
public int getProduceTime()
{
return 45;
}
public static void main(String[] args)
{
// 创建一个Printer对象,当成Output使用
Output o = new Printer();
o.getData("轻量级Java EE企业应用实战");
o.getData("疯狂Java讲义");
o.out();
o.getData("疯狂Android讲义");
o.getData("疯狂Ajax讲义");
o.out();
// 调用Output接口中定义的默认方法
o.print("孙悟空" , "猪八戒" , "白骨精");
o.test();
// 创建一个Printer对象,当成Product使用
Product p = new Printer();
System.out.println(p.getProduceTime());
// 所有接口类型的引用变量都可直接赋给Object类型的变量
Object obj = p;
}
}
结果:
打印机打印:轻量级Java EE企业应用实战
打印机打印:疯狂Java讲义
打印机打印:疯狂Android讲义
打印机打印:疯狂Ajax讲义
孙悟空
猪八戒
白骨精
默认的test()方法
45
以上程序可以看出,Printer类实现了Ooutput接口和Product接口,因此Printer对象既可直接赋给Output变量,也可直接赋给Product变量。仿佛Printer类既是Output类的子类,也是Product类的子类,这就是Java提供的模拟多继承。
四.接口与抽象类
从某种程度上来看,接口类似于整个系统的总纲,它制定了系统各模块应该遵循的标准,因此一个系统中的接口不应该经常改变。一旦接口被改变,对整个系统甚至其他系统的影响将是辐射式的,导致系统中大部分类都需要改写。
接口和抽象类的用法差异:
1.接口里只能包含抽象方法和默认方法,不能为普通方法提供方法实现;抽象类则完全可以包含普通方法。
2.接口里不能定义静态方法,抽象类里可以定义静态方法;
3.接口里只能定义静态常量,不能定义普通成员变量;抽象类两者都可以。
4.接口里不包含构造器;抽象类里可以包含构造器,抽象类里的构造器并不是用于创建对象,而是让其子类调用这些构造器来完成属于抽象类的初始化操作。
5.接口里不能包含初始化块,但抽象类则完全可以包含初始化块。
6.一个类最多只能有一个直接父类,包括抽象类;但一个类可以直接实现多个接口,通过实现多个接口可以弥补java单继承的不足。