面向对象与类
面向过程
通过设计过程(即算法)求解问题。算法是第一位的,数据结构是第二位的。适于解决小的问题,优化算法。
面向对象
把数据放在第一位,然后再去考虑操作数据的算法。面对规模较大的问题,只需要通过访问过这个数据的少量方法中锁定bug。
类 class
类是构造对象的模板。封装了数据和行为。由类 构造(construct)对象的过程 称为 创建类实例(instance)
封装 encapsulation
有时被称为数据隐藏,关键在于绝对不能让类中的方法直接地访问其他类的实例域。为的是使对象成为“黑盒”,提高重用性和可靠性。
类之间的关系
- 依赖 dependence(uses-a)—— 即耦合,A类的方法操纵B类的对象,则A依赖于B
- 聚合 aggregation(has-a)—— 即关联,A类的对象里包含着B类的对象,即B对象是A的成员
- 继承 inheritance(is-a)—— A类是B类的一种特例,A继承了B原有的东西,多了一些特殊的属性和方法
对象
对象的三个特性
- 行为(behavior)—— 方法、行为(从同一个类实例化出的不同对象,方法相同)
- 状态(state)—— 数据、实例域(可能不同)
- 对象标识(identity)—— 唯一性(一定不同)
对象与对象变量
只声明没有new,此时为对象变量。只有将构造器new出的对象赋给它,才是一个可多次使用的对象,即Object object = new Object(); 等号左边是对象变量,右边为实例对象
局部变量不会自动地初始化为null,必须通过new或者设为null进行初始化
编译器自动编译
键入命令:
javac A.java
如果A里面使用了B类
编译器会查找名为 B.class 的文件。如果没找到会自动搜索 B.java 自动编译。
如果 B.java 的版本比已有的 B.class 的版本新,还会对其重新编译。
构造器
构造器与类同名,没有返回值。作用是将实例域初始化为希望的状态。
所有Java对象都是在堆中构造的。构造器总是伴随着new操作符被调用。不能对一个已存在的对象调用构造器来重新设置实例域。
//正确用法
Employee jams = new Employee("James Bomd",100000,1950,1,1);
//错误用法,编译出错
james.Employee("James Bomd2",250000,1950,1,1);
get/set 访问器
即Java Bean的写法。维护了封装的优点
注意不要写返回引用可变对象的访问器方法,如 Date。
如果需要返回一个可变对象的引用,应该首先对它进行克隆。像这样写get
public Date getHireDay(){
return (Date)hireDay.clone();
}
这样调用get得到的就不是类变量(私有的)hireDay,而是它的克隆,一个新的内容一样的不同Date。此时对新Date的修改不会影响这个类的私有成员。
final 不可变实例域
必须确保在每个构造器执行时,final值被设置,并且对象构建后不会再被修改。
final大多用于基本类型(primitive)或 不可变类(immutable)域。
不可变类
所谓不可变类即实例创建一旦完成,就不能修改其成员变量值。所以线程安全,线程间可以共享。如:String;基本数据类型对应的包装类。
从 String 看不可变性如何做到
参考:openJDK8源码;JAVA不可变类(immutable)机制与String的不可变性;理解serialVersionUID;
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
//所谓的值。用来存储字符的数组
private final char value[];
//缓存字符串的HashCode
private int hash; // Default to 0
//反序列化时用来校验版本
private static final long serialVersionUID = -6849794470754667710L;
//序列化用的流
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
/** 新建新的同值对象,只是这个值多了个引用指向它
value[]本身不存在两份,以减少回收*/
public String(char value[]) {
this.value = Arrays.copyOf(value, value.length);
}
JAVA序列化
进程远程通信时,JAVA对象与字节序列转化的过程。
serialVersionUID:(继承了Serializable接口就需要定义)
根据类名、接口名、成员方法及属性等生成的64位哈希值。
用来验证版本一致,证明可以反序列化。
若字节流与本地的不相同,则InvalidCastException
深复制 / 浅复制
先试着解释一下,A复制出B
浅复制(shallow copy):即不会递归复制,对基本数据类型是值拷贝,对象只是引用拷贝
所以对B中的对象进行修改,还是会影响A。
深复制: 把A所引用的对象也都复制了一遍,这时B完全独立了
可以看ArrayList.clone()
中的解释
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
String的构造和ArrayList.clone()
都用到了Arrays.copyOf()
:
public static char[] copyOf(char[] original, int newLength) {
char[] copy = new char[newLength];
/** 这里的System.arraycopy()是native*/
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
如何实现深复制呢?可以借用序列化!
static 静态域(类域)
每个类中只有一个这样的域。而每个对象对于所有的实例域却都有自己的一份拷贝。
静态常量
public static final double PI = 3.14159265358979323846;
如果static被忽略,PI就变成了Math类的一个实例域。需要通过Math类的对象访问PI,并且每个对象都有它自己的一份PI拷贝。
静态方法
可由类名直接调用,是一种不能向对象实时操作的方法。可以认为静态方法是没有this的方法。不能访问实例域,但是可以访问静态域(类域)。
使用静态方法的情景
1. 所需参数都通过显式参数(参数列表)提供。方法不需要访问对象状态。
2. 方法只需要访问类的静态域。
3. 实现工厂方法
4. main方法
main方法
main不对任何对象进行操作,将执行并创建程序所需对象。
每个类都可以有一个main方法。以最外层的类的main优先,小的类中的main不会被执行,但是可善用于单元测试。
方法参数
方法得到的是所有参数值的一个拷贝,对普通变量,方法无法改变其值。但是将对象引用(对象变量)作为参数,则方法拷贝的也是引用,指向的是同一个对象,对其状态的修改自然也会真的改变这个对象。但是方法不能使对象参数引用一个新对象。因为方法只能修改拷贝来的引用的引用值,让它指向新对象,不能修改原来模板引用值。
对象构造
重载 Overload
多个方法有相同的名字,不同的参数列表。编译器按方法签名(方法名与参数列表)来匹配相应方法。不能有两个同名同参数但返回值不同的方法。
默认域初始化
如果没有自己实现构造器,编译器会自动添一个无参构造器,将没有显式赋初值的实例域在这里显式的设为默认值。但是如果自己写了含参构造器,那构造对象时就不能不传参数。此时,没有初值的实例域依然会被赋默认值,但编译器不会再补充无参的构造器了。
动态构造技巧
动态初始化实例域
class Employee{
private static int nextId;//默认初始化为0
private int id = assignId();//等待调用赋值
private static int assignId(){
int r = nextId;
nextId++;
return r;
}
}
调用另一个构造器
public Employee(double s){
//调用 Employee(String,double)
this("Employee #"+nextId,s);
nextId++;
}
Pakage 包
从编译器的角度来看,嵌套的包没有任何关系。例如,java.util与java.util.jar。每一个都拥有独立的类的集合。
import导入
导入包:需要注意只能导入一个包,不能java.*
或java.*.*
。此外,通过import staticjava.lang.System.lei
导入静态域/方法
类注释
JDK自带javadoc工具,可以由源文件生成HTML文档
网友评论