1. 变量及其传递
基本类型变量(primitive type)和引用类型变量(reference type)
基本类型(primitive type):其值直接存于变量中。“在这里”
引用型(reference type) 的变量除占据一定的内存空间外,它所引用的对象实体(由new 创建)也要占据一定空间。“在那里”,可以理解为指针。
代码
MyDate m,n;
m=new MyDate();
n=m;
m和n都指向同一个对象,两者都可以理解为一个指针。通过m和n都可以操纵同一个对象。
字段变量(Field)与局部变量(Local variable)区别
从位置看
字段变量(Field):又称成员变量(member variable),域变量,在类中;上图的
latitude
和longitude
都是字段变量。局部变量(Local variable):又称本地变量(local variable),在方法中定义的变量或方法的参变量。上图的
args
和lima
以及latIn
和lonIn
都是局部变量。
从内存角度看
Memory Model的建立流程如下所示
上图首先在堆中新建SimpleLocation
类的对象,该对象拥有两个变量latitude
和longtiude
-->>
上图展示调用函数SimpleLocation(double latIn,double lonIn)
期间,内存中新建了临时空间,如图中constructor's scope所示。-->>
如上所示,局部(临时)变量laiIn
,lonIn
将值传递给SimpleLocation
类的对象latitude
和longtiude
后,则对象的建立成功。该函数调用结束后,局部变量latIn
和lonIn
以及this
会在内存中消失。该构造器(constructor) 先返回对象的地址(指针)给 lima
变量,然后消失。最终,lima将指向新对象。
生命周期:Field 是在随着对象的创建,产生的在堆(The heap)中;Local variable
是随着方法的调用期间按需生成相应的内存空间。
初始值:Filed 编译器会自动赋初值,Local variable 需要显式赋值,否则编译无法通过;
从语法角度看
字段变量属于类,可以用public,private,static,final 修饰。
局部变量不能够被访问控制符及static修饰
都可以被final修饰
变量的传递(与c语言相似)
调用对象方法时,要传递参数。
在传递参数时,Java 是值传递,即,是将表达式的值复制给形式参数。
对于引用型变量,传递的值是引用值,而不是复制对象实体,可以改变对象的属性。
方法的返回
返回基本类型。
返回引用类型。它就可以存取对象实体。
代码
Object getNewObject() {
Object obj=new Object();
return obj; }
调用时:
Object p= GetNewObject();
2. 多态和虚方法调用
多态(Polymorphism)
是指一个程序中相同的名字表示不同的含义的情况。
1. 编译时多态
重载(overload) (多个同名的不同方法)。
代码
p.sayHello();
p.sayHello(“Wang”);
2. 运行时多态(更重要)
覆盖(override) (子类对父类方法进行覆盖)
Polymorphism means that a variable of a supertype can refer to a subtype object.
动态绑定(dynamic binding) ----虚方法调用(virtual method invoking)
在调用方法时,程序会正确地调用子类对象的方法。
多态优点:大大提高了程序的抽象程度和简洁性。
代码
public class DynamicBindingDemo {
public static void main(String[] args){
m(new GraduateStudent());//传递的是子类对象,编译通过
m(new Student());//同上!
m(new Person());//同上!
m(new Object());
}
public static void m(Object x){
System.out.println(x.toString());
}
}
class GraduateStudent extends Student{
}
class Student extends Person{
public String toString(){
return "student";
}
}
class Person{
public String toString(){
return "Person";
}
}
运行结果:
student
student
Person
java.lang.Object@60e53b93
从以上测试代码看来,虽然 m(Object x)
定义的形参是Object
类的,但允许实际参数传递子类对象:m(new GraduateStudent());
,编译通过。
另外:在类继承关系链中,对同一个方法可能对应有多个实现,但在运行时由JVM自动搜索绑定哪个实现。动态绑定流程:从最近的类(最低,最具体)找,直到 Object
类(最高,最抽象)。只要找到了实现方法就停止。比如现在GraduateStudent类中找,没找到,就在Student
类中找,结果找到了,就停止该搜索。
这里需要注意区别构造函数的执行流程。
上溯造型
上溯造型(upcasting) :是把派生类型(subclass)当作基本类型(upclass)处理.
It is always possible to cast an instance of a subclass to a variable of a superclass(knowns as upcasting) .
Person p = new Student();
void fun(Person p ){...}
在这里,虽然P的声明类型是Person,但它的实际类型是Student,Student是Person的子类。在被fun(Person p)调用时,仍可以casting。这也是动态绑定的范畴。
涉及类型转换的问题:
m(new Student());
//等价于
Object o = new Student();
m(o);
现在假设,要将o分配给一个Student类的变量,该如何做?
方法1
Student b = o;//编译报错,因为在编译器看来,o是一个Object类的变量,并不一定是个Student类的变量
方法2
Student b = (Student)o;//这其实是downcasting了,把一个Object类的对象向下映射为Student类的,编译通过。但是前提是得确保o变量的真实类型是Student类的,否则会抛出ClasCastException错误。
虚方法的调用
如何实现运行时的多态?(虚方法调用)
子类重载了父类方法时,运行时系统根据调用该方法的真实类型(actual type)来决定选择哪个方法调用
所有的非final方法都会自动地进行动态绑定
如何确定动态类型?
instanceof
是 java 的关键字。用变量
instanceof
来判断一个对象的真实类型(actual type)。结果是boolean 值
代码
Object myObject = new Circle();
if(myObject instanceof Circle){
System.out.println("The circle diameter is " + ((Circle)myObject).getDiameter());
}
在Object myObject = new Circle();
新增一个Object类的myObject变量,但是,myObject
的真实类型(actual type)是Circle()
类型,所以myObject instanceof Circle
返回True
。
另外,为什么要进行对myObject进行downcasting,即 (Circle)myObject
?
编译时,myObject
的声明类型是Object
,便于编译器决定采用哪个方法,比如myObject.getDiameter()
将会引起编译错误,所以要类型转换 (Circle)myObject
。
多说一句,为什么要将myObject
设定为 Object
对象?
将一个变量声明为父类类型,是为了更好地抽象编程(generic programming),这样myObject
能够接受任何子类对象
什么情况不是虚方法调用
Java中,普通的方法是虚方法
但static,private方法不是虚方法调用
三种非虚的方法
static的方法(从名字看,是静态,与动态绑定相对),以声明的类型为准,与实例类型无关
private方法子类看不见,也不会被虚化
final方法子类不能覆盖,不存在虚化问题
代码
public class InvokeStaticMethod {
/*调用静态方法来
*/
public static void main(String[] args){
Circle c = new Circle();
Shape s = new Shape();
Shape d = new Circle();
doSomething(c);
doSomething(s);
doSomething(d);
doSomethingVer2(c);
doSomethingVer2((Circle)d);//必须强制类型转换为Circle()
}
static void doSomething(Shape s){//注意该方法声明为静态,非虚调用,参量的声明类型是Shape,所以只能匹配到Shape类型的参数。
s.draw();
}
static void doSomethingVer2(Circle s){//注意该方法声明为静态,形参只能匹配到Circle类型的参数;
s.draw();
}
}
class Shape {
static void draw(){
System.out.println("draw shape");
}
}
class Circle extends Shape{
static void draw(){
System.out.println("draw circle");
}
}
输出结果:
draw shape
draw shape
draw shape
draw circle
draw circle
3. 对象的构造和初始化
构造方法(constructor)
对象都有构造方法
如果没有,编译器加一个default构造方法
类的成员变量和方法是可以继承的,但是类的构造器是不能继承的。只能通过调用,又分为显式调用和隐式调用。
调用本类或父类的构造方法
this调用本类的其他构造方法。
super调用直接父类的构造方法
this或super要放在第一条语句,且只能够有一条
如果既不是调用this,也不是调用super方法,则编译器会自动加上
super()
,也就是调用直接父类的无参方法。
可以看出,原则上必须令所有父类的构造方法都得到调用,否则对象的构建就不成功。
In any case, constructing an instance of a class invokes the constructors of all the superclasses along the inheritance chain. This is called constructor chaining.
--Introduction to Java Programming
构造方法的执行过程遵照以下步骤:
调用本类或父类的构造方法,直至最高一层(Object)
按照声明顺序执行字段的初始化赋值
执行构造函数中的各语句。
简单地说: 先父类构造,再本类成员赋值,最后执行构造方法中的语句。
创建对象时初始化
p = new NoConstructorTest(){{ a="A"; b="B"; }};
这样可以针对没有相应构造函数但又要赋值。
注意双括号。
代码
public class NoConstructorTest {
String a;
String b;
//no constructors
public static void main(String[] args){
NoConstructorTest p = new NoConstructorTest(){{ a="A"; b="B"; }};
System.out.println("the instance can be given value without defining constructor.\na is:\n"+p.a+"\nb is: \n"+p.b);
}
}
输出结果
the instance can be given value without defining constructor.
a is:
A
b is:
B
可见,该对象的初始化是成功的。
4. 对象清除与垃圾回收
迟点准备两个例子作为解析
5. 内部类与匿名类
内部类( inner class )是在所在类中的特殊成员
匿名类( anonymous class)是一种特殊的内部类,它没有类名。
内部类(inner class)
内部类是所在类的成员。
编译器生成
xxxx$xxxx
这样的class文件内部类不能够与外部类同名
优点是:在某些程序中,使用内部类来简化程序(比如减少source file的个数);内部类可以引用所在外部类的属性和方法。
内部类的使用
在封装它的类的内部使用内部类,与普通类的使用方式相同。
在其他地方使用类名前要冠以外部类的名字。在用
new
创建内部类实例时,也要在new
前面冠以对象变量,可以用外部对象名.new 内部类名(参数)
。
在内部类中使用外部类的成员
内部类中可以直接访问外部类的字段及方法。即使private也可以,因为内部类本质上也是一个类成员。
如果内部类中有与外部类同名的字段或方法,则可以用
外部类名.this.字段及方法
。
代码
class A {
private int s=3;
public class B{
private int s=2;
public void mb(int s){
System.out.println(s);
System.out.println(this.s);
System.out.println(A.this.s);
}
}
}
内部类的修饰符
内部类与类中的字段、方法一样是外部类的成员,它的前面也可以有 访问控制符和其他修饰符。
访问控制符:
public
,protected
,默认及private
。 注:外部类只能够使用public修饰或者默认final
,abstract
。
static
修饰符
用
static
修饰的内部类,实际上是一种外部类。因为它与外部类的实例无关
static
类的使用
实例化
static
类时,在new
前面不需要用对象实例变量;static
类中不能访问其外部类的非static的字段及方法,既只能够访问static
成员。static
方法中不能访问非static
的域及方法,也不能够不带前缀地new
一个非
static的内部类。
局部类
在一个方法中也可以定义类,这种类称为”方法中的内部类” ,或者叫局部类(local class)
局部类的使用
同局部变量一样,方法中的内部类,不能够被
public
,private
,protected
,static
修饰, 但可以被final
或者abstract
修饰。可以访问其外部类的成员
不能够访问该方法的局部变量,除非是
final
局部变量
匿名类(anonymous class)
匿名类( anonymous class)是一种特殊的内部类
它没有类名,在定义类的同时就生成该对象的一个实例
“一次性使用”的类
匿名类的使用
-
不取名字,直接用其父类或接口的名字。
也就是说,该类是父类的子类,或者实现了一个接口
编译器生成
xxxxx$1
之类的名字,其中1
表示第一个匿名类。
-
类的定义的同时就创建实例,即类的定义前面有一个new
new 类名或接口名
(){......}
不使用关键词class,也不使用
extends
及implements
。
-
在构造对象时使用父类构造方法
不能够定义构造方法,因为它没有名字
如果
new
对象时,要带参数,则使用父类的构造方法
代码
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
public class AnonymousHandlerDemo extends Application {
@Override // Override the start method in the Application class
public void start(Stage primaryStage) {
Text text = new Text(40, 40, "Programming is fun");
Pane pane = new Pane(text);
// Hold four buttons in an HBox
Button btUp = new Button("Up");
Button btDown = new Button("Down");
Button btLeft = new Button("Left");
HBox hBox = new HBox(btUp, btDown , btLeft);
hBox.setSpacing(10);
hBox.setAlignment(Pos.CENTER);
BorderPane borderPane = new BorderPane(pane);
borderPane.setBottom(hBox);
//在方法中定义的内部类称为 局部类(local class)
//可以引用方法中的变量, 比如text
class EnableEventHandler
implements EventHandler<ActionEvent>{
public void handle(ActionEvent e) {
text.setY(text.getY() > 10 ? text.getY() - 5 : 10);
}
}
//注意:必须先定义了类,才能使用.
//否则编译器会找不到这个类而报错.
btUp.setOnAction(
new EnableEventHandler());
//对比上面,非匿名类
//下面开始使用匿名类
btDown.setOnAction(new EventHandler<ActionEvent>() {
@Override // Override the handle method
public void handle(ActionEvent e) {
text.setY(text.getY() < pane.getHeight() ?
text.getY() + 5 : pane.getHeight());
}
});
//可见,使用匿名类的好处是:精简了先定义类,后使用类这一过程.
//匿名类的使用是「一次性」的。
//进一步简化,采用Lambda表达式
btLeft.setOnAction(e -> {
//但得遵循SAM原则
text.setX(text.getX() > 0 ? text.getX() - 5 : 0);
});
// Create a scene and place it in the stage
Scene scene = new Scene(borderPane, 400, 350);
primaryStage.setTitle("AnonymousHandlerDemo"); // Set title
primaryStage.setScene(scene); // Place the scene in the stage
primaryStage.show(); // Display the stage
}
/**
* The main method is only needed for the IDE with limited
* JavaFX support. Not needed for running from the command line.
*/
public static void main(String[] args) {
launch(args);
}
}
运行截图
6. Lambda 表达式
基本语法
Lambda表达式是从Java8增加的新语法
-
Lambda表达式(λ expression)的基本写法
(参数)->结果
比如:
(String s) -> s.length()
将会返回s
的长度x->x*x
将会返回x*x
的运算结果,参数的类型都省略了。
大体上相当于其他语言的“匿名函数”或“函数指针” 。
在Java中它实际上是“ 匿名类的一个实例”,即是定义后马上使用,更加简洁和高效。
代码
例子一
A dolt = new A(){
public void run(){
System.out.println("OK");
}
}
写成Lamdba表达式:
() -> { System.out.println(“OK”); }
例子二
A dolt = new A(){
public double run(double x){
return Math.sin(x);
}
}
以上写成Lambda表达式:
(x) -> Math.sin(x);
例子三
btn.addActionListener( e -> ... } ) );//编译器能够自动识别e为ActionEvent类。
以上三个例子说明:Lambda表达式是接口或者说是接口函数的简写,基本写法是 (参数)->结果
,这里,参数是()
或(1个参数)
或 (多个参数)
,结果是指 表达式
或 语句
或 {语句}
能写成Lambda的接口的条件
由于Lambda只能表示一个函数,所以
能写成Lambda的接口要求包含且最多只能有一个抽象函数
-
这样的接口可以用注记(但不强求)
@FunctionalInterface
来表示。称为函数式接口,用于对编译器的提示:@FunctionalInterface interface A { double A( double x );}
代码
Comparator<Person> compareAge = (p1, p2) -> p1.age-p2.age;
Arrays.sort(people, compareAge);
Lambda表达式,不仅仅是简写了代码, 更重要的是:它将代码也当成数据来处理。
7. 装箱、枚举、注解
从JDK1.5起,增加了一些新的语法
大部分是编译器自动翻译的,称为Complier sugar。
基本类型的包装类(wrapper class)
基本类型并不是对象,但通过使用Java API的包装类能够把基本类型包装成对象,从而方便某些方法的调用需求。
它将基本类型(primitive type) 包装成Object(引用类型)
Java的八种包装类(wrapper class)如下:
Boolean
,Byte
,Short
,Character
,Integer
,Long
,Float
,Double
注:包装类的名称首字母亦是大写。
-
装箱(Boxing)
Integer I = new Integer(10);
或简写为
Integer I = 10;//编译器自动将基本类型包装为`Integer`对象
-
拆箱(Unboxing)
int i = I;
上述过程,被编译器译为:
Integer I= Integer.valueOf(10);
int i = I.intValue();
枚举
枚举(enum)是一种特殊的class类型
-
在简单的情况下,用法与其他语言的enum相似
enum Light { Red, Yellow, Green }; Light light = Light.Red;
但实际上,编译后,它生成了
class Light extendsjava.lang.Enum
,所以可以在enum
定义体中,添加字段、方法、构造方法,可以当做一般的class,更加灵活。
代码
enum Direction{
EAST("东",1), SOUTH("南",2),
WEST("西",3), NORTH("北",4);
private Direction(String desc, int num){//允许添加构造方法
this.desc=desc; this.num=num;
}
private String desc;
private int num;
public String getDesc(){ //允许添加一般方法
return desc;
}
public int getNum(){ //允许添加一般方法
return num;
}
}
注解(annotation)
又称为注记、标记、标注、注释(不同于comments)
是在各种语法要素上加上附加信息,以供编译器或其他程序使用
所有的注解都是 java.lang.annotation.Annotation
的子类
常用的注解,如
@Override
@Deprecated 表示过时的方法
@SuppressWarnings 表示让编译器不产生警告
-
自定义注解,这个很少见啦。
public @interface Author { String name(); }
8. 没有指针的 JAVA 语言
引用(reference)实质就是指针(pointer),但在Java中,引用是安全的指针。
Java标榜其中对C/C++一个很大的改进就是:Java对程序员屏蔽了变量地址的概念,减少指针误用。
比如:
会检查空指引
没有指针运算 *(p+5)
不能访问没有引用到的内存
自动回收垃圾
C语言指针在Java中的体现
1. 传地址 -> 对象
Java引用类型(reference type),引用本身就相当于指针,可以用来修改对象的属性、调用对象的方法。
如交换两个整数,在C语言中:
void swap(int x, int y){
int t=x;
x=y;
y=t;
}
int a=8, b=9;
swap(&a,&b);
在Java中,无法实现该交换。但可以使用一种变通的办法,传出一个有两个分量x,y的对象。
变通办法,但显得很笨:
class Test{
public static void swap2(final int [] arr, final int pos1, final int pos2){
final int temp = arr[pos1];
arr[pos1] = arr[pos2];
arr[pos2] = temp;
}
public static void main(String [] args){
int [] a ={1,2};
swap2(a,0,1);
System.out.println(a[0]+" "+a[1]);
}
}
运行结果:
2 1
2. 指针运算 -> 数组
在C中的指针*(p+5)
,在Java中则可以用 args[5]
3. 函数指针 -> 接口、Lambda表达式
例如上述:求积分、线程、回调函数、事件处理。
4. 指向结点的指针 -> 对象的引用
在链表中有该类:
class Node {
Object data;
Node next;
}
next就是一个指向对象的指针。
5. 使用JNI
Java Native Interface(JNI) ,它允许Java代码和其他语言写的代码进行交互。