06-常用类

常用类

问:String、StringBuffer和StringBuilder

(1) String:不可变的字符序列

  • String类不是基本数据类型,是引用数据类型

  • String是被final修饰的类,因此一旦一个String对象被创建并初始化后,包含在这个对象里的字符序列是不可改变的,直到这个对象被销毁,即本身的内容不可改变、长度不可改变;

  • String类不可以被继承;

  • String变量作为参数时相当于基本数据类型的值传递

  • 对String对象的任何改变都不影响到原对象,每次操作都会生成新的String对象,然后将引用指向新的String对象,因此不推荐频繁改变String内容,会增加内存压力。为了提高效率节省空间,我们应该用StringBuffer类

  • 当多个String对象用+运算符联合时,其本质是先创建StringBuilder对象再进行append操作,然后将拼接后的StringBuilder对象用toString()方法转换成String对象,速度较慢。

    • 例如:String a = “abc”; String b = “def”; String c = a + b; 这种情况下是使用concatenation操作符(+),内部是新创建StringBuffer或StringBuilder对象,利用其append方法进行字符串追加,然后利用toString方法返回String串。所以此时的效率也不高。

例1:定义String的两种方式

方式1:双引号引起的数据都是字符串对象,存放在常量池中。str指向的内存中只有一个对象,在常量池中。

String str="abc";

//这种方式创建的String对象"abc"是存放在字符串常量池中的
//创建过程是:首先在字符串常量池中查找有没有"abc"对象,如果有则直接将str指向它;如果没有,就在字符串常量池中创建出"abc",然后将str指向它。
//通过这种方式创建的字符串在字符串常量池中是可共享的,同时也是不可更改的。

方式2:str指向的内存中有两个对象,分别在常量池中和堆内存中。

String str=new String("abc");

//这种方式在内存中生成两个对象,"abc"存放在常量池中,new String()存放在堆内存中。
//创建过程是:这种方式首先在常量池中创建"abc",然后在堆内存中new出一个String对象,将str指向该堆内存地址,新new出的String对象的内容是在字符串常量池中找到的"abc"对象。
//通过这种方式创建对象,实际上创建了两个对象。

例2:Java中的字符串都是常量,存放在方法区中的常量池中,可以实现共享。

String s1="abc";
String s2=new String("abc");
String s3="abc";
/*"abc"是字符串常量,被存在堆区的常量池中,当定义String s3="abc"时,不会马上创建字符串"abc"对象,而是会先查找常量池中是否存在相同常量,如果有,则s3指向同一内存空间,否则创建新的字符串对象。*/

System.out.println(s1==s2); //false
System.out.println(s1==s3); //true

//String类重写了Object类的equals()方法,使其由比较引用变为了比较引用所指向的字符串内容是否一样。
System.out.println(s1.equals(s2)); //true

例3:Java中的字符串都是常量,字符串一旦被初始化,就不可以被改变。

String str1 = "abc";  
String str2 = str1 + "def";
System.out.println(str1==str2); //false
//此时,字符串常量池中存在了两个对象"abc"和"abcdef"。

例4:String s=”a”+”b”+”c”+”d”;这条语句一共创建了多少个对象?

String s1 = "a";
String s2 = s1 + "b";
String s3 = "a" + "b";
System.out.println(s2 == "ab");
System.out.println(s3 == "ab");

第一条语句打印的结果为false,第二条语句打印的结果为true
这说明Java可以对字符串常量直接相加的表达式进行优化,不必要等到运行期去进行加法运算处理,而是在编译时去掉其中的加号,直接将其编译成一个这些常量相连的结果。

因此String s="a"+"b"+"c"+"d";只创建了一个String对象

例5例6例7例8:编译器优化

(2) StringBuffer:线程安全的可变字符序列

  • 对StringBuffer对象进行操作都是对其本身的字符序列进行操作,而不是生成新的对象。因此StringBuffer对象内容可变、长度可变,可以将多个字符串值直接联合,效率高
  • String覆盖了equals方法和hashCode方法,而StringBuffer没有覆盖equals方法和hashCode方法,所以,将StringBuffer对象存储进Java集合类中时会出现问题。

例1

定义有StringBuffer s1=new StringBuffer(10);s1.append("1234")则s1.length()和s1.capacity()分别是:4和10

(3) StringBuilder:非线程安全的可变字符序列

注意:StringBuffer和StringBuilder在功能上基本完全相同,它们都继承自AbstractStringBuilder,而AbstractStringBuilder是使用非final修饰的字符数组实现的,如:char[] value; 所以可以对StringBuffer和StringBuilder对象进行改变,每次改变都是在原来的对象上发生的,不会重新new出新的StringBuffer或StringBuilder对象。所以,当我们需要频繁修改字符串内容的时候,使用StringBuffer和StringBuilder是很好地选择。两者的核心操作都是append和insert,append是直接在字符串的末尾追加,而insert(int index,String str)是在指定位置出插入字符串。 StringBuffer和StringBuilder的最主要区别就是线程安全方面,由于在StringBuffer内大部分方法都添加了synchronized同步,所以StringBuffer是线程安全的,而StringBuilder不是线程安全的。因此,当我们处于多线程的环境下时,我们需要使用StringBuffer,如果我们的程序是线程安全的使用StringBuilder在性能上就会更优一点。

问:数组有没有length()方法? String有没有length()方法?

数组没有length()方法,但有length属性;String有length()方法。

问:String的几个方法

例1:易错的题目:replaceAll()方法

public static void main (String[] args) { 
    String classFile = "com.jd.". replaceAll(".", "/") + "MyClass.class";
    System.out.println(classFile);
}

输出为://///////MyClass.class
replaceAll方法的第一个参数是一个正则表达式,而"."在正则表达式中表示任何字符,所以会把前面字符串的所有字符都替换成"/"。
如果想替换的只是"."的话,正则表达式那里就要写成"\\."或者是"[.]"。前者将"."转义为"."这个具体字符,后者则匹配"[]"中的任意字符。

例2:易错的题目:split()方法

当直接以”.”为参数时,结果错误:

public static void main(String[] args) {
    String str = "12.03";  
    String[] res = str.split(".");
    for(int i=0; i<res.length; i++){
        System.out.println(res[i]);
    }

}

输出为空
String的split(String regex)是根据给定的正则表达式来拆分字符串的
"."是正则表达式中的关键字,没有经过转义split会把它当作一个正则表达式来处理的,需要写成str.split("\\.")进行转义处理。

例3:字符串分割,如何把一段逗号分割的字符串转换成一个数组?

方法一:使用正则表达式

    String str="abc,edg,efd,adfa";
    String [] result = str.split(",");

方法二:使用StingTokenizer

    String str="abc,edg,efd,adfa";
    StringTokenizer tokener = new StringTokenizer(str, ",");
    int length=tokener.countTokens();
    String [] result =new String[length];
    for(int i=0; i<length; i++){
        result[i]=tokener.nextToken();
    }

例3:如何实现字符串的反转?

示例:递归实现字符串反转

public String reverse(String originStr) {
    if (originStr == null || originStr.length() <= 1)
        return originStr;
    return reverse(originStr.substring(1)) + originStr.charAt(0);
}

问:String的编码转换

编码转换,实现将GB2312编码的字符串转换为ISO-8859-1编码的字符串:

String str="中"
String a=new String(str.getBytes("gb2312"), "iso-8859-1");

编码转换,实现将GBK编码字节流转换为UTF-8编码字节流:

byte[] src, dst;
dst=new String (src, "GBK").getbytes("UTF-8");

问:Math类

(1) Math.floor(a) 求小于或等于a的最大整数,返回double类型。

Math.floor(11.6)=11.0;
Math.floor(-11.3)=-12.0;
// 如果参数值总是等于某个整数,那么结果与该参数相同
Math.floor(1)=1.0
// 如果参数是NaN、无穷、正0、负0,那么结果与参数相同
Math.floor(Double.NaN)=Double.NaN
Math.floor(Double.POSITIVE_INFINITY)=Double.POSITIVE_INFINITY
Math.floor(Double.NEGATIVE_INFINITY)=Double.NEGATIVE_INFINITY
Math.floor(0.0)=0.0
Math.floor(-0.0)=-0.0

(2) Math.ceil(a) 求大于或等于a的最小整数,返回double类型。

Math.ceil(11.3)=12.0
Math.ceil(-11.3)=-11.0;
// 如果参数值总是等于某个整数,那么结果与该参数相同
Math.ceil(1)=1.0
// 如果参数是NaN、无穷、正0、负0,那么结果与参数相同
Math.ceil(Double.NaN)=Double.NaN
Math.ceil(Double.POSITIVE_INFINITY)=Double.POSITIVE_INFINITY
Math.ceil(Double.NEGATIVE_INFINITY)=Double.NEGATIVE_INFINITY
Math.ceil(0.0)=0.0
Math.ceil(-0.0)=-0.0
//如果参数小于0但大于-1.0,那么结果为-0.0(重要)
Math.ceil(-0.5)=-0.0 

(3) Math.round(a)=(long)floor(a+0.5d);将原来的数字加上0.5后再向下取整,返回long类型,注意向下取整是指小于或等于它的最大整数。

Math.round(6.4)=(long)floor(6.4+0.5d)=(long)floor(6.9)=6
Math.round(6.9)=(long)floor(6.9+0.5d)=(long)floor(7.4)=7
Math.round(6.5)=(long)floor(6.5+0.5d)=(long)floor(7.0)=7

Math.round(-6.4)=(long)floor(-6.4+0.5d)=(long)floor(-5.9)=-6
Math.round(-6.9)=(long)floor(-6.9+0.5d)=(long)floor(-6.4)=-7
Math.round(-6.5)=(long)floor(-6.5+0.5d)=(long)floor(-6.0)=-6

// 了解
Math.round(NaN)=0
Math.round(Math.round(Double.NEGATIVE_INFINITY或<=Long.MIN_VALUE))=Long.MIN_VALUE
Math.round(Math.round(Double.POSITIVE_INFINITY或>=Long.MAX_VALUE))=Long.MAX_VALUE

问:枚举类型

Java中定义枚举类型要使用enum关键字,所有的枚举值对象都默认为static

(1) 枚举值对象可以用中文字符,枚举值组成可以由字母、下划线组成,但不能单独使用数字。

例1:带中文枚举值对象的枚举类型

public class Main {
    public static void main(String[] args) {
        System.out.println(Genders.男);
        System.out.println(Genders.女);
        System.out.println(Genders.MAN);
        System.out.println(Genders.WOWAM);
    }
}

enum Genders {
    男, 女, MAN, WOWAM
}

输出:

    男
    WOWAM

(2) 带有构造方法的枚举类型

  • 枚举类型的构造方法访问控制符只能为private(且默认为private);
  • 在初始化时会对所有的枚举值对象调用一次构造函数进行初始化;
  • 含带参构造方法的枚举类型中的枚举值对象必须初始化;
  • 枚举类型中含有其他属性或方法时,枚举值对象必须定义在最前面,且需要在最后一个枚举值对象后面加分号”;”

(3) 枚举类型都继承了Enum类,因为枚举类型都不能再继承其他类了,但是可以实现其他接口

public class Main {
    public static void main(String[] args) {
        System.out.println("吉普信息:"+Cars.JEEP.getName() + "\t" +Cars.JEEP.getPrice());

        Car mini=Cars.MINI;
        mini.setCarName("mini2");
        System.out.println("Mini新名字\t"+Cars.MINI.getName());

        for(Cars car : Cars.values()){
            System.out.println(car.getName() + "\t" + car.getPrice());
        }
    }
}

interface Car{
    public void setCarName(String name);
}

enum Cars implements Car {
    BMW("宝马", 1000000), JEEP("吉普", 800000), MINI("mini", 200000);

    private String name;
    /**
     * 从这里可以看出虽然枚举值不能直接由数字组成,但是我们可以给该枚举类添加一个int类型的值,通过构造方法给其赋值,相当于间接的可以使用数值
     */
    private int price;

    private Cars(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    @Override
    public void setCarName(String name) {
        this.name = name;
    }
}

输出如下:

    吉普信息:吉普    800000
    宝马    1000000
    吉普    800000
    mini    200000
    Mini新名字    mini2

例1:

enum AccountType{
    SAVING, FIXED, CURRENT;
    private AccountType(){
        System.out.println("It is a account type");
    }
}
class EnumOne{
    public static void main(String[]args){
        System.out.println(AccountType.FIXED);
    }
}

程序输出如下:
It is a account type
It is a account type
It is a account type
FIXED

问:基本数据类型的包装类

Java语言是一个面向对象的语言,但Java中的基本数据类型却是不面向对象的,这在实际使用时存在很多的不便,为了解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,即包装类。

Java为每个基本数据类型提供了包装类型:

  • 基本数据类型: byte,short,int,long,float,double,char,boolean
  • 包装数据类型:Byte,Short,Integer,Long,Float,Double,Character,Boolean

例1:

class Two{
    Byte x;
}
class PassO{
    public static void main(String[] args){
        PassO p=new PassO();
        p.start();
    }
    void start(){
        Two t=new Two();
        System.out.print(t.x+””);
        Two t2=fix(t);
        System.out.print(t.x+” ” +t2.x);
    }
    Two fix(Two tt){
        tt.x=42;
        return tt;
    }
}

上面代码的输出为:null 42 42
基本数据类型的包装类型,属于引用类型。如果包装类成员变量没有显示初始化,那么Java默认初始化为null。

例2:下面哪些类可以被继承?

A、Thread    B、Number    C、Double    D、Math    E、ClassLoader

正确答案:ABE

(1) 我们可以通过继承Thread类来创建线程;
(2) Byte、Short、Integer、Long、Float、Double这几个数字类型都继承自Number;
(3) Byte、Short、Integer、Long、Float、Double、Boolean、Character这几个基本数据类型的包装类,以及String、Math等都被定义为public final class,因此这几个都不能被继承;
(4) 我们可以自定义类加载器来实现加载功能,因此ClassLoader是可以被继承的。

问:装箱与拆箱

从Java 5开始引入了自动装箱/拆箱机制,使得二者可以相互转换。自动装箱是Java编译器在基本数据类型和对应的包装类型之间做的一个转化,例如把int转化成Integer,double转化成Double,等等。反之就是自动拆箱。

装箱的本质是什么呢?当我们给一个Integer对象赋一个int值的时候,会调用Integer类的静态方法valueOf()。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

/**
 * Cache to support the object identity semantics of autoboxing for values between
 * -128 and 127 (inclusive) as required by JLS.
 *
 * The cache is initialized on first usage.  The size of the cache
 * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
 * During VM initialization, java.lang.Integer.IntegerCache.high property
 * may be set and saved in the private system properties in the
 * sun.misc.VM class.
 */

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() {}
}

如果整型字面量的值在-128到127之间,那么不会new新的Integer对象,而是直接引用常量池中的Integer对象。

问:装箱与拆箱中的==和equals()

下面的规则基本意思是对的,但语言尚不严格正确

(1) 基本数据类型变量和基本数据类型包装类对象进行”==”运算符的比较,基本数据类型封装类对象将会自动拆箱变为基本数据类型后再进行比较。例如Integer(0)和0比较时,Integer(0)会自动拆箱为int类型再进行比较。

(2) 两个Integer类型变量进行”==”比较,如果这两个对象的值在-128至127且值相等,那么返回true,否则返回false,这跟Integer.valueOf()方法的缓冲对象有关。请看这篇文章

(3)

(4) 两个基本数据类型封装类变量进行equals()比较,equals()会首先比较对象的类型,如果类型相同,继续比较值,如果值也相同,返回true,否则返回false。

(5) 基本数据类型封装类对象调用equals(),但是参数是基本数据类型变量,这时候,基本数据类型变量会先进行自动装箱转换为其封装类型对象,再进行4中的比较。

例1:

int a=257;
Integer b1=257; //Integer b1=257会先调用Integer.valueOf()方法将257转化为new Integer(257)对象
Integer b2=57;  //Integer b2=57会先调用Integer.valueOf()方法返回缓存中的57
Integer b3=257;
Integer b4=57;
Integer c=new Integer(257);
Integer d=new Integer(257);
Integer e1=Integer.valueOf(257); //Integer.valueOf()方法将257转化为new Integer(257)对象
Integer e2=Integer.valueOf(57);  //Integer.valueOf()方法返回缓存中的57

// int和Integer(无论new否)比,都为true
System.out.println(a==b1); // true,规则(1)
System.out.println(a==c); // true,规则(1)

// 两个都是非new出来的Integer,如果数在-128到127之间,则是true,否则为false 
System.out.println(b1==b3); // false,规则(2)
System.out.println(b2==b4); // true,规则(2)
System.out.println(b1==e1); // false,规则(3)
System.out.println(b2==e2); // true,规则(3)

// Integer与new Integer比较,结果为false;两个都是new出来的,都为false
System.out.println(b1==c); // false,规则(3)
System.out.println(c==d); // false,规则(3)

//System.out.println(a.equals(b1));  编译出错,基本型不能调用equals()
System.out.println(b1.equals(b3)); // true,规则(4)
System.out.println(c.equals(d)); // true,规则(4)

System.out.println(b1.equals(257.0)); // false,规则(5),257.0先封装成Double对象再进行比较

例2:

public static void main(String[] args){    
    Boolean flag=false;//先调用Boolean.valueOf(boolean b)返回false对应的Boolean对象Boolean.FALSE,然后赋值给flag,flag值为Boolean.FALSE    
    /* 先赋值,遇到if条件表达式自动拆箱     
     * 1. 先调用Boolean.valueOf(boolean b)返回true对应的Boolean对象Boolean.TRUE,然后赋值给flag,flag值为Boolean.TRUE     
     * 2. 调用booleanValue()返回flag值对应的基础数据类型值true     
     * 3. 结果输出true     
     */    
    if (flag=true){        
        System.out.println("true");    
    }else{        
        System.out.println("false");    
    }
}

例3例4例5例6:重要的题目

问:如何将数值型字符串转换为对应类型的数字?

例1:

Integer.parseInt("1234");
Double.parseDouble("123.2");

问:File类

例1:编程列出一个目录下所有的文件

(1) 如果只要求列出当前文件夹下的文件,代码如下所示

public static void main(String[] args) {
    File file = new File("C:/Users/Hepsilion/Desktop");
    for(File f : file.listFiles()) {
        if(f.isFile()) {
            System.out.println(f.getName());
        }
    }
}

(2) 如果需要对文件夹继续展开,代码如下所示

public static void main(String[] args) {
    showDirectory(new File("C:/Users/Hepsilion/Desktop"), 0);
}

private static void showDirectory(File file, int level) {
    if (file.isDirectory()) {
        for (File f : file.listFiles()) {
            showDirectory(f, level + 1);
        }
    } else {
        for (int i = 0; i < level - 1; i++) {
            System.out.print("\t");
        }
        System.out.println(file.getName());
    }
}

在Java 7中可以使用NIO.2的API来做同样的事情

import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;

public class Main {
    public static void main(String[] args) throws IOException {
        Path initPath = Paths.get("C:/Users/Hepsilion/Desktop");
        Files.walkFileTree(initPath, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println(file.getFileName().toString());
                return FileVisitResult.CONTINUE;
            }
        });
    }
}

问:日期类

例1: 使用Java编程,打印昨天的当前时刻

public class YesterdayCurrent{
    public static void main(String[] args){
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.DAY_OF_MONTH, -1);//从当前日期减去一天
        System.out.println(cal.getTime());
    }
} 

例2: 如何获取当前时间对应的年月日时分秒?

public class CurrentTime{
    public static void main(String[] args){
        Calendar c=Calendar.getInstance();
        System.out.print(c.get(Calendar.YEAR)+"年"+(c.get(Calendar.MONTH)+1)+"月"+c.get(Calendar.DAY_OF_MONTH)+"日, ";
        System.out.println(c.get(Calendar.HOUR_OF_DAY)+":"+c.get(Calendar.MINUTE)+":"+c.get(Calendar.SECOND));
    }
}

public class CurrentTime{
    public static void main(String[] args){
        Date date=new Date();
        System.out.println(date);
    }
}

例3: 如何取得从1970年到现在的毫秒数

public static void main(String[] args){
    System.out.println(System.currentTimeMillis());
}

例4: 如何格式化日期?SimpleDateFormat

public static void main(String[] args){
    SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    Date date=new Date();
    String dateStr=sdf.format(date);//把日期按指定格式转化为字符串
    System.out.println(dateStr);
}

例5: Java 8中的Date API

Java 8中在java.time包下包含了一组全新的时间日期API,新的日期API和开源的Joda-Time库差不多,但又不完全一样,这组新API里最重要的一些部分是Clock时钟,Clock类提供了访问当前日期和时间的方法,Clock是时区敏感的,可以用来取代System.currentTimeMillis()来获取当前的微秒数。某一个特定的时间点也可以使用Instant类来表示,Instant类也可以用来创建老的java.util.Date对象。

public static void main(String[] args) {
    Clock clock = Clock.systemDefaultZone();
    long millis = clock.millis();
    Instant instant = clock.instant();
    Date date = Date.from(instant);
    System.out.println(millis);
    System.out.println(instant);
    System.out.println(date);
}

//输出:
1504834478293
2017-09-08T01:34:38.293Z
Fri Sep 08 09:34:38 CST 2017
分享到 评论