资讯专栏INFORMATION COLUMN

深入理解JAVA语言

PumpkinDylan / 3225人阅读

摘要:在那里,可以理解为指针。局部变量不能够被访问控制符及修饰都可以被修饰变量的传递与语言相似调用对象方法时要传递参数。内部类内部类是所在类的成员。大体上相当于其他语言的匿名函数或函数指针。

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),域变量,在类中;上图的latitudelongitude都是字段变量。

局部变量(Local variable):又称本地变量(local variable),在方法中定义的变量或方法的参变量。上图的argslima以及latInlonIn都是局部变量。

从内存角度看

Memory Model的建立流程如下所示

上图首先在堆中新建SimpleLocation类的对象,该对象拥有两个变量latitudelongtiude-->>

上图展示调用函数SimpleLocation(double latIn,double lonIn)期间,内存中新建了临时空间,如图中constructor"s scope所示。-->>

如上所示,局部(临时)变量laiInlonIn将值传递给SimpleLocation类的对象latitudelongtiude后,则对象的建立成功。该函数调用结束后,局部变量latInlonIn以及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.
a is:
"+p.a+"
b is: 
"+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)

局部类的使用

同局部变量一样,方法中的内部类,不能够被publicprivateprotectedstatic 修饰, 但可以被 final 或者 abstract 修饰。

可以访问其外部类的成员

不能够访问该方法的局部变量,除非是final局部变量

匿名类(anonymous class)

匿名类( anonymous class)是一种特殊的内部类

它没有类名,在定义类的同时就生成该对象的一个实例

“一次性使用”的类

匿名类的使用

不取名字,直接用其父类或接口的名字。

也就是说,该类是父类的子类,或者实现了一个接口

编译器生成 xxxxx$1之类的名字,其中1表示第一个匿名类。

类的定义的同时就创建实例,即类的定义前面有一个new

new 类名或接口名(){......}

不使用关键词class,也不使用extendsimplements

在构造对象时使用父类构造方法

不能够定义构造方法,因为它没有名字

如果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{
            public void handle(ActionEvent e) {
                text.setY(text.getY() > 10 ? text.getY() - 5 : 10);
            }
        }
        //注意:必须先定义了类,才能使用.
        //否则编译器会找不到这个类而报错.
        btUp.setOnAction(
                new EnableEventHandler());

        //对比上面,非匿名类
        //下面开始使用匿名类
        btDown.setOnAction(new EventHandler() {
            @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 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代码和其他语言写的代码进行交互。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65865.html

相关文章

  • 深入理解Java虚拟机到底是什么

    摘要:由虚拟机加载的类,被加载到虚拟机内存中之后,虚拟机会读取并执行它里面存在的字节码指令。虚拟机中执行字节码指令的部分叫做执行引擎。 什么是Java虚拟机? 作为一个Java程序员,我们每天都在写Java代码,我们写的代码都是在一个叫做Java虚拟机的东西上执行的。但是如果要问什么是虚拟机,恐怕很多人就会模棱两可了。在本文中,我会写下我对虚拟机的理解。因为能力所限,可能有些地方描述的不够欠...

    宋华 评论0 收藏0
  • 从表到里学习JVM实现

    在社会化分工、软件行业细分专业化的趋势下,会真的参与到底层系统实现的人肯定是越来越少(比例上说)。真的会参与到JVM实现的人肯定是少数。 但如果您对JVM是如何实现的有兴趣、充满好奇,却苦于没有足够系统的知识去深入,那么可以参考RednaxelaFX整理的这个书单。 showImg(http://segmentfault.com/img/bVbGzn); 本豆列的脉络是:    1. JV...

    Cristic 评论0 收藏0
  • 深入理解虚拟机之类文件结构

    摘要:对象创建与访问指令虽然类实例和数组都是对象,但虚拟机对类实例和数组的创建和操作使用了不同的字节码指令。异常处理指令在虚拟机中,处理异常语句不是由字节码指令来实现的,而是采用异常表的方式。 《深入理解Java虚拟机:JVM高级特性与最佳实践(第二版》读书笔记与常见面试题总结 本节常见面试题(推荐带着问题阅读,问题答案在文中都有提到): 简单介绍一下Class类文件结构(常量池主要存放的是...

    张宪坤 评论0 收藏0
  • Java开发

    摘要:大多数待遇丰厚的开发职位都要求开发者精通多线程技术并且有丰富的程序开发调试优化经验,所以线程相关的问题在面试中经常会被提到。将对象编码为字节流称之为序列化,反之将字节流重建成对象称之为反序列化。 JVM 内存溢出实例 - 实战 JVM(二) 介绍 JVM 内存溢出产生情况分析 Java - 注解详解 详细介绍 Java 注解的使用,有利于学习编译时注解 Java 程序员快速上手 Kot...

    LuDongWei 评论0 收藏0
  • 深入理解Java虚拟机》(一)Java虚拟机发展史

    摘要:虚拟机发展史注本文大部分摘自深入理解虚拟机第二版作为一名开发人员,不能局限于语言规范,更需要对虚拟机规范有所了解。虚拟机规范有多种实现,其中是和中所带的虚拟机,也是目前使用范围最广的虚拟机。世界第一款商用虚拟机。号称世界上最快的虚拟机。 Java虚拟机发展史 注:本文大部分摘自《深入理解Java虚拟机(第二版)》 作为一名Java开发人员,不能局限于Java语言规范,更需要对Java虚...

    张春雷 评论0 收藏0

发表评论

0条评论

PumpkinDylan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<