java-基础

  1. 数据类型
    1.1 基本类型

    boolean/1
    byte/8
    char/16
    short/16
    int/32
    float/32
    long/64
    double/64

1.2 包装类型

基本类型都有对应的包装类型,基本类型与包装类型之间的赋值自动使用自动装箱和拆箱完成。
1
2

Integer x = 2; // 装箱
int y = x; // 拆箱

new Integer(123) 与 Integer.valueOf(123) 的区别在于:

new Integer(123) 每次都会新建一个对象;
Integer.valueOf(123) 会使用缓存池中的对象,多次调用会取得同一个对象的引用。编译器会在自动装箱过程调用 valueOf() 方法,因此多个Integer实例使用自动装箱来创建并且值相同,那么就会引用相同的对象。实现就是先判断值是否在缓存池中,如果在的话就直接返回缓存池的内容,如果不在则放入缓存池中。
在 Java 8 中,Integer 缓存池的大小默认为 -128\~127。 

Integer x = new Integer(123);
Integer y = new Integer(123);
System.out.println(x == y); // false
Integer z = Integer.valueOf(123);
Integer k = Integer.valueOf(123);
System.out.println(z == k); // true
static final int low = -128;
static final int high;
static final Integer cache[];

static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty(“java.lang.Integer.IntegerCache.high”);
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;

cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
    cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;

}
1.3 String类型

内部使用 char 数组存储数据,该数组被声明为final,数组初始化之后就不能再引用其它数组,并且 String 内部没有改变 value 数组的方法,因此可以保证 String 不可变。

public final class String
implements java.io.Serializable, Comparable, CharSequence {
/** The value is used for character storage. */
private final char value[];
1.3.1 不可变的好处

1)String Pool的需要

如果一个 String 对象已经被创建过了,那么就会从String Pool中取得引用。只有 String 是不可变的,才可能使用 String Pool。

2)安全性

String 经常作为参数,String 不可变性可以保证参数不可变。例如在作为网络连接参数的情况下如果 String 是可变的,那么在网络连接过程中,String 被改变,改变 String 对象的那一方以为现在连接的是其它主机,而实际情况却不一定是。

3)线程安全

String 不可变性天生具备线程安全。

1.3.2 String, StringBuffer and StringBuilder

1)可变性

String 不可变
StringBuffer 和 StringBuilder 可变 

2)线程安全

String 不可变,因此是线程安全的
StringBuilder 不是线程安全的
StringBuffer 是线程安全的,内部使用 synchronized 进行同步 

3)重载“+”和StringBuilder

Java中只有“+”和“+=”为重载过的操作符,重载过的“+”在进行字符串拼接时,都会创建StringBuilder,并使用StringBuilder的append方法,最后调用toString生成结果,并存为String类型。StringBuilder只会有一个对象,还可以预先指定其大小,这样可以避免多次重新分配缓冲(拼接一次重新分配一次)。
1.3.3 String Pool 与 String.intern()

String Pool是字符串常量池,存放着所有字面量,是JVM常量池中的一种(类文件常量池、全局字符串常量池、运行时常量池),不同版本jdk的字符串常量池的位置是不一致的:

jdk1.6版本的字符串常量池在永久代中,所以在编译期创建字符串常量时常量池存放的就是直接的字符串对象。
jdk1.7以及以上版本的字符串常量池移动到了heap区,所以在创建字符串常量时常量池存放的是堆中字符串对象的引用 

在这样的背景下,字符串的本地方法intern:

jdk1.6会从字符串常量池中查询当前字符串是否存在,若不存在就会将当前字符串放入常量池中,存在则返回引用。
jdk1.7,若不存在就会将当前字符串引用放入常量池中,存在则返回引用。 

public static void main(String[] args) {
String s = new String(“1”);
s.intern();
String s2 = “1”;
System.out.println(s == s2);

String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);

}
jdk6下false false
jdk7下false true

分析:
第1题jdk6:

new String("1")一共会创建两个字符串对象(前提是 String Pool 中还没有 "1" 字符串对象)。
"1" 属于字符串字面量,因此编译时期会在 String Pool 中创建一个字符串对象,指向这个 "1" 字符串字面量;
而使用 new 的方式会在堆中创建一个字符串对象。
s.intern()方法会将s的字符串值在String Pool中查询,因为已经存在该字符串对象,故无任何作用
s2="1"也拿去查询String Pool,因为字符串"1"已经存在,所以s2得到的对象是String Pool中的对象
s的值是heap的对象引用,s2的值为String Pool中的对象引用,二者不一致故为false 

第2题jdk7:

由于jdk7的String Pool在heap中,存放的是引用。s3创建完后,String Pool中无"11"的字符串对象引用
s3.intern将s3的对象引用存放到String Pool中
s4查询String Pool返回s3的对象引用,故二者引用相同 
  1. 运算
    2.1 参数传递

Java 的参数是以值传递的形式传入方法中,而不是引用传递。

一个实例化引用是一个指针,存储的是对象的地址。在将一个参数传入一个方法时,本质上是将对象的地址以值的方式传递到形参中,因此在方法中使指针引用其它对象,那么这两个指针此时指向的是完全不同的对象,在一方改变其所指向对象的内容时对另一方没有影响;而在方法中对该指针引用的对象内容进行更改的话,则指向的对象的内容发生改变。
2.2 基本运算符

赋值运算符
算术运算符
关系操作符
逻辑操作符
按位操作符
    按位与(&)
    按位或(|)
    按位异或(^)
    按位非 
移位操作符(移位大于等于其位数时,应该对其取余)
    有符号左移操作符<<(右边补全0;左边看移位后的结果)
    有符号右移操作符>>(左边补1或0看原符号位,为1补全1,为0补全0;右边看移位后的结果)
    无符号右移操作符>>>(左边补全0;右边看移位后的结果) 
三元操作符 

2.3 直接常量

1)直接常量的标志类型

后缀大写的L表示long,D表示double,八进制数以前缀0后面跟随0-9来表示,十六进制数以前缀0x后面跟随0-9或小写来表示。

字面量1.1是属于double类型,不能将1.1直接赋值给float常量,编译器通常把指数作为双精度处理,所以必须要在尾部加f,因为这是向下转型(精度高到精度低),1.1f直接常量才是float类型。

2)指数记数法

e代表10的幂次,1.39e-43f表示1.39乘于10的-43次方
2.4 类型转换操作符

Long l = (long)r;如果是不同类型的则需要响应的方法来:Integer.valueOf()和String.valueOf();
布尔型不能和任何数据类型相互转换;
在对基本数据类型算术时,只要类型比int小都会隐式地自动将这些值转换为int,并且不能隐式地将int向下转型为short精度小的数据类型,但是使用+=或++可以执行隐式类型转换 
  1. 访问权限修饰符、static关键字、final关键字
    3.1 访问权限修饰符

访问权限修饰词有public、protected、default、private,类或类中的成员(字段以及方法)加上访问修饰符,修饰类的只能是public或default(内部类除外)。

图片说明
3.2 final关键字

1)修饰数据

声明数据为常量,可以是编译时常量,也可以是在运行时被初始化后不能被改变的常量。但是,如果是随机生成的值赋予final修饰的数据时,那么就会在运行时确定,而不是在编译期。

对于基本类型,final 使数值不变;
对于引用类型,final 使引用不变,也就不能引用其它对象,但是被引用的对象本身是可以修改的。 

final int x = 1;
// x = 2; // cannot assign value to final variable ‘x’
final A y = new A();
y.a = 1;

2)修饰方法

声明方法不能被子类重写。

private 方法隐式地被指定为 final,如果在子类中定义的方法和基类中的一个 private 方法签名相同,此时子类的方法不是重写基类方法,而是在子类中定义了一个新的方法。

3)修饰类

声明类不允许被继承,由于final修饰的类表明不能被继承,所以里面所有的成员变量和方法都隐式指定为final的。
3.3 static关键字

1)静态变量

静态变量:又称为类变量,也就是说这个变量属于类的,类所有的实例都共享静态变量,可以直接通过类名来访问它。静态变量在内存中只存在一份,存在区域:1.6在方法区,1.7后在heap中。
实例变量:每创建一个实例就会产生一个实例变量,它与该实例同生共死。 

public class A {

private int x;         // 实例变量
private static int y;  // 静态变量

public static void main(String[] args) {
    // int x = A.x;  // Non-static field 'x' cannot be referenced from a static context
    A a = new A();
    int x = a.x;
    int y = A.y;
}

}

2) 静态方法

静态方法在类加载的时候就存在了,它不依赖于任何实例。

静态方法必须有实现,也就是说它不能是抽象方法。

public abstract class A {
public static void func1(){
}
// public abstract static void func2(); // Illegal combination of modifiers: ‘abstract’ and ‘static’
}

只能访问所属类的静态字段和静态方法,方法中不能有 this 和 super 关键字。

public class A {

private static int x;
private int y;

public static void func1(){
    int a = x;
    // int b = y;  // Non-static field 'y' cannot be referenced from a static context
    // int b = this.y;     // 'A.this' cannot be referenced from a static context
}

}

3) 静态语句块

静态语句块在类加载过程中的初始化时运行一次。

public class A {
static {
System.out.println(“1”);
}

public static void main(String[] args) {
    A a1 = new A();
    A a2 = new A();
}

}

4) 静态内部类

非静态内部类依赖于外部类的实例,而静态内部类不需要,不能访问外部类的非静态的变量和方法,可以理解为静态方法和变量,是属于类的。

public class OuterClass {

class InnerClass {
}

static class StaticInnerClass {
}

public static void main(String[] args) {
    // InnerClass innerClass = new InnerClass(); // 'OuterClass.this' cannot be referenced from a static context
    OuterClass outerClass = new OuterClass();
    InnerClass innerClass = outerClass.new InnerClass();
    StaticInnerClass staticInnerClass = new StaticInnerClass();
}

}

5) 静态导包

在使用静态变量和方法时不用再指明 ClassName,从而简化代码,但可读性大大降低。
1

import static com.xxx.ClassName.*
4. 对象的初始化和回收
4.1 构造器

该方法是在创建对象时自动调用的特殊方法,通过提供构造器,类的设计者都确定每个对象都会得到初始化。不接受任何参数的构造器叫默认构造器,如果不写默认的构造器那么会自动给他加上一个无参的构造器,只写有参的构造器那么就不会自动加上一个无参的构造器。

this关键字

this关键字只能在方法内部使用,表示对“调用方法的那个对象,即当前对象”的引用。

用法可以是对参数和当前对象的成员加以区分,在调用当前对象的方法的时候编译器会自动在该方法前添加this关键字;还可以是在构造方法中使用,用来调用重载构造方法,即构造方法中又调用当前对象的另外的重载构造方法,比如:this(a)调用参数为a的类型的构造方法。

super关键字

表示基类的对象。

用法是可以使用 super() 函数访问父类的构造函数,从而委托父类完成一些初始化的工作;还可以是如果子类重写了父类的某个方法,可以通过使用 super 关键字来引用父类的方法实现。
4.2 对象的初始化

初始化顺序(父类与子类):

父类静态变量->父类静态代码块->子类静态变量->子类静态代码块->父类成员变量->父类非静态代码块->父类构造方法->子类成员变量->子类非静态代码块->子类构造器

成员变量如果没有给初始值,编译器会自动根据不同的数据类型赋一个默认值;而局部变量必须被初始化,和JVM的类加载机制有关。final修饰的变量,编译器常量必须初始化(可以是初始化块、声明初始化、构造方法初始化)。
4.3 对象的回收

Java的finalize()和C++的析构函数的区别:

Java的finalize在垃圾回收器准备号释放对象占用的存储空间前调用,并在下一次垃圾回收动作发生时才会真正回收对象占用的内存,换句话说只改变状态,而且对象可能因为在程序没有用完存储空间前都不会回收。C++的析构函数在delete对象前调用析构函数来做对象内部的对象内存释放
finalize的作用在于清理本地方法创建的对象
System.gc()用来强制进行终结动作,这个方法只是提醒虚拟机,程序员希望你在这回收一下对象,但回不回收还是虚拟机来决定,也就是说程序员对回不回收没有绝对的控制权。不建议大家反复用System.gc(),最好的解决办法就是把不用的对象设置为null,便于虚拟机作出判断。 

4.4 类的加载和初始化

java采用一种不同于C++的加载方式,每个类的编译代码都在存在于独立的文件中,只有在使用该程序代码时才会被加载,即类的代码在初次使用时被加载。例如:初始化前才加载,加载发生于创建类的第一个对象之时,或访问类的static域或方法时加载。
5. 复用

通过创建新类来复用代码,从而不必重头开始写起。
两种方法:

第一种:只需在新的类中产生现有类的对象,由于新的类是由现有类的对象组成,又叫做组合
第二种:按照现有类的类型来创建新类,无需改变现有类的形式,采用现有类的形式并在其中添加新代码,这种叫做继承 

5.1 组合语法

新类和基类是has-a的关系

在新类中初始化引用的方式:

在定义对象的地方,意味着它们总是能够在构造器被调用之前被初始化(成员变量的地方)。
在类的构造器中。
在setter方法中,即就在正要使用这些对象之前。 

5.2 继承语法

派生类是基类的is-a关系。

1)初始化基类

当创建了一个导出类的对象时,该对象包含了一个基类的子对象。这个子对象与你用基类直接创建的对象是一样的,二者区别是后者来自于外部,而基类的子对象被包装在导出类对象内部。

初始化基类无参构造器(super())

如果基类只有无参构造器(没有显式也会默认添加),那么会默认在派生类的构造器最开头中插入对基类构造器的隐式调用。所以基类的构造器比派生类要早运行(运行基类构造器时,派生类的成员均未初始化)。

带参数的构造器(super(…))

如果基类有有参构造器,而又没有显式无参构造器,那么必须在派生类的构造器最开头显式插入对基类有参构造器的调用,不然会提示基类没有无参构造器而报错。

2)继承注意事项

对于普通方法普通成员变量均正常继承
对于基类的静态方法和静态域都会继承,但是会被隐藏,只能通过基类来调用静态方法和域,派生类可以有和基类一样的静态方法,但是二者均无关系(不是重写的关系),与各自类有关。
对于基类的final、private方法和域都不能继承 

5.3 代理

组合和继承的中庸之道。委托类和代理类继承同一个抽象类,将委托类对象注入代理类,并由代理类调用该对象的所有方法,这样就可以在代理类中动态扩展委托类的功能或改变行为。
5.4 重写与重载

重写(Override)

存在于继承体系中,指子类实现了一个与父类在方法声明上完全相同的一个方法。
为了满足里式替换原则,重写有有以下三个限制:

子类方法的访问权限必须大于等于父类方法。
子类方法的返回类型必须是父类方法返回类型或为其子类型。
派生类只能抛出在基类方法的异常说明里列出的那些异常,或抛出的异常是基类说明的异常的派生类。 

重载(Overload)

存在于同一个类中,指一个方法与已经存在的方法名称上相同,但是参数类型、个数、顺序至少有一个不同。

应该注意的是,返回值不同,其它都相同不算是重载。 如果Java的基类拥有多个重载方法,那么派生类也有该方法的重载方法,派生类不会屏蔽基类的重载方法,重载机制仍然工作。
5.5 Object通用方法

所有类默认继承Object超类,并继承其通用方法。

public native int hashCode()

public boolean equals(Object obj)

protected native Object clone() throws CloneNotSupportedException

public String toString()

public final native Class<?> getClass()

protected void finalize() throws Throwable {}

public final native void notify()

public final native void notifyAll()

public final native void wait(long timeout) throws InterruptedException

public final void wait(long timeout, int nanos) throws InterruptedException

public final void wait() throws InterruptedException
5.6.1 equals()

1)等价关系

自反性
1

x.equals(x); // true

对称性
1

x.equals(y) == y.equals(x); // true

传递性
1
2

if (x.equals(y) && y.equals(z))
x.equals(z); // true;

一致性

多次调用 equals() 方法结果不变
1

x.equals(y) == x.equals(y); // true

与 null 的比较

对任何不是 null 的对象 x 调用 x.equals(null) 结果都为 false
1

x.equals(null); // false;

2)等价与相等

对于基本类型,== 判断两个值是否相等,基本类型没有 equals() 方法。
对于引用类型,== 判断两个变量是否引用同一个对象,而 equals() 判断引用的对象是否等价。 

Integer x = new Integer(1);
Integer y = new Integer(1);
System.out.println(x.equals(y)); // true
System.out.println(x == y); // false

3)实现

检查对象参数是否为空,是则返回false;
检查是否为同一个对象的引用,如果是直接返回 true;
检查是否是同一个类型,如果不是,直接返回 false;
将 Object 对象进行转型,判断每个关键域是否相等。 

public class EqualExample {

private int x;
private int y;
private int z;

public EqualExample(int x, int y, int z) {
    this.x = x;
    this.y = y;
    this.z = z;
}

<a href="/profile/992988" data-card-uid="992988" class="js-nc-card" target="_blank">@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    EqualExample that = (EqualExample) o;

    if (x != that.x) return false;
    if (y != that.y) return false;
    return z == that.z;
}

}
5.6.2 hashCode()

hashCode() 返回散列值,而 equals() 是用来判断两个对象是否等价。

等价的两个对象散列值一定相同,但是散列值相同的两个对象不一定等价;不等价的两个对象散列值不一定不相同,散列值不同的两个对象肯定不等价。

1)必要性

在覆盖 equals() 方法时应当总是覆盖 hashCode() 方法,覆盖的hashCode方法和equals所比较的对象有很大的联系保证等价的两个对象散列值也相等。下面的代码中,新建了两个等价的对象,并将它们添加到 HashSet 中。我们希望将这两个对象当成一样的,只在集合中添加一个对象,但是因为 EqualExample 没有实现 hasCode() 方法,因此这两个对象的散列值是不同的,最终导致集合添加了两个等价的对象。

EqualExample e1 = new EqualExample(1, 1, 1);
EqualExample e2 = new EqualExample(1, 1, 1);
System.out.println(e1.equals(e2)); // true
HashSet set = new HashSet<>();
set.add(e1);
set.add(e2);
System.out.println(set.size()); // 2

2)实现

理想的散列函数应当具有均匀性,即不相等的对象应当均匀分布到所有可能的散列值上。这就要求了散列函数要把所有域的值都考虑进来。可以将每个域都当成 R 进制的某一位,然后组成一个 R 进制的整数。R 一般取 31,因为它是一个素数,如果是偶数的话,当出现乘法溢出,信息就会丢失,因为与 2 相乘相当于向左移一位。

一个数与 31 相乘可以转换成移位和减法:31*x == (x<<5)-x,编译器会自动进行这个优化。

@Override
public int hashCode() {
int result = 17;
result = 31 * result + x;
result = 31 * result + y;
result = 31 * result + z;
return result;
}

5.6.3 toString()

默认返回 ToStringExample

后面的数值为散列码的无符号十六进制表示。

public class ToStringExample {

private int number;

public ToStringExample(int number) {
    this.number = number;
}

}
1
2

ToStringExample example = new ToStringExample(123);
System.out.println(example.toString());
1

ToStringExample@4554617c
5.6.4 clone()

1)cloneable

clone() 是 Object 的 protected 方法,它不是 public,一个类不显式去重写 clone(),其它类就不能直接去调用该类实例的 clone() 方法。重写需要实现 Cloneable 接口,Cloneable 接口只是规定,如果一个类没有实现 Cloneable 接口又调用了 clone() 方法,就会抛出 CloneNotSupportedException。

重写 clone() 得到以下实现:

public class CloneExample implements Cloneable {
private int a;
private int b;

<a href="/profile/992988" data-card-uid="992988" class="js-nc-card" target="_blank">@Override
public Object clone() throws CloneNotSupportedException {
    return super.clone();
}

}

  1. 直接拷贝

直接赋值。在Java中,A a1 = a2,我们需要理解的是这实际上复制的是引用,也就是 说a1和a2指向的是同一个对象。因此,当a1变化的时候,a2 里面的成员变量也会跟 着变化。

3)浅拷贝(复制引用但不复制引用的对象)

创建一个新对象,复制引用但不复制引用的对象。因此,原始对象及其副本引用同一个对象。

public class ShallowCloneExample implements Cloneable {

private int[] arr;

public ShallowCloneExample() {
    arr = new int[10];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = i;
    }
}

public void set(int index, int value) {
    arr[index] = value;
}

public int get(int index) {
    return arr[index];
}

<a href="/profile/992988" data-card-uid="992988" class="js-nc-card" target="_blank">@Override
protected ShallowCloneExample clone() throws CloneNotSupportedException {
    return (ShallowCloneExample) super.clone();
}

}

ShallowCloneExample e1 = new ShallowCloneExample();
ShallowCloneExample e2 = null;
try {
e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 222

4)深拷贝

深拷贝不仅复制对象本身,而且复制对象包含的引用指向的所有对象。

public class DeepCloneExample implements Cloneable {

private int[] arr;

public DeepCloneExample() {
    arr = new int[10];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = i;
    }
}

public void set(int index, int value) {
    arr[index] = value;
}

public int get(int index) {
    return arr[index];
}

<a href="/profile/992988" data-card-uid="992988" class="js-nc-card" target="_blank">@Override
protected DeepCloneExample clone() throws CloneNotSupportedException {
    DeepCloneExample result = (DeepCloneExample) super.clone();
    result.arr = new int[arr.length];
    for (int i = 0; i < arr.length; i++) {
        result.arr[i] = arr[i];
    }
    return result;
}

}

DeepCloneExample e1 = new DeepCloneExample();
DeepCloneExample e2 = null;
try {
e2 = e1.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
e1.set(2, 222);
System.out.println(e2.get(2)); // 2

5)clone() 的替代方案

使用 clone() 方法来拷贝一个对象即复杂又有风险,它会抛出异常,并且还需要类型转换。可以使用拷贝构造函数或者拷贝工厂来拷贝一个对象。

public class CloneConstructorExample {

private int[] arr;

public CloneConstructorExample() {
    arr = new int[10];
    for (int i = 0; i < arr.length; i++) {
        arr[i] = i;
    }
}

public CloneConstructorExample(CloneConstructorExample original) {
    arr = new int[original.arr.length];
    for (int i = 0; i < original.arr.length; i++) {
        arr[i] = original.arr[i];
    }
}

public void set(int index, int value) {
    arr[index] = value;
}

public int get(int index) {
    return arr[index];
}

}

CloneConstructorExample e1 = new CloneConstructorExample();
CloneConstructorExample e2 = new CloneConstructorExample(e1);
e1.set(2, 222);
System.out.println(e2.get(2)); // 2
6. 多态

多态指对象的多种形态,将接口和实现分离开来,消除类型之间的耦合关系,创建可扩展的程序,无论在项目最初创建时还是在需要添加新功能时都可以“生长”的程序。

多态有编译时多态和运行时多态,编译时多态指方法重载,运行时多态指对象的向上转型和向下转型。
6.1 向上转型
1

Wind a = new Wind() ; Instrucmet b = a

这种将基类的引用指向派生类的对象,称之为向上转型。在向上转型的过程中,总是从一个较专用类型向较通用类型转换,符合里氏替换原则,所以是安全可靠不会报错的。

常用方法是在一个方法中参数为基类,根据传入的子类来实现不同的行为。
6.2 方法调用绑定

一个方法的调用与方法所在的类关联起来称为绑定。若在程序执行前进行绑定(编译器确定),叫做前期绑定;若在程序运行时根据对象的类型进行绑定,叫做动态绑定,向上转型中识别传进来的引用并调用相应的方法是根据动态绑定来的。

Java中除了static方法和final方法(private方法属于final方法)之外,其它所有的方法都是动态绑定,自动发生。
6.3 缺陷

1)丢失派生类方法

向上转型时,如果派生类出现的方法在基类中未有,那么向上转型后的引用就不能掉用该方法,如果派生类重写基类的方法,那么引用调用的方法会是派生类的。

2)“覆盖”私有方法

因为private方法被自动认为是final方法,而且对导出类是屏蔽的,虽然可以在派生类中写同名的方法,但这不是覆盖方法,而是和基类完全不一样的同名方法。
如果向上转型,那么就会调用基类的private方法。

3)域和静态方法

只有普通的方法调用可以是多态的,任何域访问操作都将被编译器解析,因此不是多态的,如果某个方法是静态的,它的行为就不具有多态性。
总的来说,派生类覆盖基类的静态方法和域,在向上转型调用时只会调用基类的域和静态方法。
7. 异常处理

用强制规定的形式来消除错误处理过程中随心所欲的因素,从而避免在构建大型、健壮、可维护的程序中造成困难,便引入了异常处理。

图片说明

Error用来表示编译时和系统错误(不关心);
Exception是可以被抛出的基本类型
    已检查异常(非运行时异常),把异常传递给控制台(通过异常声明直接将异常抛出到上一级:控制台),把受检查异常转换为非检查异常(包装后抛出,可以通过getCause()来获取原始异常)
    非检查异常(运行时异常),不捕获运行时异常,它就会穿越所有的执行路径直达main方法,在程序退出前,将调用异常的printStackTrace方法将错误信息传给System.err。 

7.1 异常说明

Java希望把方法可能抛出的异常告知使用此方法的人,使得调用者可以清楚知道此方法可能存在的异常并加以捕获,其语法是在指定的方法后面加上throws <异常类别>。
可以声明方法将抛出异常,实际上不抛出,这样做的好处是:为异常占位,以后可以抛出异常而不用修改已有的代码,在定义抽象类和接口很有用,这样派生类和接口实现就能够抛出预先声明的异常。
7.2 抛出异常

异常情形是指阻止当前方法或作用域继续执行的问题,抛出异常有两种方式:
一段程序有可能会出错并隐式地抛出异常,也可以显式用关键字throw抛出异常,程序执行被终止,并由异常处理机制接管程序进行恢复或处理。

在抛出异常时(用new在堆上创建异常对象),此时也伴随这存储空间地分配和构造器的调用,其中一个含参构造器以字符串作为参数,把相关信息放入异常对象的构造器。
7.3 捕获和处理异常

1)try块

如果在方法内部抛出异常,或者在方法内部调用的其它方法抛出了异常(交给上一级,即本方法处理),这个方法将在抛出异常的过程中结束,捕获异常的区域用try块。

2)catch块

抛出异常在某处得到处理,该处是异常处理程序,异常处理程序紧跟在try块后,以catch表示,catch的参数是异常对象。
抛出异常的时候,异常处理系统会按照代码的书写顺序找出最近的处理程序,找到后便不再继续寻找。查找的时候并不要求抛出的异常同处理程序所声明的异常完全匹配,派生类的对象也可以匹配基类的处理程序。

3)finally

finally子句,无论程序发生了什么都会被调用。
当要把除内存之外的资源恢复到它们的初始状态时,就要用到finally,资源包括:已经打开的文件或网络连接,屏幕画的图像等。

在return中使用finally子句:

当try语句里有return语句,finally语句中有修改return参数的语句时,在try语句遇到return语句返回,在真正返回之前会执行finally子句,此时修改其返回参数无济于事。
当try语句里有return语句,finally语句中也有return的语句,finally将会替代try语句中return的值。
try里有return,finally里没有,正常顺序执行。 

异常丢失的情况:

如果在finally里抛出异常将会覆盖try或catch子句中抛出的异常。
7.4 自定义异常

要创建自己的异常类,则必须从已有的异常类继承。
可以使用util的logging工具将异常输出记录到日志。
7.5 异常链

  1. Exception方法

获取异常信息的:

String getMessage()
String getLocalizedMessage()
String toString()

调用栈轨迹,从下至上,把你带到异常抛出地点:

void printStackTrace() --输出到标准错误流(System.err)
void printStackTrace(PrintStream)
void printStackTrace(PrintWriter)
void fillInStackTrace() --用于重新抛出错误或异常,轨迹是重新抛出的位置开始

2)异常链

在捕获一个异常后,将其包装在要重新抛出的异常中并抛出,并且希望把原始异常的信息保存下来,这样称为异常链。

Throwable的子类在构造器都可以接受一个cause对象作为参数,这个对象参数就是用来表示原始异常,这样通过把原始异常传递给新的异常,这样即便重新抛出新的异常也不会丢失原始信息。在Throwable的子类中,只有三种基本的异常提供这样的构造器,是Error、Exception、RuntimeException,其它类型异常(包括自定义异常),就应该使用initCause()
方法而不是构造器,即在throw新的异常前,先调用新异常的initCause方法,参数就是原异常对象。

重写抛出后,printStackTrace方法显示的是原来异常的栈轨迹,如果要更新为重新抛出点开始的信息,那么就要在抛出点调用fillInStackTrace方法:
1

throw (Exception)e.fillInStackTrace()。
8. 内部类

将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。
8.1 成员内部类

成员内部类是最普通的内部类,它的定义为位于另一个类的内部
class Circle {
double radius = 0;

public Circle(double radius) {
    this.radius = radius;
}

class Draw { //内部类
    public void drawSahpe() {
        System.out.println("drawshape");
    }
}

}

1)访问外部类

成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员)。

不过要注意的是,当成员内部类拥有和外部类同名的成员变量或者方法时,会发生隐藏现象,即默认情况下访问的是成员内部类的成员。如果要访问外部类的同名成员,需要以下面的形式进行访问:

外部类.this.成员变量
外部类.this.成员方法

class Circle {
private double radius = 0;

public Circle(double radius) {
    this.radius = radius;
    getDrawInstance().drawSahpe(); //必须先创建成员内部类的对象,再进行访问
}

private Draw getDrawInstance() {
    return new Draw();
}

class Draw { //内部类
    public void drawSahpe() {
        System.out.println(radius); //外部类的private成员
    }
}

}

2)访问内部类

在外部类中如果要访问成员内部类的成员,必须先创建一个成员内部类的对象,再通过指向这个对象的引用来访问。

public class Test {
public static void main(String[] args) {
//第一种方式:
Outter outter = new Outter();
Outter.Inner inner = outter.new Inner(); //必须通过Outter对象来创建

    //第二种方式:
    Outter.Inner inner1 = outter.getInnerInstance();
}

}

class Outter {
private Inner inner = null;
public Outter() {

}

public Inner getInnerInstance() {
    if(inner == null)
        inner = new Inner();
    return inner;
}

class Inner {
    public Inner() {

    }
}

}

3)访问权限

内部类可以拥有private访问权限、protected访问权限、public访问权限及包访问权限。

如果成员内部类Inner用private修饰,则只能在外部类的内部访问,如果用public修饰,则任何地方都能访问;
如果用protected修饰,则只能在同一个包下或者继承外部类的情况下访问;
如果是默认访问权限,则只能在同一个包下访问。这一点和外部类有一点不一样,外部类只能被public和包访问两种权限修饰。 

8.2 局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类。

class People{
public People() {
}
}
class Man{
public Man(){
}
public People getWoman(){
class Woman extends People{ //局部内部类
int age =0;
}
return new Woman();
}
}

它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内;局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。
8.3 匿名内部类

匿名内部类应该是平时我们编写代码时用得最多的,在编写事件监听的代码时使用匿名内部类不但方便,而且使代码更加容易维护吗,jdk1.8常用lambda代替内部类。

scan_bt.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub

        }
    });
    history_bt.setOnClickListener(new OnClickListener() {

        </a><a href="/profile/992988" data-card-uid="992988" class="js-nc-card" target="_blank">@Override
        public void onClick(View v) {
            // TODO Auto-generated method stub

        }
    });</a>

代码中需要给按钮设置***对象,使用匿名内部类能够在实现父类或者接口中的方法情况下同时产生一个相应的对象,但是前提是这个父类或者接口必须存在才能这样使用。
8.4 静态内部类

静态内部类也是定义在另一个类里面的类,相当于成员内部类的多了static修饰词。

public class Test {
public static void main(String[] args) {
Outter.Inner inner = new Outter.Inner();
}
}
class Outter {
public Outter() {
}
static class Inner {
public Inner() {
}
}
}

静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值