• Java基础知识回顾之二 ----- 修饰符和String


    前言

    在上一篇中,回顾了Java的基本数据类型 ,这篇就来回顾下Java中的一些修饰符以及String。

    修饰符介绍

    Java修饰符主要分为两类:

    • 访问修饰符
    • 非访问修饰符

    其中访问修饰符主要包括 private、default、protected、public。
    非访问修饰符主要包括 static、final、abstract、synchronized。

    访问修饰符

    访问修饰符可以使用下图这张表来说明访问权限:

    修饰符 当前类 同一包内 子类 其它包
    public Y Y Y Y
    protected Y Y Y N
    default Y Y N N
    private Y N N N

    简单点查看访问级别的话,级别是由低到高。

     private<default<protected<public
    

    private

    被private修饰的变量、方法仅限在本类中使用。
    所以private是最严的的访问级别,主要用于隐藏类的一些细节实现和保护类的数据。
    例如pojo类就是使用private修饰变量,对外提供setter和getter的方法。
    还有如果使用private用来修饰构造方法的话,该类是不能实例化的。这种在单例模式中可以经常看到!

    虽然private主要用于修饰变量和方法,不过也可以修饰内部类,只不过是内部类。

    例如:

    public class Test{
    	//修饰一个私有变量
    	private int count=1;
    	//修饰一个私有方法
    	private int add(int i,int j){
    		return i+j;
       }
    	private class Test1{    		
    	}
    }
    

    注意:private不能修饰外部类。

    因为Test类中的变量和方法是私有的,所以其他类无法调用!
    例:

    public class Test2 {
    	public static void main(String[] args) {
    		Test t=new Test();
    		//下面的变量和方法是无法获取的
    		//t.count=2;
    		//t.add(1,2);
    	}
    }
    

    说明:其实private修饰的方法和变量是可以使用反射调用,不过这里就不说明了。

    default

    default:就是不使用任何修饰符。类、接口、变量、方法都可以使用。不过仅限在同一包下。

    例如:

    class Test{
    	 int count=1;
    	 int add(int i,int j){
    			return i+j;
    	 }
    	interface Test1{
    	}
    }
    

    protected

    被protected修饰的变量、方法仅仅对同一包内的类和所有子类可见。

    例如:

    public class Test{
    	protected int count=1;
    	protected int add(int i,int j){
    			return i+j;
    	 }
    	 protected class Test1{
    	 }
    }
    

    在同包下可以直接调用,如果不在同包,则需要继承才可以使用。

    public class Test2 extends Test{
    	public static void main(String[] args) {
    		Test t=new Test();
    		t.count=2;
    		t.add(1,2);
    	}
    }
    

    注意:protected不能修饰外部类。

    public

    public:修饰的类、接口、变量、方法对所有类都可以使用。

    例如:

       public class Test{
    	public int count=1;
    	public int add(int i,int j){
    			return i+j;
    	 }
      }
    

    非访问修饰符

    为了实现一些其他的功能,Java 也提供了许多非访问修饰符。

    static

    static: 用来修饰类变量和类方法。

    静态变量
    static在修饰类变量的时候,无论该类被实例化了多少次,它的静态变量只有一份拷贝。静态变量也被称为类变量。局部变量是不能被声明为static变量的。

    静态方法
    static在修饰类方法的时候,静态方法是不能使用类的非静态变量。静态方法可以直接通过类名调用,因此静态方法中是不能用thissuper关键字的。

    示例:

    
     public class Test{
        public  String name="xuwujing";
        public  static String name2="xuwujing";
        
        public  static String getName() {
        //这个一句 会报错  因为静态方法是不能使用类的非静态变量
        //String reult=name;
        //这一句就可以
        String reult=name2;
        return reult;
         }
        
        //main方法是静态方法,可以直接调用本类中的静态方法和静态变量
        public static void main(String[] args) {
    	    System.out.println(name2);
    	    System.out.println(getName());
        }
        
        //该方法是不静态方法,所以调用本类中的静态方法和静态变量时,
        //需要使用classname.variablename和 classname.methodname的方式访问
        private void print(){
    	    System.out.println(Test.name2);
    	    System.out.println(Test.getName());
         }
        }
    

    在这里顺便提一下,static 静态块。
    在JVM类加载机制中,如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;如果类中存在初始化语句,就依次执行这些初始化语句。
    可能上述的两句话不太好理解,那么这里我们来运行下代码查看其结果,通过结果可能就能更好的理解上述语句的话了。

    示例:

    class HelloA {
    
        public HelloA() {
            System.out.println("HelloA"); 
        }
        
        { System.out.println("I'm A class"); } 
        
        static { System.out.println("static A"); } 
    
    }
    public class HelloB extends HelloA{
        public HelloB() {
            System.out.println("HelloB"); 
        }
        
        { System.out.println("I'm B class"); }  
        
        static { System.out.println("static B"); } 
        
        public static void main(String[] args) {
        	new HelloB();   
    }
    

    结果:

    static A
    static B
    I'm A class
    HelloA
    I'm B class
    HelloB
    

    那么根据这个类返回的结果是不是感觉更好理解了呢?
    创建对象时构造器的调用顺序是:

    先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器。

    那么static修饰符这块的运用可以总结如下:

    1. 静态变量在内存中只有一个拷贝,在类的所有实例中共享。
    2. 在静态方法中不能直接访问实例方法和实例变量,反之可以。
    3. 在静态方法中不能使用this和super关键字。
    4. 静态方法不能被abstract修饰。
    5. 静态方法和静态变量都可以通过类名直接访问。
    6. 当类被加载时,静态代码块只被加载一次。有多个静态变量或块时,按声明顺序加载。

    final

    final :用来修饰类、方法和变量。
    final 修饰的类不能够被继承,修饰的方法不能被继承类重新定义,修饰的变量为常量,是不可修改的。
    如果上述语句不好理解的话,我们可以通过编写相关代码进行实验。
    定义一个final修饰的变量、方法以及类。然后进行相关的测试

    示例:

    
     public class Test{
        	//定义一个final修饰的变量
        public  static final String name="xuwujing";
        
      public static void main(String[] args) {
      		//这句会报错  因为该变量已经被final修饰了
      		name="张三";
      	}
      	//类加上final之后,该类是无法被继承的
      	final class Test2{
      	}
      	//这句会报错,因为Test2是被final修饰的类
      	class Test3 extends Test2{
      	}
      	
      	class Test4{
      		//定义一个被final修饰的方法
      		 final Date getTime(){
      			return new Date();
      		}
      	}
      	
      	class Test5 extends Test4{
      		//这句会报错,因为final方法是不能被子类修改的。
      		Date getTime(){
    		return new Date();
      		}
      	}
      }
     
    

    从上述 代码结果,我们可以得出一下结论:

    final修饰类:表示该类不能被继承;
    final修饰方法:表示方法不能被重写;
    final修饰变量:表示变量只能一次赋值以后值不能被修改(常量);

    abstract

    abstract :用来创建抽象类和抽象方法。

    Java是面向对象的语言,而抽象类是Java语言中对抽象概念进行定义的一种机制,也正是因为这个,所以赋予了Java强大的面向对象的能力。

    修饰类

    会使这个类成为一个抽象类,这个类将不能生成对象实例,但可以做为对象变量声明的类型(见后面实例),也就是编译时类型。抽象类就相当于一类的半成品,需要子类继承并覆盖其中的抽象方法。

    修饰方法

    会使这个方法变成抽象方法,也就是只有声明而没有实现,需要子类继承实现。

    这里依旧使用一个简单例子来进行理解。

    public class AbstractTest{
    	public static void main(String[] args) {
    		//这句会报错,因为抽象类不能实例化
    		// Animal a=new Animal();
    		//抽象类可以实例化重写该类抽象方法的子类
    		Animal a = new Dog();
    		a.show();
    	}
    }
    abstract class Animal{
    	abstract void show();
    	public void print(){
    		System.out.println("Animal");
    	}
    }
    //继承抽象类需要实现抽象类的方法
    class Dog extends Animal{	
    	@Override
    	void show() {
    		System.out.println("This is Dog!");
    	}
    }
    

    总结:

    1、抽象类和抽象方法都需要被abstract修饰。抽象方法一定要定义在抽象类中。
    2、抽象类不可以创建实例,原因:调用抽象方法没有意义。
    3、只有覆盖了抽象类中所有的抽象方法后,其子类才可以实例化。否则该子类还是一个抽象类。

    注意事项:

    1、抽象类不能用来实例化对象,声明抽象类的唯一目的是为了将来对该类进行扩充。 2、一个类不能同时被 abstract 和 final
    修饰。如果一个类包含抽象方法,那么该类一定要声明为抽象类,否则将出现编译错误。
    3、抽象方法是一种没有任何实现的方法,该方法的的具体实现由子类提供。 4、抽象方法不能被声明成 final 和 static。
    5、任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。
    6、如果一个类包含若干个抽象方法,那么该类必须声明为抽象类。抽象类可以不包含抽象方法。

    synchronized

    synchronized: 修饰的方法同一时间只能被一个线程访问。在多线程中运用很常见。
    synchronized 的解释如下:

    synchronized 方法控制对类成员变量的访问:每个类实例对应一把锁,每个 synchronized方法都必须获得调用该方法的类实例的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。这种机制确保了同一时刻对于每一个类实例,其所有声明synchronized 的成员函数中至多只有一个处于可执行状态(因为至多只有一个能够获得该类实例对应的锁),从而有效避免了类成员变量的访问冲突(只要所有可能访问类成员变量的方法均被声明为 synchronized)。

    简单的来说,就是使用synchronized 修饰的方法,在多线程进行同时访问的时候,只会让一个线程先进行访问,其它的线程等候,当这个线程访问完了之后,再让下一个进行访问,依次类推。

    Java中还有两个不太常见的修饰符,transientnative

    transient:被 transient 修饰的实例变量时,java 虚拟机(JVM)跳过该特定的变量。
    native: 被native修饰的方法实际是由另一种语言进行实现的本地方法。例如Java中获取的Long类型的时间戳 :System.currentTimeMillis(); 实际是由native 修饰的,
    源码为:

     public static native long currentTimeMillis();
    

    String

    String 类型可能就是我们最常用的的对象了。
    首先说明,String并不是基本数据类型,而是一个对象,并且是不可变的对象。查看源码可以String类是被final修饰的,是不可被继承的!

    String的在未被初始化的时候为null,表示它还没有被创建,自然也就没有分配空间;
    而" "和 new String()不是null,它们是已经被创建,只是值为空而已!并且也分配了内存空间。

    String有15种构造方法,有两种是过时的,其中包含char[],byte[],int[],String,StringBuffer,StringBuilder。
    我们在创建String对象的的时候,一般是使用 String str="xxx",但有时也会用new String()来初始话字符串。
    例如:

        String hello="hello";
        String newHello=new String("hello");
        char []cHello ={'h','e','l','l','o'};
        String str=new String(cHello);
    

    注意:String 类是不可改变的,所以你一旦创建了 String 对象,那它的值就无法改变了。

    String常用方法

    大概讲述了String的用法之后,这里我们来列举一些String常用的方法。

    1.length :返回此字符串的长度。
    2.charAt:返回指定索引处的 char 值。
    3.compareTo:把这个字符串和另一个对象比较。
    4.concat:将指定字符串连接到此字符串的结尾。
    5.split:根据给定正则表达式的匹配拆分此字符串。
    6.equals:将此字符串与指定的对象比较。
    7.endsWith:测试此字符串是否以指定的后缀结束。
    8.startsWith:测试此字符串是否以指定的前缀结束。
    9.getBytes: 使用平台的默认字符集将此 String 编码为 byte 序列,并将结果存储到一个新的 byte 数组中。
    10.indexOf:返回指定字符在此字符串中第一次出现处的索引。
    11.replace:返回一个新的字符串,它是通过用 newChar 替换此字符串中出现的所有 oldChar 得到的。 12:substring:返回一个新的字符串,它是此字符串的一个子字符串。
    ...

    更多可以参考Api文档。

    String对象比较

    String作为我们最常用的对象,在面试中估计也会接触不少。一般来说,会考到String的常量池相关问题,主要是使用String进行比较的时候,==和equals这两种方法来判断是否相当。这里收集了一些String经常遇到的问题。
    代码如下:

      
    	        String s1 = "test";
    	        String s2 = new String("test");
    	        String s3 = "te";
    	        String s4 = "st";
    	        String s5 = "te" + "st";
    	        String s6 = s3 + s4;
    	        String s7 = new String(s1);
    	        System.out.println(s1 == s2); 
    	        System.out.println(s1 == s5); 
    	        System.out.println(s1 == s6); 
    	        System.out.println(s7==s1);       
    	        System.out.println(s7.equals(s1)); 
    	    
    

    结果:

    false
    true
    false
    false
    true
    

    如果有经验的话,大概可以一眼看出结果。但是如果经验不足的话,往往会吃这个亏。这里来解释下为什么会出现这种结果。

    1.虽然看起来是一样的,但是新建一个String类的时候会重新分配引用地址,而 == 就是比较引用地址,所以为false。
    2.在编译之前就可以确认s5=test, 并且引用地址一样,所以为true;
    3.字符串常量池的原则 这时 s6 的值是在运行时得到的,它会重新构造字符串对象 所以为false。
    4.和第一个一样的,就是换汤不换药,所以为false。
    5.equals 只比较值相等,不关心它的引用地址。

    看完上面的例子之后,再来看看下面的这个
    代码示例:

     String ab="ab";
     String c="c";
     String ab_c=ab+c;
     String ab_c1="ab"+"c";
     String abc="abc";
     System.out.println(ab_c == abc + " : " + ab_c.equals(abc));
    
     System.out.println((ab_c == abc) + " : " + ab_c.equals(abc));
    
     System.out.println((ab_c1 == abc) + " : " + ab_c1.equals(abc));
    
    

    运行结果:

    false
    false : true
    true : true

    到这里,可能就会诧异了,为什么和我想的不一样呢?
    这里其实是有陷阱的,也就是运算符的优先级。
    第一个结果就是优先级的问题导致的,它会先计算 abc + " : " + ab_c.equals(abc) ,然后再来进行比较,所以为false。同理,下面的也是如此,基本和上面的那个例子差不多,这里就不再概述了。

    String、StringBuffer和StringBuilder

    String、StringBuffer和StringBuilder的区别:

    • String: String的特点是一旦赋值,便不能更改其指向的字符对象,如果更改,则会指向一个新的字符对象。
    • StringBuffer:StringBuffer对象可以调用其方法动态的进行增加、插入、修改和删 除操作,且不用像数组那样事先指定大小,从而实现多次插入字 符,一次整体取出的效果,因而操作字符串非常灵活方便。并且生成数据之后可以toString转为String,线程安全。
    • StringBuilder:它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。

    关于字符串拼接方式,在String类中,我们最常用的是 + ,其次是使用StringBuffer或StringBuilder 的append方法,至于String类中的concat几乎很少用到。
    一般来说,如果在少量的字符串进行拼接的话,我们会使用+,如果拼接过多的话,单线程使用 StringBuilder ,多线程使用StringBuffer 进行拼接。因为使用String 的 + 在过多的字符串进行拼接的时候会极大的使用内存,因为它在凭借的时候还是使用 append()方法,然后再进行toString转换,如果是少量的时候,是感觉不到差异的,但是在大量拼接的时候就会明显感受得到。

    代码示例:

    String str="Hello World";
    String str1="";
    StringBuffer sbr=new StringBuffer(str); 
    StringBuilder sbd=new StringBuilder(str); 
    long start=System.currentTimeMillis();
       for(int i=0;i<10000;i++){
      	 str1+=str;
       }
       System.out.println("String累加用时:"+(System.currentTimeMillis()-start)+"ms");
       long start2=System.currentTimeMillis();
       for(int i=0;i<10000;i++){
      	 sbr.append(str);
       }
       System.out.println("StringBuffer累加用时:"+(System.currentTimeMillis()-start2)+"ms");
       long start3=System.currentTimeMillis();
       for(int i=0;i<10000;i++){
      	 sbd.append(str);
       }
       System.out.println("StringBuilder累加用时:"+(System.currentTimeMillis()-start3)+"ms");
    
    

    结果:

    String累加用时:701ms
    StringBuffer累加用时:2ms
    StringBuilder累加用时:0ms
    

    这里从输出结果中可以看到String 的+拼接方法的耗时了。但是使用 + 实在是方便。所以在这里建议如果字符串拼接次数在10一下,可以使用+,过多的则用StringBuffer或StringBuilder。

    其它

    参考:
    https://blog.csdn.net/qiumengchen12/article/details/44939929

    https://blog.csdn.net/chenssy/article/details/13004291

    到此,本文就结束了,谢谢阅读!欢迎留言和点赞,你的支持是我写作最大的动力!
    版权声明:
    作者:虚无境
    博客园出处:http://www.cnblogs.com/xuwujing
    CSDN出处:http://blog.csdn.net/qazwsxpcm    
    个人博客出处:http://www.panchengming.com
    原创不易,转载请标明出处,谢谢!

  • 相关阅读:
    用于Delphi的DevExpress VCL组件——富文本编辑功能升级
    5个技巧,教你优化React App性能
    DevExpress WPF模板库助力快速完成界面美化
    Web开发小技巧放送
    WinForm应用界面开发入门指南
    浅析C#中单点登录的原理和使用
    Wireshark抓包,带你快速入门
    你必须知道的EF知识和经验
    你知道SqlDataAdapter中的Fill是怎么实现的吗
    一文说通C#中的异步编程
  • 原文地址:https://www.cnblogs.com/xuwujing/p/8638329.html
Copyright © 2020-2023  润新知