Java基础篇(一)

本篇主要涉及Java基础部分中一些较难以理解的部分,主要涉及到 内部类、包装类、String相关、异常等

内部类

(非)静态内部类

public class TestInnerClass {	
public static void main(String[] args) {
		Face f = new Face();
		//非静态内部类,必须要先创建外部类,再创建内部类
    	Face.Nose n = f.new Nose();
    	n.breath();
		//静态内部类,不必先创建外部类再创建内部类
		Face.Ear e = new Face.Ear();    
    	e.listen();
	}	
	}

	class Face{
	// 外部类的属性
	String type = "瓜子脸";
	static String color = "Red";

	/**
   * 非静态内部类:必须要先创建外部类,再创建内部类,可以看成是一个对象的成员属性
 	*/
	class Nose{
    	//内部类不能定义成static的方法和属性
    	String type;
    	void breath(){
        	System.out.println(Face.this.type);  //内部类可以调用外部类的内容
       	 System.out.println("呼吸");
    	}
	}

/**
 * 静态内部类:当一个静态内部类存在,并不一定存在对应的外部类对象
 */
static class Ear{
    //静态内部类的实例方法不能直接访问外部类的实例方法,因为外部类不一定存在
    void listen(){
        System.out.println(color); //可访问内部类的静态属性
        System.out.println("听");
    }
}
}

输出结果如下

瓜子脸
呼吸
Red
听

从结果我们可以看出:
内部类既可以访问自身的数据域,也可以访问他的外围类对象的数据域(如:Face.this.type)
编写内部类构造器这一方面,可以使用语法
outerObject.new InnerClass(Construction parm); 例如 Face.Nose n = f.new Nose();
特别注意:内部类不能有static方法
局部类不能用public或private访问说明符进行,它的作用域被限制在声明这个局部类的块中-->优势:对外部世界可以完全隐藏起来

匿名内部类

将局部内部类进一步深入,假如只创建这个类的一个对象,就不必命名了,这种类被称为“匿名内部类”
详细用法-->Click


包装类

java中的数据类型int,double等不是对象,无法通过向上转型获取到Object提供的方法,而像String却可以,只因为String是一个对象而不是一个类型。基本数据类型由于这样的特性,导致无法参与转型,泛型,反射等过程。为了弥补这个缺陷,java提供了包装类。

具体内容Click->here


常用类

Date()类

public class TestDate {
  	public static void main(String[] args) {
      	Date d = new Date(2000);    
      	System.out.println(d);//Thu Jan 01 08:00:02 CST 1970
      	System.out.println(d.getTime());//2000

      	Date d2 = new Date();
      	System.out.println(d2.getTime());//1569572482084
      	System.out.println(d2.after(d));//true
  	}
}

Calendar类

public class TestCalendar {
	public static void main(String[] args) {

		//获得日期相关元素
		Calendar calendar = new GregorianCalendar(2999,10,9,22,10,50);
		int year = calendar.get(Calendar.YEAR);
		int month = calendar.get(Calendar.MONTH);  //获得月份
		int weekday = calendar.get(Calendar.DAY_OF_WEEK); //星期几

		//System.out.println(year);
		//System.out.println(month);  //0-11表示对应的月份,0是1月...
		//System.out.println(weekday); //1-7 1->星期天

		//设置日期相关元素
		Calendar calendar2 = new GregorianCalendar();
		//System.out.println(calendar2);  //不传参数,默认打印出今天的日期
		calendar2.set(Calendar.YEAR,2020);
		//System.out.println(calendar2);
		//日期的计算
		Calendar calendar3 = new GregorianCalendar();
		calendar3.add(Calendar.YEAR,-100);//将该时间向前100年,第一个参数控制加减的是年/月/日,第二参数控制加减的大小
		//System.out.println(calendar3);

		//日期对象和时间对象的转换
		Date date = calendar3.getTime();
		Calendar calendar4 = new GregorianCalendar();
		calendar4.setTime(new Date());
		printCalendar(calendar4);
	}

	//打印日期的方法进行封装
	private static void printCalendar(Calendar calendar){
		int year = calendar.get(Calendar.YEAR);
		int month = calendar.get(Calendar.MONTH) + 1;
		int date = calendar.get(Calendar.DAY_OF_MONTH);
		int dayWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1;
		String dayWeek2 = dayWeek == 0 ? "日" :dayWeek + "";  //对星期进行处理
		int hour = calendar.get(Calendar.HOUR);
		int minute = calendar.get(Calendar.MINUTE);
		int second = calendar.get(Calendar.SECOND);

		System.out.println(year + "年" + month + "月" + date + "日" + hour + "时" + minute + "分" + second + "秒"
							+ "  周" + dayWeek2);
	}
}

DateFormat类

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
* @Author: Rick
* @Date: 2019/1/28 18:38
* @Description: 测试时间对象和字符串之间的互相转换
*                DateFormat抽象类的SimpleDateFormat实现类的使用
*/
public class TestDateFormat {

	public static void main(String[] args) throws ParseException {

		//把时间对象按照“格式字符串指定的格式”转成相应的字符串
		DateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");  // SimpleDateFormat()是DateFormat()的子类
		String str = df.format(new Date(4000000));
		System.out.println(str);  //   打印已经转化成了的字符串 1970-01-01 09:06:40

		//把字符串按照“格式字符串指定的格式”转成相应的时间对象
		DateFormat df2 = new SimpleDateFormat("yyyy年MM月dd日hh时mm分ss秒");
		Date date = df2.parse("2019年01月28日18时54分40秒");
		System.out.println(date);  //打印字符串转换成的日期  Mon Jan 28 18:54:40 CST 2019

		//测试其他的格式字符。比如:利用D,获得本时间对象是所处年份的第几天
		DateFormat df3 = new SimpleDateFormat("D");
		String str3 = df3.format(new Date());
		System.out.println(str3);  //29
	}
}

Math类

public class TestMath {
	public static void main(String[] args) {
		//取整相关操作
		System.out.println(Math.ceil(3.2));   //4.0
		System.out.println(Math.floor(3.2));  //3.0
		System.out.println(Math.round(3.2));  //3
		System.out.println(Math.round(3.8));  //4

		//绝对值、开方、幂的操作
		System.out.println(Math.abs(-45));  //45
		System.out.println(Math.sqrt(64));  //8.0
		System.out.println(Math.pow(5,2));  //25.0
		System.out.println(Math.pow(2,5));  //32.0

		//常量
		System.out.println(Math.PI);  //3.141592653589793
		System.out.println(Math.E);   //2.718281828459045

		//随机数
		System.out.println(Math.random()); // [0,1)之间的随机数
	}
}

Random类

public class TestRandom {
	public static void main(String[] args) {
		Random rand = new Random();
		System.out.println(rand.nextDouble());  //随机生成[0,1)之间的double类型的数据
		System.out.println(rand.nextInt());     //随机生成int类型允许范围之内的整型数据
		System.out.println(rand.nextFloat());   //随机生成[0,1)之间的float类型的数据
		System.out.println(rand.nextBoolean()); //随机生成false或true
		System.out.println(rand.nextInt(10));  //随机生成[0,10)之间的int类型的数据
		System.out.println(20 + rand.nextInt(10));  //随机生成[20,30)之间的int类型的数据
		System.out.println(20 + (int)(rand.nextDouble() * 10));  //随机生成[20,30)之间的int类型的数据,较为复杂
	}
}

String、StringBuffer、StringBuilder

String常见问题

StringBuffer和StringBuilder

StringBuffer和Stringbuilder都继承自AbstractStringBuilder类,在AbstractStringBuilder中也是使用字符数组保存字符串 char [] value但是没有用final关键字修饰,所以这两种对象都是可变的

三者之间的比较

1.线程安全性

String 中的对象是不可变的,也就可以理解为常量,线程安全。
AbstractStringBuilder是StringBuilder 与 StringBuffer 的公共父类,定义了一些字符串的基本操作,如 expandCapacity、append、insert、indexOf 等公共方法。
StringBuffer 对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder并没有对方法进行加同步锁,所以是非线程安全

2.性能

每次对 String 类型进行改变的时候,都会生成一个新的 String 对象,然后将指针指向新的 String 对象。StringBuffer 每次都会对 StringBuffer 对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用 StringBuilder 相比使用 StringBuffer 仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险。

三者使用的总结

操作少量的数据 = String
单线程 操作字符串缓冲区下操作大量数据 = StringBuilder
多线程 操作字符串缓冲区下操作大量数据 = StringBuffer


Java中的传参机制

首先回顾一下在程序设计语言中有关将参数传递给方法(或函数)的一些专业术语。按值调用(call by value)表示方法接收的是调用者提供的值,而按引用调用(call by reference)表示方法接收的是调用者提供的变量地址。一个方法可以修改传递引用所对应的变量值,而不能修改传递值调用所对应的变量值。 它用来描述各种程序设计语言(不只是Java)中方法参数传递方式。

Java程序设计语言总是采用按值调用。也就是说,方法得到的是所有参数值的一个拷贝,也就是说,方法不能修改传递给它的任何参数变量的内容。

参考文章(一 为什么 Java 中只有值传递?) ---> Click


Error、Exception、RuntimeException

三者联系

首先搞清楚他们三者之间的关系,Error和Exception都继承自Throwable,而RuntimeException继承Exception。在Java中只有Throwable类型的实例才可被抛出或捕获

Error类层次结构描述了Java运行时系统内部错误和资源耗尽错误。应用程序不应该抛出这种类型的对象。

在设计Java程序时,需要关注Exception的层次结构。这个层次结构又分解为两个分支:一个分支派生于RuntimeException;另一个分支包含其他异常。程序错误导致的异常属于RuntimeException;而程序本身没有问题,但像I/O错误这类问题导致的异常属于其他异常。

其中常见的RuntimeException有:

  • NullPointerException:见的最多了,其实很简单,一般都是在null对象上调用方法了
  • NumberFormatException:继承IllegalArgumentException,字符串转换为数字时出现。比如int i= Integer.parseInt("ab3");
  • ArrayIndexOutOfBoundsException:数组越界。比如 int[] a=new int[3]; int b=a[3];
  • StringIndexOutOfBoundsException:字符串越界。比如 String s="hello"; char c=s.chatAt(6);
  • ClassCastException:类型转换错误。比如 Object obj=new Object(); String s=(String)obj;
  • ArithmeticException:算术错误,典型的就是0作为除数的时候。
  • IllegalArgumentException:非法参数,在把字符串转换成数字的时候经常出现的一个异常,我们可以在自己的程序中好好利用这个异常。

Error类或RuntimeException类的所有异常称为非受查异常,所有其他的异常称为受查异常

如何声明异常

如果遇到了无法处理的情况,那么Java方法可以抛出异常。
例如:

public FileInputStream(String name) throws FileNotFoundException

这个声明表示这个构造器将根据给定的String参数产生一个FileInputStream对象,但也有可能抛出一个FileNotFoundException异常。如果发生了这种糟糕的情况,构造器将不会初始化一个新的FileInputStream对象,而是抛出一个FileNotFoundException类对象。如果这个方法真的抛出了这样一个异常对象,运行时系统就会开始搜索异常处理器,以便知道如何处理FileNotFoundException对象。

什么时候使用throws子句声明异常?通常有以下几种情况

  • 调用一个受查异常的方法,例如FileInputStream构造器
  • 程序运行过程中发生错误
  • 程序出现错误
  • Java虚拟机和运行时库出现内部错误

警告 如果在子类中覆盖了一个超类的方法,子类方法中声明的受查异常不能比超类方法中声明的异常更加通用(也就是说:子类方法中可以抛出更特定的异常,或者根本不抛出任何异常)

如何抛出异常

首先看一个例子

	String readData(Scanner in) throws EOFException
	{
		...
		while(...)
		{
			if(!in.hasNext())
			{
				if(n < len)
					throw new EOFException();
			}
			...
		}
		return s;
	}

因此我们可以看到,对于一个已经存在的异常类,将其抛出很容易
1.找到一个合适的异常类
2.创建这个类的一个对象
3.将对象抛出

throw和throws关键字对比

1、throw用在方法体内,上面代码显示了,是直接在main方法体内,throws用在方法声明后面,表示再抛出异常,由该方法的调用者来处理。
2、throw是具体向外抛异常的,抛出的是一个异常实例,throws声明了是哪种类型的异常,使它的调用者可以捕获这个异常
3、throw如果执行了,那么一定是抛出了某种异常了,throws表示可能出现,但不一定抛出了异常。
4、同时出现的时候,throws出现在函数头、throw出现在函数体,两种不会由函数去处理,真正的处理由函数的上层调用处理


自定义异常

2种自定义异常的方法

/**
 * @Author: Rick
 * @Date: 2019/1/30 17:30
 * @Description: 自定义异常方式
 */
public class Test02 {
    public static void main(String[] args) {
        Person p = new Person();
        p.setAge(-10);
    }
}

class Person{
    private int age;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if(age < 0){
            try {
                throw new IllegalException("年龄不能为负");
            } catch (IllegalException e) {
                e.printStackTrace();
            }
        }
        this.age = age;
    }
}


//自己定义异常
// 方式1继承RuntimeException,可以不用在代码中try/catch进行异常处理,遇到异常,程序中断
class IllegalException extends RuntimeException{
    public IllegalException(){

    }

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

//方式2继承Exception,必须在代码中对异常进行try/catch处理,这样才能通过编译
class IllegalException extends Exception {
    public IllegalException() {

    }

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

结果如下:

Rick_03_Exception.IllegalException: 年龄不能为负
	at Rick_03_Exception.Person.setAge(Test02.java:25)
	at Rick_03_Exception.Test02.main(Test02.java:11)

Process finished with exit code 0

NoClassDefFoundError和ClassNotFoundException有什么区别?

NoClassDefFoundError是当Java虚拟机或ClassLoader实例试图加载某个类,但无法找到该类的定义时抛出此异常
ClassNotFoundException是当应用程序试图调用Class.forName(String)通过字符串名加载类,而找不到该类定义时抛出的异常。


equals()和==的区别

== : 它的作用是判断两个对象的地址是不是相等。即,判断两个对象是不是同一个对象(基本数据类型 == 比较的是值,引用数据类型 == 比较的是内存地址)。

equals() : equals用来比较的是两个对象的内容是否相等,由于所有的类都是继承自java.lang.Object类的,所以适用于所有对象,如果没有对该方法进行覆盖的话,调用的仍然是Object类中的方法,而Object中的equals方法返回的却是==的判断。请看Object类中equals方法的源码:

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

我们来看一个例子:

public class Test01 {
    public static void main(String[] args) {
    Person p1 = new Person("Rick",1001);
    Person p2 = new Person("Rick", 1001);
    System.out.println(p1.equals(p2)); 		//false
    }
}

class Person{
    String name;
    int ID;

    public Person() {
    }

    public Person(String name, int ID) {
        this.name = name;
        this.ID = ID;
    }
}

这里由于没有重写继承java.lang.Object类中的equals()方法,我们比较的是这两个对象的地址(==方法)因此出现的是false

我们再重写equals()方法看下测试结果:

public class Test02 {
    public static void main(String[] args) {
        Student stu1 = new Student("Rick",20);
        Student stu2 = new Student("Rick",20);
        System.out.println(stu1.equals(stu2));      //true
        System.out.println(stu1 == stu2);       //false
    }
}

class Student{
    String name;
    int age;

    public Student() {
    }

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

    /**
     * 重写equals()方法
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj) {
        if (obj == null)
            return false;
        if (this == obj)
            return true;
        if (this.getClass() != obj.getClass())
            return false;

        Student stu = (Student)obj;
        return (name.equals(stu.name) && age == stu.age);
    }
}

实际上我们再String类型中使用的equals()方法,已经是被重写过了-->参考博文

public class test1 {
    public static void main(String[] args) {
        String a = new String("ab"); // a 为一个引用
        String b = new String("ab"); // b为另一个引用,对象的内容一样
        String aa = "ab"; // 放在常量池中
        String bb = "ab"; // 从常量池中查找
        if (aa == bb) // true
            System.out.println("aa==bb");
        if (a == b) // false,非同一对象
            System.out.println("a==b");
        if (a.equals(b)) // true
            System.out.println("aEQb");
        if (42 == 42.0) { // true
            System.out.println("true");
        }
    }
}

在这里我们特别注意创建字符串String时

String s="abce"是一种非常特殊的形式,new 有本质的区别。它是java中唯一不需要new 就可以产生对象的途径。以String s="abce"形式赋值在java中叫直接量它是在常量池中而不是像new一样放在压缩堆中。这种形式的字符串,在JVM内部发生字符串拘留,即当声明这样的一个字符串后,JVM会在常量池中先查找有有没有一个值为"abcd"的对象,如果有,就会把它赋给当前引用.即原来那个引用和现在这个引用指点向了同一对象,如果没有,则在常量池中新创建一个"abcd",下一次如果有String s1 = "abcd";又会将s1指向"abcd"这个对象,即以这形式声明的字符串,只要值相等,任何多个引用都指向同一对象.
  而String s = new String("abcd");和其它任何对象一样.每调用一次就产生一个对象,只要它们调用。


HashCode()和equals()区别

推荐阅读:Java hashCode() 和 equals()的若干问题解答

hashCode() 的作用是获取哈希码,也称为散列码;它实际上是返回一个int整数 。这个哈希码的作用是确定该对象在哈希表中的索引位置。

hashCode() 定义在JDK的Object.java中,这就意味着Java中的任何类都包含有hashCode() 函数
虽然,每个Java类都包含hashCode() 函数。但是,仅仅当创建并某个“类的散列表”(关于“散列表”见下面说明)时,该类的hashCode() 才有用(作用是:确定该类的每一个对象在散列表中的位置;其它情况下(例如,创建类的单个对象,或者创建类的对象数组等等),类的hashCode() 没有作用。
上面的散列表,指的是:Java集合中本质是散列表的类,如HashMap,Hashtable,HashSet。

HashCode()和equals()的关系

我们以 类的用途 来将“hashCode() 和 equals()的关系”分2种情况来说明。

不会创建“类对应的散列表”

这里所说的“不会创建类对应的散列表”是说:我们不会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,不会创建该类的HashSet集合。在这种情况下,该类的“hashCode() 和 equals() ”没有半毛钱关系的!

import java.util.*;
import java.lang.Comparable;

/**
* @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class NormalHashCodeTest{
	
	public static void main(String[] args) {
	// 新建2个相同内容的Person对象,
	// 再用equals比较它们是否相等
	Person p1 = new Person("eee", 100);
	Person p2 = new Person("eee", 100);
	Person p3 = new Person("aaa", 200);
	System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
	System.out.printf("p1.equals(p3) : %s; p1(%d) p3(%d)\n", p1.equals(p3), p1.hashCode(), p3.hashCode());
	
	}

	/**
	* @desc Person类。
	*/
	private static class Person {
		int age;
		String name;

		public Person(String name, int age) {
			this.name = name;
			this.age = age;
		}

		public String toString() {
			return name + " - " +age;
		}

		/** 
		* @desc 覆盖equals方法 
		*/  
		public boolean equals(Object obj){  
			if(obj == null){  
				return false;  
			}  
			
			//如果是同一个对象返回true,反之返回false  
			if(this == obj){  
				return true;  
			}  
			
			//判断是否类型相同  
			if(this.getClass() != obj.getClass()){  
				return false;  
			}  
			
			Person person = (Person)obj;  
			return name.equals(person.name) && age==person.age;  
		} 
	}
}

得到结果如下:

p1.equals(p2) : true; p1(1169863946) p2(1901116749)
p1.equals(p3) : false; p1(1169863946) p3(2131949076)

从结果也可以看出:p1和p2相等的情况下,hashCode()也不一定相等。

会创建“类对应的散列表”

这里所说的“会创建类对应的散列表”是说:我们会在HashSet, Hashtable, HashMap等等这些本质是散列表的数据结构中,用到该类。例如,会创建该类的HashSet集合。

在这种情况下,该类的“hashCode() 和 equals() ”是有关系的:
1)、如果两个对象相等,那么它们的hashCode()值一定相同。
这里的相等是指,通过equals()比较两个对象时返回true。
2)、如果两个对象hashCode()相等,它们并不一定相等。
因为在散列表中,hashCode()相等,即两个键值对的哈希值相等。然而哈希值相等,并不一定能得出键值对相等。补充说一句:“两个不同的键值对,哈希值相等”,这就是哈希冲突
我们来看一个例子:

import java.util.*;
import java.lang.Comparable;

/**
* @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class ConflictHashCodeTest1{

	public static void main(String[] args) {
		// 新建Person对象,
		Person p1 = new Person("eee", 100);
		Person p2 = new Person("eee", 100);
	Person p3 = new Person("aaa", 200);

		// 新建HashSet对象 
		HashSet set = new HashSet();
		set.add(p1);
		set.add(p2);
	set.add(p3);

		// 比较p1 和 p2, 并打印它们的hashCode()
		System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
		// 打印set
		System.out.printf("set:%s\n", set);
}

	/**
	* @desc Person类。
	*/
	private static class Person {
		int age;
	String name;

		public Person(String name, int age) {
			this.name = name;
			this.age = age;
	}

		public String toString() {
			return "("+name + ", " +age+")";
	}

		/** 
		* @desc 覆盖equals方法 
		*/  
		@Override
		public boolean equals(Object obj){  
			if(obj == null){  
				return false;  
			}  
			
			//如果是同一个对象返回true,反之返回false  
			if(this == obj){  
				return true;  
			}  
			
			//判断是否类型相同  
			if(this.getClass() != obj.getClass()){  
				return false;  
			}  
			
			Person person = (Person)obj;  
			return name.equals(person.name) && age==person.age;  
		} 
	}
}

我们看到结果:

p1.equals(p2) : true; p1(1169863946) p2(1690552137)
set:[(eee, 100), (eee, 100), (aaa, 200)]

结果分析:


我们重写了Person的equals()。但是,很奇怪的发现:HashSet中仍然有重复元素:p1 和 p2。为什么会出现这种情况呢?
这是因为虽然p1 和 p2的内容相等,但是它们的hashCode()不等;所以,HashSet在添加p1和p2的时候,认为它们不相等。

下面我们同时覆盖equals() 和 hashCode()方法
	import java.util.*;
import java.lang.Comparable;

/**
* @desc 比较equals() 返回true 以及 返回false时, hashCode()的值。
*
* @author skywang
* @emai kuiwu-wang@163.com
*/
public class ConflictHashCodeTest2{

	public static void main(String[] args) {
		// 新建Person对象,
		Person p1 = new Person("eee", 100);
		Person p2 = new Person("eee", 100);
		Person p3 = new Person("aaa", 200);
	Person p4 = new Person("EEE", 100);

		// 新建HashSet对象 
		HashSet set = new HashSet();
		set.add(p1);
		set.add(p2);
	set.add(p3);

		// 比较p1 和 p2, 并打印它们的hashCode()
		System.out.printf("p1.equals(p2) : %s; p1(%d) p2(%d)\n", p1.equals(p2), p1.hashCode(), p2.hashCode());
		// 比较p1 和 p4, 并打印它们的hashCode()
		System.out.printf("p1.equals(p4) : %s; p1(%d) p4(%d)\n", p1.equals(p4), p1.hashCode(), p4.hashCode());
		// 打印set
		System.out.printf("set:%s\n", set);
}

	/**
	* @desc Person类。
	*/
	private static class Person {
		int age;
	String name;

		public Person(String name, int age) {
			this.name = name;
			this.age = age;
	}

		public String toString() {
			return name + " - " +age;
	}

		/** 
		* @desc重写hashCode 
		*/  
		@Override
		public int hashCode(){  
			int nameHash =  name.toUpperCase().hashCode();
			return nameHash ^ age;
	}

		/** 
		* @desc 覆盖equals方法 
		*/  
		@Override
		public boolean equals(Object obj){  
			if(obj == null){  
				return false;  
			}  
			
			//如果是同一个对象返回true,反之返回false  
			if(this == obj){  
				return true;  
			}  
			
			//判断是否类型相同  
			if(this.getClass() != obj.getClass()){  
				return false;  
			}  
			
			Person person = (Person)obj;  
			return name.equals(person.name) && age==person.age;  
		} 
     }
}

运行结果

p1.equals(p2) : true; p1(68545) p2(68545)
p1.equals(p4) : false; p1(68545) p4(68545)
set:[aaa - 200, eee - 100]

结果分析:

这下,equals()生效了,HashSet中没有重复元素。
比较p1和p2,我们发现:它们的hashCode()相等,通过equals()比较它们也返回true。所以,p1和p2被视为相等。
比较p1和p4,我们发现:虽然它们的hashCode()相等;但是,通过equals()比较它们返回false。所以,p1和p4被视为不相等。

赞赏