本文主要是介绍java中this,cloneable,instanceof详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 1 this理解
- 1.1 this基础讲解
- 1.2 this三种用法
- 1.3 用到this的地方
- 2 类名.this与类名.class
- 3 Cloneable
- 3.1 预备知识
- 3.2 为什么要clone
- 3.3 Object的clone以及为什么如此实现
- 3.4 java为什么如此实现clone
- 3.5 如何clone
- 3.6 clone和Serializable的比较
- 4 Java中的instanceof关键字
1 this理解
1.1 this基础讲解
Java
关键字this
只能用于方法方法体内。当一个对象创建后,Java
虚拟机(JVM
)就会给这个对象分配一个引用自身的指针,这个指针的名字就是 this
。因此,this
只能在类中的非静态方法
中使用,静态方法和静态的代码块中绝对不能出现this
。并且this
只和特定的对象关联,而不和类关联,同一个类的不同对象有不同的this
。
package test;
public class ThisTest {private int i=0;//第一个构造器:有一个int型形参ThisTest(int i){this.i=i+1;//此时this表示引用成员变量i,而非函数参数iSystem.out.println("Int constructor i——this.i: "+i+"——"+this.i);System.out.println("i-1:"+(i-1)+"this.i+1:"+(this.i+1));//从两个输出结果充分证明了i和this.i是不一样的!}// 第二个构造器:有一个String型形参ThisTest(String s){System.out.println("String constructor: "+s);}// 第三个构造器:有一个int型形参和一个String型形参ThisTest(int i,String s){this(s);//this调用第二个构造器//this(i);this.i=i++;//this以引用该类的成员变量System.out.println("Int constructor: "+i+"/n"+"String constructor: "+s);}public ThisTest increment(){this.i++;return this;//返回的是当前的对象,该对象属于(ThisTest)}public static void main(String[] args){ThisTest tt0=new ThisTest(10);ThisTest tt1=new ThisTest("ok");ThisTest tt2=new ThisTest(20,"ok again!");System.out.println(tt0.increment().increment().increment().i);//tt0.increment()返回一个在tt0基础上i++的ThisTest对象,//接着又返回在上面返回的对象基础上i++的ThisTest对象!}
}
运行结果:
Int constructor i——this.i: 10——11
String constructor: ok
String constructor: ok again!
Int constructor: 21
String constructor: ok again!
14
1.2 this三种用法
细节问题注释已经写的比较清楚了,这里不在赘述,只是总结一下,其实this主要要三种用法:
- 表示对当前对象的引用
- 表示用类的成员变量,而非函数参数,注意在函数参数和成员变量同名是进行区分,其实这是第一种用法的特例,比较常用,所以那出来强调一下。
- 用于在构造方法中引用满足指定参数类型的构造器(其实也就是构造方法)。但是这里必须非常注意:
只能引用一个构造方法且必须位于开始
!
还有就是注意:this
不能用在static
方法中!所以甚至有人给static
方法的定义就是:没有this
的方法!虽然夸张,但是却充分说明this
不能在static
方法中使用!
1.3 用到this的地方
说明在什么情况下需要用到this
:
- 通过
this
调用另一个构造方法,用法是this
(参数列表),这个仅仅在类的构造方法中,别的地方不能这么用。 - 函数参数或者函数中的局部变量和成员变量同名的情况下,成员变量被屏蔽,此时要访问成员变量则需要用
this.成员变量名
的方式来引用成员变量。当然,在没有同名的情况下,可以直接用成员变量的名字,而不用this
,用了也不为错,呵呵。 - 在函数中,需要引用该函数属类的当前对象时候,直接用
this
。
其实这些用法总结都是从对this是指向对象本身的一个指针
这句话的更深入的理解而来的,死记不然容易忘记而且容易搞错,要理解!
2 类名.this与类名.class
当在内部类中使用this
指的就是内部类的对象, 为了访问外层类对象,就可以使用外层类名.this
来访问
在java
中,每个class
都有一个相应的Class
对象,当编写好一个类,编译完成后,在生成的.class
文件中,就产生一个Class
对象,用来表示这个类的类型信息。获得Class
实例的三种方式:
- 利用对象调用
getClass()
方法获取该对象的Class
实例 - 使用
Class
的静态方法forName()
,用类的名字获取一个Class
实例 - 运用
.class
的方式获取Class
实例,对基本数据类型的封装类,还可以采用.TYPE
来获取对应的基本数据类型的Class
实例。
以下是TestClass.java
代码:
public class TestClass { public static void main(String[] args) { 在运行期间,如果我们要产生某个类的对象,java虚拟机会检测该类型的Class对象是否已被加载 如果没有加载,java虚拟机会根据类的名称找到.class文件并加载它。 当new Point()的时候加载这个类,用forName构造实例的时候也加载该类。 只加载一次 System.out.println("before new Point()"); new Point(); System.out.println("after new Point()"); try { Class.forName("Line"); } catch (Exception e) { e.printStackTrace(); } 利用对象调用getClass()方法获取该对象的Class实例 Point pt = new Point(); Class c1 = pt.getClass(); System.out.println(c1.getName()); 结果:Point 使用Class的静态方法forName(),用类的名字获取一个Class实例 try { Class c2 = Class.forName("Point"); System.out.println(c2.getName()); 结果:Point 一旦某个类型的Class对象已经被加载到内存,就可以用它来产生该类型的所有对象。 Point pp = (Point) c2.newInstance(); newInstance()调用类中缺省的构造方法 pp.output(); } catch (Exception e) { e.printStackTrace(); } 运用.class的方式获取Class实例(类) Class c3 = Point.class; System.out.println(c3.getName()); 结果:Point 运用.calss的方式获取Class实例(基本类型) Class c4 = int.class; System.out.println(c4.getName()); 结果:int 运用.class的方式获取Class实例(基本数据类型的封装类) Class c5 = Integer.TYPE; System.out.println(c5.getName()); 结果:int Class c6 = Integer.class; System.out.println(c6.getName()); 结果:java.lang.Integer }
} class Point { static { System.out.println("loading point"); } void output() { System.out.println("x=" + x + ",y=" + y); } int x, y;
} class Line { static { System.out.println("loading Line"); }
}
3 Cloneable
3.1 预备知识
预备知识
为了理解java
的clone
,有必要先温习以下的知识。
java
的类型,java
的类型分为两大类,一类为primitive
,如int
,另一类为引用类型,如String
,Object
等等。
java
引用类型的存储,java
的引用类型都是存储在堆上的。
public class B { int a; String b; public B(int a, String b) { super(); this.a = a; this.b = b; }
}
对这样一个引用类型的实例,我们可以推测,在堆上它的内存存储形式(除去指向class
的引用,锁的管理等等内务事务所占内存),应该有一个int
值表示a
,以及一个引用,该引用指向b
在堆上的存储空间。
3.2 为什么要clone
有名的GoF
设计模式里有一个模式为原型模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象.
简单的说就是clone
一个对象实例。使得clone
出来的copy
和原有的对象一模一样。
插一个简单使用clone
的例子,如果一个对象内部有可变对象实例的话,public API
不应该直接返回该对象的引用,以防调用方的code
改变该对象的内部状态。这个时候可以返回该对象的clone
。
问题来了,什么叫一模一样。
一般来说,有
x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x)
但是这些都不是强制的。
我们需要什么样的clone
就搞出什么样的clone
好了。
一般而言,我们要的clone
应该是这样的。copy
和原型的内容一样,但是又是彼此隔离的。即在clone
之后,改变其中一个不影响另外一个。
3.3 Object的clone以及为什么如此实现
Object
的clone
的行为是最简单的。以堆上的内存存储解释的话(不计内务内存),对一个对象a
的clone
就是在堆上分配一个和a
在堆上所占存储空间一样大的一块地方,然后把a
的堆上内存的内容复制到这个新分配的内存空间上。
看例子。
class User { String name; int age;
} class Account implements Cloneable { User user; long balance; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); }
} // user.
User user = new User();
user.name = "user";
user.age = 20;
// account.
Account account = new Account();
account.user = user;
account.balance = 10000;
// copy.
Account copy = (Account) account.clone(); balance因为是primitive,所以copy和原型是相等且独立的。
Assert.assertEquals(copy.balance, account.balance);
copy.balance = 20000; 改变copy不影响原型。
Assert.assertTrue(copy.balance != account.balance); user因为是引用类型,所以copy和原型的引用是同一的。
Assert.assertTrue(copy.user == account.user);
copy.user.name = "newName"; 改变的是同一个东西。
Assert.assertEquals("newName", account.user.name);
primitive
的确做到了相等且隔离。
引用类型仅仅是复制了一下引用,copy
和原型引用的东西是一样的。
这个就是所谓的浅copy
了。
要实现深copy
,即复制原型中对象的内存copy
,而不仅仅是一个引用。只有自己动手了。
等等,是不是所有的引用类型都需要深copy呢?
不是!
我们之所以要深copy
,是因为默认的实现提供的浅copy
不是隔离的,换言之,改变copy
的东西,会影响到原型的内部。比如例子中,改变copy
的user
的name
,影响了原型。
如果我们要copy
的类是不可变的呢,如String
,没有方法可以改变它的内部状态呢。
class User implements Cloneable { String name; int age; @Override public Object clone() throws CloneNotSupportedException { return super.clone(); }
} // user.
User user = new User();
user.name = "user";
user.age = 20; // copy
User copy = (User) user.clone(); age因为是primitive,所以copy和原型是相等且独立的。
Assert.assertEquals(copy.age, user.age);
copy.age = 30; 改变copy不影响原型。
Assert.assertTrue(copy.age != user.age); name因为是引用类型,所以copy和原型的引用是同一的。
Assert.assertTrue(copy.name == user.name); String为不可变类。没有办法可以通过对copy.name的字符串的操作改变这个字符串。
改变引用新的对象不会影响原型。
copy.name = "newname";
Assert.assertEquals("newname", copy.name);
Assert.assertEquals("user", user.name);
可见,在考虑clone
时,primitive
和不可变对象类型是可以同等对待的。
3.4 java为什么如此实现clone
也许有以下考虑:
- 效率和简单性,简单的
copy
一个对象在堆上的的内存比遍历一个对象然后内存深copy
明显效率高并且简单。 - 不给别的类强加意义。如果
A
实现了Cloneable
,同时有一个引用指向B
,如果直接复制内存进行深copy
的话,意味着B
在意义上也是支持Clone
的,但是这个是在使用B
的A
中做的,B
甚至都不知道。破坏了B
原有的接口。 - 有可能破坏语义。如果
A
实现了Cloneable
,同时有一个引用指向B
,该B
实现为单例模式,如果直接复制内存进行深copy
的话,破坏了B
的单例模式。 - 方便且更灵活,如果
A
引用一个不可变对象,则内存deep copy
是一种浪费。Shadow copy
给了程序员更好的灵活性。
3.5 如何clone
clone
三部曲:
- 声明实现
Cloneable
接口。 - 调用
super.clone
拿到一个对象,如果父类的clone
实现没有问题的话,在该对象的内存存储中,所有父类定义的field
都已经clone
好了,该类中的primitive
和不可变类型引用也克隆好了,可变类型引用都是浅copy
。 - 把浅
copy
的引用指向原型对象新的克隆体。
给个例子。
class User implements Cloneable { String name; int age; @Override public User clone() throws CloneNotSupportedException { return (User) super.clone(); }
} class Account implements Cloneable { User user; long balance; @Override public Account clone() throws CloneNotSupportedException { Account account = null; account = (Account) super.clone(); if (user != null) { account.user = user.clone(); } return account; }
}
3.6 clone和Serializable的比较
使用Serializable
同样可以做到对象的clone
。但是:
Cloneable
本身就是为clone
设计的,虽然有一些缺点,但是如果它可以clone
的话无疑用它来做clone
比较合适。如果不行的话用原型构造函数,或者静态copy
方法也可以。
Serializable
制作clone
的话,添加了太多其它的东西,增加了复杂性:
- 所有的相关的类都得支持
Serializable
。这个相比支持Cloneable
只会工作量更大 Serializable
添加了更多的意义,除了提供一个方法用Serializable
制作Clone
,该类等于也添加了其它的public API
,如果一个类实现了Serializable
,等于它的2进制
形式就已经是其API
的一部分了,不便于该类以后内部的改动。- 当类用
Serializable
来实现clone
时,用户如果保存了一个老版本的对象2进制
,该类升级,用户用新版本的类反序列化该对象,再调用该对象用Serializable
实现的clone
。这里为了一个clone
的方法又引入了类版本兼容性的问题。不划算。
4 Java中的instanceof关键字
instanceof
是Java
的一个二元操作符,和==
,>
,<
是同一类东东。由于它是由字母组成的,所以也是Java
的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean
类型的数据。举个例子:
String s = "I AM an Object!";
boolean isObject = s instanceof Object;
我们声明了一个String
对象引用,指向一个String
对象,然后用instancof
来测试它所指向的对象是否是Object
类的一个实例,显然,这是真的,所以返回true
,也就是isObject
的值为True
。
instanceof
有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:
public class Bill {//省略细节}public class PhoneBill extends Bill {//省略细节}public class GasBill extends Bill {//省略细节}
在处理程序里有一个方法,接受一个Bill
类型的对象,计算金额。假设两种账单计算方法不同,而传入的Bill
对象可能是两种中的任何一种,所以要用instanceof
来判断:
public double calculate(Bill bill) {if (bill instanceof PhoneBill) {//计算电话账单}if (bill instanceof GasBill) {//计算燃气账单}...}
这样就可以用一个方法处理两种子类。
然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现,这是面向对象编程应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了:
public double calculate(PhoneBill bill) {//计算电话账单}public double calculate(GasBill bill) {//计算燃气账单}
所以,使用instanceof
在绝大多数情况下并不是推荐的做法,应当好好利用多态。
这篇关于java中this,cloneable,instanceof详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!