面向对象的特征有哪些方面?
参考解答
1. 抽象
抽象是简化复杂现实问题的途径,是面向对象编程的基础。
数据抽象 提取客观事物中所关注的属性,放置在类定义中,称为数据抽象。 例如,我们的系统中有“用户”这一概念,这时,就需要将我们关注的属性如:用户账号,用户姓名,登录密码,联系方式等提取出来,抽象为User类。而那些无关的属性,如长相、政治面貌等,则要在抽象的过程中舍弃。
行为抽象 在数据抽象的基础上,需要将对象的行为逻辑抽取至类定义中,这称为行为抽象。例如,用户需要登录检查、需要根据用户属性进行过滤查询,这都属于用户的行为,它们将作为方法定义在类中。
2. 封装(encapsulation)
封装的思想是对数据进行“信息隐藏”,将数据设为私有,不允许直接访问,而是通过公共方法间接访问,这样的好处是可以对数据的读写加以控制。
例1:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// 创建User对象,原始密码为123
User user = new User("张三", "123");
// 密码信息被隐藏了
System.out.println(user.getPassword());
// 输出202cb962ac59075b964b07152d234b70
// 验证密码是否正确
System.out.println(user.validate("456"));
// 输出false
System.out.println(user.validate("123"));
// 输出true
}
}
class User {
private String username;
private byte[] encryptPassword;
public User(String username, String password) {
this.username = username;
// 密码加密存储
this.encryptPassword = encrypt(password);
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
// 返回密码加密后的16进制格式
public String getPassword(){
StringBuilder sb = new StringBuilder(32);
for (int i = 0; i < this.encryptPassword.length; i++) {
String hex = Integer.toHexString(this.encryptPassword[i] & 0xFF);
if (hex.length() == 1) {
sb.append("0");
}
sb.append(hex);
}
return sb.toString();
}
// 加密,这里的private也体现了一种加密这种行为的“信息隐藏”
private byte[] encrypt(String password){
try {
MessageDigest md = MessageDigest.getInstance("md5");
return md.digest(password.getBytes());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
// 只能通过公共方法验证密码的正确性
public boolean validate(String password) {
return Arrays.equals(this.encryptPassword, encrypt(password));
}
}
封装还可以对数据结构进行“信息隐藏”,让使用者无须关心其内部实现。比如对于LinkedList 类,我们都知道通过其 add(Object o) 可以向list中添加对象,remove(Object o) 可以移除list中的对象,但其内部怎么实现的对使用者隐藏起来了。
例2:
public class LinkedList<E> extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
// 通过内部的Node类,来维护整个链表结构,但对于使用者,我们不需要关心它
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
...
}
3. 继承(inheritance)
继承的思想是避免数据及方法定义的重复、冗余。将通用的属性、方法定义放置在父类当中,在继承了这个父类的子类里就不必再次出现这些定义。
- java中仅能单继承
- java中的顶层父类是java.lang.Object
- 构造方法不能被继承
- private的属性和方法不能被继承
- 可以通过方法重写来覆盖掉继承的方法
- 不要滥用继承,只有在类与类是is-a关系时才考虑使用继承,如果是has-a关系时则要使用组合
4. 多态(polymorphism)
多态的思想是一个方法或一个类型,它们在使用时表现出不同的行为。
多态的好处是:
- 让代码变得通用、易扩展
- 用一个父类型来统一处理它的子类对象
- 或用一个接口类型来统一处理它的实现对象
- 面向父类型或接口编程,方便替换不同的子类或实现类
- java中实现多态的关键在于
向上转型
+方法重写
例1:
看起来都是在调用一个名为t1的方法,但由于给它的参数类型不同,导致了运行时的行为不同。这在多态中称为Ad hoc polymorphism
,发生在编译期间:
public static void t1(double a) {
System.out.println("double...");
}
public static void t1(int a) {
System.out.println("int ...");
}
public static void main(String[] args) {
t1(10.0);
t1(10);
}
例2:
通过泛型,达到了用一个方法来操作不同类型数组的目的。这在多态中称为Parametric polymorphism
,也是发生在编译期间:
public static <T> void print(T[] array) {
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
}
public static void main(String[] args) {
print(new Integer[] { 1, 2, 3 });
print(new String[] { "a", "b", "c" });
}
例3:
t方法中的 animal引用的类型为Animal,但根据它实际所引用的对象不同,运行时表现出的行为也不同。这在多态中称为Subtyping
,发生在运行期间:
public class Test20 {
public static void t(Animal animal) {
animal.talk();
}
public static void main(String[] args) {
t(new Cat()); // 输出 喵
t(new Dog()); // 输出 汪
}
}
abstract class Animal {
abstract void talk();
}
class Cat extends Animal {
@Override
void talk() {
System.out.println("喵");
}
}
class Dog extends Animal {
@Override
void talk() {
System.out.println("汪");
}
}