跳至主要內容

面向对象编程

wangdx大约 14 分钟

面向对象编程

字符串操作

使用 Java 类库可以提升程序开发的便捷性,但是如何将给出的应用类库以面向对象的形式融合到程序之中呢?所以本次将通过一系列的案例进行具体程序类库的应用。

  • 定义一个 StringBuffer 类对象,然后通过 append() 方法向对象中添加 26 个小写字母,要求每次只添加一次,共添加 26 次,然后按照逆序的方式输出,并且可以删除前 5 个字符。

如果面对此时的问题最简单的一种做法就是直接通过主方法实例化 StringBuffer 类的对象,随后向 StringBuffer 类的对象之中添加有若干个数据信息。

范例:传统的面向过程做法

详情
/**
 * 定义一个 StringBuffer 类对象,然后通过 append() 方法向对象中添加 26 个小写字母,要求每次只添加一次,共添加 26 次,然后按照逆序的方式输出,并且可以删除前 5 个字符。
 *
 * @author wangdx
 */
public class CaseTest {
    public static void main(String[] args) {
        StringBuffer buffer = new StringBuffer();
        for (int x = 'a'; x <= 'z'; x++) {
            buffer.append((char) x);
        }
        System.out.println("【初始化数据】" + buffer);
        System.out.println("【逆序输出】" + buffer.reverse());
        System.out.println("【删除前5个字符】" + buffer.delete(0, 5));
    }
}

虽然这个时候已经完成了所对应的处理功能,但是如果仅仅是以一位所谓的初学者的角度来讲肯定是没有问题的,但问题是现在需要讨论的不是能否实现的问题了,而是属于如何实现更好的问题。

如果按照面向对象的设计形式以上的操作代码明显是不合理的,主要体现在对于当前给定的程序模型应该以接口的设计先行,而后定义具体的操作子类。

范例:面向对象做法

详情
/*
 * 面向对象
 * */
interface IContent {
    public String content();//返回原始数据

    public String reverse();//返回逆序的内容结果

    public String delete(int index);//删除结束索引
}

class StringContent implements IContent {
    private StringBuffer buffer = new StringBuffer();

    public StringContent() {
        for (int x = 'a'; x <= 'z'; x++) {
            buffer.append((char) x);
        }
    }

    @Override
    public String content() {
        return this.buffer.toString();
    }

    @Override
    public String reverse() {
        return this.buffer.reverse().toString();
    }

    @Override
    public String delete(int index) {
        return this.buffer.delete(0, index).toString();
    }
}

class Factory {
    private Factory() {
    }

    public static IContent getInstance() {
        return new StringContent();
    }
}

class Test {
    public static void main(String[] args) {
        IContent content = Factory.getInstance();
        System.out.println("【初始化数据】" + content.content());
        System.out.println("【逆序输出】" + content.reverse());
        System.out.println("【删除前5个字符】" + content.delete(5));

    }
}

随机数生成

  • 利用 Random 类产生 5 个 1~30 之间(包括 1 和 30)的随机整数。

在面向对象的设计之中,之所以会提供有接口的概念,很大的程度上是通过接口描述最终的目的,本程序的最终目的实际上就在于获取数字,而这个数字的获取根据所提出的要求一定是要通过随机数的

详情
interface INumber {
    public int[] getData();
}

class InvalidateArrayLengthException extends RuntimeException {
    public InvalidateArrayLengthException() {
    }

    public InvalidateArrayLengthException(String str) {
        super(str);
    }
}

class RandomNumber implements INumber {
    private int data[];

    public RandomNumber() {
        this(5);
    }

    public RandomNumber(int len) {
        if (len <= 0) {
            throw new InvalidateArrayLengthException("数组长度不正确。");
        }
        this.data = new int[len];
        this.createRandom();
    }

    private void createRandom() {
        Random random = new Random();
        for (int x = 0; x < this.data.length; x++) {
            this.data[x] = random.nextInt(30) + 1;//避免0和30问题
        }
    }

    @Override
    public int[] getData() {
        return this.data;
    }
}

class Factory1 {
    private Factory1() {
    }

    public static INumber getInstance(int... args) {
        if (args.length == 0) {
            return new RandomNumber();
        } else {
            return new RandomNumber(args[0]);
        }
    }
}

class Test1 {
    public static void main(String[] args) {
        INumber number = Factory1.getInstance(7);
        System.out.println(Arrays.toString(number.getData()));
    }
}

Email 验证

  • 输入一个 Email 地址,然后使用正则表达式验证该 Email 地址是否正确。

对于现在的程序开发如果面对的是一个完全陌生的程序结构,那么肯定希望调用的方式尽可能的简单一些,所以本程序的设计的时候就采用这种简单的操作思维,你可以使用默认的正则表达式,如果有需要也可以深入研究进行表达式的扩充。

详情
package com.yix.email;

/**
 * @author wangdx
 */
public interface IValidator {
    public boolean test();
}

class EmailValidator implements IValidator {
    public static final String DEFAULT_EMAIL_REGEX = "\\w+@\\w+\\.\\w+";
    private String regex;
    private String content;

    public EmailValidator(String content) {
        this(content, DEFAULT_EMAIL_REGEX);
    }

    public EmailValidator(String content, String regex) {
        this.content = content;
        this.regex = regex;
    }

    @Override
    public boolean test() {
        if (this.content == null || "".equals(this.content)) {
            return false;
        }
        return this.content.matches(this.regex);
    }
}

class Factory {
    private Factory() {
    }

    public static IValidator getInstance(String content, String... regex) {
        if (regex.length == 0) {
            return new EmailValidator(content);
        } else {
            return new EmailValidator(content, regex[0]);
        }
    }
}

class Test {
    public static void main(String[] args) {
        {
            IValidator validator = Factory.getInstance("muyan@yootk.com");
            System.out.println(validator.test() ? "当前验证通过" : "当前Email输入错误");
        }
        {
            IValidator validator = Factory.getInstance("muyan@yootk.yootk"
                    ,"[a-zA-Z][a-zA-Z0-9_\\-\\.]+@\\w+\\.(com|com\\.cn|net|net\\.cn|gov|edu|org)");
            System.out.println(validator.test() ? "当前验证通过" : "当前Email输入错误");
        }
    }
}

IP 地址验证

  • 编写正则表达式,判断给定的是否是一个合法的 IP 地址

本次的开发只考虑 IPV4 的情况,而对于 I 地址来讲一定要注意两个问题:

  • 需要注意其基本的组成:nnm.nnm.nnn.nnn;
  • 在每一位的 ip 地址组成的时候,“0.0.0.0”和“255.255.255.255”是不能够作为 IP 地址出现的;

在实际项目开发过程之中除了核心要完成的功能之外,最为常见的处理模式就是数据验证功能了,而且如果可以进行有效的 数据验证的操作管理,可以节约大量的代码。

详情
package com.yix.ip;


/**
 * @author wangdx
 */
public interface IValidator {
    public boolean test();
}

class IPHostValidator implements IValidator {
    public static final String DEFAULT_EMAIL_REGEX = "\\d{1,3}\\.\\d{1,3}.\\d{1,3}.\\d{1,3}";
    private String regex;
    private String content;

    public IPHostValidator(String content) {
        this(content, DEFAULT_EMAIL_REGEX);
    }

    public IPHostValidator(String content, String regex) {
        this.content = content;
        this.regex = regex;
    }

    @Override
    public boolean test() {
        if (this.content == null || "".equals(this.content)) {
            return false;
        }
        if ("0.0.0.0".equals(this.content) || "255.255.255.255".equals(this.content)) {
            return false;
        }
        return this.content.matches(this.regex);
    }
}

class Factory {
    private Factory() {
    }

    public static IValidator getInstance(String content, String... regex) {
        if (regex.length == 0) {
            return new IPHostValidator(content);
        } else {
            return new IPHostValidator(content, regex[0]);
        }
    }
}


class Test {
    public static void main(String[] args) {
        IValidator validator = Factory.getInstance("192.168.1.16");
        System.out.println(validator.test() ? "ip结构正确" : "ip结构错误");
    }
}

HTML 结构拆分

  • 给定一段 HTML 代码:“Kfont face="Arial,Serif"size="+2"color-"red">”,要求对内容进行拆分,拆分之后的结果是:
  • face Arial.Serif.
  • size +2
  • color red
详情
package com.yix.html;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author wangdx
 */
public interface IStringHandle {
    public String[] split();
}

class HTMLStringHandle implements IStringHandle {
    private String regex;
    private String content;

    public HTMLStringHandle(String content, String regex) {
        this.content = content;
        this.regex = regex;
    }

    @Override
    public String[] split() {
        Matcher matcher = Pattern.compile(this.regex).matcher(this.content);
        StringBuffer buffer = new StringBuffer(this.content.length());
        System.out.println("***********"+matcher.find());
        while (matcher.find()) {
            String value = matcher.group(0);
            String temp[] = value.split("=");
            buffer.append(temp[0]).append(":").append(temp[1].replaceAll("\"", "")).append("|");
        }
        return buffer.toString().split("\\|");
    }
}

class Factory {
    private Factory() {
    }

    public static IStringHandle getInstance(String content, String regex) {
        return new HTMLStringHandle(content, regex);
    }
}

class Test {
    public static void main(String[] args) {
        String html = "<font face=\"Arial,Serif\" size=\"+2\" color=\"red\">";
        String regex = "\\w+=\"[a-zA-Z0-9,\\+]+\"";
        IStringHandle handle = Factory.getInstance(html, regex);
        String result[] = handle.split();
        for (String temp : result) {
            System.out.println(temp);
        }
    }
}

国际化应用

  • 编写程序,实现国际化应用,从命令行输入国家的代号,例如,1 表示中国,2 表示美国,然后根据输入代号的不同调用不同的资源文件显示信息。
详情
1.创建Message.properties

package com.yix.locale;

import java.util.Locale;
import java.util.ResourceBundle;

/**
 * @author wangdx
 */
public interface IMessage {
    public String get(String key);
}

class ResourceMessage implements IMessage {
    private static final String BASE_NAME = "com.yix.locale.resource.Message";
    private ResourceBundle resourceBundle;

    public ResourceMessage(Locale locale) {
        this.resourceBundle = ResourceBundle.getBundle(BASE_NAME, locale);
    }

    @Override
    public String get(String key) {
        try {
            return this.resourceBundle.getString(key);
        } catch (Exception e) {
            return null;
        }
    }
}

class ParameterException extends RuntimeException {
    private ParameterException() {
    }

    public ParameterException(String msg) {
        super(msg);
    }
}

class Factory {
    private Factory() {
    }

    public static IMessage getInstance(String args[]) {
        if (args.length != 1 || !args[0].matches("\\d")) {
            throw new ParameterException("初始化参数配置错误,程序无法执行!!");
        }
        int select = Integer.parseInt(args[0]);
        switch (select) {
            case 1:
                return new ResourceMessage(Locale.CHINESE);
            case 2:
                return new ResourceMessage(Locale.US);
            default:
                return null;
        }
    }
}

class Test {
    public static void main(String[] args) {
        IMessage message = Factory.getInstance(args);
        System.out.println(message.get("welcom.info"));
    }
}

数据排序

  • 按照“姓名:年龄:成绩!姓名:年龄:成绩”的格式定义字符串“张三:21:98|李四:22:89|王五:20:70",要求将每组值分别保存在 Student 对象之中,并对这些对象进行排序,排序的原则为:按照成绩由高到低排序,如果成绩相等,则按照年龄由低到高排序。

对于当前所需要设置的内容一定是很多的,但是这些数据一定是多个 Student 对象(Siudent 对象数组),所以如果要想针对于对象数组进行排序处理,那么一定要使用比较器的方式来完成。

从整个的设计来讲,本程序的代码就属于一种传统的数据传输的处理模式,这种思想很重要

详情
package com.yix.datasort;

import java.util.Arrays;

/**
 * @author wangdx
 */
public class Student implements Comparable<Student> {
    private String name;
    private int age;
    private double score;

    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

    @Override
    public int compareTo(Student o) {
        if (this.score > o.score) {
            return 1;
        } else if (this.score < o.score) {
            return -1;
        } else {
            return this.age - o.age;
        }
    }
}

interface IDataHandle {
    public Student[] getData();
}

class StringDataHandle implements IDataHandle {
    private Student student[];
    private String value;

    public StringDataHandle(String value) {
        this.value = value;
    }

    private void handle() {
        String result[] = this.value.split("\\|");
        this.student = new Student[result.length];
        for (int x = 0; x < result.length; x++) {
            String data[] = result[x].split(":");
            this.student[x] = new Student(data[0], Integer.parseInt(data[1]), Double.parseDouble(data[2]));
        }
    }

    @Override
    public Student[] getData() {
        this.handle();
        Arrays.sort(this.student);
        return this.student;
    }
}

class Factory {
    private Factory() {
    }

    public static IDataHandle getInstance(String data) {
        return new StringDataHandle(data);
    }
}

class Test {
    public static void main(String[] args) {
        String str = "张三:21:98|李四:22:89|王五:20:70";
        IDataHandle handle = Factory.getInstance(str);
        System.out.println(Arrays.toString(handle.getData()));
        System.out.println(handle.getData());
    }
}

硬币投掷

编写程序,用 0~1 之间的随机数来模拟扔硬币试验,统计扔 1000 次后出现正、反面的次数并输出。

详情
package com.yix.coin;

import java.util.Random;

/**
 * @author wangdx
 */
public interface IStat {
    public IMap<String, Integer> report();
}

class CoinStat implements IStat {
    private int count;
    private Random random = new Random();
    private IMap<String, Integer> map = IMap.getInstance();

    public CoinStat() {
        this(100);
    }

    public CoinStat(int count) {
        this.count = count;
        this.handle();
    }

    private void handle() {
        int up = 0;
        int down = 0;
        for (int x = 0; x < this.count; x++) {
            if (this.random.nextInt(2) == 1) {
                up++;
            } else {
                down++;
            }
        }
        this.map.put("up", up);
        this.map.put("down", down);
    }

    @Override
    public IMap<String, Integer> report() {
        return this.map;
    }
}

/**
 * 定义一个根据树状结构存储的接口,同时实现数据的保存和查询
 *
 * @param <K> 要保存数据的Key类型(根据key找到value)
 * @param <V> 要保存的核心的数据内容
 */
interface IMap<K, V> { // 实现树结构的数据查询
    /**
     * 向Map集合之中保存有相应的数据内容,所有的内容都不允许为空
     *
     * @param key   要保存数据的key,key不允许重复
     * @param value 要保存数据的value,内容不允许为空
     * @return 如果指定的key不存在返回null,如果key存在了, 则将新的内容替换掉旧的内容,并返回旧信息
     */
    public V put(K key, V value);

    /**
     * 根据key查询对应的value数据
     *
     * @param key 数据查询的key,如果不存在则不返回任何的结果
     * @return key存在返回具体数据,否则返回null
     */
    public V get(K key);

    /**
     * 获取保存元素的数量
     *
     * @return 元素的个数
     */
    public int size();

    /**
     * 获取IMap的默认实例化对象
     *
     * @param <K> 与IMap接口K类型相同,一定要实现Comparable接口
     * @param <V> 与IMap接口V类型相同
     * @return IMap对象实例
     */
    public static <K, V> IMap<K, V> getInstance() {
        return new BinaryTreeMapImpl<K, V>();
    }
}

class BinaryTreeMapImpl<K, V> implements IMap<K, V> {
    // 1、如果要想进行树状结构的存储,必须要想办法把key和value进行了一次包装处理
    private class Entry<K, V> implements Comparable<Entry<K, V>> { // 用户不关注此类
        private K key; // key所在的类必须实现Comparable接口
        private V value;

        public Entry(K key, V value) {
            this.key = key;
            this.value = value;
        }

        @Override
        public int compareTo(Entry<K, V> o) {   // 所有的数据依据key实现排序操作
            return ((Comparable) this.key).compareTo(o.key);
        }
    }

    // 2、如果要想进行数据的存储肯定要有一个Node节点存在
    private class Node {
        private Entry<K, V> data; // 每一个节点保存的是Entry对象实例
        private Node left;
        private Node right;

        public Node(Entry<K, V> data) { // 节点必须要包裹Entry对象
            this.data = data;
        }

        public V addNode(Node newNode) {    // 递归方式实现节点存储
            if (this.data.compareTo(newNode.data) < 0) {    // 新的内容大于根节点
                if (this.right == null) {   // 没有右节点
                    this.right = newNode; // 保存右节点
                } else {
                    return this.right.addNode(newNode);
                }
            } else if (this.data.compareTo(newNode.data) > 0) { // 新的内容小于根节点
                if (this.left == null) {
                    this.left = newNode; // 保存左节点
                } else {
                    return this.left.addNode(newNode);
                }
            } else {    // key存在
                V old = this.data.value; // 获取旧的内容
                this.data.value = newNode.data.value; // 替换旧的信息
                return old; // 返回旧数据
            }
            return null;
        }

        public V getNode(K key) {
            if (this.data.key.equals(key)) {    // key相同
                return this.data.value; // 返回对应的value
            } else {
                if (((Comparable) this.data.key).compareTo(key) <= 0) {
                    if (this.right != null) {
                        return this.right.getNode(key);
                    } else {
                        return null;
                    }
                } else {
                    if (this.left != null) {
                        return this.left.getNode(key);
                    } else {
                        return null;
                    }
                }
            }
        }
    }

    // 以下为接口实现类的处理
    private Node root; // 定义根节点
    private int count; // 保存元素的个数

    @Override
    public V put(K key, V value) {
        if (key == null || value == null) {
            throw new NullPointerException("保存数据的key或者是value不允许为空!");
        }
        if (!(key instanceof Comparable)) { // 没有Comparable接口实现
            throw new ClassCastException("作为KEY的所在类必须实现java.lang.Comparable接口!");
        }
        Entry<K, V> entry = new Entry<>(key, value);    // 将要保存的数据转为Entry对象
        Node newNode = new Node(entry); // 将数据包裹在节点中
        this.count++; // 保存个数
        if (this.root == null) {    // 当前没有根节点
            this.root = newNode; // 第一个节点作为根节点
            return null; // 第一次存储数据不可能有重复的情况出现
        } else {    // 需要考虑到节点的保存位置
            return this.root.addNode(newNode);
        }
    }

    @Override
    public V get(K key) {
        if (key == null) {
            throw new NullPointerException("查询数据的key不允许为空!");
        }
        if (!(key instanceof Comparable)) { // 没有Comparable接口实现
            throw new ClassCastException("作为KEY的所在类必须实现java.lang.Comparable接口!");
        }
        if (this.root == null) {    // 还没有保存任何的数据
            return null;
        }
        return this.root.getNode(key); // 基于递归进行处理
    }

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

class Test {
    public static void main(String[] args) {
        IStat stat = new CoinStat();
        System.out.println("正面次数" + stat.report().get("up"));
        System.out.println("背面次数" + stat.report().get("down"));
    }
}

红黑树

但是随着时间的推移,那么有可能用户所储存的数据会有一个单方面的偏向,如何可以让二叉树保持平衡,就成为了性能设计解决的关键因素所在,所以为了解决这样的二叉树的平衡问题,引入了红黑树的概念。 红黑树本质上是一种二叉查找树,但它在二叉査找树的基础上额外添加了一个标记(颜色),同时具有一定的规则。这些规则 使红黑树保证了一种平衡,插入、删除、查找的最坏时间复杂度都为(logn)。 红黑树是在 1972 年由 RudolfBayer 发明的,当时被称为平衡二叉 B 树(symmetric binary B-tees)。后来,在 1978 年被 LeoJGuibas 和 Robert Sedgewick 修改为如今的“红黑树”。

红黑树特点

  • 每个节点或者是黑色,或者是红色

  • 根节点必须是黑色;

  • 每个叶子节点是黑色

    • Java 实现的红黑树将使用 null 来代表空节点,因此遍历红黑树时将看不到黑色的叶子节点,反而看到每个叶子节点都是红色的;
  • 如果一个节点是红色的,则它的子节点必须是黑色的;

    • 从每个根到节点的路径上不会有两个连续的红色节点,但黑色节点是可以连续的。若给定黑色节点的个数 N,最短路径情况是连续的 N 个黑色,树的高度为 N-1;最长路径的情况为节点红黑相间,树的高度为 2(N-1);
  • 从一个节点到该节点的子孙节点的所有路径上包含相同数目的黑节点数量;

    • 成为红黑树最主要的条件,后序的插入、删除操作都是为了遵守这个规定
  • 数据插入处理

  • 第一次插入,由于原树为空所以只会违反红-黑树的规则 2 所以只要把根节点涂黑即可

  • 如果插入节点的父节点是黑色的,那不会违背红-黑树的规则,什么也不需要做;但是遇到如下三种情况时,就要开始变色和旋转了:

    • 插入节点的父节点和其叔叔节点(祖父节点的另一个子节点)均为红色的;
    • 插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的左子节点。
    • 插入节点的父节点是红色,叔叔节点是黑色,且插入节点是其父节点的右子节点;
  • 二叉搜索树的数据删除

    • 1、如果待删除节点没有子节点,那么直接删掉即可

    • 2、如果待删除节点只有一个子节点,那么直接删掉,并用其子节点去顶替它;

    • 3、如果待删除节点有两个子节点,这种情况比较复杂:首选找出它的后继节点,然后处理“后继节点”和“被删除节点的父节点”之间的关系,最后处理“后继节点的子节点”和“被删除节点的子节点”之间的关系。

上次编辑于: