数据类型
变量就是申请内存来存储值。也就是说,当创建变量的时候,需要在内存中申请空间。内存管理系统根据变量的类型为变量分配存储空间,分配的空间只能用来储存该类型数据。因此,通过定义不同类型的变量,可以在内存中储存整数、小数或者字符。
八大基本数据类型
基本类型,或者叫做内置类型,是JAVA中不同于类的特殊类型。它们是我们编程中使用最频繁的类型。java是一种强类型语言,第一次申明变量必须说明数据类型,第一次变量赋值称为变量的初始化。实际上,JAVA中还存在另外一种基本类型 void,它也有对应的包装类 java.lang.Void,不过我们无法直接对它们进行操作。
Float和Double的最小值和最大值都是以科学记数法的形式输出的,结尾的"E+数字"表示E之前的数字要乘以10的多少倍。比如3.14E3就是3.14×1000=3140,3.14E-3就是3.14/1000=0.00314。
字节与字符
数据存储是以“字节”(Byte)为单位,数据传输大多是以“位”(bit,又名“比特”)为单位,一个位就代表一个0或1(即二进制),每8个位(bit,简写为b)组成一个字节(Byte,简写为B),是最小一级的信息单位。其中用一个位来进行数据校验,其他七个位用来记录数据。
按计算机中的规定,一个英文的字符占用一个字节,(如,."':;avcAVC都占用一个字节),而一个汉字以及汉字的标点符号、字符都占用两个字节,(如,。“”:;AVCavc他们就得占用两个字节)。
不同编码格式占用字节大小不一样
1、ASCII码:一个英文字母(不分大小写)占一个字节的空间,一个中文汉字占两个字节的空间。一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数,换算为十进制。最小值-128,最大值127。如一个ASCII码就是一个字节。
2、UTF-8编码:一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。中文标点占三个字节,英文标点占一个字节。
3、Unicode编码:一个英文等于两个字节,一个中文(含繁体)等于两个字节。中文标点占两个字节,英文标点占两个字节。
数据类型转换
Java基本类型存储在栈中,因此它们的存取速度要快于存储在堆中的对应包装类的实例对象。从Java5.0(1.5)开始,JAVA虚拟机(Java Virtual Machine)可以完成基本类型和它们对应包装类之间的自动转换。因此我们在赋值、参数传递以及数学运算的时候像使用基本类型一样使用它们的包装类,但这并不意味着你可以通过基本类型调用它们的包装类才具有的方法。另外,所有基本类型(包括void)的包装类都使用了final修饰,因此我们无法继承它们扩展新的类,也无法重写它们的任何方法。
转换规则
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)隐式转换也叫作自动类型转换,由系统自动完成,从存储范围小的类型到存储范围大的类型。显示类型转换也叫作强制类型转换,是从存储范围大的类型到存储范围小的类型。转换过程中可能导致溢出或损失精度,浮点数到整数的转换是通过舍弃小数得到,而不是四舍五入。
// float转int,结果为123 int num1 = (int) 123.45f;
6)如果低级类型为char型,向高级类型(整型)转换时,会转换为对应ASCII码值。
//结果为65 int num4 = 'A';
7)可以利用包装类的各种方法进行类型转换
// String转int int num2 = Integer.parseInt("99"); // String转int方法2 int num3 = Integer.valueOf("88").intValue(); //装箱 Integer i =10; //拆箱 int n = i;
常量
常量在程序运行时是不能被修改的。
1)八进制整型常量:八进制必须以0开头,如0123、034。byte、int、long、和short都可以用十进制、16进制以及8进制的方式来表示。
2)十六进制整型常量:以十六进制表示时,需以0x或0X开头,如0xff、0X9A。
3)长整型:长整型必须以L作结尾,如9L、342L。虽然常量名也可以用小写,但为了便于识别,通常使用大写字母表示常量。
4)浮点数常量:由于小数常量的默认类型是double型,所以float类型的后面一定要加f(F)。同样的,double类型后面加d(D)。
5)字符常量:字符型常量需用两个单引号括起来(注意字符串常量是用两个双引号括起来)。Java中的字符占两个字节。
转义字符
引用类型
在Java中,引用类型的变量非常类似于C/C++的指针。引用类型指向一个对象,指向对象的变量是引用变量。这些变量在声明时被指定为一个特定的类型,比如 Employee、Puppy 等。变量一旦声明后,类型就不能被改变了。所有引用类型的默认值都是null。
在 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4 种,这 4 种引用的强度依次减弱。
1)强引用:Java中默认声明的就是强引用,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了。
2)软引用:软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。
3)弱引用:弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。
4)虚引用:虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。
5)引用队列:引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去。程序可以通过判断引用队列中是否已经加入了引用,来判断被引用的对象是否将要被垃圾回收,这样就可以在对象被回收之前采取一些必要的措施。
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 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);
==与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
字符串常用方法
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适用多线程下在字符缓冲区进行大量操作的情况
+与append
字符串的+操作其本质是创建了StringBuilder对象(jdk4之前使用StringBuffer)进行append操作,然后将拼接后的StringBuilder对象用toString方法处理成String对象,在非循环下使用+或append进行字符串拼接没有特别大的区别,但是在循环下使用+会创建多个字符串缓存区对象,这时使用append性能会大大提升。
数据类型与Mysql数据类型对应关系
运算符
案例
int a = 10, b = 11, c = 14; // --在后面相当于先a=c,再c=c-1 a = c--; System.out.println("a=" + a);//14 System.out.println("c=" + c);//13 // ++在前面相当于a=a+1,在a=b b = ++a; System.out.println("a=" + a);//15 System.out.println("b=" + b);//15
&和&&的区别
&&称为短路运算,如果&&左边的表达式的值是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); } }
面向对象
访问权限修饰符
final在不同位置的不同含义
1、final:最终的;可以修饰变量,方法,类
2、可以防止继承的发生;可以防止方法被子类重写;可以防止值被更改;
3、有点类似C#中:sealed(密封的)
案例:
// 防止继承的发生 public final class Demo07 { // 要养成赋初值的习惯,因为要在各个平台跑程序,以免报错 // 防止值被更改 // 有final修饰的变量为常量,必须有值 final int a = 0; // 防止方法被子类重写 final public void eat() { System.out.println("吃饭"); } }
重载与重写
重载:类的同一功能的多种实现方式
覆盖(方法的重写):子类方法不能缩小父类方法的访问范围
重载规则
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; public void start(); public void stop(); }
抽象类与接口异同
不同
抽象类:
1、抽象类中可以定义构造器
2、可以有抽象方法和具体方法
3、接口中的成员全都是public的
4、抽象类中可以定义成员变量
5、有抽象方法的类必须被声明为抽象类,而抽象类未必要有抽象方法
6、抽象类中可以包含静态方法
7、一个类只能继承一个抽象类
接口:
1、接口中不能定义构造器
2、方法全部都是抽象方法
3、抽象类中的成员可以是 private、默认、protected、public
4、接口中定义的成员变量实际上都是常量
5、接口中不能有静态方法
6、一个类可 以实现多个接口
相同:
1、不能够实例化
2、可以将抽象类和接口类型作为引用类型
3、一个类如果继承了某个抽象类或者实现了某个接口都需要对其中的抽象方法全部进行实现,否则该
类仍然需要被声明为抽象类
异常处理
异常分类
异常父类: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); } }