主要讨论了Java编程语言中的一些基础概念和进阶内容。包括static和non-static的区别,以及辅助函数helper function的使用、测试的重要性、进一步创建List、继承,Java的原始类型和引用类型的区别,以及接口、覆写与重载等概念的介绍还有继承的相关知识,包括extends关键词、super的作用、封装以及建立自己的比较函数等

这个博客并不是我写这篇笔记地方,所以可能出各种包括发布时间、文字、样式等错误

Week1 Java入门

终端使用教程

Lab 1 Setup: Setting Up Your Computer | CS 61B Spring 2021

增强形For循环

Java also supports iteration through an array using an “enhanced for loop”. The basic idea is that there are many circumstances where we don’t actually care about the index at all. In this case, we avoid creating an index variable using a special syntax involving a colon.
For example, in the code below, we do the exact thing as in BreakDemo above. However, in this case, we do not create an index i. Instead, the String s takes on the identity of each String in a exactly once, starting from a[0], all the way up to a[a.length - 1]. You can try out this code at this link.

总结一下就是不关心index的时候可以直接遍历数组等

public class EnhancedForBreakDemo {
    public static void main(String[] args) {
        String[] a = {"cat", "dog", "laser horse", "ketchup", "horse", "horbse"};

        for (String s : a) {
            for (int j = 0; j < 3; j += 1) {
                System.out.println(s);
                if (s.contains("horse")) {
                    break;
                }
            }
        }
    }
}

在Java中所有的代码都必须在一个class中(但main不一定,可以通过其他的main调用)

static和non-static的区别:

project0会详细深入这一点

  • 示例代码:狗叫.java

    public class DogLoop {
       public static void main(String[] args) {
          Dog smallDog = new Dog(5);
          Dog mediumDog = new Dog(25);
          Dog hugeDog = new Dog(150);
          
          Dog[] manyDogs = new Dog[4];
          manyDogs[0] = smallDog;
          manyDogs[1] = hugeDog;
          manyDogs[2] = new Dog(130);
          
          int i = 0;
          while (i < manyDogs.length) {
             Dog.maxDog(manyDogs[i], mediumDog).makeNoise();
             i = i + 1;
          }
       }
       
       public static class Dog {
          /* Size of the dog in standard dog size units. */
          public int size;
    
          /* This is a constructor. It tells us how to construct
    	 * dogs from our ideal notion of dogness. */
    
          public Dog(int s) {
             size = s;
          }
    
          public void makeNoise() {
             if (size < 10) {
                System.out.println("hideous yapping");
             } else if (size < 30) {
                System.out.println("bark!");
             } else {
                System.out.println("woof!");
             }
          }
    
          /* Return the larger of dog d1 and dog d2. */
          public static Dog maxDog(Dog d1, Dog d2) {
             if (d1.size > d2.size) {
                return d1;
             }
             return d2;
          }   
       }
    }
    

辅助函数 helper function:

  • 将代码里许多功能变为一个个的辅助函数,不仅可以降低复杂度
  • 而且有利于将大任务变成小任务集中精力解决并且不容易出错
  • 出现问题时容易 🔧debug

第一个大的project,据说有点难度
但目的是为了让我们熟悉突然接受一个大项目,并且不用搞懂所有东西并完成的感觉


Week2 测试的重要性

前面的课程中讲了Java的一些语法和怎么用辅助函数来管理复杂性
这一节将focus怎么测试写的代码和进行debug

以对sort编写测试为例:

  • 首先不能滥用“! =”因为数组比较要通过Arrays.equals( , ),
    说明测试本身也可能编写出错
  • 详细的output指出错误的原因 信息等很重要
  • 怎么使用JUnit写出更好的测试代码

比如原本要写出这样的代码来找到具体错误的信息:


for (int i = 0; i < input.length; i += 1) {
    if (!input[i].equals(expected[i])) {
        System.out.println("Mismatch at position " + i + ", expected: '" 
				+ expected[i] +  "', but got '" + input[i] + "'");
        return;
  }
}

使用JUnit可以只用一行org.junit.Assert.assertArrayEquals(expected, input); 替代

  • 编写Sort的过程

    按照测试驱动开发Test-Driven Development (TDD)的思想,先写测试程序再完成功能

    编写测试程序

    首先遇到了不能直接用< 比较String的问题,这时候要善用Google,可以找到使用“a”.compareTo(”b”) 这样的比较String的方法

    最终完成了测试程序,这时候就不再需要手动改值而是有个测试程序帮你了 感觉不错😊

    截屏2022-07-19 17.04.21.png

    分为三个步骤,用三个helper functions来实现

    1. 找到最小的数

      /** Test the Sort.findSmallest method. */
          @Test
          public void testFindSmallest() {
              String[] input = {"i", "have", "an", "egg"};
              int expected = 2;
      
              int actual = Sort.findSmallest(input, 0);
              assertEquals(expected, actual);
      
              String[] input2 = {"there", "are", "many", "pigs"};
              int expected2 = 2;
      
              int actual2 = Sort.findSmallest(input2, 2);
              assertEquals(expected2, actual2);
          }
      
      public static int findSmallest(String[] x, int start) {
              int smallestIndex = start;
              for (int i = start; i < x.length; i += 1) {
                  int cmp = x[i].compareTo(x[smallestIndex]);
                  // from the internet, if x[i] < x[smallestIndex], cmp will be -1.
                  if (cmp < 0) {
                      smallestIndex = i;
                  }
              }
              return smallestIndex;
          }
      
    2. 移动到数组最前面

    通过一个temp变量就可以交换a b的位置了

    /** Swap item a with b. */
    	public static void swap(String[] x, int a, int b) {
    	    String temp = x[a];
    	    x[a] = x[b];
    	    x[b] = temp;
        }
    
    1. 在其余的数中继续(迭代)

    完成前两个helper functions就可以汇总了

    public static void sort(String[] x) {
    	/* 对下面的辅助函数起始的调用 */
            sort(x, 0);
    	}
    
    	/** Sorts x starting at position start. */
    	private static void sort(String[] x, int start) {
    	    if (start == x.length) {
    	        return;
            }
    	    int smallestIndex = findSmallest(x, start);
    	    swap(x, start, smallestIndex);
    	    sort(x, start + 1);
        }
    

    有时会遇到编写的helper functions 配合不好API不同之类的,很正常 修改一下下就好了

    调用好两个辅助方法就需要思考怎么完成第三部分的在其余的数中继续 的问题
    Java不像python那样支持子索引 x[1:],
    于是也许可以写个辅助函数来帮助我们完成从x的某个位置开始进行
    总之就是公共的方法让别人可以使用,private的提供特殊的切片服务

    断点可以提供条件,帮助定位问题

    截屏2022-07-19 16.21.41.png

    最后发现是第一步findSmallest(String[] x) 始终在整个x数组里找最小的,于是修改为findSmallest(String[] x, int start) 问题解决 (善用debugger在出错的位置思考为什么会出错),而测试和辅助函数可以让我在一堆复杂流程中,只关心修改后的findSmallest 到底对没对,这样就很轻松了

    最后整个Sort完成!🎉

    • 完整的测试代码:

      import org.junit.Test;
      import static org.junit.Assert.*;
      
      /** Tests the the Sort class. */
      public class TestSort {
          /** Test the Sort.sort method. */
          @Test
          public void testSort() {
              String[] input = {"i", "have", "an", "egg"};
              String[] expected = {"an", "egg", "have", "i"};
      
              Sort.sort(input);
      
              assertArrayEquals(expected, input);
          }
      
          /** Test the Sort.findSmallest method. */
          @Test
          public void testFindSmallest() {
              String[] input = {"i", "have", "an", "egg"};
              int expected = 2;
      
              int actual = Sort.findSmallest(input, 0);
              assertEquals(expected, actual);
      
              String[] input2 = {"there", "are", "many", "pigs"};
              int expected2 = 2;
      
              int actual2 = Sort.findSmallest(input2, 2);
              assertEquals(expected2, actual2);
          }
      
          /** Test the Sort.swap method. */
          @Test
          public void testSwap() {
              String[] input = {"i", "have", "an", "egg"};
              int a = 0;
              int b = 2;
              String[] expected = {"an", "have", "i", "egg"};
      
              Sort.swap(input, a, b);
              assertArrayEquals(expected, input);
          }
      }
      

    lectureCode-fa20/testing at master · Berkeley-CS61B/lectureCode-fa20

    整个Sort和测试Sort代码

总结

在现实的开发时个循序渐进的过程,涉及大量的修改,
而测试可以帮助我们对基本单位的正确性有信心,
也可以让我们的大脑更专注于具体的一个任务,
重构代码时也能更方便知道这块代码还是没问题的~

在测试里删掉main,并且在每个测试函数前加上@org.junit.Test 最后run的时候选择有测试的
就可以很方便的进行测试
如果进行了import 直接写Test 就可以了 ,可以让代码更简洁

但把握测试和效率的平衡也很重要


Discussion 02 Static和 Instance区别

  • 声明各种变量注意类型比如name = “huhu” ❌,String name = “huhu” ✔️
  • Java中数组一旦声明就不能扩张了
  • static是什么
class Book {
public Library library;
public static Book last = null;
}

区别在于没有static的会在每个实例中独自存在,一个类的实例都共享static的值

对于方法来说,Static method 属于class而不是具体的实例,所以static就像一个公共区域

把61b的教师看成static而每个学生的都有同一个教授,而学生的ID就像Instance,教授改变每个学生的都会变,而一个学生的ID变化不影响其他学生的ID

Lecture 4,5 构建List、迭代和递归

“=”只是复制比特,所以这就是为什么对于int a = int b 再修改b的话 a不变,对于对象 a = new Some(),b = a 修改b会让a也变化,因为a本质是对新的Some位置的引用
更为正式的说法就是值类型与引用类型的区别

  • 构建一个list

在Java中构建一个可以扩大的list对象(Java数组一旦声明就不能改变大小了)

可以注意到对IntList大小的实现方法有两种一种是使用迭代.size() 另一种是不使用迭代的.iterativeSize()

  • 迭代和递归的区别

    递归(recursion):递归常被用来描述以自相似方法重复事物的过程,在数学和计算机科学中,指的是在函数定义中使用函数自身的方法。(A调用A)

    迭代(iteration):重复反馈过程的活动,每一次迭代的结果会作为下一次迭代的初始值。(A重复调用B)

public class IntList {
	public int first;
	public IntList rest;

	public IntList(int f, IntList r) {
		first = f;
		rest = r;
	}

	/** Return the size of the list using... recursion! */
	public int size() {
		if (rest == null) {
			return 1;
		}
		return 1 + this.rest.size();
	}

	/** Return the size of the list using no recursion! */
	public int iterativeSize() {
		IntList p = this;
		int totalSize = 0;
		while (p != null) {
			totalSize += 1;
			p = p.rest;
		}
		return totalSize;
	}

	/** Returns the ith item of this IntList. */
	public int get(int i) {
		if (i == 0) {
			return first;
		}
		return rest.get(i - 1);
	}

	public static void main(String[] args) {
		IntList L = new IntList(15, null);
		L = new IntList(10, L);
		L = new IntList(5, L);

		System.out.println(L.get(100));
	}
}

在完成了IntList后我们还能更进一步,因为现在直接把public迭代展现给用户,并且需要用户手动了解和指定数字的位置比如L = new IntList(5, L); 同时这样使用方法也并不规范,
所以新建了SLList,这样我们添加数字到List就变为了更简单和规范的L.addLast(20);,此外很多东西也变成了private 防止用户使用设计以外的方法

另外可以注意到Size() 的实现有点棘手,但通过private做迭代逻辑和public提供外部访问这种常见模式可以解决,当然更好的解决办法是在添加时就记录List大小的改变,这样效率更高 不用每次使用时重新计算大小

头节点 哨兵节点 Sentinel Nodes

头结点是链表里面第一个结点,他的数据域可以不存放任何信息(有时候也会存放链表的长度等等信息),他的指针区域存放的是链表中第一个数据元素的结点(就是传说中的首元结点)存放的地址。

1、防止单链表是空的而设的.当链表为空的时候,带头结点的头指针就指向头结点.如果当链表为空的时候,头结点的指针域的数值为NULL.

2、是为了方便单链表的特殊操作,插入在表头或者删除第一个结点.这样就保持了单链表操作的统一性!

3、单链表加上头结点之后,无论单链表是否为空,头指针始终指向头结点,因此空表和非空表的处理也统一了,方便了单链表的操作,也减少了程序的复杂性和出现bug的机会 [1]  。

4、对单链表的多数操作应明确对哪个结点以及该结点的前驱。不带头结点的链表对首元结点、中间结点分别处理等;而带头结点的链表因为有头结点,首元结点、中间结点的操作相同,从而减少分支,使算法变得简单,流程清晰。对单链表进行插入、删除操作时,如果在首元结点之前插入或删除的是首元结点,不带头结点的单链表需改变头指针的值,在TurboC算法的函数形参表中头指针一般使用指针的指针(在C++中使用引用&);而带头结点的单链表不需改变头指针的值,函数参数表中头结点使用指针变量即可,对初学者更易接受。

为了避免空list出现的问题我们使用了Sentinel Nodes作为默认值,而实际的第一项放在了它的next,这样list就永远不为null了

/** An SLList is a list of integers, which hides the terrible truth
   * of the nakedness within. */
public class SLList {	
	//IntNode就是上面IntList的改名,
//另外因为IntNode不看使用SLList的方法之类的设置为static可以节约内存
	private static class IntNode {
		public int item;
		public IntNode next;

		public IntNode(int i, IntNode n) {
			item = i;
			next = n;
			System.out.println(size);
		}
	} 

	/* The first item (if it exists) is at sentinel.next. */
	private IntNode sentinel;
	private int size;

	private static void lectureQuestion() {
		SLList L = new SLList();
		IntNode n = IntNode(5, null);
	}

	/** Creates an empty SLList. */
	public SLList() {
		sentinel = new IntNode(63, null);
		size = 0;
	}

	public SLList(int x) {
		sentinel = new IntNode(63, null);
		sentinel.next = new IntNode(x, null);
		size = 1;
	}

 	/** Adds x to the front of the list. */
 	public void addFirst(int x) {
 		sentinel.next = new IntNode(x, sentinel.next);
 		size = size + 1;
 	}

 	/** Returns the first item in the list. */
 	public int getFirst() {
 		return sentinel.next.item;
 	}

 	/** Adds x to the end of the list. */
 	public void addLast(int x) {
 		size = size + 1; 		

 		IntNode p = sentinel;

 		/* Advance p to the end of the list. */
 		while (p.next != null) {
 			p = p.next;
 		}

 		p.next = new IntNode(x, null);
 	}
 	
 	/** Returns the size of the list. */
 	public int size() {
 		return size;
 	}

	public static void main(String[] args) {
 		/* Creates a list of one integer, namely 10 */
 		SLList L = new SLList();
 		L.addLast(20);
 		System.out.println(L.size());
 	}
}

Week3 进一步创建List、继承

在上周完成了SLList后仍然存在一些效率问题,比如想在末尾添加需要使用while (p.next != null) {p = p.next;} 来遍历整个List
删除的效率也很低,因为只存了指向下一个的指针(删除b时a的next也需要修改)
于是需要使用双向链表(Doubly Linked Lists),增加一个指向上一个的指针
这样我们的List就从SLListDLList
另外为了避免sentinel的next指向自己,所以在末尾也添加了一个
并且按照要求最末尾的next指向第一个sentinel(据说在完成project1时很有用)

截屏2022-07-22 10.54.01.png
  • 完成DLList的改动总结

    截屏2022-07-22 11.08.58.png

现在还有个问题就是由于使用了 IntNode 我们的List只能储存int类型
可以制作多个不同类型的SLList,但太不优雅了 于是我们可以将它参数化:

public class SLList {	
	private  class IntNode {
		public int item;
		public IntNode next;

		public IntNode(int i, IntNode n) {
			item = i;
			next = n;
		}
	} 
//……省略接下来的代码
}
//创建也会有变化 以前:
SLList s1 = new SLList(“123”)
public class SLList<LochNess> {	
	private class StuffNode {
		public LochNess item;
		public StuffNode next;

		public StuffNode(LochNess i, StuffNode n) {
			item = i;
			next = n;
		}
	}
//……省略接下来的代码
}
//创建也会有变化 参数化后加入类型:
SLList<String> s1 = new SLList(“123”)

注意以前int相关的都被placeholderLochNess 替代了


创建Arrays的三种方法,注意第三种只能在声明变量时才能使用

截屏2022-07-22 11.57.30.png

Java的原始类型和引用类型区别:

原始类型(Primitive Data Types):比如double、float、byte、short、int、long、char、boolean等的值是直接存储在内存中的
引用类型(Reference Data Types):则在内存中存储实际对象的内存地址(指针),同时它需要使用new来创建比如Array、String

对于引用类型使用= 进行比较时只会比较内存地址而不是对象的值,同样地引用类型传递值的时候也只是传递内存地址的copy


觉得有意思的练习:将两个数组放入二维数组的两个对角三角形: disc3exam


改变数组大小Resizing Arrays:Java中不能改变数组的大小,但是我们可以通过将a数组放入更大的b数组,并将引用更改来达到这个目的

截屏2022-07-22 17.31.49.png

但通过每次+1创建新数组的方式在比如把a[100]→a[100000]时就要接近10万次创建新数组和copy到新数组,也许我们能通过将每次新生成的数组+10、+100,速度是会提高,但在更高的数量级时依然存在问题,所以我们可以通过*2(或者其他数字)的形式,每次数组大小不够时给新数组翻倍,这样减少了大量创建和copy并且能保证在更大的数量级时依然很快

Interface 接口

接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

接口是一种“规范”和约束,可以有多个接口

我的理解:Interface 可以让我们在有大量相同功能的不同方法之上创建一个抽象比如给大型犬和小猫咪洗澡可能都要冲水、擦干,但大型犬听话但要吃个骨头,小型犬需要另一个人按着洗澡不然抓你就洗不成了,如果为给洗澡写说明的话,那么洗澡就是接口,大型犬小猫咪就是洗澡implements,他们可以继承洗澡 的大部分规则(比如冲水、沐浴、吹干这个流程,但具体的操作要我们自己实现),而只需要加上自己的吃骨头,按着洗等等.如果对洗澡 里的水流大小不满意可以@Override 新的水流大小,如果他们需要不同的沐浴露可以Overloading来指定不同的沐浴露(注意java8后才能在接口里就实现功能,所以也可以说这个洗澡只是给你了流程(规则)没给你具体的步骤(实现))

使用Interface替代Class 就可以做出一个接口了,在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

覆写 (Overriding)与重载 (Overloading)

覆写会覆盖之前的实现,重载 在Java中可以允许有不同实现的同名函数

比如你不满意继承的实现就可以重写一个实现
而在求和时如果你有int和string两种如果没有重载用户就需要有sumInt()sumString()两种,而重载可以让我们写sum(int)sum(string) 来自动识别用户想要使用哪个方法

Java中子类必须overriding全部方法,不然会报错

Overriding的函数前需要加上@Override的标签,这样在并非Overriding的函数前加上的话就会有提示,另外这个并不是必须加的 只是个提示

如果要在Interface加上代码,需要在方法前加上default default public void print() {}

接口中所有的方法必须是抽象方法,Java 8 之后 接口中可以使用 default 关键字修饰的非抽象方法


截屏2022-07-23 18.53.51.png

以上图为例就可以看到,Animal a = **new** Dog(); 是可行的,因为Dog是Animal的子类,
同时Dog中sniff() 是@Override(标识本身没作用,是sniff的结构和父类一样),的所以a.sniff(d) 使用的Dog的方法
而注意praise(**Dog a**) 是Overloading,他的参数(signature)和和Animal中的praise(**Animal a**) 不一样了

截屏2022-07-23 19.07.00.png

最后两项d.praise(d); // praise(Dog a)a.praise(d); **// praise(Animal a)**

由于编译时会选择Overloading的方法是哪个所以:
Dog d =new Dog(); 那么找praise() 的时候会优先在Dog类里找,由于praise() 是Overloading的所以就选择了void praise(Dog a)
Animal a = new Dog(); 那么找praise() 的时候会优先在Animal类里找,由于praise() 是Overloading的所以就选择了void praise(Animal a)
new Dog()放入什么就很重要,new放入Animal类的编译时的选择就只有Animal类,而放入Dog则是两者包含

变量的static type & dynamic type

上面的重点就是每个Java变量都拥有两种类型:

编译时的“compile-time type”→“static type”,永远不会改变
运行时的“run-time type”→ “dynamic type”和对象的类型有关,实例化时指定

拿上面的a举例他的static type: Animal、dynamic type: Dog
而b的static type: Dog、dynamic type: Dog

当我们说Animal c;他的static type: Animal、dynamic type: null
再继续说c= new Dog();他的static type: Animal、dynamic type: Dog
再继续说Dog d = c;他的static type: Dog、dynamic type: Dog
或者说c= new Cat();他的static type: Animal、dynamic type: Cat

在编译时会优先选择dynamic type中override的方法,也就是Dynamic Method Selection
而有多个Overloading的方法时,会选择static type的方法,也就是上面选praise(Animal a)还是praise(Dog a)的关键点

截屏2022-07-24 18.45.48.png

注意当编译时编译器会检查右边的是否属于左边的,也就是看他们的static type,所以不能把SLList的放入VengefulSLList,哪怕它是SLList的子类
另外编译器编译时调用方法也只看static type,如果这个类型里没有那么就会报错
(比如Dog有bark(),但Object o=((Dog)someDog)是不能使用o.bark()的,哪怕Dog本身有这个方法)

截屏2022-07-24 18.51.54.png

方法调用同样也有static type 而maxDog就是Dog,所以不能把Dog类型放入Poodle,毕竟狗有时可以是贵宾犬有时可以不是贵宾犬,这个时候如果想成功放入就需要Casting

Overridden non-static methods are selected at run time based on dynamic type.

  • Everything else is based on static type, including overloaded methods. Note: No overloaded methods for problem at left.

Casting 对象转型

通过Casting可以告诉编译器把maxDog的static type看成Poodle,这样就能成功编译啦

这样做编译器就不会检查类型是否一致了,所以是个有用但是危险的工具 应该谨慎使用

另外注意Casting是非持久性效果,并不会永久改变对象的static type,只是临时把这个对象当成Casting的类型

截屏2022-07-24 18.55.04.png

Java 包 (package)

(在 Project1:Data Structures中开始使用)

包把功能相似或相关的类或接口组织在同一个包中,方便类的查找和使用。

通过在顶部使用package关键字来指定包如package deque;
然后就可以通过如deque.ArrayDeque来使用包里的类和方法
如果在使用时import deque.ArrayDeque就可以直接用ArrayDeque来使用包里的类和方法

Week4 进一步讨论继承

extends关键词

如**public class** RotatingSLList<Blorp> **extends** SLList<Blorp>使用extends会继承SLList全部的成员(实例、变量、方法、嵌套的类…)

Java中所有的type都会隐式的extends Object

super的作用

Java的private声明很严格,即使子类也不能访问,所以可以使用super 关键词来使用父类的成员如下:

这样就不用在removeLast()里重复父类的代码了
Ps:这里想做的是在SLList的基础上实现一个可以记住删除了的元素的SSList

这样就不用在removeLast()里重复父类的代码了
Ps:这里想做的是在SLList的基础上实现一个可以记住删除了的元素的SSList

在构造函数里哪怕没写super();也会自动call,但是如果父类有两个构造函数,不写super(x);的话还是会自动callsuper();

截屏2022-07-24 17.51.44.png

Encapsulation 封装

能否管理复杂性是能不能成为一个好的programmer的关键

抽象、模块方便更改、隐藏不需要的信息

抽象、模块方便更改、隐藏不需要的信息

继承对封装的破坏

截屏2022-07-24 18.31.57.png
截屏2022-07-24 18.32.09.png

上面两个图“我”的代码没有变化,但写Dog的人对Dog方法实现进行了改变,
所以在第二张图里VerboseDog.barkMany()->Dog.bark()(由于他的dynamic type是VerboseDog接下来会调用this.barkMany(1) )->VerboseDog.barkMany()这样下去就变了无限循环

Higher Order Functions

把其他函数作为参数的函数

截屏2022-07-24 19.06.26.png

在Java8前不能直接做到,所以需要上图中把函数接口当作参数放进去的操作

~~Subtype Polymorphism vs. HoFs介绍了多态性和**[Inheritance3, Video 6] Comparator等**~~

Set (in Java)

不允许重复,没有顺序(不能说要第几个,但可以说某个东西存不存在在set里)

和List一样也是集合的一种

迭代 Iterable

如果要支持for循环:
如**for** **(int** x **:** javaset**) {**System**.**out**.**println**(**x**);}**

  • 在class里添加一个iterator() 返回Iterator的方法.
  • Iterator 拥有hasNext()和next()方法.
  • 在定义class的行里添加 implements Iterable<T>

当在Java中使用print时会隐式的call toString()System**.**out**.**println**(**javaset**);**

![Java源代码

Java源代码

另外如果把String和不是String的相加(结合),也会隐式的call toString()

toString()的两种实现方式(效率/简洁):

@Override
    public String toString() {
        StringBuilder returnSB = new StringBuilder("{");
        for (int i = 0; i < size - 1; i += 1) {
            returnSB.append(items[i].toString());
            returnSB.append(", ");
        }
        returnSB.append(items[size - 1]);
        returnSB.append("}");
        return returnSB.toString();
    }
		@Override
    public String toString() {
        List<String> listOfItems = new ArrayList<>();
        for (T x : this) {
            listOfItems.add(x.toString());
        }
        return "{" + String.join(", ", listOfItems) + "}";
    } 

子类多态性 Subtype Polymorphism

子类多态性:子类可以利用父类的性质,比如animal都可以叫,而cat是一种animal,那cat.bark()就是继承了父类

实践:

建立自己的比较函数

由于对象不能比较(java不知道比较string、size还是别的什么东西)

截屏2022-08-10 16.26.57.png

为了让我们的对象可以比较,我们可以写一个函数来比较

截屏2022-08-10 16.28.11.png

但是这就引入了新的问题,我们要为每一个类型都写一个max吗?
这样非常冗余比如要写maxDog()、maxCat()、maxBee()……
于是这时候就可以引入interface来保证我们的dog、cat等类型是可以比较的

建立OurComparable接口保证对象可以比较

这里Dog继承了max的要求,所以保证了dog是可以进行比较的并且这里的dog就表现了出了子类多态性

这里Dog继承了max的要求,所以保证了dog是可以进行比较的并且这里的dog就表现了出了子类多态性

注意compareTo的return数字正负和0就表示了大小
第一个是接口、第二个是类型、第三个是返回大的对象的函数
然后是我们使用的代码

注意compareTo的return数字正负和0就表示了大小
第一个是接口、第二个是类型、第三个是返回大的对象的函数
然后是我们使用的代码

截屏2022-08-10 16.57.47.png

另外可以看到把这么一长串优化成一行的解决方式

当然上面还有点小问题比如把Dog和Cat比的话,上面被强制转换为了Dog uddaDog = (Dog) obj;
为了解决这个问题我们其实可以使用官方的Comparables

使用内建的Comparable

使用内建的Comparable

使用内建的Comparable

截屏2022-08-10 17.27.31.png

Comparator

我们想要多种比较的办法,这个时候Comparator就派上用场了
如何做一个comparator参考这里

其他语言可能是传入一个比较函数,但java中我们传入不同的comparator

Comparable表示“我这个对象”是可以被比较的,而Comparator是比较其他两个对象