前言
在上一篇中, 回顾了 Java 的基本数据类型 , 这篇就来回顾下 Java 中的一些修饰符以及 String
修饰符介绍
Java 修饰符主要分为两类:
访问修饰符
非访问修饰符
其中访问修饰符主要包括 privatedefaultprotectedpublic
非访问修饰符主要包括 staticfinalabstractsynchronized
访问修饰符
访问修饰符可以使用下图这张表来说明访问权限:
修饰符 | 当前类 | 同一包内 | 子类 | 其它包 |
---|---|---|---|---|
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 在修饰类方法的时候, 静态方法是不能使用类的非静态变量静态方法可以直接通过类名调用, 因此静态方法中是不能用 this 和 super 关键字的
示例:
- 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 修饰符这块的运用可以总结如下:
静态变量在内存中只有一个拷贝, 在类的所有实例中共享
在静态方法中不能直接访问实例方法和实例变量, 反之可以
在静态方法中不能使用 this 和 super 关键字
静态方法不能被 abstract 修饰
静态方法和静态变量都可以通过类名直接访问
当类被加载时, 静态代码块只被加载一次有多个静态变量或块时, 按声明顺序加载
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 中还有两个不太常见的修饰符, transient 和 native
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 同理, 下面的也是如此, 基本和上面的那个例子差不多, 这里就不再概述了
StringStringBuffer 和 StringBuilder
StringStringBuffer 和 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
来源: https://www.cnblogs.com/xuwujing/p/8638329.html