深入理解JAVA语言

变量及其传递

Java数据类型划分

  • Java中的数据类型分为两大类,一类是基本数据类型;
  • 另一类是引用类型,后者相当于对象。

Java数据类型划分在Java-Part1中有详细介绍

Java数据类型划分

  • 注意:只有类、接口和数组是引用数据类型!
  • String不是引用数据类型,StringBuffer是引用数据类型!

从JDK1.4以后,引用、对象类型和原始类型基本通用。

类型 引用、对象类型 原始数据类型
整数 Integer int
单精度 Float float
双精度 Double double
布尔 Boolean boolean
字符 Character char
字节 Byte byte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//ch05.ref_val.ObjectAndPrimitive
package ch05.ref_val;
public class ObjectAndPrimitive {
public ObjectAndPrimitive() {
// TODO Auto-generated constructor stub
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Integer i1 = new Integer(0);
int i2 = i1;
String s="abc";
Object o=s;
((String)o).length();
test(i1);
// Integer可以用的地方,int也可以
test(678);
// 但是Integer的方法,int是不能直接用的
i1.hashCode();
// 下面这个是不可以的
((Integer) i2).hashCode();
Boolean b1 = Boolean.FALSE;
boolean b2 = b1;
}
public static void test(Integer i) {
}
}

基本类型变量与引用型变量

  • 基本类型:其值直接存于变量中;
  • 引用型的变量除占据一定的内存空间外,它所引用的对象实体(由new创建)也要占据一定空间;
  • 引用型变量保存的实际上是对象在内存的地址,也称为对象的句柄;
  • ch05.ref_val.MyDate体会m/n什么时候一起改变,什么时候不一起改变。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
//ch05.ref_val.MyDate
package ch05.ref_val;
/**
* 本例子演示了两个对象指向的是同一个地址,而不是简单的值的复制
*/
public class MyDate {
private int day = 12;
private int month = 6;
private int year = 1900;
public MyDate(int y, int m, int d) {
year = y;
month = m;
day = d;
}
void addYear() {

year++;
}
@Override
public Object clone() {
// TODO Auto-generated method stub
return new MyDate(this.year, this.month, this.day);
}
public void display() {
System.out.println(year + "-" + month + "-" + day);
}
public static void main(String[] args) {
// MyDate m, n;
// m = new MyDate(2020, 3, 2);// m指向了一个对象
// // n = (MyDate)m.clone();// n和m指向了同一个对象
// n=m;
// n.addYear();// m n同时改变
// m.display();
// n.display();// m n都变成了2021

// n = new MyDate(2019, 3, 2);// n指向了一个新对象
// m.display();
// n.display();// m n 具有不同的值
MyDate m = new MyDate(2020, 3, 6);
modifyDate(m);
m.display();
}

public static void modifyDate(MyDate m) {
m=new MyDate(2020, 3, 6);
m.addYear();
}
}

m/n

  • 调用对象方法时,要传递参数。在传递参数时,Java是值传递,即在调用一个方法时,是将表达式的值复制给形式参数;
  • 对于引用型变量,传递的值是引用值(可以理解为内存地址);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//ch05.ref_val.TransByValue.java
package ch05.ref_val;
/**
* 本例子演示了什么样的情况下传递的是地址,什么情况下是值的复制
*/
public class TransByValue {
private static int a;
public static void main(String[] args) {
int a = 0;
modify( a);
// System.out.println(a);// 输出0
int[] b = new int[1];
modify(b);
System.out.println(b[0]);
}
/**
* 值的复制
*/
public static void modify(int a) {
// 原始类型 传递值
// a++;
a = a + 1;
}
/**
* 传递的是地址
*/
public static void modify(int[] c) {
// 对象类型 传递地址
c[0]++;
c = new int[5];
}
}
  • Java中的参数都是按值传递的,但对于引用型变量,传递的值是引用值,所以方法中对数据的操作可以改变对象的属性;
  • 但是不能简单地认为函数如果传递的参数是一个对象,就可以在函数内部修改这个参数,例如:ch05.ref_val.TestValue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//ch05.ref_val.TestValue
package ch05.ref_val;

/**
* 函数中如果传递的是对象,未必一定可以在函数内部修改这个参数
* @author Administrator
*
*/
public class TestValue {

public TestValue() {
// TODO Auto-generated constructor stub
}

public static void main(String[] args) {
String str="a";
modify(str);
System.out.println(str);

StringBuffer buf=new StringBuffer("a");
modify(buf);
System.out.println(buf.toString());
}

/**
* s能被修改么?
* @param s
*/
public static void modify(String s) {

s=s+"1";
}
/**
* buf能被修改么
* @param buf
*/
public static void modify(StringBuffer buf) {

buf.append("1");
}
}

实例变量与局部变量

  • 从语法角度看
    • 实例变量属于类或接口,public,private,static,final修饰;
    • 而局部变量是在方法中定义的变量或方法的参变量;
    • 都可用final修饰,但局部变量则不能够被访问控制符及static修饰。
  • 从存储角度看
    • 从变量在内存中的存储方式来看,实例变量是对象的一部分,而对象是存在于堆中的,局部变量是存在于栈中;
    • 实例变量的生命周期与局部变量的声明周期不同,通常前者较长;
    • 另外,实例变量可以自动赋初值,局部变量则须显式赋值。局部变量必须显式赋值后才能够使用。
1
2
3
4
5
6
7
8
9
10
11
//ch05.LocalVarAndMemberVar
package ch05;
public class LocalVarAndMemberVar
{
int a;
void m(){
int b;
System.out.println(a); // a的值为0
System.out.println(b); //编译不能通过
}
}

变量的返回

方法的返回:

  • 返回基本类型;
  • 返回引用类型,他就可以存取对象实体。
1
2
3
4
5
6
Object getNewObject()
{
Object obj=new Object();
return obj;
}
//调用时: Object p=getNewObject();

多态

  • 子类对象可以当作父类对象,对于重载的方法,Java运行时根据实际的类型调用正确的方法,就叫"多态型性";
  • 所有的非final/static/private方法都可以实现多态;
  • 多态能够使对象所编写的程序,不用做修改就可以适应于其所有的子类。如在调用方法时,程序会正确地调用子对象的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//ch05.intergate.IntegrationDemo
package ch05.intergate;
/**
* 本例子演示了接口的作用
*/
public class IntegrationDemo {
public IntegrationDemo() {
// TODO Auto-generated constructor stub
}
/**
* 计算积分的一个函数
* @param fun
* @param a
* @param b
* @param step
* @return
*/
public double integrateAB(Integrable fun,double a,double b,double step) {
double sum=0;
while(a<b) {
sum=sum+step*fun.getValue(a);
a=a+step;
}
return sum;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
IntegrationDemo demo=new IntegrationDemo();
Integrable myFun=new MyFunction();
//Integrable myFun=new MyFun2();
double sum=demo.integrateAB(myFun,0,1,0.0005);
System.out.println(sum);
}
}
  • 多态的特点大大提高了程序的抽象程度和简洁性,最大限度地降低了类和程序模块之间的耦合性,提高了类模块的封闭性,使得它们不需了解对方的具体细节,就可以很好地共同工作。这个有点对于程序的设计、开发和维护都有很大的好处。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
//多态的例子
//ch05.virtual.testVirtualInvoke.java
//important!
package ch05.virtual;
class Shape {
void draw() {
System.out.println("Shape Drawing");
}
static void stDraw() {
System.out.println("static Shape Drawing");
}
}
class Circle extends Shape {
void draw() {
System.out.println("Draw Circle");
}
static void stDraw() {
System.out.println("static Shape Drawing");
}
}
class Triangle extends Shape {
void draw() {
System.out.println("Draw Triangle");
}
static void stDraw() {
System.out.println("static Draw Triangle");
}
}
class Line extends Shape {
void draw() {
System.out.println("Draw Line");
}
static void stDraw() {
System.out.println("static Draw Line");
}
}
public class TestVirtualInvoke {
static void doStuff(Shape s) {
s.draw();
}
public static void main(String[] args) {
Circle c = new Circle();
Triangle t = new Triangle();
Line l = new Line();
c.draw();
t.draw();
l.draw();
Shape c2=new Circle();
c2.draw();
c2.stDraw();
System.out.println(" c2 instancof Shape="+(c2 instanceof Shape));
c2=new Triangle();
c2.draw();
c2.stDraw();
System.out.println(" c2 instancof Triangle="+(c2 instanceof Triangle));
c2=new Line();
c2.draw();
c2.stDraw();
System.out.println(" c2 instancof Line="+(c2 instanceof Line));
}
}

Class类

getClass()

  • 对象可以通过getClass()方法来获得运行时的信息;
  • getClass()是java.lang.Object的方法,而Object是所有类的父类。所以任何对象都可以用getClass()方法;
  • getClass()方法得到对象的运行时的类信息,即一个Class类的对象,它的getFeilds()及getMethods()方法能进一步获得其详细信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//ch05.RunTimeClassInfo.java
package ch05;
import java.lang.reflect.*;
class RunTimeClassInfo
{
public static void main(String[] args)
{
Object obj = new Integer(1);
Class cls = obj.getClass();
System.out.println( "类名:" + cls.getName() );
Field [] fields = cls.getFields();
for( int i=0; i<fields.length; i++ ){
Field f = fields[i];
System.out.println( "域:" + f.getName() + ":" + f );
}
Method [] methods = cls.getMethods();
for( int i=0; i<methods.length; i++ ){
Method m = methods[i];
System.out.println( "方法:" + m.getName() + ":" + m );
}
}
}

instanceof

instanceof一个对象是否是某个类/接口(或子类,或实现了这个接口)

用法:对象名 instanceof 类名

3

对象构造与初始化

对象的构造与初始化

  • 调用本类或父类的构造方法;
  • this调用本类的其他构造方法;
  • super调用直接父类的构造方法;
  • 如果没有this及super,则编译器自动加上super(),即调用直接父类不带参数的构造方法;
  • this或super要放在第一条语句,且只能够有一条。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//ch05.seq.ConstructCallThisAndSuper
package ch05.seq;
class ConstructCallThisAndSuper
{
public static void main(String[] args){
PersonThisAndSuper p = new Graduate();
}
}
class PersonThisAndSuper
{
String name;
int age;
PersonThisAndSuper(){}
PersonThisAndSuper( String name, int age ){
this.name=name; this.age=age;
System.out.println("In Person(String,int)");
}
}
class StudentThisAndSuper extends PersonThisAndSuper
{
String school;
StudentThisAndSuper(){
this( null, 0, null );
System.out.println("In Student()");
}
StudentThisAndSuper( String name, int age, String school ){
super( name, age );
this.school = school;
System.out.println("In Student(String,int,String)");
}
}
class Graduate extends StudentThisAndSuper
{
Graduate(){
System.out.println("In Graduate()");
}
}
  • 在构造函数中使用this和super;
  • 在构造方法中调用this及super或自动加入的super,最终保证了任何一个构造方法都要调用父类的构造方法,而父类的构造方法又会再调用其父类的构造方法,直到最顶层的Object类。这是符合面向对象的概念的,因为必须令所有父类的构造方法都得到调用,否则整个对象的构建就可能不正确。

构造方法的执行过程

对于一个复杂的对象,构造方法的执行过程遵照以下步骤

  • 调用本类或父类的构造方法,直至最深一层派生类;
  • 按照声明顺序执行域的初始化赋值;
  • 执行构造函数中的各语句;
  • 构建器的调用顺序非常重要。先父类构造,再本类成员赋值,最后执行构造方法中的语句。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//ch05.seq.ConstructSequence.java
package ch05.seq;
class ConstructSequence {
public static void main(String[] args) {
Student p = new Student("李明", 18, "北大");
}
}
class Person {
static {
System.out.println("Person initializing!");
}
String name = "未命名";
int age = -1;
Person(String name, int age) {
System.out.println("开始构造Person(),此时this.name=" + this.name + ",this.age=" + this.age);
this.name = name;
this.age = age;
System.out.println("Person()构造完成,此时this.name=" + this.name + ",this.age=" + this.age);
}
}
class Student extends Person {
static {
System.out.println("Student initializing!");
}
String school = "未定学校";

Student(String name, int age, String school) {
super(name, age);
System.out.println(
"开始构造Student(),此时this.name=" + this.name + ",this.age=" + this.age + ",this.school=" + this.school);
this.school = school;
System.out.println(
"Student()构造完成,此时this.name=" + this.name + ",this.age=" + this.age + ",this.school=" + this.school);
}
}

构造方法内部调用的方法的多态性

在构造子类的一个对象时,子类构造方法会调用父类的构造方法,而如果父类的构造方法中调用到对象的其他方法,如果所调用的方法又被子类所覆盖的话,它可能实际上调用的是子类的方法,这是由动态绑定(虚方法调用)所决定的。从语法上来说这是合理的,但有时会造成事实上的不合理,所以在构造方法中调用其他方法时要小心。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//ch05.metamorph.ContructInvoke.java
package ch05.metamorph;
class ConstructInvoke
{
public static void main(String[] args){
Person p = new Student("李明", 18, "北大");
}
}
class Person
{
String name="未命名";
int age=-1;
Person( String name, int age ){
this.name=name; this.age=age;
sayHello();
}
void sayHello(){
System.out.println( "我是一个人,我名叫:" + name + ",年龄为:"+ age );
}
}
class Student extends Person
{
String school="未定学校";
Student( String name, int age, String school ){
super( name, age );
this.school = school;
}
void sayHello(){
System.out.println( "我是学生,我名叫:" + name + ",年龄为:"+ age + ",学校在:" + school );
}
}

对象清除与垃圾回收

  • new创建对象;
  • 自动清除,清楚过程成为垃圾回收;
  • 对象回收是由Java虚拟机的垃圾回收线程来完成的;
  • 系统中的任何对象都有一个引用计数器,当其值为0时,说明该对象可以回收;
  • 垃圾回收:System.gc()方法。它可以要求系统进行垃圾回收。但是它仅仅只有建议权。

finalize()方法

  • Object具有finalize()方法,类C++中析构函数;
  • 可以通过覆盖Object的finalize()方法来实现关闭打开的文件、清除一些非内存资源等工作需要在对象懂得回收时进行。因为系统在回收时会自动调用对象的finalize()方法;
  • 一般来说,子类的finalize()方法中应该调用父类的finalize()方法,以保证父类的清理工作能够正常运行;
  • 但是程序绝对不能完全依赖finalize()释放资源,因为finalize()是不可控的;
  • 为了准确释放资源,应该用try finally或者AutoCloseable接口。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//ch05.clean.TestFinalize
package ch05.clean;
/**
* 本例子演示了finalize的调用
*/
public class TestFinalize {
public TestFinalize() {
// TODO Auto-generated constructor stub
}
@Override
protected void finalize() throws Throwable {
// TODO Auto-generated method stub
System.out.println(" this finalize");
//finalize应该放在最后,这个和构造函数调用super正好相反
super.finalize();
}
public static void main(String[] args) {
// TODO Auto-generated method stub
TestFinalize t = new TestFinalize();
t = null;
System.gc();
}
}

内部类与匿名类

定义

  • 内部类是在其他类中的类;
  • 匿名类是一种特殊的内部类,它没有类名,在定义类的同时就生成该对象的一个实例

内部类的定义和使用

  • 将类的定义置入一个用于封装它的类内部即可;
  • 内部类不能够与外部类同名;
  • 在封装它的类的内部使用内部类,与普通类的使用方式相同;
  • 在其他地方使用,类名前要冠以外部类的名字;
  • 在用new创建内部类时,也要在new前面冠以对象变量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//ch05.inner.TestInnerUse.java
package ch05.inner;
/**
* 本例子演示了内部类的使用
*/
class TestInnerUse {
public static void main(String[] args) {
Parcel p = new Parcel();
p.testShip();
Parcel.Contents c = p.new Contents(33);
Parcel.Destination d = p.new Destination("Hawii");
p.setValue(c, d);
p.ship();
}
}
class Parcel {
private Contents c;
private Destination d;
class Contents {
private int i;
Contents(int i) {
this.i = i;
}
int value() {
return i;
}
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() {
return label;
}
}
void setValue(Contents c, Destination d) {
this.c = c;
this.d = d;
}
void ship() {
System.out.println("运输" + c.value() + "到" + d.readLabel());
}
public void testShip() {
c = new Contents(22);
d = new Destination("Tanzania");
ship();
}
}

在内部类中使用外部类的成员

  • 内部类中可以直接访问外部类的其他域及方法。即使private也可以;
  • 如内部类中有与外部类同名的域或方法,可以用this来访问外部成员。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//ch05.inner.TestInnerThis.java
package ch05.inner;
class TestInnerThis
{
public static void main(String args[]){
D a = new D();
D.B b = a.new B();
b.mb(333);
}
}
class D
{
private int s = 111;
public class B {
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量s
System.out.println(this.s); // 内部类对象的属性s
System.out.println(D.this.s); // 外层类对象属性s
}
}
}

内部类的修饰符

  • 内部类与类中的域、方法一样是外部类的成员,他的前面也可以有访问控制符和其他修饰符;

  • 内部类可用的修饰符比外部类的修饰符更多

    外部类不能够使用protected,private,static等修饰,而内部类可以;

  • 访问控制符:public, protected, default, private, final, abstract;

  • 用static修饰表明该内部类实际是一种外部类。

static内部类

static环境在使用时要遵循以下规则

  1. 实例化static内部类时,在new前面不需要用对象变量;
  2. static内部类中不能访问其外部类的非static的域及方法,即只能访问static成员;
  3. static方法中不能访问非static的域及方法,也不能够不带前缀地new一个非static的内部类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//ch05.inner.TestInnerStatic
package ch05.inner;
class TestInnerStatic {
public static void main(String[] args) {
A.B a_b = new A().new B(); // ok
A a = new A();
A.B ab = a.new B();
OuterStatic.Inner oi = new OuterStatic.Inner();
// Outer.Inner oi2 = Outer.new Inner(); //!!!error
// Outer.Inner oi3 = new Outer().new Inner(); //!!! error
}
}
class A {
private int x;
void m() {
new B();
}
static void sm() {
// new B(); // error!!!!
}
class B {
B() {
x = 5;
}
}
}
class OuterStatic {
static class Inner {
}
}

方法中的内部类

在一个方法中也可以定义类。这种类称为方法中的内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//ch05.inner.TestInnerMethod
package ch05.inner;
class TestInnerInMethod
{
public static void main(String[] args)
{
Object obj = new Outer().makeTheInner(47);
System.out.println("Hello World!" + obj.getClass().toString() );
}
}
class OuterMethod
{
private int size = 5;
public Object makeTheInner( int localVar )
{
final int finalLocalVar = 99;
class Inner {
public String toString() {
return ( " InnerSize: " + size +
// " localVar: " + localVar + // Error!
" finalLocalVar: " + finalLocalVar
);
}
}
return new Inner();
}
}
  1. 同局部变量一样,方法中的内部类前不能够用public, private, protected, static修饰,但可以被final或者abstract修饰;
  2. 方法中的内部类可以访问其外部类的成员。若是static中的内部类可以访问外部类的static成员;
  3. 方法中的内部类中,不能够访问该方法的局部变量,除非是final局部变量;
  4. 方法中定义的类,在其他地方使用时,没有类的情况,正像例中一样,只能够用其父类来引用这样的变量。

匿名类

匿名类有以下几个特点:

  1. 不取名字,直接用其父类的名字;
  2. 类的定义域创建该类的一个实例同时进行,即类的定义前面有一个new。不使用关键词class。new类名或接口名(){…};
  3. 类名前面不能够有修饰符;
  4. 类中不能够定义构造方法,因为它没有名字。在构造对象时不能够带参数。

文章源码

文章源码 备用仓库