4.Java对象方法、包装类与接口

Java 对象方法、包装类与接口
对象已经会创建和使用了。接下来看看怎样打印和比较对象,再认识包装类,最后用工厂换设备的例子理解接口。
1. Object 与 toString()
Object 是普通 Java 类的直接或间接父类。toString() 用来把对象表示成文字。
直接打印对象时,希望看到它的编号和名称,而不是一串不直观的默认文字。
在自己的类里重写 toString()。
java
public class Product {
private int id;
private String name;
public Product(int id, String name) {
this.id = id;
this.name = name;
}
// 打印对象时,使用这个方法返回的文字。
@Override
public String toString() {
return "Product{id=" + id + ", name='" + name + "'}";
}
}
接着调用:
java
Product product = new Product(1, "手机");
System.out.println(product); // Product{id=1, name='手机'}
println 会使用对象的 toString()。默认格式中的 @ 后面是哈希码的十六进制形式,不是 Java 保证的内存地址。
2. ==、equals()、hashCode()
对对象来说,== 判断是不是同一个对象;equals() 判断是否按某种规则相等;hashCode() 提供一个供哈希集合使用的整数。
比如两次拿到的商品名称都是“苹果”。它们可以是两个不同的字符串对象,但文字内容相同。
先用已经实现好内容比较的 String 看清这三个方法,不急着自己写比较规则:
java
// 特意创建两个对象,观察“同一个对象”和“内容相同”的区别。
String first = new String("苹果");
String second = new String("苹果");
System.out.println(first == second); // false,不是同一个对象
System.out.println(first.equals(second)); // true,内容相同
System.out.println(first.hashCode() == second.hashCode()); // true
这里特意用 new 创建两个对象,好让“同一个对象”和“内容相同”区别开来。
必须记住:相等的对象,哈希码必须相同;哈希码相同,对象不一定相等。
如果不重写,Object.equals() 默认也是判断是否同一个对象。字符串内容比较通常使用 equals()。
补充:自己的商品类怎样比较
前面的字符串已经有自己的比较规则。我们定义商品类时,也可以约定“编号相同就相等”。这时一起重写 equals() 和 hashCode():
java
public final class Product {
private final int id;
public Product(int id) {
this.id = id;
}
@Override
public boolean equals(Object object) {
// 先确认是商品对象,null 或其他类型都不能按商品编号比较。
if (!(object instanceof Product)) {
return false;
}
// 这个例子约定:编号相同,就认为是相等的商品。
return id == ((Product) object).id;
}
@Override
public int hashCode() {
// 哈希码也按编号计算,保证相等商品的哈希码相同。
return Integer.hashCode(id);
}
}
接着调用:
java
Product first = new Product(1);
Product second = new Product(1);
System.out.println(first == second); // false
System.out.println(first.equals(second)); // true
System.out.println(first.hashCode() == second.hashCode()); // true
3. 包装类、装箱和拆箱
包装类让基本类型的值以对象形式使用。基本类型转成包装对象叫装箱,反过来叫拆箱。
集合等 API 使用对象类型,例如 List<Integer>,不能写成 List<int>。
Java 可以自动完成常见的装箱、拆箱。
| 基本类型 | 包装类 |
|---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
char |
Character |
boolean |
Boolean |
java
Integer object = 10; // 装箱
int number = object; // 拆箱
System.out.println(number); // 10
Integer first = 200;
Integer second = 200;
// 比较包装对象的数值,不依赖 == 的缓存行为。
System.out.println(first.equals(second)); // true
包装对象比较数值时用 equals(),不要依赖 ==。部分值会被缓存,这容易让 == 的结果看起来时对时错。
包装对象可以是 null,但 null 不能拆成一个基本类型值。拆箱前要确认它不为空。
4. 字符串与数字转换
把文字形式的数字转成数值,或者把数值转成文字。
用户输入、配置文件中拿到的经常是字符串,计算前要先转换。
整数用 Integer.parseInt(),小数用 Double.parseDouble()。
java
// 字符串不能直接参与年龄计算,先转成整数。
int age = Integer.parseInt("18");
double price = Double.parseDouble("19.9");
String text = String.valueOf(age);
System.out.println(age + 1); // 19
System.out.println(price); // 19.9
System.out.println(text); // 18
"abc" 不能转成整数,会抛出 NumberFormatException。这种情况可以用异常机制处理。
5. 接口与面向接口编程
接口规定“能够做什么”,实现类负责“具体怎样做”。
还是看工厂换设备的例子。原来工厂只生产产品 A,用的是 EquipmentA;后来接到产品 B 的订单,要换成 EquipmentB。如果工厂的字段写死为 EquipmentA,换设备就得修改工厂代码。
我们想保留工厂的生产流程,只更换设备。做法是:两种设备实现同一个接口,工厂通过这个接口调用设备。
第一步,定义设备接口。这里的 work() 表示“设备可以工作”:
java
public interface Equipment {
// 接口只规定设备能够工作,具体生产什么由设备类决定。
void work();
}
第二步,让设备 A 和设备 B 分别实现它:
java
public class EquipmentA implements Equipment {
@Override
public void work() {
System.out.println("设备A运行,生产产品A");
}
}
java
public class EquipmentB implements Equipment {
@Override
public void work() {
System.out.println("设备B运行,生产产品B");
}
}
第三步,工厂的字段使用 Equipment,通过 setEquipment() 安装或更换设备:
java
public class Factory {
// 字段使用接口类型,既能放设备 A,也能放设备 B。
private Equipment equipment;
public void setEquipment(Equipment equipment) {
this.equipment = equipment;
}
public void work() {
System.out.println("开始生产.....");
// 调用前先安装设备,实际执行当前设备自己的 work()。
equipment.work();
}
}
现在让同一个工厂先用设备 A,再换成设备 B:
java
Factory factory = new Factory();
factory.setEquipment(new EquipmentA());
factory.work();
// 同一个工厂换设备,Factory.work() 的代码不用改。
factory.setEquipment(new EquipmentB());
factory.work();
输出:
text
开始生产.....
设备A运行,生产产品A
开始生产.....
设备B运行,生产产品B
Factory.work() 一直是同一段代码,实际工作的设备却可以更换。这就是“面向接口编程”:工厂只要求设备会 work(),不把具体设备类型写死。
本例使用前要先装好设备,也就是先调用 setEquipment();否则字段还是 null,无法调用它的 work()。接口本身不能直接 new,要创建它的实现类。一个类可以实现多个接口,接口也可以继承其他接口。
补充:以后再看
equals()要满足与自身相等、相互比较对称等规则;不要让结果随着无关操作随意改变。- 放入
HashSet或作为HashMap的键后,不要修改参与相等、哈希计算的字段。 Objects.equals(a, b)可以安全比较可能为空的对象。- 接口字段隐式是
public static final;普通抽象方法隐式是public abstract。 - Java 8 起接口可有默认方法、静态方法;Java 9 起可有私有辅助方法。
native表示方法实现由本地代码提供,不需要把它用于普通 Java 学习示例。- 只有一个抽象方法的接口可以学习 Lambda 写法,但先理解上面的普通实现类。

评论