JavaSE中级篇——面向对象思想 封装,继承,多态

462 阅读34分钟
1、面向对象与面向过程

面向过程:大象装冰箱,只能装大象,代码冗余,复用性差;

​ 面向对象:

​ *1)*可以装任何符合要求的动物,复用性强,比如开门,关门操作都是一样的。引入开门、关门的代码,然后我们只需要写装狮子等其他动物的代码。

​ *2)*解决问题的时候按照现实生活中的规律来思考问题,考虑问题的过程中,有几个实体参与进来。

​ *3)*类:用来描述一类具有相同特征(属性)和行为(方法)的事物。

​ *4)*对象:具体的事物

​ *5)*计算机中利用面向对象思想来做事

​ 先定义一个类(有属性),在描述的类中擦黄建一个具体的个体出来,个体来做事(方法/属性)。

public class Demo {
    public static void main (String[] args) {
        //修饰符 数据类型 属性名字 值
        Person per = new Person();
        per.name = "zhumin";
        per.gender = "female";
        per.age = 18;
        System.out.println("My name is "+per.name+",I am "+per.age+" years old,I am a "+per.gender);
    }
    	Person pe2 = per;
    	per2.name = "bessi";
    	per2.age = 22;
    	per2.gender = "female or male?";
}

public class Person {
    String name;
    String gender;
    int age;
}

​ 以上代码的流程图:

​ JVM根据项目中的类在方法区创建一个类模板,JVM会自己将主方法塞进执行栈,new的过程在堆内存中创建对象,对象的属性根据模板来画(刚开始默认值name:null,age:0,gender:null),通过方法完成我们想要的事情。

2、方法介绍
public class Person {
    public String name;
    public int age;
    public String gender;
    //无参数无返回值的方法
    public void sleep () {
        System.out.println("睡觉~~~");
    }
    //无参数,有返回值
    public String sayName () {
        return this.name;
    }
    //有参数,无返回值
    public void eat (String food,int count) {
        System.out.println("吃了"+count+"碗"+food);
    }
    //有参数有返回值
    public int buyDrinks (String drinkName,int count) {
        int price = 0;
        if(drinkName.equals("GreanTea")){
            price = 5;
        }else if(drinkName.equals("RedTea")){
            price = 3;
        }else{
            System.out.println("drink type is not exist...");
        }
        return count*price;
    }
}

​ 设计一个方法:画星星

//画星星方法
public void drawStar (int line) {
    /*输入4,打出以下图形:
       *
      **
     ***
    ****
    */
    for(int i = 0; i < line; i++){
        for(int j = 0; j < line - 1 - i;j++){
            System.out.print(" ");
        }
        for(int k = 0; k <= i; k++){
            System.out.print("*");
        }
        System.out.println();
    }
}

//通用,int line控制行数,boolean dir控制方向
public void drawStar (int line,boolean dir) {
    //dir为true,左边开始画星星
    if(dir){
        //控制行数
        for(int i = 0; i < line; i++){
            //控制星星
            for(int j = 0; j <= i; j++){
                System.out.print("*");
            }
            System.out.println();
        }
    }else{
        for(int k = 0; k < line; k++){
            //控制空格
            for(int l = 0; l < line - 1 - i; l++){
                System.out.print(" ");
            }
            //控制星星
            for(int m = 0; m <= k; m++){
                System.out.print("*");
            }
            System.out.println();
        }
    }
}

//优化
public void drawStar (int line,boolean dir) {
    for(int i = 0; i < line; i++){
        if(!dir){
            for(int j = 0; j < line - 1 - i; j++){
                System.out.print(" ");
            }
        }
        for(int j = 0; j <= i; j++){
            System.out.print("*");
        }
        System.out.println();
    }
}

​ 小任务:

1.反向三角形(既可左,又可以偏右)

2、用来交换两个数组元素 a{1,2,3,4} b{5,6,7,8}

	//交换数组元素a{1,2,3,4} b{5,6,7,8}
    public void exchangeElem (int[] arr1, int[] arr2){
        for(int i = 0; i < arr1.length; i++){
            arr1[i] = arr1[i]^arr2[i];
            arr2[i] = arr1[i]^arr2[i];
            arr1[i] = arr1[i]^arr2[i];
        }
        for(int val : arr1){
            System.out.println("arr1:"+val);
        }
        for(int val : arr2){
            System.out.println("arr2:"+val);
        }
    }
//缺点:1.循环方式交换数组元素,性能比较慢
//2.交换的时候要保证两个数组元素长度一致
public void changeElem (int[] arr1,int[] arr2) {
    int[] temp = arr1;
    arr1 = arr2;
    arr2 = temp;
}

3、用来交换一个数组(头尾互换)

public class Demo {
    //数组头尾元素互换
    public void reverseArray (int[] x) {
        for(int i = 0; i < x.length/2; i++){
            x[i] = x[i]^x[x.length - 1 -i];
            x[x.length - 1 -i] = x[i]^x[x.length - 1 -i];
            x[i] = x[i]^x[x.length - 1 -i];
        }
    }
}

4、用来寻找给定元素是否在数组内存在

public boolean findElem (int[] arr,int elem) {
    boolean b = false;
    for(int i = 0; i < arr.length; i++) {
        if(arr[i] == elem){
            b = true;
            break;
        }
    }
    return b;
}

5、合并两个数组

6、用来将一个数组按照最大值位置拆分

7、用来去掉数组中得0元素

 //删除数组中某特定元素,a{1,2,0,1,2,3,4,0,0}
    public int[] deleteItem (int[] arr,int elem) {
        int count = 0;
        for(int i = 0; i < arr.length; i++) {
            if(arr[i] != elem) {
                count++;
            }
        }
        int[] newArr = new int[count];
        int index = 0;
        for(int i = 0; i < arr.length; i++) {
            if(arr[i] != elem){
                newArr[index++] = arr[i];
        }
        }
        return newArr;
    }

8、用来存储给定范围内的素数(begin, end)

public class Demo {
    //找到2-100之间不包括1,2,3的素数,并返回素数数组
    public int[] findPrimeNum (int begin,int end) {
        if(begin < 0 || end < 0){
            System.out.println("输入不合法.");
        }
        if(begin > end){
            System.out.println("初始值不能比结束值大.");
        }
        int[] newArr = new int[(end-begin)/2];
        int index = 0;
        if(begin == 1) begin = 2;
        for(int i = begin;i <= end;i++){
            boolean b = true;
            for(int j = 2; j <= Math.sqrt(i);j++){
                if(i%j == 0){
                    b = false;
                    break;
                }
            }
            if(b){
                //newArr[index++] = i
                //输入1,20时报错,索引越界
                newArr[index] = i;
                index++;
            }
        }
        int[] primeNumArr = new int[index];
        for(int i = 0; i < index; i++){
            primeNumArr[i] = newArr[i];
        }
        return primeNumArr;
    }
}

9、用来给数组元素排序,既能升序,又能降序

//根据参数进行是否升序还是降序排列
    public int[] sortArr (int[] arr,boolean dir) {
        //dir为true时,升序排列
        for(int i = 0; i < arr.length - 1; i++){
            for(int j = arr.length - 1; j >= i + 1;j--){
                if((dir && arr[j] < arr[j-1]) || (!dir && arr[j] > arr[j-1])){
                   arr[j] = arr[j]^arr[j-1];
                   arr[j-1] = arr[j]^arr[j-1];
                   arr[j] = arr[j]^arr[j-1];
                }
            }
        }
    }

10、实现用户登录认证

11、向数组中指定位置插入元素

public class Demo {
    public int[] insertElemToIndex (int[] arr,int elem,int index){
        int[] newArr = new int[arr.length+1];
        for(int i = 0;i < arr.length; i++){
            newArr[i] = arr[i];
        }
        //将指定索引后的元素位置后移
        for(int j = arr.length; j > index; j--){
            newArr[j] = newArr[j-1];
        }
       newArr[index] = elem;
       return newArr;
    }
} 
3、类的阶段总结

类:抽象笼统的概念,用来描述一组具有相同的特征和行为的事物

属性:静态描述类的特征

属性的定义:权限修饰符 【特征修饰符】 属性类型 属性名字 【=值】

方法:动态描述类的行为

方法的定义:权限修饰符 【特征修饰符】 返回值类型 方法名字 (参数名字) 【抛出异常】【{方法执行体}】

方法的主要结构:方法的参数列表(行为发生的条件),方法的返回值类型(行为发生后得到的结果)

类的使用:类描述好后,不能直接用来执行,需要通过new的方式创建一个实例,然后才能调用类的方法(执行一次特定行为)和属性(存值和取值)

4、命名规范:

方法、属性、变量:小驼峰式命名规则[^(changeName())]

类名:大驼峰式命名规则[^(class RandomDice)]

构造方法:构造方法与类名一致,以大写字母开头的方法

静态常量:全部字母大写 ,通过_做具体说明,比如BOOKSTORE_ADMIN

包名:全部字母小写,Java关键字也是小写,注意不冲突

注意:方法设计很重要

5、方法执行的内存变化
publc class Test {
    public int changeNum (int x) {
        x = 100;
        return x;
    }
    public static void main (String[] args) {
    //隐藏过程:加载类模板(此处为Test模板)的过程
    Test t = new Test();
    int a = 1;
    a = t.changeNum(a); //10  
    //堆内存中开辟空间,方法存在对象的内部空间中
    //方法执行时会压到栈内存中执行,并根据传参和内部定义的变量开辟临时空间
    //方法执行完毕后临时空间销毁
    }
}

方法执行原理图:

形参和实参:

public class Test {
public void changeArray (int[] x) {
	x[0] = 10;
	}
	public static void main (String[] args) {
//加载Test的类模板
	Test t = new Test();
//栈中开辟变量t,保存的是指向对象的地址
int[] arr = {1,2,3,4};
//方法放到栈中执行时,开辟临时内存空间
//将栈中的arr地址复制一份到x,指向同一个数组
//更改堆内存中第一个元素的值为10
t.changeArray(arr);
System.out.println("arr[0]:"+arr[0]);//arr[0]:10
	}
}

形参:方法执行时的临时变量空间

实参:方法调用时会将实参的内容传递给形参(常量传递的是值,引用数据类型传递的是值)

6、方法重载(overload)

概念:一个类中的一组方法,相同的方法名字,不同的参数列表,这样一组方法否成了方法的重载

作用:为了让使用者便于调用 只需要记录一个名字,执行不同的操作

设计方法重载:

​ 1)调用方法的时候,首先通过方法名字定位方法

​ 2)如果方法名字一致,可以通过参数类型定位方法

​ 3)如果没有与传递参数类型一致的方法,可以找一个参数类型可以转化的方法执行(可进行自动类型转化)

​ 4)JDK1.5版本以后 出现新写法test(int...x)]

​ 动态参数列表的方法不能与相同意义的数组类型方法构成方法重载 本质上时一样的比如:test(int...x) test(int[] arr)

​ 动态参数列表的方法是可以不传参的,相当于0个

​ 前面还可以规定必须传的类型test(String a,int...x),但是后面时不能再继续传参的

​ 动态参数列表在方法的参数中只能存在一份,且必须放置在方法参数的末尾

//思考题
public class Demo {
    public void test (boolean b) {
        System.out.println("你的输入为:"+b);
    }
    public static void main (String[] args) {
        Demo d = new Demo();
        d.test();//不传参不行,参数类型不符合也不行
        System.out.println();
        d.test();
        //这个函数不传参也可以,传参,参数不同类型也可以
        //其实底层System.out.println()有十多个方法
    }
    //方法名一致 可以直接通过参数列表定位方法 
    //方法重载
    //删掉char的方法后会默认找参数类型可以进行转化的方法执行
    public void test (int b) {
        System.out.println("你的输入为:"+b);
    }
    public void test (String b) {
        System.out.println("你的输入为:"+b);
    }
    public void test (char b) {
        System.out.println("你的输入为:"+b);
    }
    public void test (int...x) {
        //x底层就是一个数组,x动态参数列表
        for(int val : x){
            System.out.println("动态参数列表:"+val);
        }
    }
    public void test () {
        System.out.println("无参数");
    }
}
7、类的成员:构造方法

类的内部成员:属性,方法,构造方法,程序块

构造方法:

​ 1)作用:构建当前类的对象

​ 2)写法:权限修饰符 返回值类型 方法名字 (参数列表)[抛出异常] {方法执行体 返回对象}

​ 3)构造方法名:必须与类名一致

import java.util.Scanner;
public class Test {
public static void  main (String[] args) {
Functions f = new Functions();
Test t = new Test("Jackson",18,"male");
System.out.println("my name is "+t.name+",I am "+t.age+" years old");
}
public String name = "goudai";
public int age = 20;
public String gender = "male";
//提供重载
public Test () {}
public Test (String name,int age,String gender) {
/*
结构上没有返回值类型,但是实际中会返回一个对象
特点:
1.每一个类都有构造方法,类中没有定义,系统会调用默认构造方法
2.如果有构造方法定义,会对默认构造方法进行覆盖
3.存在构造方法重载
作用:
在创建对象的同时想要做一些其他任务,这个时候有我们可以自己定义构造方法,
也可以定义带参数的
this指代创造出的对象
this关键字的使用:
1.关键字,代替一个对象,this可以调用属性,方法
2.调用属性和方法可以在类中任何成员位置,没有顺序
3.this可以调用其他构造方法,必须在第一行
4.构造方法不能来回调用,编译就无法通过
5.方法之间可以通过this相互调用,但是容易报错
 */
this();
this.name = name;
this.age = age;
this.gender = gender;
System.out.println(gender);
}
}
8、类的成员:程序块

​ 作用:和普通方法一样,完成特定行为的

​ 写法:程序块是一个没有修饰符,参数,返回值,名字的特殊方法

​ 用法:每一次我们调用构造方法的时候,系统会自动帮我们调用一次程序块,让它执行一遍,多个程序块会按照顺序执行

​ 用途:想要创建对象之前执行一些操作,可以使用程序块

public class Demo {
    {
        System.out.println("程序块");
    }
    public Demo () {}
}
9、类的总结与设计

​ *类的四个成员:*属性、方法、构造方法、程序块

类的加载及对象的创建

作业:

​ (1)模拟一个计算器

​ 知识补充:Scanner有阻塞的效果,nextInt() next() nextDouble()以回车符作为截止,不会读取回车符,nextLine会读取回车符

​ String转换为int类型:包装类int-->Integer char-->Character byte-->Byte float-->FloatInteget.parseInt("123")-->int 123123+""-->"123"

nextLine()next()next()看到回车或空格都认为结束,nextLine()只认为回车结束

import java.util.Scanner;
public class Demo () {
    public static void main (String[] args){
    //Scanner用法
    Scanner input = new Scanner(System.in);
    System.out.println("请输入密码:");
    int password = input.nextInt();//不读取回车
    //此处 解决办法1:input.nextLine()空执行一次,把回车符读掉
    System.out.println("请输入用户名:");
    //解决办法2:input.next()读取字符串,不读取回车符(留了两个回车在消息队列)
    //解决办法3:都用nextLine()读取,改变为String password
    String userName = input.nextLine();//读取回车
    System.out.println("结束.");
    /*
    输出结果:
    请输入密码
    123
    请输入用户名:
    结束.
    */
}
}

​ 原理图:

import java.util.Scanner;
public class Calculator {
    public float sum;
    public void calculate () {
            Scanner input = new Scanner(System.in);
            System.out.println("请输入第一个数:");
            this.sum = Float.parseFloat(input.nextLine());
            float result;
            while(true) {
                System.out.println("请输入符号:");
                String symbol = input.nextLine();
                System.out.println("请输入第二个数:");
                float two = Float.parseFloat(input.nextLine());
                switch (symbol){
                    case "+":
                        this.sum = this.add(this.sum,two);
                        break;
                    case "-":
                        this.sum = this.subtract(this.sum,two);
                        break;
                    case "*":
                        this.sum = this.multiply(this.sum,two);
                        break;
                    case "/":
                        this.sum = this.devide(this.sum,two);
                        break;
                    default:
                        break;
                }
                System.out.println("结果为:"+ this.sum);
            }
    }

        public float add (float a,float b) {
            return a + b;
        }
        public float subtract (float a,float b) {
            return a - b;
        }
        public float multiply (float a,float b) {
            return a * b;
        }
        public float devide (float a,float b) {
            return a / b;
        }
    }

​ (2)设计一个类,替代一个数组可以做的事情(增删改查)

面向对象,每一个方法只干一个具体的事情(低耦合)

public class ArrayBox {
    private int[] intArr;
    private int count = 0;
    private static final int DEFAUL_SIZE = 10;
    private int initialSize;

    //方法重载
    public ArrayBox () {
        this.intArr = new int[DEFAUL_SIZE];
    }

    public ArrayBox (int size) {
        this.initialSize = size;
        this.intArr = new int[size];
    }
    //检查空间是否足够
    private boolean checkSpace (int[] arr) {
        boolean flag = false;
        if(this.count + arr.length <= this.initialSize){
            flag = true;
        }
        return flag;
    }

    //此方法用来返回新空间的数组
    private int[] grow (int[] arr) {
        int[] newArr = new int[this.count + arr.length] ;
        return newArr;
    }
    
    /****
    源码:
    //minCapacity为此次需要得长度
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //1.5倍扩容
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果新长度扩容后还是比需要少,使用minCpacity
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //超过数组最大允许长度
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //按照新长度进行扩容,老数组为elementData
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    ***/

    //此方法用来添加元素
    public void add (int...x) {
        //检查初始化空间是否足够
        if(this.checkSpace(x)){
            for(int i = 0; i < x.length; i++) {
                intArr[this.count++] = x[i];
            }
        }else{
            //返回一个扩容后的新数组
            int[] newArr = this.grow(x);
            //添加元素
            for(int j = 0; j < newArr.length; j++){
                if(j < this.count) {
                    newArr[j] = this.intArr[j];
                }else{
                    newArr[j] = x[j - this.count];
                }
            }
            //保证Count始终为数组长度
            this.intArr = newArr;
            this.count = newArr.length;
            newArr = null;
        }
        System.out.println("添加成功~~~");
    }

    //删除指定索引元素
    public void deleteItemByIndex (int index) {
        this.checkIndex(index);
        int[] newArr = new int[this.count-1];
        //将指定索引元素移到数组的最后this.count - index - 1 + 1 - 1
        for(int i = index; i < this.count - 1; i++){
            int temp = this.intArr[i];
            this.intArr[i] = this.intArr[i+1];
            this.intArr[i+1] = temp;
        }
        for(int j = 0; j < this.count - 1; j++){
            newArr[j] = this.intArr[j];
        }
        this.intArr = null;
        this.intArr = newArr;
        this.count -= 1;
        System.out.println("删除成功~~~");
    }

    //查看数组
    public void checkAllElemInBox () {
        System.out.print("MyBox:");
        for(int val : this.intArr) {
            System.out.print(val + " ");
        }
        System.out.println();
    }

    //查找指定索引的元素
    public int get (int index) {
        this.checkIndex(index);
        System.out.println("获取的元素为:"+this.intArr[index]);
        return this.intArr[index];
    }

    //检测索引是否越界
    private void checkIndex (int index) {
        if(index < 0 || index >= this.count){
            throw new BoxIndexOutOfBounds("Current BoxSize:"+this.count+",Enter Index:"+index);
        }
    }

    public int size () {
        return this.count;
    }
}

​ Exception异常(人为规定一种不正常现象,ArrayIndexOutOfBounds),Error(计算机根本处理不了,StackOverFlowError栈内存溢出,方法相互调用容易产生)

10、类的关系

类和类之间的关系:

A is-a B泛化(继承 实现)

A has-a B包含(组合 聚合 关联)

A use-a B依赖(依赖 或者need-a

(1)继承

子类继承父类

1、子类继承父类,通过关键字extends

2、子类对象可以调用父类中的(public protected default)属性和方法,当作自己的来用

3、子类可以添加自己独有的属性和方法

4、子类从父类中继承的方法不满足需要,可以在子类中重写方法(覆盖父类方法),方法重写(override)

5、每一个类都有继承类,如果不写extends关键字,默认继承Object(任何一个引用类型的父类)

​ Object类非常重要,是任何一个引用类型的父类(直接或间接继承Object)

6、Java中继承是单个存在的(单继承),每一个类只能有一个继承类(在extends关键字后面只能写一个类),C++存在多继承,容易出现不知调用哪一个父类的方法,不安全

7、继承在内存中的存储形式

8、关于this和super(重要)

​ 1)this和super都是指代词,代替的是对象

​ 2)this代替的是当前执行方法时的那个对象,不一定是当前类的

​ 3)super代替的是当前执行方法时的对象的父类对象,空间内部的那个

​ 4)this和super都能调用一般属性和一般方法,可以放在类成员的任意位置(属性,方法,程序块,构造方法)

​ 注意:一般方法可以互相调用,会产生StackOverflowError问题,构造方法不能互相调用,就无法编译(因为默认的super和this都会抢第一行)

//父类
public class Animal () {
    public Animal () {
        System.out.println("我是Animal的构造方法");
    }
    public void eat () {
        System.out.println("我是Animal的eat方法");
    }
    public void sleep () {
        this.eat();
        System.out.println("我是Animal的sleep方法");
    }
}

//子类继承父类,通过extends关键字
public class Person extends Animal () {
    public void Person () {
        //隐藏了一行代码:super();创建父类的过程
        //如果父类没有无参构造方法,必须显示的利用super(name,18)...传递所需参数
        System.out.println("我是Person的构造方法");
    }
    public void Person (int a) {
        this();
    }
    public void study () {
        System.out.println("Good good study,day day up!");
    }
    public void eat () {
        System.out.println("我是Person的eat方法");
    }
}

public Test () {
    public static void main (String[] args) {
        Person p = new Person();
        p.sleep();//使用的Animal中的sleep方法,但是方法中this指向p
        /*输出:
        我是Animal的构造方法
        我是Person的构造方法
        我是Person的eat方法
        我是Animal的sleep方法
        */
    }
}
关键字 方法重写(override) 方法重载(overload)
产生两个继承关系的类,子类重写父类的方法 一个类中的一组方法
权限 子类可以大于等于父类 没有要求
特征 final static abstract
父类方法是final 子类不能重写
父类方法是static 子类不存在
父类方法是abstract 子类必须重写
(子类是具体必须重写,否则子类也得是抽象类,可以不重写)
没有要求
返回值 父类可以小于等于父类 没有要求
名字 子类与父类一致 一个类中的好多方法名必须一致
参数 子类参数必须与父类一致 每一个方法的参数必须一致
(个数 类型 顺序)
异常 运行时,编译时
如果父类方法抛出运行时异常,子类可以不予理会
如果父类抛出编译时异常
子类抛出异常数小于等于父类
子类抛出的异常的类型小于等于父类
没有要求
方法体 子类的方法内容与父类不一致 每一个重载的方法 执行过程不一样

权限修饰符

public

protected

默认

private

Object父类方法

1、toString() 打印输出时将对象变成String字符串

public String toString() {
return this.getClass().getName()+"@"+Integet.toHexString(this.hashCode());
//获取类的名字+@+16进制hashCode
}

2、hashCode()将对象在内存中的地址经过计算得到一个int整数(十进制)

​ 底层是由c++编写的

3、getClass()用来获取对象对应类的类映射(反射机制)

4、equals() 用来比较两个对象的内容(Object默认效果是==)

==可以比较基本类型(比较值),引用类型(比较地址)

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

5、wait()线程进入挂起等待状态(存在方法重载)

6、notify() 线程唤醒

7、notifyAll() 唤醒所有

8、finalize() 1.8版本以后被隐藏,权限修饰符是protected,在对象被GC回收时,默认调用执行的方法

final特征修饰符 finally代码块 finalize方法

9、clone() 克隆对象,权限修饰符是proctected

​ 看到native修饰符就是无法看到源码的,底层是由c++写的

(2)包含关系

包:类的个数变多以后,需要管理类-->包(可以理解为是一个文件夹)

​ 类的第一行会出现package package-name;package要出现在import className;前面

包含关系:has-a

​ 1)组合(人和大脑的关系,必须同时出现,同时消亡)

​ 2)聚合(汽车和车轮子的关系,整体和部分的关系,可以分割,电脑和主板的关系)

​ 3)关联(人有汽车,电脑的关系,后来形成的关系)

(3)依赖关系

依赖关系:use-a(need-a)

屠夫与猪的关系,后期因为某一件事情临时组合在一起,事情结束,关系不再存在

设计类关系的遵循的原则:高内聚,低耦合

耦合度:紧密程度 继承 > 包含 > 依赖

//屠夫与猪 屠夫的行为:杀猪 猪:被杀了会叫
public class Butcher {
    public void judgeTimeToKillPig (int month) {
        Pig pig = new Pig("PigOne");
        pig.growWeight(month);
        pig.bekilled();
    }
}

public class Pig {
    private String name;
    private int weight = 10;
    boolean flag;

    public Pig (String name) {
        this.name = name;
    }

    //每一个月体重长到1.5倍
    public void growWeight (int month) {
        for(int i = 0; i < month; i++){
            this.weight += this.weight >> 1;
        }
        this.flag = (this.weight)>150?true:false;
    }

    public void bekilled () {
        if(this.flag){
            System.out.println(this.name+"被杀了,好惨!");
        }else {
            System.out.println(this.name+"还小,还不能被杀");
        }
    }
    
    //获取猪的体重
    public int getWeight () {
        return this.weight;
    }
    
    //获取猪的名字
    public String getName () {
        return this.name;
    } 
}

public class Test () {
    public static void main (String[] args) {
        Butcher butcher = new Butcher();
        butcher.judgeTimeToKillPig(12);
    }
}

小任务

1、模拟学生在机房使用电脑:有5台电脑,有开关机状态,电脑被打开 被使用中 被关闭;有5个学生,使用电脑,让学生进入机房使用电脑;让学生陆续进入机房选择一台关闭的电脑使用

public class Student {
    //学生有姓名,品德值,和使用电脑的方法
    private String name;
    private int morality = (int)(Math.random()*10);

    public Student () {}

    public Student (String name){
        this.name = name;
    }

    public void useComputer (Computer computer) {
        System.out.println(this.name+"正在使用"+computer.getNumber()+"号电脑。");
        //System.out.println(this.morality);
        computer.open();
        if(this.morality > 4){
            computer.close();
        }else {
            System.out.println(this.name+"离开,居然不关闭电脑。");
        }
    }

    public String getName () {
        return this.name;
    }
}

public class ComputerRoom {
    //学生进入机房,使用电脑
    public Computer[] computers = new Computer[5];

    {
        for(int i = 0; i < this.computers.length; i++){
            this.computers[i] = new Computer(i+1);
        }
    }

    public void enterRoom (Student student) {
        System.out.println("欢迎"+student.getName());
        for(int i = 0; i < this.computers.length; i++){
            if(!this.computers[i].getStatus()){
                student.useComputer(this.computers[i]);
                break;
            }
        }
    }

}

public class Computer {
    //电脑有打开和关闭的方法,有自己的编号,状态
    private boolean status = false;
    private int number;
    public Computer (int number) {
        this.number = number;
    }

    //打开电脑
    public void open () {
        System.out.println(this.number+"号电脑被打开。");
        this.status = true;
    }

    //关闭电脑
    public void close () {
        System.out.println(this.number+"号电脑被关闭。");
        this.status = false;
    }

    //获取电脑编号
    public int getNumber () {
        return this.number;
    }

    //获取电脑状态
    public boolean getStatus () {
        return this.status;
    }

}

2、模拟警车小汽车与测速器之间的关系:测速器测量小汽车之间的速度,标准20米每秒;如果小汽车超速,警车追击,如果警车追击成功,输出追击时间,如果警车追不上,输出追不上啦

public class Car {
    private int speed = 50;
    public Car () {}
    public Car (int speed){
        this.speed = speed;
    }
    public int getSpeed () {
        return this.speed;
    }
}

public class Velometer {
    private int stanardTime = 2;
    public Velometer () {}
    public void measureSpeed (Car car) {
        if(100/car.getSpeed() < this.standardTime){
            //超速,警车追击
            PoliceCar pCar = new PoliceCar();
            pCar.chaseCar(car);
        }else{
            System.out.println("速度正常,请保持。");
        }
    }
}

public class PoliceCar {
    //后期使用观察者设计模式
    private int speed = 60;
    public PoliceCar () {}
    public PoliceCar (int speed) {
        this.speed = speed;
    }
    public chaseCar (Car car) {
        if(car.getSpeed() < this.speed){
            int time = 100/(this.speed - car.getSpeed());
            System.out.println("将在"+time+"秒后追上小汽车。");
        }else{
            System.out.println("追不上小汽车");
        }
    }
}
11、修饰符

(1)权限修饰符

​ 权限修饰符类型:

public 公共的,本类,同包,子类,任意类的位置只要有对象都可以访问

protected 受保护的,本类,同包,子类(通过子类对象在子类范围内部访问)

package pack_a;
import pack_b.Functions;
public class Test extends Functions {
    public static void main (String[] args) {
        Functions f = new Functions();
        f.testProtected();//报错,方法是受保护的
        f.test();//正常执行
        Test t = new Test();
        t.testProtected();//子类可以正常调用父类受保护的方法
    }
}

public class Functions {
    protected void testProtected () {}
    public void test () {}
}

​ 默认不写 默认的,同包(跨包,子类也无法使用方法)

private 私有的,本类

​ 权限修饰符:

​ 权限修饰符可以用来修饰类和类中的成员(除程序块)

​ 权限修饰符用来修饰类的时候只有两个可以用(public 默认不写)

Java面向对象的四个特征

​ 继承 封装 多态 (抽象)

​ 1)封装:将一些数据或执行过程,进行一个包装

​ 目的:保护这些数据或执行过程的安全

​ 方法本身就是封装,封装了执行的过程,保护过程的安全,隐藏了执行细节,增强了代码的复用性

​ 对属性本身的封装:私有属性(封装在类中),提供操作属性的方式(公有的方法,属性不要公有,非常不安全)

package packb;

import packa.TestA;

public class Test {
    public static void main (String,args) {
        TestSymbol ts = new TestSymbol();
        ts.testPublic();//本类和同包
        ts.testProtected();//本类和同包
        ts.testDefault();//本类和同包
        ts.testPrivate();//私有的 本类
    } 
}

public class TestSymbol extends TestA {
    public void testPublic () {}
    protected void testProtected () {}
    void testDefault () {}
    private void testPrivate () {}
}

​ *(2)*特征修饰符

final 最终的,不可更改的

​ 可以修饰变量,属性,方法,类

final修饰类,类不可以被继承

final修饰方法,方法不可以被子类重写

final修饰属性,属性必须赋初始值,否则报错

final修饰变量,变量为原始类型,值不能改变,引用数据类型地址不能改变

final int a = 10;//不赋值会报错,赋值以后也不可以更改值
a = 20;//报错

final int[] x = {1,2,3};
x[0] = 10;//final锁的是栈中的x的内存地址
x = new int[5];//报错,x的hashCode值不能更改

public class Test {
    private final String name = "";//要求属性必须赋初始值,否则报错
    public final int test () {}
    //final修饰的方法不允许子类重写
}

public final class Demo {}
//类是最终的类(太监类),此类不可以被其他类继承,通常是一些定义好的工具类
//比如Math,Scanner,Integer,String

​ 程序的设计问题:

​ 1)可读性,名字,缩进,注释

​ 2)健壮性,判断严谨

​ 3)优化:结构,性能,内存

​ 4)复用性,方法,类

​ 5)扩展性,抽象,接口,面向配置文件

static 静态的

​ 可以修饰属性,方法,块,内部类

​ 特点:

​ 1)静态元素存储在静态元素区,在类加载时就初始化了

​ 2)每一个类有自己的一个区域,与别的类不冲突,静态元素只加载一次(只有一份),全部类对象及类的本身共享

​ 3)由于静态元素区加载的时候可能没有创建对象,可以通过类名直接访问

​ 4)静态元素不属于任何一个对象,属于类

​ 5)静态元素区,Garbage Collection无法管理,可以粗暴的认为常驻内存(可能产生数据冲突)

​ 6)非静态成员中可以访问静态成员(对象.属性/方法)

​ 7)静态成员可以访问静态成员,不能访问非静态成员(类名.属性/方法)

​ 8)静态元素中不可以出现this或super关键字

//static final的应用
public class BookStore {
    private static final int BOOKSTORE_ADMIN = 0;
    private static final int BOOKSTORE_VIP = 1;
    private static final int BOOKSTORE_NORMAL = 2;
    public void buyBooks (float price,int identity){
        switch(identity){
            case BOOKSTORE_ADMIN:
                price*=0.5;
                break;
            case BOOKSTORE_VIP:
                price*=0.8;
                break;
            case BOOKSTORE_NORMAL:
                price*=1;
            default:
                ...
        }
    }
}

abstract 抽象的,修饰类,修饰方法

//抽象方法
public class Animal {
    //子类都用不上
    public abstract void eat ();
    public abstract void sleep ();
}

public class Person {
    //都进行重写
    public void eat(String food) {}
    public void sleep(String bed) {}
}

public class Pig {
    //都进行重写
    public void eat(String siliao) {}
    public void sleep(String zhujuan) {}
}

//抽象类中不是必须有抽象方法
//抽象方法必须放在抽象类中(或接口中)
/*抽象类的特点:
	可以含有一般属性,也可以有private static final等等
	可以含有一般方法,也可以含有private static final等等
	抽象类中允许含有抽象方法,一般类中不能有抽象方法(没有方法体)
	可以含有方法块和静态块,也可以重载
	抽象类含有构造方法,但是我们不能用调用构造方法直接创建对象
	抽象类只能通过子类继承来做事
*/
//抽象类可以单继承抽象类
//抽象类可以单继承具体类(用法上不会出现)
//具体类可以继承抽象类,但是必须将父类的抽象方法重写,如果子类也不想继承抽象类中的抽象方法,子类也可以设置为抽象类
//使用:子类不同的实现抽象的方法,让功能很灵活,还可以使用父类的方法
//抽象类没有具体成员,全部都是抽象方法-->接口(抽象类抽象到极致,还是类的结构,将abstract改用interface修饰)
public abstract class Animal2 {}

native 本地的(后续代码为c++进行指针操作,内存操作,我们无法再看到源码)

transient 瞬时的,短暂的

sychronized 同步的

volatile 不稳定的

12、单例设计模式(Singleton)

​ **设计模式:**设计模式是一种设计经验的总结,用来解决某些场景下的某一类问题的通用解决方案,让代码更容易让人理解,确保了代码的复用性,可扩展性,可靠性。

设计模式分类:(常用23种)

​ 1)创建型模式(5种):用于解决对象创建的过程

​ 单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式

​ 2)结构型模式(7种):把类或对象通过某种形式结合在一起,构成某种复杂或合理的结构

​ 适配器模式,装饰者模式,代理模式,外观模式,桥接模式,组合模式,享元模式

​ 3)行为型模式(11种):用来解决类和对象之间的交互 更合理的优化类或对象之间的关系

​ 观察者模式,策略模式,模板模式,责任链模式,解析器模式,迭代子模式,命令模式,状态模式,备忘录模式,访问者模式,中介者模式

单例模式设计场景:

​ 设计思想:一个类只能创建一个对象,有效减少内存占用空间

​ 设计模式的实现:1)饿汉式(立即加载) 2)懒汉式(延迟加载) 3)生命周期托管(单例对象别人帮我们处理 )

​ 设计一个百度搜索引擎:

//饿汉式
public class Singleton {
    //创建一个单例对象
    //通过设计,让这个类只能创建一个对象
    //1.让构造方法私有:保证外面不能随意创建对象
    private Singleton () {}
    //2.只存在一份且不允许更改
    private static final Singleton singleton = new Singleton();
    //3.提供一个获取单个对象的方法

    public static Singleton getSingleton () {
        return singleton;
    }
}

//懒汉式
public class Singleton {
    private Singleton (){}
    private static Singleton single;
    public static Singleton getSingle () {
        if(single == null){
            single = new Singleton();
        }
        return single;
    }
}

public class Test {
    public static void main (String[] args){
        Singleton s = Singleton.getSingleton();
    }
}
13、类的加载问题

类的加载顺序:

1、加载父类模板

2、父类会产生自己的静态空间

​ 先加载静态属性,再加载静态方法,然后加载静态块,最后执行静态块

3、加载子类模板

4、子类产生自己的静态空间

​ 先加载属性,方法,块,执行静态块

5、开辟对象空间

6、加载父类的非静态成员(属性,方法,块,构造方法),执行父类非静态块

7、执行父类的构造方法

8、加载子类的非静态成员(属性,方法,块,构造方法),执行子类非静态块

9、执行子类构造方法

10、将对象空间引用交给变量保存

存在继承机制的类的加载顺序图示:

接口

接口也是一个类的结构,不让有具体的

接口成员:

1)属性:不能含有一般属性,只能含有公有的静态的常量(public static final)

2)方法:不能含有一般方法,只能含有公有的抽象方法

3)块:不能含有,一般块和静态块都不行

4)构造方法:不能含有构造方法

5)与别的类结构之间的关系

​ 接口不能继承别的类,最抽象

​ 抽象类---多实现---接口

​ 具体类不可以与接口直接多实现,可以继承,直接将所有抽象方法重写

​ 接口---多继承---接口

图解:

public interface TestInterface {
    public static final String name = "test";
    //public static final 默认不写,也只能写这个
    //1.8版本以后可以使用default修饰方法
}

//抽象类可以直接多实现接口
public abstract A implements B,C {}

封装一个LinkedBox 解决Array不适合插入和删除的问题*

解决插入、删除效率低的问题(不适合遍历):

单向链表

双向链表

圆环链表

数组和链表的使用场景:需要不断插入,删除操作,使用链表

链表图:

//双向链表
package arrbox;

public class LinkedBox implements Box {
    //链表,链式
    private Node first;
    private Node last;
    private int size = 0;

    //添加元素
    public void add(int element){
        this.linkLast(element);
    }

    //根据索引找寻对象
    public int getElemByIndex(int index){
        //检查越界
        this.rangeCheck(index);
        return this.lookForNode(index).item;
    }

    //删除元素
    public int removeElemByIndex(int index){
        this.rangeCheck(index);
        Node targetNode = this.lookForNode(index);
        this.removeNode(targetNode);
        return targetNode.item;
    }

    //删除节点,使当前节点无关联,被GC回收
    private void removeNode (Node targetNode) {
        Node prev = targetNode.prev;
        Node next = targetNode.next;
        //判断节点如果为第一个节点
        if(prev == null){
            first = next;
        }else{
            prev.next = next;
        }
        //判断节点为最后一个节点
        if(next == null){
            last = prev;
        }else{
            next.prev = prev;
        }
        this.size--;
    }

    //获取box长度
    public int getBoxLength (){
        return this.size;
    }

    //寻找节点
    private Node lookForNode (int index) {
        Node targetNode;
        if(index < (size>>1)){
            targetNode = first;
            for(int i = 0;i < index - 1;i++){
                targetNode = first.next;
            }
        }else{
            targetNode = last;
            //执行次数要判断清楚
            for(int j = this.size - 1; j > index;j--){
                targetNode = last.prev;
            }
        }
        return targetNode;
    }

    private void linkLast (int element) {
        //用l保存上一个节点
        Node l = last;
        //创建新节点
        Node newNode = new Node(l,element,null);
        //最后节点改为新节点
        last  = newNode;
        //如果最后一个节点,为空,新节点就是最后一个节点
        if(l == null) {
            first = newNode;
        }else{
            //将上一次最后节点链接上新的最后节点
            l.next = newNode;
        }
        this.size++;
    }

    private void rangeCheck (int index) {
        if(index < 0 || index >= this.size) {
            throw new BoxIndexOutOfBoundsException("index越界.");
        }
    }
}
14、缺省适配器模式和多态
  • 适配器

(1)使用接口的弊端:如果规则改变了,所有子类的方法都要重写或做更改,如果继承的子类多了,成本较高

(2)设计模式:Adapter,适配器模式,类适配器,对象适配器,缺省适配器模式

(3)缺省适配器模式:如220V,110V都可以用的充电头

public class ArrBox implements Box {
    //必须都重写
}
public interface Box {
    void add();
    void get();
    void remove();
    void size();
}
public abstract class AbstractBox implements Box {
    public void add();
    public void get();
    public void remove(){
        //抛出自定义异常,让子类要么不用,要么重写
    }
    public void size(){
        //抛出自定义异常
    }
}
public class ABox extends AbstractBox {
    //只需要重写add和get
    public void add(){}
    public void get(){}
}
  • 多态

(1)Java中为什么要设计多态:

c++一个子类可以多继承很多父类,并使用父类中的所有方法,但是父类重名的方法,子类就无法知道调用的是哪一个父类的方法

多态:同一个对象,体现出来多种不同的形态,将一种行为表现出不同的效果

​ 1)实现多态效果,先要有继承关系

​ 2)父类类型的引用指向子类对象,只能调用父类的方法

​ 3)属性重名,使用的是父类的属性,如果有重写的方法,调用子类重写的方法

​ 4)需要调用子类的独有方法,需要向下转型(造型)

​ 5)若需要转换的类型与真实的类型不匹配,会产生ClassCastException造型异常(向下转型)

​ 6)可以使用instanceof运算符判断是否能造型

​ 7)方法的返回值可以小于等于父类

public class Person {
    public void eat (){}
    public void sleep (){}
    public void talk (){}
}
public class Teacher extends Person {
    //重写了eat方法
    public void eat (){}
    public void teach (){}
}
public class Test {
    public static void main (String[] args){
        
        //创建人
        Person p = new Teacher();
        
        //父类类型的引用指向子类对象
        //向上转型
        Person p2 = new Teacher();
         //p2只能调用Person的方法
        
        //使用的是teacher重写的eat方法
        p2.eat();
       
        //需要使用子类的独有方法,需要做向下转型
        Teacher t = (Teacher)p2;
        //可以使用子类独有的方法和属性
    }
}

(2)常见异常:

ClassCastException造型异常(同级之间造型)

NumberFormateException数字格式化异常(Integer.parseInt("abc")

InputMismatchException输入不匹配(Scanner的nextInt输入字符串)

NegativeArraySizeException数组长度负数(int[] arr = new int[-2];

NullPointerException空指针异常(Person p = null;p.eat();

ArithmeticException算数异常(100/0)

错误:

StackOverflowError栈内存溢出错误(方法互相调用等)

(3)多态的使用:

使用场景,一个流程中的对象行为一样,但是处理方式不同,我们写一个父类实现都一样的行为,具体细节抽象由子类自己实现,最后以父类型为参数

银行办理业务流程:

设计一个方法:等待用户来办理业务

流程:叫号码(排队),去窗口办理,办理完毕后(离开)

人的类型:老人,年轻人,土豪

策略模式(行为型):Strategy,执行流程不一样,中间某些点存在不同,用来解决执行流程固定,执行的结果由于提供了不同的策略而不同

//银行
public class Bank { 
    public void doBusiness (Client p) {
        //业务流程
        System.out.println("欢迎客户办理业务.");
        p.callNumber();
        p.transact();
        p.leave();
    }
}
//所有的客户
public abstract class Client {
    private String name;
    public Client (){}
    public Client (String name){
        this.name = name;
    }
    public void setName (String name) {
        this.name = name;
    }
    public String getName () {
        return this.name;
    }
    public abstract void callNumber ();
    public abstract void transact ();
    public abstract void leave ();
}

public class Youngmen extends Client {
    public Youngmen (){}
    public Youngmen (String name){
        super(name);
    }
    public void callNumber () {
        System.out.println("年轻人刷身份证取号");
    }
    public void transact () {
        System.out.println("年轻人正在办理业务");
    }
    public void leave () {
        System.out.println("年轻人很快处理完业务后离开");
    }
}

public class Oldmen extends Client {
    public Oldmen (){}
    public Oldmen (String name){
        super(name);
    }
    public void callNumber () {
        System.out.println("年纪很大,不知道在哪叫号");
    }

    public void transact () {
        System.out.println("叫来大堂经理,取到号码,掏出存折,办理业务");
    }
    public void leave () {
        System.out.println("老人办完业务后离开");
    }
}

public class Toff extends Client {
   public Toff () {}
   public Toff (String name){
       super(name);
   }
    public void callNumber (){
        System.out.println("土豪直接进入VIP室办理业务");
    }

    public void transact (){
        System.out.println("土豪拿出一袋现金要求存款");
    }

    public void leave () {
        System.out.println("土豪办理完业务,离开");
    }
}

public class Test {
    public static void main (String[] args){
        Client old = new Oldmen("oldmen");
        Client young = new Youngmen("Justin");
        Client toff = new Toff("boss");
        Bank bank = new  Bank();
        bank.doBusiness(old);
        bank.doBusiness(young);
        bank.doBusiness(toff);
    }
}
/*输出结果:
欢迎客户办理业务.
年纪很大,不知道在哪叫号
叫来大堂经理,取到号码,掏出存折,办理业务
老人办完业务后离开
欢迎客户办理业务.
年轻人刷身份证取号
年轻人正在办理业务
年轻人很快处理完业务后离开
欢迎客户办理业务.
土豪直接进入VIP室办理业务
土豪拿出一袋现金要求存款
土豪办理完业务,离开
*/
  • 内部类

    内部类:指的是Java中可以将一个类定义在另一个类的内部

    特点:

    内部类可以定义在类的内部(与类的成员一致)

    内部类可以定义在方法/块内部(与类成员相差一个层次,方法的局部变量一个层次)

    若想要在内部类中通过对象.调用外部类成员写法:外部类.this.外部类成员

    划分:

    1)成员内部类创建

    Demo d = new Demo();d.new InnerDemo();

    Demo.InnerDemo inner = new Demo.InnerDemo();

    2)匿名内部类

    public interface Demo {
        public abstract void test();
    }
    //让抽象方法也可以new,匿名类内部没有构造方法,也不能用任何修饰符修饰
    Demo d = new Demo(){
        public void test () {}
    };
    

    3)静态内部类

    优点:

    省略一个.java的文件,内部类可以使用外部类的属性和方法(不能使用this调用外部类的方法,因为this始终指向调用的对象)

  public class Demo {
      private String name = "minmin";
      //成员内部类
      public class InnerDemo {
          private String name = "chen";
          public void test () {
              //调用外部重名的属性,方法
              System.out.println(Demo.this.name);
              
 //局部内部类,不能用权限修饰符,特征修饰符只能使用abstract和final
              //局部内部类命名规则,Demo$1InnerFunctionClass
              //局部内部类使用的变量只能是final修饰
              class InnerFunctionClass {
                  
              }
          }
      }
  }

import 包名.Demo.InnerDemo;
public class Test {
    public static void main (String[] args){
        //使用点的方式调用
        //不导包的写法Demo.InnerDemo innerdemo = Demo.new InnerDemo();
        InnerDemo id = new InnerDemo();
        //匿名内部类,可以认为mi是一个实现MyInterface接口的子类
        MyInterface mi = new MyInterface(){
            public void eat(){System.out.println("I am eating.");}
        }
        mi.eat();//I am eating
    }
}
public interface MyInterface (){
    void eat();
}