Java面向对象编程

一、Java面向对象编程

1.1 类和对象

  • **类(Class)对象(Object)**是面向对象的核心概念。
    • 类是对一类事物的描述,是抽象的、概念上的定义。
    • 对象是实际存在的该类事物的每个个体,因而也称为实例(instance)。
  • 设计类,其实就是设计类的成员。
    • 属性 = 成员变量 = field = 域、字段
    • 方法 = 成员方法 = 函数 = method
    • 创建类的对象 = 类的实例化 = 实例化类
  • 类和对象的使用(面向对象思想落地的实现)
    • 创建类,设计类的成员。
    • 创建类的对象。
    • 通过“对象.属性”或“对象.方法”调用对象的结构。
  • 如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。
  • 类的访问机制:
    • 在一个类中的访问机制:类中的方法可以直接访问类中的成员变量。(例外:static方法访问非static,编译不通过。)
    • 在不同类中的访问机制:先创建要访问类的对象,再用对象访问类中定义的成员。

1.2 属性

  • 成员变量vs局部变量

    • 相同点:

      • 定义变量的格式:数据类型 变量名 = 变量值。
      • 先声明,后使用。
      • 变量都有其对应的作用域。
    • 不同点:

      • 在类中声明的位置的不同

        • 属性:直接定义在类的一对{}内。
        • 局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量。
      • 关于权限修饰符的不同

        • 属性:可以在声明属性时,指明其权限,使用权限修饰符。(常用的权限修饰符:private、public、缺省、protected —>封装性)
        • 局部变量:不可以使用权限修饰符。
      • 默认初始化值的情况

        • 属性:类的属性,根据其类型,都有默认初始化值。
        1
        2
        3
        4
        5
        整型(byte、short、int、long):0
        浮点型(float、double):0.0
        字符型(char):0 (或'\u0000')
        布尔型(boolean):false
        引用数据类型(类、数组、接口):null
        • 局部变量:没有默认初始化值。(意味着,我们在调用局部变量之前,一定要显式赋值。特别地:形参在调用时,我们赋值即可。)
      • 在内存中加载的位置

        • 属性:加载到堆空间中(非static)。
        • 局部变量:加载到栈空间。

1.3 方法

  • 方法的分类:按照是否有形参及返回值。

1.3.1 方法的重载(overload)

  • 同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
  • 重载的特点:与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。
1
2
3
4
5
6
7
8
9
10
11
public class PrintStream {
public static void print(int i) {……}
public static void print(float f) {……}
public static void print(String s) {……}

public static void main(String[] args) {
print(3);
print(1.2f);
print("hello!");
}
}

1.3.2 可变个数的形参

  • JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
    • JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量。
      • public static void test(int a ,String[] books)
    • JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量。
      • public static void test(int a ,String…books)
  • 声明格式:方法名(参数的类型名 …参数名)
  • 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
  • 可变个数形参的方法与同名的方法之间,彼此构成重载。
  • 可变个数形参的方法与本类中方法名相同,形参类型也相同的数组之间不构成重载。换句话说,二者不能共存。
  • 可变参数方法的使用与方法参数部分使用数组是一致的。
  • 方法的参数部分有可变形参,需要放在形参声明的最后。
  • 在一个方法的形参位置,最多只能声明一个可变个数形参。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class MethodArgsTest {

public static void main(String[] args) {
MethodArgsTest test = new MethodArgsTest();
test.show(12);
// test.show("hello");
// test.show("hello","world");
// test.show();
test.show(new String[]{"AA","BB","CC"});
}

public void show(int i){

}

public void show(String s){
System.out.println("show(String)");
}

public void show(String ... strs){
System.out.println("show(String ... strs)");
for(int i = 0;i < strs.length;i++){
System.out.println(strs[i]);
}
}
//不能与上一个方法同时存在
// public void show(String[] strs){
//
// }

//The variable argument type String of the method
//show must be the last parameter
// public void show(String ...strs,int i){
//
// }
}

1.3.3 方法参数的值传递机制

  • Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。

    • 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参。
    • 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class ValueTransferTest {

    public static void main(String[] args) {

    System.out.println("***********基本数据类型:****************");
    int m = 10;
    int n = m;

    System.out.println("m = " + m + ", n = " + n);//m = 10, n = 10

    n = 20;

    System.out.println("m = " + m + ", n = " + n);//m = 10, n = 20

    System.out.println("***********引用数据类型:****************");

    Order o1 = new Order();
    o1.orderId = 1001;

    Order o2 = o1;//赋值以后,o1和o2的地址值相同,都指向了堆空间中同一个对象实体。

    System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);//o1.orderId = 1001,o2.orderId = 1001

    o2.orderId = 1002;

    System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " +o2.orderId);//o1.orderId = 1002,o2.orderId = 1002

    }
    }

    class Order{

    int orderId;

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class ValueTransferTest1 {
    public static void main(String[] args) {
    int m = 10;
    int n = 20;
    System.out.println("m = " + m + ", n = " + n);//m = 10, n = 20
    ValueTransferTest1 test = new ValueTransferTest1();
    test.swap(m, n);
    System.out.println("m = " + m + ", n = " + n);//m = 10, n = 20
    }

    public void swap(int m,int n){
    int temp = m ;
    m = n;
    n = temp;
    }
    }

    图解:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    public class ValueTransferTest {

    public static void main(String[] args) {
    Data data = new Data();
    data.m = 10;
    data.n = 20;
    System.out.println("m = " + data.m + ", n = " + data.n);//m = 10, n = 20
    ValueTransferTest2 test = new ValueTransferTest2();
    test.swap(data);
    System.out.println("m = " + data.m + ", n = " + data.n);//m = 20, n = 10
    }

    public void swap(Data data){
    int temp = data.m;
    data.m = data.n;
    data.n = temp;
    }

    }

    class Data{
    int m;
    int n;
    }

    图解:

1.4 封装与隐藏

  • 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
  • Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:
    • 隐藏一个类中不需要对外提供的实现细节。
    • 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作。
    • 便于修改,增强代码的可维护性。
  • 封装性的体现,需要权限修饰符来配合。
    • Java规定的4种权限(从小到大排列):private、缺省、protected 、public 。
    • 4种权限可以用来修饰类及类的内部结构:属性、方法、构造器、内部类。
    • 具体的,4种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类;修饰类的话,只能使用:缺省、public。

1.5 构造器(或构造方法)

  • 构造器的特征
    • 它具有与类相同的名称。
    • 它不声明返回值类型。(与声明为void不同)
    • 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值。
  • 根据参数不同,构造器可以分为如下两类:
    • 隐式无参构造器(系统默认提供)
    • 显式定义一个或多个构造器(无参、有参)
  • 注意:
    • Java语言中,每个类都至少有一个构造器。
    • 默认构造器的修饰符与所属类的修饰符一致。
    • 一旦显式定义了构造器,则系统不再提供默认构造器。
    • 一个类可以创建多个重载的构造器。
    • 父类的构造器不可被子类继承。

1.6 JavaBean

  • JavaBean是一种Java语言写成的可重用组件。
  • 所谓javaBean,是指符合如下标准的Java类:
    • 类是公共的。
    • 有一个无参的公共的构造器。
    • 有属性,且有对应的get、set方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Customer {

private int id;
private String name;

public Customer(){

}
public void setId(int i){
id = i;
}
public int getId(){
return id;
}
public void setName(String n){
name = n;
}
public String getName(){
return name;
}

}

1.7 this的使用

  • 在Java中,this关键字比较难理解,它的作用和其词义很接近。
    • 它在方法内部使用,即这个方法所属对象的引用。
    • 它在构造器内部使用,表示该构造器正在初始化的对象。
  • this 可以调用类的属性、方法和构造器。
  • 在类的方法中,我们可以使用”this.属性”或”this.方法”的方式,调用当前对象属性或方法。但是,通常情况下,我们都选择省略”this.”。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式的使用”this.变量”的方式,表明此变量是属性,而非形参。
  • 在类的构造器中,我们可以使用”this.属性”或”this.方法”的方式,调用当前正在创建的对象属性或方法。但是,通常情况下,我们都选择省略”this.”。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式的使用”this.变量”的方式,表明此变量是属性,而非形参。
  • this调用构造器:
    • 我们在类的构造器中,可以显式的使用”this(形参列表)”方式,调用本类中指定的其他构造器
    • 构造器中不能通过”this(形参列表)”方式调用自己。
    • 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了”this(形参列表)”。
    • 规定:”this(形参列表)”必须声明在当前构造器的首行。
    • 构造器内部,最多只能声明一个”this(形参列表)”,用来调用其他的构造器。

1.8 继承性

  • 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
  • 此处的多个类称为子类(派生类),单独的这个类称为父类(基类或超类)。
  • 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类的结构而已。
  • 子类继承父类以后,还可以声明自己特有的属性或方法:实现功能的拓展。
  • Java中关于继承性的规定:
    • 一个类可以被多个子类继承。
    • Java中类的单继承性:一个类只能有一个父类。
    • 子父类是相对的概念。
    • 子类直接继承的父类,称为:直接父类。间接继承的父类称为:间接父类。
    • 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法。
    • 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类。
    • 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类。
    • 意味着,所有的java类具有java.lang.Object类声明的功能。
  • 作用:
    • 继承的出现减少了代码冗余,提高了代码的复用性。
    • 继承的出现,更有利于功能的扩展。
    • 继承的出现让类与类之间产生了关系,提供了多态的前提。

1.9 方法的重写(override/overwrite)

  • 重写:子类继承父类以后,可以对父类中同名同参数的方法,进行覆盖操作。
  • 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表。
  • 重写以后,当创建子类对象以后,通过子类对象调用子父类中的同名同参数的方法时,实际执行的是子类重写父类的方法。
  • 子类重写的方法的方法名和形参列表与父类被重写的方法的方法名和形参列表相同。
  • 子类重写的方法的权限修饰符不小于父类被重写的方法的权限修饰符。(特殊情况:子类不能重写父类中声明为private权限的方法)
  • 返回值类型:
    • 父类被重写的方法的返回值类型是void,则子类重写的方法的返回值类型只能是void。
    • 父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。
    • 父类被重写的方法的返回值类型是基本数据类型(比如:double),则子类重写的方法的返回值类型必须是相同的基本数据类型(必须也是double)。
  • 子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型。
  • 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写)。

1.10 super的使用

  • super调用属性和方法
    • 我们可以在子类的方法或构造器中。通过使用”super.属性”或”super.方法”的方式显式的调用父类中声明的属性或方法。但是,通常情况下,我们习惯省略”super.”。
    • 特殊情况:当子类和父类中定义了同名的属性时,我们要想在子类中调用父类中声明的属性,则必须显式的使用”super.属性”的方式,表明调用的是父类中声明的属性。
    • 特殊情况:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用”super.方法”的方式,表明调用的是父类中被重写的方法。
  • super调用构造器
    • 我们可以在子类的构造器中显式的使用”super(形参列表)”的方式,调用父类中声明的指定的构造器。
    • “super(形参列表)”的使用,必须声明在子类构造器的首行!
    • 我们在类的构造器中,针对于”this(形参列表)”或”super(形参列表)”只能二选一,不能同时出现。
    • 在构造器的首行,没有显式的声明”this(形参列表)”或”super(形参列表)”,则默认调用的是父类中空参的构造器:super()
    • 在类的多个构造器中,至少有一个类的构造器中使用了”super(形参列表)”,调用父类中的构造器。

1.11 多态性

  • 对象的多态性:父类的引用指向子类的对象。
    • 可以直接应用在抽象类和接口上。
  • Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。简称:编译时,看左边;运行时,看右边。
    • 若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)。
    • 多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法);“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
  • 多态性的使用前提: ① 类的继承关系 ② 方法的重写
  • 对象的多态性,只适用于方法,不适用于属性(编译和运行都看左边)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
public class Person {
String name;
int age;

int id = 1001;

public void eat(){
System.out.println("人:吃饭");
}

public void walk(){
System.out.println("人:走路");
}

}

public class Man extends Person{

boolean isSmoking;

int id = 1002;

public void earnMoney(){
System.out.println("男人负责挣钱养家");
}

public void eat(){
System.out.println("男人多吃肉,长肌肉");
}

public void walk(){
System.out.println("男人霸气的走路");
}

}

public class Woman extends Person{

boolean isBeauty;

public void goShopping(){
System.out.println("女人喜欢购物");
}

public void eat(){
System.out.println("女人少吃,为了减肥");
}

public void walk(){
System.out.println("女人窈窕的走路");
}
}

public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.eat();//人:吃饭
Man man = new Man();
man.eat();//男人多吃肉,长肌肉
man.age = 25;
man.earnMoney();//男人负责挣钱养家

System.out.println("*******************");
//对象的多态性:父类的引用指向子类的对象
Person p2 = new Man();
// Person p3 = new Woman();
//多态的使用:当调用子父类同名同参数的方法时,实际执行的是子类重写父类的方法 ---虚拟方法调用
p2.eat();//男人多吃肉,长肌肉
p2.walk();//男人霸气的走路
// p2.earnMoney();调用不了
System.out.println(p2.id);//1001
}
}
  • 虚拟方法调用(多态情况下)
    • 子类中定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的。

1.12 instanceof 操作符

  • a instanceof A:判断对象a是否是类A的实例。如果是,返回true;如果不是,返回false。
    • 使用情境:为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型。如果返回false,不进行向下转型。
    • 如果 a instanceof A返回true,则 a instanceof B也返回true,其中,类B是类A的父类。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class PersonTest {
public static void main(String[] args) {

Person p1 = new Person();
Person p2 = new Man();
//不能调用子类所特有的方法、属性:编译时,p2是Person类型。
p2.name = "Tom";
// p2.earnMoney();
// p2.isSmoking = true;
//有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致
//编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
//如何才能调用子类特有的属性和方法?
//向下转型:使用强制类型转换符。
Man m1 = (Man)p2;
m1.earnMoney();
m1.isSmoking = true;
//使用强转时,可能出现ClassCastException的异常。
if(p2 instanceof Woman){
Woman w1 = (Woman)p2;
w1.goShopping();
System.out.println("******Woman******");
}

if(p2 instanceof Man){
Man m2 = (Man)p2;
m2.earnMoney();
System.out.println("******Man******");
}

if(p2 instanceof Person){
System.out.println("******Person******");
}
if(p2 instanceof Object){
System.out.println("******Object******");
}

//练习:
//问题一:编译时通过,运行时不通过
//举例一:
// Person p3 = new Woman();
// Man m3 = (Man)p3;
//举例二:
// Person p4 = new Person();
// Man m4 = (Man)p4;

//问题二:编译通过,运行时也通过
// Object obj = new Woman();
// Person p = (Person)obj;

//问题三:编译不通过
// Man m5 = new Woman();
// String str = new Date();
// Object o = new Date();
// String str1 = (String)o;
}
}

1.13 ==操作符与equals方法

  • == :运算符

    • 可以使用在基本数据类型变量和引用数据类型变量中。
    • 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
    • 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体。
  • equals()方法

    • 是一个方法,而非运算符。

    • 只能适用于引用数据类型。

    • Object类中equals()的定义:

      1
      2
      3
      public boolean equals(Object obj) {
      return (this == obj);
      }
      • 说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体。
    • 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的”实体内容”是否相同。

    • 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的”实体内容”是否相同。那么,我们就需要对Object类中的equals()进行重写。

      • 重写的原则:比较两个对象的实体内容是否相同。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class EqualsTest {
public static void main(String[] args) {
//基本数据类型
int i = 10;
int j = 10;
double d = 10.0;
System.out.println(i == j);//true
System.out.println(i == d);//true

boolean b = true;
// System.out.println(i == b);

char c = 10;
System.out.println(i == c);//true

char c1 = 'A';
char c2 = 65;
System.out.println(c1 == c2);//true

//引用类型:
Customer cust1 = new Customer("Tom",21);
Customer cust2 = new Customer("Tom",21);
System.out.println(cust1 == cust2);//false

String str1 = new String("atguigu");
String str2 = new String("atguigu");
System.out.println(str1 == str2);//false
System.out.println("****************************");
System.out.println(cust1.equals(cust2));//false--->true
System.out.println(str1.equals(str2));//true

Date date1 = new Date(32432525324L);
Date date2 = new Date(32432525324L);
System.out.println(date1.equals(date2));//true
}
}

1.14 toString()方法

  • 当我们输出一个对象的引用时,实际上就是调用当前对象的toString()。

  • Object类中toString()的定义:

    1
    2
    3
    public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
  • 像String、Date、File、包装类等都重写了Object类中的toString()方法。使得在调用对象的toString()时,返回”实体内容”信息。

  • 自定义类也可以重写toString()方法,当调用此方法时,返回对象的”实体内容”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class Customer {

private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Customer() {
super();
}
public Customer(String name, int age) {
super();
this.name = name;
this.age = age;
}

//自动生成的equals()
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Customer other = (Customer) obj;
if (age != other.age)
return false;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}

@Override
public String toString() {
return "Customer [name=" + name + ", age=" + age + "]";
}
}

public class ToStringTest {
public static void main(String[] args) {

Customer cust1 = new Customer("Tom",21);
System.out.println(cust1.toString());//Customer[name = Tom,age = 21]
System.out.println(cust1);//Customer[name = Tom,age = 21]

String str = new String("MM");
System.out.println(str);//MM

Date date = new Date(4534534534543L);
System.out.println(date.toString());//Mon Sep 11 08:55:34 GMT+08:00 2113
}
}

1.15 包装类的使用

  • 针对八种基本数据类型定义相应的引用类型—包装类(封装类)。
  • 有了类的特点,就可以调用类中的方法,Java才是真正的面向对象。
  • 基本数据类型 —>包装类:调用包装类的构造器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@Test
public void test1(){
int num1 = 10;
// System.out.println(num1.toString());
Integer in1 = new Integer(num1);
System.out.println(in1.toString());

Integer in2 = new Integer("123");
System.out.println(in2.toString());

//报异常
// Integer in3 = new Integer("123abc");
// System.out.println(in3.toString());

Float f1 = new Float(12.3f);
Float f2 = new Float("12.3");
System.out.println(f1);
System.out.println(f2);

Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean("TrUe");
System.out.println(b2);//true
Boolean b3 = new Boolean("true123");
System.out.println(b3);//false

Order order = new Order();
System.out.println(order.isMale);//false
System.out.println(order.isFemale);//null
}

class Order{
boolean isMale;
Boolean isFemale;
}
  • 包装类—>基本数据类型:调用包装类Xxx的xxxValue()
1
2
3
4
5
6
7
8
9
10
11
@Test
public void test2(){
Integer in1 = new Integer(12);

int i1 = in1.intValue();
System.out.println(i1 + 1);

Float f1 = new Float(12.3);
float f2 = f1.floatValue();
System.out.println(f2 + 1);
}
  • JDK 5.0 新特性:自动装箱 与 自动拆箱
1
2
3
4
5
6
7
8
9
10
11
@Test
public void test3(){
//自动装箱:基本数据类型 --->包装类
int num2 = 10;
Integer in1 = num2;//自动装箱
boolean b1 = true;
Boolean b2 = b1;//自动装箱
//自动拆箱:包装类--->基本数据类型
System.out.println(in1.toString());
int num3 = in1;//自动拆箱
}
  • 基本数据类型、包装类—>String类型:调用String重载的valueOf(Xxx xxx)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test4(){
int num1 = 10;
//方式1:连接运算
String str1 = num1 + "";
//方式2:调用String的valueOf(Xxx xxx)
float f1 = 12.3f;
String str2 = String.valueOf(f1);//"12.3"

Double d1 = new Double(12.4);
String str3 = String.valueOf(d1);
System.out.println(str2);
System.out.println(str3);//"12.4"
}
  • String类型 —>基本数据类型、包装类:调用包装类的parseXxx(String s)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Test
public void test5(){
String str1 = "123";
//错误的情况:
//int num1 = (int)str1;
//Integer in1 = (Integer)str1;
//可能会报NumberFormatException
int num2 = Integer.parseInt(str1);
System.out.println(num2 + 1);

String str2 = "true1";
boolean b1 = Boolean.parseBoolean(str2);
System.out.println(b1);//false
}

练习题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Test
public void test1() {
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);// 1.0,因为有类型自动提升
}

@Test
public void test3() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false

//Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
//保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
//-128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的:提高效率
Integer m = 1;
Integer n = 1;
System.out.println(m == n);//true

Integer x = 128;//相当于new了一个Integer对象
Integer y = 128;//相当于new了一个Integer对象
System.out.println(x == y);//false
}

1.16 关键字:static

  • 当我们编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生出对象,这时系统才会分配内存空间给对象,其方法才可以供外部调用。我们有时候希望无论是否产生了对象或无论产生了多少对象的情况下,某些特定的数据在内存空间里只有一份,例如所有的中国人都有个国家名称,每一个中国人都共享这个国家名称,不必在每一个中国人的实例对象中都单独分配一个用于代表国家名称的变量。
  • static可以用来修饰:属性、方法、代码块、内部类。
    • 使用static修饰属性:静态变量(或类变量)
      • 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值的修改。
      • 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致其他对象调用此静态变量时,是修改过了的。
      • static修饰属性的其他说明
        • 静态变量随着类的加载而加载。可以通过”类.静态变量”的方式进行调用。
        • 静态变量的加载要早于对象的创建。
        • 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
    • 使用static修饰方法:静态方法
      • 随着类的加载而加载,可以通过”类.静态方法”的方式进行调用。
      • 静态方法中,只能调用静态的方法或属性;非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性。
  • static注意点
    • 在静态的方法内,不能使用this关键字、super关键字。
  • 开发中,如何确定一个属性是否要声明为static的?
    • 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
    • 类中的常量也常常声明为static。
  • 开发中,如何确定一个方法是否要声明为static的?
    • 操作静态属性的方法,通常设置为static的。
    • 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections。

内存解析如下图:

1.17 理解main方法

  • main()方法作为程序的入口。
  • main()方法也是一个普通的静态方法。
  • main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)

例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainTest {
public static void main(String[] args) {//入口
Main.main(new String[100]);
MainTest test = new MainTest();
test.show();
}
public void show(){

}
}

class Main{
public static void main(String[] args) {
for(int i = 0;i < args.length;i++){
args[i] = "args_" + i;
System.out.println(args[i]);
}
}
}

例2:

1
2
3
4
5
6
7
public class MainDemo {
public static void main(String[] args) {
for(int i = 0;i < args.length;i++){
System.out.println("*****" + args[i]);
}
}
}

在idea编译器选择Edit Configurations后在Program arguments处填写几个字符串:

运行结果:

1
2
3
*****aa
*****bb
*****cc

或者使用“java MainDemo aa bb cc”输出同样的结果。

1.18 代码块

  • 代码块的作用:用来初始化类、对象。
  • 代码块如果有修饰的话,只能使用static。
  • 代码块可分为静态代码块非静态代码块
    • 静态代码块
      • 内部可以有输出语句。
      • 随着类的加载而执行,而且只执行一次。
      • 作用:初始化类的信息。
      • 如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行。
      • 静态代码块的执行要优先于非静态代码块的执行。
      • 静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构。
    • 非静态代码块
      • 内部可以有输出语句。
      • 随着对象的创建而执行。
      • 每创建一个对象,就执行一次非静态代码块。
      • 作用:可以在创建对象时,对对象的属性等进行初始化。
      • 如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行。
      • 非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法。

例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class BlockTest {
public static void main(String[] args) {
Person person = new Person();
System.out.println(person.age);
}
}


class Person{
String name;

int age;

static String desc = "我是一个人";

public Person(){
System.out.println("执行构造器");
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//非static的代码块
{
System.out.println("非静态代码块1");
}
{
System.out.println("非静态代码块2");
//调用非静态结构
age = 1;
eat();
//调用静态结构
desc = "非静态代码块给desc赋值";
info();
}
//static的代码块
static{
System.out.println("静态代码块1");
}
static{
System.out.println("静态代码块2");
//调用静态结构
desc = "静态代码块给desc赋值";
info();
}

public void eat(){
System.out.println("吃饭");
}

public static void info(){
System.out.println("desc的值为:" + desc);
}

@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}

执行结果:

1
2
3
4
5
6
7
8
9
静态代码块1
静态代码块2
desc的值为:静态代码块给desc赋值
非静态代码块1
非静态代码块2
吃饭
desc的值为:非静态代码块给desc赋值
执行构造器
1
  • 对属性可以赋值的位置:

    • ①默认初始化
    • ②显式初始化/⑤在代码块中赋值
    • ③构造器中初始化
    • ④有了对象以后,可以通过”对象.属性”或”对象.方法”的方式,进行赋值

    执行的先后顺序:① →② / ⑤ →③ → ④

1.19 关键字:final

  • final可以用来修饰的结构:类、方法、变量。
    • final 用来修饰一个类:此类不能被其他类所继承。(比如:String类、System类、StringBuffer类)
    • final 用来修饰方法:表明此方法不可以被重写。(比如:Object类中getClass();)
    • final 用来修饰变量:此时的”变量”就称为是一个常量。
      • final修饰属性:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
      • final修饰局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class FinalTest {
//显示初始化
final int WIDTH = 0;
final int LEFT;
final int RIGHT;
final int DOWN;

//代码块中初始化
{
LEFT = 1;
}

//构造器中初始化
public FinalTest(){
RIGHT = 2;
}

//报错,如果new对象时使用这个构造器,不能保证RIGHT和DOWN被初始化。
//public FinalTest(int n){

//}

//报错,不能保证这个方法会被调用;而且可能会被多次调用,这样final的变量会被多次赋值
//public void setDown(int down){
// this.DOWN = down;
//}


public void show(){
final int NUM = 10;//常量
//报错,不能被重新赋值
//NUM += 20;
}

public void show(final int num){
//num = 20;//编译不通过
System.out.println(num);
}

public static void main(String[] args) {
FinalTest test = new FinalTest();
test.show(10);
}
}
  • static final可以用来修饰属性和方法。

1.20 抽象类与抽象方法

  • abstract可以用来修饰的结构:类、方法
    • abstract修饰类:抽象类
      • 此类不能实例化。
      • 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
      • 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作。
    • abstract修饰方法:抽象方法
      • 抽象方法只有方法的声明,没有方法体。
      • 包含抽象方法的类,一定是一个抽象类。反之,抽象类中是可以没有抽象方法的。
      • 若子类重写了父类中的所有的抽象方法后,此子类方可实例化。若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰。
  • abstract不能用来修饰:属性、构造器等结构。
  • abstract不能用来修饰私有方法、静态方法、final的方法、final的类。
  • 抽象类的匿名子类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class PersonTest {
public static void main(String[] args) {
method(new Student());//匿名对象

Worker worker = new Worker();
method1(worker);//非匿名的类非匿名的对象

method1(new Worker());//非匿名的类匿名的对象

System.out.println("********************");

//创建了一匿名子类的对象:p
Person p = new Person(){
@Override
public void eat() {
System.out.println("吃东西");
}

@Override
public void breath() {
System.out.println("好好呼吸");
}
};

method1(p);
System.out.println("********************");
//创建匿名子类的匿名对象
method1(new Person(){
@Override
public void eat() {
System.out.println("吃好吃东西");
}

@Override
public void breath() {
System.out.println("好好呼吸新鲜空气");
}
});
}


public static void method1(Person p){
p.eat();
p.breath();
}

public static void method(Student s){

}
}

class Worker extends Person{

@Override
public void eat() {
}

@Override
public void breath() {
}
}

1.21 接口(interface)

  • 接口使用interface来定义。

  • Java中,接口和类是并列的两个结构。

  • 定义接口

    • JDK7及以前:只能定义全局常量和抽象方法。

      • 全局常量:public static final的,但是书写时,可以省略不写。
      •    抽象方法:public abstract的。
        
    • JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法。

  • 接口中不能定义构造器的,意味着接口不可以实例化。

  • Java开发中,接口通过让类去实现(implements)的方式来使用。

    • 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化。
    • 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类。
  • Java类可以实现多个接口—>弥补了Java单继承性的局限性。(格式:class AA extends BB implements CC,DD,EE)

  • 接口与接口之间可以继承,而且可以多继承。

  • 接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。继承是一个”是不是”的关系,而接口实现则是 “能不能”的关系。

  • 接口使用上也满足多态性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
//1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);

//2. 创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());

//3. 创建了接口的匿名实现类的非匿名对象
USB phone = new USB(){
@Override
public void start() {
System.out.println("手机开始工作");
}

@Override
public void stop() {
System.out.println("手机结束工作");
}

};
com.transferData(phone);

//4. 创建了接口的匿名实现类的匿名对象
com.transferData(new USB(){
@Override
public void start() {
System.out.println("mp3开始工作");
}

@Override
public void stop() {
System.out.println("mp3结束工作");
}
});
}
}

class Computer{

public void transferData(USB usb){//USB usb = new Flash();
usb.start();

System.out.println("具体传输数据的细节");

usb.stop();
}


}

interface USB{
//常量:定义了长、宽、最大最小的传输速度等

void start();

void stop();

}

class Flash implements USB{

@Override
public void start() {
System.out.println("U盘开启工作");
}

@Override
public void stop() {
System.out.println("U盘结束工作");
}

}

class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}

@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface A {
//全局常量
int x = 0;
}

class B {
int x = 1;
}

class C extends B implements A {
public void pX() {
//编译不通过。因为x是不明确的
// System.out.println(x);
System.out.println(super.x);//1
System.out.println(A.x);//0

}

public static void main(String[] args) {
new C().pX();
}
}
  • java8接口新特性(除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
public interface CompareA {
//静态方法,public可以省略
public static void method1(){
System.out.println("CompareA:北京");
}
//默认方法,public可以省略
public default void method2(){
System.out.println("CompareA:上海");
}
default void method3(){
System.out.println("CompareA:上海");
}
}

public interface CompareB {
default void method3(){
System.out.println("CompareB:上海");
}
}

public class SuperClass {
public void method3(){
System.out.println("SuperClass:北京");
}
}

public class SubClassTest {

public static void main(String[] args) {
SubClass s = new SubClass();
//知识点1:接口中定义的静态方法,只能通过接口来调用。
//s.method1();
//SubClass.method1();
CompareA.method1();//CompareA:北京
//知识点2:通过实现类的对象,可以调用接口中的默认方法。
//如果实现类重写了接口中的默认方法,调用时,仍然调用的是重写以后的方法
s.method2();//SubClass:上海

//知识点3:如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的默认方法,
//那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法。-->类优先原则

//知识点4:如果实现类实现了多个接口,而这多个接口中定义了同名同参数的默认方法,
//那么在实现类没有重写此方法的情况下,报错。-->接口冲突。
//这就需要我们必须在实现类中重写此方法
s.method3();//SubClass:深圳
}

}

class SubClass extends SuperClass implements CompareA,CompareB{

public void method2(){
System.out.println("SubClass:上海");
}

public void method3(){
System.out.println("SubClass:深圳");
}

//知识点5:如何在子类(或实现类)的方法中调用父类、接口中被重写的方法
public void myMethod(){
method3();//调用自己定义的重写的方法
super.method3();//调用的是父类中声明的
//调用接口中的默认方法
CompareA.super.method3();
CompareB.super.method3();
}
}

1.22 内部类

  • 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类。
  • 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
  • 内部类的分类:成员内部类(静态、非静态)局部内部类(方法内、代码块内、构造器内)
    • 成员内部类:
      • 一方面,作为外部类的成员:调用外部类的结构;可以被static修饰;可以被4种不同的权限修饰。
      • 另一方面,作为一个类:类内可以定义属性、方法、构造器等;可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承;可以被abstract修饰。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
public class InnerClassTest {
public static void main(String[] args) {

//创建Dog实例(静态的成员内部类):
Person.Dog dog = new Person.Dog();
dog.show();
//创建Bird实例(非静态的成员内部类):
// Person.Bird bird = new Person.Bird();//错误的
Person p = new Person();
Person.Bird bird = p.new Bird();
bird.sing();

System.out.println();
bird.display("黄鹂");
}
}


class Person{

String name = "小明";
int age;

public void eat(){
System.out.println("人:吃饭");
}


//静态成员内部类
static class Dog{
String name;
int age;

public void show(){
System.out.println("卡拉是条狗");
//eat();报错
}
}

//非静态成员内部类
class Bird{
String name = "杜鹃";

public Bird(){

}

public void sing(){
System.out.println("我是一只小小鸟");
Person.this.eat();//调用外部类的非静态属性
eat();//即省略了Person.this
System.out.println(age);
}

public void display(String name){
System.out.println(name);//方法的形参
System.out.println(this.name);//内部类的属性
System.out.println(Person.this.name);//外部类的属性
}
}

public void method(){
//局部内部类
class AA{

}
}

{
//局部内部类
class BB{

}
}

public Person(){
//局部内部类
class CC{

}
}
}

其中,局部内部类的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class InnerClassTest1 {

//开发中很少见
public void method(){
//局部内部类
class AA{

}
}

//返回一个实现了Comparable接口的类的对象
public Comparable getComparable(){

//创建一个实现了Comparable接口的类:局部内部类
//创建了一个实现了Comparable接口的有名实现类的匿名对象
//方式一:
// class MyComparable implements Comparable{
//
// @Override
// public int compareTo(Object o) {
// return 0;
// }
//
// }
// return new MyComparable();

//方式二:创建了一个实现了Comparable接口的匿名实现类的匿名对象
return new Comparable(){
@Override
public int compareTo(Object o) {
return 0;
}
};
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class InnerClassTest {
// public void onCreate(){
//
// int number = 10;
// 匿名局部内部类的使用
// View.OnClickListern listener = new View.OnClickListener(){
//
// public void onClick(){
// System.out.println("hello!");
// System.out.println(number);
// }
//
// }
//
// button.setOnClickListener(listener);
//
// }
/*
* 在局部内部类的方法中(比如:show)如果调用外部类所声明的方法(比如:method)中的局部变量(比如:num)的话,
* 要求此局部变量声明为final的。
*
* jdk 7及之前版本:要求此局部变量显式的声明为final的
* jdk 8及之后的版本:可以省略final的声明
*
*/
public void method(){
//局部变量
int num = 10;

class AA{
public void show(){
// num = 20;报错,实际上调用的num是个副本
System.out.println(num);
}
}
}
}