Java面向对象编程

1、面向对象

所谓面向过程指的是面对与一个问题的具体解决方案。更多情况下不会做出重用的设计思考的,面向对象的主要的设计形式为模块化设计,并且可以进行重用配置。在整个面向对象设计里面更多情况下考虑的是标准。而后在使用的时候根据标准进行拼装。

面向对象三个主要的特征:

  • 封装性:内部的操作对外部不可见
  • 继承性:在已有结构的基础上继续进行功能的扩充
  • 多态性:在继承性的基础上扩充而来的概念,指的是类型的转换处理

2、类和对象

类是对某一类事物共性的抽象概念,而对象描绘的是一个具体的产物。

组成:

  • 成员属性(Field)
  • 操作方法(Method)

类与对象的定义

对象实例化

  • 声明并实例化 类名称 对象名称=new 类名称()
  • 分步骤完成 声明对象: 类名称 对象名称=null 实例化对象:对象名称=new 类名称()

对象内存分析

堆内存:保存的是对象的具体信息

栈对象:保存的是一块堆内存的地址 即:通过地址找到堆内存,而后找到对象内容

Person per=new Person() new开辟新的堆空间(堆的具体内容Person定义)

per在栈内存中保存了堆内存的地址

应用传递分析

类本身属于引用数据类型,既然是引用数据类型,那么就牵扯到内存的引用传递。

引用传递的本质:同一块堆内存空间可以被不同栈空间指向,也可以更换指向。

应用传递和垃圾产生的原因分析

所谓垃圾空间指的是没有任何栈内存所指向的堆内存空间将被GC(Gabage Collector垃圾收集器)进行不定期回收并且释放无用的内存空间,如果垃圾过多,将影响GC的处理性能,从而降低整体的程序性能,在开放中垃圾的产生应该越少越好。

一个栈内存只能够保存一个堆内存的地址数据,如果发生更改,则之前的地址数据将从该栈内存中彻底消失。

深入分析类和对象

成员属性封装

默认情况下,对于类中的属性是可以通过其他类利用对象进行调用的

private 封装后对类的外部不可见

[setter,getter] 设置或者取得属性

访问修饰符的区别

作用域当前类同包子类其他
public
protected×
默认××
private×××

构造方法

  • 构造方法名称必须和类名一致

  • 构造方法不允许设置任何的返回值

    这是因为程序编译器是根据代码结构进行编译处理的,执行的时候 也是根据代码结构,如果使用了void 关键字就和普通的方法无异,编译器就会将该方法认为是一个普通方法

  • 构造方法是在使用new关键字实例化对象的时候自动调用的

默认无参构造

有参构造

匿名对象

由于匿名对象没有任何的引用,使用一次后就将被GC进行回收与释放

3、this 、static关键字、代码块

this关键字

使用this可以实现以下三类结构的描述

  • 当前类中的属性:this.属性
  • 当前类中的方法(普通方法、构造方法)this()、this.方法名称()
  • 描述当前对象

评价一个代码的好坏

  • 代码结构可以重用,提供的是一个中间独立的支持
  • 目标是没有重复代码

可以利用this进行构造优化

优化前:

/*
要求:
无参构造   编号定义为10000,姓名定义为无名氏
单参构造   传递编号,姓名定义为”新员工“ 部门定义为”未定“ 工资为0 
三参构造   传递编号、姓名、部门、工资为2500
四参构造   所有属性全部进行传递
*/
class Emp{
    private long empno;//员工编号
    private String ename;//员工姓名
    private String dept;//部门名称
    private double salary;//员工薪水
    public Emp(){
        this.empno=1000;
        this.ename="无名氏";
    }
    public Emp(long  empno){
        this.empno=empno;
        this.ename="新员工";
        this.dept="未定";
        this.salary=0;
     
    }
    public Emp(long empno,String ename,String dept){
        this.empno=empno;
        this.ename=ename;
        this.dept=dept;
        this.salary=2500;
    }
    public Emp(long empno,String ename,String dept,double salary){
        this.empno=empno;
        this.ename=ename;
        this.dept=dept;
        this.salary=salary;
        
    }
    public String getInfo(){
        System.out.println("员工编号"+this.emno+"\t"+"员工姓名"+this.ename+"\t"+"部门名称"+this.dept+"\t"+"员工薪水"+this.salary)
}
    
}

优化后:

/*
要求:
无参构造   编号定义为10000,姓名定义为无名氏
单参构造   传递编号,姓名定义为”新员工“ 部门定义为”未定“ 工资为0 
三参构造   传递编号、姓名、部门、工资为2500
四参构造   所有属性全部进行传递
*/
class Emp{
    private long empno;//员工编号
    private String ename;//员工姓名
    private String dept;//部门名称
    private double salary;//员工薪水
    public Emp(){
        this(1000,"无名氏"null,0.0);
    }
    public Emp(long  empno){
      
     	this(empno,"新员工""未定",0)
    }
    public Emp(long empno,String ename,String dept){
        this(empno,ename,dept,2500)
    }
    public Emp(long empno,String ename,String dept,double salary){
        this.empno=empno;
        this.ename=ename;
        this.dept=dept;
        this.salary=salary;
        
    }
    public String getInfo(){
        System.out.println("员工编号"+this.emno+"\t"+"员工姓名"+this.ename+"\t"+"部门名称"+this.dept+"\t"+"员工薪水"+this.salary)
}
    
}

对于本类方法的互相调用时需要注意的问题

  • 构造方法必须在实例化新对象的时候调用,所以”this()“的居于只允许放在构造方法的第一行

  • 构造方法相互调用时请保留有程序的出口,别形成死循环

    例如以下代码就构成了循环

    class Person{
        private String name;
        private int age;
        public Person(){
            this("hahh",11)
    }
        public Person(String name){
            this();
            this.name=name;
        }
        public Person(String name,int age ){
            this(name)
            this.age=age;
        }
    }
    

static 关键字

static 定义属性

  • 在一个类中,所有的属性一旦定义了实际上内容都交由各自的堆内存空间保存

  • static公共属性 存储在全局数据区中

  • 对于static属性的访问需要注意一点:由于其是一个公共的属性,虽然可以通过所有的对象进行访问,但最好的方法是通过所有对象的最高代表(类)来进行访问,所以static 属性可以由类名直接调用。

  • static属性虽然定义在类中,但是其并不受到类实例化对象的控制

在进行类的设计的时候首选的一定是非static属性(95%)而后再考虑公共存储属性

static定义方法

class Person{
    private String name;
    private int age;
    private static String country="中国";
    public static void setCountry(String c){
	country=c;//注意不能加this
    }
}

static 方法可以直接由类名进行调用

static方法和非static方法在调用上

  • static 方法只允许调用static属性或者static方法

  • 非static 方法允许调用static属性或static方法

所有的static定义的属性和方法都可以在没有实例化对象的前提下使用,而所有非static定义的方法和属性必须要有实例化对象的情况下才可以使用

static定义的方法和属性都不是编写代码是所考虑的,只有在回避实例化对象并且描述公共属性的情况下才会使用static定义的方法或者属性

代码块

  • 普通代码块

    可以在一个方法中进行一些结构的拆分,以防止相同变量名称所带来的互相影响

  • 构造代码块

    定义在类中 构造块会优先与构造方法执行,并且每一次实例化新对象的时候都会调用构造块中的代码

  • 静态代码块

    使用static关键定义的代码块

    • 非主类中定义的静态代码块 主要是为了类中static属性初始化 优先于静态代码块执行 并且不管有多少个实例化对象出现静态代码块只会执行一次
    • 主类中定义的静态代码块 静态代码块优先主方法执行
  • 同步代码块(多线程)

4、数组的定义和使用

5、引用传递实际应用

引用传递整个Java开发与设计之中最为重要的技术组成

类关联结构

以人车关系为例:

package oop;
class Car{
    private String name;
    private double price;
    private Person person;//每辆车都有一个车主
    public Car(String name,double price){
        this.name=name;
        this.price=price;
    }
    public void setPerson(Person person){
        this.person=person;
    }
    public Person getPerson(){
        return this.person;
    }
    public String getInfo(){
        return "汽车品牌"+this.name+"、汽车价格"+this.price;
    }
}
class Person{
    private String name;
    private int age;
    private Car car;//一个人有一辆车
    public  Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    public void setCar(Car car){
        this.car=car;
    }
    public Car getCar(){
        return this.car;
    }
    public String getInfo(){
        return "人名"+this.name+"、年龄"+this.age;
    }
}

public class Demo1 {
    public static void main(String[] args) {
        Person wang = new Person("wang", 22);
        Car benz = new Car("Benz", 10000000);
        wang.setCar(benz);
        benz.setPerson(wang);
        System.out.println(wang.getCar().getInfo());
        System.out.println(benz.getPerson().getInfo());
    }
}

自身关联

package oop;
class Car{
    private String name;
    private double price;
    private Person person;//每辆车都有一个户主
    public Car(String name,double price){
        this.name=name;
        this.price=price;
    }
    public void setPerson(Person person){
        this.person=person;
    }
    public Person getPerson(){
        return this.person;
    }
    public String getInfo(){
        return "汽车品牌"+this.name+"、汽车价格"+this.price;
    }
}
class Person{
    private String name;
    private int age;
    private Car car;//一个人有一辆车
    private Person[] children;//每个人有多个孩子
    public  Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    public void setCar(Car car){
        this.car=car;
    }
    public Car getCar(){
        return this.car;
    }
    public void setChildren(Person[] children){
        this.children=children;
    }

    public Person[] getChildren() {
        return children;
    }

    public String getInfo(){
        return "人名"+this.name+"、年龄"+this.age;
    }
}

public class Demo1 {
    public static void main(String[] args) {
        //1.声明对象,并设置彼此的关系
        Person wang = new Person("wang", 22);
        Car benz = new Car("Benz", 10000000);
        Person childA = new Person("childA", 10);
        childA.setCar(new Car("Tensla",80000));
        Person childB = new Person("childB", 10);
        childB.setCar(new Car("BMW",800000));
        //父子关系
        wang.setChildren(new Person[]{childA,childB});
        wang.setCar(benz);
        benz.setPerson(wang);
        //2.根据关系获取数据
        System.out.println(wang.getCar().getInfo());
        System.out.println(benz.getPerson().getInfo());
        for (int i = 0; i <wang.getChildren().length ; i++) {
            System.out.println(wang.getChildren()[i].getInfo());
            System.out.println(wang.getChildren()[i].getCar().getInfo());

        }
    }
}

合成设计模式

以电脑组装为类

class 电脑{
    private 显示器 对象数组[];
    private 主机 主机;
}
class 显示器{}
class 主机{
    private 主板 对象;
    private 鼠标 对象;
    private 键盘 对象;
}
class 主板{
    private 内存 对象数组[];
    private CPU 对象数组[];
    private 显卡 对象;
    private 硬盘 对象数组[];
}
class 键盘{}
class 内存{}
class CPU{}
class 显卡{}
class 鼠标{}

6、数据表与简单java类的映射关系

数据表与简单java类之间的基本映射关系如下:

  • 数据实体表设计=类的定义

  • 表中的字段=类的成员属性

  • 表的外键关联=引用关联

  • 表的一行记录=类的一个实例化对象

    表的多行记录=对象数组

在实际项目开发的过程中一定是分两个步骤实现的:

  • 第一步:根据表的结构关系进行对象的配置
  • 第二部:根据要求通过结构获取数据

一对多映射

多对多映射

复杂多对多映射

7、String 类

String 类的特点分析

JDK1.8以前String保存的是字符数组;JDK1.9及以后String保存的是字节数组

字符串就是对数组的一种特殊包装应用,但同时也应该清楚,既然包装的是数组,所以字符串中的内容是无法改变的

字符串比较

  • “==”:进行的是数值比较,如果用于对象比较则比较的是两个对象的内存地址
  • equals():是类所提供的一个比较方法,可以直接进行字符串内容的判断

字符串常量

所有使用“”定义的字符串变量实际上描述的都是一个String类的匿名对象

所谓的String 类的直接赋值描述的是,将一个匿名对象设置成一个具体的引用名字

观察匿名对象的存在:字符串常量可以调用equals方法实现对象相等的比较

关于对象相等的小技巧

​ 在进行项目开发的时候 如果现在某些数据是由用户输入,并且要求数据为一个指定内容的情况下,建议将字符串常量写在前面。

String input="mldn";//用户输入的内容
System.out.println(str.equals("mldn"))//若用户输入为空则会报空指针异常的错误NUllPointerException
//将字符常量写在前面则可以规避这些问题    因为equals方法提供有一个可以回避空的判断
    

String 两种实例化方法的区别

  1. 直接赋值

    在Java程序的底层里面有一个专门的字符串池(字符串数组)

    在采用直接赋值的过程之中,对于字符串而言可以实现池数据的自动保存,这样如果再有相同数据定义时,可以减少对象的产生以提高系统的性能

  2. 构造方法实例化

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

​ 会在内存中开辟两块空间 其中一块成为垃圾空间

​ 因此直接赋值更加节省内存

String StrA="mldn"
String STrB=new String("mldn")

 

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

除了以上特带你之外,在使用构造方法实例化String 类对象时不会自动出现保存到到对象池 可以手动入池 intern()方法间

简而言之 String类两种对象实例化方式的区别:

  • 直接赋值:只会产生一个实例化对象,并且可以自动保存到对象池中,以实现对象的复用
  • 构造方法赋值:会产生两个实例化的对象,并且不会自动入池,无法实现对象的重用

String对象(常量)池

对象池的主要目的是实现数据的共享处理,以String对象池为例,里面的内容就是为了重用,而重用实际上就属于共享设计。但是在java中对象(常量)池可以分为两种。

  • 静态常量池:在程序(*.class)在加载时会自动将此程序中保存的字符串、普通的常量、类和方法的信息等等,全部进行分配

    String strA="www.baidu.com";
    String strB="www"+"baidu"+"com";
    System.out.println(strA==strB);//true
    

    该程序中所有的内容都是都是常量数据(字符串的常量都是匿名数据),所以在程加载时会处理好相应的连接;

  • 运行时常量池:一个程序加载后,里面可能有一些变量

    String info="baidu";
    String strA="www.baidu.com";
    String strB="www"+info+"com";//存储在运行时常量池
    System.out.println(strA==strB);//false
    

    这是因为info是变量,变量的内容是可以修改的, 程序在加载的时候并不确定info是什么内容。因此在字符串连接的时候,并不认为StrB的结构就是所需的最终的结果。

字符串修改分析

在String类中包含的是一个数组,当设置了一个字符串之后,会自动的进行

一个数组空间的开辟,开辟的内容长度是固定的

String str="www.";
Str+="baidu.";
str=str+"com";
System.out.println(str);

在程序的整个处理之中,字符串常量的内容并没有发生任何,改变的只是String类对象的引用,这种改变将有可能带来大量的垃圾空间。

String类在开发中不要进行内容的频繁修改

String 类常用方法

Java.lang.String(学会使用官方文档)

字符串与字符数组

在jdk1.9之前,所有的String都利用了字符数组实现了包装的处理,所以在String类中是提供有相应的转换处理方法的,这类方法包括了构造方法和普通方法两类。

No方法名称类型描述
01public String(char[] value)构造将传入的全部字符数组变为字符串
02public String(char[]value,int offset,int count)构造将部分字符数组转变为字符串
03public char charAt(int index)普通获取指定索引位置的字符
04public char[] toCharArray()普通将字符串中数据以字符数组的形式返回

程序中的索引下表都是从0开始的

实例:

  1. 实现字符串与字符数组的转换
String str="hello";
char[] result=str.toCharArray();
//转换为大写
for(int x=0;x<result.length;x++){
    result[x]-=32;
}
//将处理后的数组转换为String
String newStr=new String(result);
System.out.println();
  1. 判断也给字符串中数组是否全部由数字所组成

    public boolean isNumber(String str){
        char[] result=str.toCharArray();//将字符串转换为字符数组
        for(int x=0;x<result.length;x++){
            for(result[x]<'0'||result[x]>'9'){
    		 return false;
           }
        }
        
    }
    

    在实际开发处理中文的时候往往使用char类型,因为可以包含中文数据

字符串与字节数组

字符串与字节转换时,其主要目的是为了进行二进制的数据传输,或者进行编码转换

No方法名称类型描述
01public String(byte[] bytes)构造将所有的字节数组转换为字符串
02public String(byte[]bytes ,int offset,int length)构造将部分字节数组转换为字符串
03public byte[] getBytes()普通将字符串转换为字符数组
04public bytes[] getBytes(String charsetName) throws UnsupportedEncodingException普通编码转换

字符串比较

最常用的方法euqals(),但这个方法需要注意的是该方法是进行大小写区分的

No方法名称类型描述
01public boolean equals(String anObject)普通区分大小写的相等判断
02public boolean equalsIgnoreCase(String anotherString)构造不区分大小写判断
03public int compareTo(String anotherString)普通进行字符串大小写判断,大于(>0)小于(<0)等于(=0)
04public int compareToIgnoreCase(String str)普通忽略大小写进行字符串比较

字符串查找

No方法名称类型描述
01public boolean contains(String s)普通判断子字符串是否存在
02public int indexOf(String str)普通从指定位置查找字符串的位置,找不到返回-1
03public int indexOf(String str,int fromIndex)普通从指定位置查找指定字符串的位置
04public int lastIndexOf(String str)普通从指定位置由后到前查找字符串
05public int lastIndex(String str,int fromIndex)普通由指定位置由后到前查找字符串
06public boolean startsWith(String prefix)普通判断是否以指定的字符串开头
07public boolean startsWith(String prefic,int offset)普通由指定位置判断是否以指定的字符串开头
08public boolean endsWith(String str)普通是否以指定的字符串结尾

字符串替换

No方法名称类型描述
01public repalceAll(String regex,String String replacement)普通全部替换
02public String replaceFirst(String regex,String replacement)普通替换首个
String str="hello";
System.out.println(str.replaceAll("l","X"));
System.out.println(str.replaceFirst("l","X"));

字符串拆分

No方法名称类型描述
01public String[] split(String regex)普通按照指定的字符串拆分
02public String[] split(String regex,int limit)普通按照指定字符串拆分指定的个数

在进行拆分的时候会遇到拆了的情况,这个时候最简单的理解就是使用“\\”进行转义,例如 String str =“193.198.1.3” String[] result[]=str.split(“\\.”)

字符串的截取

No方法名称类型描述
01public String substring(int beginIndex)普通从指定索引截取到结尾
02public String substring(int beginIndex,int endIndex)普通截取指定范围内的字符串

在实际的开发中beginIndex和endIndex的值是由一点过的计算来确定的

//截取出姓名
String str="01-photo-张三.jpg";
int beginIndex=str.indexOf("-",str.indexOf("photo"));
int endIndex=str.lastIndex(".");
System.out.println(str.substring(begeinIndex,endIndex));

字符串格式化

从jdk1.5开始引入,java提供了格式化数据的处理操作,类似C语言中的输出语句,可以利用占位符来实现数据的输出,对于常用的占位符常用的有:字符串(%s)、字符(%c)、整数(%d)、小数(%f)等来作描述

No方法名称类型描述
01public static String format(String format,各种类型…args)普通根据指定结构进行文本格式化显示
String name="张三";
int age=10;
double score=98.768888489;
String str=String.format("姓名:%s、年龄:%d、成绩:%5.2f",name,age,score);

其他操作方法

No方法名称类型描述
01public String concat(String str)普通字符串连接
02public String intern()普通将字符串入池
03public boolean isEmpty()普 通是否为空字符串(不是null)
04public int length()普通计算字符串的长度
05public String trim()普通去除左右的空格信息
06public String toUpperCase()普通转大写
07public String toLowCase()普通转小写
String strA="www.baidu.com";
String strB="www.".concat("baidu.").concat("com");
System.out.println(strA==strB);//false

结果为false说明此操作并没有实现静态的定义

在进行一些数据输入的时候,很难保证输入的数据没有空格,有空格需要对输入的数据处理时可以受用trim()

  • 虽然java的String类中提供了大量的方法,但是缺少首字母大写的方法,开发者可以自己实现,利用方法的组合即可

    class StringUtils{
        //此代码是在日常开发中必定会使用的程序
        public static String initcap(String str){
            if(str==null||"".equals(str)){
               return str;
            }
            if(str.length=1){
                return str.toUppperCase();
            }
            return str.substring(0,1).toUpperCase()+str.substring(1);
        }
        
    }
    

8、继承

继承的定义与使用

继承的主要特点在于:扩充已有类的功能

继承的引出

所谓的良好的代码指的的是结构性合理、适合于维护、可重用性高。引入继承可以提高代码的重用性。

继承的实现

class 子类 extends 父类{}

很多情况下将字类称为派生类,将父类称为超类(SuperClass)

子类能够使用父类的属性,但从内存的角度看子类和父类是分开定义的

子类对象实例化流程

在对象实例化时一定要先实例化好父类对象,目的是所有的属性可以进行空间的分配

class Person{
    public Person(){
        System.out.println("这是一个父类构造方法");
    }
}
class Student extends Person{
   	public Student(){
        this.super();//调用无参构造写不写该方法效果一样 该语句只允许放在子类构造方法的首页 在默认情况下的实例化处理 子类只会调用父类的无参构造方法   如果父类中并没有提供无参构造 则必须通过super()调用有参构造
        System.out.println("这是一个子类构造方法")}
}

即使没有进行父类对象初始化,也会有系统自动调用父类的构造方

class Person{
    private String name;
    private int age;
    public Person(String name,int age){
       this.name=name;
        this.age=age;
    }
}
class Student  extends Person(){
	private String name;
    private int age;
    private String school;
    public Student(String name,int age,String school){
        this.super(name,age);
        this.school=school;
        
}
}

super和this都可以调用构造方法,不同的是super调用的是父类额构造,而this调用的是本类的构造。两者都只能出现在构造方法的首行,所有二者不能同时出现。

覆写

继承时子类可以继承父类的所有定义,但是这里面也有可能出现不合适的情况,如果发现父类中设计不足并且需要保留父类中的方法或者属性名称的情况下就会发生覆写。

方法覆写

  • 当子类中定义了与父类方法名称相同,参数类型相同,参数个数相同的方法的时候,就成为方法的覆写

  • 在子类中如果想要继续调用父类的方法,那么必须使用"super.方法()"

方法覆写限制

  • 虽然利用方法的覆写能够很好地扩充父类的功能,但是对覆写也是有其自身要求的:被覆写的方法不能比父类方法拥有更为严格的访问控制权限。

​ 对于常用的访问控制权限:public>default>privvate

  • 使用private修饰的方法对于子类不可见,不能够进行覆写

解释Override和Overloading的区别?Overloading时返回值的类型是否相同?

NO区别OverloadingOverride
1中文含义重载覆写
2概念方法名称相同,参数的类型,个数不同方法名称,参数类型和个数都相同
3权限没有权限限制不能比父类有更加严格的权限限制
4范围发生在一个类中发生在继承关系类中

在进行方法重载的时候,并没有对返回类型进行限制,但是好的习惯应该保持返回类型的一致,

属性覆盖

面试题:super和this的区别:

  • 在程序类中this()表示先从本类查找所需要的属性或方法,如果本类不存在则查找父类定义;如果使用super()则不查找子类,直接查找父类
  • this和super都可以进行构造方法的调用,但是this()调用的是子类的构造方法,而super()则由子类调用父类构造,二者必须放在构造方法的首行,所以不能够同时出现
  • this可以表示当前对象

final关键字

final在程序之中描述的是种终结器的概念,在java中使用final关键字可以实现如下的功能 :

  1. 定义不能被继承的类
  2. 定义不能被覆写的方法
  3. 定义常量
    常量往往都是公共的概念 ,所以为了可以体现出共享的概念,往往会使用一种全局常量的形式来定义 使用public static final来定义全局常量

综合案例:继承分析

  1. 编写程序,统计字符串”want you to know one thing“中字母n和字母出现的次数

/**
   方法一、定义工具类
 */
class StringUtils{
    public static int[] count(String str){
        int[] countData=new int[2];
        char[] data=str.toCharArray();
        for (int i = 0; i <data.length ; i++) {
            if(data[i]=='n'){
                countData[0]++;
            }
            if(data[i]=='o'){
                countData[1]++;
            }
        }
        return countData;
    }


}

public class JavaDemo {
    public static void main(String[] args) {
        String str="want you to know one thing";
        int[] count = StringUtils.count(str);
        System.out.println("n的个数:"+count[0]);
        System.out.println("o的个数"+count[1]);
    }
}

以上解决方案严格来说只是一种顺序式的思维模式解决,不利于后期进行结构化的优化 。采用面向对象的方式实现如下:

/**
 * 编写程序,统计字符串”want you to know one thing“中字母n和字母出现的次数
 */
/**
   方法二,采用面向对象的方式 继承实现
 */
class StringUtils{
    /**
     * content 需要保存的字符串
     */
    private String content;
    public StringUtils(String content){
        this.content=content;
    }
    public String getContent(){
        return this.content;
    }
    public String getInfo(){
        return this.getContent();
    }
}

class StringCount extends StringUtils{
    private int ncount;
    private int ocount;
    public StringCount(String content){
        super(content);
    }
    public void countChar(){
        char[] chars = super.getContent().toCharArray();
        for (int i = 0; i <chars.length ; i++) {
            if(chars[i] == 'n'){
                this.ncount ++;
            }
            if(chars[i]=='o'){
                this.ocount ++;
            }

        }
    }
    public int getNCount(){
        return this.ncount;
    }
    public int getOcount(){
        return this.ocount;
    }
}



public class JavaDemo {
    public static void main(String[] args) {
        String str="want you to know one thing";
        StringCount stringCount = new StringCount(str);
        stringCount.countChar();
        System.out.println("n 的个数:"+stringCount.getNCount());
        System.out.println("o的个数:"+stringCount.getOcount());
    }
}

  1. 数组操作

    建立一个可以实现整型数组的操作类(Array)而后在里面可以操作数组的大小由外部来决定,而后在Array类中需要提供有数组的如下处理:进行数据的增加(如果数据满了则无法增加)、可以实现数组的容量扩充、取得全部的数组内容。完成之后在此基础上在派生出两个子类:

    • 数组排序类:返回数据必须是排序后的结果
    • 数组反转类:可以实现内容的首尾交换
    class Array{
        //整型数组
        private int[] data;
        //进行数组索引控制
        private int foot;
        public Array(int len){
            if(len>0){
                //开辟数组
                this.data=new int[len];
            }
            else{
                this.data=new int[len];
            }
    
        }
        public void increment(int num){
            //实现数组的扩容 给出的是扩容的大小 实际的大小:已有大小加扩容大小
            int[] newData=new int[this.data.length+num];
            //将原来的数组拷贝到新的数组上
            System.arraycopy(this.data,0,newData,0,this.data.length);
            //改变引用
            this.data=newData;
    
        }
        public boolean add(int num){
            //数据增加
            if(this.foot<this.data.length){
                this.data[this.foot ++]=num;
                return true;
            }
            return false;
    
        }
    
        public int [] getData(){
            return this.data;
        }
    }
    public class JavaDemo01 {
        public static void main(String[] args) {
            Array array = new Array(3);
            System.out.println(array.add(0));
            System.out.println(array.add(1));
            System.out.println(array.add(2));
            array.increment(4);
            System.out.println(array.add(3));
        }
    }
    
    

    数组排序子类

    class SortArray extends Array{
        //定义排序子类
        public SortArray(int len){
            super(len);
        }
        public int getData(){
            //获得排序结果
            java.utils.Arrays.sort(super.getData());
            reutrn super.getData();
        }
    }
    

    定义反转子类

    class ReverseArray extends Array{
        //定义反转子类
        public ReverseArray(int len){
           super(len);
        }
       public int[] getData(){
           int center=super.getData().length/2;
           int head=0;
           int tail=super.getData().length-1int temp;
           for(int i=0;i<center;x++){
              temp=super.getdata()[head];
              super.getData()[head]=super.getData()[tail];
              super.getData()[hail]=temp;
              head++;
              tail--;
           }
       return super.getData();
        
    }
        
    }
    

9、Annotation注解

简介

​ Annotation是从JDK1.5之后提出的一个新的开发技术结构,利用Annottion可以有效的减少程序配置的代码,并且可以利用Annotation进行一些结构化的定义。Annotation是一种以注解的形式实现的程序开发。

​ 从历史来讲程序的开发一共分为了三个过程:

  1. 过程一:在程序定义的时候将所有可能用到的资源全部定义到程序代码中
    • 如果此时服务器的相关的地址发生了改变,那么对于程序而言就需要进源代码的修改,维护不方便,需要开发人员来完成,这样的做法是不方便的。
  2. 过程二:引入配置文件,在配置文件中定义全部要使用的服务器资源
    • 在配置项不多的情况下,此类配置非常的好用,并且十分的简单,但是如果这个时候所有的项目都是采用这种结构开发,就会导致配置文件十分的多。
    • 所有的操作都需要通过配置文件完成,提高了开发的难度。
  3. 过程三:将配置信息重新写回到程序里面,利用一些特殊的标记与程序代码进行分离,这就是注解的作用,也就是Annotation提出的基本依据。
    • 如果全部都使用注解开发,难度太高了,配置文件有好处也有缺点,所以所以现在人们的开发基本上是围绕这配置文件+注解的形式进行的。

几个基本的注解

  1. 准确覆写 @Override

    • 该注解主要是帮助开发者在程序编译的时候可以检查出程序的错误
  2. 过期声明@Deprecated

    • 所谓国企操作指的是在一个软件项目的迭代开发过程之中,可能有某一个方法或者是某一个类,由于在最初设计的时候考虑不周,导致新版本的应用会有不适应的地方(老版本不影响),这个时候又不可能直接删除这些操作(老版本在用),那么就希望给一个过渡的时间,目的告诉新的用户这些操作不要再用了,这样的方法就必须利用"@Deprecated"注解进行定义。
  3. 压制警告@SupppressWarings

    • 例如@SuppressWarnings(“deprecation”)

10、多态性

简介

​ 多态是同一个行为具有多个不同表现形式或形态的能力。

​ 多态性是面向对象中的第三大主要特征,多态性是在继承性的基础之上扩展出来的概念,也就是说可以实现父子类之间的相互转换。

​ java之中的多态性有两种实现的模式:

  1. 方法的多态性:

    • 方法的重载
    • 方法的覆写
  2. 对象的多态性:父子实例之间的相互转型,有两种模式:

    • 对象向上转型: 父类 父类实例=子类实例(自动完成转换)
    • 对象的向下转型: 子类 实例=父类实例(强制完成转换)

    从实际的转型处理来讲,大部分情况下考虑最多的一定是对象的向上转型、对于对象的向下转型往往都是在使用子类特殊功能的时候采用向下转型、还有些时候是不会考虑转型的(String类型)。

对象转型

​ 对象转型的处理属于多态性,而这一特性必须在继承的基础上实现。

对象向上转型

​ 向上转型的主要特点在于可以对参数进行统一的设计

​ (接受或返回参数的统一性)

class Sql{
    public getMessgage(){
        System.out.println("sql...");
    }
}
class Mysql extends Sql{
    public getMessage(){
        System.out.println("Mysql...")
    }
}
class SqlServer extends SQl{
    public getMessage(){
        System.out.println("SqlServer")
    }
}
class JavaDemo{
    public static void main(String []args){
        getMessage(new SqlServer());
        getMessage(new Mysql());
        	
    }
    public static getMesage(Sql sql){
        sql.getMessgae();
    }
}

对象向下转型

​ 向下转型主要特点在于使用到一些子类自己特殊的定义处理

​ 向下转型需要使用强制转换

class Person{
    public void  print(){
        System.out.println("这是一个普通人呢");
    }

}
class SuperMan extends Person{
    public void  fly(){
        System.out.println("我可以飞");
    }

}
public class JavaDemo{
    public static void main(String []args){
        Person person=new SuperMan();
        person.print();//向上转型只能使用父类中也存在的方法
        SuperMan superMan=(SuperMan) person;//强转
        superMan.fly();

    }

}

​ 向上描述的是一些公共的特征,而向下描述的是子类自己特殊的定义环境。

​ 对象向下转型之前一定要首先发生向上转型,这是因为两个没有任何关系的实例相互转型会出现“ClassCastException”异常,所以向下转型并不是一件安全的事情。

instanceof关键字

​ 向下转型本身是一件存在安全隐患的事情,所以为了保证向下转型的正确性,往往在转型之前需要进行判断,判断某个实例是否是某个类的对象,这个就需要instanceof语法来实现。

​ 在以后进行一些完善性的程序开发之中,对于转型之前一定要使用instanceof

public class JavaDemo{
    public static void main(String []args){
        Person person=new SuperMan();//向上转型
       if(person instanceof SuperMan){
           person.print();
        }

    }

}

11、Object类

​ Object类的主要特点是可以解决参数的统一问题,使用Object类可以接受所有的参数类型。

简介

​ 在java中只有一个类是不存在继承关系的,这个类就是Object类。

​ 在Object类设计的时候考虑到了所有的继承问题,所以该类提供无参构造方法,这样所有的类在定义时即使不知道Object类的存在也不会出现构造方法调用失败的问题。

​ 在java设计的过程中对于所有的引用数据类型,实际上都可以用Object类进行接受,包括数组

public class JavaDemo{
    public static void main(String[]args){
        Object obj=new int[]{1,2,3};//向上转型
        if(obj instanceof int[]){//是否是整型数组
            int[] data=(int[])obj;//向下转型
            for(int temp:data){
                System.out.println(temp);
            }
            
        }
        
    }
}

取得对象信息

​ Object类本身提供一些处理方法,其中:

​ public String toString() 获取一个对象的完整信息。

​ 编写简单java类的时候只需要覆写toString()方法即可。

对象比较

​ public boolean equals(Object obj)

​ 默认情况下是进行了两个地址的比较

//源码
public boolean equals(Object obj){
   return "this=obj";
}

​ 也就是说对于实际的使用者来说,如果想要正确实现判断,就必须在子类中覆写此方法,并且进行属性判断。

class Person(){
    private String name;
    private int age;
    public Person(String name,int age){
        this.name=name;
        this.age=age;
    }
    public String toString(){
        return "姓名"+this.name+"、年龄:"+this.age;
    }
    public boolean equals(Object obj){
        if(obj instanceof Person){//很必要,转型时发生错误
           return false;
        }
        if(obj==null){//不关心null的比较
            return false;
        }
        if(this==obj){//同一个地址
            return true;
        }
        Person per=(Person)obj;
        return this.name.equals(per.name) && this.age==per.age;//per.name由于在类中所以可以直接调用
    }
}

String 类中已经覆写了equals方法。

12、抽象类的定义和使用

​ 类继承的主要功能在于可以扩充已有类的功能,但对之前的继承操作而言会发现,子类可以由自己的选择来决定是否要覆写某一个方法,这个时候父类无法对子类做出强制性约束。在实际开发中很少好会出现继承一个比较完善的类,而是必须要继承抽象类。

基本概念

​ 抽象类的主要作用在于对子类中覆写方法进行约定,在抽象类中可以定义一些抽象方法来实现这样的约定。抽象方法指的是使用了abstract关键字定义的并且没有提供方法体的方法,抽象方法所在的类必须为抽象类,抽象类必须使用abstract关键字来定义。

​ 关于抽象类使用的建议:

  • 抽象类的使用很大程度上有一个核心的问题(抽象类自己无法直接实例化)
  • 抽象类中主要的目的是进行过度操作使用,所以当使用抽象类进行开发的时候,往往都是你在设计中需要解决类继承的问题时所带来的代码重复处理。

抽象类的相关说明

  1. 定义抽象类时不能使用final关键字,因为抽象类必须有子类。
  2. 抽象类是作为一个普通类的加强版出现的(抽象类的组成就是在普通类的基础之上扩展而来的,只是追加了抽象方法)。既然是在普通类基础之上扩展的,那么普通之中就可以定义属性和方法,那么这些属性一定要求进行内存空间开辟的,因次抽象类可以提供有抽象方法,并且子类也一定会按照子类对象的实例化原则进行构造调用。
  3. 抽象类中允许没有抽象方法,但也不能直接进行实例化。
  4. 抽象类中可以提供有stati方法,并且该方法不受抽象类对象的限制(可通过类名进行直接调用)。

抽象类的应用

模板设计模式

abstract class Action{
    public static final int EAT=1;
    public static final int SLEEP=5;
    public static final int WORK=10;
    public void commmand (int code){
        switch(code){
            case EAT:	{
                this.eat();
                break;
            }
            case SLEEP:
               	this.sleep();
                break;
        }
        case WORK:
        	this.work()break;
    }
    public abstract void eat();
    public abstract void sleep();
    public  abstract void work();
}

13、包装类

包装类实现原理分析

​ 包装类的主要功能是针对于基本数据类型的对象转换而实现的。

认识包装类

​ Object类最大的特点是所有类的父类找个,并且可以接收所有的数据类型,但是在这个过程中存在一个问题:基本数据类型并不是一个类,如何要想将基本数据类型以类的形式进行处理,那么就需要对其进行封装。

	以int数据为例实现一个包装类的定义:
class Int{
    private int data;//包装了一个数据类型
    public Int(int data){
        this.data=data;
    }
    public int intValue(){
        return this.data;
    }
}
public class JavaDemo{
    public static void main(String []args){
       Object obj=new Int(10);//装箱:将基本数据类型保存到包装类中
       int x=((Int)obj).intValue();//拆箱:从包装类对象中获取基本数据类型
        System.out.println(x);

    }

}

​ 八种基本数据类型对应有8种包装类,Java中包装类一共提供有两种类型:

  • 对象型包装类(Object直接子类):Boolean ,Character

  • 数值型包装类(Number直接子类):Byte,Short,Integer,Long,Float,Double

    Numbe类是一个抽象类,定义有如下方法:

序号方法名称类型描述
01public byte byteValue()普通从包装类中获取byte数据
02public short shortValue()普通从包装类中获取short数据
03public abstract int intValue()普通从包装类中获取int数据
04public abstract long longValue()普通从包装类中获取long数据
05public abstract float floatValue()普通从包装类中获取float数据
06public abstract double doubleValue()普通从包装类中获取double数据

​ Number类中的方法是直接提供有获取包装类中基本数据类型的功能,一共只定义了6种方法

装箱与拆箱操作

  • 数据装箱:将基本数据类型保存到包装类中,一般可以利用构造方法完成

  • 数据拆箱:从包装类中获取基本的数据类型

自动拆箱装箱

​ 从JDK1.9之后,所有包装类中提供的构造方法就已经过期处理了,不建议用户继续使用,这是因为从JDK1.5之后,为了方便处理提供了自动的装箱拆箱操作,手工的模式就基本没人用了。

Integer obj=10;//自动装箱,不用再考虑构造方法
obj++;//包装类对象可以直接进行数学运算
int x=obj;//自动拆箱		
System.out.println(x*obj);
	除了提供有这种自动的数学运算支持外,实现自动装箱的最大好处是可以实现Object接受基本数据类型的操作
Object obj=19.2;//	double自动装箱为Double,向上转型为OBject
double num=(Double) obj;//向下转型为包装类,再自动转型

相等判断

​ 进行包装类相等判断的时候使用equals()方法完成,而包装类本身要考虑占位的长度,如果超过了一位就要使用equals()进行比较,不超过则可以进行直接比较。

14、接口的定义和使用

接口的定义

基本定义

​ 抽象类与普通类相比最大的优势在于:可以实现对子类覆写方法的控制,但是在抽象类中可能会保留一些普通方法,而普通方法里可能会涉及到一些安全或者隐私的操作问题,那么在开发之中如果想要对外部隐藏全部的实现细节,则可以通过接口描述。

​ 接口可以理解为一个纯粹的抽象类(最原始的定义接口之中是只包含有抽象方法与全局常量的),从JDK1.8之后由于引入了Lambda表达式的概念,接口的定义也得到了加强。除了有抽象方法和全局常量之外,还可以定义普通方法和静态方法。如果从设计本身的角度来讲,接口之中的组成还应该以抽象方法和全局常量为主。

​ 在java中接口主要使用interface关键字来进行定义。

//由于类名称与接口名称的定义要求相等,为了进行区分往往在接口名称前加字母I
interface Imessage{
    //定义一个接口
    public static final String INFO="hhahahhahah";//全局常量
    public abstract String getInfo();//抽象方法
}

​ 接口无法直接产生实例化对象,对于接口的使用原则:

  • 接口需要被子类实现(implements),一个子类可以实现多个父接口

  • 子类(如果不是抽象类)那么一定要覆写接口之中的全部抽象方法

  • 接口对象可以利用子类对象的向上转型进行实例化

    在java里之所以使用接口主要的目的是一个子类可以实现多个接口,利用接口可以实现多继承的概念

    interface IMessage{
        public static final String  INFO="message";
        public abstract String getInfo();
    }
    interface IChanel{
        public abstract boolean connect();//定义抽象方法
    }
    class MessageImpl implements IMessage,IChanel{
        @Override
        public String getInfo(){
            if (this.connect()) {
                return "获得消息";
            }
            return "创建失败,无法获得消息";
        }
        @Override
        public boolean connect(){
            return true;
        }
    }
    public class JavaDemo{
        public static void main(String []args){
            IMessage message = new MessageImpl();
            System.out.println(message.getInfo());
        }
    
    }

观察转换:

 IChanel chanel=(IChanel) message;
 System.out.println(chanel.connect());

​ 由于MessageImpl实现了Imessage与IChanel两个接口,所以这个子类可以是这两个接口的任意一个接口的实例,表示这两个接口实例之间是可以转换的。

在java程序中接口是不允许去继承父类的,所以接口绝不会是Object的子类,但是根据之前的分析可以发现MessageImpl是Object的子类,所以接口一定可以通过Object类接收

观察Object与接口进行转换:

 IMessage message = new MessageImpl();
 Object obj=message;
 IChanel chanel=(IChanel) obj;
 System.out.println(chanel.connect());

Object类对象可以接收任意的数据类型,包括基本数据类型、类对象、接口对象、数组

由于接口描述的是一个公共的定义标准,所以接口中所有的抽象方法的访问权限都是public ,abstract也可省略。

​ 接口中的方法访问权限也是public,不是default,所以覆写的时候只能够使用public。在实际开发中,实现接口的有可能是抽象类。一个抽象类可以实现多个接口, 而一个普通类只能够继承一个抽象类并且可以实现多个接口,但是要求先继承后实现。虽然接口无法去实现一个父类,但是一个接口可以通过extends实现若干个父接口,称为接口的多继承

    interface IMessage{

         String getInfo();
    }
    interface IChanel{
        boolean connect();//定义抽象方法
    }
    //extends在类继承上可以继承一个父类,但在接口上可以继承多个
    interface IService extends IMessage,IChanel{
    //接口多继承
            String service();
    }
    class IServiceImpl implements IService{
        @Override
        public String getInfo() {
            return "message";
        }

        @Override
        public boolean connect() {
            return false;
        }

        @Override
        public String service() {
            return "service";
        }
    }
    public class JavaDemo{
        public static void main(String []args){
            IService iService = new IServiceImpl();
            System.out.println(iService.getInfo()+iService.connect()+iService.service());
        }

    }

​ 在实际开发中,接口的使用往往有三种形式:

  • 进行标准设置
  • 表示一种操作的能力
  • 暴露远程方法视图,这个一般都是在RPC分布式开发中使用

接口的加强定义

​ 接口最早的主要特点是 全部由抽象方法和全局常量组成,但是如果项目设计不当就可能出现一种非常严重的问题。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8bvKZsxX-1661919535137)(C:\Users\Leo\Desktop\markdown\Java基础\img\2.png)]

该操作是属于结构设计不当的结构,最开始的时候不能保证接口设计的足够完善,为了方便子类的修改,往往不会让子类直接实现接口,而是中间追加一个过渡的抽象类。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jbqAcART-1661919535138)(C:\Users\Leo\Desktop\markdown\Java基础\img\3.png)]

​ 从JDK1.8之后,为了解决接口设计的缺陷,允许开发者在接口中定义普通方法。接口中的普通方法必须追加default声明。不过该操作属于挽救手段,不应该作为设计的首选。

​ 除了可以追加普通方法之外,接口中也可以定义static方法。

使用接口定义标准

​ 对接口而言,开发中最重要的应用就是进行标准的制定

interface IUSB{
        boolean check();
        void work();
    }
    class Computer{
        public void plugin(IUSB iusb){
            if(iusb.check()){
                iusb.work();
            }
        }
    }
    class KeyBoard implements IUSB{
        @Override
        public boolean check() {
            return true;
        }

        @Override
        public void work() {
            System.out.println("开始进行打字");
        }
    }
    public class JavaDemo{
        public static void main(String []args){
            Computer computer = new Computer();
            computer.plugin(new KeyBoard());
        }

    }

工厂设计模式

interface IFood{//定义一个食物标准
    void eat();

}
class Bread implements IFood{
    @Override
    public void eat() {
        System.out.println( "吃面包");
    }
}
public class JavaDemo{
        public static void main(String []args){
            IFood food=new Bread();
            food.eat();
        }

    }

此时程序会出现耦合的问题,造成耦合的最直接原因关键字new。以JVM的设计为例,JVM是java实现可移植性的关键,而jvm的核心原理:利用一个虚拟机来运行java程序,所有的程序并不与具体的操作系统有任何的联系,而是由JVM来进行匹配,所以得出结论:良好的设计应该避免耦合。

interface IFood{//定义一个食物标准
    void eat();

}
class Bread implements IFood{
    @Override
    public void eat() {
        System.out.println( "吃面包");
    }
}
class Milk implements IFood{
    @Override
    public void eat() {
        System.out.println( "喝牛奶");
    }
}
//建立工厂
class Factory{
    public static IFood getInstance(String className){
        if("bread".equals(className)){
            return new Bread();
        }
        else if("milk".equals(className)){
            return new Milk();
        }
        else {
            return null;
        }
    }
}
    public class JavaDemo{
        public static void main(String []args){
            IFood food=Factory.getInstance(args[0]);
            food.eat();
        }

    }

在本程序中,客户端程序类和IFood的子类没有任何的关联,所有的关联都是通过Factory类完成的,而在程序运行的时候可以通过初始化参数进行要使用的参数的定义。

需要进行子类扩充的话,改写Factory类即可。

代理设计模式(Proxy)

​ 代理设计模式的主要功能是可以帮助用户将所有的开发注意力只集中在核心业务功能的处理上。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iIv08hDA-1661919535139)(C:\Users\Leo\Desktop\markdown\Java基础\img\4.png)]

interface IEat{
    void get();

}
class EatReal implements IEat{
    @Override
    public void get() {
        System.out.println("[真实主题]得到美食 开始品尝");
    }
}
class EatProxy implements IEat{//服务代理
    private IEat eat;//为吃服务
    public EatProxy(IEat eat){
        //一定要有一个代理项
        this.eat=eat;
    }

    @Override
    public void get() {
        prepare();
        this.eat.get();
        clear();
    }

    public void prepare(){//准备过程
        System.out.println("[代理主题]1、购买食材");
        System.out.println("[代理主题]2、处理食材");
    }
    public void clear(){
        System.out.println("[代理主题]3、收拾碗筷");
    }
}

    public class JavaDemo{
        public static void main(String []args){
            EatProxy eat = new EatProxy(new EatReal());//这里最好用工厂设计模式实现
            eat.get();
        }

    }

代理模式的主要特点:一个接口提供有两个子类,其中一个是真实业务操作类,另一个是代理业务操作类没有代理业务操作,真实业务无法执行。

抽象类与接口的区别

​ 在实际开发中可以发现抽象类与接口的定义形式是非常相似的,尤其是从JDK1.8之后,接口中可以定义default或static方法,但两者依然有着明显的定义区别和使用区别。

序号区别抽象类接口
1定义关键字abstract class 抽象类名称{}interface 接口名称{}
2组成构造、普通方法、静态方法、全局常量、普通方法、static方法抽象方法、全局常量、普通方法、static方法
3权限可以使用各种权限定义只能够实现public
4子类使用extends 继承一个抽象类implements 实现多个接口
5两者关系抽象类可以实现若干接口接口不允许实现抽象类,但可以实现多个父接口
6使用1. 抽象类或接口必须实现子类
2.子类一定要覆写抽象类或接口中的全部抽象方
3.通过子类向上转型实现抽象类或接口对象实例化

​ 当抽象类和接口都可以使用的情况下,优先使用接口,因为接口可以避免子类的单继承。

​ 从正常的设计角度而言,也需要先从接口进行项目的整体设计。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H4rPWGAe-1661919535140)(C:\Users\Leo\Desktop\markdown\Java基础\img\1.png)]

抽象类与接口应用

​ 抽象类与接口是java中最为核心的概念,也是所有设计模式的综合体现。

  1. 定义一个ClassName接口,接口中只有一个抽象方法getClassName();设计一个类Company,该类实现接口ClassName中的getClassName方法,功能是获取该类的名称;编写应用程序使用Company类。

  2. 考虑一个绘图的标准,并且可以根据不同的图形来进行绘制。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pmiUyYHi-1661919535141)(C:\Users\Leo\Desktop\markdown\Java基础\img\5.png)]

import java.awt.*;

interface IGraphical{//定义绘图的标准
    void paint();//绘图

}
//三角形
class Triangle implements IGraphical{
    private Point[] a;//保存第一条边的坐标
    private Point[] b;//保存第二条边的坐标
    private Point[] c;//保存第三条边的坐标
    public Triangle(Point[] a,Point[] b,Point[]c){
        this.a=a;
        this.b=b;
        this.c=c;

    }
    @Override
    public void paint() {
        System.out.println("绘制第一条边,开始坐标:【"+this.a[0].getX()+","+this.a[0].getY()+"】"+",结束坐标:【"+this.a[1].getX()+","+this.a[1].getY()+"】");
        System.out.println("绘制第二条边,开始坐标:【"+this.b[0].getX()+","+this.b[0].getY()+"】"+",结束坐标:【"+this.b[1].getX()+","+this.b[1].getY()+"】");
        System.out.println("绘制第三条边,开始坐标:【"+this.c[0].getX()+","+this.c[0].getY()+"】"+",结束坐标:【"+this.c[1].getX()+","+this.c[1].getY()+"】");
    }
}
//圆形
class Circular implements IGraphical{
    private double radius;
    public Circular(double radius){
        this.radius=radius;
    }

    @Override
    public void paint() {
        System.out.println("以半径"+this.radius+"绘制圆形");
    }
}
//工厂
class Factory{
    public static IGraphical getInstance(String className ,int ...args){//使用可变参数传递
        if("Triangle".equalsIgnoreCase(className)){
            return new Triangle(new Point[]{new Point(args[0],args[1]),new Point(args[2],args[3])},
                    new Point[]{new Point(args[2],args[3]),new Point(args[4],args[5])},
                    new Point[]{new Point(args[4],args[5]),new Point(args[0],args[1])});
        }
        else if("Circular".equalsIgnoreCase(className)){
            return new Circular(args[0]);
        }
        else {
            return null;
        }
    }
}
    public class JavaDemo{
        public static void main(String []args){
                IGraphical iGraphical=Factory.getInstance("Triangle",1,2,3,4,5,6);
                iGraphical.paint();
        }

    }
  1. 定义类Shape,用来表示一般二维图形。Shape具有抽象方法area和perimeter,分别用来计算形状的面积和周长。试定义二维形状(如矩形,三角形,圆形,椭圆形等),这些类均为Shape的子类。

15、泛型

​ 泛型是从JDK1.5之后追加到Java语言中的,其只要目的是为了解决ClassCastException的问题,在进行对象的向下转型时都可能存在有安全隐患,而Java希望通过泛型可以慢慢解决掉此类问题。

泛型问题的引出

​ 现在假设说定义一个x与y坐标的处理类,并且在这个类之中允许开发者保存有三类数据:

  • 整型数据: x=10,y=20

  • 浮点型数据:x=10.1,y=20.9

  • 字符串型数据:x=东经120度,y=北纬30度

    在设计Point时候就需要去考虑具体的x和y的类型,这个类型要求可以保存以上三种数据,最原始的方法就是使用

Object类来进行定义,因为存在有如下的转型关系:

  • 整型数据:基本数据类型->包装为Integer类对象->自动向上转型为Object
  • 浮点型型数据:基本数据类型->包装为Double类对象->自动向上转型为Object
  • 字符型型数据:String类->自动向上转型为Object
class Point{
    private Object x;
    private Object y;

    public void setX(Object x) {
        this.x = x;
    }

    public void setY(Object y) {
        this.y = y;
    }

    public Object getX() {
        return x;
    }

    public Object getY() {
        return y;
    }
}
    public class JavaDemo{
        public static void main(String []args){
            Point point = new Point();
            point.setX(10);
            point.setY(20);
            int x=(Integer) point.getX();
            int y=(Integer) point.getY();
            System.out.println("x="+x+"、y="+y);
        }

    }

​ 本程序之所以可以解决当前的设计问题,主要原因在于,Object可以接收所有的数据类型,但是正因为如此本代码也会有严重的安全隐患

  point.setX(10);
  point.setY("北纬30度");
  int x=(Integer) point.getX();
  int y=(Integer) point.getY();

​ 此处程序明显出现了问题,在程序编译的时候实际上是不会有任何问题产生的,而程序执行的时候就会出现“ClassCastException”异常类型,所以本程序的设计是存在有安全隐患的。而这个安全隐患存在的依据在于使用了Object类型,因为Object可以涵盖的范围太广了。对于这样的错误如果可以直接出现在编译的过程中,那么就可避免运行时候的尴尬。

泛型基本定义

​ 如果要想避免出现"ClassCastException"最好的做法就是直接避免对象的强制转型,所以在JDK1.5之后提供有泛型技术,而泛型的本质在于,类中的属性或方法的参数和返回值可以由对象实例化的时候动态决定 。

​ 那么此时就需要在类的定义的时候明确的定位占位符(泛型标记)。

class Point<T>{//T type ,可以指定多个泛型
    private T x;
    private T y;

    public void setX(T x) {
        this.x = x;
    }

    public void setY( T y) {
        this.y = y;
    }

    public Object getX() {
        return x;
    }

    public Object getY() {
        return y;
    }
}
    public class JavaDemo{
        public static void main(String []args){
            Point point = new Point();
            point.setX(10);
            point.setY(20);
            int x=(Integer) point.getX();
            int  y=(Integer) point.getY();
            System.out.println("x="+x+"、y="+y);
        }

    }

​ 未指定泛型类型,编译时出现警告:

注: JavaDemo.java使用了未经检查或不安全的操作。
注: 有关详细信息, 请使用 -Xlint:unchecked 重新编译。

​ 关于泛型的默认类型:

  • 由于泛型是JDK1.5之后的产物,但在这之前已经有不少内置的程序类或者是接口广泛的应用到了项目开发之中,为了保证这些类或接口追加泛型之后,原始的程序类依然可以使用,所以如果不设置泛型类型时,自动将使用Object作为类型,以保证程序的正常类型,但是在编译时会出现警告。

    ​ 泛型定义完成后可以在实例化对象的时候进行泛型类型的设置:

Point<Integer> point = new Point<Integer>();

​ 泛型使用的注意点:

  • 泛型之中只允许设置引用类型,如果想要操作基本类型必须使用包装类。

  • 从JDK1.7开始,泛型对象实例化可以简化为:

Point<Integer> point = new Point<>();

使用泛型可以解决大部分的类对象的强制转换处理,这样的程序才是一个合理的设计。

泛型统配符

​ 虽然泛型帮助开发者解决了一系列的对象的强制转换带来的安全隐患,但是也带来了新的问题:引用传递处理。

class Message<T>{
   private T content;

    public void setContent(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }
}
    public class JavaDemo{
        public static void main(String []args){
            Message<String> message = new Message<>();
            message.setContent("message");
            fun(message);//引用传递

        }
        public static void fun (Message<String> temp){
            System.out.println(temp.getContent());
        }

    }

​ 这个时候问题也就出现了,而问题的关键在于fun()方法上,在实际开发中不可能只使用一种泛型类型,fun()方法应该能接收泛型类型的Message对象。这种情况的一种解决方案时不设置泛型类型,但是会出现安全隐患,这个时候如果不使用泛型,那么在方法之中就有可能对你的数据进行修改,所以此时需要寻找一种方案:可以接收所有的泛型类型,但是不能够修改里面的数据(允许获取),那么就需要通过通配符“?”来解决。

  public static void fun (Message<?> temp){
      //此时set方法不能使用
            temp.getContent();
            System.out.println(temp.getContent());
        }

​ 此时在fun()方法里面与由于采用了Message结合通配符的处理,所以可以接收所有的数据类型,并且不允许修改只允许获取数据。

​ 在“?”这个通配符的基础上实际上还提供了两类小的通配符:

  • ? extends 类:设置泛型的上限。例如:定义“?extends Number”:表示该泛型类型只允许设置Number及其子类。
  • ?super 类:设置泛型的下限。例如:定义”? super String ‘’:只能够使用String及其父类。

泛型上限:

class Message<T extends Number>{
   private T content;

    public void setContent(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }
}
    public class JavaDemo{
        public static void main(String []args){
            Message<String> message = new Message<>();//错误: 类型参数String不在类型变量T的范围内

            message.setContent("message");
            fun(message);

        }
        public static void fun (Message<? extends Number> temp){
            temp.getContent();
            System.out.println(temp.getContent());
        }

    }

泛型下限:

class Message<T >{
   private T content;

    public void setContent(T content) {
        this.content = content;
    }

    public T getContent() {
        return content;
    }
}
    public class JavaDemo{
        public static void main(String []args){
            Message<String> message = new Message<>();
            message.setContent("message");
            fun(message);

        }
        public static void fun (Message<? super String > temp){
            temp.getContent();
            System.out.println(temp.getContent());
        }

    }

​ 通配符是一个十分重要的概念,在以后学习系统类库的时候会遇到大量的通配符使用。

泛型接口

​ 泛型除了可以在类上定义之外,也可以直接在接口之中进行使用。

定义一个泛型接口:

interface IMessage<T >{
   String echo(T t);
}

​ 对于泛型接口的子类有两种实现方式:

  1. 在子类之中继续进行泛型定义:

    interface IMessage<T>{
       String echo(T t);
    }
    class MessageImpl<S> implements IMessage<S>{
        @Override
        public String echo(S s) {
            return "echo"+s;
        }
    }
        public class JavaDemo{
            public static void main(String[] args) {
                IMessage<String> message = new MessageImpl<String>();
                System.out.println(message.echo("s"));
            }
        }
    
  2. 在子类实现父接口的时候直接定义出具体的泛型类型:

    interface IMessage<T>{
       String echo(T t);
    }
    class MessageImpl implements IMessage<String>{
        @Override
        public String echo(String  s) {
            return "echo"+s;
        }
    }
        public class JavaDemo{
            public static void main(String[] args) {
                IMessage<String> message = new MessageImpl();
                System.out.println(message.echo("s"));
            }
        }
    

泛型方法

​ 在之前的程序类种可以发现在泛型类中如果将泛型标记写在了方法上,那么这样的方法就被称为泛型方法,但是需要注意的是泛型方法不是非要出现在泛型类中。即一个类中如果没有定义泛型也可以使用泛型方法。

 public class JavaDemo{
        public static void main(String[] args) {
            Integer[] num=fun(1,2,3);
            for (Integer integer : num) {
                System.out.println(integer);
            }
        }
        public static <T> T[] fun(T ...args){
            return args;
        }
    }

​ 在后期进行项目开发时,这种泛型方法很常见。可以利用泛型改进传统的工厂模式

import java.awt.image.ImageConsumer;

interface IMessage{
   void send (String str);
}
class MessageImpl implements IMessage{
    @Override
    public void send(String str) {
        System.out.println("发送消息"+str);
    }
}
class Factory {
    public static <T> T getInstance(String className){
        if("MessageImpl".equalsIgnoreCase(className)){
            return (T) new MessageImpl();
        }
        else{
            return null;
        }
    }
}
    public class JavaDemo{
        public static void main(String[] args) {
          IMessage iMessage=Factory.getInstance("MessageImpl");
          iMessage.send("message");
        }

    }

16、包的定义和使用

​ 在程序开发中,肯定要一直存在有包的概念,利用包可以实现类的包装,在以后的开发中,所有的类必须放在包里面。

包的定义

​ 对于项目而言,有其是现在的项目是不可能一个人开发完成的,开发过程中可能出现的类的重名问题(在操作系统中已经明确地定义了一个严格的要求:同一个目录中不允许存放有相同的程序类文件),所以为了进行类的管理,那么往往可以把程序文件放在不同的目录中,不同的目录之中是可以提供有相同文件的,而这个目录就称为包。

package cn.demo;//定义包,其中.表示分割子目录(子包)

​ 一旦程序开发之中出现有包,此时程序编译后的结果就必须将*.class的文件保存在指定的目录之中,但是如果手工建立则非常的麻烦,最好的做法是可以进行打包编译处理:

javac -d . Hello.java
  • “-d”:表示要生成的目录,而目录的结构就是package定义的结构。

  • “.”:表示在当前目录中生成程序类文件。

    ​ 在程序执行的时候一定要带着包执行类:java cn.demo.Hello 也就是说从此之后完整的类名称是**”包.类名称“**

包的导入

​ 利用包的定义实际上就可以将不同功能的类保存到不同的包中,但是这些类之间也一定存在彼此调用的关系,那么在这个时候就需要使用import语句来导入其他包中的程序类。

[问题] 关于public class 和class定义的区别:

  • public class: 类名称必须和文件名称保持一致,一个*.java中只允许有一个public class ,同时一个类如果要被其他的包所使用,那么这个类一定要定义为public class。

  • class: 类名称可以与文件名称不同,并且在一个*.java中可以提供有多个class定义,编译后将形成不同的*.class文件,但是这些类只能被本包所访问,外包无法访问。

  • 在实际开发中往往在一个*.java源代码中只会提供一个程序类,而这个类一般都用public class定义;程序类中包名称必须采用小写字母的形式定义。

    可以采用” 包.*“的形式进行导入,但注意并不是导入所有的类,而是根据程序需要将类导入到程序中。

    在实际开发中会遇到不同包类名相同的情况,这个时候可以通过”包名.类名”的形式进行定义。

静态导入

​ 加入说有一个类,这个类中的全部方法都是静态方法,按照原始做法需要导入程序所在的“包.类”,而后才可以通过类名称调用这些静态方法。

​ 从JDK1.5开始对于类中全部由静态方法提供的特殊类可以采用静态导入处理:

import static .....

​ 当使用了静态导入处理后就好比该方法是直接定义在主类中,可以由主方法直接调用。

生成jar文件

​ 当一个项目开发完成之后一定会存在大量的*.class文件,那么对于这些文件的管理往往可以利用一种压缩结构的形式来进行处理,而这样的文件在java中就称为jar文件,如果想要将程序打包为jar文件,可以直接利用jdk中提供jar命令完成。

​ 在最开始的时候如果想要知道jar的时候可以直接输入jar即可,而在JDK1.9之后为了统一化,所以需要使用“–help”查看相关的说明。

​ 下面通过程序的具体演示来实现jar的使用和配置:

  1. 定义一个程序类:

    package cn.mldn.util;
    public class Message{
        public String getContent(){
            return "message";
        }
    }
    
  2. 对程序进行编译和打包:

    • 对程序打包编译:
    javac -d . Message.java
    
    • 此时会形成cn的包,包里面有相应的子包和*.class文件,将其打包为mldn.jar
    jar -cvf mldn.jar cn
    

    ​ “-c”: 创建一个新的jar文件;

    ​ “-v”:得到一个详细输出

    ​ ”-f“: 设置要生成的jar文件名称

  3. 每一个*.java文件都是一个独立程序路径,如果想要在java的程序中使用此路径,则必须通过CLASSPATH进行配置:

    SET CLASSPATH=.;C:\Users\Leo\Desktop\JavaDemo\mldn.java
    
  4. 建立测试类,直接导入Message类并调用方法:

    package cn.mldn.test;
    import cn.mldn.util.*;
    public class TestMessage{
        public static void main(String[] args) {
            cn.mldn.util.Message message = new cn.mldn.util.Message();
            System.out.println(message.getContent());
        }
    }
    

    随后就可以正常编译TestMessage类并可以使用这个类。

    如果此时程序编译通过之后,但是由于CLASSPATH发生改变,类无法加载到了,则执行TestMessage类的时候将会出现如下的错误提示:

    Exception in thread "main" java.lang.NoClassDefFoundError: cn/mldn/util/Message
    

    出现这种错误只有一种情况:*.jar没有配置成功。

在JDK1.9之后出现了模块化操作

  • 在JDK1.9之前所有的历史版本之中实际上提供的是一个所有类的*.jar文件(rt.jar、tools.jar),在传统的开发之中只要启动了java的虚拟机,那么就需要加载这几十兆的类文件。
  • 在JDK1.9之后提供了模块化的设计,将原本很大的要加载的*.jar文件,变成了若干个模块文件,这样在启动的时候可以根据程序加载指定的模块(模块中有包),就可以加快启动速度。

系统常用类

​ Java语言发展至今一直有大量的支持类库,这些类库一般有两个方面组成:

  • Java自身提供的(除JDK提供的类库之外还会有一些标准)

  • 第三方厂商提供的Java类库

    ​ JDK中含有大量的类库,并且这些类库都是封装在不同的开发包中的,常见的包如下:

    • java.lang: 像String,Number,Object等类都在这个包中,jdk1.1之后自动导入。
    • java.lang.reflect: 反射机制处理包,所有的设计从此开始。
    • java.util: 工具类定义,包括数据结构的定义。
    • java.io: 输入输出开发程序包。
    • java.net: 网络程序开发程序包。
    • java.sql: 数据库编程开发包。
    • java.applet: Java最原始的使用形式,直接嵌套在网页上执行的程序类。现在的程序已经以application为主(有主方法的程序)
    • java.awt、java.swing:java图像界面开发包(GUI),其中awt属于重量级的组件,而swing是轻量级的组件。

17、UML图形

​ UML是统一的建模语言,本质就是利用图形化的形式来实现程序类关系的描述。

类图

​ 一般情况下如果想要对类结构进行描述,往往可以采用3层的架构:

类名称
属性
方法
  • 普通类名称往往直接书写即可,而如果是抽象类,往往使用斜体描述。为了更清楚地描述往往在抽象类上在加一个"abstract"。
  • 对于类中的属性可以使用”访问权限 属性名称:属性类型“的格式来进行定义,对于访问权限基本上重点考虑3个,public(+)、pretected(#)、privated(-)。
  • 类中方法采用的格式"访问权限 方法名称():返回值"

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jTreYFGV-1661919535142)(C:\Users\Leo\Desktop\markdown\Java基础\img\6.png)]

在实际的项目开发中一般会将程序的代码通过转换引擎变为图形显示(PowerDesigner)。

时序图

​ 时序图主要描述的是代码的执行流程。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V9elGHYM-1661919535143)(C:\Users\Leo\Desktop\markdown\Java基础\img\7.png)]

用例图

​ 用例图描述的是程序执行的分配。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NzA5QTTM-1661919535143)(C:\Users\Leo\Desktop\markdown\Java基础\img\8.png)]

​ 用例图一般出现在项目的设计中比较多。

18、单例设计模式

单例设计

​ 单例设计模式主要是一种控制实例化对象产生个数的设计操作。

​ 现有一个程序类的定义如下:

class Singleton{
    public void print(){
        System.out.println("single");
    }

}
public class JavaDemo{
        public static void main(String[] args) {
            Singleton singleton = new Singleton();//可以有无数个实例化对象
            singleton.print();
        }

    }

​ 若只允许提供一个实例化对象,那么首先应该控制的就是构造方法。

class Singleton{
    private static final Singleton INSTANCE=new Singleton();//内部调用构造方法
    private Singleton(){//构造方法私有化了

    }
    public static Singleton getInstance(){
        return INSTANCE;
    }
    public void print(){
        System.out.println("single");
    }


}
public class JavaDemo{
        public static void main(String[] args) {
            Singleton singleton =Singleton.getInstance();
            singleton.print();
          
        }

    }

​ 在很多情况下,一些类是不需要重复产生对象的,例如:如果一个程序类启动,那么肯定有一个类负责保存有一些程序加载的数据信息。

​ 对于单例设计模式分为两种:

  1. 饿汉式:(如上程序 在系统加载类的时候就会自动提供有Singleton类的实例化对象)

  2. 饿汉式:(在第一次是哟个的时候进行实例化处理)

    class Singleton{
        private static  Singleton instance;
        private Singleton(){//构造方法私有化了
    
        }
        public static Singleton getInstance(){
            if(instance==null){
                return new Singleton();
            }
            return instance;
        }
        public void print(){
            System.out.println("single");
        }
    
    
    }
    public class JavaDemo{
            public static void main(String[] args) {
                Singleton singleton =Singleton.getInstance();
                singleton.print();
    
            }
    
        }
    

【面试题】请编写Singleton程序,并说明其主要特点:

  • 代码如上,可以把懒汉式(后面需要考虑到线程同步问题)和饿汉式都写上。
  • 特点:构造方法私有化,类内部提供有static方法获取实例化对象,这样不管外部如何操作,外部都只有一个实例化对象

多例设计

​ 与单例设计模式对应的是多例设计模式,例如:如果现在要定义一个描述颜色的类,只能产生红,绿,蓝三种颜色的类。

import java.util.Collection;

class Color{//定义描述颜色的类
    public static final Color RED=new Color("红色");
    public static final Color GREEN=new Color("绿色");
    public static final Color BLUE=new Color("蓝色");
    private String title;
    private Color(String title){
        this.title=title;
    }
    public static Color getInstance(String color){
        switch (color){
            case "red": return RED;
            case "green": return GREEN;
            case "blue": return BLUE;
            default:return null;
        }
    }
    public String toString(){
        return this.title;
    }

}
public class JavaDemo{
        public static void main(String[] args) {
            Color red = Color.getInstance("red");
            System.out.println(red.toString());

        }

    }

19、枚举

​ 很多编程语言都有枚举的概念,但是Java直到jJDK1.5之后才提出了所谓枚举的概念。在实际开发之中枚举的主要作用是定义有限个数对象的一种结构(多例设计)。枚举就属于多例设计,其结构比枚举更加地简单。

定义枚举类

​ 从JDK1.5之后提供了enum的关键字,利用此关键字可以实现枚举的定义。

enum Color{//定义枚举类
    RED,GREEN,BLUE;//实例化对象
}
public class JavaDemo{
        public static void main(String[] args) {
            Color c=Color.RED;//获取实例化对象
            System.out.println(c);

        }

    } 

​ 多例设计和枚举设计虽然可以实现相同的功能,但是使用枚举可以在程序编译的时候就判断所使用的实例化对象是否存在。

​ 在进行枚举处理的时候还可以利用values()方法获取所有的枚举对象进行输出。

import sun.text.resources.ro.CollationData_ro;

enum Color{//定义枚举类
    RED,GREEN,BLUE;//实例化对象
}
public class JavaDemo{
        public static void main(String[] args) {
           for(Color c:Color.values()){
               System.out.println(c);
           }

        }

    }

​ 如果此时同样的功能需要通过多例设计来解决的话,那么就需要使用对象数组了。

​ 从JDK1.5之后追加了枚举结构之后,就可以在switch之中进行枚举项的判断:

import sun.text.resources.ro.CollationData_ro;

enum Color{//定义枚举类
    RED,GREEN,BLUE;//实例化对象
}
public class JavaDemo{
        public static void main(String[] args) {
          Color c =Color.RED;
          switch (c){//直接支持枚举
              case GREEN:
                  System.out.println("绿色");
                  break;
              case RED:
                  System.out.println("红色");
                  break;
              case BLUE:
                  System.out.println("蓝色");
                  break;
          }

        }

    }

​ 多例上是无法实现这种与switch直接连接的,多例想要实现就需要使用大量的if判断。

Enum类

​ 严格意义上来讲枚举并不是一种新的结构,它的本质相当于一个类这个类会默认继承Enum类。

​ Enum类的基本结构(java.lang)的基本定义:

public abstract class Enum<E extends Enum<E>>
extends Object
implements Comparable<E>, Serializable

​ 现在定义的枚举类的类型就是Enum中所使用E类型。观察Enum中定义的方法:

No方法名称类型
1protected Enum(String name, int ordinal)构造传入名字和序号
2public final String name()普通获得对象的名字
3public final int ordinal()普通获得对象序号

​ 观察Enum类的存在:

 for(Color c:Color.values()){
                   System.out.println(c.name()+" "+c.ordinal());
           }

[面试题] 请解释enum和Enum的区别:

  • enum是从JDK1.5之后提供的一个关键字,用于定义枚举类。
  • Enum是一个抽象类,所以使用enum关键字定义的类就默认继承了此类。

定义枚举结构

​ 枚举本身就是一种多例设计模式,那么既然是多例设计模式,在一个类中可以定义的结构是非常多的,例如构造方法、普通方法、属性等。这些内容在枚举类中依然可以直接定义,但是需要注意的是:枚举类中定义的构造方法不能够采用非私有化定义(public 无法使用)。

在枚举类中定义其他结构:


enum Color{//定义枚举类
    RED("红色"),GREEN("绿色"),BLUE("蓝色");//枚举类对象要写在首行
    private String title;
    private Color(String title){
        this.title=title;
    }
    public String toString(){
        return this.title;
    }
}
public class JavaDemo{
        public static void main(String[] args) {
           for(Color c:Color.values()){
               System.out.println(c);
           }

        }

    }

​ 除了这种基本的结构之外,在枚举类中也可以实现接口的继承。

枚举实现接口:

interface IMessage{
    void getMessage();
}
enum Color implements IMessage{//定义枚举类
    RED("红色"),GREEN("绿色"),BLUE("蓝色");//枚举类对象要写在首行
    private String title;
    private Color(String title){
        this.title=title;
    }
    public String toString(){
        return this.title;
    }
    public void  getMessage(){
        System.out.println(this.title);
    }
}
public class JavaDemo{
        public static void main(String[] args) {
          IMessage iMessage=Color.RED;
          iMessage.getMessage();

        }

    }

​ 在枚举类中可以直接定义抽象方法,并且要求每一个枚举对象都要独立覆写此方法。

枚举中定义抽象方法:


enum Color {//定义枚举类
    RED("红色"){
        public String getMessage(){
            return this.toString();
        }

    },GREEN("绿色"){
        public String getMessage(){
            return this.toString();
        }
    },BLUE("蓝色"){
        public String getMessage(){
            return this.toString();
        }
    };//枚举类对象要写在首行
    private String title;
    private Color(String title){
        this.title=title;
    }
    public String toString(){
        return this.title;
    }
    public abstract String  getMessage();
}
public class JavaDemo{
        public static void main(String[] args) {
            Color red = Color.RED;
            System.out.println(red.getMessage());

        }

    }

​ 枚举的定义非常灵活,在实际的使用中,枚举在更多情况下还是建议使用它的正确用法,就是定义几个范围。

枚举应用案例


enum Sex {
    MALE("男"),FEMALE("女");
    private String sex;
    private Sex(String sex) {
        this.sex=sex;
    }
    public String toString(){
        return this.sex;//注意不要忘记toString方法
    }
}
class Person{
    private String name;
    private int age;
    private Sex sex;
    public Person(String name,int age,Sex sex){
        this.name=name;
        this.age=age;
        this.sex=sex;
    }
    public String toString(){
        return "姓名:"+this.name+"、年龄"+this.age+"、性别:"+this.sex;
    }
}
public class JavaDemo{
        public static void main(String[] args) {
            System.out.println(new Person("李四",20,Sex.MALE));

        }

    }

20、异常的捕获和处理

​ java语言提供的最强大的支持就在于异常的处理操作上。

认识异常对程序的影响

​ 异常指的是导致程序中断的一种指令流。

  System.out.println("计算结果:"+(2/0));
Exception in thread "main" java.lang.ArithmeticException: / by zero

​ 在出现错误之后,整个程序不会按照既定的方式执行,而是中断了执行。为了保证程序出现非致命后程序依然可以正常完成,所以就需要有一个完善的异常处理机制。

处理异常

​ 在java之中如果想要异常的处理,可以使用:try、catch、finally这几个关键字。

其基本结构:

try{
    //可能出现异常语句
}[catch	(异常类型 异常对象){
    //异常处理
}catch	(异常类型 异常对象){
    //异常处理
}...][finally{
    //不管异常是否处理都要执行
}]

​ 在此格式中可以使用的组合:try…catch、try…catch…finally、try…finally

处理异常:

try{
                System.out.println("计算结果:"+(2/0));
            }catch (ArithmeticException e){
                System.out.println(e);
            }finally {
                System.out.println("不管是否出现异常我都会执行");
            }

此时输出的异常信息并不完整,可以使用异常类提供的printStackTrace()获取完整的异常信息

printStackTrace(e);

处理多个异常

try{
                int x=Integer.parseInt(args[0]);
                int y=Integer.parseInt(args[1]);
                System.out.println("计算结果:"+(x/y));
            }catch (ArithmeticException e){
                e.printStackTrace();
            }finally {
                System.out.println("不管是否出现异常我都会执行");
            }
            System.out.println("程序执行完毕");

​ 此时程序就有可能产生三类异常:

  • 【未处理】程序执行的时候没有输入初始化参数 java.lang.ArrayIndexOutOfBoundsException
  • 【未处理】输入的参数不是数字 java.lang.NumberFormatException
  • 【已处理】输入的被除数是零 java.lang.ArithmeticException

​ 即便有了异常处理语句,如果没有正确的捕获异常,那么异常也会导致中断(finally类的代码依然会执行)。在这种情况下就必须要进行多个异常的捕获。

try{
                int x=Integer.parseInt(args[0]);
                int y=Integer.parseInt(args[1]);
                System.out.println("计算结果:"+(x/y));
            }catch (ArithmeticException e){
                e.printStackTrace();
            }
            catch (ArrayIndexOutOfBoundsException e){
                e.printStackTrace();
            }
            catch (NumberFormatException e){
                e.printStackTrace();
            }finally {
                System.out.println("不管是否出现异常我都会执行");
            }
            System.out.println("程序执行完毕");

异常处理流程

​ 在进行异常处理的时候如果将所有可能已经明确知道要产生的异常都进行了捕获,虽然可以得到非常良好的代码结构,但是这种代码的编写是十分麻烦的,所以想要进行合理异常就必须清楚异常在产生之后程序到底做了哪些处理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JqlwGbJJ-1661919535144)(C:\Users\Leo\Desktop\markdown\Java基础\img\异常处理.jpg)]

程序之中可以处理的异常的最大类型就是Throwable,而打开Throwable可以观察此类中提供有两个子类:

  • Error: 此时程序还未执行出现的错误,开发者无法处理

  • Exception: 程序中出现的异常 开发者可以处理。真正在开发之中需要关注的就是error.

      异常产生的时候会产生异常的实例化对象,按照对象的引用原则,可以自动向父类对象转型,按照这样的逻辑所有的异常都可以使用Exception来处理。
    

简化异常处理:


public class JavaDemo{
        public static void main(String[] args) {
            try{
                int x=Integer.parseInt(args[0]);
                int y=Integer.parseInt(args[1]);
                System.out.println("计算结果:"+(x/y));
            }catch (Exception e){
                e.printStackTrace();
            }
            finally {
                System.out.println("不管是否出现异常我都会执行");
            }
            System.out.println("程序执行完毕");


        }

    }

​ 当不确定异常的类型的时候,这种处理方式是最方便的,但是如果这样也会产生一个问题,就是错误的描述信息不明确。

​ 在进行多个异常同时处理的时候要把捕获范围大的异常放在捕获异常小的范围之后。

throws关键字

​ 在定义方法的时候明确告诉使用者,这个方法可能会产生何种异常,那么就可以在方法的声明上使用throws关键字来进行异常类型的标注。

class MyMath{
    public static int div(int x,int y) throws Exception{
        return x/y;
    }
}
public class JavaDemo{
        public static void main(String[] args) {
            try{
                System.out.println(MyMath.div(3,1));
            }catch (Exception e){
                 e.printStackTrace();
            }


        }

    }

​ 主方法本身也是一个方法,那么实际上主方法也可以继续向上抛出。

在主方法上抛出异常:

class MyMath{
    public static int div(int x,int y) throws Exception{
        return x/y;
    }
}
public class JavaDemo{
        public static void main(String[] args) throws Exception{

            System.out.println(MyMath.div(1,0));
        }

    }

​ 如果主方法继续向上抛出异常,那么就表示异常交由JVM负责处理。

throw关键字

​ 与throws对应的是throw关键字,此关键字的主要作用在于表示手工进行异常的抛出,即此时将手工产生一个异常类实例化对象,并且进行异常的抛出处理。

try{
                throw new Exception("自定义异常");
            }catch (Exception e){
                e.printStackTrace();
            }

【区别】 throw和throws:

  • throw: 在代码块中使用,主要是手工进行异常对象的抛出。
  • throws: 是在方法对象上使用的,表示将此方法可能产生的异常明确告诉给调用处,由调用处处理。

异常处理模型

​ 目前已经学习完成了大部分地异常处理格式:try、catch、finally,throw、throws。这些关键字在开发之中往往会一起使用。

观察程序:定义一个可以实现除法的方法,要求(1、在进行数学计算开始与结束的时候进行信息提示;2、如果在进行计算的过程中产生了异常,则要交给调用处来处理)

class MyMath{
    //异常交给调用处处理则一定要在方法上使用throws
    public static int div(int x,int y) throws Exception{
        int temp=0;
        System.out.println("[start]");
        try {
            temp = x / y;
        }catch (Exception e){
            throw e;//向上抛出异常
        }finally {
            System.out.println("[end]");
        }

        return temp;
    }
}
public class JavaDemo{
        public static void main(String[] args){
            try{
                System.out.println(MyMath.div(10,2));
            }catch (Exception e){
                e.printStackTrace();
            }

        }

    }

​ 对于这样的操作实际上可以简化,省略掉catch和throw的操作。

RuntimeException

【面试题】解释RuntimeException 和Exception的区别?请列举处常见的RuntimeException类

  • RuntimeException是Exception的子类
  • RuntimeException标注的异常可以不需要进行强制性处理,而Exception异常必须强制性处理
  • 常见的Exception类:
    • NumberFormatException
    • ClassCastException
    • NullPointException

自定义异常类

​ 在JDK中提供由大量的异常类,但是在实际的开发中这些异常类可能并不够用,也不可能只抛出Exception,所以这个时候就需要考虑进行自定义异常类。

​ 对于自定义异常有两种实现方式:继承Exception和RuntimeException

class BigBangException extends Exception{
    public  BigBangException(String msg){
       super(msg);
    }
}
class Food{
    public static void eat(int num) throws BigBangException{
        if(num>10){
            throw new BigBangException("快要爆炸了...");

        }else{
            System.out.println("还能再吃点...");
        }
    }
}
public class JavaDemo{
        public static void main(String[] args) throws Exception{
            try{
                Food.eat(100);
            }catch (Exception e){
                e.printStackTrace();
            }

        }

    }

asert断言

​ 从JDK1.4之后开始追加有一个断言的功能,确定代码执行到某一行之后一定是期待的结果。在实际的开发中断言并不一定是准确的,也有可能出现偏差。但是这种偏差不应该影响程序的正常执行。

  public static void main(String[] args) throws Exception{
            int x=10;
            //中间会经过很多的x变量的操作步骤
            assert x==100:"x的内容不是100";
            System.out.println(x);
        }

​ 如果现在想要执行断言,则必须在程序执行的时候加入参数。

java -ea JavaDemo     #-ea启动断言

21、内部类

​ 虽然在类之中的基本组成就是成员属性与方法,但是在任何的语言里面结构也是允许嵌套的,所以在一个类的内部也可以定义其他的类,这样的类就成为内部类。

内部类的基本定义

​ 内部类肯定其本身是一个独立且完善的类结构,在一个类内部除了属性和方法之外还可以使用class关键字定义内部类。

class Outer{//外部类
    private String msg="msg";//私有成员属性
    public void fun(){//普通方法
        Inner inner = new Inner();
        inner.print();
    }
    class Inner{//在Outer内部定义内部类Inner
                public void print(){
                    System.out.println(Outer.this.msg);//Outer类中的属性
                }
    }

}
public class JavaDemo{
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.fun();
    }
}

​ 从整体的结构上来讲内部类的结构并不合理,所以内部类本身最大的缺陷在于破坏了程序的结构,但是破坏要有目的地破坏,它也有其优势所在。如果想要更好地观察内部类的结构,就可以将内部类拿到外边来。

class Outer{//外部类
    private String msg="msg";//私有成员属性
    public void fun(){//普通方法
        //思考五、需要将当前对象Outer传递到Inner类中

        Inner inner = new Inner(this);
        inner.print();
    }
    //思考一:msg属性如果想要外部访问需要提供有getter方法
    public String getMsg(){
        return this.msg;
    }

    }
class Inner {//在Outer内部定义内部类Inner
    //思考三:Inner这个类对象实例化的时候需要Outer类的引用
    private Outer outer;

    //思考四:应该通过Inner类的构造方法获取Outer类对象
    public Inner(Outer outer) {
        this.outer = outer;
    }

    public void print() {

        //思考二:如果想要外部类中的getter方法,那么一定要有Outer实例化对象
        System.out.println(this.outer.getMsg());//Outer类中的属性
    }
}
public class JavaDemo{
    public static void main(String[] args) {
        Outer outer = new Outer();
        outer.fun();
    }
}

​ 可以看到以上代码的目就是为了让Inner这个内部类可以访问Oute这个类中私有属性。由此可以知道内部类的优点是轻松地访问外部类的私有属性

内部类的相关说明

​ 以上程序定义的内部类,都是普通的内部类的形式,普通的类内部往往提供有属性和方法,需要注意的是,内部类虽然可以方便地访问外部类中的私有成员或私有方法,同时外部类也可以轻松访问内部类中的私有成员或者方法。使用了内部类之后,内部类和外部类之间的私有操作的访问就不再需要getter,setter以及其他的间接方式完成。

​ 但是需要注意的是,内部类本身也是一个类,虽然再大部分情况下内部类往往是被外部类包裹的,外部依然可以产生内部类的实例化对象,而此时内部类实例化对象的格式如下:

外部类.内部类 内部类对象=new 外部类().new 内部类()

​ 在内部类编译完成之后会自动形成一个”Outer I n n e r . c l a s s “类文件,其中 " Inner.class“类文件,其中" Inner.class类文件,其中""这个符号换到程序之中就变成了”.“,所以内部类的全称:“外部类.内部类”。内部类与外部类之间可以直接进行私有成员的访问,这样以来内部类如果提供了实例化对象,一定要保证外部类已经实例化了。

class Outer {//外部类
    private String message="message";
    class Inner{
        public void print(){
            System.out.println(Outer.this.message);
        }
    }
}
public class JavaDemo{
    //
    public static void main(String[] args) {
        Outer.Inner inner = new Outer().new Inner();
        inner.print();

    }
}

如果Inner类只允许Outer类来使用,那么在这样的情况下就可以使用private进行私有定义。

​ 在Java之中类作为最基础的结构体,实际上还有与之相似的抽象类或者接口。抽象类和接口中都可以定义内部结构。

定义内部类:

interface IChanel{
    void send(IMessage iMessage);
    interface IMessage{
        String getMessage();
    }
}
class ChanelImpl implements IChanel{
    @Override
    public void send(IMessage iMessage) {
        System.out.println(iMessage.getMessage());
    }
    //内部接口不强制实现
    class MessageImpl implements IMessage{
        @Override
        public String getMessage() {
            return "message";
        }
    }
}
public class JavaDemo{

    public static void main(String[] args) {
      IChanel chanel=new ChanelImpl();
      chanel.send(((ChanelImpl)chanel).new MessageImpl());//注意要进行转型处理
    }
}

继续观察一个内部抽象类,内部抽象类可以定义在普通类、抽象类、接口中。

interface IChanel{
    void send();
    abstract class AbstractMessage{
        public abstract String getMessage();
    }
}
class ChanelImpl implements IChanel{
    @Override
    public void send() {
        System.out.println(new MessageImpl().getMessage());
    }
    //内部接口不强制实现
    class MessageImpl extends AbstractMessage{
        @Override
        public String getMessage() {
            return "message";
        }
    }
}
public class JavaDemo{

    public static void main(String[] args) {
      IChanel chanel=new ChanelImpl();
      chanel.send();
    }
}

​ 内部类中还有一些更为有意思的结构,即:现在定义了一个接口,那么可以利用类实现该结构,在JDK1.8之后在接口中追加了static 方法可以不受到实例化对象的控制,可以利用此特性来完成功能。

interface IChanel{
    void send();
    class ChanelImpl implements IChanel{
        @Override
        public void send() {
            System.out.println("message");
        }
    }
    public static IChanel getInstance(){
        return new ChanelImpl();
    }
}
public class JavaDemo{

    public static void main(String[] args) {
      IChanel chanel=IChanel.getInstance();
      chanel.send();
    }
}

内部类是一种非常灵活的结构,只要你的语法满足了,各种需求都可以帮你实现

static 定义内部类

​ 如果在内部类上使用了static定义,那么这个内部类就变成了”外部类“。static定义的都是独立于类的结构,所以该类结构就相当于一个独立的程序类了。需要注意的是static定义 只爱不管是类还是方法只能访问static 成员,所以static定义的内部类只能访问外部类的static成员或方法。

class Outer{
    private static final String MSG="message";
    static class Inner{
        public void print(){
            System.out.println(Outer.MSG);
        }
    }
}
public class JavaDemo{

    public static void main(String[] args) {

      Outer.Inner inner=new Outer.Inner();
      inner.print();

    }
}

实例化格式:外部类.内部类=new 外部类.内部类()

在开发之中如果遇到类名称提供有".",首先应该立即想到这是一个内部类的结构,如果可以直接实例化,则应该认识到这是static定义的内部类。

static定义内部类的形式并不常用,static定义内部接口的形式最常用

使用static定义内部接口

interface IMessageWrap{//消息包装
    static interface IMessage{
        public String getContent();
    }
    static interface IChanel{
        public boolean connect();//消息的发送通道
    }
    public static void send(IMessage msg,IChanel chanel){
        if(chanel.connect()){
            System.out.println(msg.getContent());
        }else{
            System.out.println("消息通道无法建立,消息发送失败");
        }
    }

}
class DefaultMessage implements IMessageWrap.IMessage{
    @Override
    public String getContent() {
        return "message";
    }
}
class NetChanel implements IMessageWrap.IChanel{
    @Override
    public boolean connect() {
        return true;
    }
}
public class JavaDemo{

    public static void main(String[] args) {

     IMessageWrap.send(new DefaultMessage(),new NetChanel());

    }
}

之所以用static定义内部接口,主要是这些操作属于一组相关的定义,有了外部接口之后,可以更加明确的描述一些接口的主要功能。

方法中定义内部类

​ 内部类可以在任意的结构中,这就包括了:类中,方法中,代码块中,但是在实际开发中在方法中定义内部类的形式过多。

class Outer{
    private String msg="msg";
    public void fun(long time){
        class Inner{
            public void print(){
                System.out.println(Outer.this.msg);
                System.out.println(time);
            }
        }
        new Inner().print();
    }
}
public class JavaDemo{

    public static void main(String[] args) {

        Outer outer = new Outer();
        outer.fun(1000L);


    }
}

可以发现此时内部类可以直接访问外部类的私有参数,也可以直接访问方法中的参数。对于方法中参数的直接访问是从JDK1.8开始的支持的。在此之前如果方法中的内部类想要访问方法中的参数,要在参数前加final关键字。之所以取消这样的限制是为了拓展的函数式编程准备的功能。

匿名内部类

interface IMessage{
    public void sent(String str);
}
public class JavaDemo{

    public static void main(String[] args) {

       IMessage iMessage=new IMessage(){
           public void sent(String str){
               System.out.println(str);
           }

       };
        iMessage.sent("内部类");

    }
}

有些时候为了更加方便地体现出匿名内部类使用,往往可以利用静态方法做一个内部类。

interface IMessage{
    public void sent(String str);
    public static IMessage getInstance(){
        return new IMessage() {
            @Override
            public void sent(String str) {
                System.out.println(str);
            }
        };
    }
}
public class JavaDemo {

    public static void main(String[] args) {

        IMessage.getInstance().sent("内部类");
    }
}

与内部类相比,匿名内部类只是一个没有名字的只能够使用一次的,并且结构固定的一个子类操作。

22、函数式编程

Lamda表达式

​ 从JDK1.8开始为了简化使用者进行代码的开发,专门提供有Lambda表达式的支持,利用此操作形式可以实现函数式的编程,对于函数式编程比较著名的语言:haskell、Scale,利用函数式编程可以避免掉面向对象编程之中的一些繁琐的处理问题。

传统开发:

interface IMessage{
    public void send(String str);

}
public class JavaDemo {

    public static void main(String[] args) {

       IMessage message=new IMessage() {
           @Override
           public void send(String str) {
               System.out.println(str);
           }
       };
       message.send("message");
    }
}

在这样一个程序里面,实际上核心的功能只有一行语句“System.out.println(“message”),但是依然需要按照完整的面向对象给出的设计结构进行开发。于是这些问题随着技术的发展越来越突出。

使用Lambda表达式:

@FunctionalInterface //该注解标注这是一个函数式接口。
interface IMessage{
    public void send(String str);

}
public class JavaDemo {

    public static void main(String[] args) {

       IMessage message=(str)->{
           System.out.println(str);
       }
       ;
       message.send("message");
    }
}

​ Lambda表达式如果想要使用,那么必须有一个重要的实现要求:SAM(Single Abstract Method)只有一个抽象方法。以上面的IMessage接口为例,在这个接口里发现只提供了一个send方法,除此之外没有任何的其他方法定义,这样的接口就被称为函数式接口。而只有函数式接口才能够被Lambda表达式所使用。

​ 对于Lambda表达式而言,提供有如下的格式:

  • 方法没有参数:()->{}
  • 方法有参数:(参数)->{}
  • 只有一行语句返回:(参数)->返回语句

方法引用

引用类型最大的特点是可以进行内存的指向处理。但是在传统的开发中一直所使用的只有对象引用操作,从JDK1.8之后也提供有方法的引用。即:不同的方法名称可以描述同一个方法。

​ 在java中方法引用提供有四种形式:

  • 引用静态方法: 类名称::static 方法名称
  • 引用某个实例对象的方法: 实例化对象::普通方法
  • 引用特定类型的方法: 特定类::普通方法
  • 引用构造方法: 类名称::new
  1. 引用静态方法

String.valueOf

@FunctionalInterface  //函数接口
interface IFuntion<P,R>{
    public R change(P p);

}
public class JavaDemo {

    public static void main(String[] args) {
        IFuntion<Integer, String> fun = String::valueOf;
        System.out.println(fun.change(100));

    }
}

利用方法引用这一概念可以为一个方法定义多个名字,但要求必须是函数式接口。

  1. 引用实例化对象方法

public String toUpperCase()(有实例化对象提供的情况下才能调用)

@FunctionalInterface  //函数接口
interface IFuntion<R>{
    public R upper();

}
public class JavaDemo {

    public static void main(String[] args) {
        IFuntion<String> fun = "upper"::toUpperCase;
        System.out.println(fun.upper());

    }
}
  1. 引用特定类型的方法

public int compareTo(String anotherString)

这是一个普通方法,如果想要引用普通方法,则往往都需要实例化对象,但是如果不想给出实例化对象,只是想引用这个方法,则既可以使用特定类类进行引用处理。

    @FunctionalInterface  //函数接口
    interface IFuntion<P>{
        public int compareTo(P p1,P p2);

    }
    public class JavaDemo {

        public static void main(String[] args) {
            IFuntion<String> fun = String::compareTo;
            System.out.println(fun.compareTo("A","a"));

        }
    }
  1. 引用构造方法
   class Person{
        private String name;
        private int age;
        public Person(String name,int age){
            this.name=name;
            this.age=age;
        }
        public String toString(){
            return this.name+","+this.age;
       }
   }
    @FunctionalInterface  //函数接口
    interface IFuntion<R>{
        public R create(String p1,int p2);

    }
    public class JavaDemo {

        public static void main(String[] args) {
            IFuntion<Person> fun = Person::new;
            System.out.println(fun.create("小王",22));

        }
    }

​ 提供方法引用的概念更多情况下也只是弥补了对于引用的支持功能。

内建函数式接口

​ 在JDK1.8中提供有Lambda表达式,也提供有方法引用,但是如果由开发者自己定义函数式接口,往往使用”@FunctionalInterface“注解进行大量声明,于是很多情况下为了方便可以之际额使用系统中提供的函数式接口。

​ 在系统之中专门有一个java.util.function的开发包,里面可以直接使用函数式接口,其核心接口如下:

  1. 功能型函数式接口:
@FunctionalInterface
public interface Function<T,R>{
    public R apply(	T t)
}

//符合的方法例如
public boolean startsWith(String str)
  1. 消费型函数式接口:只能够进行数据的返回,而没有任何的返回

    @FunctionalInterface
    public interface Consumer<T>{
        public void accept(	T t)
    }
    
    //符合的方法例如
    System.out.println()
    
  2. 供给式函数式接口:

    @FunctionalInterface
    public interface Supplier<T>{
        public T get();
    }
    
    //符合的方法例如
    public String toLowerCase()
    
  3. 断言型函数式接口:进行判断处理

    @FunctionalInterface
    public interface Predicate<T>{
        public boolean	test(T t)
    }
    
    //符合的方法例如
    compareToIgnoreCase()
    

23、链表的定义和使用

​ 链表的本质是一个动态的数组,它可以实现若干对象的存储。

链表使用简介

​ 传统对象数组的开发操作依赖于脚标(索引)的控制,如果想要实现内容的动态维护,那么难度太高了,而且复杂度攀升,对于一成不变的数据可以使用对象数组来实现,但是对于随时可能变化的数据,就需要一个 可以动态扩充的对象数组。

链表的实质是利用引用的逻辑关系来实现类似于数组处理操作

链表的结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R223ImKc-1661919535146)(C:\Users\Leo\Desktop\markdown\Java基础\img\link.jpg)]

数据增加

​ 在进行链表操作的过程中,为了避免转型的异常应该使用泛型,同时也应该设计一个链表的标准接口。同时具体实现该接口时还应该通过Node类做出节点的关系描述。

interface ILink<E>{//设置泛型避免安全隐患
    public void add(E e);
}
class LinkImpl<E> implements ILink<E>{
    //-------------Node保存数据的节点以及关系---------------
    private class Node{
		//没有设置set和get方法时因为外部类可以直接访问内部类的私有成员
        private E data;
        private Node next;
        public Node(E data){
            this.data=data;
        }
        public void addNode(Node newNode){//保存新的数据
            if(this.next==null){
                this.next=newNode;//保存当前节点
            }else{
                this.next.addNode(newNode);
            }

        }
    }
    //-------------以下为Link类中定义的成员-----------------
    private Node root;
    //-------------以下为Link类中定义的方法-----------------

    @Override
    public void add(E e) {
        if(e==null){
            return ;//数据为空
        }
        Node node = new Node(e);
        if(this.root==null){
            this.root=node;//第一个节点作为根节点
        }else {//根节点存在
            this.root.addNode(node);//将新节点保存到合适的位置

        }


    }
}
    public class JavaDemo {

        public static void main(String[] args) {

            LinkImpl<String> link = new LinkImpl<>();
            link.add("a");
            link.add("b");

        }
    }

获取集合个数

  1. ILink中增加

    public int size()
    
  2. LinkImpl实现子类中追加统计属性private int count

  3. add()方法执行后this.count++;

空集合判断

  1. ILink中增加

    public boolean isEmpty()
    
  2. LinkImpl覆写 return this.count==0或者this.root=null

返回集合数据

​ 链表本身就属于一个动态对象数组,既然是一个数组,就应该可以把所有的数据以数组的形式返回来。

  1. ILink中增加

    public Object[] toArray();
    
  2. LinkImpl中追加两个属性

    private int foot;//描述的是数组操作的脚标
    private Object[] returnDate;//返回的数据保存
    
  3. 在Node类中根据递归获取数据

    public void toArray(){//以数组形式返回数据
                LinkImpl.this.returnDate[LinkImpl.this.foot++]=this.data;
                if(this.next!=null){
                    this.next.toArray();
                }
    
            }
    
  4. LinkImpl中覆写方法 获取数据时一定要先进行非空判断

    public Object[] toArray() {
            if(this.isEmpty()){
                return null;//没有数据
            }
            this.foot=0;//脚标清零
            this.returnDate=new Object[this.count];//根据已有的长度开辟数组
            //利用Node类进行递归数据获取
            this.root.toArray();
            return this.returnDate;
    
        }
    

根据索引取得数据

  1. 在ILink中增加

    public E get(int Index)
    
  2. 在Node 中增加根据索引获取数据的方法

     public E get(int index){
                if(LinkImpl.this.count==index){
                    return this.data;
                }else{
                    return this.next.get(index);
                }
            }
    
  3. LinkImpl中实现方法

    public E get(int index) {
            if(index>=this.count){
                return null;//索引应该在指定的范围内
            }
            this.foot=0;//重置索引的下标
    
            return  this.root.get(index);
        }
    

修改指定索引数据

  1. 在ILink中增加方法

    public void set(int index,E data);
    
  2. 在Node中增加方法

     public void addNode(Node newNode){
                if(this.next==null){
                    this.next=newNode;//保存当前节点
                }else{
                    this.next.addNode(newNode);
                }
            }
    
  3. LinkImpl中覆写方法

     public E get(int index) {
            if(index>=this.count){
                return null;//索引应该在指定的范围内
            }
            this.foot=0;//重置索引的下标
    
            return  this.root.get(index);
        }
    
    

判断数据是否存在

  1. ILink中增加方法

    public boolean contains(E data)	
    
  2. 在Node类中进行判断

    public boolean contains(E data){
                if(data.equals(this.data)){ //!!!!!注意这里把data放在前面进行比较能够避免链表中有空数据而产生的错误
                    return true;
                }else{
                    if(this.next==null){
                        return false;
                    }
                    else{
                        return this.next.contains(data);
                    }
                }
            }
    
  3. LinkImpl实现类

      public boolean contains(E data) {
            if(data==null){
                return false;//判断数据是否为空
            }
            return root.contains(data);
        }
    

数据删除

要注意删除的节点是root结点还是中间结点

  1. ILink中增加方法

    public void remove(E data)
    
  2. Node中增加方法

     public void remove(Node previous,E data){
                if(this.data.equals(data)){
                    previous.next=this.next;//空出当前节点
                }
                else{
                    if(this.next!=null){//有后续节点
                        this.next.remove(this,data);
                    }
                }
            }
    
  3. LinkImpl中覆写方法

    public void remove(Node previous,E data){
                if(this.data.equals(data)){
                    previous.next=this.next;//空出当前节点
                }
                else{
                    if(this.next!=null){//有后续节点
                        this.next.remove(this,data);
                    }
                }
            }
    

清空链表

  1. ILink中增加方法

    public void clean() 
    
  2. LinkImpl覆写方法

    public void clean() {
            this.root=null;
            this.count=0;
        }
    

链表相关操作完整代码



interface ILink<E>{//设置泛型避免安全隐患
    public void add(E e);//增加数据
    public int size();//获取数据个数
    public boolean isEmpty();//集合空集合判断
    public Object[] toArray();//将集合元素以数组的形式返回
    public E get(int Index);//根据索引获取数据
    public void set(int index,E data);//修改指定索引数据
    public boolean contains(E data);//判断数据是否存在
    public void remove(E data);//删除数据
    public void clean();//清空链表
}
class LinkImpl<E> implements ILink<E>{
    //-------------Node保存数据的节点以及关系---------------
    private class Node{
        private E data;
        private Node next;
        public Node(E data){
            this.data=data;
        }
        //保存新的数据
        public void addNode(Node newNode){
            if(this.next==null){
                this.next=newNode;//保存当前节点
            }else{
                this.next.addNode(newNode);
            }
        }
        //以数组形式返回数据
        public void toArray(){
            LinkImpl.this.returnDate[LinkImpl.this.foot++]=this.data;
            if(this.next!=null){
                this.next.toArray();
            }
        }
        //根据索引获取数据
        public E get(int index){
            if(LinkImpl.this.foot++==index){
                return this.data;
            }else{
                return this.next.get(index);
            }
        }
        //更改指定索引数据
        public void setNode(int index,E data){
            if (LinkImpl.this.foot++==index){
                this.data=data;
            }
            else{
                this.next.setNode(index,data);
            }
        }
        //判断数据是否存在
        public boolean contains(E data){
            if(data.equals(this.data)){
                return true;
            }else{
                if(this.next==null){
                    return false;
                }
                else{
                    return this.next.contains(data);
                }
            }
        }
        //数据删除
        public void remove(Node previous,E data){
            if(this.data.equals(data)){
                previous.next=this.next;//空出当前节点
            }
            else{
                if(this.next!=null){//有后续节点
                    this.next.remove(this,data);
                }
            }
        }
    }
    //-------------以下为Link类中定义的成员-----------------
    private Node root;
    private int count;//保存数据个数
    private int foot;//描述的是数组操作的脚标
    private Object[] returnDate;//返回的数据保存
    //-------------以下为Link类中定义的方法-----------------

    @Override
    public void add(E e) {
        if(e==null){
            return ;//数据为空
        }
        Node node = new Node(e);
        if(this.root==null){
            this.root=node;//第一个节点作为根节点
        }else {//根节点存在
            this.root.addNode(node);//将新节点保存到合适的位置
        }
        this.count++;
    }

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

    @Override
    public boolean isEmpty() {
        return this.count==0;
    }

    @Override
    public Object[] toArray() {
        if(this.isEmpty()){
            return null;//没有数据
        }
        this.foot=0;//脚标清零
        this.returnDate=new Object[this.count];//根据已有的长度开辟数组
        //利用Node类进行递归数据获取
        this.root.toArray();
        return this.returnDate;
    }

    @Override
    public E get(int index) {
        if(index>=this.count){
            return null;//索引应该在指定的范围内
        }
        this.foot=0;//重置索引的下标
        return  this.root.get(index);
    }

    @Override
    public void set(int index, E data) {
        if(index>=this.count){//判断索引
            return;
        }
        this.foot=0;//重置
        this.root.setNode(index,data);
    }

    @Override
    public boolean contains(E data) {
        if(data==null){
            return false;//判断数据是否为空
        }
        return root.contains(data);
    }

    @Override
    public void remove(E data) {
        if(this.contains(data)){
            if(this.root.data.equals(data)){
                this.root=this.root.next;//根节点为要删除的节点
            }else{//删除的节点为中间节点
                this.root.remove(this.root,data);

            }
            this.count--;
        }
    }

    @Override
    public void clean() {
        this.root=null;
        this.count=0;
    }
}
    public class JavaDemo {

        public static void main(String[] args) {

            LinkImpl<String> link = new LinkImpl<>();
            //增加数组
            link.add("a");
            link.add("b");
            link.add("c");
            //链表的长度
            System.out.println(link.size());
            //返回数据
            Object[] result=link.toArray();
            for (Object obj : result) {
                System.out.println(obj);
            }
            //根据索引查询数据
            System.out.println(link.get(0));
            //根据索引修改数据
//            link.set(0,"hhahahha");
//            System.out.println(link.get(0));
            //判断数据是否存在
            System.out.println(link.contains("b"));
            //删除数据
            link.remove("b");
            System.out.println(link.get(1));
        }
    }

综合实战:宠物商城

​ 假设有一个宠物商店,里面可以出售各种宠物,要求可以实现宠物的上架处理、下架处理、也可以根据关键字查询出宠物的信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WNqRfZhd-1661919535146)(C:\Users\Leo\Desktop\markdown\Java基础\img\宠物商城.jpg)]

interface ILink<E>{//设置泛型避免安全隐患
    public void add(E e);//增加数据
    public int size();//获取数据个数
    public boolean isEmpty();//集合空集合判断
    public Object[] toArray();//将集合元素以数组的形式返回
    public E get(int Index);//根据索引获取数据
    public void set(int index,E data);//修改指定索引数据
    public boolean contains(E data);//判断数据是否存在
    public void remove(E data);//删除数据
    public void clean();//清空链表
}
class LinkImpl<E> implements ILink<E>{
    //-------------Node保存数据的节点以及关系---------------
    private class Node{
        private E data;
        private Node next;
        public Node(E data){
            this.data=data;
        }
        //保存新的数据
        public void addNode(Node newNode){
            if(this.next==null){
                this.next=newNode;//保存当前节点
            }else{
                this.next.addNode(newNode);
            }
        }
        //以数组形式返回数据
        public void toArray(){
            LinkImpl.this.returnDate[LinkImpl.this.foot++]=this.data;
            if(this.next!=null){
                this.next.toArray();
            }
        }
        //根据索引获取数据
        public E get(int index){
            if(LinkImpl.this.foot++==index){
                return this.data;
            }else{
                return this.next.get(index);
            }
        }
        //更改指定索引数据
        public void setNode(int index,E data){
            if (LinkImpl.this.foot++==index){
                this.data=data;
            }
            else{
                this.next.setNode(index,data);
            }
        }
        //判断数据是否存在
        public boolean contains(E data){
            if(data.equals(this.data)){
                return true;
            }else{
                if(this.next==null){
                    return false;
                }
                else{
                    return this.next.contains(data);
                }
            }
        }
        //数据删除
        public void remove(Node previous,E data){
            if(this.data.equals(data)){
                previous.next=this.next;//空出当前节点
            }
            else{
                if(this.next!=null){//有后续节点
                    this.next.remove(this,data);
                }
            }
        }
    }
    //-------------以下为Link类中定义的成员-----------------
    private Node root;
    private int count;//保存数据个数
    private int foot;//描述的是数组操作的脚标
    private Object[] returnDate;//返回的数据保存
    //-------------以下为Link类中定义的方法-----------------

    @Override
    public void add(E e) {
        if(e==null){
            return ;//数据为空
        }
        Node node = new Node(e);
        if(this.root==null){
            this.root=node;//第一个节点作为根节点
        }else {//根节点存在
            this.root.addNode(node);//将新节点保存到合适的位置
        }
        this.count++;
    }

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

    @Override
    public boolean isEmpty() {
        return this.count==0;
    }

    @Override
    public Object[] toArray() {
        if(this.isEmpty()){
            return null;//没有数据
        }
        this.foot=0;//脚标清零
        this.returnDate=new Object[this.count];//根据已有的长度开辟数组
        //利用Node类进行递归数据获取
        this.root.toArray();
        return this.returnDate;
    }

    @Override
    public E get(int index) {
        if(index>=this.count){
            return null;//索引应该在指定的范围内
        }
        this.foot=0;//重置索引的下标
        return  this.root.get(index);
    }

    @Override
    public void set(int index, E data) {
        if(index>=this.count){//判断索引
            return;
        }
        this.foot=0;//重置
        this.root.setNode(index,data);
    }

    @Override
    public boolean contains(E data) {
        if(data==null){
            return false;//判断数据是否为空
        }
        return root.contains(data);
    }

    @Override
    public void remove(E data) {
        if(this.contains(data)){
            if(this.root.data.equals(data)){
                this.root=this.root.next;//根节点为要删除的节点
            }else{//删除的节点为中间节点
                this.root.remove(this.root,data);

            }
            this.count--;
        }
    }

    @Override
    public void clean() {
        this.root=null;
        this.count=0;
    }
}
interface Pet{//1.定义宠物标准
    public String getName();//获得宠物名字
    public String getColor();//获得宠物颜色
}
class PetShop{//2.宠物商店 以宠物的标准为主
    private ILink<Pet> allPets=new LinkImpl<Pet>();//保存多个宠物
    //追加宠物 宠物上架
    public void add(Pet pet){
        this.allPets.add(pet);
    }
    //删除宠物 宠物下架
    public void delete(Pet pet){
        this.allPets.remove(pet);
    }
    //查询宠物
    public ILink<Pet> search (String keyword){
        ILink<Pet> searchResult = new LinkImpl<>();//保存查询的数据
        Object[] result = this.allPets.toArray();//查询所有的结果
        for (Object obj : result) {
            Pet pet=(Pet)obj;
            if(pet.getName().contains(keyword)||pet.getColor().contains(keyword)){
                searchResult.add(pet);//保存查询结果
            }
        }
        return searchResult;
    }
}
//3.根据宠物标准获得宠物信息
class Cat implements Pet{
    private String name;
    private String color;
    public Cat(String name,String color){
        this.name=name;
        this.color=color;
    }
    //覆写equals方法
    public boolean equals(Object obj){
        if(obj==null){
            return false;
        }
        if(!(obj instanceof Cat)){
            return false;
        }
        if(this==obj){
            return true;
        }
        Cat cat=(Cat)obj;
        return this.name.equals(cat.name)&&this.color.equals(cat.color);
    }
    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }

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

    @Override
    public String getColor() {
        return this.color;
    }
}
class Dog implements Pet{
    private String name;
    private String color;
    public Dog(String name,String color){
        this.name=name;
        this.color=color;
    }
    //覆写equals方法   该方法必须覆写,不覆写的话无法进行删除
    public boolean equals(Object obj){
        if(obj==null){
            return false;
        }
        if(!(obj instanceof Cat)){
            return false;
        }
        if(this==obj){
            return true;
        }
        Dog dog=(Dog)obj;
        return this.name.equals(dog.name)&&this.color.equals(dog.color);
    }
    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", color='" + color + '\'' +
                '}';
    }

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

    @Override
    public String getColor() {
        return this.color;
    }
}
public class JavaDemo{
    public static void main(String[] args) {
        PetShop shop=new PetShop();
        shop.add(new Dog("黄狗","黄色"));
        shop.add(new Dog("黄猫","黄色"));
        Object[] result = shop.search("黄").toArray();
        for (Object obj : result) {
            System.out.println(obj);

        }
    }
}

​ 所有的程序开发都是以接口为标准进行的,这样在进行后期程序处理的时候就可以非常的灵活。只要符合标准的对象就都可以保存。

综合实战:超市购物车

​ 使用面向对象概念表示出下面的生活场景:小明去超时买东西,所有买到的东西都放在购物车中,最后到收银台一起结账。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v4k48uv6-1661919535147)(C:\Users\Leo\Desktop\markdown\Java基础\img\超市1.jpg)]

interface ILink<E>{...}
classs LinkImpl<E> implements ILink<E>{...}//代码同上
interface IGoods{//1.定义商品标准
    public String getName();
    public double getPrice();
}
interface IShopCar{//2.定义购物车标准
    public void add(IGoods good);
    public void delete(IGoods good);
    public Object[] getAll();

}
class ShopCarImpl implements IShopCar{//3.实现购物车
    private ILink<IGoods> allGoods=new LinkImpl<IGoods>();
    @Override
    public void add(IGoods good) {
            this.allGoods.add(good);
    }

    @Override
    public void delete(IGoods good) {
        this.allGoods.remove(good);
    }

    @Override
    public Object[] getAll() {
        return this.allGoods.toArray()  ;
    }
}

//4.收银台
class Crashier{
    private IShopCar shopCar;
    public Crashier(IShopCar shopCar){
        this.shopCar=shopCar;
    }
    public double getAllPrice(){//获得总价
        double sum=0.0;
        Object[] all = this.shopCar.getAll();
        for (Object obj : all) {
            IGoods good=(IGoods) obj;
            sum+=good.getPrice();
        }
        return sum;
    }
    public int getAllCount(){//获得总数
        return this.shopCar.getAll().length;
    }

}
//5.实现商品
class Book implements IGoods{
    private String name;
    private double price;
    public Book(String name,double price){
        this.name=name;
        this.price=price;
    }
    //覆写equals方法
    public boolean equals(Object obj){
        if(obj==null){
            return false;
        }
        if(!(obj instanceof Book)){
            return false;
        }
        if(this==obj){
            return true;
        }
        Book book=(Book)obj;
        return this.name.equals(book.name)&&this.price==book.price;
    }
    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", price='" + price + '\'' +
                '}';
    }

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

    @Override
    public double getPrice() {
        return this.price;
    }
}

public class JavaDemo{
    public static void main(String[] args) {
        IShopCar shopCar = new ShopCarImpl();
        shopCar.add(new Book("book1",999));
        shopCar.add(new Book("book2",99));
        Crashier crashier = new Crashier(shopCar);
        Object[] all = shopCar.getAll();
        for (Object obj : all) {
            System.out.println((IGoods)obj);
        }

    }
}
    }
    if(this==obj){
        return true;
    }
    Cat cat=(Cat)obj;
    return this.name.equals(cat.name)&&this.color.equals(cat.color);
}
@Override
public String toString() {
    return "Cat{" +
            "name='" + name + '\'' +
            ", color='" + color + '\'' +
            '}';
}

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

@Override
public String getColor() {
    return this.color;
}

}
class Dog implements Pet{
private String name;
private String color;
public Dog(String name,String color){
this.name=name;
this.color=color;
}
//覆写equals方法 该方法必须覆写,不覆写的话无法进行删除
public boolean equals(Object obj){
if(objnull){
return false;
}
if(!(obj instanceof Cat)){
return false;
}
if(this
obj){
return true;
}
Dog dog=(Dog)obj;
return this.name.equals(dog.name)&&this.color.equals(dog.color);
}
@Override
public String toString() {
return “Dog{” +
“name='” + name + ‘’’ +
“, color='” + color + ‘’’ +
‘}’;
}

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

@Override
public String getColor() {
    return this.color;
}

}
public class JavaDemo{
public static void main(String[] args) {
PetShop shop=new PetShop();
shop.add(new Dog(“黄狗”,“黄色”));
shop.add(new Dog(“黄猫”,“黄色”));
Object[] result = shop.search(“黄”).toArray();
for (Object obj : result) {
System.out.println(obj);

    }
}

}


​		所有的程序开发都是以接口为标准进行的,这样在进行后期程序处理的时候就可以非常的灵活。只要符合标准的对象就都可以保存。

## 综合实战:超市购物车

​		使用面向对象概念表示出下面的生活场景:小明去超时买东西,所有买到的东西都放在购物车中,最后到收银台一起结账。

[外链图片转存中...(img-v4k48uv6-1661919535147)]

```java
interface ILink<E>{...}
classs LinkImpl<E> implements ILink<E>{...}//代码同上
interface IGoods{//1.定义商品标准
    public String getName();
    public double getPrice();
}
interface IShopCar{//2.定义购物车标准
    public void add(IGoods good);
    public void delete(IGoods good);
    public Object[] getAll();

}
class ShopCarImpl implements IShopCar{//3.实现购物车
    private ILink<IGoods> allGoods=new LinkImpl<IGoods>();
    @Override
    public void add(IGoods good) {
            this.allGoods.add(good);
    }

    @Override
    public void delete(IGoods good) {
        this.allGoods.remove(good);
    }

    @Override
    public Object[] getAll() {
        return this.allGoods.toArray()  ;
    }
}

//4.收银台
class Crashier{
    private IShopCar shopCar;
    public Crashier(IShopCar shopCar){
        this.shopCar=shopCar;
    }
    public double getAllPrice(){//获得总价
        double sum=0.0;
        Object[] all = this.shopCar.getAll();
        for (Object obj : all) {
            IGoods good=(IGoods) obj;
            sum+=good.getPrice();
        }
        return sum;
    }
    public int getAllCount(){//获得总数
        return this.shopCar.getAll().length;
    }

}
//5.实现商品
class Book implements IGoods{
    private String name;
    private double price;
    public Book(String name,double price){
        this.name=name;
        this.price=price;
    }
    //覆写equals方法
    public boolean equals(Object obj){
        if(obj==null){
            return false;
        }
        if(!(obj instanceof Book)){
            return false;
        }
        if(this==obj){
            return true;
        }
        Book book=(Book)obj;
        return this.name.equals(book.name)&&this.price==book.price;
    }
    @Override
    public String toString() {
        return "Book{" +
                "name='" + name + '\'' +
                ", price='" + price + '\'' +
                '}';
    }

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

    @Override
    public double getPrice() {
        return this.price;
    }
}

public class JavaDemo{
    public static void main(String[] args) {
        IShopCar shopCar = new ShopCarImpl();
        shopCar.add(new Book("book1",999));
        shopCar.add(new Book("book2",99));
        Crashier crashier = new Crashier(shopCar);
        Object[] all = shopCar.getAll();
        for (Object obj : all) {
            System.out.println((IGoods)obj);
        }

    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值