Java基础知识

Java基础知识

基础知识入门

基础概念合集

概念 说明
Java SE 标准版,Java基础,窗口程序
Java EE 企业版,企业级应用开发,WEB应用
Java ME 微型版,针对消费类电子设备,安卓
JDK Java开发工具包,Java.lang 、Java.io、Java.sql、Java.util、Java.awt、Java.net、Java.math
JRE Java运行环境
JVM Java虚拟机

数据类型概览图

20200603164541344

八大基本数据类型

基本类型,或者叫做内置类型,是Java中不同于类的特殊类型。它们是我们编程中使用最频繁的类型。Java是一种强类型语言,第一次申明变量必须说明数据类型,第一次变量赋值称为变量的初始化。实际上,JAVA中还存在另外一种基本类型void,它也有对应的包装类java.lang.Void,不过我们无法直接对它们进行操作。

1522811972141036582

Float和Double的最小值和最大值都是以科学记数法的形式输出的,结尾的"E+数字"表示E之前的数字要乘以10的多少倍。比如3.14E+3就是3.14×1000=3140,3.14E-3就是3.14/1000=0.00314。

数据类型转换

c1ecad970a869eba27005378187877c3

包装类说明:

Java基本类型存储在栈中,因此它们的存取速度要快于存储在堆中的对应包装类的实例对象。从Java5.0(1.5)开始,JAVA虚拟机(Java Virtual Machine)可以完成基本类型和它们对应包装类之间的自动转换。因此我们在赋值、参数传递以及数学运算的时候像使用基本类型一样使用它们的包装类,但这并不意味着你可以通过基本类型调用它们的包装类才具有的方法。另外,所有基本类型(包括void)的包装类都使用了final修饰,因此我们无法继承它们扩展新的类,也无法重写它们的任何方法。

转换规则:

v2-ba4c2e21ba5b09310608ef05133ac660_r

1)基本数据类型中,布尔类型boolean占有一个字节,由于其本身所代码的特殊含义,boolean类型与其他基本类型不能进行类型的转换(既不能进行自动类型的提升,也不能强制类型转换), 否则,将编译出错。

2)在Java中,整数类型(byte/short/int/long)中,对于未声明数据类型的整形,其默认类型为int型。在浮点类型(float/double)中,对于未声明数据类型的浮点型,默认为double型。

3)Jvm在编译过程中,对于默认为int类型的数值时,当赋给一个比int型数值范围小的数值类型变量(如byte/char/short类型),会进行判断,如果此int型数值超过数值类型,那么会直接编译出错。原因在于小的数据类型无法存储大的数据类型变量,但是如果此int型数值尚在范围内,Jvm会自定进行一次转换,将此int型数值转换成更小类型。

4)在其他情况下,当将一个数值范围小的类型赋给一个数值范围大的数值型变量,Jvm在编译过程中将此数值的类型进行了自动提升。在数值类型的自动类型提升过程中,数值精度至少不应该降低(整型保持不变,float转double精度将变高)。

5)隐式转换也叫作自动类型转换,由系统自动完成,从存储范围小的类型到存储范围大的类型。显示类型转换也叫作强制类型转换,是从存储范围大的类型到存储范围小的类型。转换过程中可能导致溢出或损失精度,浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入。

// 强制类型转换
// 浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入
int num1 = (int) 123.45f, num2 = (int) 123.88f, num3 = (int) 123.88D;
// 123:123:123
System.out.println(num1 + ":" + num2 + ":" + num3);

6)如果低级类型为char型,向高级类型整型转换时,会转换为对应ASCII码值。

// 结果为65,对应阿克斯码表
int num6 = 'A';
System.out.println(num6);

7)可以利用包装类的各种方法进行类型转换

// String转int
int num4 = Integer.parseInt("99");
System.out.println(num4);

// String转int方法2
int num5 = Integer.valueOf("88").intValue();
System.out.println(num5);

// int转String
String strNum5 = Integer.toString(num5);
System.out.println(strNum5);

//装箱
Integer i =10;
//拆箱
int n = i;

数据类型与MySQL数据类型对应关系

image-20210729170114200

字节与字符

数据存储是以字节Byte为单位,数据传输大多是以位(bit,又名比特)为单位,一个位就代表一个0或1(即二进制),每8个位(bit,简写为b)组成一个字节(Byte,简写为B),是最小一级的信息单位。其中用一个位来进行数据校验,其他七个位用来记录数据。按计算机中的规定,一个英文的字符占用一个字节,而一个汉字以及汉字的标点符号、字符都占用两个字节。 

不同编码格式占用字节大小不一样
1、ASCII码:一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数,换算为十进制。最小值-128,最大值127。如一个ASCII码就是一个字节。

2、UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。中文标点占三个字节,英文标点占一个字节。

3、Unicode编码:一个英文等于两个字节,一个中文(含繁体)等于两个字节。中文标点占两个字节,英文标点占两个字节。

变量与常量

变量:变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。因此,通过定义不同类型的变量,可以在内存中储存整数、小数或者字符。

/**
 * @author XDZY
 * @date 2020/03/19 21:58
 * @description 变量在不同范围的定义
 */
public class Demo03 {
    // 全局变量,但想在静态方法中使用要加static
    static String name = "张三";

    // 常量的定义final,命名一般全大写,必须赋值初始化
    static final int AGE = 18;

    public static void main(String[] args) {
        // 局部变量
        String h = "hello";
        System.out.println(h + ":" + name + "," + AGE);//hello:张三,18

        name="李四";
        //AGE=20;常量无法动态修改
        System.out.println(h + ":" + name + "," + AGE);//hello:李四,18
    }
}

转义字符对照表

t016012d46a29378f67

String类型解析

定义:程序当中的所有双引号字符串,都是string类的对象,就算没有new,也照样是。

字符串的特点:
1)字符串的内容永不可变
2)由于字符串不可变,所以字符串可以共享
3)字符串效果上相当于char[]字符数组,但是底层是byte[]字节数组

关于字符串的不可变:从源码中可以看出,String类用了final修饰符,我们知道当一个类被final修饰时,表明这个类不能被继承,所以String类不能被继承,这是String不可变的第一点;再往下看,用来存储字符串的char value[]数组被private和final修饰,我们知道对于一个被final的基本数据类型的变量,则其数值一旦在初始化之后便不能更改,这是String不可变的第二点。

为什么要将String设置成不可变的:
1、保证String对象的安全性,假设String对象是可变的,那么String对象将可能被恶意修改。
2、保证hash属性值不会频繁变更,确保了唯一性,使得类似HashMap容器才能实现相应的key-value缓存功能。
3、可以实现字符串常量池。

String对象创建方式

//空参构造
String str1 = new String();
System.out.println(str1);

//传入字符数组,结果ABC
char[] chars = {'A', 'B', 'C'};
String str2 = new String(chars);
System.out.println(str2);

//传入字节数组(对应阿克斯码表),结果abc
byte[] bytes = {97, 98, 99};
String str3 = new String(bytes);
System.out.println(str3);

//直接创建
//注意:这里JVM帮我们创建了字符串对象
String str4 = "hello word";
System.out.println(str4);

String常用方法

20161019092114652

String、StringBuilder与StringBuffer

1)可变性:String str = "a" + "b" + "c" = "abc";
上面会产生5个字符串(a、b、c、ab、abc),字符串缓冲区可以看做是一个可变的字符串,没有被final修饰,底层也是一个数组(初始值16,超出可自动扩容)。String字符串常量,在修改时不会改变自身,若修改,等于重新生成新的字符串对象。StringBuffer在修改时会改变对象自身,每次操作都是对StringBuffer对象本身进行修改,不是生成新的对象;使用场景:对字符串经常改变情况下,主要方法:append(),insert()等。
2)从性能速度方面说:StringBuilder > StringBuffer > String
3)从线程安全方面说:String对象定义后不可变所以线程安全,StringBuffer也是线程安全的,而StringBuilder是线程不安全的,打开StringBuffer源码就会发现所有写操作都被synchronized修饰了,所以所有修改操作都是串行的。而StringBuilder的写操作则没有使用synchronized进行修饰,也不包含其他串行化修改的算法。

总结:
1)String适用于少量的字符串操作的情况
2)StringBuilder适用于单线程下在字符缓冲区进行大量操作的情况
3)StringBuffer适用多线程下在字符缓冲区进行大量操作的情况

==与equals

字符串常量池:程序中直接写上双引号的字符串,就存储在字符串常量池中。

:对于基本类型来说,是进行数值的比较;对于引用类型来说,==是进行地址值的比较。

equals:对象的内容比较,是同一种对象(如都是string)并且内容相同的,就会返回true。

//深度分析实现原理
//1)第一个字符串首先会在堆中创建一个字节数组byte[],保存abc;
//2)然后由于是直接创建string对象,所以string对象保存在堆的字符串常量池中,如该对象地址为0x11;
//3)该string对象会保存byte[]数组的地址值,然后在栈中的str1会指向该string对象
String str1 = "abc";
//4)第二个字符串也是同样的创建,字符串常量池中已经有该对象,所以直接指向该对象
String str2 = "abc";
//5)第三个字符串由于是new出来的,所以会在堆中创建一个string对象,如该对象地址为0x23;
//6)该对象保存字节数组的地址,在栈中的str3指向该对象
String str3 = new String("abc");

//7)str1与str2的对象地址都是0x11;而str3的对象地址是0x23
System.out.println(str1 == str2);//true
System.out.println(str1 == str3);//false
System.out.println(str2 == str3);//false

//8)equals:字符串的内容比较,因为参数都是字符串并且内容都相同,所以都会返回true
System.out.println(str1.equals(str2));//true
System.out.println(str1.equals(str3));//true
System.out.println(str2.equals(str3));//true
//9)推荐使用"abc".equals(str1)的方式比较,防止空指针异常
System.out.println("abc".equals(str1));//true
//10)忽略大小写的比较
System.out.println("ABC".equals(str1));//false
System.out.println("ABC".equalsIgnoreCase(str1));//true

equals与hashcode

1)如果没有重写equals()方法, x.equals(y)==true, 那么hashcode的值一定相同。因为源码里面equals()比较的是地址, 同一个对象的hashcode的值一定是相同的。

2)如果只重写equals()方法, 没有重写hashcode()方法, x.equals(y)==true, 那么hashcode的值可能不相同(因为重写了equals之后可能比较的不是地址了,也就是说x.equals(y)==true也不一定是同一个对象)。这样的话,这个类就不能和某些集合类一起使用了(比如:set,hashmap), 因为这些集合是通过hash判断存储的。所以在开发里面用个约定俗成, 重写了equals一般会重写hashcode。

hashCode通常在可以起到快速初次判断对象是否相等的作用,可以提高性能
1、Hashcode方法是用来计算一个元素在哈希桶中的存储位置
2、equals方法用来判断当前哈希桶中是否存在相同的元素
hashcode不同, 对象一定不同, 不用再使用equals()比较了;hashcode相同, 对象可能不同, 这个时候再使用equals()进一步比较。

+与append

字符串的+操作其本质是创建了StringBuilder对象(jdk4之前使用StringBuffer)进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象,在非循环下使用+或append进行字符串拼接没有特别大的区别,但是在循环下使用+会创建多个字符串缓存区对象,这时使用append性能会大大提升。

运算符概览图

22d5000147609d4ac946

75888b20776f40bfb6f04c9eed32bb70

++与--

int a, b, c = 14, d = 14;

// --在后面相当于先a=c,再c=c-1
a = c--;
System.out.println("a=" + a);//14
System.out.println("c=" + c);//13

// ++在前面相当于d=d+1,在b=d
b = ++d;
System.out.println("b=" + b);//15
System.out.println("d=" + d);//15

// d=15+15+15+14,a的值需要从左到右依次计算,a值的变化(15、16、15、14)
d = ++a + a++ + --a + --a;
System.out.println("a复杂操作结果:" + a);//14
System.out.println("d复杂操作结果:" + d);//59

// d=15+14+14+14+58+14
d = ++a + --b + ++c + --a + --d + a;
System.out.println("d复杂操作结果:" + d);//129

&和&&的区别

&&称为短路运算,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为username != null && !username.equals(""),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

判断与循环语法

if条件判断

int week = 2;
if (week == 1) {
	System.out.println("星期一");
} else if (week == 2) {
	System.out.println("星期二");
} else if (week == 3) {
	System.out.println("星期三");
} else if (week == 4) {
	System.out.println("星期四");
} else {
	System.out.println("其他星期");
}

switch条件判断

能用于switch判断的类型有:byte、short、int、char(JDK1.6),还有枚举类型,但是在JDK1.7后添加了对String类型的判断

int week = 7;
// week应和case后的数据类型一致
switch (week) {
    case 1:
        System.out.println("星期一");
        break;
        // 6,7之间没有break会贯穿下去,直到看见break
    case 6:
    case 7:
        System.out.println("周末");
        break;
        // 如果没有符合条件的case就执行default下的代码块,default并不是必须的,也可以不写
    default:
        System.out.println("不存在");
        break;
}

switch条件判断枚举

新建枚举类

/**
 * 路径枚举类
 */
@Getter
enum UrlEnums {
    ADD_DEVICE("新增设备", "/iot/visual/facility/add"),
    UPDATE_DEVICE("编辑设备", "/iot/visual/facility/modify"),
    DEL_DEVICE("删除设备", "/iot/visual/facility/del"),
    DOWN_DEVICE_FACE("下发设备人脸", "/iot/hik/down/face"),
    DEL_DEVICE_PERSON("删除设备人员", "/iot/hik/del/user"),
    SYNC_DEVICE_STATE("获取设备状态", "/iot/visual/facility/get/state"),
    DEL_DEVICE_FACE("删除设备人脸", "/iot/hik/del/face"),
    UPDATE_DEVICE_OPENDOOR("开门", "/iot/hik/open/door"),
    PREPARE_ISSUE("预下发", "/iot/hik/issued/advance"),
    OPERATIONAL_DEAL("运维处理", "/itsm/request/third/create");

    private final String name;
    private final String url;

    UrlEnums(String name, String url) {
        this.name = name;
        this.url = url;
    }

    /**
     * 根据名称获取枚举对象
     *
     * @param name
     * @return
     */
    public static UrlEnums getEnumByName(String name) {
        if ("".equals(name)) {
            return null;
        }
        for (UrlEnums enums : UrlEnums.values()) {
            if (enums.getName().equals(name)) {
                return enums;
            }
        }
        return null;
    }

    /**
     * 根据名称获取路径
     *
     * @param name
     * @return
     */
    public static String getUrlByName(String name) {
        for (UrlEnums opEnums : UrlEnums.values()) {
            if (name.equals(opEnums.getName())) {
                return opEnums.getUrl();
            }
        }
        return null;
    }
}

根据枚举对象进行判断

UrlEnums enumType = UrlEnums.getEnumByName("新增设备");
switch (enumType) {
    case ADD_DEVICE:
        System.out.println(enumType.getName());
        break;
    case UPDATE_DEVICE:
        System.out.println(enumType.getName());
        break;
    default:
        System.out.println("没有匹配值");
        break;
}

do...while()循环

不管条件成不成立都会先执行一次

Scanner ner = new Scanner(System.in);
int count = 0;
do {
	System.out.println("请输入用户名:");
	String name = ner.next();
	System.out.println("请输入密码:");
	String pwd = ner.next();

    if ("root".equals(name) && "admins".equals(pwd)) {
        System.out.println("欢迎进入系统界面");
        break;
    } else {
        count++;
        if (count >= 3) {
            System.out.println("输入3次,明天再来");
            // break:只是退出循环或单独块,
            // exit:退出整个程序,不会回到main主方法
            System.exit(0);
        }
    }
} while (true);

while死循环

int count = 0;
// 定义一个boolean值来控制结束
boolean isRun = true;
while (isRun) {
    int rand = (int) Math.round(Math.random() * 10);
    System.out.println(rand);
    count++;
    if (rand == 7) {
        isRun = false;
    }
}
System.out.println("一共执行了:" + count);

break和continue

break和continue都是用来控制循环的语句,break用于完全结束一个循环,跳出循环体执行循环后面的语句,continue用于跳过本次循环,执行下次循环。 

first:
for (int i = 0; i < 10; i++) {
    if (i == 3) {
        break first;
    }
    System.out.println("================循环开始============");
    second:
    for (int j = 0; j < 10; j++) {
        if (j == 4) {
            continue second;
        }
        System.out.println(j);
    }
}

面向对象相关知识

访问权限修饰符

e7dc217a3964b5661c3b053090a68f63

instanceof关键字

instanceof关键字,用来查看一个对象是否为一个类的实例,boolean result = obj instanceof Class,其中obj为一个对象,Class表示一个类或者一个接口,当obj为Class的对象,或者是其直接或间接子类,或者是其接口的实现类,结果result都返回true,否则返回false。注意:编译器会检查obj是否能转换成右边的Class类型,如果不能转换则直接报错,如果不能确定类型,则先通过编译,再具体看运行时定。

public class Demo27 {
    public static void main(String[] args) {
        int i = 0;
        //编译不通过i必须是引用类型,不能是基本类型
        //System.out.println(i instanceof Integer);
        //编译不通过
        //System.out.println(i instanceof Object);

        Integer integer = new Integer(1);
        //true
        System.out.println(integer instanceof Integer);
        //true
        System.out.println(integer instanceof Object);
        //false,在JavaSE规范中对instanceof运算符的规定就是:如果obj为null,那么将返回false。
        System.out.println(null instanceof Object);
    }
}

Object常用方法(toString等)

Equals、Hashcode、toString、wait、notify、clone、getClass

toString返回该对象的字符串表示。通常,toString方法会返回一个以文本方式表示此对象的字符串。结果应是一个简明但易于读懂的信息表达式,建议所有子类都重写此方法。

什么情况下要重写toString,object类里的toString只是把字符串的直接打印,数字的要转化成字符再打印,而对象,则直接打印该对象的hash码。所以当你要想按照你想要的格式去字符串一些对象的时候,就需要重写toString了。比如一个Student对象,直接toString肯定是一个hash码。然而你想得到的比如是:name:*,age:*。这时就重写toString就是在toString里写:System.out.println(“name:”+student.getName);System.out.println(“age:”+student.getAge)。这样再toString就直接反回你想要的格式。通过查api我们就可以知道HashSet的toString是把s的值格式化成[*,*,*],就是给s的加个中括号,而且用逗号分开。而HashMap的toString是把m的值格式化成{key1=value1,key2=value2,key3=value3} 所以你打印出来的是那样的格式,这就是重写toString的作用。

按照java开发的既定规范,任何和数据有关的类定义都需要重写三个方法:1、hashcode 2、equals 3、toString 同时实现这三个方法有一个要求,既是,被定义为相同数据的两个对象,这三个方法表现必须一致:既是如果a.equals(b)==true的情况下,a.hashcode()==b.hashcode()返回true;a.toString()==b.toString()也要返回true。

注意:lombok的@data注解也会重写toString方法。

final在不同位置的不同含义

1、final:最终的;可以修饰变量,方法,类
2、可以防止继承的发生;可以防止方法被子类重写;可以防止值被更改;
3、有点类似C#中:sealed(密封的)

// 防止继承的发生
public final class Demo07 {
    // 要养成赋初值的习惯,因为要在各个平台跑程序,以免报错
    // 防止值被更改
    // 有final修饰的变量为常量,必须有值
    final int a = 0;

    // 防止方法被子类重写
    final public void eat() {
        System.out.println("吃饭");
    }
}

构造函数

1、构造函数的命名必须和类名完全相同。在java中普通函数可以和构造函数同名,但是必须带有返回值。

2、构造函数的功能主要用于在类的对象创建时定义初始化的状态。它没有返回值,也不能用void来修饰。这就保证了它不仅什么也不用自动返回,而且根本不能有任何选择。而其他方法都有返回值,即使是void返回值。尽管方法体本身不会自动返回什么,但仍然可以让它返回一些东西,而这些东西可能是不安全的。

3、构造函数不能被直接调用,必须通过new运算符在创建对象时才会自动调用;而一般的方法是在程序执行到它的时候被调用的。

4、当定义一个类的时候,通常情况下都会显示该类的构造函数,并在函数中指定初始化的工作也可省略,不过Java编译器会提供一个默认的构造函数,此默认构造函数是不带参数的。而一般的方法不存在这一特点。

重载与重写

重载:类的同一功能的多种实现方式
覆盖(方法的重写):子类方法不能缩小父类方法的访问范围

重载规则
1、方法名一致,参数的类型,个数,顺序至少一项不同
2、方法返回类型或修饰符可以不同
3、只是返回类型或修饰符不一样不能构成重载

重写规则
1、参数必须完全与被重写方法的一致,返回类型必须完全与被重写方法的返回类型一致。 
2、构造方法不能被重写,声明为final的方法不能被重写,声明为static的方法不能被重写,但是能够被
再次声明。 
3、访问权限不能比父类中被重写的方法的访问权限更低。 
4、重写的方法能够抛出任何非强制异常(UncheckedException,也叫非运行时异常),重写的方法不
能抛出新的强制性异常,或者比被重写方法声明的更广泛的强制性异常。 

面向对象的三大特征

1、抽象(可忽略该特征):抽象是将一类对象的共同特征总结出来构造类的过程,包括数据抽象和行为抽象两方面。抽象只关注对象有哪些属性和行为,并不关注这些行为的细节是什么。 

2、封装:通常认为封装是把数据和操作数据的方法绑定起来,对数据的访问只能通过已定义的接口。面向对象的本质就是将现实世界描绘成一系列完全自治、封闭的对象。我们在类中编写的方法就是对实现细节的一种封装,我们编写一个类就是对数据和数据操作的封装。可以说,封装就是隐藏一切可隐藏的东西,只向外界提供最简单的编程接口。

3、继承:继承是从已有类得到继承信息创建新类的过程。提供继承信息的类被称为父类(超类、基类),得到继承信息的类被称为子类(派生类)。继承让变化中的软件系统有了一定的延续性,同时继承也是封装程序中可变因素的重要手段。继承可以实现代码的复用。

4、多态:多态是指允许不同子类型的对象对同一消息作出不同的响应。简单的说就是用同样的对象引用调用同样的方法但是做了不同的事情。多态性分为编译时的多态性和运行时的多态性。如果将对象的方法视为对象向外界提供的服务,那么运行时的多态性可以解释为:当A系统访问B系统提供的服务时, B系统有多种提供服务的方式,但一切对 A 系统来说都是透明的。

方法重载(overload)实现的是编译时的多态性(也称为前绑定),而方法重写(override)实现的是运行时的多态性(也称为后绑定)。运行时的多态是面向对象最精髓的东西,要实现多态需要做两件事:1)方法重写(子类继承父类并重写父类中已有的或抽象的方法)2)对象造型(用父类型引用引用子类型对象,这样同样的引用调用同样的方法就会根据子类对象的不同而表现出不同的行为)。

定义内部类

//内部类
public class Demo22 {
    private String name;

    //成员内部类
    public class Inner {
        //内部类方法
        public void innerMethod() {
            System.out.println("内部类方法");
            //可以访问外部类属性
            System.out.println(name);
        }
    }

    //外部类方法
    public void outerMethod() {
        System.out.println("外部类方法");
        //调用内部类方法
        new Inner().innerMethod();
    }
}

//内部类访问
Demo22.Inner inner = new Demo22().new Inner();
inner.innerMethod();

//匿名内部类
public interface Demo25 {
    void innerMethod();
}

//匿名内部类访问
Demo25 demo25 = new Demo25() {
    @Override
    public void innerMethod() {
        System.out.println("这是匿名内部类");
    }
};
demo25.innerMethod();

定义抽象类

0、用abstract来修饰该类(抽象类)
1、抽象类不能实例化(他自己的方法都不知道要干什么,类也是不完整的,抽象出来的)
2、抽象类可以没有抽象方法
3、当父类方法不确定时,可以用abstract关键字来修饰该方法(抽象方法)
4、抽象方法不实现(没有“{}”),即不能有实体

/**
 * 如果一个类里有抽象方法,那么这个类必须是抽象类(加abstract)
 */
abstract class Animal2 {
    String name;
    int age;

    public void eat() {
        System.out.println("可以有其他非抽象方法");
    }

    abstract public void cry();
}

/**
 * 抽象类继承抽象类可以不实现抽象方法
 */
abstract class Animal3 extends Animal2 {

}

/**
 * 当一个类继承的父类是抽象类,需要把抽象类中所有的抽象方法实现
 */
class Cat2 extends Animal2 {
    /**
     * 实现父类的cry()
     */
    @Override
    public void cry() {
        System.out.println("猫叫声");
    }
}

定义接口

接口:是一些没有内容的方法封装在一起,到某个类要使用的时候,在根据具体情况把这些方法写出来
1、接口是更加抽象的抽象类
2、接口所有方法都不能有方法体
3、接口体现了程序设计的多态和高内聚、低偶合(就是说分散,你是你,我是我)的设计思想
4、接口不能实例化
5、一个类只能有一个父类,可以有多个接口
6、接口不能继承类,但是可以继承其他接口
7、继承:一般是类的多态;接口:一般是功能的多态

interface Usb {
    // 接口可以有变量,但是不能用private和protected修饰
    // 接口变量本质上都是static && final的,不管加不加关键字
    // 访问时:接口名.变量名
    int a = 1;

    /**
     * 接口默认方法需要通过接口的实现类去调用,并且如果实现类实现了多个接口中相同的方法,则需要进行方法重写
     */
    default void defTest() {
        System.out.println("接口中默认方法定义");
    }

    /**
     * 接口中静态方法的使用可以直接通过(接口名.方法名)的形式进行调用
     */
    static void staTest() {
        System.out.println("接口中静态方法定义");
    }

    public void start();

    public void stop();
}

抽象类与接口

抽象类:
1、抽象类中可以定义构造器
2、可以有抽象方法和具体方法
3、接口中的成员全都是public的
4、抽象类中可以定义成员变量
5、有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法
6、抽象类中可以包含静态方法
7、一个类只能继承一个抽象类

接口: 
1、接口中不能定义构造器
2、方法全部都是抽象方法
3、抽象类中的成员可以是private、默认、protected、public
4、接口中定义的成员变量实际上都是常量
5、之前版本接口中不能有静态方法,但在jdk1.8中,接口中可以定义多个默认方法和多个静态方法
6、一个类可以实现多个接口

不同之处
1、抽象类可以提供成员方法的实现细节,而接口中没有;但是JDK1.8之后,在接口里面可以定义default方法,default方法里面是可以具备方法体的。
2、抽象类中的成员变量可以是各种类型的,而接口中的成员变量只能是public static final类型;
3、接口中每一个方法也是隐式指定为public abstract,不能含有静态代码块以及静态方法,而抽象类可以有静态代码块和静态方法;
4、一个类只能继承一个抽象类,而一个类却可以实现多个接口。

相同之处
1、不能够实例化
2、可以将抽象类和接口类型作为引用类型
3、一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该
类仍然需要被声明为抽象类

使用场景
1、抽象类用于表示一类事物,抽象概念产品描述。
2、接口表示一种能力、规范、约束,实现接口表示具有某种能力。

异常处理相关知识

异常分类

异常父类:java.lang.Throwable
1)检查性异常:java.lang.Exception
2)运行期异常:java.lang.RuntimeException
3)错误:java.lang.Error
按照异常需要处理的时机分为编译时异常(也叫强制性异常)也叫CheckedException和运行时异常
(也叫非强制性异常)也叫RuntimeException。只有Java语言提供了Checked异常,Java认为Checked异常都是可以被处理的异常,所以Java程序必须显式处理Checked异常。如果程序没有处理Checked异常,该程序在编译时就会发生错误无法编译。这体现了Java的设计哲学:没有完善错误处理的代码根本没有机会被执行。对Checked异常处理方法有两种: 
1、当前方法知道如何处理该异常,则用try...catch块来处理该异常。 
2、当前方法不知道如何处理,则在定义该方法是声明抛出该异常。运行时异常只有当代码在运行时才发行的异常,编译时不需要try catch。Runtime如除数是0和数组下标越界等,其产生频繁,处理麻烦,若显示申明或者捕获将会对程序的可读性和运行效率影响很大。所以由系统自动检测并将它们交给缺省的异常处理程序,当然如果你有处理要求也可以显示捕获它们。

public static void main(String[] args) {
    // 打开不存在的文件
    // 如果有多个catch,当捕获第一个异常时,程序结束,不会捕获下一个异常
    FileReader fr = null;
    try {
        // 这里设置会出现异常的代码
        fr = new FileReader("d:/a.txt");
        System.out.println("异常发生时会执行吗?");
    } catch (FileNotFoundException e) {
        // 捕获异常
        // 输出异常信息
        // System.out.println(e.getMessage());
        e.printStackTrace();

        // 非正常退出(值为-1),finally不执行
        // System.exit(-1);
    } finally {
        System.out.println("finally会执行吗?");
        // 这里设置要关闭的资源
        if (fr != null) {
            try {
                fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    System.out.println("结束之后会执行吗?");
}

finally不会执行的情况

1)finally块中发生了异常
2)程序所在线程死亡
3)在前面代码中用了System.exit()
4)关闭CPU

常见的运行时异常(RuntimeException)

1、java.lang.NullPointerException(空指针异常),出现原因:调用了未经初始化的对象或者是不存在的对象。 
2、java.lang.ClassNotFoundException(指定的类找不到),出现原因:类的名称和路径加载错误,通常都是程序图通过字符串来加载某个类时可能引发异常。 
3、java.lang.NumberFormatException(字符串转换为数字异常),出现原因:字符型数据中包含非数字型字符。
4、java.lang.IndexOutOfBoundsException(数组角标越界异常),常见于操作数组对象时发生。 
5、java.lang.IllegalArgumentException(方法传递参数错误)。 
6、java.lang.ClassCastException(数据类型转换异常)。 
7、java.lang.NoClassDefFoundException(未找到类定义错误)。 
8、SQLException SQL异常(常见于操作数据库时的SQL语句错误)。
9、java.lang.InstantiationException(实例化异常)。 
10、java.lang.NoSuchMethodException(方法不存在异常)。 

throw和throws

throw: 
1)throw语句用在方法体内,表示抛出异常,由方法体内的语句处理。 
2)throw是具体向外抛出异常的动作,所以它抛出的是一个异常实例,执行throw一定是抛出了某种异常。

throws: 
1)throws语句是用在方法声明后面,表示如果抛出异常,由该方法的调用者来进行异常的处理。 
2)throws主要是声明这个方法会抛出某种类型的异常,让它的使用者要知道需要捕获的异常的类型。 
3)throws表示出现异常的一种可能性,并不一定会发生这种异常。 
4)throws把异常抛给调用者处理,调用者可以继续抛出给调用者,也可以在调用者中处理异常,一直抛出最终会交给JVM虚拟机处理。

泛型相关知识

可变参数

可变参数是jdk5新特性:当方法参数数据类型已经确定,但是参数个数不确定,就可以使用可变参数。
可变参数原理:底层是数组,根据传递的参数个数不同会创建不同长度的数组。
注意事项:
1)一个方法的参数列表,只能有一个可变参数
2)如果一个方法有多个参数,可变参数必须写在末尾

public class Demo16 {
    /**
     * 计算n个数之和
     *
     * @return
     */
    static int sum(int... arr) {
        int sum = 0;
        for (int i : arr) {
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) {
        int sum = sum(10, 20, 40, 50);
        System.out.println(sum);
    }
}

泛型通配符

通配符 含义
E - Element 在集合中使用,因为集合中存放的是元素
T - Type Java类
K - Key
V - Value
N - Number 数值类型
表示不确定的java类型,可以代表任意的数据类型,不能创建对象使用,只能做方法的参数使用
S、U、V 2nd、3rd、4th types

泛型类与方法

泛型:是一种未知的数据类型,创建对象的时候,就会确定泛型的数据类型。
含有泛型的方法:泛型定义在方法的修饰符与返回值之间。
在调用方法的时候确定泛型的数据类型,传递什么类型参数,泛型就是什么类型。

public class GenericClass<E> {
    private E name;

    public E getName() {
        return name;
    }

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

    /**
     * 定义一个含有泛型的方法
     *
     * @param m
     * @param <M>
     */
    public <M> void method(M m) {
        System.out.println(m);
    }

    /**
     * 定义一个含有泛型静态方法
     *
     * @param s
     * @param <S>
     */
    public static <S> void test(S s) {
        System.out.println(s);
    }
}

测试:
//不使用泛型
GenericClass gc = new GenericClass();
gc.setName("XDZY");
//不写泛型默认是object类型
Object name = gc.getName();
System.out.println(name);

//使用泛型
GenericClass<String> gc2 = new GenericClass<>();
gc2.setName("XDZY");
String name1 = gc2.getName();
System.out.println(name1);

//测试含有泛型的方法
GenericClass gc3 = new GenericClass();
gc3.method(10);
gc3.method("XDZY");

//测试含有泛型静态方法
GenericClass.test("静态方法");
GenericClass.test(18);

泛型接口

//接口定义
public interface GenericInterface<I> {
    public abstract void test(I i);
}

1)在实现类中定义泛型的数据类型
public class GenericInterfaceImpl implements GenericInterface<String>{
    @Override
    public void test(String s) {
        System.out.println(s);
    }
}
使用:
GenericInterface gi = new GenericInterfaceImpl();
gi.test("XDZY");

2)由接口定义泛型的数据类型
public class GenericInterfaceImpl2<I> implements GenericInterface<I> {
    @Override
    public void test(I i) {
        System.out.println(i);
    }
}
使用:
GenericInterface<String> gi2 = new GenericInterfaceImpl2<>();
gi2.test("XDZY");

泛型上下限

上限限定:?extends E ——代表使用的泛型只能是E类型的子类/本身
下限限定:?super E ——代表使用的泛型只能是E类型的父类/本身

public class Demo08 {
    /**
     * 泛型的上限
     *
     * @param list
     */
    static void test1(Collection<? extends Number> list) {

    }

    /**
     * 泛型的下限
     *
     * @param list
     */
    static void test2(Collection<? super Number> list) {

    }


    public static void main(String[] args) {
        //定义保存不同数据类型的集合
        Collection<Integer> list1 = new ArrayList<>();
        Collection<String> list2 = new ArrayList<>();
        Collection<Number> list3 = new ArrayList<>();
        Collection<Object> list4 = new ArrayList<>();

        //上限限定测试
        test1(list1);
        //test1(list2);//报错
        test1(list3);
        //test1(list4);//报错

        //下限限定测试
        //test2(list1);//报错
        //test2(list2);//报错
        test2(list3);
        test2(list4);
    }
}

代码块相关知识

静态代码块

在java类中使用static关键字和{}声明的代码块,注意方法中不能存在静态代码块:
public class Test {
    static{
        System.out.println("静态代码块");
    }
}

执行时机:静态代码块在类被加载的时候就运行了,而且只运行一次,并且优先于各种代码块以及构造函数。如果一个类中有多个静态代码块,会按照书写顺序依次执行。

静态代码块的作用:一般情况下,如果有些代码需要在项目启动的时候就执行,这时候就需要静态代码块。比如一个项目启动需要加载的很多配置文件等资源,我们就可以都放入静态代码块中。

静态代码块不能存在任何方法体中:这个应该很好理解,首先我们要明确静态代码块是在类加载的时候就要运行了。我们分情况讨论:对于普通方法,由于普通方法是通过加载类,然后new出实例化对象,通过对象才能运行这个方法,而静态代码块只需要加载类之后就能运行了。对于静态方法,在类加载的时候,静态方法也已经加载了,但是我们必须要通过类名或者对象名才能访问,也就是说相比于静态代码块,静态代码块是主动运行的,而静态方法是被动运行的。不管是哪种方法,我们需要明确静态代码块的存在在类加载的时候就自动运行了,而放在不管是普通方法还是静态方法中,都是不能自动运行的。

静态代码块不能访问普通变量:这个理解思维同上,普通变量只能通过对象来调用,是不能放在静态代码块中的。

构造代码块

在java类中使用{}声明的代码块,和静态代码块的区别是少了static关键字:
public class Test {
    static{
        System.out.println("静态代码块");
    }
    
    {
        System.out.println("构造代码块");
    }
}

执行时机:构造代码块在创建对象时被调用,每次创建对象都会调用一次,但是优先于构造函数执行。需要注意的是,听名字我们就知道,构造代码块不是优先于构造函数执行,而是依托于构造函数,也就是说,如果你不实例化对象,构造代码块是不会执行的。

构造代码块的作用:和构造函数的作用类似,都能对对象进行初始化,并且只要创建一个对象,构造代码块都会执行一次。但是反过来,构造函数则不一定每个对象建立时都执行(多个构造函数情况下,建立对象时传入的参数不同则初始化使用对应的构造函数)。利用每次创建对象的时候都会提前调用一次构造代码块特性,我们可以做诸如统计创建对象的次数等功能。

普通代码块

普通代码块和构造代码块的区别是,构造代码块是在类中定义的,而普通代码块是在方法体中定义的。且普通代码块的执行顺序和书写顺序一致。
public void sayHello() {
    {
        System.out.println("普通代码块");
    }
}

代码块执行顺序

同一个类:静态代码块 > 构造代码块 > 构造函数 > 普通代码块

父类与子类:首先执行父类静态的内容,父类静态的内容执行完毕后,接着去执行子类的静态的内容,当子类的静态内容执行完毕之后,再去看父类有没有构造代码块,如果有就执行父类的构造代码块,父类的构造代码块执行完毕,接着执行父类的构造方法;父类的构造方法执行完毕之后,它接着去看子类有没有构造代码块,如果有就执行子类的构造代码块。子类的构造代码块执行完毕再去执行子类的构造方法。总之一句话,静态代码块内容先执行,接着执行父类构造代码块和构造方法,然后执行子类构造代码块和构造方法。

四大引用类型详解

在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如Employee、Puppy等。变量一旦声明后,类型就不能被改变了。所有引用类型的默认值都是null。在JDK.1.2之后,Java对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用的强度依次减弱。

强引用(Strong References)

Java中默认声明的就是强引用,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了。

/**
 * 强引用类型是我们平时写代码的时候最常用的引用,而大部分人往往都会忽略这个概念。
 * 强引用在程序内存不足(OOM)的时候也不会被回收。
 */
public void strongDemo() {
    //创建一个对象,new出来的对象都是分配在java堆中的
    //sample这个引用就是强引用
    Sample sample = new Sample();
    //将这个引用指向空指针,那么上面那个刚new来的对象就没有任何其它有效的引用指向它了
    //也就说该对象对于垃圾收集器是符合条件的
    //因此在接下来某个时间点,GC进行收集动作的时候,该对象将会被销毁,内存被释放
    sample = null;
}

软引用(Soft References)

软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。在JDK1.2之后,用java.lang.ref.SoftReference类来表示软引用。

/**
 * 软引用在java.lang.ref包中有与之对应的类java.lang.ref.SoftReference。
 * 重点:被弱引用指向的对象不会被垃圾收集器收集,即使该对象没有强引用指向它,除非jvm使用内存不够了,
 * 才会对这类对象进行销毁,释放内存。
 * 可用场景:创建缓存的时候,创建的对象放进缓存中,当内存不足时,JVM就会回收早先创建的对象。
 */
public void softDemo() {
    //创建一个对象,new出来的对象都是分配在java堆中的
    //sample这个引用就是强引用
    Sample sample = new Sample();

    //创建一个软引用指向这个对象,那么此时就有两个引用指向Sample对象
    SoftReference<Sample> softRef = new SoftReference<Sample>(sample);

    //将强引用指向空指针,那么此时只有一个软引用指向Sample对象
    //注意:softRef这个引用也是强引用,它是指向SoftReference这个对象的
    //那么这个软引用在哪呢? 可以跟一下java.lang.Reference的源码
    //private T referent; 也就是SoftReference类中T泛型,这个才是软引用,只被jvm使用
    sample = null;

    //可以重新获得Sample对象,并用一个强引用指向它
    sample = softRef.get();
}

弱引用(Weak References)

弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要JVM开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在JDK1.2之后,用java.lang.ref.WeakReference来表示弱引用。

private static final List<Object> TEST_DATA = new LinkedList<>();

private static final ReferenceQueue<Sample> QUEUE = new ReferenceQueue<>();

/**
 * 弱引用会被jvm忽略,也就说在GC进行垃圾收集的时候,如果一个对象只有弱引用指向它,
 * 那么和没有引用指向它是一样的效果,jvm都会对它就行果断的销毁,释放内存。
 * 其实这个特性是很有用的,jdk也提供了java.util.WeakHashMap这么一个key为弱引用的Map。
 * 比如某个资源对象你要释放(比如db connection),但是如果被其它map作为key强引用了,就无法释放,被jvm收集。
 * 弱引用对象并不需要在jvm耗尽内存的情况下才进行回收,是可以随时回收的。
 */
public void weakDemo() {
    //创建一个对象,new出来的对象都是分配在java堆中的
    //sample这个引用就是强引用
    Sample sample = new Sample();

    //SoftReference<Sample> softRef = new SoftReference<Sample>(sample, QUEUE);

    //创建一个弱引用指向这个对象,那么此时就有两个引用指向Sample对象
    WeakReference<Sample> weakRef = new WeakReference<Sample>(sample, QUEUE);

    //将强引用指向空指针 那么此时只有一个弱引用指向Sample对象
    sample = null;

    new Thread() {
        @Override
        public void run() {
            while (true) {
                System.out.println(weakRef.get());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
                TEST_DATA.add(new byte[1024 * 1024 * 5]);
            }
        }
    }.start();

    new Thread() {
        @Override
        public void run() {
            while (true) {
                Reference<? extends Sample> poll = QUEUE.poll();
                if (poll != null) {
                    System.out.println("--- 弱引用对象被jvm回收了 ---- " + poll);
                    System.out.println("--- 回收对象 ---- " + poll.get());
                }
            }
        }
    }.start();

    try {
        Thread.currentThread().join();
    } catch (InterruptedException e) {
        System.exit(1);
    }
}

虚引用(Phantom References)

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在JDK1.2之后,用PhantomReference类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个get()方法,而且它的get()方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和ReferenceQueue引用队列一起使用。引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。

/**
 * 虚引用和弱引用的回收机制差不多,都是可以被随时回收的。但是不同的地方是,它的构造方法必须强制传入ReferenceQueue,
 * 因为在jvm回收前(软引用和弱引用都是回收后),会将PhantomReference对象加入ReferenceQueue中,
 * 还有一点就是PhantomReference.get()方法永远返回空,不管对象有没有被回收。
 * 由于这个机制,所以虚引用大多被用于引用销毁前的处理工作。
 */
public void phantomDemo() {
    Sample sample = new Sample();

    //虚幻引用
    PhantomReference<Sample> phantomRef = new PhantomReference<>(sample, QUEUE);

    sample = null;

    new Thread() {
        @Override
        public void run() {
            while (true) {
                System.out.println(phantomRef.get());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    Thread.currentThread().interrupt();
                }
                TEST_DATA.add(new byte[1024 * 1024 * 5]);
            }
        }
    }.start();

    new Thread() {
        @Override
        public void run() {
            while (true) {
                Reference<? extends Sample> poll = QUEUE.poll();
                if (poll != null) {
                    System.out.println("--- 虚幻引用对象被jvm回收了 ---- " + poll);
                    System.out.println(poll.isEnqueued());
                    System.out.println("--- 回收对象 ---- " + poll.get());
                }
            }
        }
    }.start();

    try {
        Thread.currentThread().join();
    } catch (InterruptedException e) {
        System.exit(1);
    }
}
posted @ 2018-08-13 11:16  肖德子裕  阅读(384)  评论(0编辑  收藏  举报