java语言学习之包装类,抽象类,接口

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单继承的不足。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值