【Java基础】(02)部分细节

来源:互联网 发布:2017淘宝宝贝数量限制 编辑:程序博客网 时间:2024/06/09 13:40

【Java基础】(02)深入部分细节

Auther: Thomas Shen
E-mail: Thomas.shen3904@qq.com
Date: 2017/10/13
All Copyrights reserved !

      • Java基础02深入部分细节
        • 简述
        • 变量及其传递
          • 1 基本类型变量与引用型变量
          • 2 字段变量与局部变量
          • 3 不定长参数
        • 多态和虚方法
          • 1 多态
          • 2 虚方法调用
        • 对象构造和初始化
          • 1 构造函数
          • 2 实例初始化与静态初始化
          • 3 构造方法的执行过程
        • 对象清除与垃圾回收
        • 内部类局部类匿名类
          • 1 内部类
          • 2 static修饰符
          • 3 局部类
          • 4 匿名类
        • Lambda表达式
        • 装箱枚举注解
          • 1 包装类
          • 2 枚举
          • 3 注解
        • Java对象相等性判断
        • References


1. 简述:

本文总结了Java基础的部分细节,包括变量及其传递,多态和虚方法,对象构造与初始化,对象清除与垃圾回收,内部类与匿名类,Lambda表达式等知识点。


2. 变量及其传递:

2.1 基本类型变量与引用型变量:
  • 基本类型(primitive type):其值直接存于变量中。
  • 引用型(reference type) 的变量除占据一定的内存空间外,它所引用的对象实体(由new 创建)也要占据一定空间。
2.2 字段变量与局部变量:
  • 字段变量(field)与局部变量(Local variable):
    前者是在类中,后者是方法中定义的变量或方法的参变量

  • 从内存角度看:
    – 存储位置,字段变量为对象的一部分、存在于堆中的,局部变量是存在于栈中;
    – 生命周期不同;
    – 初始值:字段变量可以自动赋初值,局部变量则须显式赋值

  • 两种变量的区别:
    – 从语法角度看,字段变量属于类,可以用public,private,static,final修饰;
    – 局部变量不能够被访问控制符及static修饰;
    – 都可以被final修饰。

2.3 不定长参数:

不定长参数(Variable length arguments),从JDK1.5开始用省略号表示, 并且是最后一个参数。

实际上Java当成一个数组:

intsum( int… nums){    ints=0;    for(intn : nums) s+=n;    return s;}

调用:sum(1,2,3,4);

又例如:public static void main( String…argv)


3. 多态和虚方法 :

3.1 多态:

多态(Polymorphism)有两种情形:

  • 编译时多态:
    – 重载(overload) (多个同名的不同方法)。

  • 运行时多态:
    – 覆写(override)(子类对父类方法进行覆盖);
    – 动态绑定(dynamic binding):虚方法调用(virtual method invoking);
    – 在调用方法时,程序会正确地调用子类对象的方法。

  • 多态的特点大大提高了程序的抽象程度和简洁性

3.2 虚方法调用:

虚方法调用,可以实现运行时的多态!

子类重载了父类方法时,运行时系统根据调用该方法的实例的类型来决定选择哪个方法调用,所有的非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);

输出:

Draw Circle
Draw Three Lines
Draw Line

Java中,普通的方法是虚方法,但static,private方法不是虚方法调用。
- static的方法,以声明的类型为准,与实例类型无关;
- private方法子类看不见,也不会被虚化;
- final方法子类不能覆盖,不存在虚化问题。


4. 对象构造和初始化:

4.1 构造函数:

对象(constructor)都有构造方法,如果没有,编译器加一个default构造方法。

调用本类或父类的构造方法:

  • this调用本类的其他构造方法。
  • super调用直接父类的构造方法
  • this或super要放在第一条语句,且只能够有一条

如果没有this及super,则编译器自动加上super(),即调用直接父类不带参数的构造方法。因为必须令所有父类的构造方法都得到调用,否则整个对象的构建就可能不正确。

在构造方法中尽量避免调用任何方法,尽可能简单地使对象进入就绪状态,惟一能够安全调用的是final的方法。

4.2 实例初始化与静态初始化:
  • 实例初始化(Instance Initializers)在类中直接写:
    { 语句…. }

实例初始化,先于构造方法{}中的语句执行。

  • 静态初始化(Static Initializers)
    static { 语句…. }

静态初始化,在第一次使用这个类时要执行,但其执行的具体时机是不确定的,但是可以肯定的是:总是先于实例的初始化。

4.3 构造方法的执行过程:
  • 构造方法的执行过程遵照以下步骤:
    • 调用本类或父类的构造方法,直至最高一层(Object);
    • 按照声明顺序执行字段的初始化赋值;
    • 执行构造函数中的各语句。
  • 简单地说:
    • 先父类构造,再本类成员赋值,最后执行构造方法中的语句。

5. 对象清除与垃圾回收 :

垃圾回收(garbage collection ):对象回收是由Java虚拟机的垃圾回收线程来完成的。

  • System.gc()方法:
    它是System类的static方法,它可以要求系统进行垃圾回收,但它仅仅只是”建议(suggest)”。

  • Java中没有“析构方法(destructor)”,但Object的finalize() 有类似功能,系统在回收时会自动调用对象的finalize() 方法。

  • 子类的finalize()方法,可以在子类的finalize()方法释放系统资源,一般来说,子类的finalize()方法中应该调用父类的finalize()方法,以保证父类的清理工作能够正常进行。

  • try-with-resources:
    由于finalize()方法的调用时机并不确定,所以一般不用finalize()。关闭打开的文件、清除一些非内存资源等工作需要进行处理,可以使用try-with-resources语句(JDK1.7以上)。
    对于实现了java.lang.AutoCloseable的对象:

    try( Scanner scanner= new Scanner( … ) ){    .........}

    会自动调用其close()方法,相当于:

    finally{    Scanner.close();}

6. 内部类,局部类,匿名类 :

6.1 内部类:

参考:https://www.cnblogs.com/chenssy/p/3388487.html

在Java中内部类主要分为成员内部类、局部内部类、匿名内部类、静态内部类。

内部类的定义:

  • 将类的定义class xxxx{…}置入一个类的内部即可,编译器生成xxxx$xxxx这样的class文件;
  • 内部类不能够与外部类同名。

内部类的使用:

  • 在封装它的类的内部使用内部类,与普通类的使用方式相同
  • 在其他地方使用:
    • 类名前要冠以外部类的名字。
    • 在用new创建内部类实例时,也要在new前面冠以对象变量。
      外部对象名.new 内部类名(参数)
  • 内部类中可以直接访问外部类的字段及方法,即使private也可以;
  • 如果内部类中有与外部类同名的字段或方法,则可以用:
    外部类名.this.字段及方法

为什么要使用内部类?

在《Think in java》中有这样一句话:使用内部类最吸引人的原因是:每个内部类都能独立地继承一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。

其实使用内部类最大的优点就在于它能够非常好的解决多重继承的问题,但是如果我们不需要解决多重继承问题,那么我们自然可以使用其他的编码方式,但是使用内部类还能够为我们带来如下特性(摘自《Think in java》):

  1. 内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独 立。
  2. 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或者继承同一个类。
  3. 创建内部类对象的时刻并不依赖于外围类对象的创建。
  4. 内部类并没有令人迷惑的“is-a”关系,他就是一个独立的实体。
  5. 内部类提供了更好的封装,除了该外围类,其他类都不能访问。

内部类的修饰符:

  • 内部类与类中的字段、方法一样是外部类的成员,它的前面也可以有访问控制符和其他修饰符。
  • 访问控制符:public,protected,默认及private。
    注:外部类只能够使用public修饰或者默认 final,abstract
6.2 static修饰符:
  • 用static修饰内部类表明该内部类实际是一种外部类
    • 因为它与外部类的实例无关;
    • 有人认为static的类是嵌套类(nested class),不是内部类inner class。
  • static类在使用时:
    1. 实例化static类时,在new前面不需要用对象实例变量;
    2. static类中不能访问其外部类的非static的字段及方法,既只能够访问static成员;
    3. static方法中不能访问非static的域及方法,也不能够不带前缀地new 一个非static的内部类。
6.3 局部类:

在一个方法中也可以定义类,这种类称为”方法中的内部类”,或者叫局部类(local class)。

使用局部类:

  • 同局部变量一样,方法中的内部类,不能够用public,private,protected,static修饰, 但可以被final或者abstract修饰。
  • 可以访问其外部类的成员。
  • 不能够访问该方法的局部变量,除非是final局部变量。

局部类能且只能访问其所属代码段中的声明为final的局部变量。为什么只能访问声明为final的局部变量呢?我们知道,局部变量在其所属的代码段(譬如某个函数)执行完毕后就会被回收,而一个局部类的实例却可以在其类定义所属代码段执行完毕后依然存在,如果它可操控非final的局部变量,用户就可以通过该实例修改已不存在的局部变量,无意义。

局部类约束:

  • 内部类只在定义它的代码段中可见,不能在它所属代码段之外的代码中使用;因此也就没有public/private/default权限修饰符(无意义);
  • 不能以局部类形式定义一个接口。局部类只在其所属代码段中可见,定义这样的接口无意义;
  • 局部类类名不能与其外部类类名重复。

什么时候使用局部类 :

  • 局部类大部分以匿名类的形式使用。
6.4 匿名类:

参考:http://www.cnblogs.com/chenssy/p/3390871.html

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

  • 它没有类名,在定义类的同时就生成该对象的一个实例
  • “一次性使用”的类

匿名类的使用:

  1. 不取名字,直接用其父类或接口的名字。
    也就是说,该类是父类的子类,或者实现了一个接口;
    编译器生成xxxxx$1之类的名字。

  2. 类的定义的同时就创建实例,即类的定义前面有一个new:
    new 类名或接口名(){……}
    不使用关键词class,也不使用extends及implements。

  3. 在构造对象时使用父类构造方法:
    不能够定义构造方法,因为它没有名字;
    如果new对象时,要带参数,则使用父类的构造方法。

匿名类的应用:

  • 用到界面的事件处理:注册一个事件侦听器;
  • 作为方法的参数:排序,给一个比较大小的接口;
Arrays.<Book>sort( books, newComparator<Book>(){    public intcompare(Book b1, Book b2){        return b1.getPrice()-b2.getPrice();    }});

在使用匿名内部类的过程中,我们需要注意如下几点:

  1. 使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
  2. 匿名内部类中是不能定义构造函数的。
  3. 匿名内部类中不能存在任何的静态成员变量和静态方法。
  4. 匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
  5. 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
  6. 拷贝引用,为了避免引用值发生改变,例如被外部类的方法修改等,而导致内部类得到的值不一致,于是用final来让该引用不可改变。故如果定义了一个匿名内部类,并且希望它使用一个其外部定义的参数,那么编译器会要求该参数引用是final的。
  7. 一般都是利用构造器来完成某个实例的初始化工作的,但是匿名内部类是没有构造器的!那怎么来初始化匿名内部类呢?使用构造代码块!利用构造代码块能够达到为匿名内部类创建一个构造器的效果。
  8. 它没有类名,在定义类的同时就生成该对象的一个实例,“一次性使用”的类。

7. Lambda表达式 :

参考:http://blog.oneapm.com/apm-tech/226.html

Lambda表达式是从Java8增加的新语法。
Lambda表达式(λexpression)的基本写法:

(参数)->结果
如(String s) -> s.length()
如x->x*x
如() -> { System.out.println(“aaa”); }

  • 大体上相当于其他语言的“匿名函数”或“函数指针”;
  • 在Java中它实际上是“匿名类的一个实例”。
Runnable doIt= new Runnable(){    public void run(){        System.out.println("aaa");    }};new Thread( doIt).start();
new Thread(() ->System.out.println("aaa") ).start();

Lambda表达式是接口或者说是接口函数的简写。

由于Lambda只能表示一个函数,所以能写成Lambda的接口要求包含且最多只能有一个抽象函数。这样的接口可以(但不强求)用注记:@FunctionalInterface来表示,称为函数式接口。

Comparator<Person> compareAge = (p1, p2) -> p1.age-p2.age;Arrays.sort(people, compareAge);Arrays.sort(people, (p1, p2) -> p1.age-p2.age);

8. 装箱、枚举、注解 :

8.1 包装类 :

基本类型的包装类将基本类型(primitive type) 包装成Object(引用类型),共8类:Boolean, Byte, Short, Character, Integer, Long, Float, Double

Integer I = new Integer(10);

装箱与拆箱:

  • 装箱(Boxing)Integer I = 10;
  • 拆箱(Unboxing)int i= I;

实际译为:

  • Integer I= Integer.valueOf(10);
  • int i= I.intValue();
8.2 枚举 :

枚举(enum)是一种特殊的class类型,在简单的情况下,用法与其他语言的enum相似:

enumLight { Red, Yellow, Green };Light light= Light.Red;

但实际上,它生成了class Light extends java.lang.Enum

自定义枚举:

可以在enum定义体中,添加字段、方法、构造方法。

enumDirection{    EAST("东",1), SOUTH("南",2),    WEST("西",3), NORTH("北",4);    private Direction(String desc, intnum){        this.desc=desc; this.num=num;    }    private String desc;    private intnum;    public String getDesc(){ return desc; }    public intgetNum(){ return num; }}
8.3 注解 :

所有的注解都是java.lang.annotation. Annotation的子类

常用的注解,如:

  • @Override 表示覆盖父类的方法
  • @Deprecated 表示过时的方法
  • @SuppressWarnings表示让编译器不产生警告

自定义注解,比较复杂:

public @interface Author {
String name();
}


9. Java对象相等性判断:

  • String对象有缓存池;

    • 判断相等,一定不要用==,要用equals;
    • 但是字符串常量(String literal)及字符串常量会进行内部化(interned),相同的字符串常量是==的;
      String hello = "Hello", lo = "lo";System.out.println( hello == "Hello"); //trueSystem.out.println( Other.hello== hello ); //trueSystem.out.println( hello == ("Hel"+"lo") ); //trueSystem.out.println( hello == ("Hel"+lo) ); //falseSystem.out.println( hello == new String("Hello")); //falseSystem.out.println( hello == ("Hel"+lo).intern()); //true
  • Integer比较有缓存( -128 and 127 (inclusive));

  • 枚举类型:内部进行了惟一实例化,所以可以直接判断;

  • 引用对象:

    • 是直接看两个引用是否一样;
    • 如果要判断内容是否一样,则要重写equals方法;
    • 如果重写equals方法,则最好重写hashCode()方法。

References. :

  • [ 1 ]. Coursera | Java程序设计 | PKU
  • [ 2 ]. https://www.cnblogs.com/chenssy/p/3388487.html
  • [ 3 ]. http://www.cnblogs.com/chenssy/p/3390871.html
  • [ 4 ]. http://blog.oneapm.com/apm-tech/226.html
原创粉丝点击