Java常见问题扫盲

Lou.Chen
大约 44 分钟

1.return、continue、break

return:

  • 跳出当前方法,无论循环多少次,包括多层for循环和while循环,return后面代码无需执行。

continue:

  • 结束本次循环,进入下次循环 包括while本次循环
        for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 5; j++) {
                if (j == 3) {
//                    跳出当前j索引的循环,进入下一次j的循环
                    continue;
                }
            }
        }

break:

  • 跳出当前循环体,结束整个循环体过程,包括while循环体。继续执行循环体后面的代码
 for (int i = 0; i < 10; i++) {
            for (int j = 0; j < 5; j++) {
                System.out.println("j:"+j);
                if (j == 3) {
//                   跳出当前整个j循环体,进入i循环体
                    break;
                }
            }
            System.out.println("i:"+i);
  }

⚠️注意,在for循环和增强for循环中均能使用contine、break、return操作。

在JDK1.8的中,使用流中的forEach循环时,只能使用return关键字,且和continue的含义一致,代表结束当次循环,进入下次循环,并且使用continue和break会报错

        List<String> list=new ArrayList<>();
        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("5");
        list.forEach(item->{
            if (item.equals("3")) {
                System.out.println("执行break....");
                return;
            }
            System.out.println(item);
        });
1
2
执行break....
4
5

2.final的使用?

1、当final修饰变量时:

被修饰的final变量必须在定义时它的值被确定

 无论属性是基本类型还是引用类型,“值”都是不能变的。 这个值,对于基本类型来说,变量里面放的就是实实在在的值,而对于引用类型来说,变量里面放的就是引用地址,所以当用final修饰引用类型变量时,其实指的是它里面的地址不能变,并不是说 这个地址所指向的对象或数组的内容不能变,这个一定要注意。

例如:类中有一个属性是final Person p=new Person("name"); 那么你不能对p进行重新赋值,是可以改变p里面属性的值,p.setName('newName');

2、当final修饰方法时:可以被继承,但继承后不能被重写。

3、当final修饰类时:类不可以被继承。

3.常见ASCII范围比较

字符ASCII码(十进制)
0-948-57
A-Z65-90
a-z97-122

4.断言

1、 说明

java断言assert是jdk1.4引入的。

jvm断言默认是关闭的。

断言可以局部开启的,如:父类禁止断言,而子类开启断言,所以一般说“断言不具有继承性”。

断言只适用复杂的调式过程。

断言一般用于程序执行结构的判断,千万不要让断言处理业务流程。

2、判断程序是否开启的断言

public static void main(String args[]) {
        boolean isOpen = false;

        // 如果开启了断言,会将isOpen的值改为true
        assert isOpen = true;

        // 打印是否开启了断言,如果为false,则没有启用断言
        System.out.println(isOpen);
 }

3、开启/关闭断言

选择菜单:run --> run Configurations

打开断言:-ea

关闭断言:-da,或者删除-ea

4、断言使用

①直接抛出断言异常

使用形式:assert 变量:异常消息

若变量为true,则不会抛出异常,若为false会抛出异常并且携带自定义的异常信息

public static void main(String[] args) {
        boolean isOk = 1 > 2;
        
        //assert isOk;	
        assert isOk:"判断错误";
       
        System.out.println("程序正常运行!");
}
Exception in thread "main" java.lang.AssertionError: 判断错误
	at org.lc.jdk1_8.AssertTest.main(AssertTest.java:14)

②捕获断言异常

public static void main(String[] args) {
        boolean isOk = 1 > 2;
        try {
            assert isOk:"判断错误";
            System.out.println("程序正常运行!");
        } catch (AssertionError e) {
            System.out.println(e.getMessage());
        }
    }
判断错误

assert后面跟个冒号表达式。如果冒号前为true,则冒号后面的被忽略,否则抛出AssertionError,错误内容为冒号后面的内容。

按F3查看源代码,如下,可以看到AssertionError是继承自Error,而不是Exception,所以catch部分用Exception是不能捕捉到AssertionError信息的。

5、断言其他参数

-ea java -ea 打开所有用户类的assertion
-da java -da 关闭所有用户类的assertion
-ea:<classname> java -ea:MyClass1 打开MyClass1的assertion
-da:<classname> java -da: MyClass1 关闭MyClass1的assertion
-ea:<packagename> java -ea:pkg1 打开pkg1包的assertion
-da:<packagename> java -da:pkg1 关闭pkg1包的assertion
-ea:... java -ea:... 打开缺省包(无名包)的assertion
-da:... java -da:... 关闭缺省包(无名包)的assertion
-ea:<packagename>... java -ea:pkg1... 打开pkg1包和其子包的assertion
-da:<packagename>... java -da:pkg1... 关闭pkg1包和其子包的assertion
-esa java -esa 打开系统类的assertion
-dsa java -dsa 关闭系统类的assertion
综合使用 java -dsa:MyClass1:pkg1 关闭MyClass1和pkg1包的assertion

5.instanceof关键字

instanceof 严格来说是Java中的一个双目运算符,用来测试一个对象是否为一个类的实例

boolean result = obj instanceof Class

其中 obj 为一个对象Class 表示一个类或者一个接口,当 obj 为 Class 的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result 都返回 true,否则返回false。

注意:编译器会检查 obj 是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行时定。

1、obj 必须为引用类型,不能是基本类型

int i = 0;
System.out.println(i instanceof Integer);//编译不通过
System.out.println(i instanceof Object);//编译不通过

instanceof 运算符只能用作对象的判断。

2、obj 为 null

System.out.println(null instanceof Object);//false

3、obj 为 class 类的实例对象

Integer integer = new Integer(1);
System.out.println(integer instanceof  Integer);//true

4、obj 为 class 接口的实现类

我们可以用 instanceof 运算符判断 某个对象是否是 List 接口的实现类,如果是返回 true,否则返回 false

ArrayList arrayList =new ArrayList();
System.out.println(arrayList instanceof List);//true

或者反过来也是返回 true

List list =new ArrayList();
System.out.println(list instanceof ArrayList);//true

5、obj 为 class 类的直接或间接子类

6.基本数据类型范围

基本类型字节数位数最大值最小值
byte1byte8bit2^7-1-2^7
short2byte16bit2^15-1-2^15
int4byte32bit2^31-1-2^31
long8byte64bit2^63-1-2^63
float4byte32bit3.4028235E381.4E - 45
double8byte64bit1.7976931348623157E3084.9E - 324
char2byte16bit2^16 - 10
boolean不确定不确定
序号数据类型位数默认值取值范围举例说明
1byte(位)80-2^7 2^7-1byte b = 10;
2short(短整数)160-2^15 2^15-1short s = 10;
3int(整数)320-2^31 2^31-1int i = 10;
4long(长整数)640-2^63 2^63-1long l = 10l;
5float(单精度)320.0-2^31 2^31-1float f = 10.0f;
6double(双精度)640.0-2^63 2^63-1double d = 10.0d;
7char(字符)160 2^16-1char c = 'c';
8boolean(布尔值)不确定falsetrue、falseboolean b = true;
1、关于boolean的占用的字节数

https://www.jianshu.com/p/2f663dc820d0open in new window

①1个bit

理由是boolean类型的值只有true和false两种逻辑值,在编译后会使用1和0来表示,这两个数在内存中只需要1位(bit)即可存储,位是计算机最小的存储单位。

②1个字节

理由是虽然编译后1和0只需占用1位空间,但计算机处理数据的最小单位是1个字节,1个字节等于8位,实际存储的空间是:用1个字节的最低位存储,其他7位用0填补,如果值是true的话则存储的二进制为:0000 0001,如果是false的话则存储的二进制为:0000 0000。

③4个字节

​ 理由来源是《Java虚拟机规范》一书中的描述:“虽然定义了boolean这种数据类型,但是只对它提供了非常有限的支持。在Java虚拟机中没有任何供boolean值专用的字节码指令,Java语言表达式所操作的boolean值,在编译之后都使用Java虚拟机中的int数据类型来代替,而boolean数组将会被编码成Java虚拟机的byte数组,每个元素boolean元素占8位”。这样我们可以得出boolean类型占了单独使用是4个字节,在数组中又是1个字节。

​ 显然第三条是更准确的说法,那虚拟机为什么要用int来代替boolean呢?为什么不用byte或short,这样不是更节省内存空间吗。大多数人都会很自然的这样去想,我同样也有这个疑问,经过查阅资料发现,使用int的原因是,对于当下32位的处理器(CPU)来说,一次处理数据是32位(这里不是指的是32/64位系统,而是指CPU硬件层面),具有高效存取的特点。

总结:

布尔类型:布尔数据类型只有两个可能的值:真和假。使用此数据类型为跟踪真/假条件的简单标记。这种数据类型就表示这一点信息,但是它的“大小”并不是精确定义的。

可以看出,boolean类型没有给出精确的定义,《Java虚拟机规范》给出了4个字节,和boolean数组1个字节的定义,具体还要看虚拟机实现是否按照规范来,所以1个字节、4个字节都是有可能的。这其实是运算效率和存储空间之间的博弈,两者都非常的重要。

7.try-catch-finally

从字节码的角度我们可以看到finally 中的代码被复制了 3 份,分别放入 try 流程catch 流程以及 catch 剩余的异常类型流程

1、基本语法
        try {
            //处理的代码
        }catch (NullPointerException e){
            //捕捉的异常  注意:异常从子类到父类,依次从上到下捕获
        }catch (ArrayIndexOutOfBoundsException e){
            
        }
        catch (Exception e){

        }finally {
            //无论是否异常都要执行的代码
        }
    }
2、执行顺序
①try中带有return

修改的基本类型(包括String):

static int getCount(){
        int count=0;
        try {
            count++;
            //try:1
            System.out.println("try:"+count);
            //返回结果为1
            return count;
        }catch (Exception e){
            count++;
            System.out.println("catch:"+count);
        }finally {
            count++;
            //finally:2 
            System.out.println("finally:"+count);
        }
        return count;
    }
try:1
finally:2
getCount:1

因为try中有return,所以在return之前会查看是否有finally块,有则先保存return之前的值,执行完finally后,返回try中reutrn之前保存的值,注意:这里不包含finally中执行的值

修改的引用类型:

    static List<Integer> getCount(){
        ArrayList<Integer> count=new ArrayList<>();
        try {
            count.add(1);
            //try:[1]
            System.out.println("try:"+count);
            //结果为:[1,3]
            return count;
        }catch (Exception e){
            count.add(2);
            System.out.println("catch:"+count);
        }finally {
            count.add(3);
            //finally:[1, 3]
            System.out.println("finally:"+count);
        }
        return count;
    }
try:[1]
finally:[1, 3]
getcount:[1, 3]

因为修改的为引用类型,所以finally修改的也有效,即try中return之后的值包括finally中修改的值

所以当finally通过地址改变了变量,还是会影响方法返回值的

②catch带有return
static int getCount(){
        int count=0;
        try {
            count++;
            //try:1
            System.out.println("try:"+count);
            //异常
            int a=count/0;
        }catch (Exception e){
            count++;
            //catch:2
            System.out.println("catch:"+count);
            //结果为2
            return count;
        }finally {
            count++;
            //finally:3
            System.out.println("finally:"+count);
        }
        count++;
        return count;
    }
try:1
catch:2
finally:3
getcount:2

catch中return与try中一样,会先执行return前的代码,然后暂时保存需要return的信息,再执行finally中的代码,最后再通过return返回之前保存的信息。

③finally带有return(不推荐)
static int getCount(){
        int count=0;
        try {
            count++;
            //try:1
            System.out.println("try:"+count);
            //不会执行
            return count;
        }catch (Exception e){
            count++;
            System.out.println("catch:"+count);
            //不会执行
            return count;
        }finally {
            count++;
            //finally:2
            System.out.println("finally:"+count);
            //结果为2
            return count;
        }
    }
try:1
finally:2
getcount:2

当finally中有return的时候,try中的return会失效,在执行完finally的return之后,就不会再执行try中的return。

总结:
  • finally中的代码总会被执行。

  • 当try、catch中有return时,也会执行finally。return的时候,要注意返回值的类型,是否受到finally中代码的影响。

  • finally中有return时,会直接在finally中退出,导致try、catch中的return失效。

  • 尽量不要在try和catch同时写return,或者在finally中写return.因为这样函数后面的代码永远无法执行。

3、try-with-resources(jdk7)

​ java库中有很多资源需要手动关闭,比如InputStreamOutputStreamjava.sql.Connection等等。在此之前,通常是使用try-finally的方式关闭资源;Java7之后,推出了try-with-resources声明来替代之前的方式。 try-with-resources 声明要求其中定义的变量实现 AutoCloseable 接口,这样系统可以自动调用它们的close方法,从而替代了finally中关闭资源的功能。

赋值文本代码:

public static void copy(String src, String dst) {
    InputStream in = null;
    OutputStream out = null;
    try {
        in = new FileInputStream(src);
        out = new FileOutputStream(dst);
        byte[] buff = new byte[1024];
        int n;
        while ((n = in.read(buff)) >= 0) {
            out.write(buff, 0, n);
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (out != null) {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

上面的代码非常的臃肿。下面来看使用了try-with-resources后的效果:

需要关闭的流声明在**try()**中。

public static void copy(String src, String dst) {
    try (InputStream in = new FileInputStream(src);
         OutputStream out = new FileOutputStream(dst)) {
        byte[] buff = new byte[1024];
        int n;
        while ((n = in.read(buff)) >= 0) {
            out.write(buff, 0, n);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

try-with-resources将会自动关闭try()中的资源,并且将先关闭后声明的资源。

AutoCloseable接口:
 *
 * @author Josh Bloch
 * @since 1.7
 */
public interface AutoCloseable {
      void close() throws Exception;
 }

其中仅有一个close方法,实现AutoCloseable接口的类将在close方法中实现其关闭资源的功能。

除却处理异常相关的代码,其实就是调用了资源的close方法。

8.内部类详解

https://www.cnblogs.com/dolphin0520/p/3811445.htmlopen in new window

在Java中,可以将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。广泛意义上的内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。下面就先来了解一下这四种内部类的用法。

1、成员内部类

成员内部类是最普通的内部类,它的定义为位于另一个类的内部,形如下面的形式:

class Circle{
    private double radius;
    private int count=1;

    public Circle(double radius) {
        this.radius = radius;
    }

    //通过外部类获得内部类对象
    public Draw getDrawInstance() {
        return new Draw();
    }

    public void getDrawShape(){
        //外部内要使用内部类成员必须 创建内部类对象
        new Draw().drawShape();
    }

    //定义一个成员内部类
    //成员内部中不允许有静态成员
    class Draw{
        //若内部类中的成员和外部类中的成员有相同的名称。则默认优先使用内部类中的成员
        private double radius=10;
        final int count=0;
        public void drawShape() {
            //成员内部类可以直接访问外部类中的成员
            //直接访问外部私有成员
            System.out.println("drawShape...radis:"+radius);
            //访问外部同名的成员。若为静态成员,直接 Circle.成员 调用
            System.out.println(Circle.this.count);
        }
    }
}

成员内部类的使用

public class T1 {
    public static void main(String[] args) {
        //创建内部类对象
        //1、第一种方式
        //先创建外部类对象
        Circle circle = new Circle(1);
        //使用外部类对象调用new无参的内部类构造函数
        Circle.Draw draw=circle.new Draw();
        draw.drawShape();

//        2、第二种方式
        //直接在外部类中创建内部类对象调用
        Circle.Draw draw1=circle.getDrawInstance();
        draw1.drawShape();
    }
}
  • 内部类可以直接调用外部类的所有成员(私有成员,静态成员,final成员)

  • 成员内部类中不允许有静态成员

  • 若内部类中定义的成员和外部类中定义的成员名称一致,则优先使用内部类中的成员

  • 若内部类向使用外部类的同名成员则可以使用 外部类名.this.变量名 外部类名.this.方法名

  • 外部内使用内部类的成员时必须创建内部类对象才能使用

  • 成员内部类看起来像是外部类的一个成员,所以可以像类的成员一样拥有多种权限修饰

  • 在内部类中获取外部类对象和内部类对象

    • 外部类 类对象=外部类.this;
    • 内部类 类对象=内部类.this;
  • 成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象

    • Circle out=new Circle();
      Circle.Draw inner=out.new Draw();
      
    • //通过在外部类中创建获取内部类对象的方法
          public Draw getDrawInstance() {
              return new Draw();
          }
      //创建第二种方式
      Circle.Draw inner=out.getDrawInstance();
      
2、局部内部类

局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。

class People{
    public People() {
    }
}
class Man{
    public Man() {
    }
    public People getWoman(){
        //局部内部类
        class Woman extends People{
            int age=0;
        }
        return new Woman();
    }
}

注意,局部内部类就像是方法里面的一个局部变量一样,是不能有public、protected、private以及static修饰符的。

3、匿名内部类
public class T7 {
    public static void main(String[] args) {
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                System.out.println("ok");
            }
        };
    }
}

转换之后的代码:

// 额外生成的类 
final class Candy11$1 implements Runnable {    
    Candy11$1() {    }    
    public void run() {        
        System.out.println("ok");    
    } 
}
public class T7 {
    public static void main(String[] args) {
        Runnable runnable=new Candy11$1();
    }
}
4、静态内部类

静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static。静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法,这点很好理解,因为在没有外部类的对象的情况下,可以创建静态内部类的对象,如果允许访问外部类的非static成员就会产生矛盾,因为外部类的非static成员必须依附于具体的对象。

public class T4 {
    public static void main(String[] args) {
        //创建静态内部类成员
        Outter.Inner inner=new Outter.Inner();
        inner.test();
    }
}
class Outter{
    int a=10;
    static int b=5;
    public Outter() {
    }
    static class Inner{
        public void test() {
            //不能直接访问外部的非静态成员
//            System.out.println(a);
            System.out.println(b);
        }
    }
}
总结:
  • 对于成员内部类,必须先产生外部类的实例化对象,才能产生内部类的实例化对象。而静态内部类不用产生外部类的实例化对象即可产生内部类的实例化对象。
    • 创建静态内部类对象的一般形式为: 外部类类名.内部类类名 xxx = new 外部类类名.内部类类名()
    • 创建成员内部类对象的一般形式为: 外部类类名.内部类类名 xxx = 外部类对象名.new 内部类类名()

9.枚举的使用

枚举类型可以取代以往常量的定义方式,即将常量封装在类或接口中。此外,枚举类型还提供了安全检查功能。枚举类型本质上还是以类的形式存在。

1、枚举常量基本定义

**注意:**枚举类已经继承了Enum,所以枚举类不能继承其他类,只能实现接口

//
public enum EnumTest {
    //每个枚举 即一个类实例
    PICK,
    RED,
    GREEN,
    BLACK,
    BLUE;
}

反编译后的形式: 继承Enum类

final enum EnumTest extends Enum{
    public final static enum EnumTest PICK;
    public final static enum EnumTest RED;
    public final static enum EnumTest GREEN;
    public final static enum EnumTest BLACK;
    public final static enum EnumTest BLUE;
    //...
}
①命名规范:

final常量:使用大写字母命名,并且中间使用下划线进行连接。

enum枚举:使用大写字母命名,并且中间使用下划线进行连接。

2、操作枚举成员方法

​ 用户可以将一个枚举类型看作是一个类,它继承于java.lang.Enum类,当定义一个枚举类型时,每一个枚举类型成员都可以看作是枚举类型的一个实例,这些枚举类型成员默认都被final、public、static所修饰,所以当使用枚举类型成员时直接使用枚举类型名称调用枚举类型成员即可。

由于枚举类型对象继承与java.lang.Enum类,所以该类中一些操作枚举类型的方法都可以应用到枚举型中。 继承自Enum的方法:

方法名称具体含义使用方法返回类型
values()返回所有枚举类的实例类方法:EnumTest.values()该枚举类型的数组
valueOf(String name)根据枚举类型名称返回类中定义的枚举实例类方法:EnumTest.valueOf("PICK");该枚举实例对象
ordinal()返回该枚举实例在类中的顺序,索引从0开始实例方法:pick.ordinal()int
compareTo(EnumType s)根据ordinal比较实例方法:pick.compareTo(EnumTest.RED)int
3、枚举类型的构造方法

在枚举类型中,可以添加构造方法,但是注意枚举类不能通过new创建对象,只能通过枚举类调用枚举实例对象

public enum EnumTest{
    
    PICK(1,"粉色"),
    RED(2,"红色"),
    GREEN(3,"绿色"),
    BLACK(4,"黑色"),
    BLUE(5,"蓝色");

    //给枚举实例添加额外的属性
    private int value;
    private String desc;

    public int getValue() {
        return value;
    }

    public String getDesc() {
        return desc;
    }

    //添加构造方法
    private EnumTest(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }

    @Override
    public String toString() {
        return "EnumTest{" +
                "value=" + value +
                ", desc='" + desc + '\'' +
                '}';
    }

}
class T2 {
    public static void main(String[] args) {
        EnumTest pick = EnumTest.valueOf("PICK");
//        通过get方法,获取某个枚举实例的属性
//        粉色
        System.out.println(pick.getDesc());
//        -1
        System.out.println(pick.compareTo(EnumTest.RED));
//        0
        System.out.println(pick.ordinal());
//        EnumTest{value=1, desc='粉色'}
        System.out.println(pick);
        EnumTest[] values = EnumTest.values();
//        [EnumTest{value=1, desc='粉色'}, EnumTest{value=2, desc='红色'}, EnumTest{value=3, desc='绿色'}, EnumTest{value=4, desc='黑色'}, EnumTest{value=5, desc='蓝色'}]
        System.out.println(Arrays.toString(values));

    }
}

4、枚举实例中独立的方法
public enum EnumTest{

    PICK(1,"粉色"){
        //枚举中定义独立的方法。若类中的实例存在则会覆盖
        @Override
        public int getValue() {
            return 100;
        }

        @Override
        public String getDesc() {
            return "i am pink";
        }

        @Override
        public String toString() {
            return "我是粉色";
        }
    },
    RED(2,"红色"),
    GREEN(3,"绿色"),
    BLACK(4,"黑色"),
    BLUE(5,"蓝色");

    //给枚举实例添加额外的属性
    private int value;
    private String desc;

    public int getValue() {
        return value;
    }

    public String getDesc() {
        return desc;
    }

    private EnumTest(int value, String desc) {
        this.value = value;
        this.desc = desc;
    }
    @Override
    public String toString() {
        return "EnumTest{" +
                "value=" + value +
                ", desc='" + desc + '\'' +
                '}';
    }
}
class T2 {
    public static void main(String[] args) {
        EnumTest pick = EnumTest.valueOf("PICK");
//        i am pink
        System.out.println(pick.getDesc());
//        我是粉色
        System.out.println(pick.toString());
//        100
        System.out.println(pick.getValue());
    }
}
5、枚举中使用静态变量
public enum SelectTypeEnum {
    public static Map<Object,String> labelMap=new HashMap<>();

    static{
        labelMap.put("1", "质差SFU");
        labelMap.put("2", "单频网关(HGU)");
        labelMap.put("3", "其他质差终端(非iHGU)");
        labelMap.put("4", "其他质差终端(iHGU)");
        labelMap.put("7", "单频网关(iHGU)");
        labelMap.put("5", "CPU高占比");
        labelMap.put("6", "内存高占比");
        labelMap.put("8", "WiFi弱覆盖");
        labelMap.put("9", "常驻频段2.4G");
        labelMap.put("10", "频繁上下线");
        labelMap.put("11", "光功率质差");
    }
}

10.创建对象的几种方式

使用new关键字创建对象应该是最常见的一种方式,但我们应该知道,使用new创建对象会增加耦合度。无论使用什么框架,都要减少new的使用以降低耦合度。

1、通过new关键字
public class Hello {
    public void sayHello() {
        System.out.println("hello world");
    }
}
class T1{
    public static void main(String[] args) {
        Hello hello=new Hello();
        hello.sayHello();
    }
}
2、通过反射
Class中的newInstance方法
public class Hello {
    public void sayHello() {
        System.out.println("hello world");
    }
}
class T1{
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class aClass = Class.forName("org.lc.load_class.Hello");
        Hello hello = (Hello)aClass.newInstance();
        //或者
        //Hello hello = Hello.class.newInstance();
        hello.sayHello();
    }
}
Constructor中的newInstance方法
public class Hello {
    public void sayHello() {
        System.out.println("hello world");
    }
}
class T1{
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        Class aClass = Class.forName("org.lc.load_class.Hello");
        Constructor constructor = aClass.getConstructor();
        Hello hello = (Hello) constructor.newInstance();
        hello.sayHello();
    }
}
3、采用clone

clone时,需要已经有一个分配了内存的源对象,创建新对象时,首先应该分配一个和源对象一样大的内存空间。要调用clone方法需要实现Cloneable接口,由于clone方法是protected的,所以要重写Object中的clone方法.

注意:通过clone创建的对象地址不相同

//实现Cloneable接口 代表此类可以clone
public class Hello implements Cloneable{
    public void sayHello() {
        System.out.println("hello world");
    }

    //重新Object中的clone即可 
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
class T1{
    public static void main(String[] args) throws CloneNotSupportedException {
        Hello h1=new Hello();
        Hello h2 = (Hello) h1.clone();
        h2.sayHello();
        //fasle  两者并不是同一对象
        System.out.println(h1==h2);

    }
}
4、采用序列化机制

注意: 要序列化的对象必须实现Serializable接口。序列化前和序列化后的对象地址并不相同

//要序列化的对象需要实现Serializable接口
public class Hello implements Serializable {
    private String name;
    private int age;
    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public Hello setAge(int age) {
        this.age = age;
        return this;
    }

    public Hello() {
    }
    public Hello(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @Override
    public String toString() {
        return "Hello{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
class T1{
    public static void main(String[] args) throws IOException {
        Hello h1=new Hello("张三",20);
//        写
        writeHello("file/1.txt",h1);
//        读
        Hello h2 = readHello("file/1.txt");
//        Hello{name='张三', age=20}
        System.out.println(h1);
//        Hello{name='张三', age=20}
        System.out.println(h2);
//        false
        System.out.println(h1==h2);

    }

    private static Hello readHello(String filename) throws IOException {
        try(ObjectInputStream ois=new ObjectInputStream(new FileInputStream(new File("file/1.txt")))){
            Hello hello = (Hello)ois.readObject();
            return hello;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    private static void writeHello(String pathname,Hello h1) {
        //将对象序列化到磁盘
        try(ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream(new File(pathname)))){
            oos.writeObject(h1);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

11.BigDecimal的使用

https://www.cnblogs.com/zhangyinhua/p/11545305.htmlopen in new window

1、概述

Java在java.math包中提供的API类BigDecimal,用来对超过16位有效位的数进行精确的运算。双精度浮点型变量double可以处理16位有效数,但在实际应用中,可能需要对更大或者更小的数进行运算和处理。一般情况下,对于那些不需要准确计算精度的数字,我们可以直接使用Float和Double处理,但是Double.valueOf(String) 和Float.valueOf(String)会丢失精度。所以开发中,如果我们需要精确计算的结果,则必须使用BigDecimal类来操作。

BigDecimal所创建的是对象,故我们不能使用传统的+、-、*、/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法。方法中的参数也必须是BigDecimal的对象。构造器是类的特殊方法,专门用来创建对象,特别是带有参数的对象。

问题复现

https://www.jianshu.com/p/353834bbe7daopen in new window

问题很简单,是由于我们输入的十进制的 double 类型的数据在进行计算的时候,计算机会先将其转换为二进制数据,然后再进行相关的运算。

然而在十进制转二进制的过程中,有些十进制数是无法使用一个有限的二进制数来表达的,换言之就是转换的时候出现了精度的丢失问题,所以导致最后在运算的过程中,自然就出现了我们看到的一幕

double d1 = 0.1;
double d2 = 0.2;
//0.30000000000000004
System.out.println(d1 + d2);
//0.10000000000000009
System.out.println(1.1-1);
2、常用构造函数
  • BigDecimal(int)

    创建一个具有参数所指定整数值的对象

  • BigDecimal(double)

    创建一个具有参数所指定双精度值的对象

  • BigDecimal(long)

    创建一个具有参数所指定长整数值的对象

  • BigDecimal(String) (常用方式)

    创建一个具有参数所指定以字符串表示的数值的对象

BigDecimal b1=new BigDecimal(0.1);
//0.1000000000000000055511151231257827021181583404541015625
System.out.println(b1);

BigDecimal b2=new BigDecimal("0.1");
//0.1
System.out.println(b2);
3、常用方法

进行构造BigDecimal对象时,建议把所有的值转为字符串再计算

  • add(BigDecimal)

    BigDecimal对象中的值相加,返回BigDecimal对象

  • subtract(BigDecimal)

    BigDecimal对象中的值相减,返回BigDecimal对象

  • multiply(BigDecimal)

    BigDecimal对象中的值相乘,返回BigDecimal对象

  • divide(BigDecimal)

    BigDecimal对象中的值相除,返回BigDecimal对象

  • toString()

    将BigDecimal对象中的值转换成字符串

  • doubleValue()

    将BigDecimal对象中的值转换成double双精度数

  • floatValue()

    将BigDecimal对象中的值转换成float单精度数

  • longValue()

    将BigDecimal对象中的值转换成long长整数

  • intValue()

    将BigDecimal对象中的值转换成int整数

  • abs()

    求绝对值

4、大小比较

java中对BigDecimal比较大小一般用的是BigDecimal的compareTo(BigDecimal val)方法

BigDecimal b1=new BigDecimal("1.0");
BigDecimal b2=new BigDecimal("1.2")
int i = b1.compareTo(b2);
//-1
System.out.println(i);
  • b1>b2 : 返回 1

  • b1<b2 : 返回 -1

  • b1=b2 : 返回 0

5、格式化

由于NumberFormat类的format()方法可以使用BigDecimal对象作为其参数,可以利用BigDecimal对超出16位有效数字的货币值,百分值,以及一般数值进行格式化控制。

以利用BigDecimal对货币和百分比格式化为例。首先,创建BigDecimal对象,进行BigDecimal的算术运算后,分别建立对货币和百分比格式化的引用,最后利用BigDecimal对象作为format()方法的参数,输出其格式化的货币值和百分比。

①建立货币格式化引用
//美元格式
NumberFormat c1 = NumberFormat.getCurrencyInstance(Locale.US);
//人民币格式
NumberFormat c2 = NumberFormat.getCurrencyInstance(Locale.CHINA);

String f1 = c1.format(new BigDecimal("1000.44"));
String f2 = c2.format(new BigDecimal("1000.44"));
//$1,000.44
System.out.println(f1);
//¥1,000.44
System.out.println(f2);
②建立百分比格式
NumberFormat p = NumberFormat.getPercentInstance();
//百分比小数点最多3位  默认超出的四舍五入
p.setMaximumFractionDigits(3);

String f3 = p.format(new BigDecimal("0.100014"));
//10.001%
System.out.println(f3);
6、除法异常

通过BigDecimal的divide方法进行除法时当不整除,出现无限循环小数时,就会抛异常:Exception in thread "main" java.lang.ArithmeticException: Non-terminating decimal expansion; no exact representable decimal result.

BigDecimal b1 = new BigDecimal("10");
BigDecimal b2 = new BigDecimal("3");
System.out.println(b1.divide(b2));

解决办法 divide(BigDecimal divisor, int scale, int roundingMode)

scale: 保留的小 数位

roundingMode: 舍入模式

BigDecimal b1 = new BigDecimal("10");
BigDecimal b2 = new BigDecimal("3");
//3.33  保留两位小数,四舍五入
System.out.println(b1.divide(b2,2,BigDecimal.ROUND_HALF_UP));
7、BigDecimal保留小数
①除法精度
  • scale: 保留的小 数位

  • roundingMode: 舍入模式

    • BigDecimal.ROUND_HALF_UP 四舍五入

divide(BigDecimal divisor, int scale, int roundingMode)

②其他(加减乘)精度
  • newScale: 保留的小数位
  • roundingMode:舍入模式

注意:这里设置完精度后,返回的是一个新对象,不能用之前的对象。

public BigDecimal setScale(int newScale, int roundingMode)

BigDecimal b1 = new BigDecimal("10");
BigDecimal b2 = new BigDecimal("0.145643");

BigDecimal multiply = b1.multiply(b2);
//注意这里计算完乘法后,手动进行精度的改变操作 
BigDecimal newMultiply = multiply.setScale(2, BigDecimal.ROUND_HALF_UP);
//1.46  四舍五入
System.out.println(newMultiply);
8、使用场景介绍
  • 在需要精确的小数计算时再使用BigDecimal,BigDecimal的性能比double和float差,在处理庞大,复杂的运算时尤为明显。故一般精度的计算没必要使用BigDecimal。

  • 尽量使用参数类型为String的构造函数。

  • BigDecimal都是不可变的(immutable)的, 在进行每一次四则运算时,都会产生一个新的对象 ,所以在做加减乘除运算时要记得要保存操作后的值。

9、其他保留小数的几种姿势
- 舍入方式
  • BigDecimal.ROUND_DOWN 与正负无关。即不管如何都不会进位 (只会向下舍弃,不进位)

    BigDecimal b1=new BigDecimal(-10.34);
    BigDecimal b2=new BigDecimal(10.34);
    //-10.3
    System.out.println(b1.setScale(1, BigDecimal.ROUND_DOWN));
    //10.3
    System.out.println(b2.setScale(1, BigDecimal.ROUND_DOWN));
    
  • BigDecimal.ROUND_UP 与正负无关。即不管如何都会进位(只会向上进位)

    • 注意:如果舍弃后面一位为0,则也要向上进位

      BigDecimal b1=new BigDecimal(-10.30);
      BigDecimal b2=new BigDecimal(10.30);
      //-10.4
      System.out.println(b1.setScale(1, BigDecimal.ROUND_UP));
      //10.4
      System.out.println(b2.setScale(1, BigDecimal.ROUND_UP));
      
    BigDecimal b1=new BigDecimal(-10.34);
    BigDecimal b2=new BigDecimal(10.34);
    //-10.4
    System.out.println(b1.setScale(1, BigDecimal.ROUND_UP));
    //10.4
    System.out.println(b2.setScale(1, BigDecimal.ROUND_UP));
    
  • BigDecimal.ROUND_CEILING 与正负有关

    • 正数时:相当于ROUND_UP 只向上进位

    • 负数时:相当于ROUND_Down 只向下舍弃

    • 巧记:CEILING天花板的意思,只会越取越大。

    BigDecimal b1=new BigDecimal(-10.34);
    BigDecimal b2=new BigDecimal(10.34);
    //-10.3
    System.out.println(b1.setScale(1, BigDecimal.ROUND_CEILING));
    //10.4
    System.out.println(b2.setScale(1, BigDecimal.ROUND_CEILING));
    
  • BigDecimal.ROUND_FLOOR 与正负有关

    • 正数时:相当于ROUND_DOWN 只会向下舍弃

    • 负数时:相当于ROUND_UP 只会向上进位

    • 巧记:FLOOR地板的意思,只会越取越小。

    BigDecimal b1=new BigDecimal(-10.34);
    BigDecimal b2=new BigDecimal(10.34);
    //-10.4
    System.out.println(b1.setScale(1, BigDecimal.ROUND_FLOOR));
    //10.3
    System.out.println(b2.setScale(1, BigDecimal.ROUND_FLOOR));
    
  • BigDecimal.ROUND_HALF_UP 与正负无关 四舍五入

    BigDecimal b1=new BigDecimal(-10.34);
    BigDecimal b2=new BigDecimal(10.34);
    //-10.3
    System.out.println(b1.setScale(1, BigDecimal.ROUND_HALF_UP));
    //10.3
    System.out.println(b2.setScale(1, BigDecimal.ROUND_HALF_UP));
    
  • BigDecimal.ROUND_HALF_DOWN 与正负无关 五舍六入

     BigDecimal b1=new BigDecimal(-10.35);
     BigDecimal b2=new BigDecimal(10.35);
    //-10.3
     System.out.println(b1.setScale(1, BigDecimal.ROUND_HALF_DOWN));
    //10.3
     System.out.println(b2.setScale(1, BigDecimal.ROUND_HALF_DOWN));
    
- String.Format方法

与正负无关,四舍五入的方式。保留的小数位不足,则补0

//n表示保留的小数位数
double d=22.32324;
String.format("%.nf",d);
//保留两位小数
String s1 = String.format("%.2f",-2.346343);
String s2 = String.format("%.2f",2.346343);
//-2.35
System.out.println(s1);
//2.35
System.out.println(s2);
- String.format和BigDecimal结合使用
BigDecimal multiply = new BigDecimal(0.222).multiply(new BigDecimal("100"));
//保留两位小数
String format = String.format("%.2f", multiply);
- DecimalFormat

**0和#**的区别:

  • 0补位时
    • 如果数字少了,就补0,小数和整数都会补。
    • 如果数字多了,就切掉,但只切小数的末尾,整数不能切。
    • 同时被切掉的小数位数会进行四舍五入。
  • 以**#**补位时
    • 如果数字少了,不处理。
    • 如果数字多了,就切掉,但只切小数的末尾,整数不能切。
    • 同时被切掉的小数位数会进行四舍五入。
double p=4232.23513;
//整数不能切,整数不足补0     04232
System.out.println(new DecimalFormat("00000").format(p));
//保留两位小数(四舍五入)  4232.24
System.out.println(new DecimalFormat("0.00").format(p));
//保留六位小数(四舍五入),小数不足补0, #号补位不足不处理  4232.235130
System.out.println(new DecimalFormat("#####.000000").format(p));
//保留两位小数(四舍五入),并做%处理, 423223.51%
System.out.println(new DecimalFormat("#.##%").format(p));
//每三位以逗号进行分隔。  4,232
System.out.println(new DecimalFormat(",###").format(p));
//嵌入文本     速度为每秒4232米
System.out.println(new DecimalFormat("速度为每秒#米。").format(p));
- BigDecimal中的setScale方法

见上面BigDecimal用法

10、实例(保留两位小数)

转换需要的示例:

11.22%

11.2%

11%

public class DefinedNumUtil {
    public static String  convertNumToPercent(String number) {
        BigDecimal multiply = new BigDecimal(number).multiply(new BigDecimal("100"));
        String format = String.format("%.2f", multiply);
        String[] split = format.split("\\.");
        if ("00".equals(split[1])) {
            return split[0] + "%";
        } else {
            return format + "%";
        }
    }
}

12.\n\r\t\f的区别

字符作用
\n换行\n 换行符,使光标定位到下一行。
\r回车\r 回车符,使光标回到当前行的行首。如果之前该行有内容,则会被覆盖;
\t制表 (相当于tab)\t 是制表符。相当于tab缩进。
\f换页\f 是换页符,在控制台上输出没有意义。

13.try-catch 语法糖 多个异常并行捕获

public static void main(String[] args) {
        try {
            int a = 1 / 0;// 除以0
        } catch (ArithmeticException | ArrayIndexOutOfBoundsException e) {
            // 多个异常见用 | 隔开
            // 多个异常必须是平级关系
            System.out.println("发生了ArithmeticException 或者 ArrayIndexOutOfBoundsException 异常");
        }
}

14.重新和重载

1、重写

重写是子类对父类的允许访问的方法的实现过程进行重新编写, 返回值和形参(参数列表)都不能改变。即外壳不变,核心重写!

  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected。

  • 子类不能重写父类修饰的private方法

  • 声明为 final 的方法不能被重写。

  • 声明为 static 的方法不能被重写,但是能够被再次声明。如果参数列表一致,且只能声明为静态方法

  • 返回类型与被重写方法的返回类型可以不相同但是必须是父类返回值的子类(java5 及更早版本返回类型要一样,java7 及更高版本可以不同)。

  • 参数列表必须完全与被重写方法的相同,方法类型和方法方法个数一致,顺序一致

  • 不能抛出新的检查异常或者比被重写方法申明更加宽泛的异常 。例如:父类的一个方法申明了一个检查异常 IOException,但是在重写这个方法的时候不能抛出 Exception 异常,因为 Exception 是 IOException 的父类,只能抛出 IOException 的子类异常

  • 构造方法不能被重写,但可以被重载。

  • 如果不能继承一个方法,则不能重写这个方法

  • 当需要在子类中调用父类的被重写方法时,要使用 super 关键字

public class T6{
    public static void main(String[] args) {
        Animals d = new Dog();
        Dog dog=new Dog();
        //当用父类的引用时,如果子类覆盖了父类的此方法,则使用子类的方法,否则使用父类的该方法
        //报错,父类无此方法
//        d.eat();

//        字段只能继承。使用父类的引用,则调用父类的字段
//        1
        System.out.println(d.a);
//        使用子类的引用,则调用子类的字段
//        2
        System.out.println(dog.a);
    }
}
class Animals {
    int a=1;
    public static void move(int a,byte b){
        System.out.println("动物可以移动");
    }
}
class Dog extends Animals{
    int a=2;
    public void move(byte b,int a){
        System.out.println("狗可以跑和走");
    }

    public void eat() {
        System.out.println("狗可以吃饭");
    }
}
2、重载

重载(overloading) 是在一个类里面,方法名字相同,而参数列表不同返回类型可以相同也可以不同。

每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。

最常用的地方就是构造器的重载。

  • 被重载的方法可以改变访问修饰符,与方法修饰符无关
  • 被重载的方法可以加final,staic
  • 被重载的方法可以改变返回类型;与返回类型无关
  • **被重载的方法必须改变参数列表(参数个数或类型不一样,顺序不一样);**只能以参数列表作为重载的标准
  • 被重载的方法可以声明新的或更广的检查异常;与抛出的异常无关
class Animals {
    public Integer move(byte b,int a){
        System.out.println("动物可以移动");
        return 0;
    }
    private void move(int a,byte c){
        System.out.println("动物可以移动");
    }
    private  final static void move(int a){
        System.out.println("动物可以移动");
    }
}

15.相对路径和绝对路径

https://blog.csdn.net/qq_41856633/article/details/89715920open in new window

https://www.cnblogs.com/fstimers/p/10882361.htmlopen in new window

maven普通工程

1、读取resources下的文件
①通过类加载器获取 /.的区别

/ : 类加载器中不允许写/ 路径,因为类加载器不到根路径下的位置

. : 代表绝对路径下的编译后的classes下的位置

//F:\桌面内容\面试\java基础\javase\target\classes\
Thread.currentThread().getContextClassLoader().getResource(".").getFile());

///F:\桌面内容\面试\java基础\javase\target\classes\file1\2.txt
Thread.currentThread().getContextClassLoader().getResource("./file1/2.txt").getFile();
Thread.currentThread().getContextClassLoader().getResource("file1/2.txt").getFile();
//  ./ 和 . 写法一致

写入resource一般不适用,因为classes下的文件时java编译动态生成的文件

通过当前线程的类加载器读取。类加载读取的是jvm已经编译好的文件,即在classes文件夹下

直接把读取到的文件转换为流

//直接把文件转换为流的形式
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("file1/2.txt");

获取编译后的classes下的绝对路径。 注意:不能有中文路径,否则会乱码

///F:/%e6%a1%8c%e9%9d%a2%e5%86%85%e5%ae%b9/%e9%9d%a2%e8%af%95/java%e5%9f%ba%e7%a1%80/javase/target/classes/file1/2.txt
String name=Thread.currentThread().getContextClassLoader().getResource("file1/2.txt").getFile();
②URLDecoder解决中文路径乱码
String name=Thread.currentThread().getContextClassLoader().getResource("file1/2.txt").getFile();
String urlDecoder=URLDecoder.decode(name, "UTF-8");
2、写入和读取文件到当前项目的根路径下
①通过File获取 / . 的区别

/ : 代表当前项目盘符下的根路径

. : 代表当前项目下的根路径 . (无需写任何路径) 和 ./ 表示的路径相同

File file = new File("/");
/// 代表的绝对路径为:F:\
System.out.println("/ 代表的绝对路径为:" + file.getAbsolutePath());

//* javase这里代表项目文件夹
//. 代表的绝对路径为F:\桌面内容\面试\java基础\javase\.
File file1 = new File(".");
System.out.println(". 代表的绝对路径为" + file1.getAbsolutePath());

所以写文件对应的路径位置:

//写入的位置:  F:\aa.txt
File file = new File("/aa.txt");

//写入的位置:  F:\桌面内容\面试\java基础\javase\bb.txt
File file = new File("./bb.txt"); 
File file = new File("bb.txt");
//两种写法一致

读取文件时的位置和写入文件的位置写法一致

3、使用类加载器加载文件
①加载当前项目根路径下的文件
  • 使用主程序类的类加载器T1.class.getClassLoader();
  • 使用运行线程下的上下文类加载器Thread.currentThread().getContextClassLoader();

上述方法都可以获得 应用程序类加载器去加载编译后的target/classes/下的文件

public class T1 {
    public static void main(String[] args) throws IOException {
        Properties properties=new Properties();
//        ClassLoader contextClassLoader = T1.class.getClassLoader();
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        properties.load(contextClassLoader.getResourceAsStream("person.properties"));
        System.out.println(contextClassLoader);
        System.out.println(properties.getProperty("username"));
        System.out.println(properties.getProperty("age"));
    }
}
②加载src/main/java/下的文件

只需更改加载路径即可 如下:

public class T1 {
    public static void main(String[] args) throws IOException {
        Properties properties=new Properties();
//        ClassLoader contextClassLoader = T1.class.getClassLoader();
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        properties.load(contextClassLoader.getResourceAsStream("src/main/java/person.properties"));
        System.out.println(contextClassLoader);
        System.out.println(properties.getProperty("username"));
        System.out.println(properties.getProperty("age"));
    }
}
③注意pom.xml过滤
    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                </includes>
            </resource>
            <resource>
                <directory>./</directory>
                <includes>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>
    </build>
4、什么叫类路径?

主要看运行期间 和 编译后文件的位置即可

  • 普通工程(javase,maven)
    • 在我们的普通java工程下,src下的文件都成为类路径
    • 打包之后所有的类路径下的文件放在 target/classes/ 下

16.异常及自定义异常

Error和Exception区分:

Error是编译时错误和系统错误,系统错误在除特殊情况下,都不需要你来关心,基本不会出现。而编译时错误,如果你使用了编译器,那么编译器会提示。

Exception则是可以被抛出的基本类型,我们需要主要关心的也是这个类。

Exception又分为RunTimeException和其他Exception。

RunTimeException和其他Exception区分:

其他Exception,受检查异常。可以理解为错误,必须要开发者解决以后才能编译通过,解决的方法有两种,1:throw到上层,2,try-catch处理。 RunTimeException:运行时异常,又称不受检查异常,不受检查,因为不受检查,所以在代码中可能会有RunTimeException时Java编译检查时不会告诉你有这个异常,但是在实际运行代码时则会暴露出来,比如经典的1/0,空指针等。如果不处理也会被Java自己处理。

public class T4 {
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            if (i == 5) {
                try {
                    throw new GenderException("不能等于5");
                } catch (GenderException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
class GenderException extends Exception{
    private String message;

    public GenderException(String message) {
        super(message);
    }
}
org.lc.load_class.GenderException: 不能等于5
	at org.lc.load_class.T4.main(T4.java:15)

17.方法修饰符

权限修饰符同一个类同一个包不同包的子类不同包的非子类
private
default(相当于成员默认不写方法修饰符)或者接口中的default方法
protected
public

18.静态代码块和实例代码块执行顺序

//创建对象时,首先在该对象的类中,从上到下依次加载静态成员 ,包括创建的对象(有构造器),游离块也按照顺序执行,但总是先加载静态的代码块。
public class T {
	public static void main(String[] args) {
		S2 s2=new S2();
	}
}
class S1{
	static int a=0;
	public S1(){
		System.out.println("我是构造函数1");
	}
	{
		System.out.println("我是游离块1");
	}
	static int b=0;
	
	static{
		System.out.println("我是静态块1");
	}
}
class S2{
	
	public S2(){
		System.out.println("我是构造函数2");
	}
	static{
		System.out.println("我是静态块2");
	}
	
	S1 s1=new S1();  //从上到先 依次先加载静态的成员  创建对象数该成员会调用S1的构造函数。要执行完所有的静态块,游离块和构造函数。
	
	{
		System.out.println("我是游离块2");
	}
}
    执行结果:  我是静态块2
              我是静态块1
              我是游离块1
              我是构造函数1
              我是游离块2
              我是构造函数2

19.子类和父类的类型转换

1、基本类型
  • 类型小的向类型大的类型自动转换,无需强转。
int i=12;
long l=i;
  • 类型大的向类型小的需求强转。可能会丢失精度或者数据溢出。
long l=222;
int i= (int) l;
  • 基本类型可以自动转换为包装类型。包装类型也可以指定转为基本类型。但是包装类型直接不能转换,因为不存在继承关系
Integer integer=2;
int i = new Integer(2);
Long lon=2L;
long long2 = new Integer(2);
2、引用类型
  • 子类可以自动转为父类
  • 父类需要强转才能转为子类,如果父类的实现还是其父类,那么转换异常。
public class A {
}
class B extends A{
}
class M{
    public static void main(String[] args) {
        A a=new A();
        //× ClassCastException
        B b=(B)a;

        A a1=new B();
        // √ 转换成功
        B b1=(B)a1;
    }
}

20.URL的解码与编码

  • 编码 String value = URLEncoder.encode("张三", "utf-8"); %E5%BC%A0%E4%B8%89
  • 解码 String decode = URLDecoder.decode("%E5%BC%A0%E4%B8%89", "utf-8"); 张三

21.常见在XML中的转义字符

<小于&lt;&#60;
>大于&gt;&#62;
&&符号&amp;&#38;
"双引号&quot;&#34;
©版权&copy;&#169;
®已注册商标&reg;&#174;
商标(美国)\™&#8482;
×乘号&times;&#215;
÷除号&divide;&#247;

22.位 & ~ ^ | 运算

1、^ 异或运算

将两个数转为二进制,位对齐,相同则为0,不同为1

例如 8 ^ 11 = 3

8对应的二进制:1000

11对应的二进制:1011

​ 结果:0011 ==> 3

2、& 运算

将两个数转为二进制,位对齐,两个数都为1则为1,否则为0。

129 & 128 = 128

129对应的二进制:10000001

128对应的二进制:10000000

			   结果:10000000 ==> 128
3、| 运算

将两个数转为二进制,位对齐,两个数只要有一个为1则为1,否则就为0

129 | 128 = 129

129对应的二进制:10000001

128对应的二进制:10000000

				 结果:10000001 ==> 129
4、~ 运算

如果位为0,结果是1,如果位为1,结果是0.

~8 = -9

如果8的类型为byte 对应的二进制:0000 1000

		   		  进行`~`运算,结果为 :1111 0111   表名这是一个负数。 
  • 我们知道在计算机中,所有的数都是以补码的形式表示的,正数的原码、反码、补码相同。负数的原码符号位为1,反码为:符号位不变,其它位取反。补码为:符号位不变,在反码的基础上加1

所已 对于~8的结果为 :1111 0111 为负数的表示形式

​ 转换为反码结果为: 1111 0110

​ 再次转换为原码为: 1000 1001 ==> -9

https://blog.csdn.net/yuyonbbo/article/details/88696494open in new window

https://www.cnblogs.com/wqbin/p/11142873.htmlopen in new window

23.>>、<<、>>> 移位运算

1、<< 左移位

左移时不管正负,低位补0

8 << 2 = 32

若这里的8的类型为byte类型,对应的二进制为:0000 1000

​ 向左移两位,低位补0结果为:0010 0000 ==> 32

-8 << 2 = -32

若这里的-8的类型为byte类型,对应的二进制为:1000 1000 ,该数为负数,这里为原码。

​ 转为反码为: 1111 0111

​ 转为补码为: 1111 1000 ,负数的表示为补码形式

在补码的基础上左移两位,低位补0的结果为: 1110 0000

​ 转为反码结果为: 1101 1111

​ 转为原码结果为: 1010 0000 ==> -32

结论:

num << n ,相当于 num×2n,算数左移(逻辑左移)

2、>> 右移位

如果该数为正,则高位补0,若为负数,则高位补1

8 >> 2 = 2

若这里的8的类型为byte类型,对应的二进制为:0000 1000

​ 向右移两位,高位补0结果为:0000 0010 ==> 2

-8 >> 2 = -2

若这里的-8的类型为byte类型,对应的二进制为:1000 1000,该数为负数,这里为原码。

​ 转为反码为: 1111 0111

​ 转为补码为: 1111 1000 ,负数的表示为补码形式

在补码的基础上右移两位,低位补0的结果为: 1111 1110

​ 转为反码的结果为:1111 1101

​ 转为源码的结果为:1000 0010 ==> -2

结论:

num>>n 相当于num/2n,算数右移

3、>>> 无符号右移

注意:只是对32位(int)64位(long)的值有意义

无符号右移,也叫逻辑右移,即若该数为正(则和 >>右移位 结果一致),则高位补0,而若该数为负数,则右移后高位同样补0

8 >> 2 =2

-8 >>> 2 = 1073741822

24.接口和抽象类的区别

1、接口
  • implments 后面只能跟接口

  • 接口可以多继承(只能继承接口),但是不能有任何实现,也可以有多实现。

  • default或者static修饰的方法必须要有实现

  • 字段默认只能被:public static final 修饰

  • 方法默认只能被:public abstract 修饰

2、抽象类
  • 抽象类不能实例化对象,但是其中的方法和字段和普通类一样,
  • 抽象方法不能声明为private私有的,抽象方法必须包含在抽象类中,抽象类中可以没有抽象方法。抽象方法没有方法体
  • 抽象类的子类必须给出抽象类中的抽象方法的具体实现(就是extends继承抽象类完成对抽象成员的实现),除非该子类也是抽象类
  • 构造方法,类方法(用 static 修饰的方法)不能声明为抽象方法。
  • 继承抽象类,必须实现抽象类中的所有抽象方法。

25.访问修饰符

访问修饰符类的内部本包子类其他包
public
protected×
default(默认不写,非接口中的default)××
private×××
  • 注意:jdk1.8中的接口中default关键字的方法还是public的,即公共的。

26.Objects类

1.判断对象是否相等
public static boolean equals(Object a, Object b) {
        return (a == b) || (a != null && a.equals(b));
}
2.对象的比较
public static <T> int compare(T a, T b, Comparator<? super T> c) {
        return (a == b) ? 0 :  c.compare(a, b);
    }
3.对象为空抛异常,否则返回此对象
    public static <T> T requireNonNull(T obj) {
        if (obj == null)
            throw new NullPointerException();
        return obj;
    }
    public static <T> T requireNonNull(T obj, String message) {
        if (obj == null)
            throw new NullPointerException(message);
        return obj;
    }
    public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
        if (obj == null)
            throw new NullPointerException(messageSupplier.get());
        return obj;
    }
4.判断对象是否为空/不为空
    public static boolean isNull(Object obj) {
        return obj == null;
    }
	public static boolean nonNull(Object obj) {
        return obj != null;
    }

27.静态导入

定义静态类

public class DateUtils {
    public static String getCompleteMonth(int month) {
        if (month < 10) {
            return "0" + month;
        }else{
            return String.valueOf(month);
        }
    }
}

直接导入静态方法使用

import static com.org.DataUtils.getCompleteMonth;

public class T{
    public static void main(String[] args) {
        getCompleteMonth(1);
    }
}

28.流的复制

获取到一个inputstream后,可能要多次利用它进行read的操作。由于流读过一次就不能再读了,而InputStream对象本身不能复制,而且它也没有实现Cloneable接口,所以得想点办法。

实现思路:

  • 先把InputStream转化成ByteArrayOutputStream
  • 后面要使用InputStream对象时,再从ByteArrayOutputStream转化回来

代码实现如下:

package com.test;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
 
public class StreamOperateUtil {
	public static void main(String[] args) throws FileNotFoundException {
		 
		InputStream input =  new FileInputStream("c:\\test.txt"); 
		//InputStream input =  httpconn.getInputStream(); //这里可以写你获取到的流
		
		ByteArrayOutputStream baos = cloneInputStream(input);
		
		// 打开两个新的输入流  
		InputStream stream1 = new ByteArrayInputStream(baos.toByteArray());  
		InputStream stream2 = new ByteArrayInputStream(baos.toByteArray());
		
	}
 
	private static ByteArrayOutputStream cloneInputStream(InputStream input) {
		try {
			ByteArrayOutputStream baos = new ByteArrayOutputStream();
			byte[] buffer = new byte[1024];
			int len;
			while ((len = input.read(buffer)) > -1) {
				baos.write(buffer, 0, len);
			}
			baos.flush();
			return baos;
		} catch (IOException e) {
			e.printStackTrace();
			return null;
		}
	}
 
}

这种适用于一些不是很大的流,因为缓存流是会消耗内存的。

可以使用 org.apache.commons.io.output.ByteArrayOutputStream工具包的toBufferedInputStream方法进行流的拷贝

流为什不能被重复读取?

“InputStream就类比成一个杯子,杯子里的水就像InputStream里的数据,你把杯子里的水拿出来了,杯子的水就没有了,InputStream也是同样的道理。”

“在InputStream读取的时候,会有一个pos指针,他指示每次读取之后下一次要读取的起始位置,当读到最后一个字符的时候,pos指针不会重置。” 就是说InputStream的读取是单向的。但是并不是所有的InputStream实现类都是这样的实现方式。

//BufferedInputStream代码片段:  
 public synchronized int read() throws IOException {  
        if (pos >= count) {  
            fill();  
            if (pos >= count)  
                return -1;  
        }  
        return getBufIfOpen()[pos++] & 0xff;  
    }  
  
//FileInputStream代码片段:  
public native int read() throws IOException; 

Java 的List内部是使用数组实现的,遍历的时候也有一个pos指针。但是没有说List遍历一个第二次遍历就没有了。第二次遍历是创建新的Iterator,所以pos也回到了数组起始位置。对于某些InputStream当然可以也这么做。例如:ByteArrayInputStream ByteArrayInputStream就是将一个Java的byte数组保存到对象里,然后读取的时候遍历该byte数组。

public ByteArrayInputStream(byte buf[]) {  
        this.buf = buf;  
        this.pos = 0;  
        this.count = buf.length;  
}  
  
public synchronized int read() {  
        return (pos < count) ? (buf[pos++] & 0xff) : -1;  
}  

这样看来,InputStream其实是一个数据通道,只负责数据的流通,并不负责数据的处理和存储等其他工作范畴。

28. 基本数据类型和包装数据类型区别

  • 初始值不同,基本类型的初始值如int为0,boolean为false,而包装类型的初始值为null