java-封装知识点总结

封装

private关键字

  • private是一个权限修饰符,作用是保护成员不被别的类使用,被private修饰的成员只在本类中才能访问.要是类的成员变量被private修饰,这样的话其他类要直接用"对象名.属性名"来操作或读取这个成员变量的话,就会出现错误.所以被的类不能直接访问或操作这个成员变量,那怎么办呢?要是别的类想取操作或访问这个成员变量可以间接来操作这个成员变量.即,在那个private修饰的成员变量的那个类里面定义一个可以被外面访问的方法(比如,定义一个public修饰的方法),通过这个方法去读或写那个属性就行.(private的修饰符同样也作用与方法,表示这个方法只能被这个类里面的语句直接使用)

    那这样有什么好处呢?这样的话,外面的类想直接修改某类的成员变量就不行,只能通过那个类的某个方法间接地去修改成员变量的值,这样我们只要给那个方法设置一些条件,外面的访问就可以被约束.但是如你把成员变量设置为public的,那么外面类想怎么改值就怎么改值,非常地不安全.

    比如这样(我们设置了这个setAge()方法的一下判断条件,使外面的输入得符合你定义的规则,你看年龄不能为负数吧,年龄太大也不科学吧,这样就控制了修改这个age的条件了,使之更加安全(必须得符合你定义的规则,你定义规则可以在这个允许访问的方法里定义))

在这里插入图片描述

this关键字

  1. this修饰的变量用于指代成员变量

    • 如果这个方法的局部变量与成员变量同名,不带this修饰的变量指的是局部变量,而不是成员变量(相当于局部变量与成员变量名字一致的时候,局部变量会把成员变量隐藏,你直接用那个一致的名字就是用了局部变量,你要用this关键字的话,就是指的是成员变量)

      this对于成员变量的使用如下图(带this的变量和不带this的变量,他们分别指的是什么):

    在这里插入图片描述

    • 方法的形参与成员变量不同名,不带this修饰的变量指的是成员变量,没有隐藏这一说法了.(只有成员变量和局部变量的名字一样的时候才会出现局部变量隐藏成员变量)

    • this:this表示的是对象.这个方法被哪个对象调用,this就代表哪个对象.你看这个测试类里面执行s1.setName(“林青霞”);调用setName()的是s1对象,所以这个运行到这个s1.setName(“林青霞”);的时候,跳到那个setName()方法里面执行的时候,那个this就是指s1这个对象,所以这个this.name就相当于调用那个这个s1对象的成员变量,然后把局部变量的name值赋给它.这种this.XX的格式就像是在类的外面调用成员变量的那个格式.不同的是,this指的是自己这个类的对象.

      在这里插入图片描述

      (注意一点:成员变量定义过的标识符,可以定义一个与它名字一样的标识符,这个是不会报错的,在定义局部变量的时候,已经活着的(在声明周期内)变量中有一个标识符名字与这个局部变量一样的话.要是那个是成员变量,是可以通过的.要是定义局部变量的时候,还活着的变量中有一个标识符与之一样的局部变量,那么就不行(因为在一个栈帧中,不允许存在两个标识符一样的变量)(在一个对象中,即那个堆的那个圈起来的区域中,也是不允许有同样的标识符的,所以,比如你定义了一个int i的成员变量,然后再去定义一个double i的成员变量,那么也是会报错的))

  2. this的内存原理

    • 这个setName()方法执行的时候,它记住了调用这个方法的对象的地址(对象的调用某方法都会在方法中记住调用这个方法的那个对象的地址的),然后this就被赋值为这个引用了,然后用this.name就和外面访问成员变量一样,this.name是先找到对象的地址,然后去找这个属性(总之就是this会拿到调用这个方法的对象的地址,然后用地址去访问那个对象的成员变量,而不是访问本栈帧中的这个局部变量)

      在这里插入图片描述

封装的概念

  1. 封装概述
    是面向对象三大特征之一(封装,继承,多态)
    封装是将成员变量都隐藏在对象内部,外界是无法直接操作

  2. 封装原则
    将类的某些信息隐藏在类内部,不允许外部程序直接访问,而是通过该类提供的方法来实现对隐藏信息的操作和访问

比如成员变量private,提供对应的getXxx()/setXxx()方法

  1. 封装好处
    通过方法来控制成员变量的操作,提高了代码的安全性
    把代码用方法进行封装,提高了代码的复用性

private等关键字就体现了封装的思想,就是限定访问权限,把特定的方法在类里面写好。

构造方法

  1. 构造方法是一种特殊的方法,方法名和类名一样,且没有返回值

  2. 功能:主要是完成对象数据的初始化

    创建对象,即new的时候就要看这个构造方法,什么是构造方法看下图:

在这里插入图片描述

格式:

public class 类名{
        修饰符 类名( 参数 ) {
        }
}
  1. 构造方法的创建
    如果没有定义构造方法,系统将给出一个默认的无参数构造方法
    如果定义了构造方法,系统将不再提供默认的构造方法,就算你自己写无参的构造方法,这个系统默认的那个构造方法也没有了(但是推荐每次都自己写无参的构造方法)

  2. 构造方法的重载
    如果自定义了带参构造方法,还要使用无参数构造方法,就必须再写一个无参数构造方法

标准格式

自己写类的时候格式建议以如下标准写:(总之就是:多个构造方法(无参+带参),getXX和setXX,show方法。成员变量用private)

在这里插入图片描述

API

  • 什么是API

    ​ API (Application Programming Interface) :应用程序编程接口

  • JAVA中的API

    指的就是 JDK 中提供的各种功能的 Java类,这些类将底层的实现封装了起来,我们不需要关心这些类是如何实现的,只需要学习这些类如何使用即可,我们可以通过帮助文档来学习这些API如何使用。

  • 注意:

    在API中看到的那些,java.lang包下的那些类是不用导包的,比如System这个类和String这个类(System类它的全限定类名应该这么写java.lang.System那个String类的全限定类名应该这么写java.lang.String),Scanner是在java.util包下的,所以用Scanner的时候就要导包。

  • java帮助文档的使用步骤,还包括看哪些重要的信息

    1. 打开帮助文档(双击或右键打开)

      在这里插入图片描述

    2. 点击索引

    在这里插入图片描述

    1. 然后搜索自己想要查看的类,比如我们查看Random类

      下面这些圈起来的就是我们比较需要重点关注的。知道这个类的全限定类名,我们就知道导入哪个包;知道这个类是做什么的,我们就知道以后要干嘛大概需要找哪些包;知道父类和子类(子类这里没有圈,忘记了)可以让我们发现一些信息。知道这个类的构造方法,我们就知道怎么去创建这个类的对象(但是这个类下可能也会有一些静态方法可以帮我们来创建对象)。然后就是关注这个类的方法了,这样我们才可以知道这个类的某个方法怎么用。

    在这里插入图片描述

Sting类

​ String 类代表字符串,Java 程序中的所有字符串文字(例如“abc”)都被实现为此类的实例(实例就是对象)。也就是说,Java 程序中所有的双引号字符串,都是 String 类的对象。因为String 类在 java.lang 包下,所以使用的时候不需要导包!

  1. String字符串类型的特点:

    • 字符串不可变,它们的值在创建后不能被更改
    • 虽然 String 的值是不可变的,但是它们可以被共享(因为String类型的对象是不可变的,所以,String的replace()等方法是生成一个新对象,不是改变原来String对象的值。共享是因为常量池里面的对象要是内容一样就不会重新创建一个对象,而是会直接用前面那个对象)
    • 字符串效果上相当于字符数组( char[] )(就是你可以取字符串的某个索引的一个或几个字符,所以效果上像是字符数组),但是底层原理是字节数组( byte[] ),但是在JDK1.8中底层是byte[]数组。这个底层我们在本文件夹下的另一个dowmmark中说明了,要想了解的话就看看那个文件
  2. 常见生成字符串对象的方式

    方法名说明
    public String()创建一个空白字符串对象,不含有任何内容
    public String(char[] chs)根据字符数组的内容,来创建字符串对象
    public String(byte[] bys)根据字节数组的内容,来创建字符串对象
    String s = “abc”;直接赋值的方式创建字符串对象,内容就是abc
    • 例子

      public class StringDemo01 {
          public static void main(String[] args) {
              //public String():创建一个空白字符串对象,不含有任何内容
              String s1 = new String();
              System.out.println("s1:" + s1);//结果是输出	s1:
      
              //public String(char[] chs):根据字符数组的内容,来创建字符串对象
              char[] chs = {'a', 'b', 'c'};
              String s2 = new String(chs);
              System.out.println("s2:" + s2);//结果是输出:	s2:abc
      
              //public String(byte[] bys):根据字节数组的内容,来创建字符串对象
              byte[] bys = {97, 98, 99};
              String s3 = new String(bys);
              System.out.println("s3:" + s3);//结果输出是:	s3:abc 为什么呢?因为String s3 = new String(bys);在这个构造方法里面把byte的数组转为char类型然后再给s3.所以现在的时候就是显示abc
      
              //String s = “abc”;	直接赋值的方式创建字符串对象,内容就是abc。推荐使用这个方式建字符串对象
              String s4 = "abc";
              System.out.println("s4:" + s4);//结果输出是	s4:abc
          }
      }
      
  3. 创建字符串对象两种方式的区别【理解】

    • 通过构造方法创建

      ​ 通过 new 创建的字符串对象,每一次 new 都会申请一个内存空间,虽然内容相同,但是地址值不同

    • 直接赋值方式创建

      ​ 以“”方式给出的字符串,只要字符序列相同(顺序和大小写),无论在程序代码中出现几次,JVM 都只会建立一个 String 对象,并在字符串池中维护

  4. 用new创建字符串和用字面量来创建字符串这两种方式在内存上是怎样的

    比如代码如下:

    package com.liudashuai;
    
    public class Demo1 {
        public static void main(String[] args) {
            char[] chs={'a','b','c'};
            String s1=new String(chs);
            String s2=new String(chs);
            System.out.println(s1==s2);//==比较的是两个引用变量的地址值是不是相同,要比较字符串内容要用equals方法
            String s3="abc";
            String s4="abc";
            System.out.println(s3==s4);
            System.out.println(s1==s3);
        }
    }
    

    这个程序执行,先是Demo1类的main方法入栈,然后运行main方法的第一行语句,char[] chs={‘a’,‘b’,‘c’};即在堆内存里面生成一个数组然后把那个空间的地址给到栈中的main这个栈帧的变量chs,如图

    在这里插入图片描述

    然后执行第二句语句,在堆里面new一个空间,地址是002,且把地址给main里面的s1变量,还有就是这个堆里面对象的内容是什么呢?因为这个new是用字节数组对象来创建的,所以这个字符串内容是参考(reference参考)001这个地址的对象的。所以要是原来的语句要是 String s1=new String(new char[]{‘a’,‘b’,‘c’});那么这句语句就是创建了两个对象,一个字节数组对象,一个字符串对象,且把这个字符串对象的内容的reference值指向那个char数组的对象。

    在这里插入图片描述

    下一句语句的执行也与第二句差不多,他的参考也是指向那个char类型数组的对象

    在这里插入图片描述

    然后运行下一句语句,这个println方法入栈出栈,打印false(因为s1和s2这两个地址是不一样的,所以返回false)

    然后执行下一句String s3=“abc”;因为这个是用字面量来建字符串对象的,所以这句语句在常量池生成一个“abc”这个常量对象(但是不是执行的时候生成的,是编译的时候生成的,这一点要注意),然后把地址给s3。(常量池实际上在JDK1.7之后就放在堆里面了,但是常量池在逻辑上是在方法区。虽然逻辑上是把他放在方法区的,但是实际实现是放在在堆里面的,所以在“常量池到底放在什么地方”这个问题大家在网上的讨论很多,众说纷纭)

在这里插入图片描述

然后运行下一句String s4=“abc”;因为这个“abc”这个对象在常量池里面已经存在,所以它不会在生成一个一样的对象。s3和s4就直接指向一个对象就行了。

在这里插入图片描述

运行下一句,System.out.println(s3==s4);因为s3和s4的存的地址是一样的,所以这个返回值是true。

运行下一句,System.out.println(s1==s3);因为s1和s3存的地址不是一样的,所以结果是false。

然后main方法执行完,退栈。栈中没有方法了,所以程序结束。

所以打印的输出结果是:

false

true

false

注意,其实在代码中有直接出现的“ ”括起来的字符串都是会创建对象的,比如System.out.println(“s:”+s);这样一句语句,在编译的时候就会创建一个“s:”这个字符串对象。所以要是下面有一个语句是:String ss=“s:”;这句语句就没有创建字符串对象。

还要注意一点,面试问:

String s1=new String(“abc”);

这句语句执行的时候创建了多少个对象,这里应该回答是1个,其实这句语句是生成了两个对象,但是要说是执行时的话,就是生成一个,因为“abc”这个对象是在编译的时候在常量池创建对象的。代码里那些字面写的“XX”都会在编译的时候在常量池里生成这个对象,且要是已经有了就不会再生成内容一样的字符串对象。

  1. 字符串的地址是否相等

    • 要是如下面这样:直接用字面量创建字符串对象的方式,第一句生成了一个对象,第二个语句没有生成对象,且s1和s2存的地址是一样的。且这个“abc”是在编译的时候创建的,第二句语句没有生成对象,因为编译时第二句语句要生成对象的时候,发现常量池里面已经有了,所以第二句语句没有生成对象(注意这里我没有说是第一句语句执行的时候生成“abc”对象哦,执行的时候这句语句没有生成对象,是编译的时候生成的)

      String s3="abc";
      String s4="abc";
      
    • 要是如下面这样:第一句在堆中新建了一个数组对象且在栈中新建了一个char的chs变量,然后第二句是在栈里面建了一个s1且堆里面新建了一个对象且这个的参考值指向前面那个char数组对象,第三句是在栈里面建了一个s2且与第二句一样也在堆里面新建了一个对象且这个的参考值也指向前面那个char数组对象。(这里的三条语句并没有在常量池里建对象)

      char[] chs={'a','b','c'};
      String s1=new String(chs);
      String s2=new String(chs);
      

      这两条都是上面4这一点说过了的。看上面的分析可以看出每次new都是会在java堆区建一个新的对象,然后把地址给s1或s2,虽然这两个在堆中的对象的ref值都是指向chs地址指向的对象,但是给s1的是这个new 的那个对象的地址,给s2是堆中另一个new的对象的地址,所以s1和s2的地址不同,这也是每次new的对象的地址都不一样的原因。

    • 要是如下这样:第一句是在常量池里建了一个对象,内容是”abc“,然后把地址给s1;第二句是在堆里面建了一个String的对象,且把地址给s2,且这个String对象的ref指向常量池中的”abc“,因为前面的String s1=”abc“;在常量池中创建了”abc“所以第二句编译的时候本来要去常量池里建一个内容为“abc”的对象,但是发现常量池里面有这个内容为”abc“的对象了,所以这里直接指向那个”abc“,所以这一句语句就建了一个对象;但是第三句语句是在堆里面建了一个对象,且把地址给s3,且在常量池里建了一个”abcdd“的对象,且堆里面的那个String对象的ref指向这个”abcdd“这个对象。(虽然这个abcdd看起来像是abc拼接了dd,但是却是新建了一个对象。常量池里面要是已经有“abc”和“d”你下一次要生成“abcd”的时候还是会生成“abcd”的)

      String s1=”abc“;
      String s2=new String("abc");
      String s3=new String("abcdd");
      String s4=new String("abc");
      
    • 要是如下面这样:s1== s3的结果是true,为什么呢?答:s3虽然是动态拼接出来的字符串,但是所有参与拼接的部分都是已知的字面量(就是不是变量,变量的话要运行的时候才会知道,你编译为class文件的时候,还不知道那个变量是什么呢,拼接要是有变量参与在编译期间是不会自动优化的),像这种两个字面量连接的,在编译期间,这种拼接会被优化,编译器直接帮你拼好,因此String s3 = “Hel” + “lo”;在class文件中被优化成String s3 = “Hello”,又因为“Hello”在常量池里面已经有了,所以这个s3直接指向那个“Hello”对象,所以s1 == s3成立。字面量字符串对象+连接若产生了新对象,才会被加入字符串池中(即要是String s3=”Hel“+“elo”,这样拼起来是“Heello”这个常量,但是在这句话之前只有”Hello“没有”Heello“呀,所以要是这样的话这句话就会在常量池建一个新的对象内容为”Heelo“)。所以这里这个第一句语句是创建了一个对象,第二句语句没有创建对象(因为被优化,所以这句的”Hel“和”lo“也没有创建对象)。

      String s1="Hello";
      String s3="Hel"+"lo";
      
    • 要是如下面这样:s1 == s4不相等,s4虽然也是拼接出来的,但new String(“lo”)这部分不是已知字面量(在编译为class文件的时候并不认识这个对象的值是什么),是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,所以s4指向堆中某个地址(字面量+字符串变量用的应该是StringBuilder的原理)。对于所有包含new方式新建对象(包括null)的“+”连接表达式,它所产生的新对象都不会被加入字符串池中。这里图中的“Hel”是在常量池(可以说常量池在方法区,因为常量池在逻辑上是在方法区的,虽然1.7之后实际在堆区),但是注意这个s4指向的对象不是放在常量池的。这个new String(“lo”)也是在堆区的,因为它是new的。字符串拼接是怎么使用StringBuilder呢?他是创建了一个StringBuilder对象,然后把要拼接的String对象的都append给这个StringBuilder对象,然后用toString()方法返回一个String对象(toString中其实也是创建了一个String对象然后把StringBuilder的值放进去然后return,这样这个s4就指向那个String的对象了,且对象里面的内容是StringBuilder的内容,即那个Hello),toString生成String对象也是在堆区的,且用完了这个StringBuilder对象由GC来销毁,所以这第二句话是生成了四个对象,一个”Hel“在常量池,一个new String(“lo”)在堆区,还有一个StringBuilder对象,一个StringBuilder的toString生成的String对象,这个String对象它也是在堆区的,如图中这个String对象的地址给了s4这个变量且内容为拼接后的“Hello”。这里没有放画StringBuilder,因为用完可能就被GC回收了。

      常量池的好处是:避免重复创建内容相同的对象,省了时间又省空间。

      String s1 = "Hello";
      String s4 = "Hel" + new String("lo");
      

      String的拼接用StringBuilder优化的证明如下图:(所以看图中第二点就知道,我们下面这个代码的那个截图,用String的那个方法每次拼接的那个string+=“1”;语句都是会在建一个StringBuilder对象,然后用append拼接,然后toString生成一个String对象赋值给string,然后StringBuilder对象可能会被GC销毁(String用+拼接生成的这个StringBuilder对象是用完由GC来控制要不要销毁),String类型的变量多次拼接就建立多个StringBuilder对象,又生成多个String对象,而且gc会回收没有用的对象内存(尤其是那些没有引用类型变量相互关联的临时对象),这个回收内存的过程很耗时间(其实new耗的时间不是很多,但是GC耗时是比较大的)所以效率很低,所以耗时间很多。但是用StringBuilder的那个方法,只建了一个StringBuilder对象(因为方法中我们把这个StringBuilder对象建在for外面),然后一直append(因为StringBuilder对象里面的字符内容和长度是可以变的,不像String那样不可变,所以拼接不会生成新对象),并且没有很多对象需要回收,所以很省时间)。

      字符串拼接用StringBuilder的优化可能是指String本来只能用来拼接字面量和字面量的拼接,本来只能靠编译时的拼接的。但是后来想要有能与变量一起拼接,所以出现了这一个优化,但是这个优化得到了功能,却损失了时间和空间(因为每次都要new StringBuilder和new String很耗时间,还有回收这些中间的StringBuilder也耗时间),所以我们经常要用StringBuilder来做大量字符串拼接的任务)。

    在这里插入图片描述

    在这里插入图片描述

    • 要是如下面这样:s1 == s9是false,道理差不多,虽然s7、s8在赋值的时候使用的字符串字面量,但是拼接成s9的时候,s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,不能在编译期被确定,所以不做优化。像这样拼接的几个东西,只要其中有一个是变量,结果就在堆中(即等号右边的那个s7+s8生成的对象就是在堆中的)。变量拼接的原理是用StringBuilder进行拼接(是生成了一个StringBuilder对象,然会把字符串的值append进去,然后toString返回的就是字符串拼接的结果,因为是toString方法会创建一个String对象(因为StringBuilder的toString其实return new String(value, 0, count);这样的,value是字符数组(JDK1.8是字符数组),count是字符数组的长度。所以我觉得,new了一个String对象,其实是在堆里的某个空间生成了一个字符数组,然后在String对象中用ref这个值指向那个字符数组,但是我们一般内存分析都不画这个字符数组的内存,你看下图,直接用把s7+s8这个对象里面的内容写在里面了,其实应该再画一个字符数组空间,里面放Hello,然后这个s7+s8,这个对象的ref指向那个字符数组),所以返回的结果还是String类型的,然后每次new出来的StringBuilder对象没有用了就开始GC销毁)。因为这个s9(即图中的s7+s8)的这个对象是在堆中的,而那个”Hello“是在常量池的,所以肯定地址不一样呀,s1和s9指向的肯定不是一个对象呀。

      String s1 = "Hello";
      String s7 = "H"; 
      String s8 = "ello"; 
      String s9 = s7 + s8;
      

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2ZQvUYIz-1658397268526)(第九次任务-封装/webp-16579757572392.webp)]

    • 要是如下面这样:s4==s5结果是false,他们都是在堆中,但是地址不同。

      String s4 = "Hel" + new String("lo");
      String s5 = new String("Hello");
      
    • 像这样:结果是false

      package com.liudashuai;
      
      public class Demo1 {
          public static void main(String[] args) {
              String a="abc";
              String b=new String("abc");
              System.out.println(a==b);//false
          }
      }
      

      但是像这样,结果就是true,

      package com.liudashuai;
      
      public class Demo1 {
          public static void main(String[] args) {
              String a="abc";
              String b=new String("abc");
              System.out.println(a==b.intern());//true
          }
      }
      

      b.intern()这个语句会判断这个b的字符串的内容在常量池里面有没有,如果在常量池里面没有的话,会在常量池里面建一个引用,然后返回那个堆里面的地址(所以下面的代码的结果是true )。如果那个内容在常量池里面已经有了,就不在常量池里面生成,直接返回那个已经存在的字符串的地址。

      package com.liudashuai;
      
      public class Demo1 {
          public static void main(String[] args) {
              String a="abc";
              String b=new String("abc")+"d";
              System.out.println(b==b.intern());//true
          }
      }
      

      下面这个结果也是true,说明一点,就是这个”abcd“是在System.out.println(“abcd”==b.intern());这句编译的时候生成的。

      package com.liudashuai;
      
      public class Demo1 {
          public static void main(String[] args) {
              String a="abc";
              String b=new String("abcd");
              System.out.println(b.intern()=="abcd");//true
          }
      }
      
    • 比如下面这样:结果是false,这个是因为普通的变量编译时不被优化。

      package com.liudashuai;
      public class Demo1 {
          public static void main(String[] args) {
              String a="helloa";
              String b="hello";
              String c=b+"a";
              System.out.println(a==c);//false
          }
      }
      

      但是这样:结果就是true,因为这个final修饰的变量在编译期间,就会把变量代替为那个常量,所以这个String c=b+“a”;在编译的时候会被优化。所以结果是true

      package com.liudashuai;
      
      public class Demo1 {
          public static void main(String[] args) {
              String a="helloa";
              final String b="hello";
              String c=b+"a";
              System.out.println(a==c);//true
          }
      }
      

      又但是:这里返回时false,虽然这里final修饰了b,但是它的值是方法的返回值,这个返回值是要运行时计算才知道的,不像上面那个b的值是一个常量不用计算就知道,所以这里这个b在编译期间不知道b值是什么,所以不会编译期间优化,所以返回false

      package com.liudashuai;
      
      public class Demo1 {
          public static void main(String[] args) {
              String a="helloa";
              final String b=f();
              String c=b+"a";
              System.out.println(a==c);//false
          }
          public static String f(){
              return "hello";
          }
      }
      
    • 像这样的,结果也是true,y3==“efghij”,问的就是y3是不是指向的就是这个对象。或说,问的是y3和这个"efghij"返回的地址是不是一样的。(可以理解为new一个对象或直接用一个对象返回都是一个地址,但是对于编译器来说地址是有类型的,比如你”ello“返回是一个地址,但是编译知道是String类型的地址,所以你用Integer i=”ello“编译器会告诉你有问题不让你通过编译。)这句话String y3=“efg”+“hij”;在编译的时候就会生成一个“ efghij ”的字符串对象,然后 System.out.println(y3 ==“efghij”);这句话编译的时候也想生成一个”efghij“这个对象,但是因为发现已经有了,这句语句所以没有生成"efghij"对象。

      package com.liudashuai;
      
      public class Demo1 {
          public static void main(String[] args) {
              String y3="efg"+"hij";
              System.out.println(y3=="efghij");//true
          }
      }
      
    • 像这样:这个new String(“a”)+new String(“b”);的拼接结果也是在堆中的,也是StringBuilder的原理拼接的。

      String s = new String("a")+new String("b");//new String ("ab")
      System.out.println(s =="ab");//结果是false。(这里用了s ==”ab“)然后这个“ab”就已经创建了,但是不是在执行的时候创建的,是编译为class文件的时候就已经在常量池里创建了。所以这句话执行的时候并没有创建ab这个对象,这个对象是编译的时候创建的,然后下面这个String s1="ab";因为这里已经有创建“ab”对象了,所以下面这句语句没有创建对象。
      String s1="ab";
      
  2. equals()方法

    • 声明长这样:public boolean equals(String s)

    • 使用如下:

      String s1 = "abc";
      String s2 = "abc";
      System.out.println(s1==s2);
      System.out.println(s1.equals(s2));
      
    • s1.equals(s2)的作用是:比较s1和s2两个字符串对象的内容是不是一样。一样返回true,不一样返回false。且这个equals方法是区分大小写。

    • 注意==和equals方法的区别:

      s1==s2返回是:这s1和s2的地址值是不是相同。

      s1.equals(s2)返回的是s1和s2这两个字符串对象的内容是不是相同

  3. chatAt()方法

    • 声明:public char charAt(int index)
    • 作用:返回指定索引处的char值,字符串的索引也是从0开始的
    • 使用:System.out.println(line.charAt(2));返回line这个字符串对象的索引为2的那个位置的字符。
  4. length()方法

    • 声明:public int length()

    • 作用:返回此字符串的长度

    • 使用:line.length();返回的值是这个line字符串对象内容的长度。

      ​ for(int i=0; i<line.length(); i++) {
      ​ System.out.println(line.charAt(i));
      ​ }

    • 注意(数组的是没括号的,是那个对象的属性,这个字符串的length()是有括号的,是字符串对象的方法。在遍历的时候尤其要注意这一点):

      数组的长度:数组名.length
      字符串的长度:字符串对象.length()

  5. 字符串的通用遍历格式:

    String line="afjafk";
    for(int i=0; i<line.length(); i++) {
        System.out.println(line.charAt(i));
    }
    
  6. char类型的变量是可以比较大小的。

    ==,<,>都是可以用在char类型数据或变量上的,比较的是他们的asccii码的大小

    所以我们也可以用这个特点判断某字符是不是大写字母,小写字母,数字。

    例子如下:

    package com.liudashuai;
    
    public class Demo1 {
        public static void main(String[] args) {
            System.out.println('c' == 'a');//打印false
            System.out.println('c' < 'a');//打印false
            System.out.println('c' > 'a');//打印true
            char c='H';
            if ('a'<c && c<'z'){
                System.out.println("小写字母");
            } else if ('A'<c && c<'Z'){
                System.out.println("小写字母");
            } else if('0'<c && c<'9'){
                System.out.println("数字");
            }
        }
    }
    

StringBuilder类

  1. StringBuilder和StringBuffer的一些区别;

    • StringBuffer是线程安全的,但是StringBuilder线程不安全的。因为StringBuffer的方法实现是用了锁。
    • 因为StringBuffer用了锁,这里涉及到了锁的竞争的问题,所以StringBuffer会比StringBuilder的速度慢一些。所以我们在没有涉及到多线程的时候或者说在多线程中不会涉及到线程安全问题的地方应该尽量使用StringBuilder类代替使用StringBuffer类。
  2. 不断使用String拼接两个字面量的话,会在堆里面不断地添加新对象,但是你用StringBuilder来创建对象然后拼接的话,就是在一个对象中不断扩展,且是=就算是StringBuilder的内存长度不够时,它也会自己阔大容量(会扩大很多的,你超过长度原来是16,你满了你现在要存的是17的长度,它不是扩大到17可能它扩大到100,这样很长时间都不用继续扩大了,而且它不会一下子给你扩大到100000这样太耗空间了)(StringBuilder的底层是用数组来实现的,可能还是用链表来实现的,这样要是数组长度不够,它就new一个新的更大的数组,所以这个类是长度不够时才会有创建对象的过程,但是String来连接的话,String的长度是不能变的,所以每一次变化都创建新对象),而不会重新创建一个对象,这样既省时间又省空间。

  3. StringBuilder 是一个可变的字符串类,我们可以把它看成是一个容器,这里的可变指的是 StringBuilder 对象中的内容是可变长度也可以变。所以你要是改变StringBuilder中内容的其中一个值也是可以的,比如你把内容为Hello的StringBuilder对象的内容改为Helle也是可以的,但是要是想让String s=“Hello”;这个s对象的内容改变就只能重新创建一个String对象赋值给s了。

  4. 常用的构造方法

    方法名说明
    public StringBuilder()创建一个空白可变字符串对象,不含有任何内容
    public StringBuilder(String str)根据字符串的内容,来创建可变字符串对象

    例子:

    public class StringBuilderDemo01 {
        public static void main(String[] args) {
            //public StringBuilder():创建一个空白可变字符串对象,不含有任何内容
            StringBuilder sb = new StringBuilder();
            System.out.println("sb:" + sb);
            System.out.println("sb.length():" + sb.length());
    
            //public StringBuilder(String str):根据字符串的内容,来创建可变字符串对象
            StringBuilder sb2 = new StringBuilder("hello");
            System.out.println("sb2:" + sb2);
            System.out.println("sb2.length():" + sb2.length());
        }
    }
    
  5. 添加和反转方法

    方法名说明
    public StringBuilder append(任意类型)添加数据,并返回对象本身
    public StringBuilder reverse()返回相反的字符序列
     StringBuilder sb = new StringBuilder();
    StringBuilder sb2 = sb.append("hello");
    System.out.println("sb:" + sb);
    System.out.println("sb2:" + sb2);
    System.out.println(sb == sb2);//结果是true,所以可以看出这个 sb.append("hello")返回的是sb这个对象,相当于把那个对象的内存地址给返回了,sb和sb2共享那个内存
    

    所以我们要显示拼接后的内容不用让一个新的变量来接受它了,直接输出那个原来那个StringBuilder对象,如

    StringBuilder sb = new StringBuilder();
    //StringBuilder sb2 = sb.append("hello");
    sb.append("hello");
    sb.append("world");
    sb.append("java");
    sb.append(100);
    //System.out.println(sb2);
    System.out.println( sb);//直接输出原来那个StringBuilder类的对象就行,这个StringBuilder对象的变量直接输出也是会显示内容的,不是地址哦,因为它的toString方法是已经做了的,其实它的toString方法是new了一个String(即return new String(value, 0, count);)它是把值给了value这个字符数组,然后把字符字符数组的地址给了String的ref。
    

    因为sb.append方法的返回类型和sb的类型是一致的,且每次改变都是会直接影响这个sb所代表的对象的值的。所以可以使用链式编程的思路。

    StringBuilder sb = new StringBuilder();
    sb.append("hello").append("world").append("java").append(100);
    System.out.println("sb:" + sb);
    

    下面展示反转reverse方法的效果

     StringBuilder sb = new StringBuilder();
            sb.append("abcde");
            sb.reverse();
            System.out.println( sb);//结果是:edcba,也是直接影响这个sb对象的内容
    
  6. String和StringBuilder的相互转换(为什么需要String和StringBuilder的相互转换呢?因为,String是没有append和reverse这两个好用的方法的,然后String想要增长或改变内容就得重新创建对象,这样就很耗时间和空间,所以你可以先用String创造一个StringBuilder的对象,然后去复杂的变化,然后最后把这个StringBuilder对象转变为String这里不就行了吗,中间过程只有创建StringBuilder对象的时候创建对象,和StringBuilder的内部长度不够创建数组对象,还有转变为String的时候创建String对象这么几次创建对象,所以少了很多开销)String变为StringBuilder用的是下面这个 StringBuilder(String s)方法,StringBuilder转变为String类型的对象用的是toString()方法

    • StringBuilder转换为String

      ​ public String toString():通过 toString() 就可以实现把 StringBuilder 转换为 String

    • String转换为StringBuilder

      ​ public StringBuilder(String s):通过构造方法就可以实现把 String 转换为 StringBuilder

    例子如下:

    (注意:

    直接StringBuilder sb = new StringBuilder();sb.append(“hello”);String s = sb;是错误的,因为他们的类型不同。

    但是要是是这样的StringBuilder sb = new StringBuilder();sb.append(“hello”);然后直接System.out.println(sb);是可以的,在sout中直接用引用对象它是会优先去找那个toString的方法的,然后返回那个toString的返回值作为引用类型变量的值,要是没有找到toString那么就会去父类找,最终会到Object中找,要是到Object中才找到,那么返回的就是地址值了)

    public class StringBuilderDemo02 {
        public static void main(String[] args) {
            
            //StringBuilder 转换为 String
            StringBuilder sb = new StringBuilder();
            sb.append("hello");
            //String s = sb; //这个是错误的做法
            String s = sb.toString();
            System.out.println(s);
            
    
            //String 转换为 StringBuilder
            String s1 = "hello";
            //StringBuilder sb = s; //这个是错误的做法
            StringBuilder sb1 = new StringBuilder(s1);
    
            System.out.println(sb1);
        }
    }
    

    下面给你看看什么时候用这个转换的思想比较合适,下面这样的arrayToString方法中的那些要对字符串进行多次操作的时候,我们把它先赋值给StringBuilder对象,然后进行操作,操作完之后,转为String然后给返回。这样省了每次改变String的内容都得重新创建对象的这个过程,所以省了时间和空间。

    package com.liudashuai;
    
    public class HelloWorld {
        public static void main(String[] args) {
            String s1="liudashuai";
            String s2 = arrayToString(s1);
            System.out.println("s2:" + s2);
    
        }
    
        public static String arrayToString(String s1) {
            StringBuilder sb = new StringBuilder(s1);
            sb.reverse();
            sb.append("666");
            sb.append("非常叼");
            String s2 = sb.toString();
            return s2;
        }
    }
    
    
  7. StringBuilder常用方法

    方法名说明
    public StringBuilder append (任意类型)添加数据,并返回对象本身
    public StringBuilder reverse()返回相反的字符序列
    public int length()返回长度,实际存储值
    public String toString()通过toString()就可以实现把StringBuilder转换为String

注意点

  1. 如下面这样的,输出结果是: new A(new B());这样的语句是先执行内括号里面的语句的。 还有就是A(B b)方法运行this();就运行本调用A(B b)方法的那个对象的无参构造方法。且this()这个方法只能放在方法的第一句,且只能放在构造方法里面。但是this.方法();是可以放在成员方法里面的且不要求是第一句。this.XX()方法与this.XX属性一样this都在指调用那个方法的对象,比如g()方法下的那个this.f()的this指的就是a对象,所以执行a对象的f()方法,因为a对象的 i 是15所以结果输出了15

    B
    A
    AB
    B
    A
    AB
    15
    16

    package work9_1.five;
    public class Test1 {
    
        public static void main(String[] args) {
            new A(new B());
            B b=new B();
            A a = new A(b);
            a.i=15;
            a.g();//g()的方法里面的this指的就是这个a对象
        }
    }
    class A{
        int i;
        public void f(){
            System.out.println(i);
        }
        public void g(){
    //        this();
            this.f();
            i++;
            this.f();
        }
        public A(){
            System.out.println("A");
        }
        public A(B b){
            this();//且this()这个方法只能放在方法的第一句,且只能放在构造方法里面。但是this.方法();是可以放在成员方法里面的且不要求是第一句。
            System.out.println("AB");
    //        this();错误
        }
    }
    class B{
        public B(){
            System.out.println("B");
        }
    }
    
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值