Java是通过类来构建代码结构的,类分为两类:一类是Java提供的,无需开发者自定义,可直接调用;另一种是由开发者根据不同的业务需求自定义的类。
Object类
Object类是Java提供的一个类,位于java.lang包中,该类是所有类的直接父类或间接父类。无论是Java提供的类还是开发者自定义的类,都是Object的直接子类或者间接子类。Object类是所有类的祖先。
Object类的源码如下
package java.lang;
public class Object {
private static native void registerNatives();
static {
registerNatives();
}
/**
* @return The {@code Class} object that represents the runtime
*/
public final native Class<?> getClass();
/**
* @return a hash code value for this object.
*/
public native int hashCode();
/**
* @param obj the reference object with which to compare.
* @return {@code true} if this object is the same as the obj argument; {@code false} otherwise.
*/
public boolean equals(Object obj) {
return (this == obj);
}
/**
* @return a clone of this instance.
* @throws CloneNotSupportedException if the object's class does not support the {@code Cloneable} interface. Subclasses that override the {@code clone} method can also throw this exception to indicate that an instance cannot be cloned.
*/
protected native Object clone() throws CloneNotSupportedException;
/**
* @return a string representation of the object.
*/
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
/**
* @throws IllegalMonitorStateException if the current thread is not the owner of this object's monitor.
*/
public final native void notify();
/**
* @throws IllegalMonitorStateException if the current thread is not the owner of this object's monitor.
*/
public final native void notifyAll();
/**
* @param timeout the maximum time to wait in milliseconds.
* @throws IllegalArgumentException if the value of timeout is negative.
* @throws IllegalMonitorStateException if the current thread is not the owner of the object's monitor.
* @throws InterruptedException if any thread interrupted the current thread before or while the current thread was waiting for a notification. The <i>interrupted status</i> of the current thread is cleared when this exception is thrown.
*/
public final native void wait(long timeout) throws InterruptedException;
/**
* @param timeout the maximum time to wait in milliseconds.
* @param nanos additional time, in nanoseconds range 0-999999.
* @throws IllegalArgumentException if the value of timeout is negative or the value of nanos is not in the range 0-999999.
* @throws IllegalMonitorStateException if the current thread is not the owner of this object's monitor.
* @throws InterruptedException if any thread interrupted the current thread before or while the current thread was waiting for a notification. The <i>interrupted status</i> of the current thread is cleared when this exception is thrown.
*/
public final void wait(long timeout, int nanos) throws InterruptedException {
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
/**
* @throws IllegalMonitorStateException if the current thread is not the owner of the object's monitor.
* @throws InterruptedException if any thread interrupted the current thread before or while the current thread was waiting for a notification. The <i>interrupted status</i> of the current thread is cleared when this exception is thrown.
*/
public final void wait() throws InterruptedException {
wait(0);
}
/**
* @throws Throwable the {@code Exception} raised by this method
*/
protected void finalize() throws Throwable { }
}
可以看到Object类中提供了许多方法,子类可以直接继承这些方法。
重写Object方法
对于继承Object类的子类来说,父类提供的方法不能满足自身的需求,所以可以通过重写父类的方法以完成自己的需求。这种方式也是多态的一体现,父类信息通过不同的子类呈现出不同的形态。
Object类中经常被子类重写的方法有:
- toString():以字符串的形式返回该类的实例化对象信息。
- equals():判断两个对象是否相等
- hashCode():返回对象的散列值
重写toString()方法
原生的toString()方法会返回对象的类名以及散列值,直接打印对象默认调用toString()方法。
public class Main {
public static void main(String[] args){
User user = new User();
user.getId(1);
user.getName("Tom");
user.getAge(18);
System.out.println(user.toString());
}
}
//输出
com.test.a.User@74a14482
使用Object定义的类,返回对象的内存地址,在实际开发中意义不大,可能希望返回对象的属性值,因此需要对toString()方法进行重写。
public class User{
...
private int id;
private String name;
private int age;
public String toString(){
return "用户id:" + id + ",用户名:" + name + ",用户年龄:" + age;
}
}
public class Main {
public static void main(String[] args){
User user = new User();
user.getId(1);
user.getName("Tom");
user.getAge(18);
System.out.println(user.toString());
}
}
// 输出
用户id:1,用户名:Tom,用户年龄:18
重写equals()方法
Object类中equals()方法的代码是public boolean equals(Object obj) { return (this == obj);},通过内存地址对两个对象进行判断,即两个对象的引用必须指向同一块内存地址才会认为他们相等。但在实际开发中,User user1 = new User("Tom"),User user2 = new User("Alice")。虽然 user1 和 user2 是完全不同的对象,但他们的值是一样的,一般认为它们是相等的,所以需要对equals()方法进行重写。
public class User{
...
public boolean equals(User user){
if (this == user){
return true;
}
if (this.name.equals(user.name)){
return true;
}
return false;
}
}
public class Main {
public static void main(String[] args){
User user = new User();
user.setName("Tom");
User user1 = new User();
user1.setName("Tom");
System.out.println(user.equals(user));
}
}
// 输出
true
重写hashCode()方法
hashCode()方法返回一个对象的散列值,这个值是由对象的内存地址结合对象内部信息计算而得,任何两个对象的内存地址是不一样的,所以这样比较的意义不大,如果两个对象的属性值是相同的,那么认为两个对象的散列值应该是一样的。
/**
* 未重写hashCode()方法
*/
public class Main {
public static void main(String[] args){
User user = new User();
user.setName("Tom");
User user1 = new User();
user1.setName("Tomi");
System.out.println(user.hashCode());
System.out.println(user1.hashCode());
}
}
// 输出
1956725890
356573597
/**
* 重写hashCode()方法
*/
public class User{
...
public int hashCode(){
return this.name.hashCode();
}
}
public class Main {
public static void main(String[] args){
User user = new User();
user.setName("Tom");
User user1 = new User();
user1.setName("Tom");
System.out.println(user.hashCode());
System.out.println(user1.hashCode());
}
}
// 输出
84274
84274
包装类
Java中的数据类型从本职上看可以分为两类,8种基本数据类型和引用类型。8种基本数据类型前面介绍过,引用类型简单理解就是通过构造函数new出来的对象就是引用类型。基本类型的数据不是对象,引用类型的数据才能称之为对象。
包装类是Java提供的一组类,专门用来创建8种基本数据类型对应的对象。因此包装类有8个,都保存在java.lang包中。
| 基本数据类型 | 包装类 |
|---|---|
| byte | Byte |
| short | Short |
| int | Integer |
| long | Long |
| float | Float |
| double | Double |
| char | Character |
| boolean | Boolean |
| 包装类的结构如下图 | |
![]() |
装箱与拆箱
装箱与拆箱是包装类的特有名词,装箱是指将基本数据类型转为对应的包装类对象;拆箱指将包装类对象转为对应的基本数据类型。
1、装箱
(1)public DataType(type value)
每个包装类都提供了一个有参构造函数,用来实例化包装类对象。
public class Main {
public static void main(String[] args){
byte b = 1;
Byte by = new Byte(b);
short s = 2;
Short aShort = new Short(s);
int i = 3;
Integer integer = new Integer(i);
long l = 4;
Long aLong = new Long(l);
float f = 5.5f;
Float aFloat = new Float(f);
double d = 6.6;
Double aDouble = new Double(d);
char c = 'a';
Character character = new Character(c);
boolean bl = false;
Boolean aBoolean = new Boolean(bl);
}
}
(2)public DataType(String value) / public DataType(char value)
每个包装类还提供一个重载构造函数,Character类的重载构造函数为:public DateType(char value),其他包装类的重载构造函数为:public DateType(String value)。
public class Main {
public static void main(String[] args){
Byte by = new Byte("1");
Short aShort = new Short("2");
Integer integer = new Integer("3");
Long aLong = new Long("4");
Float aFloat = new Float("5.5f");
Double aDouble = new Double("6.6");
Character character = new Character('c');
Boolean aBoolean = new Boolean("false");
}
}
(3)valueOf(dataType value)
每个包装类都有一个valueOf(dataType value)方法:public static DataType value(dataType value),可以将基本数据类型转为包装类类型。
public class Main {
public static void main(String[] args){
byte b = 1;
Byte by = Byte.valueOf(b);
short s = 2;
Short aShort = Short.valueOf(s);
int i = 3;
Integer integer = Integer.valueOf(i);
long l = 4;
Long aLong = Long.valueOf(l);
float f = 5.5f;
Float aFloat = Float.valueOf(f);
double d = 6.6;
Double aDouble = Double.valueOf(d);
char c = 'a';
Character character = Character.valueOf(c);
boolean bl = false;
Boolean aBoolean = Boolean.valueOf(bl);
}
}
(4)valueOf(String value) / valueOf(char value)
Character包装类有valueOf(char value) 方法:public static DataType value(char value),可以将char类型转为包装类类型。其他包装类的重载方法:public static DataType value(String value),可以将String类型转为包装类类型。
public class Main {
public static void main(String[] args){
Byte by = Byte.valueOf("1");
Short aShort = Short.valueOf("2");
Integer integer = Integer.valueOf("3");
Long aLong = Long.valueOf("4");
Float aFloat = Float.valueOf("5.5f");
Double aDouble = Double.valueOf("6.6");
Character character = Character.valueOf('c');
Boolean aBoolean = Boolean.valueOf("false");
}
}
2、拆箱
(1)*Value()
*表示包装类对应的基本数据类型的名称,例如Byte包装类就有byteValue()方法,其他包装类类似。通过该方法可以将包装类转为基本数据类型。
public class Main {
public static void main(String[] args){
Byte by = Byte.valueOf("1");
byte b = by.byteValue();
Short aShort = Short.valueOf("2");
short s = aShort.shortValue();
Integer integer = Integer.valueOf("3");
int i = integer.intValue();
Long aLong = Long.valueOf("4");
long l = aLong.longValue();
Float aFloat = Float.valueOf("5.5f");
float f = aFloat.floatValue();
Double aDouble = Double.valueOf("6.6");
double d = aDouble.doubleValue();
Character character = Character.valueOf('c');
char c = character.charValue();
Boolean aBoolean = Boolean.valueOf("false");
boolean bl = aBoolean.booleanValue();
}
}
(2)parse*()
除了Character类以外的其他包装类都有一个静态方法可以将字符串类型转为基本数据类型。
public class Main {
public static void main(String[] args){
byte b = Byte.parseByte("1");
short s = Short.parseShort("2");
int i = Integer.parseInt("3");
long l = Long.parseLong("4");
float f = Float.parseFloat("5.5f");
double d = Double.parseDouble("6.6");
boolean bl = Boolean.parseBoolean("false");
}
}
3、其他方法
每一个包装类都有一个静态方法 toString(dataType value),可以将基本数据类型转为 String 类型。
public class Main {
public static void main(String[] args){
byte b = 1;
String aByte = Byte.toString(b);
short s = 2;
String aShort = Short.toString(s);
int i = 3;
String integer = Integer.toString(i);
long l = 4;
String aLong = Long.toString(l);
float f = 5.5f;
String aFloat = Float.toString(f);
double d = 6.6;
String aDouble = Double.toString(d);
char c = 'a';
String character = Character.toString(c);
boolean bl = false;
String aBoolean = Boolean.toString(bl);
}
}
接口
接口是Java程序开发中很重要的一种思想,准确地讲不仅仅是Java编程,对于其他高级编程语言来说接口也是非常重要的,在实际开发中使用非常广泛。接口是由抽象类衍生出来的一个概念,并由此产生了一种编程方式:面向接口编程。面向接口编程不是一种思想,更准确地讲它应该是一种编程方式。面向接口编程就是将程序的业务逻辑进行分离,以接口的形式去对接不同的业务模块。接口只串联不实现,真正的业务逻辑实现交给接口的实现类来完成。当用户需求变更的时候,只需要切换不同的实现类,而不需要修改串联模块的接口,减少对系统的影响。
实际开发中应该尽量降低程序的耦合性,以提高程序的扩展性,便于维护。面向接口编程具备以下优点:
- 能够最大限度地解耦,降低程序地耦合性
- 使程序易于扩展
- 有利于程序的后期维护
接口的使用
接口在Java中是独立存在的一种结构,和类相似,需要先创建一个接口文件,Java中用class关键字来表示类,用interface来表示接口。
public interface 接口名{
public 返回值 方法名(参数列表);
}
接口与抽象类非常相似,同样是定义了没有实现的方法,只是一个抽象的概念,没有具体实现。接口其实就是一个极度抽象的抽象类。
一个类中一旦存在没有具体实现的抽象方法,那么该类就必须定义为抽象类,同时抽象类中是允许存在非抽象方法的。但是接口完全不同,接口中不能存在非抽象方法,必须全部是抽象方法。
接口是极度抽象的抽象类,因为接口中全部是抽象方法,所以修饰抽象方法的abstract可以省略,不需要添加在方法定义处。
接口中可以定义成员变量,但是有如下要求:
- 不能定义private和protected修饰的成员变量,只能定义public和默认访问权限修饰的成员变量
- 接口中的成员变量在定义时必须被初始化
- 接口中的成员变量都是静态常量,即可以直接通过接口访问,同时值不能被修改
public interface Interf{
public int ID = 1;
String NAME = "Tome";
public void test();
}
接口是不能被实例化的,它描述的是一个抽象的信息,抽象的信息是没有实例的,需要实例化的是接口的实现类。实现类就是对接口的抽象方法进行具体实现的,实现类本身就是一个普通的Java类
public class 实现类名 implements 接口名 {
public 返回值 方法名(参数列表){
}
}
通过关键字implements来指定实现类具体要实现的接口,在实现类的内部需要对接口的所有抽象方法进行实现,同时要求访问权限修饰符,返回值类型,方法名和参数列表必须完全一致。
public class Test implements Interf{
public void test(){
System.out.println("接口方法的实现");
}
}
接口和继承的对比:继承只能实现单继承,即一个子类只能继承一个父类;接口可以多实现,即一个实现类可以同时实现多个接口
面向接口编程的实际应用
面向接口编程是一种常用的编程方式,可以有效地提高代码的复用性,增强程序的扩展性和维护性。
现有如下例子,某工厂生成成品A,主要由设备A来完成生产,用程序模拟这一过程。分别创建Factory类和EquipmentA类,并将EquipmentA设置为Factory的成员变量,在Factory的业务方法中调用EquipmentA的方法来完成生产。
// 1、定义EquipmentA类
public class EquipmentA{
public void work(){
System.out.println("设备A运行,生产成品A");
}
}
// 2、定义Factory类
public class Factory{
private EquipmentA equipmentA;
// getter、setter方法
public void work(){
System.out.println("开始生产...");
this.equipmentA.work();
}
}
// 3、Test类生产成品
public class Test{
public static void main(String[] args){
EquipmentA equipmentA = new EquipmentA();
Factory factory = new Factory();
factory.setEquipmentA(equipmentA);
factory.work();
}
}
// 输出
开始生产...
设备A运行,生产成品A
现在工厂接了一份新订单,要求生产成品B,需要设备B来完成生产,用程序实现这一个过程,首先需要创建EquipmentB类,同时修改Factory内部的属性。
// 1、定义EquipmentB类
public class EquipmentB{
public void work(){
System.out.println("设备B运行,生产成品B");
}
}
// 2、定义Factory类
public class Factory{
private EquipmentB equipmentB;
// getter、setter方法
public void work(){
System.out.println("开始生产...");
this.equipmentB.work();
}
}
// 3、Test类生产成品
public class Test{
public static void main(String[] args){
EquipmentB equipmentB = new EquipmentB();
Factory factory = new Factory();
factory.setEquipmentB(equipmentB);
factory.work();
}
}
// 输出
开始生产...
设备B运行,生产成品B
这种方式需要修改Factory类的内部结构,如果此时需求又改回到生产成品A或者生产成品C,就需要创建新的类,同时再次修改Factory类的属性信息,这种当需求发生变更就要频繁修改类结构的方式是应该避免的。这种结构的程序扩展性非常差,如何改进呢?使用面向接口编程即可。将Equipment以接口的形式集成到Factory类中。
// 1、定义Equipment接口
public interface Equipment {
public void work;
}
// 2、定义Equipment接口的实现类EquipmentA和EquipmentB
public class EquipmentA implements Equipment{
public void work(){
System.out.println("设备A运行,生产成品A");
}
}
public class EquipmentB implements Equipment{
public void work(){
System.out.println("设备B运行,生产成品B");
}
}
// 3、修改Factory类,将Equipment接口设置为成员变量
public class Factory{
private Equipment equipment;
// getter、setter方法
public void work(){
System.out.println("开始生产...");
this.equipment.work();
}
}
// 4、Test类中生产成品A
public class Test{
public static void main(String[] args){
Equipment equipment = new EquipmentA();
Factory factory = new Factroy();
factory.setEquipment(equipment);
factory.work();
}
}
// 输出
开始生产...
设备A运行,生产成品A
// 5、生产成品B
public class Test{
public static void main(String[] args){
Equipment equipment = new EquipmentB();
Factory factory = new Factroy();
factory.setEquipment(equipment);
factory.work();
}
}
// 输出
开始生产...
设备B运行,生产成品B
异常
Java中的错误可以分为两类:一类是编译时错误,一般是指语法错误;另一类时运行时错误。语法错误在编写代码时,IDE集成开发环境会进行提示,所以一般能够避免。而运行时错误在编写代码的过程中和程序编译期间都难以发现,甚至可以正常编译通过,一旦运行就会报错。
Java中有一组类专门来描述各种不同的运势错误,叫做异常类。Java结合异常类提供了处理错误的机制,具体步骤就是当程序出现错误时,会创建一个包含错误信息的异常类的实例化对象。并将该对象提交给系统,由系统转交给能处理该异常的代码进行处理。
异常分为两类,包括Error和Exception,Error指系统错误,由Java虚拟机生成,我们编写的程序无法处理。Exception指程序运行期间出现的错误,我们编写的程序可以对其进行处理
异常的使用
异常的使用需要用到关键字try和catch,并且这两个关键字需要结合起来使用,用try来监听可能会抛出异常的代码,一旦捕获到异常,生成异常对象并交给catch来处理
try{
// 可能抛出异常
}catch(异常对象){
// 处理异常
}
public class Test{
public static void main(String[] args){
try{
int num = 10/0;
}catch (Exception e){
e.printStackTrace();
}
}
}
可以看到因为“int num = 10/0;”代码中将0作为除数,所以程序在执行时会产生错误并自动生成一个Exception对象,在catch代码块中捕获Exception对象并进行处理,将错误信息打印出来。如果我们将代码修改为“int num = 10/10;”再次运行,就不会看到异常信息了。因为此时没有发生错误,就不会产生Exception对象,catch 代码块不执行。以上代码是异常最基本的使用.
通常除了使用try和 catch关键字,我们还需要用到finally关键字,这个关键字的作用是:无论程序是否抛出异常,finally代码块中的程序都会执行。finally一般跟在catch 代码块后面,基本语法如下:
try{
// 可能抛出异常
}catch(异常对象){
// 处理异常
}finally{
// 必须执行的代码
}
public class Test{
public static void main(String[] args){
try{
int num = 10/0;
}catch (Exception e){
e.printStackTrace();
}finally{
System.out.println("执行finally中的代码!");
}
}
}
finally的重要特性
public class Main {
public static void main(String[] args){
System.out.println(test());
}
public static int test(){
try{
System.out.println("执行try中的代码");
return 1;
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println("执行finally中的代码");
return 2;
}
}
}
// 输出
执行try中的代码
执行finally中的代码
2
虽然try代码块中执行了return操作,但是finally代码块中的程序依然会执行,并且会覆盖try中return的结果,将finally中的结果返回给外部调用者,正是因为finally的特性,一般会在finally中进行资源的释放操作。
异常类
Java将运行时出现的错误全部封装成类,并且不是一个类,而是一组类。这些类之间是有层级关系的,由树状结构一层层向下分级,处在最顶端的类是Throwable,是所有异常类的根结点。Throwable有两个直接子类:Error和 Exception,Error表示系统错误,程序无法解决;Exception指程序运行时出现的错误,程序可以处理。Throwable、Error和 Exception都存放在java.lang包中。
Error常见的子类有VirtualMachineError、AWTError、IOError。VirtualMachineError的常见的子类有StackOverflowError 和 OutOfMemoryError,用来描述内存溢出等系统问题。VirtualMachineError、StackOverflowError和 OutOfMemoryError都存放在java.lang包中,AWTError存放在java.awt包中,IOError存放在java.io包中。
Exception常见的子类主要有IOException和 RuntimeException,IOException存放在java.io包中,RuntimeException存放在java.lang 包中。Exception类要重点关注,因为这部分异常是需要在编写代码的过程中手动进行处理。
IOException的常用子类有FileLockInterruptionException、FileNotFoundException和FilerException,这些异常通常都是处理通过IO流进行文件传输时发生的错误。FileLockInterruptionException存放在 java.nio.channels包中,FileNotFoundException存放在java.io包中,FilerException存放在javax.annotation.processing包中。
RuntimeException类及其常用子类(以下列举的)全部存放在java.lang 包中。ArithmeticException:表示数学运算异常;ClassNotFoundException:表示类为定义异常;IllegalArgumentException:表示参数格式错误异常;ArrayIndexOutOfBoundsException:表示数组下标越界异常;NullPointerException:表示空指针异常;NoSuchMethodError:表示方法未定义异常;NumberFormatException:表示将其他数据类型转为数值类型时的不匹配异常。
throw 和 throws
throw 和 throws 是Java在处理异常时使用的两个关键字,都用来抛出异常,但是使用的方式以及表示的含义完全不同。
- try-catch 是捕获可能抛出的异常
- throw 是确定会抛出异常
- throws 是作用于方法,用来描述该方法可能会抛出的异常
Java中有些异常在throw之后,还需要在方法定义处添加 throws声明,有些异常则不需要,直接throw即可。这是因为Exception的异常分checked exception 和 runtime exception,checked exception表示需要强制去处理的异常,即 throw 异常之后需要立即处理,要么自己try-catch,要么抛给上一层去处理,否则会报错,例如“Unhandledexception type Exception”。而runtime exception没有这个限制,throw之后可以不处理。
直接继承自Exception的类就是checked exception,继承自RuntimeException的类就是runtime exception。我们自定义的MyNumberExcpetion是直接继承Exception的,所以需要在add()方法定义处声明throws MyNumberExcpetion。
