概述
OOP-面向对象编程(Object Oriented Programming),在Java中(几乎)一切都是对象。
用引用操作对象
在Java中一切都是被看作为对象,因此可以采用单一固定的语法。
尽管一切都看做对象,但操作的标示符实际上仅仅是对象的一个“引用”(reference)。
如果想操作一个字符串,则可以创建一个String 引用:
String s ;
但是这里创建的仅仅是引用,而不是对象。因此如果要操作s,这会返回错误。这是因为s实际上并没有与任何对象关联.
错误如下所示:
因此一种安全的做法就是:创建一个引用的同时便进行初始化
String s = "abc";
必须由你创建所有对象
一旦创建了一个引用,就希望它能与一个新的对象相关联。
通常使用new操作符来实现这一目的。比如
String s = new String("abc");
存储到什么位置
程序运行时,对象是怎样放置安排的呢? 特别是内存是怎样分配的呢?
其实有五个不同的地方可以存储数据:
名称 | 说明 |
---|---|
寄存器 | 最快的存储区,位于存储器内部,但是寄存器的数量极其有限,按需分配。你不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。 |
堆栈 | 位于通用RAM(随机访问存储器)中,但通过堆栈指针可以从处理器那里获得直接支持。堆栈指针向下移动—>分配新的内存,向上移动—>释放内存。这种分配存储的方法效率仅次于寄存器。 一般来讲,引用存放在堆栈中,Java对象并不存储在其中。 |
堆 | 一种通用的内存池(也位于RAM中),用于存放所有的Java对象。堆不同于堆栈的好处是:编译器不需要知道存储的数据在堆里存活多长时间。 因此在堆里分配存储有很大的灵活性。这种灵活性必须付出相应的代价:比用堆栈进行分配存储需要更多的时间 |
常量存储 | 常量值通常存放在程序代码内部,永远不会被改变。 |
非RAM存储 | 数据完全存活于程序之外,比如 流对象 和 持久化对象。 |
特例:基本类型
在程序设计中常用到一系列类型,他们需要特殊对待,可以把他们想象成基本类型。
之所以特殊对待,是因为new将对象存储在“堆”中,故用new创建一个对象–特别是小的、简单的变量,往往不是很有效。
因此,Java采用了和C,C++相同的方法,也就是不用new创建,而是创建一个并非是引用的“自动“变量。 这个变量直接存储”值“,并置于堆栈中,因此更高效。
Java要确定每种基本类型所占存储控件的大小,它们的大小并不像其他语言那样随着机器硬件架构的变化而变化。
基本类型 | 大小 | 最小值 | 最大值 | 包装类型 |
---|---|---|---|---|
boolean | – | – | – | Boolean |
char | 16-bit | Unicode 0 | Unicode 2^16-1 | Character |
byte | 8-bits | -128 | +127 | Byte |
short | 16-bits | -2^15 | +2^15-1 | Short |
int | 32-bits | -2^31 | +2^31-1 | Integer |
long | 64-bits | -2^63 | +2^63-1 | Long |
float | 32-bits | IEEE754 | IEEE754 | Float |
double | 64-bits | IEEE754 | IEEE754 | Double |
void | – | – | – | Void |
基本类型具有的包装器类,使得可以在堆中创建一个非基本的对象,用来表示对应的基本类型。比如:
char c = ‘x‘;
Character ch = new Character(c);
或者:
Character ch = new Character(‘x‘);
Java SE5的自动包装功能自动的将基本类型转换为包装器类型
Character ch = ‘x‘;
并可以反向转换:
char c = ch;
高精度数字:
Java提供了2个高精度计算的类
BigInteger BigDecimal.
虽然他们大体上属于包装器类的范畴,但是却没有对应的基本类型。
- 这两个类都有自己的一系列方法,类似于我们针对主类型执行的操作,也就是说能用 int 或float 做的事情,用BigInteger和BigDecimal 一样可以做,只是必须换用方法调用,而不是使用运算符。此外由于牵涉更多,所以运算速度会慢一点总之我们牺牲了速度,但换来了精度。
- BigInteger支持任意精度的整数,也就是说我们可精确表示任意大小的整数值;同时在运算过程中不会丢失任何信息;
- 在BigInteger类中有所有的基本算术运算方法,如加、减、乘、除,以及可能会用到的位运算如或、异或、非、左移、右移等。
- 从数值上比较两个 BigDecimal 值时,应该使用 compareTo() 而不是 equals()。
- 要小心使用 BigDecimal(double) 构造函数, 因为如果不了解它,会在计算过程中产生舍入误差。请使用基于整数或 String 的构造函数。
- 由于 BigDecimal 对象是不可变的,这些方法中的每一个都会产生新的 BigDecimal 对象。因此,因为创建对象的开销,BigDecimal 不适合于大量的数学计算,但设计它的目的是用来精确地表示小数。如果您正在寻找一种能精确表示如货币量这样的数值,则 BigDecimal 可以很好地胜任该任务。
永远不需要销毁对象
作用域
作用域由花括号的位置决定。
{
// x available
int x = 12 ;
System.out.println("x:" + x );
{
// Both x & y available
int y = 9 ;
System.out.println("x:" + x + ",y:" + y);
}
// only x available
System.out.println("x:" + x );
}
在作用域中定义的变量只可用于作用域结束之前。
对象的作用域
Java对象不具备和基本类型一样的生命周期。
当使用new创建一个java对象时,它可以存活于作用域之外。
比如
{
String s = new String("sss");
}// end of scope
引用s在作用域终点就消失了,但是s指向的对象任然占据这内存空间。
我们无法在这个作用域之后访问这个对象,因为对它唯一的引用已经超出了作用域的范围。
事实证明,由new创建的对象,只要你需要就会一直保留下去。
那java如何防止这些对象填满内存空间,进而阻塞你的程序呢?
Java中有一个垃圾回收器,用来监视用new创建的所有对象,并辨别那些不会被再引用的对象。随后,释放这些对象占用的内存空间,以便提供给其他新的对象使用。
创建新的数据类型:类
如果一切都是对象,那什么决定了某一类对象的外观和行为呢? 换句话说 什么确定了对象的类型?
在Java中使用class这个关键字
class ATypeName {
// class body gose here
}
实例化
ATypeName a = new ATypeName();
字段和方法
一旦定义了一个类,就可以在类中设置两种类型的元素:字段+方法。
字段可以是任何类型的对象,可以通过起引用与其进行通信;也可以是基本类型中的一种。
如果字段是某个对象的引用,那么必须初始化该引用,以便使其与一个实际的对象相关联。
每个对象都有用来存储其字段的空间; 普通字段不能在对象间共享。
class DataOnly{
int i ;
double d ;
boolean b ;
}
尽管这个类除了存储数据之外什么也不能做,但是依然可以创建它的一个对象:
DataOnly data = new DataOnly();
可以给字段赋值,但首先必须知道如何引用一个对象的成员:
objectReference.member
例如:
data.i = 10;
data.d = 1.1;
data.b = false;
基本成员默认值
若类的某个成员是基本类型数据,即使没有初始化,Java会确保它有一个默认值。
当变量作为类的成员变量使用时,Java才确保给定其默认值,以确保那些是基本类型的成员变量得到初始化,防止产生程序错误。
不过最好明确的对变量进行初始化。
上述确保初始化的方法并不适用于“局部变量”(即并非某个类的字段)。
如下:
在某个方法中定义
int x ;
这是不会被自动化初始为0 ,如果未明确的赋值,在编译时会抛出异常
方法、参数和返回值
Java的方法决定了一个对象能接收什么样的的消息。 参数列表给出了要传给方法的信息的类型和名称。 方法名和参数列表唯一的标识出某个方法。
Java中的方法只能作为类的一部分创建。 方法只能通过对象才能被调用(static方法是针对类调用的,并不依赖于对象的存在),且这个对象必须能执行这个方法的调用。
如下:
objectName.methodName(arg1,arg2,arg3);
参数列表
像java中任何传递对象的场合一样,这里传递的实际上也是引用(对于基本类型是一个类外。通常尽管传递的是对象,而实际上传递的对象的引用。)
并且引用的类型必须正确。
比如 参数类型为String ,则必须传递一个String对象,否则编译器抛出异常。
假设某个方法接收String为其参数,具体定义如下,该方法必须置于某个类的定义内才能被正确的编译。
int storage(String s){
return s.length();
}
通过上面的例子,我们还可以了解到return关键字的用法。
- 代表已经执行完毕,离开此方法
- 如果该方法有返回值,需要放到return后
boolean flag(){ return true;}
double naturalLogBase(){ return 2.879;}
void nothing(){return ;}
void nothing2(){}
若返回类型是void ,return关键字的作用只是用来退出方法。因此没有必要到方法结束时才离开,可以在任何地方离开。
但如果返回类型不是void ,无论在何处返回,编译器都会强制返回一个正确类型的返回值。
static关键字
通常来说,当创建一个类时,就是在描述那个类的对象的外观与行为。
除非用new创建那个类的对象,否则,实际上并没有获取到任何对象, 执行new 来创建对象时,数据存储空间才能被分配,其方法才能被外界调用。
有两种情况是以上方法无法解决的:
1. 只想为某特定域分配单一存储空间,而不考虑究竟要创建多少对象,甚至根本就不创建对象。
2. 第二种情况是:希望某个方法不与包含它的类的任何对象关联在一起,也就是说,即使没有创建对象,也能够调用该方法。
通过static关键字可以满足这两方面的需求。
当声明一个事物是static时,也就意味着这个域或者方法不会与包含它的那个类的任何对象实例关联子啊一起, 所以即使从未创建某个类的任何对象,也可以通过调用其static方法或者访问其static域。
通常你必须创建一个对象,并且用它来访问数据或者方法,因为非static域和方法必须知道他们一起运作的特定对象。
只需要将static关键字放在定义之前,就可以将字段或者方法设定为static
例如
class StaticTest{
static int i = 5;
}
即使我们创立了两个StaticTest对象,StaticTest.i也只有一份存储空间,这两个对象共享同一个i.
在这里 a.i和b.i都指向同一个存储空间。
引用static变量的两种方法:
- 可以通过一个对象去引用它,比如 上面中的 a.i
- 也可以通过类名直接引用,而这对于非静态成员则不行,比如
Demo21.i == Demo21.i
这种方式是首选方式,推荐使用这种方式。
类似逻辑同样也适用于 静态方法。