第四章 深入理解Java语言
文章目录
4.1 变量及其传递
基本类型变量与引用型变量
- 基本类型(primitive type):其值直接存于变量中。
- 引用型(reference type)的变量除占据一定的内存空间外,它所引用的对象实体(由new创建)也要占据一定空间
引用型变量与对象实体的关系
MyDate m,n;
m = new MyDate();
n = m;
n.addYear();
字段变量与局部变量
字段变量(field)与局部变量(Local variable)
- 前者是在类中,后者是方法中定义的变量或方法的参变量
从内存角度看
- 存储位置:字段变量为对象的一部分,存在于堆中,局部变量是存在于栈中。
- 生命周期不同
- 初始值:字段变量可以自动赋初值,局部变量则须显式赋值。
从语法角度看
- 字段变量属于类,可以用
public
,private
,static
,final
修饰 - 局部变量不能够被访问控制符及
static
修饰 - 都可以被
final
修饰
变量的传递
调用对象方法时,要传递参数。在传递参数时,Java是值传递,即是将表达式的值赋值给形式参数。
对于引用型变量,传递的值是引用值,而不是复制对象实体(可以改变对象的属性)
变量的返回
方法的返回
- 返回基本类型
- 返回引用类型,它就可以存取对象实体
不定长参数:用省略号标识,并且是最后一个参数(实际上java当成一个数组)
int sum(int…nums){ int s = 0; for(int n : nums) s+=n; return s; }
调用:
sum(1,2,3,4);
4.2 多态和虚方法调用
多态
多态(Polymorphism)是指一个程序中相同的名字表示不同的含义的情况
- 编译时多态
- 重载(
Overload
)(多个同名的不同方法)
- 重载(
- 运行时多态
- 覆盖(
Override
)(子类对父类方法进行覆盖) - 动态绑定(dynamic binding)——虚方法调用(virtual method invoking)
- 在调用方法时,程序会正确的调用子类的方法
- 覆盖(
多态的特点大大提高了程序的抽象程度和简洁性
上溯造型(upcasting):把派生类型当作基本类型处理
Person p = new Student();
void fun(Person p){…}
fun(new Person());
虚方法调用
用虚方法调用,可以实现运行时的多态
- 子类重载父类方法时,运行时
- 运行时系统根据调用该方法的实例的类型来决定选择哪个方法调用
- 所有的非
final
方法都会自动进入动态绑定
void doStuff(Shape s){
s.draw();
}
Circle c = new Circle();
Triangle t = new Triangle();
Line l = new Line();
doStuff(c);
doStuff(t);
doStuff(l);
动态类型确定
变量 instanceof
类型,结果时boolean
值
什么情况不是虚方法调用
Java中,普通的方法时虚方法
但static
,private
方法不是虚方法调用
static
,private
与虚方法编译后使用的指令是不同的
三种非虚的方法
static
的方法,以声明的类型为准,与实例类型无关
private
方法子类看不见,也不会被虚化
final
方法子类不能覆盖,不存在虚化问题
4.3 对象构造与初始化
构造方法(constructor)
对象都有构造方法,如果没有,编译器加一个default
构造方法
调用本类或父类的构造方法
this
调用本类的其他构造方法super
调用直接父类的构造方法this
或super
要放在第一条语句,且只能够有一条
如果没有this
及super
,则编译器自动加上super(),即调用直接父类不带参数的构造方法,因为必须令所有父类的构造方法都得到调用,否则整个对象的构建就可能不正确
创建对象时初始化
p = new Person(){{age=18;name ="aaa";}}
可以正对没有响应构造函数,但又要赋值(注意双括号)
实例初始化与静态初始化
实例初始化(Instance Initializers)
- 在类中直接写{语句},先于构造方法{}中的语句执行
静态初始化(Static Initializers)
- static{语句}在第一次使用这个类时要执行,但其执行的具体时机是不确定的,但总是先于实例的初始化
构造方法的执行过程
构造方法的执行过程遵照以下步骤
- 调用本类或父类的构造方法,直至最高一层(
Object
) - 按照声明顺序执行字段的初始化赋值
- 执行构造函数中的各语句
即先父类构造,再本类成员赋值,最后执行构造方法中的语句
在构造方法中尽量避免调用任何方法,尽可能简单地使对象进入就绪状态
唯一能够安全调用的是
final
的方法
4.4 对象清除与垃圾回收
对象的自动清除
垃圾回收(garbage collection)是由Java虚拟机的垃圾回收线程来完成的
任何对象都有一个引用计数器,当其值为0时,说明该对象可以回收,即系统知道该对象为垃圾
String method(){
String a,b;
a = new String("hello world");
b = new String("game over";);
Systems.out.println(a+b+"OK");
a = null;
a = b;
return a;
}
System.gc()
方法,是System
类的static
方法,它可以要求系统进行垃圾回收,但仅仅只是建议
finalize()方法
Java中没有“析构方法(destructor)”,但Object
的finalize()
有类似功能,系统在回收时会自动调用对象的finalize()
方法。
子类的finalize()
方法可以释放系统资源,但一般来说,子类的finalize()
方法中应该调用父类的finalize()方法,以保证父类的清理工作能够正常进行。
try-with-resources
由于finalize()
方法的调用并不确定,所以一般不用finalize()
,关闭打开的文件、清除一些非内存资源等工作需要进行处理,可以使用try-with-resources
语句
而对于实现了java.lang.AutoCloseable
的对象,会自动调用其close()
方法。
Scanner scanner = new Scanner();
Scanner.close()
4.5 内部类与匿名类
内部类(inner class)是在其他类中的类
匿名类(anonymous class)是一种特殊的内部类,它没有类名
内部类(Inner class)
内部类定义:
- 将类的定义置入一个类的内部即可,编译器生成xxxx$xxxx这样的class文件
- 内部类不能够和外部类同名
内部类的使用
- 在封装他的类的内部使用内部类,与普通类的使用方式相同
- 在其他地方使用
- 类名要冠以外部类的名字
- 在使用
new
创建内部类实例时,也要在new
前面冠以对象变量。即外部对象名.new 内部类名(参数)
在内部类中使用外部类的成员
-
内部类中可以直接访问外部类的字段及方法,即使
private
也可以 -
如果内部类中有与外部类同名的字段或方法,则可以用
外部类名.this.字段及方法
内部类的修饰符
内部类与类中的字段、方法一样是外部类的成员,他的前面也可以有访问控制符和其他修饰符。
局部类
在一个方法中也可以定义类,这种类称为”方法中的内部类“或者交局部类(local class)
使用局部类
-
同局部变量一样,方法中的内部类不能用
public
,private
,protect
,static
修饰,但可以被final
或者abstract
修饰 -
可以访问其外部类的成员
-
不能狗访问该方法的局部变量,除非是
final
局部变量。
匿名类(annoymous class)
匿名类是一种特殊的内部类,它没有类名,在定义类的同时就生成该对象的一个实例,是”一次性使用”的类
匿名类的使用
- 不取名字,直接用其父类或接口的名字,即该类是父类的子类,或者实现了一个接口
- 类的定义的同时就创建实例,即类的定义前面有一个new:
new 类名或接口名(){}
- 在构造对象时使用父类构造方法,不能定义构造方法,因为它没有名字,如果new对象时,要带参数,则使用父类的构造方法
匿名类的应用
-
界面的事件处理 -
作为方法的参数
Arrays.<Book>sort(books,new Comparator<Book>(){ public int compare(Book b1,Book b2){ return b1.getPrice()-b2.getPrice(); } });
4.6 Lambda表达式
基本写法:
(参数)->结果
(String s)->s.length()
x->x*x
()->{String.out.println("aaa");}
大体相当于其他语言的“匿名函数”和“函数指针”,实际上是“匿名类的一个实例”
//普通形式
Runnable doIt = new Runnable(){
public void run(){
System.out.println("aaa");
}
};
new Thread(doIt).start();
//Lambda表达式
Runnable doIt = ()->System.out.println("aaa");
new Thread(deIt).start();
new Thread(()->System.out.println("aaa")).start();
Lambda表达式时接口或者说是接口函数的简写
其基本写法是参数->结果,
-
这里参数是()或1个参数或多个参数
-
结果是指表达式或语句或{语句}
inteface Fun{double fun(double x);}
double d = Integral(new Fun(){
public double fun(double x) {
return Math.sin(x);
}
},0,Math.PI,1e-5);
double d = Integral(x->Math.sin(x)),0,Math.PI,1E-5);
能写成Lambda的接口的条件
由于Lambda只能表示一个函数,所以能写成Lambda的接口要求包含且最多只能有一个抽象函数
这样的接口可以(不强求)用注记
@FunctionalInterface
来表示,称为函数式接口。
Comparator<Person> compareAge =
(p1,p2) -> p1.age - p2.age;
Arrays.sort(people,compareAge);
Array.sort(people,
(p1,p2) -> p1.age-p2.age);
Arrays.sort(people,
(p1,p2) -> (int)(p1.score-p2.score));
Arrays.sort(people,
(p1,p2) -> p1.name.compareTo(p2.name));
Arrays.sort(people,
(p1,p2) -> -p1.name.compareTo(p2.name));
Lambda表达式,不仅仅是简写了代码,更重要的是他将代码也当成数据来处理
4.7 装箱、枚举、注解
基本类型的包装类
他将基本类型(primitive type)包装成Object(引用类型)
eg:int
->Integer
共8类:Boolean
,Byte
,Short
,Character
,Integer
,Long
,Float
,Double
装箱与拆箱
装箱(Boxing)
拆箱(Unboxing)
4.8 没有指针的Java语言
引用与指针
引用(reference)实质就是指针(pointer),但是它是受控的,安全的,比如
- 会检查空指引
- 没有指针运算
- 不能访问没有引用到的内存
- 自动回收垃圾
C语言指针在Java中的体现
传地址->对象
引用类型,引用本身就相当于指针,可以用来修改对象的属性,调用对象的方法
基本类型:没用对应的
-
如交换两个整数
void swap(int x,int y){ int t = x; x = y; y = t; } int a = 8,b=9; swap(a,b);
指针运算 -> 数组
*(p+5)
则可以用arg[5]
函数指针 -> 接口、Lambda表达式
指向结点的指针 -> 对象的引用
class Node{
Object data;
Node next;
}
使用JNI
Java Native Interface(JNI)
它允许Java代码与其他语言写的代码进行交互
相等还是不等
==
简单来说,基本类型是值相等,引用类型是引用相等
基本类型的相等
数值类型:转换后比较
浮点数,最好不直接用==
Double.NAN==Double.NAN
结果为false
boolean
型无法与int
相比较
Integer i = new Integer(10);
Integer j = new Integer(10);
System.out.println(i==j); //false,因为对象是两个
Integer m = 10;
Integer n = 10;
System.out.println(m==n); //true,因为对象有缓存
Integer p = 200;
Integer q = 200;
System.out.println(p==q); //false,因为对象是两个
装箱对象是否相等
注意缓存
If the value p being boxed is
true
,false
, abyte
, or achar
in the range \u0000 to \u007f, or anint
orshort
number between -128 and 127 (inclusive), then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2.
枚举、引用对象是否相等
枚举类型
- 内部进行了唯一实例化,所以可以直接判断
引用对象
- 是直接看两个引用是否一样
- 如果要判断内容是否一样,则要重写
equal
方法 - 如果重写
equal
方法,则最好重写hashCode
方法
String对象的特殊性
String对象判断相等,一定不要用==
,要用equals
,但是字符串常量(String literal)及字符串常量会进行内部化(interned),相同的字符串常量是==
的
String hello = "Hello", lo = "lo";
System.out.println( hello == "Hello"); //true
System.out.println( Other.hello == hello ); //true
System.out.println( hello == ("Hel"+"lo") ); //true
System.out.println( hello == ("Hel"+lo) ); //false
System.out.println( hello == new String("Hello")); //false
System.out.println( hello == ("Hel"+lo).intern()); //true
练习
附件是一个模拟事件消息订阅的程序,请阅读并修改。
主要是加上一个自已的订阅者,并且该订阅者要用匿名类来实现。(基本要求占8分)
如果可以,也加上用Lambda来实现的订阅者。(较高要求,占2分)。
/**
* 一个简单的新闻事件侦听演示程序
*
* @author tds
* @author 修改者:zhangwentao
*/
public class NewsDemo {
public static void main(String[] args) {
//模拟有一家新闻社
NewsAgency bbc = new NewsAgency("BBC");
//订阅该社的新闻
bbc.addListener(new MyListener());
//TODO 请在这里再加入一个Listener
//普通形式
/*bbc.addListener(new Listener() {
@Override
public void newsArrived(NewsEvent e){
if(e.level > 5) {
String name = "tao";
System.out.println(name +" please note," + e.text + " happed!");
}
}
});*/
//lambda形式
bbc.addListener(e -> {
if (e.level > 5) {
String name = "tao";
System.out.println(name + " please note," + e.text + " happed!");
}
});
//新闻社启动其工作流程
bbc.start();
}
}
/**
* 事件信息
*/
class NewsEvent {
Object source; //事件来源
String text; //事件内容
int level; //事件级别
NewsEvent(Object source, String text, int level) {
this.source = source;
this.text = text;
this.level = level;
}
}
interface Listener {
void newsArrived(NewsEvent e);
}
/**
* 新闻机构
*/
class NewsAgency {
String name; //机构名
public NewsAgency(String name) {
this.name = name;
}
Listener[] listeners = new Listener[100]; //侦听者(订阅者)
int listenerCnt = 0; //已有的订阅者
//加入一个订阅者
void addListener(Listener oneListener) {
if (listenerCnt < listeners.length) {
listeners[listenerCnt] = oneListener;
listenerCnt++;
}
}
//模拟一个事件发生,并通知所有的订阅者
void start() {
NewsEvent event = new NewsEvent("Mr. Joan", "Bombing", 9);
for (int i = 0; i < listenerCnt; i++) {
listeners[i].newsArrived(event);
}
}
}
/**
* 实现一个订阅者
*/
class MyListener implements Listener {
//当接收到消息后,进行一些显示
@Override
public void newsArrived(NewsEvent e) {
if (e.level > 5) {
System.out.println("warning :");
}
System.out.println("please note," + e.text + " happed!");
}
}