java中this,cloneable,instanceof详解

2024-06-22 17:08

本文主要是介绍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 预备知识

预备知识
为了理解javaclone,有必要先温习以下的知识。
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以及为什么如此实现

Objectclone的行为是最简单的。以堆上的内存存储解释的话(不计内务内存),对一个对象aclone就是在堆上分配一个和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的东西,会影响到原型的内部。比如例子中,改变copyusername,影响了原型。
如果我们要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的,但是这个是在使用BA中做的,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关键字

instanceofJava的一个二元操作符,和==><是同一类东东。由于它是由字母组成的,所以也是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详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1084894

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听