浅拷贝
public class User {
String name;
int id;
public User() {
}
public User(String name,int id ) {
this.id = id;
this.name = name;
}
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
public class Main {
public static void main(String[] args) {
User u = new User("tx",19);
User a = new User();
a = u;
System.out.println(a);
}
}
打印结果
User{id=19, name='tx'}
这就是浅拷贝 ,创建两个对象 , 第一个对象的引用指向第二个对象 , 第一个对象就被GC回收 ,这只是把user这个引用所保存的对象的内存首地址给到了u , 两个引用指向了同一个对象 ,这就是浅拷贝
深拷贝
如果想要在堆里面有两个一模一样的对象,他们的内存地址不一样
方法1. 构造方法 ,构造两个一模一样的对象
方法2. clone( )
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
重写clone()方法,因为Object的clone()是protected的,它在java.lang包下,我们不可能在lang包下创建应用程序,所以跨包无法访问Object的clone()方法。我们思考一下,为什么要用protected修饰?克隆的本意是复制一个全新的对象出来,脱离了对象,一直调用Object的clone方法是不是不够用啊。所以一定要重写。
第一种方案:先将其做成父类型的引用的指向子类型的对象,然后向下转型。
也就是,重写的方法的返回值类型是Object的。(在本例中)
public class Main {
public static void main(String[] args) {
User u = new User("tx",19);
User a = null;
a = u;
try {
a = (User) u.clone();// clone()之后再强转
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
System.out.println(a);
}
}
第二种方案:
重写的时候可以改变Object类中的clone()方法的返回值类型。从这里我们补充一下重写的规则:
方法的重写一定要有继承关系,这是大前提,没有这个前提没有重写机制。
然后,private修饰的不能重写。重写的时候方法名、参数列表不能变;
访问权限不能更低,只能相等或更高。
这里可以看到,方法的返回值类型是可以改变的,但一定要是父类型或者是父类型派生的子类型。
@Override
public User clone() throws CloneNotSupportedException{
return (User) super.clone();
}
使用的时候,就不用强制类型转换了,因为在这里已经强转了。
第三种方案:用得最多的方式,序列化与反序列化,以后再说。
大概的意思:一个对象是一个实实在在的东西,比如一台电脑是一个对象,它放在你家的书桌上,我们可以把这台电脑一个部件一个部件的拆开,邮寄到学校,然后一个部件一个部件的装上。在内存中,我们发送出去的是一份拷贝。
内部类
类可以嵌套定义 ,即在一个类的类体中可以嵌套定义另一个类 。被嵌套的类称为内部类 , 它的上级被称为外部类 。 内部类中还可以再嵌套一个类 , 最外层的类被称为顶层类 。
除外部类外,其他类不能访问内部类 。当一个类只在某一个类中使用 ,并且不允许除外部类外的其他类访问时 ,可以考虑把该类设计成内部类
内部类的使用
public class Outer {
private int x=10;
class Inner{
private int x = 20;
void print(){
int x = 30;
System.out.println(x); //输出内部类局部变量
System.out.println(this.x); //输出内部类成员变量
System.out.println(Outer.this.x); //输出外部类成员变量
}
}
}
public class Main {
public static void main(String[] args) {
Outer out = new Outer();
Outer.Inner a = out.new Inner();
a.print();
}
}
运行结果
30
20
10
内部类的演变
public interface Work {
void work();
}
public class Outer implements Work {
private int x=10;
@Override
public void work() {
System.out.println("工作");
}
}
public class Main {
public static void main(String[] args) {
Outer out = new Outer();
out.work();
}
}
运行结果
工作
成员内部类:相当于一个成员属性
内部类编译之后得到的.class文件,两个类名之间带有$符号:Animal$AnimalRun.class
注意语法上与正常的类的定义没有区别,只是相当于成员属性。我们以前学过类中有引用类型的成员属性,这种类型的成员属性是不是一定要new出来?
内部类也一样,只是语法上有个包含关系。
OuterClass.InnerClass 标识符 = new OuterClass().new InnerClass();
public class Outer implements Work {
@Override
public void work() {
System.out.println("工作");
}
}
public class Good {
public Outer out;
class In implements Work {
@Override
public void work() {
System.out.println("继续工作");
}
}
}
public class Main {
public static void main(String[] args) {
Outer out = new Outer();
Good good = new Good();
Good.In tx = good.new In();
tx.work();
out.work();
}
}
运行结果
继续工作
工作
静态内部类:相当于一个静态属性
public class Main {
public static void main(String[] args) {
Outer out = new Outer();
Good good = new Good();
//Good.In tx = good.new In();
//tx.work();
Good.In tx = new Good.In();
tx.work();
}
}
public class Good {
public Outer out;
static class In implements Work {
@Override
public void work() {
System.out.println("继续工作");
}
}
}
局部内部类:相当于一个局部变量
public class Good {
public void see()
{
class In implements Work {
@Override
public void work() {
System.out.println("局部内部类运行");
}
}
new In().work();
}
}
public class Main {
public static void main(String[] args) {
Outer out = new Outer();
Good good = new Good();
good.see();
//Good.In tx = good.new In();
//tx.work();
}
}
匿名内部类
重点(很重要) 匿名内部类
思考一个问题:匿名就是没有名字,匿名内部类就是没有名字的内部类,请问一个类没有名字如何实例化对象?
不能实例化对象。
所以,匿名内部类的定义方式为:
new 接口名[或者是父类名] {
这里是实现接口的抽象方法或者是父类中的抽象方法
};后面一定要加上分号。
这就好比是new Outer();
public class Good {
public void see()
{
new Work(){
public void work(){
System.out.println("匿名内部类运行");
}
}.work();
}
}
public class Main {
public static void main(String[] args) {
Outer out = new Outer();
Good good = new Good();
good.see();
//Good.In tx = good.new In();
//tx.work();
}
}
匿名内部类的另外一种调用方式:在调用的方法的时候,以参数的形式写匿名内部类
public interface Work {
void work();
}
public class Good {
public void see(Work worker)
{
worker.work();
}
}
public class Main {
public static void main(String[] args) {
Outer out = new Outer();
Good good = new Good();
good.see(new Work(){
@Override
public void work() {
System.out.println("匿名内部类参数形式的调用");
}
});
}
}
写在方法体中的匿名内部类:
public void test(){
new xxx() {
m1(){
....
}
}.m1();
}
第二种形式:
public void test(Interface i){
i.m1();
}
可以在方法被调用时,直接写在参数列表中。
test(new Interface() {
public void m1() {
....
}
});
这样就不需要再次调用m1方法了。
new Work(){
@Override
public void work() {
System.out.println("匿名内部类---run!!");
}
}.work();
解读一下这种语法:
new 是为了产生一个对象,new Work()这是不成立的,因为Work是接口,接口不能被实例化,所以不能将new Work()理解为new一个接口类型的对象,这是错的!!!!
new Work()在匿名内部类中才能这么写,其他地方不能这么写,写了就错了。这是匿名内部类特有的写法,只属于匿名内部类。意思是:new一个Work接口的实现类,它的实现类是没有名字的,new这个实现类的依据是{}中的代码,这个{}所包裹的代码就是匿名内部类的代码。
{
@Override
public void work() {
System.out.println("匿名内部类---run!!");
}
}
这是一个完整的匿名内部类。他一定要配合new 接口名()来使用,否则无法给它创建对象,因为它连个名字都没有,所以我们只好用它父类的名字来指代它。
并不是new了一个接口类型的对象,没有这种玩法,编译都通不过。
匿名内部类的简化——lambda表达式
good.work(()->{
System.out.println("匿名内部类runrunrun");
});
good.work(()-> System.out.println("faefawefawefawefawefawe"));
// 以上,两种叫做lambda表达式。这个一定要知道怎么来的,要看得懂。否则线程就没法学
ambda表达式的前提:这个非常重要。
函数式接口。什么是函数式接口?只含有一个抽象方法的接口叫做函数式接口
只有这样才能使用lambda表达式。
也就是说:接口中有且仅有一个方法才能使用lambda表达式。