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
原创不易,转载请标明出处,谢谢!

你可能感兴趣的:(Java基础知识回顾之二 ----- 修饰符和String)