对象的特性
- 类 == 数据结构;常见的数据结构就是语言已经实现的可供调用的类。
- 程序员就是扩展编程语言,为解决问题定义一些新的数据结构。
UML
表示类
UML(Unified Model Language),统一建模语言。
一个方框分三部分,上部分类名,中间属性,最下方方法。
实心黑色菱形表示组合
空心三角形表示继承
接口
接口就是一个标准。
权限控制
权限控制的出现背景:厘清类库开发者和类库使用者之间代码权限。
- public,类库开发者专门为使用者准备的 API。
- private 表明,除了类库开发者创建的内部方法,其他任何人都不可以调用。
- protectd,子类可以访问 父类的 protected 方法/属性。
- friendly,也称为包访问权限,同一个包中可以随便访问。
前期绑定 / 后期绑定
- 前期绑定:编译器将产生对一个具体函数名字的调用,而运行时将这个调用解析到将要被执行的代码的绝对地址;
- 后期绑定:当向对象发送消息时,被调用的代码直到运行时才能确定。编译器确保被调用的方法存在,并且对调用参数和返回值执行类型检查。在 C++ 中,使用 virtual 关键字来声明后期绑定。
向上转型(upcasting)/向下转型(downcasting)
向上转型
向上,来源于类图中父类型与字类型的位置;
转型,模具塑模时的动作。
在以父类为参数的地方,传入子类的实例同样合法。
public class Father{}
public class Sub extends Father{}
public void test(Father f){}
Sub s = new Sub();
test(s);
向下转型
把 Obejct 对象转化为某种子类型,这个操作是有风险的,因此要考虑异常。
例子:如声明一个数组List<Object>
,可以向里面放入Integer / String 等类型。但是元素放进去以后就会丢失身份,再取出来的元素全部都是 Object,使用的时候还需要把 Object 转化为 Integer / String 等类型。
单根继承
单根指所有的类都继承自 Object。
好处:
- 使垃圾回收变得简单
- 确保某些功能所有的对象都具备
容器
ArrayList / LinkedList
ArrayList 访问一个元素的时间固定,LinkedList 插入元素用时更少。
泛型
用一对尖括号,里面是类型信息,这就是泛型。
堆(heap)/生命周期
C++ 是在栈中创建对象,在栈中创建存储空间和释放存储空间通常各需要一条汇编指令即可。编译器可以确定对象的存活时间。
Java 在堆(heap)也被称为内存池中动态地创建对象。在这种方式中,直到运行时才知道需要多少对象,它们的生命周期如何,以及它们具体的类型。存储空间在运行时被动态管理的,所以需要大量的时间在堆中分配存储空间,这可能远远大于在栈中创建存储空间的时间。编译器不知道对象的生命周期。所以 Java 需要垃圾回收机制。
对象存储的位置
- 寄存器,最快的存储区,它位于处理器(CPU)内部,但是寄存器的数量极其有限,程序员不能控制。
- 栈(stack),位于通用RAM(随机访问存储器),但是通过堆栈指针可以从处理器那里获得直接支持。堆栈指针向下移动,则分配新的内存;上移,则释放内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时,Java 系统必须知道存储在栈内所有项的确切生命周期,以便上下移动指针。这点限制了程序的灵活性,所以 Java 把对象的引用放在栈中,但是对象不放在栈中。
- 堆。一种通用的内存池(也位于RAM区),用于存放所有 Java 对象。堆不同于栈的好处在于:编译器不需要知道存储的数据在堆里的存活时间。因此,在堆里分配内存有很大的灵活性,当需要一个对象时,只需要 new 写一行简单的代码,当执行这行代码时,会自动在堆里进行存储分配。坏处就是:用堆进行存储分配和清理比用栈进行存储分配需要更多时间。
- 常量存储。常量直接存放在程序代码内部。
- 非 RAM 存储。流对象和持久化对象。对象转化为字节流,发送给另外的机器;持久化对象可以存放到数据库里。
存储的特例——基本类型
8种基本类型,boolean / char / byte/ int / short / long / float / double /void, 它们都存储在栈中。
static
- 普通方法必须和实例联系起来才能运行。实例是类的具象,所以具有类的种种特征,所以实例能调用静态方法。
- 静态方法和实例无关,而普通方法必须和实例关联,所以静态方法和普通方法无关,也就是说静态方法不能调用普通方法。
- 静态方法只需要一份存储空间。
main
public static void main(String[] args)
args 用来存储命令行参数。
赋值 / 别名引用
对基本类型赋值是复制,对对象赋值是引用。
基本类型直接在栈上创建,开销小,编译器知道生命周期,所以赋值都是复制。
a = 1111
b = 2333
a = b
a = 7777
print(b) #2333
对象在堆上创建,赋值给变量名的只是一个句柄,指向内存池中的对象。所以 a = b, 是把指向内存池中对象地址也指向了b,所以如果发生了改变,都会改变
class Tank:
level = 0
class Assignment:
# t1 / t2 都是一个句柄而已,指向的都是内存池中的实例化的对象。
t1 = Tank() //指向实例 t1
t2 = Tank() // 指向实例 t2
t1.level = 2555
t2.level = 2777
t1 = t2 // 这个操作含义是,t1 指向 (t2 指向在内存池中的对象)
print(t1, t2)
以上操作对于 Python / Java 来说都一样,在编程思想中认为以上对于属性的操作实在让人懵逼,所以推荐使用修饰符。
自动递增 / 递减
i++ /++i 的区别
i++ / ++i 对于 i 的影响是相同的,i 都会加1.
但是对于下面的 front 和 end 来说是有区别的:
Integer m = 0;
Integer n = 0;
Integer front = null;
Integer end = null;
Integer counter = 0;
while(counter < 5){
front = m++;
end = ++n;
System.out.println("front "+front+" end "+end);
counter++;
}
执行结果发现,front 最终为4,end 最终结果为5;
这说明 m++ 对于 front 来说是先赋值后加,++n 对于 end 来说是先加后赋值。
我记这个,是通过哪个离等号近来判断的,m离等号近说明是先赋值再加,+离等号近说明是先加再赋值。
另外 ++n 在多线程环境下会造成异常,因为 ++n 不是原子性操作。
== / equals
- 八大基本类型(char,byte,boolean,float,double,int,long,short)由于直接在栈上创建,它们没有引用,所以直接用
==
判断基本类型是否相等即可。 - a == b,对于非基本类型的a、b而言,a 和 b 需要是同一个对象的实例并且值相等(也就是同一个内存地址),才会返回 true
- equals,类库的大都重写了 equals 方法,使 equals 只是作为比较对象值是否相等的工具。
- 但是如果新建一个类,不重写
equals
方法,使用父类 object 的equals
方法的话,那就是引用了,必须两个变量指向的内存地址相等才是相等。
public class Inc{
public int i;
}
Inc a = new Inc();
Inc b = new Inc();
a.i = b.i = 100;
System.out.println( a == b); /false
System.out.println(a.equals(b)); //false
逻辑运算的短路
我经常碰到下面的这种情况,逻辑运算中的两个条件是有联系的,比如 result
有可能是null, 如果是 null 的话,result + 3
会直接抛异常了。
@Test
public void autoIncTest() {
Integer result = foo(0);
if (result == null || result + 3 >7){
System.out.println("ok");
return;
}
}
public Integer foo(Integer arg){
if(arg == 0){
return null;
} else {
return 3;
}
}
以前我没理解逻辑短路之前,代码不敢写成上面这种,而是这么实现:
@Test
public void autoIncTest() {
Integer result = foo(0);
if (result == null){
System.out.println("ok");
return;
}
if ( result + 3 >7){
System.out.println("ok");
return;
}
}
学了逻辑短路之后踏实了,因为一旦能够明确无误地确定整个表达式的值,就不再计算表达式余下的部分了。因此整个逻辑表达式靠后部分有可能不计算!这句话真是福音 + 定心丸!
public boolean test1(){
System.out.println("i am test1");
return true;
}
public boolean test2(){
System.out.println("i am test2");
return false;
}
public boolean test3(){
System.out.println("i am test3");
return true;
}
@Test
public void testBoolean(){
// boolean bool = test1() && test2() && test3();
boolean bool = test1() || test2() || test3();
System.out.println(bool);
}
这个短路的例子也表明 Java 不会对所有的表达式求知,一旦算出了结果,便会立即返回。