【java基础】常用类

181 阅读13分钟

常用类

一、内部类

在类的内部定义的类。

在一个类中,根据定义的方式不同,变量可以分为:

  • 实例变量(成员变量、属性)
  • 静态变量
  • 局部变量
  • 匿名变量
public class Outter {
    String name; // 实例变量
    static int n; // 静态变量
    
    public void test() {
        int m; // 局部变量
        System.out.println(new int[] {1,2,3,4,5}.length); // 匿名变量
    }
}

在类的内部定义的类,与变量的操作方式相似,所以也分为:

  • 成员内部类
  • 静态内部类
  • 局部内部类
  • 匿名内部类
1.1 成员内部类

在使用时参考成员变量的使用,需要先创建外部类的对象再使用。


public class Outter {
    public String name = "outter==name"; // 实例变量
    
    class Inner{
        public String name = "inner==name";
        public int age;
//      public static int m = 5; // 不能定义静态变量
        
        public void test() {
            // 调用自己的属性
            System.out.println("inner" + this.name);
            // 调用外部的属性
            System.out.println("inner" + Outter.this.name);
        }
        
        // 不能定义静态方法
//      public static void test1() {
//          
//      }
    }
}

public class TestMain {
    public static void main(String[] args) {
        // 先创建对象后使用
//      Outter o = new Outter();
//      o.name = "hello";
//      
//      Inner inner = o.new Inner();
//      inner.age = 20;
//      inner.test();
        
        // 必须先创建外部类的对象才能创建内部类的对象
        Inner i1 = new Outter().new Inner();
        i1.test();
    }
}
1.2 静态内部类【重点】

在类的内部使用static定义的类,操作与普通的类基本一样,一般用于将一个类封装在类的内部。例如在Arrays类内部定义的ArrayList类,就只是在Arrays类的内部使用,外部无法访问。


public class Outter1 {
    public static String name;
    
    public void a() {
        Inner1 n = new Inner1();
    }
        
    public static class Inner1{
        public int age;
        public static String sex;
        
        public void test() {
            
        }
        
        public static void test1() {
            
        }
    }
}

public class TestMain1 {
    public static void main(String[] args) {
        Inner1 inner1 = new Inner1();
        inner1.age = 20;
        inner1.test();
        Inner1.test1();
    }
}
1.3 局部内部类

在方法内部定义的类,仅在方法内部使用。


public class Outter2 {
    public void test() {
        
        class Inner2{
            private int age;
            public void setAge(int age) {
                this.age = age;
            }
            
            public void hello() {
                System.out.println(age);
            }
        }
        
        // 创建对象并使用
        Inner2 i2 = new Inner2();
        i2.setAge(20);
        i2.hello();
    }
}
1.4 匿名内部类[重点]

在类的内部使用的没有名字的类。

一般需要接口或者抽象类来实现。

直接创建接口的对象并将该接口中的所有方法都实现。此时实际上系统会动态生成一个类,并实现该接口,该类的名字由于是动态生成的,是不可见的,称为匿名内部类。

一般用在某些需要实现接口或者继承父类的类,该类在项目中只使用一次。


public interface MyInterface {
    void hello();
}

public class Outter3 {
    public void test() {
        MyInterface i = new MyInterface() {
            @Override
            public void hello() {
                System.out.println(this.getClass().getName());
            }
        };
        
        i.hello();
        
//      new MyInterface() {
//          @Override
//          public void hello() {
//              System.out.println("hello");
//          }
//      }.hello();
        
//      MyInterface m = ()->System.out.println("hello");
//      m.hello();
    }
}

二、Object类

Object类是所有类的顶层父类,所有的类直接或者间接继承了Object类,所有的类都具备有Object类中的方法。

如果使用Object类作为参数或者返回值,能适用于所有的类型。

2.1 getClass()

返回对象的实际类型,可以用来比较对象类型是否一致。


public class Person {
    public void feed(Animal a) {
        System.out.println(a.getClass());
        a.eat();
//      if(a instanceof Dog) {
//          // 
//      }
        if(a.getClass() == Dog.class) { // 比较类型是否相同
            System.out.println(a.getClass().getName());
        }
    }
}
2.2 hashCode()

返回该对象的十进制的哈希码值。

使用哈希算法,即根据对象的地址或者字符串,或数字计算出来的值。

哈希码并不是唯一的。

注意:应该保证相同的对象具有相同的哈希码,尽量保证不同的对象具有不同的哈希码。

字符串的哈希码通过字符串组成的字符的ascii计算,数字使用数字计算,自定义对象使用地址计算。

2.3 equals()

用来比较对象是否相同,会调用此方法。

经典面试题:

equals和==的区别:

  • ==只会比较对象的地址。
  • 通过equals比较,如果没有重写Object类的equals,那么也是比较地址,此时与==相同,但是可以重写equals方法,自己定义对象的比较方式。

public class Student {
    public String id;
    public String name;
    public int age;
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)// 先处理快速比较
            return true;
        if (obj == null)// 先排除错误情况
            return false;
        if (getClass() != obj.getClass())// 如果不是一个类型,不予比较
            return false;
        Student other = (Student) obj;
        if (id == null) {
            if (other.id != null)
                return false;
        } else if (!id.equals(other.id))
            return false;
        return true;
    }
}
2.4 toString()

用来描述当前对象的,如果不重写,父类会直接输出类名和地址,没有意义,应该在子类中重写。


@Override
public String toString() {
    return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
}
2.5 finalize()

用来标记垃圾对象,进入回收队列,由JVM自动调用。

三、包装类

是指基本数据类型所对应的引用数据类型。

注意:基本数据类型作为属性或数组元素,有相应的默认值,例如int的默认值为0,但是如果使用包装类,则默认值为null。

8个基本数据类型对应的包装类名,除了int对应Integer,char对应Character以外,其他的都是该单词首字母大写,例如:float对应Float,double对应Double。

3.1 基本用法

public class Test1 {
    public static void main(String[] args) {
        int n = 5;
        // 将基本数据类型转换成包装类
        Integer in1 = new Integer(n); // 直接创建
        Integer in2 = Integer.valueOf(n); // 使用自带的方法转化
        Integer in3 = n; // 自动装箱,jdk1.5以后的功能
        
        Integer in4 = 10;
        
        // 将包装类转换成基本数据类型
        int n1 = in1.intValue();
        // 自动拆箱,jdk1.5以后的功能
        int n2 = in2;
        
        // 将字符串转换成数字,如果转换失败会报错NumberFormatException
        String str = "23";
        int n3 = Integer.parseInt(str);
        String str1 = "23.5";
        double u1 = Double.parseDouble(str1);
        
        // 将一个十进制的数字转换成2进制,并以字符串的形式呈现
        String s1 = Integer.toBinaryString(23); 
        System.out.println(s1);
        // 将一个十进制的数字转换成8进制,并以字符串的形式呈现
        String s2 = Integer.toOctalString(2334535); 
        System.out.println(s2);
        // 将一个十进制的数字转换成16进制,并以字符串的形式呈现
        String s3 = Integer.toHexString(2353454); 
        System.out.println(s3);
        
        double d = 5.0;
        Double d1 = new Double(d);
        Double d2 = Double.valueOf(d);
        Double d3 = d; 
        double u = d1.doubleValue();
        
        Integer i1 = new Integer(5); 
        Integer i2 = new Integer(5); 
        System.out.println(i1 == i2);
        Integer i3 = Integer.valueOf(5); 
        Integer i4 = Integer.valueOf(5); 
        System.out.println(i3 == i4);
        Integer i5 = Integer.valueOf(128); 
        Integer i6 = Integer.valueOf(128); 
        System.out.println(i5 == i6);
    }
}
3.2 自动装箱拆箱

在JDK1.5之后,加入了自动装箱拆箱功能,即包装类和其对应的基本数据类型之间可以相互赋值。


public class Test1 {
    public static void main(String[] args) {
        int n = 5;
        // 将基本数据类型转换成包装类
        Integer in1 = new Integer(n); // 直接创建
        // 自动装箱
        Integer in4 = 10;
        // 自动拆箱,jdk1.5以后的功能
        int n2 = in2;
    }
}
3.3 整数常量池

整数常量池是系统在一开始就在内存中创建了一个Integer类型的数组,一般情况下保存了256个常用Integer整数,范围为-128~127之间,当使用该范围的整数时,不用重新创建对象,直接返回数组中的相应对象,以减少对象的创建和内存的消耗。

注意:使用Integer.valueOf()或者直接Integer in = 3;才会去取常量池中的值,如果使用new Integer()还是会创建对象。

// 来源于Integer类的源代码
public static Integer valueOf(int i) {
    // 如果在该范围内,会直接返回数组中已创建的对象,否则会new一个新对象
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
​
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];
​
    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;
​
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);
​
        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }
​
    private IntegerCache() {}
}

四、字符串

4.1 基本理论
  • String类是final的,不允许继承

  • String存储数据是使用char型数组

  • String类型的字面值是不能改变的,字符串的值一旦创建是不能改变的,只能重新创建一个新的对象

    • 值使用char数组存储,该数组使用final修饰,所以不能重新创建一个新数组来赋值
    • 该数组也没有提供通过数组下标修改元素的办法,所以,里面的元素也无法改变
    • 该类不能被继承,也无法通过重写方法修改方法中的逻辑达到修改元素的办法,何况该数组是私有的,即使有子类也无法访问。
4.2 字符串的常量池

当直接使用String str = "hello";时,是在常量池中创建hello,那么多次使用地址是一样的,可以通过==比较是否相等。但是使用String str = new String("hello"),是在堆中创建的对象,对象中创建过程中,先引用了常量池中的"hello",但是str中存放的堆中的地址,所以不能使用==比较,只能使用equals比较。

注意:String类中intern方法,可以将字符串重新指向常量池的地址。


public class Test2 {
    public static void main(String[] args) {
        String str1 = "hello";
        String str2 = "hello";
        String str3 = new String("hello");
        str3 = str3.intern(); // 得到常量池中的地址
        System.out.println(str1==str3); // true
    }
4.3 字符串作为参数传递【重点】

字符串的值是不能变的,所以传递到方法中,方法中如果想改变该字符串,一定会创建新的字符串,对外面的字符串没有影响,如果想将改变后的字符串返回,一定要接收返回值。


public class Test2 {
    public static void main(String[] args) {
        String str4 = "bbb";
        test(str4);
        System.out.println(str4); // 结果是bbb
    }
    
    public static void test(String s) {
        s = "aaa"; // 改变了地址的指向,对原地址没有影响
    }
}

注意:包装类作为参数传递与字符串相似。


public class Test2 {
    public static void main(String[] args) {
        Integer in = 5;
        test1(in);
        System.out.println(in); // 结果是5
    }
    
    public static void test1(Integer n) {
        n = 3;
    }
}
4.4 字符串编译时优化【重点】

字符串连接时,如果都是常量,在编译时会直接优化成一个字符串。


public class Test3 {
    public static void main(String[] args) {
        final String a = "a";
        final String b = "b";
        String a1 = "a";
        String b1 = "b";
        String ab = "ab";
        // 会直接优化成"ab"
        String s1 = "a" + "b";
        // 会优化,因为是常量
        String s2 = a + b;
        // 不会优化,因为是变量
        String s3 = a1 + b1;
        // 返回new String("ab")
        String s4 = "a".concat("b");
        // 直接返回ab
        String s5 = "ab".concat("");
        // 返回常量池的ab
        String s6 = s4.intern();
        System.out.println(ab == s5);
    }
}
4.5 字符串常用方法

char charAt(int index); 得到字符串中某个位置的字符。

boolean contains(String str); 判断是否包含某个字符串。

int indexOf(String str); 找到字符串中包含某个字符串的起始位置,如果不存在,则返回-1

char [] toCharArray(); 返回字符串中包含的数组的一个拷贝

int length(); 得到字符串的长度

String trim(); 去掉字符串两边的空格

String substring(int beginIndex); 从下标beginIndex(包含下标beginIndex的位置)开始截取,截取后面所有的

String substring(int beginIndex, int endIndex); 从下标beginIndex(包含下标beginIndex的位置)开始截取,截取到下标endIndex(不包含下标endIndex)

String toUpperCase(); 将字符串转换成大写

String toLowerCase(); 将字符串转换成小写

boolean endsWith(String str); 判断是否以某个字符串结尾

boolean startsWith(String str); 判断是否以某个字符串开头

String s.replace("oldString", "newString"); 替换字符串中的内容

String[] str5.split("符号"); 将字符串切割成一个数组

String String.join(",", array); 将数组使用某个字符串拼接成一个字符串


public class Test4 {
    public static void main(String[] args) {
        String s = "hello, world";
        char ch = s.charAt(0); // 得到某个位置的字符
        System.out.println(ch);
        
        boolean b = s.contains("world"); // 判断是否包含某个字符串
        System.out.println(b);
        
        // 找到字符串中包含某个字符串的起始位置,如果不存在,则返回-1
        int index = s.indexOf("word"); 
        System.out.println(index);
        
        // 返回字符串中包含的数组的一个拷贝
        char [] chs = s.toCharArray();
        System.out.println(Arrays.toString(chs));
        
        // 得到字符串的长度
        int len = s.length();
        System.out.println(len);
        
        String s1 = "    hello, world    ";
        // 去掉字符串两边的空格
        s1 = s1.trim();
        System.out.println(s1);
        
        // 从下标3(包含下标3的位置)开始截取,截取后面所有的
        String str = s.substring(3);
        System.out.println(str);
        
        // 从下标3(包含下标3的位置)开始截取,截取到下标8(不包含下标8)
        // 范围:[3, 8)
        String str1 = s.substring(3, 8);
        System.out.println(str1);
        
        // 将字符串转换成大写
        String str2 = s.toUpperCase();
        System.out.println(str2);
        
        // 将字符串转换成小写
        String str3 = str2.toLowerCase();
        System.out.println(str3);
        
        // 判断是否以某个字符串结尾
        boolean b1 = s.endsWith("rld");
        System.out.println(b1);
        
        // 判断是否以某个字符串开头
        boolean b2 = s.startsWith("hel");
        System.out.println(b2);
        
        // 替换字符串中的内容
        String str4 = s.replace("l", "L");
        System.out.println(str4);
        
        // 将字符串切割成一个数组
        String str5 = "湖北,湖南,江西,福建";
        String[] strings = str5.split(",");
        System.out.println(Arrays.toString(strings));
        
        // 将数组拼接成一个字符串
        String [] arr = {"aaa", "bbb", "ccc", "ddd", "eee"};
        String str6 = String.join(",", arr);
        System.out.println(str6);
    }
}

五、可变字符串

普通字符串的值不可变,如果经常需要修改字符串的值,那么可以使用可变字符串,减少字符串的创建次数和空间的浪费。特别是需要频繁拼接字符串时,应该使用可变字符串。

StringBuffer:JDK1.0提供,线程安全,效率低

StringBuilder:JDK5.0提供,线程不安全,效率高


public class Test5 {
    public static void main(String[] args) {
        // 创建时可以指定大小,如果不指定,默认为16
        // 当超出了数组大小时,会扩容,每次扩容为原来的2倍+2
        StringBuilder sb = new StringBuilder(40); // 默认为长度为16
        sb.append("hello1="); // 拼接内容
        sb.append("world2="); // 拼接内容
        sb.append("hello3="); // 拼接内容
        sb.append("world4="); // 拼接内容
        sb.append("hello5="); // 拼接内容
        sb.append("world6="); // 拼接内容
        sb.append("hello7="); // 拼接内容
        sb.append("world8="); // 拼接内容
        System.out.println(sb);
        
        // 删除delete(start, end)范围:[start, end)
        sb.delete(3, 8);
        System.out.println(sb);
        
        // 修改replace(start, end, str)
        sb.replace(3, 8, "AAA");
        System.out.println(sb);
        
        // 插入insert(offset, str)
        sb.insert(3, "BBB");
        System.out.println(sb);
    }
}

注意:在用法和使用方式上,StringBuilder和StringBuffer没区别。

六、BigDecimal

主要有两个作用:

  • 解决浮点数的精度问题
  • 解决数字计算超出最大范围的问题

public class Test6 {
    public static void main(String[] args) {
        // 精度问题
        double d = 1.0 - 0.9;
        System.out.println(d);
        double d1 = 237943729 / 67.0;
        System.out.println(d1);
        
        // 当创建大数字对象时,一定一定一定要使用字符串
        BigDecimal b1 = new BigDecimal("1.0");
        BigDecimal b2 = new BigDecimal("0.9");
        // 减法
        b1 = b1.subtract(b2);
        System.out.println(b1);
        // 加法
        b1 = b1.add(b2);
        System.out.println(b1);
        
        // 超出数字范围问题
        // 乘法
        BigDecimal b3 = new BigDecimal("34759347567863453453453453456345346345645645645645645645678689345798");
        BigDecimal b4 = new BigDecimal("4589085308503485903845908349058390485348590348590834905834590348");
        b3 = b3.multiply(b4);
        System.out.println(b3);
        
        // 除法
        // 当两个数字能除尽,则可以不指定小数位长度,否则需要指定小数位长度,不然会报错
        BigDecimal b5 = new BigDecimal("5");
        BigDecimal b6 = new BigDecimal("3");
        // 小数长度位200位,最后结果四舍五入
        b5 = b5.divide(b6, 20, BigDecimal.ROUND_HALF_UP);
        System.out.println(b5);
    }
}

注意:

  • 创建对象时,一定要传入字符串,避免不精确或超出范围
  • 当使用除法时,注意是否需要指定小数位以及四舍五入等参数,避免报错

七、日期时间相关类

7.1 Date日期类

常用的日期类,


public class Test7 {
    public static void main(String[] args) {
        Date now = new Date(); // 当前时间
        System.out.println(now);
        // 距离GMT 1970-1-1 0:0:0的毫秒数
        System.out.println(now.getTime()); 
        // before和after方法用来比较两个Date对象的早晚
    }
}
7.2 Calendar日历类

public class Test8 {
    public static void main(String[] args) {
        // 不能通过new创建对象,使用下面的方法得到唯一的对象,因为是单例模式
        Calendar c = Calendar.getInstance();
//      c.set(Calendar.MONTH, 0);
//      c.set(Calendar.DAY_OF_MONTH, 7);
        System.out.println(c);
        // 将Calendar转换成Date
        System.out.println(c.getTime());
        c.setTime(new Date()); // 将Date转换成Calendar
        System.out.println(c.getTimeInMillis()); // 得到毫秒数
        
        // 得到年
        int year = c.get(Calendar.YEAR);
        System.out.println(year);
        // 得到当年的第多少周
        int weeks = c.get(Calendar.WEEK_OF_YEAR);
        System.out.println(weeks);
        // 得到月份,从0开始
        int month = c.get(Calendar.MONTH);
        System.out.println(month);
        // 得到星期,周日为1,周一为2
        int week = c.get(Calendar.DAY_OF_WEEK);
        System.out.println(week);
        
        // 时间的计算,可以使用负数,表示向前
        c.add(Calendar.WEEK_OF_MONTH, -88);
        System.out.println(c.getTime());
    }
}

八、字符串与时间的转换SimpleDateFormat


public class Test9 {
    public static void main(String[] args) throws ParseException {
        Date now = new Date();
        // 格式化日期
        // yyyy年,MM月,dd日,hh12小时制时,a上午下午,HH24小时制时,mm分,ss秒,SSS毫秒
        SimpleDateFormat sdf = new SimpleDateFormat("中国yyyy年MM月dd日 ahh时mm分ss秒SSS毫秒");
        // 将日期格式化成字符串
        String str = sdf.format(now);
        System.out.println(str);
        
        // 将字符串转换成日期对象
        String str1 = "2021-10-10 14:12:12";
        SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date d1 = sdf1.parse(str1);
        System.out.println(d1);
    }
}

九、System系统类


public class Test10 {
    public static void main(String[] args) {
        // 得到距离...毫秒数
        System.out.println(System.currentTimeMillis());
        // 提示系统可以回收垃圾
//      System.gc();
        // 停止系统(杀死)
        System.exit(0);
        // 不会执行
        System.out.println("程序结束");
    }
}