类(Class)
类是一个模板,它包含一个对象的一些特征,和行为。
举个例子,我们是人。
我们可以有姓名、年龄、性别、身高等等属性、而我们的行为就是吃、看、闻等等方法。
这里只是简单的举个例子,实际上会有很多。
然后我们来创建一个猫的类
public class Cat {
//姓名
private String name;
//性别
private String sex;
//重量
private String weight;
//品种
private String species;
//年龄
private String old;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getWeight() {
return weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public String getOld() {
return old;
}
public void setOld(String old) {
this.old = old;
}
public Cat(String name, String sex, String weight, String species, String old) {
this.name = name;
this.sex = sex;
this.weight = weight;
this.species = species;
this.old = old;
}
public Cat() {
}
@Override
public String toString() {
return "Cat [name=" + name + ", old=" + old + ", sex=" + sex + ", species=" + species + ", weight=" + weight
+ "]";
}
}
创建对象
public class Demo {
public static void main(String[] args) {
//现在我们就使用了猫类创建了两个猫的实例对象
Cat catOne = new Cat();
Cat catTwo = new Cat();
//给它们设一个名字
catOne.setName("猫一");
catTwo.setName("猫二");
}
}
对象的引用
public class Demo {
public static void main(String[] args) {
//我们给catOne创建一个实例
Cat catOne = new Cat();
Cat catTwo;
//然后为catOne设置属性
catOne.setName("猫一");
catOne.setSex("公");
catOne.setWeight("十斤");
catOne.setSpecies("中华田园猫");
catOne.setOld("七岁");
//我们将catOne的引用赋给catTwo
catTwo = catOne;
//此时我们输出catTwo
System.out.println(catTwo.toString());
//然后我们更改catOne实例中的数据
catOne.setName("猫三");
//在把catTwo输出
System.out.println(catTwo);
}
}
我们运行上面的代码,发现输出时会有一些问题。
我们更改的明明是catOne的名字,为什么catTwo的名字也跟着一块更改了呢?
catTwo = catOne;
原因就出在这行代码上。
我们的catTwo并不是通过new 关键字创建出来的,而是通过catOne直接赋值。
那么就会导致catOne把自己的引用地址赋给了catTwo,所以catOne和catTwo指向的其实是一块内存。
其中一个对象更改,就会导致引用该地址的对象全部发生变化。
toString()
细心的小伙伴可能发现了我们在输出时的代码不一样一个是catOne.toString()一个是catTwo但是输出并没有出现什么问题,这是因为Java在直接输出对象时会调用toString()方法。
这样会出现什么问题呢?
public class Cat {
private String name;
private String sex;
private String weight;
private String species;
private String old;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getWeight() {
return weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public String getOld() {
return old;
}
public void setOld(String old) {
this.old = old;
}
public Cat(String name, String sex, String weight, String species, String old) {
this.name = name;
this.sex = sex;
this.weight = weight;
this.species = species;
this.old = old;
}
public Cat() {
}
//我们在原来的代码上稍微改动一下
//后面加了一个this,指的就是当前对象
@Override
public String toString() {
return "Cat [name=" + name + ", old=" + old + ", sex=" + sex + ", species=" + species + ", weight=" + weight
+ "]" + this ;
}
}
然后我们在运行Demo类。
其实我们写这段代码的本意可能是在输出当前类信息的基础上在输出一个当前实例类名加哈希码(类似这种,Demo@304e94a4)但是因为我们重写了toString方法,然后我们在toString时还对本身的对象进行了输出,就导致this会继续执行toString方法。
所以就造成了死循环。
说着说着就跑题了。
父类Object
任何一个类都有一个父类Object
Object类中有一些方法,感兴趣的小伙伴可以了解一下。
向上转型
public class Demo {
public static void main(String[] args) {
//我们给catOne创建一个实例
Cat catOne = new Cat();
catOne.setName("猫一");
catOne.setSex("公");
catOne.setWeight("十斤");
catOne.setSpecies("中华田园猫");
catOne.setOld("七岁");
//向上转型
Object obj = catOne;
System.out.println(obj.toString());
}
}
我们把一个Cat类型赋给一个Object对象,这是我们在使用toString方法。
可以看到依然可以使用。
但是我们如果使用只有在Cat类中才能使用的方法会怎么样呢?
我们在输出下面添加一些代码。
public class Demo {
public static void main(String[] args) {
//我们给catOne创建一个实例
Cat catOne = new Cat();
catOne.setName("猫一");
catOne.setSex("公");
catOne.setWeight("十斤");
catOne.setSpecies("中华田园猫");
catOne.setOld("七岁");
//向上转型
Object obj = catOne;
System.out.println(obj.toString());
obj.setName("猫二");
System.out.println(obj.toString());
}
}
可以看到并不能通过编译。
我们在来改动一下代码。
public class Demo {
public static void main(String[] args) {
//我们给catOne创建一个实例
Cat catOne = new Cat();
catOne.setName("猫一");
catOne.setSex("公");
catOne.setWeight("十斤");
catOne.setSpecies("中华田园猫");
catOne.setOld("七岁");
//向上转型
Object obj = catOne;
System.out.println(obj.toString());
//转换回来
Cat catTwo = (Cat) obj;
catTwo.setName("猫二");
System.out.println(obj.toString());
}
}
将Object对象转换回Cat就又可以使用了。
结论,我们在向上转型时,子类会覆盖父类的方法,并且只能使用父类中存在的方法,但是可以调用子类中的成员变量。
这就是为什么我们使用toString输出Cat类对象时成员变量里的内容也可以正常输出的原因。
如何复制一个对象而不是复制一个引用呢?
浅拷贝
我们在稍微修改一下Cat类,给Cat类添加一个Cloneable的接口用来实现我们的拷贝。
并且在Cat类中添加一个“猫孩子”的成员变量。
import java.lang.Cloneable;
public class Cat implements Cloneable{
public String name;
private String sex;
private String weight;
private String species;
private String old;
private Cat child;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getWeight() {
return weight;
}
public void setWeight(String weight) {
this.weight = weight;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public String getOld() {
return old;
}
public void setOld(String old) {
this.old = old;
}
public Cat(String name, String sex, String weight, String species, String old, Cat child) {
this.name = name;
this.sex = sex;
this.weight = weight;
this.species = species;
this.old = old;
this.child = child;
}
@Override
public String toString() {
return "Cat [child=" + child + ", name=" + name + ", old=" + old + ", sex=" + sex + ", species=" + species
+ ", weight=" + weight + "]";
}
public Cat getChild() {
return child;
}
public void setChild(Cat child) {
this.child = child;
}
public Cat() {
}
@Override
public Cat clone() {
try {
return (Cat)super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
public class Demo {
public static void main(String[] args) {
//浅拷贝
Cat catChild = new Cat("猫一的孩子", "不明", "二十斤", "英国短毛猫", "三岁", null);
Cat catOne = new Cat();
catOne.setName("猫一");
catOne.setSex("公");
catOne.setWeight("十斤");
catOne.setSpecies("中华田园猫");
catOne.setOld("七岁");
catOne.setChild(catChild);
//猫一的孩子
System.out.println("先输出猫一");
System.out.println(catOne);
//复制catOne 给 catTwo
Cat catTwo = catOne.clone();
//修改catTwo
System.out.println("输出修改后的猫二");
catTwo.setName("猫二");
catChild.setName("猫二的孩子");
System.out.println(catTwo);
//在输出catOne
System.out.println("在输出猫一");
System.out.println(catOne);
}
}
运行上面的代码。
看到这些数据可以知道,如果成员变量的属性是基本数据类型(包括包装类)那么复制的就是数值,如果成员变量是引用类型那么复制的就是内存地址(比如:“猫孩子成员”)。
这里可能你会有一些疑问,String和Integer这些类型不也是类吗?
为什么他们不会复制地址,而Cat会被复制地址呢?
我们打开String类的源码。
可以看到他的成员变量全部都被final修饰符修饰,也就证明他不可以被修改,每次都是在内存中创建新的对象。
深拷贝
其实深拷贝的实现非常的简单。
public Object clone() {
//我们重写Clone方法
Cat cat;
if(this.child==null) {
cat = new Cat(this.name, this.sex, this.weight, this.species, this.old, null);
} else {
cat = new Cat(this.name, this.sex, this.weight, this.species, this.old, this.child.clone());
}
return cat;
}
深拷贝还有很多实现方法,比如序列化也可以实现深拷贝。