VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • Java学习_Java核心类

  • 字符串和编码
    • 字符串在String内部是通过一个char[]数组表示的,因此,可以按下面的写法:
      String s2 = new String(new char[] {'H', 'e', 'l', 'l', 'o', '!'});
    • Java字符串的一个重要特点就是字符串不可变。这种不可变性是通过内部的private final char[]字段,以及没有任何修改char[]的方法实现的。
    • Java编译器在编译期,会自动把所有相同的字符串当作一个对象放入常量池。
    • 两个字符串比较,必须总是使用equals()方法。
    • 要忽略大小写比较,使用equalsIgnoreCase()方法。
    • String还提供了isEmpty()isBlank()来判断字符串是否为空和空白字符串
    • 拼接字符串使用静态方法join(),它用指定的字符串连接字符串数组。
      String[] arr = {"A", "B", "C"};
      String s = String.join("***", arr); // "A***B***C"
    • 字符串提供了formatted()方法和format()静态方法,可以传入其他参数,替换占位符,然后生成新的字符串。
      复制代码
      1 public class Main {
      2     public static void main(String[] args) {
      3         String s = "Hi %s, your score is %d!";
      4         System.out.println(s.formatted("Alice", 80));
      5         System.out.println(String.format("Hi %s, your score is %.2f!", "Bob", 59.5));
      6     }
      7 }
      复制代码
    • 要把任意基本类型或引用类型转换为字符串,可以使用静态方法valueOf()
      1 String.valueOf(123); // "123"
      2 String.valueOf(45.67); // "45.67"
      3 String.valueOf(true); // "true"
      4 String.valueOf(new Object()); // 类似java.lang.Object@636be97c
    • new String(char[])创建新的String实例时,它并不会直接引用传入的char[]数组,而是会复制一份,所以,修改外部的char[]数组不会影响String实例内部的char[]数组,因为这是两个不同的数组。
    • 那我们经常使用的UTF-8又是什么编码呢?因为英文字符的Unicode编码高字节总是00,包含大量英文的文本会浪费空间,所以,出现了UTF-8编码,它是一种变长编码,用来把固定长度的Unicode编码变成1~4字节的变长编码。通过UTF-8编码,英文字符'A'UTF-8编码变为0x41,正好和ASCII码一致,而中文'中'UTF-8编码为3字节0xe4b8ad
  • StringBuilder

    • String虽然可以直接拼接字符串,但是,在循环中,每次循环都会创建新的字符串对象,然后扔掉旧的字符串。
    • 为了能高效拼接字符串,Java标准库提供了StringBuilder,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder中新增字符时,不会创建新的临时对象。
    • StringBuilder还可以进行链式操作。
      复制代码
       1 public class Main {
       2     public static void main(String[] args) {
       3         var sb = new StringBuilder(1024);
       4         sb.append("Mr ")
       5           .append("Bob")
       6           .append("!")
       7           .insert(0, "Hello, ");
       8         System.out.println(sb.toString());
       9     }
      10 }
      复制代码
    • 查看StringBuilder的源码,可以发现,进行链式操作的关键是,定义的append()方法会返回this,这样,就可以不断调用自身的其他方法。
    • 对于普通的字符串+操作,并不需要我们将其改写为StringBuilder,因为Java编译器在编译时就自动把多个连续的+操作编码为StringConcatFactory的操作。在运行期,StringConcatFactory会自动把字符串连接操作优化为数组复制或者StringBuilder操作。
    • public StringBuilder delete​(int start, int end)
  • StringJoiner
    • StringJoiner​(CharSequence delimiter) 
    • StringJoiner​(CharSequence delimiter, CharSequence prefix, CharSequence suffix)
  • 包装类型
    • 自动装箱(Auto Boxing)
    • 所有的包装类型都是不变类。
      1 //源码
      2 public final class Integer {  
      3     private final int value;
      4 }
    • 我们把能创建“新”对象的静态方法称为静态工厂方法。Integer.valueOf()就是静态工厂方法,它尽可能地返回缓存的实例以节省内存。创建新对象时,优先选用静态工厂方法而不是new操作符。

    • 程序设计的一个重要原则:数据的存储和显示要分离。
    • 处理无符号整型
  • JavaBean

    • class的定义都符合这样的规范:

      • 若干private实例字段;
      • 通过public方法来读写实例字段。
        // 读方法:
        public Type getXyz()
        // 写方法:
        public void setXyz(Type value)
      • boolean字段比较特殊,它的读方法一般命名为isXyz()。
        // 读方法:
        public boolean isChild()
        // 写方法:
        public void setChild(boolean value)
      • 把一组对应的读方法(getter)和写方法(setter)称为属性(property)。例如,name属性:

        • 对应的读方法是String getName()
        • 对应的写方法是setName(String)
        • 只有getter的属性称为只读属性(read-only)
        • 只有setter的属性称为只写属性(write-only枚举一个JavaBean的所有属性,可以直接使用Java核心库提供的Introspector。
      • 复制代码
         1 import java.beans.*;
         2 
         3 public class Main {
         4     public static void main(String[] args) throws Exception {
         5         BeanInfo info = Introspector.getBeanInfo(Person.class);
         6         for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
         7             System.out.println(pd.getName());
         8             System.out.println("  " + pd.getReadMethod());
         9             System.out.println("  " + pd.getWriteMethod());
        10         }
        11     }
        12 }
        13 
        14 class Person {
        15     private String name;
        16     private int age;
        17 
        18     public String getName() {
        19         return name;
        20     }
        21 
        22     public void setName(String name) {
        23         this.name = name;
        24     }
        25 
        26     public int getAge() {
        27         return age;
        28     }
        29 
        30     public void setAge(int age) {
        31         this.age = age;
        32     }
        33 }
        
        输出:
        age
          public int Person.getAge()
          public void Person.setAge(int)
        class
          public final native java.lang.Class java.lang.Object.getClass()
          null
        name
          public java.lang.String Person.getName()
          public void Person.setName(java.lang.String)
        复制代码
  • 枚举类
    • Java中,我们可以通过static final来定义常量。但使用这些常量来表示一组枚举值的时候,有一个严重的问题就是,编译器无法检查每个值的合理性。定义的常量仍可与其他变量比较。
      复制代码
       1 class Weekday {
       2     public static final int SUN = 0;
       3     public static final int MON = 1;
       4     public static final int TUE = 2;
       5     public static final int WED = 3;
       6     public static final int THU = 4;
       7     public static final int FRI = 5;
       8     public static final int SAT = 6;
       9 }
      10 
      11 //可编译下列代码
      12 if (weekday == 6 || weekday == 7) {   //Weekday定义的常量范围是0~6,并不包含7,编译器无法检查不在枚举中的int值;
      13     if (tasks == Weekday.MON) {
      14         // TODO:
      15     }
      16 }
      复制代码
    • 定义枚举类是通过关键字enum实现的,只需依次列出枚举的常量名。enum常量本身带有类型信息,即Weekday.SUN类型是Weekday(第二代码块),编译器会自动检查出类型错误。
      复制代码
       1 public class Main {
       2     public static void main(String[] args) {
       3         Weekday day = Weekday.SUN;
       4         if (day == Weekday.SAT || day == Weekday.SUN) {
       5             System.out.println("Work at home!");
       6         } else {
       7             System.out.println("Work at office!");
       8         }
       9     }
      10 }
      11 
      12 enum Weekday {
      13     SUN, MON, TUE, WED, THU, FRI, SAT;
      14 }
      复制代码
      复制代码
       1 public class Main {
       2     public static void main(String[] args) {
       3         Weekday day = Weekday.SUN;
       4         System.out.println(day.getClass());
       5     }
       6 }
       7 
       8 enum Weekday {
       9     SUN, MON, TUE, WED, THU, FRI, SAT;
      10 }
      复制代码
    • 使用enum定义的枚举类是一种引用类型。比较要使用equals()方法,但enum类型的每个常量在JVM中只有一个唯一实例,所以可以直接用==比较。
    • enum定义的类型就是class,有以下几个特点:

      • 定义的enum类型总是继承自java.lang.Enum,且无法被继承;
      • 只能定义出enum的实例,而无法通过new操作符创建enum的实例;
      • 定义的每个实例都是引用类型的唯一实例;
      • 可以将enum类型用于switch语句。
    • enum是一个class,每个枚举的值都是class实例,因此,这些实例有一些方法:
      • name() 返回常量名   
        String s = Weekday.SUN.name(); // "SUN"
      • ordinal() 返回定义的常量的顺序,从0开始计数

        int n = Weekday.MON.ordinal(); // 1
      • values() 返回枚举类中所有的值。
      • 枚举跟普通类一样可以用自己的变量、方法和构造函数,构造函数只能使用 private 访问修饰符,所以外部无法调用。enum的构造方法要声明为private,字段强烈建议声明为final。
        复制代码
         1 public class Main {
         2     public static void main(String[] args) {
         3         Weekday day = Weekday.SUN;
         4         if (day.dayValue == 6 || day.dayValue == 0) {
         5             System.out.println("Today is " + day + ". Work at home!");
         6         } else {
         7             System.out.println("Today is " + day + ". Work at office!");
         8         }
         9     }
        10 }
        11 
        12 enum Weekday {
        13     MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");
        14 
        15     public final int dayValue;
        16     private final String chinese;
        17 
        18     private Weekday(int dayValue, String chinese) {
        19         this.dayValue = dayValue;
        20         this.chinese = chinese;
        21     }
        22 
        23     @Override
        24     public String toString() {
        25         return this.chinese;
        26     }
        27 }
        复制代码
      • 枚举类中的抽象方法实现,需要枚举类中的每个对象都对其进行实现。
        复制代码
         1 public class Test{
         2     public static void main(String[] args) {
         3         for (Color c:Color.values()){
         4             System.out.print(c.getColor() + "、");
         5         }
         6     }
         7 }
         8 
         9 enum Color{
        10     RED{
        11         public String getColor(){//枚举对象实现抽象方法
        12             return "红色";
        13         }
        14     },
        15     GREEN{
        16         public String getColor(){//枚举对象实现抽象方法
        17             return "绿色";
        18         }
        19     },
        20     BLUE{
        21         public String getColor(){//枚举对象实现抽象方法
        22             return "蓝色";
        23         }
        24     };
        25     public abstract String getColor();//定义抽象方法
        26 }
        复制代码
      • switch内使用(枚举开关的大小写标签必须是枚举常量的非限定名称,通俗的讲,就是不带类名,如不能是Weekday.MON,而是MON)
        复制代码
         1 public class Main {
         2     public static void main(String[] args) {
         3         Weekday day = Weekday.SUN;
         4         switch(day) {
         5         case MON:
         6         case TUE:
         7         case WED:
         8         case THU:
         9         case FRI:
        10             System.out.println("Today is " + day + ". Work at office!");
        11             break;
        12         case SAT:
        13         case Weekday.SUN:
        14             System.out.println("Today is " + day + ". Work at home!");
        15             break;
        16         default:
        17             throw new RuntimeException("cannot process " + day);
        18         }
        19     }
        20 }
        21 
        22 enum Weekday {
        23     MON, TUE, WED, THU, FRI, SAT, SUN;
        24 }
        复制代码
  • 纪录类
    • StringInteger等类型都是不变类,一个不变类具有以下特点:
      • 定义class时使用final,无法派生子类;
      • 每个字段使用final,保证创建实例后无法修改任何字段。
    • Java 14开始,引入了新的Record类。
      复制代码
       1 public class Main {
       2     public static void main(String[] args) {
       3         Point p = new Point(123, 456);
       4         System.out.println(p.x());
       5         System.out.println(p.y());
       6         System.out.println(p);
       7     }
       8 }
       9 
      10 public record Point(int x, int y) {}
      复制代码
      复制代码
      public record Point(int x, int y) {}
      
      //把上述定义改写为class,相当于以下代码:
      
      public final class Point extends Record {
          private final int x;
          private final int y;
      
          public Point(int x, int y) {
              this.x = x;
              this.y = y;
          }
      
          public int x() {
              return this.x;
          }
      
          public int y() {
              return this.y;
          }
      
          public String toString() {
              return String.format("Point[x=%s, y=%s]", x, y);
          }
      
          public boolean equals(Object o) {
              ...
          }
          public int hashCode() {
              ...
          }
      }
      复制代码
    • 编译器默认按照record声明的变量顺序自动创建一个构造方法,并在方法内给字段赋值。假设Point类的xy不允许负数,我们就得给Point的构造方法加上检查逻辑。

      复制代码
      public record Point(int x, int y) {
          public Point {
              if (x < 0 || y < 0) {
                  throw new IllegalArgumentException();
              }
          }
      }
      
      //方法public Point {...}被称为Compact Constructor,它的目的是让我们编写检查逻辑,编译器最终生成的构造方法如下:
      
      public final class Point extends Record {
          public Point(int x, int y) {
              // 这是我们编写的Compact Constructor:
              if (x < 0 || y < 0) {
                  throw new IllegalArgumentException();
              }
              // 这是编译器继续生成的赋值代码:
              this.x = x;
              this.y = y;
          }
          ...
      }
      复制代码
    • 作为recordPoint仍然可以添加静态方法。一种常用的静态方法是of()方法,用来创建Point。

      复制代码
       1 public record Point(int x, int y) {
       2     public static Point of() {
       3         return new Point(0, 0);
       4     }
       5     public static Point of(int x, int y) {
       6         return new Point(x, y);
       7     }
       8 }
       9 
      10 //这样我们可以写出更简洁的代码:
      11 
      12 var z = Point.of();
      13 var p = Point.of(123, 456);
      复制代码
  • BigInteger

    • 使用的整数范围超过了long型时。java.math.BigInteger用来表示任意大小的整数。BigInteger内部用一个int[]数组来模拟一个非常大的整数。
      BigInteger bi = new BigInteger("1234567890");
      System.out.println(bi.pow(5)); // 2867971860299718107233761438093672048294900000
    • BigInteger做运算的时候,只能使用实例方法。
      BigInteger i1 = new BigInteger("1234567890");
      BigInteger i2 = new BigInteger("12345678901234567890");
      BigInteger sum = i1.add(i2); // 12345678902469135780
    • BigIntegerIntegerLong一样,也是不可变类,并且也继承自Number类。

      • 转换为bytebyteValue()
      • 转换为shortshortValue()
      • 转换为intintValue()
      • 转换为longlongValue()
      • 转换为floatfloatValue()
      • 转换为doubledoubleValue()
    • BigInteger的值甚至超过了float的最大范围(3.4x1038),那么返回的float是 InfinityBigDecimal
  • BigDecimal
    • BigInteger类似,BigDecimal可以表示一个任意大小且精度完全准确的浮点数。
      BigDecimal bd = new BigDecimal("123.4567");
      System.out.println(bd.multiply(bd)); // 15241.55677489
    • BigDecimalscale()表示小数位数。

      复制代码
      BigDecimal d1 = new BigDecimal("123.45");
      BigDecimal d2 = new BigDecimal("123.4500");
      BigDecimal d3 = new BigDecimal("1234500");
      System.out.println(d1.scale()); // 2,两位小数
      System.out.println(d2.scale()); // 4
      System.out.println(d3.scale()); // 0
      复制代码
    • 通过BigDecimalstripTrailingZeros()方法,可以将一个BigDecimal格式化为一个相等的,但去掉了末尾0的BigDecimal。

      复制代码
       1 BigDecimal d1 = new BigDecimal("123.4500");
       2 BigDecimal d2 = d1.stripTrailingZeros();
       3 System.out.println(d1.scale()); // 4
       4 System.out.println(d2.scale()); // 2,因为去掉了00
       5 
       6 BigDecimal d3 = new BigDecimal("1234500");
       7 BigDecimal d4 = d3.stripTrailingZeros();
       8 System.out.println(d3.scale()); // 0
       9 System.out.println(d4.scale()); // -2
      10 //如果一个BigDecimal的scale()返回负数,例如,-2,表示这个数是个整数,并且末尾有2个0。
      复制代码
    • 可以对一个BigDecimal设置它的scale,如果精度比原始值低,那么按照指定的方法进行四舍五入或者直接截断。

      复制代码
      import java.math.BigDecimal;
      import java.math.RoundingMode;
      
      public class Main {
          public static void main(String[] args) {
              BigDecimal d1 = new BigDecimal("123.456789");
              BigDecimal d2 = d1.setScale(4, RoundingMode.HALF_UP); // 四舍五入,123.4568
              BigDecimal d3 = d1.setScale(4, RoundingMode.DOWN); // 直接截断,123.4567
              System.out.println(d2);
              System.out.println(d3);
          }
      }
      复制代码
    • BigDecimal做加、减、乘时,精度不会丢失,但是做除法时,存在无法除尽的情况,这时,就必须指定精度以及如何进行截断。

      BigDecimal d1 = new BigDecimal("123.456");
      BigDecimal d2 = new BigDecimal("23.456789");
      BigDecimal d3 = d1.divide(d2, 10, RoundingMode.HALF_UP); // 保留10位小数并四舍五入
      BigDecimal d4 = d1.divide(d2); // 报错:ArithmeticException,因为除不尽
    • 可以对BigDecimal做除法的同时求余数。

      复制代码
      1 public class Main {
      2     public static void main(String[] args) {
      3         BigDecimal n = new BigDecimal("12.345");
      4         BigDecimal m = new BigDecimal("0.12");
      5         BigDecimal[] dr = n.divideAndRemainder(m);
      6         System.out.println(dr[0]); // 102
      7         System.out.println(dr[1]); // 0.105
      8     }
      9 }
      复制代码
    • 比较两个BigDecimal的值是否相等时,要特别注意,使用equals()方法不但要求两个BigDecimal的值相等,还要求它们的scale()相等。

      BigDecimal d1 = new BigDecimal("123.456");
      BigDecimal d2 = new BigDecimal("123.45600");
      System.out.println(d1.equals(d2)); // false,因为scale不同
      System.out.println(d1.equals(d2.stripTrailingZeros())); // true,因为d2去除尾部0后scale变为2
      System.out.println(d1.compareTo(d2)); // 0
    • 必须使用compareTo()方法来比较,它根据两个值的大小分别返回负数、正数和0,分别表示小于、大于和等于。(总是使用compareTo()比较两个BigDecimal的值,不要使用equals()!)

    • 查看BigDecimal的源码,可以发现,实际上一个BigDecimal是通过一个BigInteger和一个scale来表示的,即BigInteger表示一个完整的整数,而scale表示小数位数。

      1 public class BigDecimal extends Number implements Comparable<BigDecimal> {
      2     private final BigInteger intVal;
      3     private final int scale;
      4 }
    • BigDecimal也是从Number继承的,也是不可变对象。

  • 常用工具类

    • Math

      • Math类是用来进行数学计算的,提供了大量的静态方法来便于实现数学计算。
      • Math还提供了几个数学常量    Math.PI   Math.E
      • 生成一个随机数x,x的范围是0 <= x < 1     Math.random(); // 0.53907... 每次都不一样
      • 如果要生成一个区间在[MIN, MAX)的随机数,可以借助Math.random()实现。
        复制代码
        // 区间在[MIN, MAX)的随机数
        public class Main {
            public static void main(String[] args) {
                double x = Math.random(); // x的范围是[0,1)
                double min = 10;
                double max = 50;
                double y = x * (max - min) + min; // y的范围是[10,50)
                long n = (long) y; // n的范围是[10,50)的整数
                System.out.println(y);
                System.out.println(n);
            }
        }
        复制代码
      • Java标准库还提供了一个StrictMath,它提供了和Math几乎一模一样的方法。这两个类的区别在于,由于浮点数计算存在误差,不同的平台(例如x86和ARM)计算的结果可能不一致(指误差不同),因此,StrictMath保证所有平台计算结果都是完全相同的,而Math会尽量针对平台优化计算速度,绝大多数情况下,使用Math就足够。Random
    • Random
      • Random用来创建伪随机数。所谓伪随机数,是指只要给定一个初始的种子,产生的随机数序列是完全一样的。要生成一个随机数,可以使用nextInt()nextLong()nextFloat()nextDouble()。

        复制代码
        Random r = new Random();
        r.nextInt(); // 2071575453,每次都不一样
        r.nextInt(10); // 5,生成一个[0,10)之间的int
        r.nextLong(); // 8811649292570369305,每次都不一样
        r.nextFloat(); // 0.54335...生成一个[0,1)之间的float
        r.nextDouble(); // 0.3716...生成一个[0,1)之间的double
        复制代码
      • 创建Random实例时,如果不给定种子,就使用系统当前时间戳作为种子,因此每次运行时,种子不同,得到的伪随机数序列就不同。如果我们在创建Random实例时指定一个种子,就会得到完全确定的随机数序列。

        复制代码
        import java.util.Random;
        
        public class Main {
            public static void main(String[] args) {
                Random r = new Random(12345);
                for (int i = 0; i < 10; i++) {
                    System.out.println(r.nextInt(100));
                }
                // 51, 80, 41, 28, 55...
            }
        }
        复制代码
      • 前面我们使用的Math.random()实际上内部调用了Random类,所以它也是伪随机数,只是我们无法指定种子。

    • SecureRandom

      • 有伪随机数,就有真随机数。实际上真正的真随机数只能通过量子力学原理来获取,而我们想要的是一个不可预测的安全的随机数,SecureRandom就是用来创建安全的随机数的。

        SecureRandom sr = new SecureRandom();
        System.out.println(sr.nextInt(100));
      •  SecureRandom无法指定种子,它使用RNG(random number generator)算法。JDK的SecureRandom实际上有多种不同的底层实现,有的使用安全随机种子加上伪随机数算法来产生安全的随机数,有的使用真正的随机数生成器。实际使用的时候,可以优先获取高强度的安全随机数生成器,如果没有提供,再使用普通等级的安全随机数生成器。

        复制代码
         1 import java.util.Arrays;
         2 import java.security.SecureRandom;
         3 import java.security.NoSuchAlgorithmException;
         4 
         5 public class Main {
         6     public static void main(String[] args) {
         7         SecureRandom sr = null;
         8         try {
         9             sr = SecureRandom.getInstanceStrong(); // 获取高强度安全随机数生成器
        10         } catch (NoSuchAlgorithmException e) {
        11             sr = new SecureRandom(); // 获取普通的安全随机数生成器
        12         }
        13         byte[] buffer = new byte[16];
        14         sr.nextBytes(buffer); // 用安全随机数填充buffer
        15         System.out.println(Arrays.toString(buffer));
        16     }
        17 }
        复制代码
 
原文:https://www.cnblogs.com/fjnuczq/p/14186661.html


相关教程