一、Object 中的 equals方法,重要!!!
public class Hello {
public static void main(String[] args) {
User u = new User(10,"小牛");
System.out.println(u);
User p = new User(10,"小牛");
System.out.println(p);
System.out.println(p.equals(u));
}
}
class User {
private int id;
private String name;
public User() {
}
public User(int id, String name){
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
//这种情况输出false
@Override
public boolean equals(Object obj) {
return (this == obj);
}
// equals 比较两个对象是否是一个对象,在这里也就是说 u 和 p
// Object类中原生的方法实现只能比较两个对象的“引用”是否相等,所以这里返回false
//这种情况输出 true
@Override
public boolean equals(Object o) {//形参是Object o,这里传入的实参是User类型的对象引用,Objcet o = new User();
// 在调用equals(Object o)时,实际上传入的是指向了子类型User对象的父类型引用Object o
// 该equals(Object o)是成员方法,该方法中还有一个隐藏的参数this,是第一个形参,equals(Object this, Object o);
// 方法的调用者给这个this传实际值,然后同Object o所指向的实参类型进行比较
if (this == o) return true; //Object类中原生的equals()方法
if (o == null || getClass() != o.getClass()) return false;//如果传入的实参为空,没有指向堆内存中的对象;
// 如果不为null,那么堆中已经new了对象,如果this所代表的对象的类型和传入的实参对象的类型不一样,也就是getClass和o.getClass类型不一样;
User user = (User) o;//由Object o = new User();知道o所指向的是子类型User的对象,接着便是向下转型
// 问:这里问什么没有ClassCastException异常? 答:第三条语句,如果不是一个类型,直接返回false。
return id == user.id && //比较两个整型的数据是否相等??
Objects.equals(name, user.name); //比较两个String类型的name属性是否相等,调用了Objects类中的equals方法,见下 ↓
}
// 这种方式是两个不同引用指向的对象,拥有相同的属性值我们也认定为是相同的对象
public static boolean equals(Object a, Object b) { //Object a = new String(name);
// 首先,上面的 name 和 user.name是String类型的,注意:String类型也是一个引用类型!!!String的父类是Object
// 也就是父类型的引用指向子类型对象
return (a == b) || (a != null && a.equals(b));//a、b同时为null,如果同时为空,id又相等,那两个User类型的对象当然是同一个
// a.equals(b)方法本质上调用的是String中重写的equals方法。 见下 ↓
}
// 补充String中重写的equals方法如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length; //数组有length用法
if (n == anotherString.value.length) {
char v1[] = value; //char[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
}
二、浅深拷贝
2.1 浅拷贝
public class Hello {
public static void main(String[] args) {
User u = new User(1, "小牛");
User user = new User(2, "小美");
u = user;
//这里只是 在栈中的 u 和 user 指向了堆内存中同一个首地址对象User(2, "小美")
System.out.println(u);// User{id=2, name='小美'}
}
}
2.2 深拷贝clone
深拷贝就是重写clone()方法
方法的重写:
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
这里注意一下:因为Object的clone()是protected的,它在java.lang包下,跨包无法访问Object的clone()方法,要导包。
方法一:
先在main里面调用,再强转
也就是,重写的方法的返回值类型还是Object的
public class Hello {
public static void main(String[] args) {
User u = new User(2, "小美");
User user = new User();
try {
user = (User) u.clone();// clone()之后再强转
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(user);
}
}
方法二:
重写的时候可以改变Object类中的clone()方法的返回值类型。
也就是将Object类型变为User类型
(方法的返回值类型可以改变的,但一定要是父类型或者是父类型派生的子类型。)
@Override
public User clone() throws CloneNotSupportedException{
return (User) super.clone();
}
public class Hello {
public static void main(String[] args) {
User u = new User(2, "小美");
User user = new User();
user = u; //就不用强制类型转换了。
System.out.println(user);//User{id=2, name='小美'}
}
}
三、内部类
3.1 成员内部类
内部类编译之后得到的.class文件,两个类名之间带有 符 号 : A n i m a l 符号:Animal 符号:AnimalAnimalRun.class
注意语法上与正常的类的定义没有区别,只是相当于成员属性。我们以前学过类中有引用类型的成员属性,这种类型的成员属性是不是一定要new出来?内部类也一样,只是语法上有个包含关系。
内部类表达调用方法:
OuterClass.InnerClass 标识符 = new OuterClass().new InnerClass();
// 定义一个接口
public interface IRun {
void run();
}
// 写一个接口的实现类
public class Person implements IRun {
@Override
public void run() {
System.out.println("人跑步!");
}
}
// 测试
public class Test {
public static void main(String[] args) {
Person p= new Person();
p.run();// 人跑步
}
}
public class Animal {
public Person p;
class AnimalRun implements IRun {
@Override
public void run() {
System.out.println("动物跑!");
}
}
}
public class Test {
public static void main(String[] args) {
Person p= new Person();
p.run();
第一种:
// Animal animal = new Animal();
// Animal.AnimalRun ar = animal.new AnimalRun();
// ar.run();
第二种:
// OuterClass.InnerClass 标识符 = new OuterClass().new InnerClass();
Animal.AnimalRun ar1 = new Animal().new AnimalRun();
}
}
3.2 静态内部类
相当于一个静态属性
public class Animal {
public Person p;
static class AnimalRun implements IRun {
@Override
public void run() {
System.out.println("动物跑!");
}
}
}
public class Test {
public static void main(String[] args) {
Person p= new Person();
p.run();
相当于一个静态static属性
Animal.AnimalRun ar2 = new Animal.AnimalRun();
ar2.run();
}
}
3.3 局部内部类
相当于一个局部变量
public class Animal {
public void walk() {
相当于一个局部变量
class LocalClass implements IRun {
@Override
public void run() {
System.out.println("局部内部类---run!");
}
}
// new LocalClass().run();
LocalClass lc = new LocalClass();
lc.run();
}
}
public class Test {
public static void main(String[] args) {
Animal ar3= new Animal();
ar3.walk();
}
}
3.4 匿名内部类
匿名内部类就是没有名字的内部类,不能实例化对象。
new 接口名 [或者是父类名] {
实现接口的抽象方法或者是父类中的抽象方法
}; 后面一定要加上分号。
相当于new Person();
public class Animal {
public void walk() {
new IRun(){
@Override
public void run() {
System.out.println("匿名内部类---run!!");
}
};
}
}
调用匿名内部类中的方法,采用new 接口名(){}.方法名();的形式,相当于new Person().run();
new IRun(){
@Override
public void run() {
System.out.println("匿名内部类---run!!");
}
}.run();
匿名内部类的另外一种调用方式:在调用的方法的时候,以参数的形式写匿名内部类。
public interface IRun {
void run();
}
public class Animal {
public void walk(IRun iRun) {
iRun.run();
}
}
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
animal.walk(new IRun(){
@Override
public void run() {
System.out.println("匿名内部类的另外一种调用方式");
}
});
}
}
new IRun这里并不是指new了一个接口类型的对象!!!
意思是:new一个IRun接口的实现类,它的实现类是没有名字的,new这个实现类的依据是{}中的代码,这个{}所包裹的代码就是匿名内部类的代码。
new IRun(){
@Override
public void run() {
System.out.println("匿名内部类---run!!");
}
}
这是一个完整的匿名内部类。他一定要配合new 接口名()来使用,否则无法给它创建对象。
3.5 匿名内部类的简化——lambda
lambda表达式的前提————要是函数式接口
函数式接口: 只含有一个抽象方法的接口叫做函数式接口。
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
----------------------------------------------------------------------------
没简化的时候 ↓
----------------------------------------------------------------------------
animal.walk(new IRun(){
@Override
public void run() {
System.out.println("匿名内部类的另外一种调用方式");
}
});
----------------------------------------------------------------------------
对比一下,简化之后的表达式样式,叫做lambda表达式,该省的省 ↓
----------------------------------------------------------------------------
animal.walk(()->{
System.out.println("匿名内部类runrunrun");
});
animal.walk(()-> System.out.println("faefawefawefawefawefawe"));
----------------------------------------------------------------------------
}
}
带参数的lambda表达式
interface IEat {
// @param str 表示吃早饭还是中饭、晚饭的描述
void eat(String str);
}
public class Person {
public void dinner(IEat iEat,String str) {
iEat.eat(str);
}
public static void main(String[] args) {
Person p= new Person();
p.dinner(str-> System.out.println(str),"晚餐");
}
}
总结:千万注意(提醒我自个儿),匿名内部类的特殊用法,不是不是new了一个接口对象哈