目录

1.面向对象与面向过程

1.1 面向过程

  1. 以过程为中心的编程思想,分析出解决问题所需要的步骤,用函数将这些步骤实现,使用时依次调用即可。
  2. 强调的是功能行为,以函数为最小单位,考虑怎么做。

1.2 面向对象

  1. 面向对象编程(Object Oriented Programming,OOP,面向对象程序设计)的主要思想是把构成问题的各个事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙一个事物在整个解决问题的步骤中的行为。
  2. 强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。

2.类和对象

2.1 定义

类:对一类事物的描述,是抽象的、概念上的定义;(人类)
对象:类的一个实例;(具体某个人)
举例:我们跟控制台交互需要使用java提供的Scanner类,使用时我们要创建一个Scanner对象,通过这个对象来操作它的功能方法,完成我们和控制台的交互。

2.2 类的成员

  1. Filed = 属性 = 成员变量
  2. Method = 行为 = 成员方法
  3. 构造器
  4. 代码块
  5. 内部类

2.3 类和对象的使用

  1. 创建类,设计属性和行为;
  2. 创建类的对象(实例化对象);
  3. 通过对象调用类的属性和方法;

2.4 内存结构

java学习笔记2–面向对象编程-小白菜博客
对于引用数据类型来说,变量值不是null就是地址值(包含变量类型);
在这里插入图片描述

2.5 成员变量和局部变量对比

成员变量定义在类中方法外,除了成员变量其余变量都是局部变量;

2.5.1 相同点

  1. 定义格式相同:数据类型 变量名 = 变量值;
  2. 都是先定义后使用;
  3. 都有其对应的定义域;

2.5.2 不同点

  1. 在内存中加载的位置不同:成员变量在堆中(非static);局部变量在栈中。
  2. 权限修饰符的不同:成员变量可以添加权限修饰符(public private 缺省 protected);局部变量不可以。
  3. 在类中声明的位置不同:成员变量直接声明在类中,方法体外;局部变量声明在方法、形参、构造器形参、构造器内部。
  4. 默认初始值不同:成员变量有默认初始值,与一维数组相同;局部变量除形参外,都需要显示初始化。

2.5.3 定义域

成员变量的定义域是整个类中,局部变量定义域是在其最近一层的结构中(方法、代码块);
成员变量可以定义在类的任意位置,对于非静态的成员变量,它是随着对象的创建而产生的,而非静态的结构都是通过对象来调用,所以成员变量的定义位置与其被调用的结构的位置无关;
局部变量初始化必须在被调用的结构之前,因为无论是方法中还是代码块中,都是自上向下执行的,必须执行完局部变量的初始化语句,才会给局部变量分配内存空间;
两者的定义位置归根到底还是与其生命周期有关;

2.6 练习–内存分配

画出如下代码在执行时的内存分配情况
class Car{
       String color = "red";
       int num = 4;
       void show(){
			  int a = 10;
	          System.out.println("color="+color+",num="+num);
        }
  }
class CarTest {
	public static void main(String[] args) {
		Car c1 = new Car();   
	Car c2 = new Car(); 
		c1.color = "blue";  
		c1.show();   
		c2.show();
	}  }

  • 给成员变量显示赋值,初始值也是默认的;
  • 方法中的局部变量都在栈中;
  • main函数执行完成后,c1和c2都会被回收;
    在这里插入图片描述

2.7 匿名对象

  1. 理解:我们创建的对象,没有显示的赋值给一个变量名。即为匿名对象。
  2. 特征:匿名对象只能调用一次。
  3. 使用:充当方法的实参。

3.方法

3.1 定义

权限修饰符 返回值类型 方法名(形参列表){
		方法体;
}

3.1.1 权限修饰符

  • public
  • private
  • 缺省
  • protected

3.1.2 返回值类型

  • 可以是基本数据类型,也可以是引用数据类型。
  • 如果方法有返回值,则必须在方法声明时,指定返回值的类型。同时,方法中,需要使用return关键字来返回指定类型的变量或常量“return 数据”。如果方法没有返回值,则方法声明时,使用void来表示。通常,没有返回值的方法中,就不需要使用return.但是,如果使用的话,只能“return;”表示结束此方法的意思。

3.1.3 方法名

标识符,和变量的命名规范一样,首单词的首字母小写,其余单词首字母大写。

3.1.4 参数列表

格式:数据类型1 形参1,数据类型2 形参2,…

3.1.5 return

  • 使用范围:方法体中
  • 作用:结束方法;对于有返回值类型的方法,使用“return 数据”返回所需数据。
  • 注意:return关键字后不可声明执行语句。

3.1.6 注意

  • 成员方法可以调用类中的属性和其它成员方法。
  • 方法中不可以定义方法。

3.2 方法重载

3.2.1 意义

重载的最直接作用是方便了开发人员可以根据不同的参数个数,顺序,类型,自动匹配方法,减少了方法的命名和记忆。

3.2.2 定义

在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。
“两同一不同”:同一个类,同一个方法名;形参列表不同。

3.2.3 特点(两同一不同,四无关)

  • 同一个类,同一个方法名;
  • 形参列表不同:形参个数不同,对应位置的形参数据类型不同;
  • 四个无关:与返回值类型无关,与权限修饰符无关,与方法体无关;与形参名无关;
  • 如果没有实参对应的数据类型,会进行自动类型转换;(int -> double)
  • 方法可以是同一个类中声明的,也可以是继承于同一个父类的,也可以是一个自己声明,一个继承的;

3.3 可变个数的形参

JavaSE 5.0 中提供了Varargs(variable number of arguments)机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。

3.3.1 格式

数据类型 … 形参名称

定义:
   public void max(String... str) {
   // 与数组的遍历方式相同
		for (int i = 0; i < str.length; i++) {
            System.out.println(str[i]);
        }
   }
调用:(两种方式)
   对象.max("a","b","c");
   对象.max(new String[]{"a","b"});

3.3.2 细节

  • 可以与同一个类中方法名相同,形参列表不同的方法构成重载;
  • 不能与同一个类中方法名相同,形参为相同数据类型的数组同时存在;
  • 用可变个数形参的方法时,实参的个数可以是0个,1个…;
  • 可变个数参数在方法的形参中,必须声明在末尾;
  • 一个形参列表中只能由一个可变个数形参;

3.3.3 与数组的区别

  • 调用方法时,可以不用传入数组类型实参(相当于少创建一个数组对象);
  • 必须声明在形参列表末尾且只能由一个。

3.4 方法的值传递机制

3.4.1 变量的赋值复习

  • 对于基本数据类型,变量间的赋值是进行值传递,赋值之后两个变量间互相独立;
  • 对于引用数据类型,变量间的赋值是进行地址传递,复制之后两个变量共享一块内存空间;

3.4.2 形参和实参

  • 形式参数:方法定义时小括号中的参数;
  • 实际参数:方法调用时传递的参数;

3.4.3 形参的传递机制:值传递

  • 不管是数据值还是地址值,归根到底参数传递只有一种类型,就是值传递
  • 对于基本数据类型,实参传递给形参的是实参中存储的数据值。
  • 对于引用数据类型,实参传递给形参的是实参所指向内存空间的地址值(包含数据类型)

3.4.4 练习1

public class TransferTest3{
	public static void main(String args[]){
		TransferTest3 test=new TransferTest3();
		test.first();
	}
	
	public void first(){
		int i=5;
		Value v=new Value();
		v.i=25;
		second(v,i);
		System.out.println(v.i+" "+i);
	}
	
	public void second(Value v,int i){
		i=0;
		v.i=20;
		Value val=new Value();
		v=val;
		System.out.println(v.i+" "+i);
		
	}
}
class Value {
	int i= 15;
} 
15 0
20 5

在这里插入图片描述

3.4.5 练习2

在这里插入图片描述

方法1:在method方法中输出a、b,并终止程序;
    private static void method(int a, int b) {
        System.out.println("a = " + 100);
        System.out.println("b = " + 200);
        System.exit(0); // 终止程序
    }
方法2:重新println方法
    private static void method(int a, int b) {
        PrintStream ps = new PrintStream(System.out){
        public void println(String x){
             if("a=10".equals(x)){
                 x = "a=100";
             }else if("b=10".equals(x)){
                 x = "b=200";
             }
             super.println(x);
	         }
	     };
     	System.setOut(ps);
    }

3.4.6 练习3

微软: 定义一个int型的数组:int[] arr = new int[]{12,3,3,34,56,77,432};
让数组的每个位置上的值去除以首位置的元素,得到的结果,作为该位置上的新值。遍历新的数组。

//错误写法:如果从arr[0]开始,那么arr[0]就变为1了,其余值都是除以1。
for(int i= 0;i < arr.length;i++){
	arr[i] = arr[i] / arr[0];
}

//正确写法1
for(int i = arr.length –1;i >= 0;i--){
	arr[i] = arr[i] / arr[0];
}

//正确写法2
int temp = arr[0];
for(int i= 0;i < arr.length;i++){
	arr[i] = arr[i] / temp;
}

3.4.7 练习4

println(char[] arr)方法体是对char[]数组进行遍历

 * int[] arr = new int[10];
 * System.out.println(arr);//地址值
 * 
 * char[] arr1 = {'a','b','c'};
 * System.out.println(arr1);//abc

4.递归(后面补)

5.封装

5.1 定义

封装指的是将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过类中提供的公共方法来实现对隐藏信息的操作和访问。

5.2 目的

追求程序设计的“高内聚,低耦合”特性,提高系统的可拓展性和可维护性。

当我们创建一个类的对象以后,我们可以通过"对象.属性"的方式,对对象的属性进行赋值。这里,赋值操作要受到属性的数据类型和存储范围的制约。但除此之外,没有其他制约条件。但是,实际问题中,我们往往需要给属性赋值加入额外限制条件。这个条件就不能在属性声明时体现,我们只能通过方法进行条件的添加。比如说,setLegs。同时,我们需要避免用户再使用“对象.属性”的方式对属性进行赋值。则需要将属性声明为私有的(private)此时,针对于属性就体现了封装性。

5.3 封装性的体现

  • 将类的属性私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)属性;
  • 方法私有化;
  • 单例模式:构造器私有化;
  • 将类的权限修饰符设置为default,其它包就不可以调用该类;

5.4 权限修饰符

Java 权限修饰符public、protected、default(缺省)、private置于类的成员定义前,用来限定对象对该类成员的访问权限。

  • 4 种权限都可以用来修饰类的内部结构:属性、方法、构造器、内部类;
  • 修饰类的话,只能使用:缺省、public

在这里插入图片描述

5.5 权限修饰符之于封装

封装性的实现需要权限修饰符的配合–Java 提供了 4种权限修饰符来修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性大小。

6.构造器

6.1 格式

    权限修饰符 类名(形参列表) {
        方法体;
    }

6.2 作用

  • 创建对象;
  • 初始化对象信息;

6.3 细节

  • 如果没有显式定义构造器,那么系统会默认提供一个无参构造器;
  • 一个类中可以定义多个构造器,它们之间构成重载;
  • 构造器与方法不同,没有返回值类型,且构造器名称就是类名;
  • 如果显式定义了构造器,那么系统就不提供默认无参构造器;
  • 一个类中至少有一个构造器;

7.属性赋值的过程

  1. 默认初始化
  2. 显式初始化
  3. 构造器初始化
  4. 通过“对象.属性”或“对象. 方法”给属性赋值
    正确顺序:1 -> 2 -> 3 -> 4

8.JavaBean

有一个无参的公共的构造器

/*
 * JavaBean 是一种 Java 语言写成的可重用组件。
 * 所谓 javaBean,是指符合如下标准的 Java 类:
 * 		> 类是公共的
 * 		> 有一个无参的公共的构造器
 * 		> 有属性,且有对应的 get、set 方法
 * 
 */
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;
	}
}

9.UML类图

介绍类以及类内部结构
在这里插入图片描述

10.this关键字

10.1 作用

this表示当前对象或者当前正在创建的对象(相对于构造器来说),可以用来修饰、调用属性,方法,构造器。

10.2 使用方法

this.属性(用来区分形参与成员变量)
this.方法()
this(形参列表) //在构造器中调用本类中其余构造器

1 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性和方法。
通常情况下,我们都选择省略“this.”。特殊情况下,如果方法的形参和类的属性同名,我们必须显式地使用"this.变量"的方式,表明此变量是属性,而非形参。

2 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用正在创建的对象属性和方法。
但是,通常情况下,我们都选择省略“this.”。特殊情况下,如果构造器的形参和类的属性同名,我们必须显式地使用"this.变量"的方式,表明此变量是属性,而非形参。

3.this 调用构造器(减少代码冗余)
① 我们可以在类的构造器中,显式的使用"this(形参列表)“的方式,调用本类中重载的其他的构造器!
② 构造器中不能通过"this(形参列表)“的方式调用自己。
③ 如果一个类中声明了n个构造器,则最多有n -1个构造器中使用了"this(形参列表)”。
"this(形参列表)"必须声明在类的构造器的首行!
⑤ 在类的一个构造器中,最多只能声明一个"this(形参列表)”。

4.this也可以单独使用,表示当前对象;super不可以单独使用。

10.3 getter和setter

idea快捷键alt+insert
getter方法中不用加this,因为没有重名的形参;

    private String name;
    
    public void setName(String name){
        this.name = name;
    }
    public String getName(){
        return name;
    }

11.package和import关键字

11.1 package包

  • 作用:便于项目中类或接口的管理;
  • 同一个包中不能有同名的接口和类,不同的包中可以;
  • 包名全部是小写字母;
  • 包实质上就是一个文件夹;

11.2 import导入

  • 作用:导入指定包下的类或接口;
  • import的落脚点是类或接口,不是包;
  • 导入xxx包下所有的类和接口,使用import xxx.*;
  • 如果同时使用位于两个包下相同类名的类,那么至少有一个类要使用全类名的方式来显示;test1.Account account1 = new test1.Account();
  • 如果使用A包的子包B中的类或接口,只写import A.*不行,要导入子包B中的类或接口;
  • 如果类或接口是java.lang包中定义的,那么不需要导入,直接使用即可;
  • import static组合的使用:调用指定类或接口下的静态的属性或方法.

12.项目2–客户信息管理系统

12.1 CMUtility

12.1.1 sc.next()和sc.nextLine()

  • sc.next()不会读入空格,在有效字符之后输入空格再输入字符,空格之后的字符不会被记录;
        Scanner sc = new Scanner(System.in);
        System.out.print("输入:");
        String s = sc.next();  //不会读入空格
        System.out.print("输出:");
        System.out.println(s);
输入:  123 123  2312
输出:123

  • sc.nextLine()会读入空格
        Scanner sc = new Scanner(System.in);
        System.out.print("输入:");
        String s = sc.nextLine();  //不会读入空格
        System.out.print("输出:");
        System.out.println(s);
输入:   12321 2312 213  
输出:   12321 2312 213  

12.1.2 try-catch-finally

  • 异常捕获成功后会继续执行后续代码;
  • 发生异常后,try代码块中位于异常语句后的代码不会执行,所以如果有必须要执行的语句,一般放在finally中;

12.2 CustomerList增删改查操作

12.2.1 删除一个用户

数组中删除一个元素后,后边元素都要前移。

    public boolean delCustomer(int index) {
        if (index >= total || index < 0)
            return false;
        for (int i = index; i < total - 1; i++) {
            customers[i] = customers[i + 1];
        }
        customers[total - 1] = null;
        total--;
        return true;
    }

12.3 CustomerView

12.3.1 modifyCustomer

  • 修改用户信息时,如果不想修改某一项,可以直接回车,此时后台判断为空白输入,会将原值返回;
  • 修改时可以不用new一个新对象,直接调用原对象的setXxx方法进行修改即可;

12.4 内存解析

在这里插入图片描述

13.继承

13.1 定义

当多个类中拥有相同的属性和方法时,将这些内容抽取到一个单独的类中,别的类就不需要定义这些类或方法,直接继承哪一个类即可。
格式:class A extends B{}

13.2 目的

  • 减少代码冗余,提高代码的复用性;
  • 便于功能拓展;
  • 为多态的实现提供前提;

13.3 细节

  • 单继承:一个类只能有一个直接父类;
  • 一个类可以被多个类继承;
  • 子类继承父类后,就获取了直接父类和所有间接父类中的属性和方法,构造器不能被继承
  • 关于父类中私有(private)的属性和方法,子类也会获取。只是由于封装性,子类不能直接调用父类的私有结构,但是可以通过父类中的getter或者公共的方法来间接调用父类中的私有结构;
  • 子类可以声明自己特有的属性和方法,实现功能的拓展;
  • java.lang.Object类是所有没有显示继承的类的父类;
  • 所有的 java 类(除 java.long.Object 类之外)都直接或间接地继承于 java.lang.Object 类;
  • 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系(张三是个人,不能说旺财(狗)是个人);

13.4 向上查找原则(每一个父类都是一个子类)

13.4.1 规则

因为方法可以重写,而属性不能重写,所以方法满足动态绑定机制,而属性是就近原则。
多态是在继承的基础上实现的,多态主要的作用是提高代码的通用性,所以继承和多态的方法和属性调用逻辑相同。
方法:编译看左边,运行看右边。(普通继承等号左右是同一个类)
属性:编译看左边,运行看左边。

  • 继承中的方法也满足动态绑定机制(运行看右边),调用方法时优先看等号右边,如果没有再向上查找,在方法中掉用方法也满足这个原则;
  • 通过对象访问属性时,无论是不是多态,都看等号左边;
  • 通过成员方法访问属性时,方法属于哪个类,就优先使用这个类中的属性,没有则向上找;

13.4.2 例题(重要)

  1. 上面的规则,注意在类A中的this是new B(), super代表A。
  2. try-catch-finally中,在try中return执行之前始终会执行finally里面的代码,如果finally里面有return,则数据跟随finally改变并返回,不会再执行try中的return。如果没有return,则原数据不跟随finally里改变的数据改变!
class Test {
    public static void main(String[] args) {
        System.out.println(new B().getValue()); // 
    }
    static class A {
        protected int value;
        public A (int v) {
            setValue(v);
        }
        public void setValue(int value) {
            this.value= value;
        }
        public int getValue() {
            try {
                value ++;
                return value;
            } finally {
                this.setValue(value);
                System.out.println(value);
            }
        }
    }
    static class B extends A {
        public B () {
            super(5);
            setValue(getValue()- 3);
        }
        public void setValue(int value) {
            super.setValue(2 * value);
        }
    }
}
22 34 17

13.5 实例化对象时构造器的执行顺序

先执行父类的构造器,在执行子类构造器中的语句。
在这里插入图片描述

14. 方法的重写

14.1 定义

方法的重写是指子类继承父类后,可以对父类同名同参数的方法进行覆盖操作;

14.2 细节(两小一大)

子类:重写方法;
父类:被重写方法;
如果子类的方法与父类的方法同名同参数,那么子类一定要满足重写的规则,不然编译报错

14.2.1 权限修饰符(大)

  • 重写方法的权限修饰符要大于等于被重写方法的;
  • 子类不能重写父类中声明为private权限的方法;

14.2.2 返回值类型(小)

  • 被重写方法的返回值类型是void,重写方法的返回值类型也必须是void;
  • 重写方法的返回值类型是被重写方法返回值类型的自身或者其子类;
  • 如果被重写方法返回值是基本数据类型,那么重写方法必须与其保持一致,因为基本数据类型没有子类;

14.2.3 异常(小)

  • 重写方法抛出的异常必须小于等于被重写方法抛出的异常

14.2.4 static

  • 重写方法和被重写方法必须同时声明为static或者非static;
  • 同时声明为非static时,可能是重写;
  • 同时声明为static时,不是重写,因为static方法属于类的,子类无法覆盖父类的方法;
  • 静态方法可以被继承,但是不可以被重写,所以通过子类或者子类对象可以访问父类的静态方法;

14.3 调用

  • 重写之后,子类对象调用的是本类中重写的方法,而父类对象调用的仍是被重写的方法;

14.4 重写与重载的区别

在这里插入图片描述

14.5 意义

父类方法中的逻辑不适用于子类,所以子类需要对父类的方法进行重写。

15.super关键字(表示父类的xxx)

15.1 作用

在子类中使用,可以调用父类的属性、方法或者构造器;

15.2 使用

super.属性/方法/构造器

15.2.1 属性和方法

  • 在子类的方法或者构造器中,当我们要使用父类的属性或方法时,需要 super.属性super.方法显式调用,但是通常情况下,可以省略super;
  • 特殊情况:子类的属性与父类的属性同名(与数据类型无关)时,在子类中调用父类的属性,需要使用super.属性
  • 特殊情况:在子类中重写了父类的方法,调用父类中被重写的方法时,需要使用super.方法
  • 如果父子类中没有重名成员,访问父类的成员时,使用super,this和直接访问是一样的效果,但是执行过程不同(super是直接访问父类成员;this和直接访问先要在本类中查找,本类中没有再去父类中找);
  • 使用super不能直接访问父类中的私有方法;

15.2.2 构造器

  • 在子类构造器中可以使用super(形参)来显式调用父类的构造器;
  • super(形参)this(形参)一样,只能放在构造器的首行,所以在同一个构造器中两者只能出现一个且必须出现一个
  • 如果在子类构造器中没有显示声明super(形参)this(形参),那么默认会有一个super()调用父类的空参构造器,但是如果父类没有空参构造器,那么子类构造器中必须显示调用父类的其余构造器;
  • this(形参)调用本类构造器不能形成循环可知,子类中至少有一个构造器会显式或隐式调用父类的构造器;

15.3 为什么super(…)或this(…)调用语句只能作为构造函数中的第一句出现?

无论通过哪个构造器创建对象,都要保证先初始化父类;
目的:当子类继承父类后,继承了父类中所有的属性和方法,因此子类有必要知道父类是如何进行初始化的。

15.4 this和super的区别

super不可以单独使用
在这里插入图片描述

16.子类对象实例化的过程

  • 通过子类的构造器创建子类对象时,一定会直接或间接调用父类的构造器,进而调用父类的父类的构造器,直到调用到java.lang.Object的空参构造器为止,正是因为加载过所有的父类成员,所有子类对象才可以调用父类成员。
  • 虽然创建子类对象时调用了父类构造器,但是自始至终就创建了一个对象,就是new的那个对象;
    在这里插入图片描述

17.多态(向上转型)

17.1 对多态的理解

一种事物具有多种状态。

  • 多态的意义在于提高了代码的通用性,主要体现在参数传递的过程中。比如说Object类中有一个equal()方法public boolean equals(Object o),由于多态性的存在,我们传入的实参可以可以是任意类;但是如果没有多态性,我们就需要写多个不同形参类型的equals方法,造成代码冗余。
  • 抽象类和接口的使用可定体现了多态性,因为它们无法实例化,作为形参时,只能传入子类/实现类对象。
  • JDBC

17.2 使用

17.2.1 使用前提

  • 存在继承关系;
  • 子类对父类方法进行了重写;

17.2.2 格式

父类引用指向子类对象(向上转型):父类名称 父类引用 = new 子类名称();

// 1,2两种写法相同,都是向上转型
子类 子类对象 = new 子类();
父类 父类引用 = 子类对象;// 1
父类 父类引用 = (父类名称)子类对象; //2 

17.2.3 多态的使用:虚拟方法调用

虚拟方法:子类重写了父类的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的,只能在运行期间确定。(多态情况下,父类中被重写的方法称为虚拟方法

编译看左边,运行看右边有了对象多态性以后,我们在编译期,只能调用父类声明的方法,但在执行期实际执行的是子类重写父类的方法。

Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。==若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)。==多态情况下,
“看左边”:看的是父类的引用(父类中不具备子类特有的方法)
“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)

17.2.4 注意事项

  • 对象的多态性只适用于非静态方法,不适用于属性和静态方法(编译和运行都看左边);
  • 父类引用无法直接调用子类中特有的成员方法,编译时类型由声明该变量时使用的类型决定,父类中没有这些方法,编译会报错;
  • 父类引用调用的是子类中重写的方法,不是子类中其它方法;
  • “编译看左边,运行看右边”和“编译和运行都看左边”指的是通过父类引用直接调用方法或属性;如果是在方法中使用this或直接调用属性,那么方法属于谁,就是用谁的属性。

17.3 作用

提高了代码的通用性,常称作接口重用

17.4 向下转型

在这里插入图片描述

17.4.1 作用

多态情况下,内存中实际上加载了子类中特有的成员方法和属性,但是由于变量声明为父类类型,导致编译时只能调用父类的成员,无法直接调用子类中特有的成员,所以要通过向下转型将父类引用转为子类对象。(类似于强制转换)

17.4.2 格式

子类名称 对象名 = (子类名称)父类引用

17.4.3 instanceOf关键字

格式:x instanceof A:检验x的运行类型是否为类A或A子类的对象,返回值为boolean型。
作用:避免向下转型时,出现java.lang.ClassCastException异常。

17.4.4 向下转型的常见问题

  • 父类引用可以强转为子类对象,但是父类对象不能强转为子类对象,可以先将子类转为父类的父类,再将其向下转型为父类(问题二)
  • 父类引用指的是使用多态,而父类对象是普通定义;
		//问题一:编译时通过,运行时不通过
		//举例一
//		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;

17.5 练习

/*
 * 练习:子类继承父类
 * 
 * 1.若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖了父类里的同名方法,
 * 系统将不可能把父类里的方法转移到子类中。
 * 
 * 2.对于实例变量则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,
 * 这个实例变量依然不可能覆盖父类中定义的实例变量
 * 
 */
public class FieldMethodTest {
	public static void main(String[] args){
		Sub s= new Sub();
		System.out.println(s.count);	//20
		s.display();//20
		
		Base b = s;
		//==:对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否一样。
		System.out.println(b == s);	//true
		System.out.println(b.count);	//10 编译看左边,运行也看左边
		b.display(); // 20 多态:编译看左边,运行看右边
	}
}

class Base {
	int count= 10;
	public void display() {
		System.out.println(this.count);
	}
}

class Sub extends Base {
	int count= 20;
	public void display() {
		System.out.println(this.count);
	}
}

17.6 多态是编译时行为还是运行时行为?

多态时运行时行为。

 * 证明见如下:
import java.util.Random;

class Animal  {

	protected void eat() {
		System.out.println("animal eat food");
	}
}

class Cat  extends Animal  {

	protected void eat() {
		System.out.println("cat eat fish");
	}
}

class Dog  extends Animal  {

	public void eat() {
		System.out.println("Dog eat bone");
	}
}

class Sheep  extends Animal  {

	public void eat() {
		System.out.println("Sheep eat grass");

	}

}

public class InterviewTest {

	public static Animal  getInstance(int key) {
		switch (key) {
		case 0:
			return new Cat ();
		case 1:
			return new Dog ();
		default:
			return new Sheep ();
		}

	}

	public static void main(String[] args) {
		int key = new Random().nextInt(3);

		System.out.println(key);
		// 通过使用随机数,可以知道多态时运行时的行为;
		Animal  animal = getInstance(key);
		
		animal.eat();
		 
	}
}

17.7 练习–多态调用的是子类中重写的方法

  • int[] arr和 int… arr是一样的,所以Sub类中的第一个add方法是对Base类中add方法的重写,第二个add方法不是重写,多态调用的是重写后的方法。
  • 向下转型后调用的是第二个add方法,因为第二个方法的形参更加准确。
public class InterviewTest1 {

	public static void main(String[] args) {
		Base base = new Sub();
		base.add(1, 2, 3); //sub_1

		Sub s = (Sub)base;
		s.add(1,2,3); // sub_2
	}
}

class Base {
	public void add(int a, int... arr) {
		System.out.println("base");
	}
}

class Sub extends Base {

	public void add(int a, int[] arr) {
		System.out.println("sub_1");
	}

	public void add(int a, int b, int c) {
		System.out.println("sub_2");
	}

}

17.8 动态绑定机制(继承也适用

17.8.1 为什么只有非静态的成员方法有动态绑定机制

因为多态是在重写的基础上实现的,只有非静态方法可以重写,而静态方法和属性都不可以重写,所以只有非静态的成员方法有动态绑定机制。

17.8.2 定义

在这里插入图片描述

17.8.3 使用

对于多态或继承来说,调用成员方法时,执行的是运行时类型的方法(就是子类中重写的方法),如果子类中没有进行重写,就会去调用父类中的方法;如果在调用的方法中还有方法调用,那么还是先执行运行时类型的方法,没有再向上找;

public class DynamicBinding {
    public static void main(String[] args) {
        A a = new B();//向上转型
        System.out.println(a.sum());// 30
        System.out.println(a.sum1());// 20
    }
}

class A {
    public int i = 10;

    public int sum() {
        return getI() + 10;
    }

    public int sum1() {
        return i + 10;
    }

    public int getI() {
        return i;
    }
}

class B extends A {
    public int i = 20;

//    public int sum() {
//        return i + 20;
//    }

//    public int sum1() {
//        return i + 10;
//    }

    public int getI() {
        return i;
    }
}

18.Object类

18.1 Object类的基本介绍

  • Object类是所有java类的根父类;
  • 如果一个类没有显式声明继承其它类,那么这个类直接继承于Object类;
  • Object类只有一个空参构造器;
  • Object类中没有属性,只有方法;
  • 数组可以看作一个特殊的类,它的父类也是Object;

18.2 "=="运算符

  • 用“==”进行比较时,符号两边的数据类型必须兼容(可自动转换的基本数据类型除外),否则编译出错;
  • 可以在基本数据类型变量之间、引用数据类型变量之间比较;
  • 对于基本数据类型变量,比较的是变量存放的数值是否相等。”==“也是运算符,比较时会有自动类型转换,所以不同的基本数据类型也可以比较(boolean除外);
  • 对于引用数据类型变量,比较的是对象的内存地址是否相等;

18.3 equals()方法

18.3.1 Object类中源码

    public boolean equals(Object obj) {
        return (this == obj);
    }

18.3.2 细节

  • 所有的类都继承了equals()方法,如果没有进行重写,那么equals()方法和”==“是一样的;
  • 基本数据类型没有方法/属性,因为类是引用数据类型;
  • String、Date、File、包装类对equals()方法进行了重写,比较的是对象的实体内容(属性)

18.3.3 重写equals()方法

idea中可以使用alt+insert自动生成;
重写equals()方法时,如果属性也是自定义类,那么属性所属类中的equals()方法也要重写;

    public boolean equals(Object o) {
//        1.比较两个对象的内存地址
        if (this == o) return true;
//        2.判断o是否为空,不为空再比较两者的类型是否一致
        if (o == null || getClass() != o.getClass()) return false;
//        3.向下转型
        Test1 test1 = (Test1) o;
//        4.比较两者的属性是否相等
//        对于引用数据类型的属性,要先判断对象是否为空,不为空才能调用equals方法
        return age == test1.age && Objects.equals(name, test1.name);
    }
	public static boolean equals(Object a, Object b) {
		return (a == b) || (a != null && a.equals(b));
	}

18.4 "=="和equals()的区别

  • "=="既可以用于基本数据类型的比较,也可以用于引用数据类型的比较;而equals()只能用于引用数据类型的比较。
  • 对于基本数据类型变量,"==“比较的是变量存放的数值是否相等;对于引用数据类型变量,”=="比较的是对象的内存地址是否相等;equals()如果没有被重写,那么和”==“作用相同,比较的也是对象的内存地址,如果被重写,一般用于比较对象的实体内容(例如:String,包装类,Date)

18.5 equals()练习

int it = 65;
float fl = 65.0f;
System.out.println(“65和65.0f是否相等?” + (it == fl)); //true
char ch1 = 'A'; char ch2 = 12;
System.out.println("65和'A'是否相等?" + (it == ch1));//true
System.out.println(“12和ch2是否相等?" + (12 == ch2));//true
String str1 = new String("hello");
String str2 = new String("hello");
System.out.println("str1和str2是否相等?"+ (str1 == str2));//false
System.out.println("str1是否equals str2?"+(str1.equals(str2)));//true
System.out.println(“hello” == new java.util.Date()); //编译不通过

18.6 toString()方法

18.6.1 Object类中toString的定义方法

public String toString() {
 	return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

18.6.2 细节

1.System.out.println(对象); System.out.println(对象.toString());当对象不是null时,两者效果相同;当对象为null时,直接会输出null,调用方法会报空指针异常;
2.像String、Date、File、包装类等都重写了Object类中的toString()方法,使得在调用toString()时,返回"实体内容"信息;
3.char[]数组也重写了同toString方法;
4.自定义类如果重写toString()方法,当调用此方法时,返回对象的"实体内容".
5.String与其它类型数据进行连接操作时,自动调用toString()方法,对于基本数据类型则是调用包装类的toString()方法;

18.6.3 举例

public class ToStringTest {
	public static void main(String[] args) {
		
		Customer cust1 = new Customer("Tom" ,21);
		System.out.println(cust1.toString());	//github4.Customer@15db9742
		System.out.println(cust1); 	//github4.Customer@15db9742 ---> Customer[name = Tom,age = 21]
		
		String str = new String("MM");
		System.out.println(str);
		
		Date date = new Date(45362348664663L);
		System.out.println(date.toString());	//Wed Jun 24 12:24:24 CST 3407
		
	}
}

    public void test() {
        char[] arr = new char[] { 'a', 'b', 'c' };
        System.out.println(arr);//abc
        int[] arr1 = new int[] { 1, 2, 3 };
        System.out.println(arr1);//[I@722c41f4
        double[] arr2 = new double[] { 1.1, 2.2, 3.3 };
        System.out.println(arr2);//[D@5b80350b
    }

19.单元测试

19.1 要求

  • 类的权限修饰符是public,且只能有一个public的空参构造器;
  • 测试方法的权限修饰符是public,返回值为void,参数为空;

19.2 idea中添加模板

在live-template中添加了代码模板,快捷键是test;
在这里插入图片描述

19.3 使用

import org.junit.Test;

/**
 * @Description:
 * @author:zhou
 * @create: 2022-01-22 16:56
 */
public class JUnitTest {
    @Test
    public void testEquals(){
        System.out.println("123");
    }
    @Test
    public void test123(){
        System.out.println("456");
//        System.out.println(1/0);
    }

}

20.包装类

java学习笔记2–面向对象编程-小白菜博客
包装类是引用数据类型,变量的默认值是null

20.1 8种基本数据类型对应的包装类

基本数据类型 包装类
int Integer
byte Byte
short Short
long Long
float Float
double Double
boolean Boolean
char Character

20.2 基本数据类型转为包装类

使基本数据类型拥有类的特性(方法,属性),常用自动装箱

        int i = 123;
//        方法1:将基本数据类型当作参数传入Integer构造器
        Integer n = new Integer(i);
        System.out.println(n); // 通过println输出时,n和n.toString()的效果相同
        System.out.println(n.toString());

//        方法2:通过字符串参数构造包装类对象,字符串中的类型一定要与包装类兼容
        Integer n1 = new Integer("123");
        System.out.println(n);

        Float f = new Float(23.1);
        System.out.println(f);

        Double d = new Double("23.1");
        System.out.println(d);

//        方法3:自动装箱
        Integer n2 = 5;
        System.out.println(n2.toString());

20.3 包装类转为基本数据类型

包装类转为基本数据类型,可以进行运算;常用自动拆箱

//        方式1:通过包装类的xxxValue()方法
        Integer i = new Integer(123);
        int i1 = i.intValue();
        System.out.println(i1);
//        方式2:自动拆箱
        int i2 = i;
        System.out.println(i2);

20.4 基本数据类型、包装类转为String

  • 因为包装类会自动拆箱转为基本数据类型,所以基本数据类型转为String的方法包装类也适用;
  • 除此之外,包装类还可以直接调用toString()方法;
//        方式1:直接与""连接
        Integer i = new Integer(123);
        String s = i + "";
        System.out.println(s);

//        方式2:调用String的valueOf()方法
        int i1 = 234;
        String s1 = String.valueOf(i1);
        System.out.println(s1);

20.5 String转为基本数据类型

使用包装类的parseXxx()方法,返回值类型是基本数据类型;
使用包装类的valueOf()方法;
Character没有parseChar()方法,使用String的charAt()方法就行;

        int i2 = Integer.parseInt(s1);
        System.out.println(i2+1);

20.6 面试题

20.6.1 习题1

Integer类中内置了一个Integer数组,里面存放的是[-128,127]的所有整数,如果使用自动装箱的方式赋值并且数值在[-128,127],那么就可以直接使用数组中的元素,不用new新的对象,提高了效率;如果超过这个范围,就需要new新对象;

public void method1() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); // false
Integer m = 1;
Integer n = 1;
System.out.println(m == n);// true
Integer x = 128;
Integer y = 128;
System.out.println(x == y);// false
}

20.6.2 习题2

java学习笔记2–面向对象编程-小白菜博客
通过IntegerCache方法可以得知,缓存在第一次使用时初始化,所以说如果已经创建了一个缓存中的整数,使用valueOf创建第二次时,不会使用new关键字,而用已经缓存的对象。所以使用valueOf方法创建两次对象,若对应的数值相同,且数值在-128~127之间时,两个对象都指向同一个地址。Integer i = 400这样的方式来创建Integer对象,与valueOf方法的效果是一样的。

    public static Integer valueOf(int i) {
        if (i >= IntegerCache.low && i <= IntegerCache.high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
}

20.7 int和Integer进行比较

int1 == integer1,Integer是int的封装类,当Integer与int进行==比较时,Integer就会拆箱成一个int类型,所以还是相当于两个int类型进行比较,这里的Integer,不管是直接赋值,还是new创建的对象,只要跟int比较就会拆箱为int类型,所以就是相等的。

21.static关键字

21.1 意义

我们有时候希望无论是否产生对象,无论产生了多少个对象,某些特定的内容在内存中只有一份,这样可以节省内存空间。

21.2 使用

static可以用来修饰属性、方法、代码块、内部类,表示这些内容都是属于类的,所有对象共享一份,任何对象去访问修改都是同一个。

21.2.1 static修饰属性

  • 格式:权限修饰符 static 数据类型 变量名;
  • static修饰的属性称为类变量或静态变量,没有static修饰的属性称为实例变量;
  • 静态变量随类的加载而加载,只加载一次,位于方法区的静态域中;
  • 静态变量可以使用类名或对象名调用,实例变量只能使用对象名调用;

21.2.2 static修饰方法

  • 格式:权限修饰符 static 返回值类型 方法名(参数){方法体}
  • 如果方法与调用者无关,则这样的方法通常被称为类方法或静态方法,由于不需要创建对象就可以调用类方法,简化了方法的调用。
  • 静态方法随类的加载而加载,只加载一次;
  • 静态方法可以使用类名或对象名调用,其中使用对象名调用时底层会转化为 "类名."调用的方式,所以如果对象值为null,通过此对象调用静态方法依然可以正常编译运行Test test = null ;但是test.hello();------>Test.hello();
  • 静态方法中不能使用this和super,因为它不属于任何一个对象;

21.3 细节

  • static只能用于修饰成员变量,不能用于修饰局部变量;
  • 静态方法只能访问静态成员,不能直接访问非静态,因为静态是在非静态之前加载的,如果想要访问,必须创建对象来访问;
  • 非静态方法可以访问静态成员和非静态成员;
  • 使用类来调用静态成员时,也需要考虑权限修饰符
  • 静态方法可以被继承,但是不能被重写;

21.4 内存解析

在这里插入图片描述

21.5 什么时候需要静态变量和静态方法

21.5.1 静态变量

如果需要让一个类中的所有对象都共享一个变量,那么就可以考虑静态变量。例如:定义了一个Student类,需要统计所有学生个数,可以使用静态变量来做计数器。

21.5.2 静态方法

  • 操作静态属性的方法,通常声明为静态方法。例如静态变量的getter和setter方法;
  • 方法中不涉及到任何与对象相关的成员,可以考虑静态方法。例如:Arrays,Math,Collection集合类等;

21.6 单例模式

21.6.1 设计模式定义

设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。可以理解为套路。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。不同的英雄拥有不同的连招。

21.6.2 单例模式

定义:所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法。
三大要素:私有构造器,存放当前类对象的静态变量(因为静态方法不能访问非静态成员),提供给外界获取对象的静态方法。

21.6.3 饿汉型

优点:线程安全;
缺点随着类的加载而创建对象,所以对象的生命周期过长;
**注意事项:**如果不提供静态方法,而是直接将属性改为public的,在外部也可以通过调用属性获取对象,但是违背了封装性的原则,因为此时在外部可以随意修改属性的值。如果仍然想使用属性获取对象,那么可以将属性变为static final,这样外部就不能修改属性值了。

public class SingleTon {
    private SingleTon() {
    }
    private static SingleTon singleTon = new SingleTon();

    public static SingleTon getSingleTon() {
        return singleTon;
    }
}
public class SingleTon {
    private SingleTon() {
    }
    public static final SingleTon singleTon = new SingleTon();
}

21.6.4 懒汉型

优点:第一次调用静态方法时创建对象,延迟对象创建,减少系统开销;

当前写法线程不安全
class SingleTon2{
    private SingleTon2(){

    }
    private static SingleTon2 singleTon2;
    public static SingleTon2 getSingleTon2(){
        if(singleTon2==null){
            singleTon2 = new SingleTon2();
        }
        return singleTon2;
    }
}

21.6.5 单例模式的优点

由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。

21.6.6 单例模式的应用

  • 网站的计数器,一般也是单例模式实现,否则难以同步。
  • 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
  • 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
  • Application也是单例的典型应用
  • Windows 的 Task Manager (任务管理器)就是很典型的单例模式
  • Windows 的 Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。

22.理解 main 方法的语法

22.1 main()方法格式解析

由于 Java 虚拟机需要调用类的 main()方法,所以该方法的访问权限必须是 public,又因为 Java 虚拟机在执行 main()方法时不必创建对象,所以该方法必须是 static 的,该方法接收一个 String 类型的数组参数,该数组中保存执行 Java 命令时传递给所运行的类的参数。

22.2 细节

  • main()方法主要的作用是充当程序的入口;
  • main()方法也可以看作普通的静态方法,可以通过类名或者对象来调用;
  • main()方法也可以作为我们与控制台交互的方式;
  • 因为 main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员;

23.代码块

  • 代码块位于类中方法外;
  • 代码块只能用static修饰,不能使用权限修饰符;
  • 代码块无需调用,随着类或对象的加载而自动执行;

23.1 静态代码块

23.1.1 格式

static{代码块内容;}

23.1.2 细节

  • 静态代码块一般用于初始化类的信息;
  • 静态代码块随着类的加载而执行,且只执行一次,所以优先于main方法和构造器的执行;
  • 一个类中定义多个静态代码块时,按声明的先后顺序执行,但是一般一个类中只定义一个静态代码块;
  • 静态代码块中只能调用静态成员,不能调用非静态成员;

23.2 非静态代码块

23.2.1 格式

{代码块内容;}

23.2.2 细节

  • 非静态代码块一般用于初始化对象的信息;
  • 非静态代码块随着对象的创建而执行,每创建一个对象都会执行一次,优先于构造器的执行;
  • 一个类中定义多个非静态代码块时,按声明的先后顺序执行,但是一般一个类中只定义一个非静态代码块;
  • 非静态代码块可以调用静态成员和非静态成员;

23.3 程序中加载顺序

总原则:由父及子,静态先行。

23.3.1 练习1

过程:
1.想要创建对象,先要加载类的信息,因为“由父及子”,所以先加载父类的信息(Object类 -> Root类 -> Mid类 -> Leaf类),静态代码块随类的加载而执行,所以就先执行三个类中的静态代码块和静态成员变量(按照在程序中出现的顺序初始化);
2.类的信息加载完毕,然后创建对象,非静态代码块和普通成员变量随对象的创建而执行,且先于构造器的执行,所以先执行父类中的非静态代码块、普通成员变量(按照在程序中出现的顺序初始化),接着执行父类中的构造器,然后执行子类的;
3.第二次创建对象时,静态代码块不再执行。

class Root{
	static{
		System.out.println("Root的静态初始化块");
	}
	{
		System.out.println("Root的普通初始化块");
	}
	public Root(){
		System.out.println("Root的无参数的构造器");
	}
}
class Mid extends Root{
	static{
		System.out.println("Mid的静态初始化块");
	}
	{
		System.out.println("Mid的普通初始化块");
	}
	public Mid(){
		System.out.println("Mid的无参数的构造器");
	}
	public Mid(String msg){
		//通过this调用同一类中重载的构造器
		this();
		System.out.println("Mid的带参数构造器,其参数值:"
			+ msg);
	}
}
class Leaf extends Mid{
	static{
		System.out.println("Leaf的静态初始化块");
	}
	{
		System.out.println("Leaf的普通初始化块");
	}	
	public Leaf(){
		//通过super调用父类中有一个字符串参数的构造器
		super("123");
		System.out.println("Leaf的构造器");
	}
}
public class LeafTest{
	public static void main(String[] args){
		new Leaf(); 
		
		new Leaf();
	}
}

Root的静态初始化块
Mid的静态初始化块
Leaf的静态初始化块
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器
--------------------------------------------------------
Root的普通初始化块
Root的无参数的构造器
Mid的普通初始化块
Mid的无参数的构造器
Mid的带参数构造器,其参数值:尚硅谷
Leaf的普通初始化块
Leaf的构造器

23.3.2 练习2

静态代码块优于main()方法的执行

class Father {
	static {
		System.out.println("11111111111");
	}
	{
		System.out.println("22222222222");
	}

	public Father() {
		System.out.println("33333333333");
	}
}

public class Son extends Father {
	static {
		System.out.println("44444444444");
	}
	{
		System.out.println("55555555555");
	}
	public Son() {
		System.out.println("66666666666");
	}
	public static void main(String[] args) { // 由父及子 静态先行
		System.out.println("77777777777");
		System.out.println("************************");
		new Son();
		System.out.println("************************");

		new Son();
		System.out.println("************************");
		new Father();
	}
}
11111111111
44444444444
77777777777
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333
55555555555
66666666666
************************
22222222222
33333333333

23.4 属性赋值的执行顺序

1.默认初始化
2.显式初始化或者非静态代码块初始化(看两者声明的先后顺序)
3.构造器初始化
4.调用对象.属性对象.方法进行赋值

24.final关键字

  • static和final同时使用时(两者没有先后顺序),可以修饰属性和方法;
    在这里插入图片描述

24.1 练习1

    public class Something {
        public int addOne(final int x) {
            return ++x;  编译错误,x不能重新赋值
            // return x + 1; 正确,没有赋值操作
        } }

24.2 练习2

public class Something {
public static void main(String[] args) {
Other o = new Other();
new Something().addOne(o);
}
public void addOne(final Other o) {
// o = new Other();  错误,不能重新赋值
o.i++;  正确,引用数据类型变量中的内容可以改变,因为其属性不是final} }
class Other {
public int i; }

25. abstract关键字

25.1 意义

如果一个方法不确定方法体的内容,可以将该方法声明为抽象的,到子类中去重写该方法,该方法所在的类是抽象类。

25.2 抽象类

25.2.1 格式

权限修饰符 abstract  class 类名{}

25.3 抽象方法

25.3.1 格式

权限修饰符 abstract 返回值类型 方法名(形参列表)

25.4 细节

  • 抽象类不能实例化,但是有构造器,因为抽象类的子类实例化需要调用父类的构造器;
  • 有抽象方法的类一定是抽象类,但是抽象类中可以没有抽象方法;
  • 抽象类的子类要么重写父类(直接或间接)所有的抽象方法,要么也声明为抽象类;
  • 抽象类的子类如果想要实例化,必须重写父类中所有的抽象方法;
  • abstract只能用于修饰方法和类;
  • 抽象类可以实现接口,因为实现类中如果不重写接口中的所有抽象方法,就是抽象类;
  • 抽象类可以继承非抽象的类,因为除了Object类外,其余类都有父类;

25.5 抽象类与普通类的区别

抽象类只比普通类多了定义抽象方法的功能,而且不能被实例化,其它没有什么不同(都可以有任意的成员)。

25.6 abstract不能修饰静态方法、私有方法、final方法、final类

  • 静态方法:静态方法不能被重写,abstract修饰的方法必须被重写;抽象的方法不允许直接调用,如果是静态的,就可以通过类名进行调用,存在冲突;
  • 私有方法:由于封装性,私有方法不能被重写,abstract修饰的方法必须被重写;
  • final方法:final修饰方法表示不能被重写,abstract修饰的方法必须被重写;
  • final类:final修饰的类不能被继承,但是抽象类必须要被继承才有意义;

25.7 抽象类匿名子类的匿名对象

  • 一次性且需要重写所有的抽象方法
  • 非抽象类也有匿名子类的匿名对象,和抽象类的格式相同;

25.7.1 抽象类匿名子类的对象

class EmployeeTest {
    public static void main(String[] args) {
     Employee是抽象类,匿名子类是不创建抽象类的子类
     直接使用下面的形式来创建对象,需要重写所有的抽象方法
        Employee e = new Employee() {
            @Override
            public void work() {
                System.out.println();
            }
        };
        
    }
}
public abstract class Employee {
   public Employee() {
    }
   public abstract void work();
 }

25.7.2 抽象类匿名子类的匿名对象

class EmployeeTest {
    public static void main(String[] args) {
    需要重写所有的抽象方法
        new Employee() {
            @Override
            public void work() {
                System.out.println();
            }
        }.work();
        
    }
}
public abstract class Employee {
   public Employee() {
    }
   public abstract void work();
 }

25.8 模板方法设计模式

  • 抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式;
  • 当功能内部一部分实现是确定的,一部分实现是不确定的,将不确定的抽象化,让子类去实现;

25.8.1 模板方法的应用

  • 数据库访问的封装
  • Junit单元测试
  • Spring中JDBCTemlate、HibernateTemplate等

25.8.2 例子

public class TemplateTest {
    public static void main(String[] args) {
        SubTemplate subTemplate = new SubTemplate();
        subTemplate.sendTime();
    }

}

abstract class Template {
    public final void sendTime() {
        long start = System.currentTimeMillis();
        code();
        long end = System.currentTimeMillis();
        System.out.println(end = start);
    }
//  将code抽象化,让子类重写
    protected abstract void code();
}

class SubTemplate extends Template {
    @Override
    protected void code() {
        for (int i = 0; i < 1000; i++) {
            for (int j = 0; j < 1000; j++) {

            }
        }
    }
}

25.8 抽象方法应用实例

  • 圆形、矩形、三角形有共同的父类几何图形,在几何图形中有一个求面积的方法,但是在几何图形中不知道如何实现该方法,所以只能将该方法声明为抽象的,在子类中对其重写。
  • IO中的InputStream,OutputStream,Reader,Writer,在其内部定义了read()和write()抽象方法。

26.接口

26.1 概述

26.1.1 定义

接口就是规范,定义的是一组规则,体现了现实世界中“如果你是/要…则必须能…”的思想。==继承是一个"是不是"(is a)的关系,而接口实现则是"能不能"的关系。==接口的本质是契约,标准,规范,就像我们的法律一样。制定好后大家都要遵守。

26.1.2 格式

  • 接口的权限修饰符和类一样,只能是public和缺省;
权限修饰符  interface 接口名1,接口名2,....{}

26.1.3 意义

  • 类与接口的区别:一方面,有时必须从几个类中派生出一个子类,继承它们所有的属性和方法。但是,Java 不支持多重继承。有了接口,就可以得到多重继承的效果(一个类实现多个接口)。
  • 实现与继承的区别:另一方面,有时必须从几个类中抽取出一些共同的行为特征,而它们之间又没有 is-a 的关系,仅仅是具有相同的行为特征而已。例如:鼠标、键盘、打印机、扫描仪、摄像头、充电器、MP3 机、手机、数码相机、移动硬盘等都支持 USB 连接;鸟和飞机都能费。

26.1.4 细节

  • 接口不能实例化;
  • 接口中的静态成员只能通过接口名调用,不能通过实现类对象调用;
  • 接口中只能声明全局常量和方法,没有代码块和构造器;

26.1.5 实现类

格式:权限修饰符 class 类名 extends 父类名 implements 接口名{}

26.1.6 接口和类之间的关系

在这里插入图片描述

26.2 接口中的成员

26.2.1 JDK1.8之前

接口中只能有全局常量和抽象方法;

26.2.2 JDK1.8之后

接口中增加了静态方法(1.8)、默认方法(1.8)、私有方法(1.9)、私有静态方法(1.9);

26.2.3 默认方法

格式:[public] default 返回值类型 方法名(参数){}

  • 接口中的默认方法相当于类中的普通方法,由于JDK1.8之前接口中的方法都是抽象的,为了兼容,所以增加了default关键字;
  • public 可以省略;
  • 接口中的默认方法有方法体,可以被实现类继承,也可以被实现类重写(重写不用加default);
  • 接口中的默认方法在实现类中被重写后,如果想要在实现类中调用接口中的默认方法,使用接口.super.默认方法();
  • 一个类实现多个接口,并且多个接口中的默认方法同名同参,那么实现类必须重写一个默认方法;
  • 接口中的默认方法和父类中的非抽象方法同名同参时,不会冲突,优先使用父类中的方法;

26.2.4 静态方法

格式:[public] static 返回值类型 方法名(参数){}

  • 接口中的静态方法不能被实现类继承,只能通过接口名进行调用;

26.2.5 抽象方法

格式:[public] [abstract] 返回值类型 方法名();

  • 接口中的抽象方法的权限修饰符和abstract关键字都可以省略;
  • 实现类中必须重写接口中的所有抽象方法,不然实现类也是抽象的;

26.2.6 私有方法和私有静态方法

格式:private [static] 返回值类型 方法名(参数){}

  • 只能在接口内部被调用,主要用于解决多个默认/静态方法代码冗余的问题;

26.2.7 全局常量

格式:[public static final] 类型 变量名 = 变量值;

  • 只能显式赋值,因为接口中没有代码块和构造器;
  • 只能通过接口名进行调用;

26.3 抽象类与接口的异同

  • 抽象类和接口都不能实例化,但是抽象类中有构造器,接口中没有;
  • 抽象类和接口的权限修饰符一样,只能是public和缺省;
    在这里插入图片描述

26.4 应用实例

  • JDBC:操作数据库时,我们调用的都是JDBC中定义的接口,而不是某个数据库厂商提供的api

26.5 父类的变量和接口中的变量相同

interface  A{
   int x = 0;
}
class B{
   int x =1;
}
class C extends B implements A {
   public void pX(){
      System.out.println(x);  //super.x   A.x
   }
   public static void main(String[] args) {
      new C().pX();
   }
}
答案:错误。在编译时会发生错误(错误描述不同的JVM有不同的信息,意思就是未明确的x调用,
两个x都匹配(就象在同时import java.util和java.sql两个包时直接声明Date一样)。对于父类的变量,可以用super.x来明确,
而接口的属性默认隐含为 public static final.所以可以通过A.x来明确。

27.内部类

  • 成员内部类和局部内部类都会生成对应的字节码文件;

27.1 概述

27.1.1 定义

允许一个类A声明在另一个类B的内部,那么类A就是内部类,类B是外部类;根据类声明的位置,分为成员内部类(静态和非静态)和局部内部类(方法内、代码块内、构造器内);

27.1.2 不同类的权限修饰符

  • 外部类:public和缺省
  • 成员内部类:public/protected/确省/private
  • 局部内部类:什么都不写

27.1.3 意义

只有当前类使用此结构,所以定义为内部类。

27.2 成员内部类

27.2.1 作为外部类的成员

  • 调用外部类的结构
  • 可以被static修饰
  • 可以被4种不同的权限修饰

27.2.2 作为一个类

  • 类内可以定义属性、方法、构造器等
  • 可以被final修饰,表示此类不能被继承。言外之意,不使用final,就可以被继承
  • 可以abstract修饰

27.2.3 实例化内部类

静态成员内部类:InnerClass.A a = new InnerClass.A();
非静态成员内部类:InnerClass innerClass = new InnerClass(); InnerClass.B b = innerClass.new B();

public class InnerClass {
    public static class A{
//        成员内部类
    }
    class B{

    }
  }

27.2.4 细节

  • 静态内部类只能访问外部类的静态成员;
  • 非静态内部类可以访问外部类的所有成员,包括私有成员;
  • 静态内部类可以有静态成员,非静态内部类不可以有静态成员;

27.2.5 内部类中调用外部类成员

  • 如果没有重名,直接调用即可;
  • 如果重名,this.成员表示调用内部类对象,外部类.this.成员表示调用外部类成员;

27.2.6 例题

public class OuterClass { 
  private double d1 = 1.0; 
    //insert code here 
} 
You need to insert an inner class declaration at line 3. Which two inner class declarations are 
valid?(Choose two.) 
A. class InnerOne{
     public static double methoda() {return d1;}
   } 
B. public class InnerOne{
     static double methoda() {return d1;}
   } 
C. private class InnerOne{
     double methoda() {return d1;}
   } 
D. static class InnerOne{
     protected double methoda() {return d1;}
   } 
E. abstract class InnerOne{
     public abstract double methoda();
   } 
说明如下:
一.静态内部类可以有静态成员,而非静态内部类则不能有静态成员。 故 AB 错
二.静态内部类的非静态成员可以访问外部类的静态变量,而不可访问外部类的非静态变量;return d1 出错。 
故 D 错
三.非静态内部类的非静态成员可以访问外部类的非静态变量。 故 C 正确
四.答案为CE 

27.3 局部内部类


使用较少,一般使用匿名内部类
局部内部类只能在方法内部使用,出了方法就不能使用

27.3.1 匿名内部类

  1. 匿名内部类必须继承父类或实现接口。
  2. 匿名内部类对象只能使用多态形式引用,父类引用指向子类对象。
  3. 重写接口中所有的抽象方法是因为只有非抽象的类才能创建对象,除了抽象方法,匿名内部类中可以重写父类或接口中的任意方法来实现自己的需求
接口名称 变量名 = new 父类构造器(实参列表)|实现接口(){重写接口中所有的抽象方法}

27.3.2 练习

public class Test {
    public static void main(String[] args) {   
    // 创建了一个Object类的子类对象重写了Object中的equals方法,并赋值给父类引用,
    // 所以使用对象o调用的是子类重写的方法
        Object o = new Object() {  
             public boolean equals(Object obj) {  
                 return true; 
         }
     };   
     System.out.println(o.equals("Fred"));
     }
}

27.3.3 匿名内部类的匿名对象

new 接口名称(){重写接口中所有的抽象方法}.方法();

27.3.4 注意事项

局部内部类中访问所在方法的局部变量时,这个局部变量必须是final的(java8之后可以省略)。因为从生命周期角度来说,new一个内部类对象是在堆中,而局部变量在栈中,方法结束后出栈,局部变量消失,而内部类的对象还存在堆中,等待垃圾回收; 后边在说