Java基础(08) 面向对象

Java基础(八)-- 面向对象


面向对象(Object Oriented Programming, 简称OOP )是Java基础学习的重点,必须掌握每个细节,接下来对面向对象的学习主要围绕以下三点:

  1. Java类及类的成员:属性、方法、构造器、代码块、内部类
  2. 面向对象的三大(有些书中是四大)特征:封装性、继承性、多态性、(抽象性)
  3. 其他关键字:this、super、static、final、abstract、interface、package、implement、package、import

面向过程(POP)与面向对象(OOP)

  • 面向过程强调的是功能行为,以函数为最小单位,考虑怎么做
  • 面向对象强调具备了功能的对象,以类 / 对象为最小单位,考虑谁来做

面向对象分析问题的思路

  1. 根据问题需要,选择问题针对的现实世界中的实体

  2. 从实体中抽象出问题相关的属性和功能,形成概念世界中的类

  3. 将抽象的实体使用计算机语言描述,形成计算机世界的类

  4. 类实例化成计算机世界中的对象,对象是计算机世界中解决问题的最终工具

    ​ 简而言之,便是从现实世界中寻找实体,将实体的属性与功能进行抽象,将它包装成实体的类,将该类用高级编程语言实现,再对该类实例化为对象,调用其中的属性与方法解决问题

类和对象

​ 类(Class)和对象(Object)是面向对象的核心概念

  • 是对一类事物的描述,是抽象的,概念上的定义

  • 对象是实际存在的该类的事物的具体个体,因此也可称为实例(instance)

  • “万事万物皆对象”

    1. 在Java语言范畴中,都将功能、结构等封装到类中,通过类的实例化来调用具体的功能结构
    2. 前后端在Java层面交互时,都体现为类、对象

类及类的成员

​ 设计类实际上就是设计类的成员,而类的成员包括:

  1. 属性,实际上相当于(成员变量 = field = 域、字段)
  2. 方法,实际上相当于(成员方法 = method = 函数)
  3. 构造器,用于构造对象或同时进行对象初始化(Constructor)
  4. 代码块
  5. 内部类

对象的创建与使用

​ 通过类 变量名 = new 类();即可实例化一个该类的对象,通过对象.属性可以调用该对象的属性,对象.方法可以调用该对象的方法

​ 注:引用变量的值只有 null地址值

  • 其中new 类().属性new 类().方法,即创建对象没有显式的赋值给一个变量名,即为匿名对象

    特征:匿名对象只能调用一次

对象的内存解析

Java基础(08) 面向对象_第1张图片

属性与局部变量

  • 相同点

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

    1. 在类中声明的位置不同

      属性:直接在类中的一对{ }内声明

      局部变量:声明在方法内、方法形参、代码块内、构造器形参、构造器内部变量

    2. 关于权限修饰符不同

      属性:可以在声明属性时,使用权限修饰符指明其权限

      常用的权限修饰符:private、public、protected、缺省

      局部变量:不能使用权限修饰符

    3. 默认初始化值

      属性:类的属性,根据其类型都有默认的初始值

      ​ 整型(long、int、short、byte):0

      ​ 浮点型(double、float):0.0

      ​ 字符型(char):0(或’\u0000’,不是’0’)

      ​ 布尔型(boolean):false

      ​ 引用数据类型(类、数组、接口):null

      局部变量:没有默认初始化值,意味着在使用前需要显式赋值;特别的,形参在调用时赋值即可

    4. 在内存中加载的位置

      属性:非static的属性加载到堆空间中(static修饰的属性加载到方法区中)

      局部变量:加载到栈空间

类的声明和使用

  1. 属性

    权限修饰符 修饰符 变量名(即为标识符);
    
  2. 方法

    方法的声明:
    
    权限修饰符 修饰符 返回值类型 方法名(形参列表){
        方法体;
    }
    
  3. 构造器

    权限修饰符 类名(形参列表){
        实例化该对象时的操作;
    }
    

​ 注:代码块和内部类不是必须具备的类的成员,此处不多加讨论,在下文中详细讨论

类的成员:属性(Field)

属性赋值的先后顺序

类的属性有几种赋值方式:

class Test{
    // 1.默认赋值(默认初始化)
    int i;
    // 2.显式赋值(显式初始化)
    int j = 1;
    // 3.构造器中赋值(构造器中初始化)
    Test(int i){
      this.i = i;
    }
    // 4.通过"对象.方法" 或 “对象.属性”的方式赋值
    public void setI(int i){
      this.i = i;
    }
    // 5. 在代码块中赋值
    {
        j = 3;
    }
}
  • 其中(1)(2)(3)(5)在创建一个对象时都只执行一次,所以也可以称为初始化,而(4)可执行多次

  • 以上赋值操作的先后顺序是:默认赋值 ==> 显式赋值 / 在代码块中赋值 ==> 构造器中赋值 ==> 通过"对象.方法" 或 “对象.属性”的方式赋值

  • 在非静态代码块中对属性赋值,其赋值顺序与显示赋值相同,显式赋值与代码块赋值按照其编写的先后顺序来执行

    // 例1
    {
        i = 1;
    }
    int i = 2;
    // 此时依旧可以执行,且i为2
        
    // 例2
    int j = 2;
    {
        j = 3;
    }
    // 此时j为3
    

类的成员:方法(Method)

方法的重载(overload)
  • 重载的概念:在同一个类中,允许存在一个以上同名的方法,只需它们之间的参数个数或参数类型不同即可

  • 重载的特点:与返回值类型无关(返回值类型可以相同,也可以不同),只看参数列表,且参数列表必须不同,调用时,根据参数列表的不同来进行区别

  • 重载示例:

    // 对int型数组进行排序
    static void sort(int[] arr){}
    // 对double型数组进行排序
    static void sort(double[] arr){}
    // 对范围内int型数组进行排序
    static boolean sort(int[] arr, int head, int rear){return false;}
    

    注:判断方法是否重载只需关注“两同一不同”:方法在同一个类中定义、方法名相同、参数列表不同(即参数个数不同、参数类型不同);与方法的权限修饰符、返回值类型、形参变量名、方法体均无关系

方法的重写(override / overwirte)
  • 重写的概念:在子类中可以根据需要对父类中继承来的方法进行改造,这也称为方法的重置、覆盖,在程序执行时,子类的方法覆盖父类的同名同参方法

  • 重写的规则:

    1. 子类重写的方法的方法名和形参列表与父类中被重写的方法的方法名和形参列表相同

    2. 子类重写的方法的权限修饰符 不小于 父类中被重写的方法的权限

      注:子类不能重写父类的private方法

      注:静态方法不能被重写

    3. 返回值类型:

      • 若父类被重写的方法的返回值类型为void,则子类重写的方法的返回值只能为void
    • 若父类被重写的方法的返回值类型为A类,则子类重写的方法的返回值可以是A类或A类的子类
      • 父类被重写的方法的返回值类型为基本数据类型,则子类重写的方法的返回值必须是相同的基本数据类型
    1. 子类重写的方法抛出的异常类型 不大于 父类被重写的方法抛出的异常

    注:子类和父类中的同名同参方法要么都声明为非static的(考虑重写),要么都声明为static的(以static声明的方法不是重写)

JavaSE 5.0新特性:可变个数形参

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

// JDK 5.0之前,采用数组形参来定义方法,传入多个统一类型变量
public static void test(int i, String[] ss);
// JDK 5.0之后,采用可变个数形参来定义方法,传入多个统一类型变量
public static void test(int a, String ... strs);

具体使用:

  • 可变个数形参的格式:数据类型 ... 变量名

  • 当调用可变个数形参的方法时,传入的参数个数可以是:0、1、2…

  • 可变个数形参的方法与本类中方法名相同,形参不同的方法之间构成重载

  • 可变个数形参与本类中方法名相同,形参也相同的数组之间不构成重载,换而言之,二者不能共存

    示例:
    static void test(String ... strs){}
    // 使用可变个数形参后不能有String数组作为形参的test方法,下述方法不能定义
    // Duplicate method test(String[]) in type ......
    static void test(String[] strs){}
    
  • 可变个数参数在方法的形参中必须声明在末尾

  • 因为可变个数i形参必须声明在末尾,所以最多只能声明一个可变形参

    示例:
    // 可以正常声明的函数
    static void test(int i, String ... strs){}
    // 报错:The variable argument type String of the method test must be the last parameter
    static void test(String ... strs, int i){}
    

    注:在使用可变个数形参时,可以当作传入数组使用

    public static void main(String[] args){
        // TODO
        test("hello", "world", "!!!");
        System.out.println();
        test(new String[]{"你好", "世界", "!!!"});
    }
    
    static void test(String ... strs){
        for(int i = 0; i < strs.length; i++)
            System.out.print(strs[i] + '\t');
    }
    
    // 发现其输出效果相同
    
方法参数的值传递机制(重要)

方法,必须由其所在的类或对象调用才有意义,弱方法中含有参数:

  • 形参:方法声明时的参数
  • 实参:方法调用时实际传给形参的参数值

​ 而Java中参数传递的方式只有一种值传递,即将实际参数值的复制传入方法内,而参数本身不受到影响

  • 形参是基本数据类型:将实参基本数据类型变量的**“数据值”**传递给形参
  • 形参是引用数据类型:将实参引用数据类型的**“地址值”**传递给形参
递归方法

递归方法:一个方法体内调用它自身

  • 方法递归包含了一种隐式的循环,它会重复执行某段代码,但是这种重复执行无序循环控制
  • 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,即递归一定要有其中止条件
// 示例 TODO
// 经典例题一:斐波那契数列 f(1) = 1, f(2) = 1, f(n) = f(n-1) + f(n - 2)
public int f(int n){
    if(n < 3)
        return n;
    return f(n - 1) + f(n - 2);
}

// 经典例题二:汉诺塔

// 经典例题三:快速排序

类的成员:构造器(Constructor)

构造器的作用:

  • 创建对象 new 构造器
  • 初始化对象

定义构造器的格式:权限修饰符 类名(形参列表){}

构造器的特点:

  1. 一个类中定义多个构造器,彼此也构成重载

  2. 若是没有显式定义构造器,系统会默认提供一个空构造器,一旦显式定义了构造器后,系统就不再默认提供无参构造器

  3. 一个类中,至少会有一个构造器

类的成员:代码块(block)

代码块,又称初始化块

代码块的作用:用于初始化类、对象

代码块若要使用关键字修饰,只能使用static修饰

代码块的格式:{ }

代码块分类:静态代码块、非静态代码块

  • 静态代码块
    • 内部可以有输出语句
    • 随着类的加载而执行,且只执行一次
    • 若在一个类中定义了多个静态代码块,则按照声明的先后顺序执行
    • 静态代码块 先于 非静态代码块 执行
    • 静态代码块内只能调用静态属性、静态方法,不能调用非静态的结构
    • 作用:初始化类的信息
  • 非静态代码块
    • 内部可以有输出语句
    • 随着对象的创建而执行
    • 每创建一个对象就执行一次
    • 若在一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
    • 非静态代码块内可以调用调用静态属性、静态方法,或调用非静态属性、非静态方法
    • 作用:可以在创建对象时,对对象的属性进行初始化
// 代码块执行顺序
{
    System.out.println("block_1");
}

static {
    System.out.println("static block_1");
}

{
    System.out.println("block_2");
}

static {
    System.out.println("static block_2");
}

类中各成员的加载执行顺序

由父及子,静态先行

package com.demo.main;

class Root{
	static {
		System.out.println("Root静态代码块执行");
	}
	
	{
		System.out.println("Root非静态代码块执行");
	}
	
	public Root(){
		System.out.println("Root无参构造器执行");
	}
	
	public static void run(){
		System.out.println("Root静态方法执行");
	}
}

class Mid extends Root{
	static {
		System.out.println("Mid静态代码块执行");
	}
	
	{
		System.out.println("Mid非静态代码块执行");
	}
	
	public Mid(){
		System.out.println("Mid无参构造器执行");
	}
	
	public static void run(){
		System.out.println("Mid静态方法执行");
	}
}

class Leaf extends Mid{
	static {
		System.out.println("Leaf静态代码块执行");
	}
	
	{
		System.out.println("Leaf非静态代码块执行");
	}
	
	public Leaf(){
		System.out.println("Leaf无参构造器执行");
	}
	
	public static void run(){
		System.out.println("Leaf静态方法执行");
	}
}

public class BlockTest extends Leaf {
	
	public static void main(String[] args) {
		// 
		System.out.println("main方法执行");
		new Leaf();
	}
}

从上述代码的运行结果可以看出,创建对象时,会先执行其父类中的静态代码块,然后向下执行直至本类中的静态代码块执行完成,而后再执行从父类到本类中的所有非静态代码块,再同样的执行从父类到本类中的无参构造器,体现了由父及子,静态先行的规则

类的成员:内部类(Inner class)

Java中允许将一个类 A 声明在另一个类 B 中,则类A是内部类,类B为外部类

内部类的分类:

  • 成员内部类(静态、非静态)

    • 作为外部类的成员:

      • 可以被 static 修饰,静态内部类
      • 可以调用外部类的结构 外部类.this.结构
      • 可以被四种权限修饰符修饰
    • 作为一个类:

      • 类内部可以定义所有类的成员
      • 可以用 final 修饰,表示不能被继承
      • 可以被 abstract 修饰
public class InnerClassTest {

public static void main(String[] args) {
		// 创建非静态成员内部类
		Outside out = new Outside();
		Outside.Inner inner = out.new Inner();
		inner.run();
		System.out.println("--------------");
		inner.display("形参");
		System.out.println("--------------");
		// 创建静态成员内部类
		Outside.StaticInner staticInner = new Outside.StaticInner();
		staticInner.run();
	}
}

class Outside {

	public String name = "Outside Class";

	// 非静态的成员内部类
	class Inner{

		public String name = "Inner Class";

		public Inner(){}

		public void run(){
			// 调用外部类中的非静态方法
			Outside.this.run();
			System.out.println("非静态成员内部类非静态方法");
		}

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

	// 静态的成员内部类
	static class StaticInner {
		// 内部类可以拥有外部类中的基本成员
		// 注意:静态内部类是作为类的成员存在的,所以只能调用外部类中的静态属性或方法
		// 但是静态内部类中可以有非静态的结构
		static {
			System.out.println("静态内部类被加载");
		}

		public int i = 1;

		public StaticInner(){}

		public void run(){
			// 调用外部类的静态方法
			Outside.runStatic();
			/*
			 * 要通过Outside.this调用的是非静态方法,因为静态内部类随着外部类加载而加载,
			 * 所以此时没有对象存在,不能通过对象调用方法 
			 * 报错:No enclosing instance of the type Outside is accessible in scope
			 */
			// Outside.this.run();
			System.out.println("静态成员内部类静态方法");
		}
	}

	public void run(){
		System.out.println("外部类非静态方法");
	}

	public static void runStatic(){
		System.out.println("外部类静态方法");
	}

}
  • 局部内部类(方法内、代码块内、构造器内)

    • 开发中,局部内部类的使用

      public class InnerClassTest {
          
      	// 开发中较为少见
      	public void method(){
      		// 局部内部类
      		class Inner{}
      	}
          
      	// 返回一个实现了Comparable接口的类的对象
      	public Comparable getComparable(){
      		// 创建一个实现了Comparable接口的类
      		// 方式一:通过非匿名的局部内部类实现
      		/* 
      		 * class MyComparable implements Comparable{
      		 * 
      		 *	@Override
      		 *	public int compareTo(Object o) {
      		 *		// TODO Auto-generated method stub
      		 *		return 0;
      		 *	}
      		 *	
      		 * }
      		 * 
      		 * return new MyComparable();
      		 */
          
      		// 方式二:匿名接口实现类
      		return new Comparable() {
          
      			@Override
      			public int compareTo(Object o) {
      				// TODO Auto-generated method stub
      				return 0;
      			}
      		};
          
      	}
      }
      

总结

成员内部类和局部内部类在编译后,都会生成字节码文件,其格式如下:

  • 成员内部类:外部类$内部类名.class

  • 局部内部类:外部类$数字 内部类名.class

    局部内部类的字节码文件中,文件名的数字是为了避免多个局部内部类重名

注意:在使用局部内部类时,若局部内部类要调用其所在方法的局部变量时,该局部变量必须声明为 final 才能调用

  • JDK7及之前都需要显示声明为 final 的

  • JDK8之后,实际编写时可以省略 final 的声明,但局部内部类在使用时默认为 final 的,不能进行修改

public void method(){
    // 局部变量
    int num = 10;
    
    class Inner{
        public void show(){
            // 报错:调用时默认为final的
            // num = 20;
            System.out.println(num);
        }
    }
}

面向对象三大特征

封装性(封装与隐藏)

封装的作用与含义:隐藏对象内部的复杂性,对外公开简单的接口,便于外界调用从而提高系统的可扩展性、可维护性,简而言之,就是**把该隐藏的隐藏起来,该暴露的暴露出来,这就是封装性的设计思想**

“高内聚,低耦合”

  • 高内聚:类的内部数据操作细节自己完成,不允许外部干涉
  • 低耦合:仅对外暴露少量的方法用于使用

封装性的体现

  1. 我们将属性私有化(private),同时,提供公共的(public)方法来获取(getXxx)和设置(setXxx)此属性的值
// 封装与隐藏在setter、getter方法中也有体现

// 此类中value属性不允许赋负数值,我们便可以将value属性隐藏起来,暴露出setter方法给用户调用,其中处理错误的内容也隐藏起来
class Data{
    // private修饰的属性只能在本类中调用
    private int value;
    // public修饰setValue方法将setter方法暴露给用户
    public void setValue(int value){
        if(value < 0)
            value = 0;
        this.value = value;
    }
    
    public int getValue(){
        return this.value;
    }
}
  1. 不对外暴露的私有方法
  2. 单例模式(将构造器私有化)
  3. 若不希望类在包外被调用,可以将类设置为缺省的

封装性总结:Java提供了四种权限修饰用于修饰类及类的内部结构,体现类及类的内部结构在被调用时的可见性大小

继承性

继承性的作用:

  1. 减少代码冗余,提高了代码的复用性
  2. 便于功能的拓展
  3. 为多态性的使用提供了前提

使用继承的格式:class A extends B { }

  • A类:子类、派生类、subclass
  • B类:父类、超类、基类、superclass

继承性的体现

  1. 子类继承父类后,子类就获取了父类中声明的所有的属性、方法

注:父类中声明为private的属性和方法,子类继承父类后,仍认为获取到了父类中的私有结构,只是因为受到封装性的影响,使得子类不能直接调用父类的私有结构

  1. 子类继承父类后,还能声明自己特有的属性和方法,实现功能的拓展

Java中对继承性的规定

  1. 一个类可以被多个子类继承

  2. Java中类的单继承性:一个类只能有一个父类

    Java的类是单继承的,但接口可以多继承

  3. 子类父类是相对的概念

  4. 子类直接继承的父类称为直接父类,间接继承的父类称为间接父类

  5. 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的所有属性与方法

注:若没有显式声明一个类的父类,则此类继承于java.lang.Object,所有的Java类(Object类除外)都直接或间接的继承于java.lang.Object类,这意味着,所有的Java类都具有java.lang.Object类声明的功能

子类对象实例化过程

  1. 子类继承父类以后,就获取了父类中声明的属性和方法;创建子类对象,在堆空间中就会加载所有父类中声明的属性
  2. 当通过子类的构造器创建子类对象时,我们一定会直接或间接的调用父类的构造器,进而父类的父类的构造器,直至调用了 java.lang.Object 类中的空参构造器为止;正因为加载过所有的父类结构,所以内存中会出现父类的结构,子类对象才可以考虑进行调用

注:虽然创建子类对象时,调用了父类的构造器,但自始至终仍只创建了一个对象,即为new的子对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eJK8wfhX-1623843584374)(C:\Users\911\AppData\Roaming\Typora\typora-user-images\image-20210206122018565.png)]

思考

  1. 为什么 super(…) 和 this(…) 调用语句不能同时出现在一个构造器中

    因为 super(…) 和 this(…) 都只能出现在首行

  2. 为什么 super(…) 和 this(…) 调用语句只能出现在构造器的首行

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

多态性

多态性可以理解为一个事物的多种形态

  1. 对象的多态性:父类的引用指向子类对象(子类对象赋给父类引用)

    多态性的使用:父类 标识符 = new 子类();

    注:可以直接应用在抽象类和接口上

  2. 虚拟方法调用:在多态的情况下,当调用子父类同名同参方法时,实际执行的是子类重写父类的方法

    子类中定义了与父类同名同参的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法,这样的方法调用在编译期是无法确定的

  3. 多态的使用:虚拟方法调用

    • Java引用变量有两种类型:编译时类型运行时类型,编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定,简称:编译看左边,运行看右边

    • 若编译时类型和运行时类型不一致,就出现了对象的多态性,有了对象的多态性以后,在编译期,只能调用父类中声明的方法,但在运行时,实际执行的是子类重写父类的方法(即编译看左边,运行看右边)

    • 该对象不能调用子类特有的方法与属性

  4. 多态性的使用前提:

    • 类的继承关系
    • 方法的重写
  5. 对象的多态性只适用于方法,不适用于属性(属性编译运行都看左边)

  6. 多态的作用

    • 不必编写每一子类的功能调用,可以直接把不同子类当父类看,屏蔽子类间的差异,提高代码的通用率/复用率

      如:JDBC(使用Java程序操作数数据库)在使用中无需为每种不同的数据库连接(MySQL、Oracle、DB2、SQL Sever等)写不同的方法,只需用 Object 代替即可

    • 父类引用可以调用不同子类的功能,提高了代码的扩充性和可维护性

    • 抽象类和接口的意义便是通过多态性体现(抽象类和接口不能实例化)

多态性的使用

// 父类
class Animal {
    public void eat(){
        System.out.println("eat something");
    }
    
    public void shout(){
        System.out.println("shout something");
    }
}

// 子类
class Cat extends Animal {
    
    boolean isCatte;
    
    public void eat(){
        System.out.println("Cat eat fish");
    }
    
    public void shout(){
        System.out.println("miao miao miao");
    }
}

class Dog extends Animal {
    
    boolean isDoge;
    
    public void eat(){
        System.out.println("Dog eat meat");
    }
    
    public void shout(){
        System.out.println("wang wang wang");
    }
}

// 测试类
class AnimalTest {
    
    public static void main(String[] args) {
        // TODO
        method(new Cat()); // 相当于Animal animal = new Cat();
        method(new Dog()); // 相当于Animal animal = new Dog();
        
        Animal cat = new Cat();
        cat.eat();
        // 多态不适用于属性,多态情况下不能调用子类特有方法
        // cat.isCatte = false;
    }
    
    // 通过此方法便可体现多态的作用,可以屏蔽子类间的差异,提高代码的通用率
    // 通过使用多态,可以不用写多个方法的重载,如:method(Dog dog); method(Cat cat);之类的
    public static void method(Animal animal) {
        animal.shout();
        animal.eat();
    }
}

向下转型的使用

​ 有了对象的多态性以后,内存中实际上是加载了子类特有的属性和方法的,但是由于变量声明为父类类型,导致编译时只能调用父类中声明的属性和方法

多态情况下,若要调用子类特有的属性和方法,要通过向下转型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T2RH5Kns-1623843584377)(C:\Users\911\AppData\Roaming\Typora\typora-user-images\image-20210206232619581.png)]

​ 向下转型:使用强制类型转换符 子类 标识符 = (子类)父类对象;

​ 使用强转时,可能会出现 ClassCastException 异常,此异常是强转失败异常,此时可以通过使用instanceof关键字先对要进行强转对象是不是被强转成的类的实例进行判断,然后再进行强转

// 编译通过,运行时出错
// 例一:此时内存中加载的是Dog及其父类的属性于方法,并没有Cat的属性与方法,所以运行时报错
Animal animal = new Dog();
Cat cat = (Cat)annimal;
// 例二:此时并未加载Animal子类的属性和方法,只加载了其父类的内容,所以运行时报错
Animal animal = new Animal();
Cat cat = (Cat)Animal();

// 强转正确示例
Object object = new Dog();
Animal animal = (Animal)object;

// 编译不通过
// 此处并不能形成多态,两类之间并没有继承关系
Cat cat = new Dog();

// 若要再多态的情况下使用子类的特有属性或方法,需要向下转型
// 避免出现ClassCastException,先通过instanceof进行判断
if(animal instanceof Dog) {
    Dog dog = (Dog)animal;
    dog.isDoge = true;
}

多态性练习

  • 此处练习说明两点:
    1. 若子类重写了父类方法,就意味着类中定义的方法彻底覆盖了父类中同名同参的方法,系统将不可能把父类中的方法转移到子类中:编译看左边,运行看右边
    2. 对于实例变量则不存在这样的现象,即使子类中定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖父类中定义的实例变量:编译运行都看左边
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);
    }
}

public class FieldMethodTest {
    public static void main(String[] args) {
        Sub sub = new Sub();
        // 此时display调用的是子类中display
        System.out.println(sub.count);	// 20
        sub.display();					// 20
        // 此时添加多态性
        Base base = sub;
        System.out.println(base == sub);	// true
        // 此时由于多态性,base只能调用Base类中的属性与方法以及Sub类中重写父类Base的方法
        // 所以base.count是10,但是display()方法子类已重写,所以调用的是子类Sub中的display()方法
        // 所以此时的this.count相当于sub.count为20
        System.out.println(base.count);		// 10
        base.display();						// 20
    }
}

关键类与工具的使用

Object类

java.lang.Object类是所有Java类的根父类

  • 若在类的声明中未使用extends关键字指明其父类,则默认父类为 java.lang.Object
  • Object类中的方法具有通用性(Object类中并未定义通用的属性)
  • Object类中只声明了一个空参构造器

注:数组也可以看作一个特殊的类,其父类是 Object 也可以调用 Object 中的方法

Object类中的主要构造

方法名 类型 功能描述
public Object() 构造方法 构造器
public boolean equals(Object obj) 普通方法 对象比较
public int hashCode() 普通方法 取得哈希码
public String toString() 普通方法 对象打印时调用
protected Object clone() 普通方法 复制该对象
protected void finalize() 普通方法 JVM中的垃圾收集器回收该对象时调用该方法

Object类的使用

  • equals() 方法的使用(见下 == 和equals 的区别)

  • toString() 方法

    • toString()方法在Object类中的定义其返回值是String类型,返回类名及其引用地址

      // Object类中toString()的定义
      public String toString() {
          return getClass().getName() + "@" + Integer.toHexString(hashCode());
      }
      
    • 当我们输出一个对象的引用时,实际上就是调用当前对象的 toString() 方法

      System.out.println(obj);
      // 相当于
      System.out.println(obj.toString());
      
    • 在String类与其他数据类型进行连接操作时,自动调用 toString() 方法

    • 可以根据需要在自定义类中重写 toString() 方法,返回 “ 实体内容 ”(String、Date、File、包装类等都重写了Object类中的 toString() 方法,使得在调用对象的 toString() 方法时,返回 “ 实体内容 ”)

      // 重写toString()
      @Override
      public String toString() {
          return 实体内容;
      }
      // IDE可以自动生成重写的toString()方法
      
    • 基本数据类型转换为String类型时,调用了对应包装类的 toString() 方法

包装类的使用

包装类(Wrapper),基本数据类型并不能使用面向对象所设计的丰富的功能,所以针对八种基本数据类型定义相应的引用类型 – 包装类(又称封装类),有了类的特征,就可以调用类中的方法,Java才是真正的面向对象

注:包装类的引入是为了使基本数据类型的变量具有类的特征

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

其中 Byte、Short、Integer、Long、Float、Double其父类为Number

基本数据类型、包装类、String类间的相互转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HNSIPPel-1623843584378)(C:\Users\911\Desktop\Java笔记\Java基础\pic\基本数据类型、String类、包装类间的转化.png)]

import org.junit.Test;

public class DemoTest {
	
	// 基本数据类型 --> 包装类(装箱)
	@Test
	public void testPacking(){
		int num = 10;
		// 1. 调用包装构造器
		Integer i = new Integer(num);
		System.out.println(i);
		
		// 2. 通过字符串参数进行装箱
		Float f = new Float("32.1F");
		System.out.println(f.toString());
		
		// 异常 java.lang.NumberFormatException
		// Integer ii = new Integer("asd");
		
		// 3. 自动装箱
		Integer ii = num;
		System.out.println(ii);
	}
	
	// 包装类 --> 基本数据类型(拆箱)
	@Test
	public void testUnpacking(){
		Integer i = new Integer(11);
		// 1. 调用包装类的方法 xxxValue()
		int iValue = i.intValue();
		// 2. 自动拆箱
		int num = i;
	}
	
	// 基本数据类型、包装类 --> String类型
	@Test
	public void test2String(){
		float num = 12.3f;
		Double dNum = new Double(12.5);
		// 1. valueOf()方法
		String str = String.valueOf(num);
		String dStr = String.valueOf(dNum);
		System.out.println(str);
		System.out.println(dStr);
		
		// 2. 连接运算
		str = num + "";
		System.out.println(str);
	}
	
	// String类 --> 基本数据类型、包装类
	@Test
	public void testString2Type(){
		// 调用包装类的parseXxx()
		String str = "123";
		int num = Integer.parseInt(str);
		System.out.println(num + 1);
	}
}

自动装箱、自动拆箱

JDK5.0 中加入新特性:自动装箱自动拆箱

  • 自动装箱

    int num = 10;
    Integer i = num;
    // 相当于 Integer i = new Integer(num);
    System.out.println(i.toString());
    
  • 自动拆箱

    Integer i = new Integer(11);
    int num = i;
    // 相当于 int num = i.intValue();
    

JUnit 单元测试

JUnit是一个Java语言的单元测试框架

使用步骤

  1. 选中当前工程 – 右键选择 build path – add libraries – JUnit – 下一步

  2. 创建Java类,进行单元测试

    用于测试的Java类要求:① 此类时public的 ② 此类提供公共的无参构造器(默认提供)

  3. 此类中声明单元测试方法

    此时的单元测试方法:方法的权限是public,没有返回值,没有形参

  4. 此单元测试方法上需要声明注解@Test,并在单元测试类中导入import org.junit.Test;(一般要测试什么方法便将测试方法命名为testMethod()

  5. 声明好单元测试方法后,就能在方法体内测试相关的代码

  6. 写完需要测试的代码后,左键双击单元测试方法名,右键 run as – JUnit Test

    // JUnit测试格式如下
    import org.junit.Test;
    
    public class DemoTest {
    
    	@Test
    	public void testDemo(){
    		
    	}
    }
    

执行结果说明

  • 若执行结果未出现异常:绿条

  • 若执行结果出现异常:红条

JavaBean

JavaBean是一种Java写成的可重用组件

所谓JavaBean,是指符合如下标准的Java类:

  • 类是公共的
  • 有一个无参的公共构造器
  • 有属性,且有对应的setter、getter方法

​ 用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创建的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他的JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的赋值和粘贴功能,而不用关心任何改变。

关键字

权限修饰符

Java的权限修饰符可以修饰类的成员(属性、方法、构造器、内部类),用于限定该类的对象对该类成员的访问权限,权限修饰符置于类的成员定义前

若是修饰类只能使用:缺省、public

  • public类可以在任意地方被访问
  • 缺省类只能被同一个包内的类访问
关键字 修饰后作用范围
private private的作用域为本类
缺省(default) 缺省的作用域为本类,包内
protected protected作用域为本类,包内,包外子类
public public作用域为本类,包内,包外子类,包外

this关键字

this关键字表示当前对象,可以通过this调用本类的属性、方法和构造器

  • this可以用来修饰、调用:属性、方法、构造器
  • this调用属性和方法:
    • 在类的方法中,可以使用this.属性this.方法的方式,调用当前对象的属性或方法,通常情况下,我们都可以省略this关键字,但是,若方法的形参与类的属性同名时,必须使用this来区分属性与形参
    • 在类的构造器中,可以使用this.属性this.方法的方式,调用当前正在创建的对象的属性或方法,通常情况下,我们都可以省略this关键字,但是,若方法的形参与类的属性同名时,必须使用this来区分属性与形参
  • this调用构造器:
    • 在类的构造器中,可以通过this(形参列表)显式调用本类中指定的其他构造器
    • 构造器中不能通过this(形参列表)的方式调用自己
    • 规定:this(形参列表)必须声明在当前构造器的首行
    • 构造器内部最多只能声明一个this(形参列表),用于调用其他构造器
// 示例
class Test{
    private int num;
    private String ss;
    
    public void setNum(int num){
        // 1.通过this调用属性
        this.num = num;
    }
    
    public int getNum(){
        return num;
        // 此处省略了this关键字,也可加上this
        // return this.num;
    }
    
    public void setSs(String ss){
        this.ss = ss;
    }
    
    public String getSs(){
        return ss;
    }
    
    public void demo(){
        System.out.println("demo");
    }
    
    public void test(){
        // 2.通过this调用方法
        this.demo();
    }
    
    public Test(){
        this.test();
    }
    
    public Test(int num){
        // 3.通过this调用构造器
        // 此处this()便相当于Test(),调用了无参构造器
        this();
        this.num = num;
    }
    
    public Test(int num, String ss){
        // 综上,可以将不同构造器中都需要执行的代码写到无参构造器中(如:上述的调用test方法),在写不同构造方法时,调用无参构造或调用重复参数的构造器即可
        this(num);
        this.ss = ss;
    }
}

package、import关键字

package关键字的使用:

  1. 为了更好的实现项目中类的管理,提供包的概念
  2. 使用package声明类或接口所属的包
  3. 注:package要声明在源文件首行
  4. 包名属于标识符,也要遵循标识符的规则、规范(包名均为小写字母:xxxyyyzzz),要“见名知意”
  5. 各层目录通过 . 进行分隔
  6. 注:同一个包下,不能命名同名的接口、类;不同包下,可以命名同名的接口、类

import关键字的使用:

  1. 在源文件中显式的使用import导入指定包下的类、接口
  2. 声明在包的声明(package)与类的声明之间
  3. 若要导入多个结构,并列写出即可
  4. 可以使用packagename.*导入该包内的所有结构
  5. 若使用的类或接口是java.lang包中定义的,则可以省略import
  6. 若使用的类或接口是本包中定义的,可省略import
  7. 若在源文件中,使用了不同包下同名的类,则至少有一个类要通过以全类名的方式显示packagename.ClassName
  8. 若使用packagename.*的方式表名可以使用packagename下的所有结构,但是不能使用其子包内的结构,若要使用其子包中的结构,则任需显式导包packagename.subpackagename.*
  9. import static ...可以导入指定类或接口中的静态结构(属性或方法)
package com.test
// 导入System中的静态结构,此时可以直接调用out静态属性,而不用写全类名System.out.ptint(),而是可以直接out.print()
import static java.lang.System.*;

super关键字

super关键字表示父类对象,可以通过super调用父类的属性、方法、构造器

  • super可以用来修饰、调用:属性、方法、构造器

  • super调用属性和方法:

    • 可以在子类的方法或构造器中,通过使用 super.属性super.方法 的方式,显式调用父类中声明的属性或方法,但是通常情况下(属性不重名、方法未被重写),习惯省略super

    • 当子类与父类定义了同名属性时,要想在子类中调用父类声明的属性,则必须显式使用 super.属性 的方式表明调用的时父类中声明的属性

      注:子类在继承父类属性时,若定义了同名属性,子类属性并不会覆盖父类属性,而在内存中,两者同时存在

    • 当子类重写了父类方法后,若要在子类中调用父类 的被重写的方法时,则必须显式使用 super.方法 的方式表明调用的是父类中被重写的方法

  • super调用构造器:

    • 可以在子类的构造器中显式的使用 super(形参列表) 的方式,调用父类中声明的指定的构造器

    • super(形参列表) 的使用,必须声明在构造器首行

    • 在类的构造器中,对于 this(形参列表)super(形参列表) ,在一个构造器中,二者只能存一,不能同时出现

    • 在构造器首行,若没有显式的声明 this(形参列表)super(形参列表) ,则默认在首行使用了 super()

      注:若父类中定义了有参的构造器,但并未定义无参构造器,则子类中默认使用 super() 时会报错

    • 在类的多个构造器中,至少有一个构造器使用了 super(形参列表) 调用父类构造器

instanceof关键字

instanceof关键字的使用:

  1. 通过 a instanceof A :判断对象a是否是类A的实例,若是,返回true
  2. 若B类是A类的父类,a instanceof A 返回true,则 a instanceof B 也返回true;反之,若 a instanceof B 为true时,a instanceof A 不一定为true
Animal animal = new Animal();
Animal dog = new Dog();
// 测试instanceof关键字
System.out.println(animal instanceof Animal);   // true
System.out.println(animal instanceof Cat);      // false
System.out.println(animal instanceof Dog);      // false
System.out.println(dog instanceof Animal);      // true
System.out.println(dog instanceof Cat);         // false
System.out.println(dog instanceof Dog);         // true

static关键字

在编写一个类时,其实就是在描述其对象的属性和行为,而并没有产生实质上的对象,只有通过new关键字才会产生对象,此时系统才会分配内存给对象,堆空间中才会有对象出现,其方法才可以被外部调用;但有时候我们希望无论是否产生对象或无论产生了对少个此对象的情况下,某些特定的数据在内存空间中只有一份,此时便需要用到static关键字

static关键字的使用

  • static(静态的),可以用于修饰属性(成员变量)、方法、代码块、内部类

  • 使用static修饰属性:静态变量(类变量)

    1. 属性按照是否使用static修饰又分为:静态属性(类变量)、非静态属性(实例变量)

      • 实例变量:创建多个此类的对象,每个对象都独立的拥有一套类中的非静态属性,当修改其中一个对象中的非静态属性时,不会导致其他对象中同样的属性值修改
      • 静态变量:创建多个此类的对象,多个对象共享同一个静态变量,当修改静态属性时,会导致其他对象调用此属性时,其内容为修改过的值
    2. static修饰属性的说明

      • 静态变量随着类的加载而加载,可以通过类名.静态变量的方式调用(也可通过此类对象调用)
      • 静态变量的加载要早于对象的创建
      • 由于类只会加载一次,则静态变量在内存中也只会存在一份(存在方法区的静态域中)
    3. 静态属性举例:System.out、Math.PI

    4. 静态属性调用说明

      可否通过它进行调用 静态变量 实例变量
      ×
      对象
  • 使用static修饰方法:静态方法

    1. 静态方法随着类的加载而加载,可以通过类名.静态方法来调用

    2. 在静态方法中,只能调用静态的方法或属性;在非静态方法中,既可以调用静态的方法和属性,也可以调用非静态的方法或属性

    3. 静态方法调用说明

      可否通过它进行调用 静态方法 非静态方法
      ×
      对象
  • static 注意点

    • 在静态方法中,不能使用 this、super 关键字
    • 对于静态属性与静态方法的使用,可以通过其生命周期去理解

开发中如何确定一个属性是否要声明为static?

  • 属性时可以被多个对象所共享的,不会随着对象的不同而不同的便可以使用static修饰
  • 类中的常量(final修饰)也常声明为static

开发中如何确定一个属性是否要声明为static?

  • 操作静态属性的方法,通常设置为static的
  • 工具类中的方法习惯上声明为static的,例:Math、Arrays、Collections

final关键字

**final(最终的)**可以用于修饰类、方法、变量

  • 使用 final 修饰类:表明此类不能被继承(此类无子类)

    例如:String类、System类、StringBuffer类

  • 使用 final 修饰方法:表明此方法不能被重写

    例如:Object类中的 getClass() 方法

  • 使用 final 修饰变量:表明此变量值不能再进行修改,此时该 “变量” 就被称为 常量

    变量分为 成员变量(属性)局部变量

    1. final 修饰属性:可以显式赋值、代码块中赋值、构造器中赋值(多个构造器要注意每个构造器中都要确保 final 修饰的变量被赋值)
    2. final 修饰局部变量:需要注意的是,使用 final 修饰形参时,表明此形参是一个常量,当调用此方法为此常量形参赋实参后,便只能在方法体中使用此形参,但不能进行重新赋值

    static final 用于修饰属性:全局常量

abstract关键字(抽象性)

随着继承层次中新子类的定义越来越多,也越来越具体,而父类则更加一般,更通用;类的设计应该保证父类和子类能够共享特征;有时便将父类设计得十分抽象,以至于它没有具体实例,这样的类称为**抽象类**

abstract关键字的使用

  • abstract(抽象的)可以修饰的结构:类、方法

  • 使用 abstract 修饰类:抽象类

    • 此类不能实例化(不能创建此类对象)
    • 抽象类中一定有构造器,便于子类对象实例化时调用
    • 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关操作
  • 使用 abstract 修饰方法:抽象方法

    • 抽象方法只有方法的声明,没有方法体
    • 包含抽象方法的类一定是个抽象类,但抽象类中可以没有抽象方法
    • 子类在重写了父类中所有的抽象方法后,该子类才能实例化;若子类未重写父类中所有的抽象方法,此子类不能实例化,且该子类必须是抽象类(需使用 abstract 修饰)
  • abstract 使用的注意点:

    1. abstract 不能用于修饰 属性、构造器、代码块等结构

    2. abstract 不能用于修饰私有方法静态方法final修饰的方法final修饰的类

      因为 private、static、final修饰的方法都不能被重写,而 abstract修饰的方法必须被重写才能具备功能,而 final 修饰的类不允许子类继承,而 abstract 修饰的类必须通过子类继承才能实例化,因此不能共用

  • 抽象类应用:

    例一:

    ​ IO流中设计到的抽象类:InputStream / OutputStream / Reader / Writer,其内定义了 read()、write() 等抽象方法

    例二:

//抽象类
abstract class Root{
	// 抽象方法
	abstract public void methodOne();
	abstract public void methodTwo();
	// 普通方法
	public void show(){
		System.out.println("抽象类中的非抽象方法");
	}
}

abstract class Mid extends Root{
	@Override
	public void methodOne(){
		System.out.println("Override methodOne in Mid");
	}
	// 若为重写父类中所有抽象方法,则子类也必须用abstract修饰
	// The type Mid must implement the inherited abstract method Root.methodTwo()
}

class Sub extends Root{
	// 重写所有抽象方法的子类可以不用abstract修饰
	@Override
	public void methodOne() {
		// TODO Auto-generated method stub
		System.out.println("Override methodOne");
	}

	@Override
	public void methodTwo() {
		// TODO Auto-generated method stub
		System.out.println("Override methodTwo");
	}
	
}

抽象类的匿名子类对象

虽说抽象类不能创建对象,但重写其内所有抽象方法的子类便能创建对象

可以通过在使用时重写其内所有抽象方法的匿名子类完成对抽象类的使用,而无需新写一个子类

注:匿名子类对象不一定是匿名对象,两者不同(匿名对象是指在创建对象时未显式的赋予对象一个变量名)

public class AbstractTest {

	public static void main(String[] args) {
		// 创建匿名子类对象
		// 此处相当于创建了一个匿名的重写了父类中所有抽象方法的子类的对象
		Root root = new Root(){
			@Override
			public void methodOne(){
				System.out.println("匿名子类重写方法一");
			}

			@Override
			public void methodTwo() {
				System.out.println("匿名子类重写方法二");
			}
		};
		// 所创建的匿名子类对象可以调用所重写的方法以及父类中的普通方法及非私有的属性
		root.methodOne();
		root.methodTwo();
		root.show();
		
		// 创建匿名子类的匿名对象
		new Root(){
			@Override
			public void methodOne(){
				System.out.println("匿名子类的匿名对象重写方法一");
			}

			@Override
			public void methodTwo() {
				System.out.println("匿名子类的匿名对象重写方法二");
			}
		}.methodOne();;
	}
}

//抽象类
abstract class Root{
	// 抽象方法
	abstract public void methodOne();
	abstract public void methodTwo();
	// 普通方法
	public void show(){
		System.out.println("抽象类中的非抽象方法");
	}
}

接口(Interface)

接口(Interface),是一个抽象类型,是抽象方法的集合,接口通常以interface来声明;一个类通过继承接口的方式,从而来继承接口的抽象方法。

  • 由于Java是单继承的,所以在需要从多个类中派生出一个子类时,并不能通过继承父类获取多个类的属性与方法,此时便需要实现多个接口达到多继承的效果。
  • 有时需要从多个类中抽取出一些共同的行为特征(共同的功能、方法),而这几个类之间并没有 “ is-a ” 的关系,仅仅是有相同的行为特征而已,此时便需要提取出共同的特征到接口中
  • 继承是 “ is-a ” (是不是)关系,而接口是 “ has-a ” (能不能)关系
  • 接口的本质是标准、规范

类的继承与接口关系如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nfbOQezF-1623843584379)(C:\Users\911\AppData\Roaming\Typora\typora-user-images\image-20210309162248594.png)]

接口的使用

  • 接口使用 interface 定义

    // 接口定义格式
    [public] interface 接口名称 [extends 其他的接口名] {
            // 声明变量
            // 抽象方法
    }
    
  • Java中,接口与类是并列的两个结构

  • 定义接口中的成员

    • JDK7及之前,接口中只能定义全局常量及抽象方法

      • 全局常量:public static final(在接口中定义时可省略不写,因为在接口中定义的变量默认就是全局常量)
      • 抽象方法:public abstract(在接口中定义时可省略不写,定义的方法默认为抽象方法)
    • JDK8除了定义全局常量和抽象方法外,还可以定义静态方法、默认方法

      • 接口中定义的静态方法,只能通过接口调用,实现类并不能继承接口中定义的静态方法

      • 通过实现类的对象,可以调用接口中的默认方法(default修饰的方法)

      • 若实现类重写了接口中的默认方法,则实现类对象调用的是重写后的默认方法

      • 若子类(或实现类)继承父类和实现的接口中声明了同名同参的默认方法,那么子类再没有重写此方法的情况下,默认调用的是父类中同名同参的方法**(类优先原则)**

      • 若实现类实现了多个接口,而这些接口中定义了同名同参的默认方法,而在实现类没有重写此方法的情况下会报错**(接口冲突)**,在实现类中重写此方法就能解决

        public class InterfaceJDK8Test {
        
        	public static void main(String[] args) {
        		// 
        		Man man = new Man();
        		Man2 man2 = new Man2();
        		
        		man.help();
        		man2.help();
        	}
        }
        
        interface Filing {
        	// 默认方法
        	default void help(){
        		System.out.println("Help that man");
        	}
        }
        
        interface Spoonful {
        	// 
        	default void help(){
        		System.out.println("Help that beautiful gril");
        	}
        }
        
        class Man implements Filing, Spoonful{
        	// 接口冲突,必须重写方法
        	@Override
        	public void help() {
        		// 可以通过此方法调用接口中定义的默认方法
        		Filing.super.help();
        		Spoonful.super.help();
        		System.out.println("who should i help");
        	}
        	
        }
        
        class WiseMan {
        	
        	public void help(){
        		System.out.println("Ladies first....");
        	}
        }
        
        class Man2 extends WiseMan implements Filing, Spoonful{
        	// 类优先原则,此时调用的是父类中的help方法,无需重写也不会报错
        }
        
      • 在子类(或实现类)中调用父类、接口中被重写的方法

        super.method(); // 调用父类中被重写的方法
        InterfaceName.super.method(); // 调用接口中被重写的默认方法
        
  • 接口中不能定义构造器,这意味着接口不能实例化

  • Java开发中,接口通过让类**实现(implements)**的方式使用;若实现类实现了接口中所有的抽象方法,则此实现类便可实例化;若未实现接口中所有的抽象方法,此实现类仍是一个抽象类

  • Java类可以实现多个接口(多实现),弥补了Java单继承的局限性

    例:public class Sub extends Root implements InterfaceA, InterfaceB { }

  • 接口与接口之间可以多继承

    例:Interface CC extends AA, BB {}

  • 接口的具体使用体现多态性

  • 接口实际上可以看作是一种规范

体会面向接口编程

**面向接口编程:**在应用程序中,调用的结构都是JDBC中定义的接口,不会出现某个数据库厂商的API

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tN3jV4lD-1623843584380)(C:\Users\911\AppData\Roaming\Typora\typora-user-images\image-20210311114054812.png)]

创建接口匿名实现类的对象

public class InterfaceTest {

	public static void main(String[] args) {
		// 1. 创建接口非匿名实现类的非匿名对象
		B b = new B();
		b.show();
		
		// 2. 创建接口非匿名实现类的匿名对象
		new B().show();
		
		// 3. 创建接口匿名实现类的非匿名对象
		A a = new A(){

			@Override
			public void show() {
				System.out.println("匿名实现类的非匿名对象 a");
			}
			
		};
		a.show();
		
		// 4. 创建接口匿名实现类的匿名对象
		new A(){

			@Override
			public void show() {
				System.out.println("匿名实现类的匿名对象");
			}
			
		}.show();
	}
}

interface A {
	// 抽象方法
	public void show();
	
	// JDK8之后可以定义静态方法以及默认方法(default关键字修饰)
	public static void run(){
		System.out.println("interface A running...");
	}
}

class B implements A {

	@Override
	public void show() {
		System.out.println("class B implement interface A");
	}
	
}

单例(Singleton)设计模式

设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格以及解决问题的思考方式

  1. 单例设计模式定义

    ​ 所谓类的单例设计模式,就是采取一定方式保证在整个软件系统中某个类只能存在一个对象实例,并且该类只能提供一个取得其对象实例的方法

  2. 如何实现单例设计模式

    ​ 若要在一个虚拟机中只能产生一个此对象,便要将该类构造器的访问权限设置为private,此时就不能用 new 操作符在类外部创建类的对象了,但在类内部仍能产生该类对象;因为在类外部无法得到类的对象,所以只能调用该类的某个静态方法已返回类内部创建的对象,因为静态方法只能访问静态变量,所以指向该类内部产生的对象的变量也必须以static来修饰

    • 单例的饿汉式实现

      // 饿汉式就是类加载时便造好对象
      class Order{
          // 1. 私有化类的构造器
          private Order(){
              
          }
          
          // 2. 内部创建当前类对象
          // 此对象也必须用 static 修饰
          private static Order instance = new Order();
          
          // 3. 提供公共的、静态的方法,返回该类的对象
          public static Order getInstance(){
              return instance;
          }
      }
      
    • 单例的懒汉式实现

      // 懒汉式就是在需要使用到该对象时才创建对象
      class Order{
          // 1. 私有化类的构造器
          private Order(){
              
          }
          
          // 2. 声明当前类对象,未初始化
          // 此对象也必须用 static 修饰
          private static Order instance = null;
          
          // 3. 声明 public、static 的返回当前类对象的方法
          public static Order getInstance(){
              if(instance == null)
                  instance = new Order();
              return instance;
          }
      }
      
  3. 区分饿汉式与懒汉式

    单例设计模式
    饿汉式 饿汉式是线程安全的 对象加载到内存中存放的时间过长
    懒汉式 延迟对象的创建 上述写法的坏处是线程不安全(到多线程章节讲解线程安全的懒汉式实现)
  4. 单例模式的优点

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

  5. 单例模式的应用场景

    • 网站的计数器

    • 应用程序的日志应用

    • 数据库连接池

    • 项目中读取配置文件的类

    • Application(进程)也是单例的典型应用

    • Windows的Task Manager(任务管理器)

    • Windows的Recyle Bin(回收站)

模板方法设计模式(TemplateMethod)

多态的应用:模板方法设计模式

抽象类体现的就是一种模板模式的实际,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式,它解决了如下问题:

  • 当功能内部一部分实现是确定的,一部分实现是不确定的,这是可以把不确定的部分暴露出去,让子类实现
  • 换而言之,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了,但某些部分易变,易变部分可以抽象出来,供不同子类实现,这就是一种模板模式
// 模板方法设计应用举例如下

public class TemplateMethodTest {

	public static void main(String[] args) {
		
		Bank draw = new DrawMoney();
		
		Bank manage = new ManageMoney();
		
		draw.process();
		
		manage.process();
	}
}

abstract class Bank{
	// 具体方法
	public void take(){
		System.out.println("取号排队");
	}
	// 抽象方法,具体业务不同,根据子类不同重写
	public abstract void transact();
	
	public void evaluate(){
		System.out.println("服务评分");
	}
	
	// 模板方法,将基本操作组合到一起,子类一般不能重写
	public final void process(){
		
		take();
		// 根据不同子类执行不同方法
		transact();
		
		evaluate();
	}
}

class DrawMoney extends Bank{
	
	@Override
	public void transact() {
		// TODO Auto-generated method stub
		System.out.println("取钱");
	}

}

class ManageMoney extends Bank{
	
	@Override
	public void transact() {
		// TODO Auto-generated method stub
		System.out.println("理财");
	}
	
}

模板方法设计模式是编程中经常用得到的模式,各个框架、类库中都有它的影子,常见的有:

  • 数据库访问的封装
  • JUnit单元测试
  • JavaWeb中Servlet的 doGet / doPost 方法调用
  • Hibernate中模板程序
  • Spring中JDBC Template、Hibernate Template等

代理模式(Proxy)

代理模式就是为其他对象提供一种代理,用以控制这个对象的访问

代理模式的主要角色:

  1. 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
  2. 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
  3. 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。

代理模式分为 静态代理动态代理

  • 静态代理(静态定义代理类)
  • 动态代理(动态生成代理类),其中涉及反射等知识

代理模式的应用场景:

  • 安全代理:屏蔽对真实角色的直接访问
  • 远程代理:通过代理类处理远程方法调用(RMI)
  • 延迟加载:先加载轻量级的代理对象,真正需要再加载真实对象
public class ProxyTest {
    
    public static void main(String[] args) {
        Proxy proxy = new Proxy();
        proxy.Request();
    }
}

// 抽象主题
interface Subject {
    
    void Request();
}

// 真实主题
class RealSubject implements Subject {
    
    public void Request() {
        System.out.println("访问真实主题方法...");
    }
}

// 代理类
class Proxy implements Subject {
    
    private RealSubject realSubject;
    
    public void Request() {
        if (realSubject == null) {
            realSubject = new RealSubject();
        }
        preRequest();
        realSubject.Request();
        postRequest();
    }
    
    public void preRequest() {
        System.out.println("访问真实主题之前的预处理。");
    }
    
    public void postRequest() {
        System.out.println("访问真实主题之后的后续处理。");
    }
}

工厂模式(Factory Pattern)

**工厂模式:**实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的

工厂模式分类:

  1. 无工厂模式
  2. 简单工厂模式
  3. 工厂方法模式
  4. 抽象工厂模式
// 以工厂方法模式为例体现工厂模式
public class FactoryMethodTest {

	public static void main(String[] args) {
		// 通过工厂对象获取产品
		Factory factoryBMW = new BMWFactory();
		Factory factoryBenz = new BenzFactory();
		
		Car carBMW = factoryBMW.getCar();
		Car carBenz = factoryBenz.getCar();
		
		carBMW.run();
		carBenz.run();
	}
}

interface Car {
	void run();
}

// 实现类
class BMW implements Car{

	@Override
	public void run() {
		System.out.println("BMW running...");
	}
	
}

class Benz implements Car{

	@Override
	public void run() {
		System.out.println("Benz running...");
	}
	
}

// 工厂接口
interface Factory {
	Car getCar();
}

// 工厂类
class BMWFactory implements Factory{

	@Override
	public Car getCar() {
		return new BMW();
	}
	
}

class BenzFactory implements Factory{

	@Override
	public Car getCar() {
		return new Benz();
	}
	
}

理解main方法

main方法是程序的入口,但main方法其实也只是一个普通的静态方法,且main方法也可以作为我们与控制台交互的方式

main()方法作为程序入口与普通方法

public calss MainTest{
    // 程序的入口 
    public static void main(String[] args){
        // 调用Main中作为普通静态方法的main方法
        Main.main(new String[]{"First", "Second", "Third"});
    }
}

class Main{
    // main作为普通的静态方法
    public static void main(String[] args){
        for(int i = 0; i < args.length; i++)
            System.out.println("args_" + i + ":" + args[i]);
    }
}

与main()方法进行交互

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

在程序编译完成后,通过下述方式执行即可进行交互:

java MainDemo "参数1" "参数2" ......

或在Eclipse中右键选中 Run as – Run Configurations – Arguments 在 Program arguments 中输入参数即可

MVC设计模式

MVC是常用的设计模式之一,它将整个程序分为三个层次:数据模型层(Model)视图模型层(View)控制器层(Controller),这种将程序输入输出、数据处理,以及数据的展示分离开来的设计模式使程序结构变得灵活且清晰,同时也描述了程序各个对象间的通信方式,降低了程序的耦合性

模型层(model)主要处理数据

  • 数据对象封装 model.bean/domain
  • 数据亏操作类 model.dao
  • 数据库 model.db

视图层(view)显示数据

  • 相关工具类 view.utils
  • 自定义 view.ui

控制层(controller)处理业务逻辑

  • 应用界面相关 controller.activity
  • 存放fragment controller.fragment
  • 显示列表的适配器 controller.adapter
  • 服务相关 controller.service
  • 抽取的基类 controller.base

面试中需要注意的题目

区分重载与重写

  1. 从二者的定义细节(见上 类的成员:方法)

  2. 从编译和运行的角度看:

    重载,是指允许存在多个同名方法,而这些方法的参数不同。编译器根据方法不同的形参列表,对同名方法的名称做修饰,对于编译器而言,这些同名方法就成了不同的方法。它们的调用地址在编译期就绑定了。Java的重载是可以包括父类和子类的,即子类可以重载父类的同名不同参的方法。所以,对于重载而言,在方法调用前,编译器就已经确定了所要调用的方法,这称为早绑定静态绑定

    而对于多态,只有等到方法调用的那一刻,解释运行器才会确定所要调用的具体方法,这称为晚绑定动态绑定

    注:如果不是动态绑定(晚绑定)就不是多态

多态是编译时行为还是运行时行为?如何证明?

​ 多态是运行时行为,如下代码证明:

import java.util.Random;

// 父类
class Animal {
    public void eat(){
        System.out.println("animal eat something");
    }
    
}

// 子类
class Cat extends Animal {
    
    boolean isCatte;
    
    public void eat(){
        System.out.println("Cat eat fish");
    }
}

class Dog extends Animal {
    
    boolean isDoge;
    
    public void eat(){
        System.out.println("Dog eat meat");
    }
}

class Sheep extends Animal {
    
    boolean isDoge;
    
    public void eat(){
        System.out.println("Sheep eat grass");
    }
}

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

        System.out.println(key);

        Animal animal = getInstance(key);

        animal.eat();
    }


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

final、finally、finalize的区别

  • final 修饰符(关键字)。被final修饰的类,就意味着不能再派生出新的子类,不能作为父类而被子类继承。因此一个类不能既被 abstract 声明,又被 final 声明。将变量或方法声明为 final,可以保证他们在使用的过程中不被修改。被声明为final的变量必须在声明时给出变量的初始值,而在以后的引用中只能读取。被 final 声明的方法也同样只能使用,即不能方法重写
  • finally 是在异常处理时提供 finally 块来执行任何清除操作。不管有没有异常被抛出、捕获,finally 块都会被执行。try 块中的内容是在无异常时执行到结束。catch 块中的内容,是在 try 块内容发生 catch 所声明的异常时,跳转到 catch 块中执行。finally 块则是无论异常是否发生,都会执行 finally 块的内容,所以在代码逻辑中有需要无论发生什么都必须执行的代码,就可以放在 finally 块中
  • finalize 是方法名。java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在 object 类中定义的,因此所有的类都继承了它。子类覆盖 finalize() 方法以整理系统资源或者被执行其他清理工作。finalize() 方法是在垃圾收集器删除对象之前对这个对象调用的

== 和 equals 的区别

  1. == 既可以比较基本数据类型也可以比较引用类型,对于基本类型就是比较值,对于引用类型就是比较内存地址
  2. equals() 方法是属于 java.lang.Object 类中的方法,若该方法未被重写过,默认也是 == ;但我们可以看到String等类中的 equals() 方法是被重写过的,就不能以 == 来判断其结果
  3. 具体要看自定义类中有没有重写Object的 equals() 方法来判断
  4. 通常情况下,重写 equals() 方法会比较类中的相应属性是否都相等
  • == 的使用:

    • 可以使用在基本数据类型和引用数据类型中

    • 若比较的是基本数据类型变量,则比较两个变量保存的数据是否相等(比较的两个变量的数据类型不一定要相同);若比较的是引用数据类型变量,则比较两个对象地址值是否相同

    • 补充:== 符号使用时,必须保证符号左右两边的变量类型一致(不一定要相同)

  • equals() 方法的使用:

    • equals() 在 Object类中有定义,它是一个方法,而非运算符

    • equals() 方法只适用于引用数据类型

    • Object类中 equals() 方法的定义如下:

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

      这说明Object类中定义的 euqals() 方法和 == 的作用相同

    • String、Date、File、包装类(Integer、Double…)等都重写了Object中的 equals() 方法,重写后比较的不是两个引用地址是否相同,而是比较两个对象的“实体内容”是否相同

    • 通常情况下,我们自定义的类重写 equals() 方法一般也是比较两个对象的“实体内容”是否相同(在一些 IDE 中可以自动生成equals()方法)

      一般重写equals()方法结构如下:

      @Override
      public boolean equals(Object obj) {
        if(this == obj) {
              return true;
          }
          
          // 比较此子类的实体内容,并返回相应的布尔值
              .
              .
              .
      }
      
      • 在重写 equals() 方法时,若是比较的实体内容为基本数据类型时可以使用==比较,若是比较的是引用数据类型要使用equals()方法进行比较

包装类常见面试题

  1. 如下连段代码输出结果相同吗?分别是什么?

    // 代码段1
    Object o1 = true ? new Integer(1) : new Double(2.0);
    System.out.println(o1);
    
    // 代码段2
    Object o2;
    if(true)
        o2 = new Integer(1);
    else
        o2 = new Double(2.0);
    System.out.println(o2);
    

    答:输出结果不同,o1为1.0,o2为1;因为三元运算符在编译时要求结果的类型能统一为同一类型(例:若结果为String : int 就会报错),在统一类型时Integer自动提升为Double了,所以其结果为1.0

  2. 判断下列代码输出结果

    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
    

    答:用==比较两对象比较的是对象的地址,但第二段代码输出结果为true,其原因如下:

    Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],其中保存了-128 ~ 127范围的整数;若是使用自动装箱时,给Integer赋值的范围在-128 ~ 127范围内时,可以直接使用数组中的元素,不需要再去new一个新的对象(其目的是为了提高效率)

    所以Integer x = 128;在执行过程中相当于Integer x = new Integer(128);,但Integer m = 1;在IntegerCache定义的范围内,所以不需要new创建新的对象,所以在-128 ~ 127 内数值相同的对象其地址也相同,是同一个对象

抽象类与接口有哪些异同

相同点:都不能实例化,都可以包含抽象方法,都可以被继承

不同点:

  • 抽象类:有构造器,单继承,其内可以定义所有类的成员
  • 接口:不能声明构造器,接口与接口间可以多继承,一个类可以实现多个接口;JDK7及之前,只能定义全局常量与抽象方法;JDK8新特性,可以在接口中定义静态方法和默认方法

学习过程中的思考

子类能否继承父类中的静态变量以及静态方法

import org.junit.Test;

public class DemoTest {
	// 测试子类能否继承父类的静态属性及静态方法
	@Test
	public void testStaticExtends(){
		// 
		CC cc = new CC();
		System.out.println(cc.staticStr);
		System.out.println(cc.nonStaticStr);
		cc.staticMethod();
		cc.nonStaticMethod();
		System.out.println("----------------------");
		
		AA ac = new CC();
		System.out.println(ac.staticStr);
		System.out.println(ac.nonStaticStr);
		ac.staticMethod();
		ac.nonStaticMethod();
		System.out.println("----------------------");
		
		BB bb = new BB();
		System.out.println(bb.staticStr);
		System.out.println(bb.nonStaticStr);
		bb.staticMethod();
		bb.nonStaticMethod();
		System.out.println("----------------------");
		
		AA ab = new BB();
		System.out.println(ab.staticStr);
		System.out.println(ab.nonStaticStr);
		ab.staticMethod();
		ab.nonStaticMethod();
		System.out.println("----------------------");
	}
}

class AA {
	
	public static String staticStr = "staticStrAA";
	
	public String nonStaticStr = "nonStaticStrAA";
	
	public static void staticMethod(){
		System.out.println("class AA static method");
	}
	
	public void nonStaticMethod(){
		System.out.println("class AA non-static method");
	}
}

class BB extends AA {
	
	public static String staticStr = "staticStrBB";
	
	public String nonStaticStr = "nonStaticStrBB";
	
	public static void staticMethod(){
		System.out.println("class BB static method");
	}
	
	@Override
	public void nonStaticMethod(){
		System.out.println("class BB non-static method");
	}
}

class CC extends AA {}

上述代码运行结果如下:

staticStrAA
nonStaticStrAA
class AA static method
class AA non-static method


staticStrAA
nonStaticStrAA
class AA static method
class AA non-static method


staticStrBB
nonStaticStrBB
class BB static method
class BB non-static method


staticStrAA
nonStaticStrAA
class AA static method

class BB non-static method


​ 首先,从前两段的运行结果可以看出,CC 类继承了AA 类的所有属性以及方法,无论静态非静态都继承了;从后两端可以看出,BB 类继承了AA 类的所有方法以及属性,但对同名同参的静态方法进行覆盖后,运行时依旧看左边,并不构成多态,而且可以看出无论是否为静态属性,运行都看左边,即多态性只适用于非静态方法,不适用于属性以及静态方法

​ 所以说,子类可以继承父类中的静态属性以及静态方法,但它们并不适用于多态

你可能感兴趣的:(Java基础,java)