目录
JavaSE基础
数据类型
基本数据类型
八种基本数据类型分别为byte、short、int、long、float、double、boolean、char。
数据类型 | 包装类 | 字节长度(单位:Byte) | 默认值 | 缓存池 |
---|---|---|---|---|
byte(字节) | Byte | 1 | 0 | [-128,127] |
short(短整形) | Short | 2 | 0 | [-128,127] |
char(字符型) | Character | 2 | 空字符(\u0000) | [\u0000,\u007F] |
int(整形) | Integer | 4 | 0 | [-128,127] |
long(长整形) | Long | 8 | 0L | [-128,127] |
float(单精度浮点型) | Float | 4 | 0.0f | |
double(双精度浮点型) | Double | 8 | 0.0d | |
boolean(布尔类型) | Boolean | 1 | false |
装箱拆箱
装箱:装箱就是自动将基本数据类型转换为包装器类型;
public class BaseJavaSEApplication { public static void main(String[] args) { // 装箱 Integer integer = Integer.valueOf(99); } }
拆箱:拆箱就是自动将包装器类型转换为基本数据类型;
public class BaseJavaSEApplication { public static void main(String[] args) { // 拆箱 int i = integer.intValue(); } }
自动装箱拆箱
public static void main(String[] args) { int a = 180; Integer b = 180; Integer c = new Integer(180); System.out.println(a == b); System.out.println(a == c); System.out.println(b == c); } 结果: true true false
总结:
-
int与Integer、new Integer()数据相等进行
==
比较时,结果都是true
,因为Integer会自动拆箱为int,比较的数据值而非地址。 -
两个new Integer()的数据
==
比较时则为false。 -
Integer b = 180
编译器会在缓冲池范围内的基本类型自动装箱过程调用 valueOf() 方法,如果数据在缓冲区外则不等,在缓冲区范围内使用的都是缓冲区的数据地址。
数据类型转换
不同类型的数据在运算的时候,会向高精度的数据类型转换。
隐式数据类型转换
隐式传递运算符(+=,-=,*=,/=)。
public static void main(String[] args) { short sum = 0; // 编译错误,1为int类型,需要强转 sum = sum + 1; // 不会出现编译错误,+=会进行隐式转换 sum += 1; }
自动装箱拆箱异常
在开发过程中尽量避免自动装箱拆箱。
案例:自动装箱拆箱导致的空指针异常。
// 三元运算符装箱拆箱导致空指针异常 public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = null; boolean flag = false; int i = a + b; // a+b 的结果是 int 类型,那么 c 会强制拆箱成 int 类型,抛出NullPointerException异常 Integer result=(flag ? i : c); }
引用数据类型
数组
数组就是多个相同类型的数据按照一定的顺序排列的集合。简单理解数组就是一个数据容器。数组是编程中最常见的一种数据结构,可用于存储多个数据,每个数组元素存放一个数据,通常我们可以通过数组元素的索引来访问数组元素。包括为数组元素赋值和取出数组元素的值。
数组的基本特性:
-
数组本身是引用数据类型,而数组中的元素可以是任何数据类型,包括基本数据类型和引用数据类型。
-
创建数组对象会在内存中开辟一整块连续的空间,而数组的引用是这块连续空间的首地址。
-
数组一旦初始化完成,数组在内存所占用的空间将被固定下来,因此数组的长度不可变。
-
数组可以直接通过下标的方式来调用,下标从0开始。
数组声明
数组的声明比较简单,和变量基本一样,只是多了个括号[]
。数组的声明如下:
//推荐 元素的数据类型[] 数组的名称; int[] age; //不推荐 元素的数据类型 数组名[]; int age[];
数组初始化
数组使用之前需要先初始化,什么是数组初始化?就是给数组分配内存空间,并给元素赋值。数组的初始化有两种:静态初始化
和动态初始化
。
静态初始化:定义数组的同时为数组分配内存空间,并赋值。程序员只给定数组元素的初始值,不指定数组长度,由系统决定数组的长度。
数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...}; 或 数据类型[] 数组名; 数组名 = new 数据类型[]{元素1,元素2,元素3...}; 简化方式: 数据类型[] 数组名 = {元素1,元素2,元素3...};//必须在一个语句中完成,不能分开两个语句写
动态初始化:初始化时候程序员只指定数组长度,由系统元素分配初始值(为对应类型的默认值,比如int默认赋值为0)。
数据类型 [ ] 数组名称 = new 数据类型 [数组长度] 或 数据类型 数组名称[ ] = new 数据类型 [数组长度]
正确写法:
//声明一个元素为1,2,3的int型数组 int[] arr=new int[]{1,2,3}; int[] arr1={1,2,3}; //声明一个长度为3的数组 int[] arr2=new int[3];
错误写法:
//未指定数组长度 int [] arr1=new int[]; //数组前面[]不能写长度 int [5] arr2=new int[5]; //静态初始化不能写长度 int [] arr3=new int[3]{1,2,3};
数组访问
数组是存在下标索引的,索引的范围为[0,数组的length-1]
,通过下标可以获取指定位置的元素,数组下标是从0开始的,也就是说下标0对应的就是数组中第1个元素,可以很方便的对数组中的元素进行存取操作。格式为:数组名[索引]
。
public static void main(String[] args) { //声明一个长度为3的数组 int[] arr=new int[3]; //给arr第1个元素赋值1 arr[0]=1; //给arr第2个元素赋值2 arr[1]=2; //输出 System.out.println(arr[0]); System.out.println(arr[1]); }
当访问下标超过数组length-1
时会报ArrayIndexOutOfBoundsException
数组下标越界异常。
数组遍历
将数组中的每个元素分别获取出来,这就是遍历。遍历也是数组操作中的基石,数组有个 length 属性,是记录数组的长度的,我们可以利用length属性来遍历数组。语句为:数组名.length
,返回int类型结果。由次可以推断出,数组的最大索引值为数组名.length-1
。
//声明一个元素为1,2,3的int型数组 int[] arr=new int[]{1,2,3}; //遍历arr数组 for (int i = 0; i < arr.length; i++) { System.out.println(arr[i]); } // 使用增强for循环foreach进行遍历 for (int i : arr) { System.out.println(i); } // 将数组转换成stream流使用Lambda表达式 Arrays.stream(arr).forEach(System.out::println);
接口
接口的英文名称是 interface。接口是属于和类类型同等级别的结构,但是它不是类,却和类类型有着共同点,在接口中可以含有变量、方法。接口使用interface
这个关键字来进行修饰。
接口特性
-
接口:用interface关键字修饰的类。
-
接口中的变量:接口中的变量会被隐式地指定为
public static final
变量,并且只能是public static final变量,用private修饰会报编译错误。 -
接口中的方法:接口中的方法会被隐式地指定为
public abstract
方法,且只能是public abstract方法,如果用其他关键字,如private、protected、static、 final等修饰都会导致报编译错误。 -
接口的实现:使用
implement
关键字。 -
接口没有构造方法,不能创建对象。
-
接口是用来被实现的,其实现类必须重写它的所有抽象方法,除非实现类是个抽象类。
-
接口可以多实现,一个类可以同时实现多个接口。
-
接口可以继承接口,接口之间支持多继承。
-
如果子类(或实现类)继承的父类和实现的接口中声明了同名的成员变量,那么在调用的时候会报错,模糊不清。
-
如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法,那么子类在没有重写该方法的情况下,默认调用父类中的同名同参数的方法。-->类优先原则。
-
如果实现类实现了多个接口(没有继承),而多个接口中定义了同名同参数的默认方法,那么在实现类没有重写此方法的情况下,会报错。-->接口冲突。这就需要我们在实现类中重写此方法。
-
如果需要调用父类中方法或接口中默认方法,父类使用 super . 方法名,接口使用 接口名.super.方法名。
-
如果需要接口中静态方法,接口名.方法名。
版本说明:
-
在JDK1.8之前接口中的变量全都是常量,方法都是抽象方法。
-
在JDK1.8中允许我们在接口中定义static方法和default方法。
-
default方法:必须用
default
关键字修饰,而且可以被实现类重写,它只能通过接口实现类的对象来调用。 -
static方法:只能通过接口名调用,不可以通过实现类的类名或者实现类的对象调用。
-
-
在JDK1.9中允许定义private的方法,用于服务于本接口其他方法。
接口声明格式
【修饰符】 interface 接口名{ //接口的成员 [public static final] 数据类型 变量名;//静态常量,默认修饰符public static final [public abstract] 返回值类型 方法名(形参列表);//抽象方法,默认自带修饰符public abstract default 方法名(形参列表){}//jdk8后,缺省方法,扩展方法,默认public修饰,实现类可以自行选择是否实现此方法 static void testStatic(){}//jdk8后,默认public修饰,通常定义服务于此接口的实现类的一些工具类方法 private void testPrivate(){}//jdk9后出现,服务于本接口其他方法。 }
接口实现格式
接口可以多实现,类只能单继承
修饰符 class 实现类名 implement 接口{ //重写接口中的方法 public 返回值类型 方法名(形参列表){ 方法体 } } 修饰符 class 实现类名 extends 父类 implements 接口名1,接口名2,...{ //重写接口和抽象类中的抽象方法 }
接口实现代码
/** * 定义接口 */ public interface InterfaceDemo { //接口成员变量(前面默认加了public static final) int MAX_VALUES = 1000; //接口抽象方法(前面默认加了public abstract) void abstractMethod(); //接口默认方法(必须加default) default void defaultMethod() { System.out.println("接口default方法..."); } //接口静态方法 static void staticMethod() { System.out.println("接口static方法..."); } } //定义接口的实现类 class InterfaceImpl implements InterfaceDemo { @Override public void abstractMethod() { System.out.println("重写接口中的abstract方法..."); } @Override public void defaultMethod() { System.out.println("重写接口中的default方法..."); } } //定义测试类 class Main { public static void main(String[] args) { InterfaceImpl ifi = new InterfaceImpl(); ifi.abstractMethod(); ifi.defaultMethod(); System.out.println("接口中的常量值:" + InterfaceDemo.MAX_VALUES); InterfaceDemo.staticMethod(); } }
结果:
重写接口中的abstract方法... 重写接口中的default方法... 接口中的常量值:1000 接口static方法...
抽象类
抽象类是用来描述一种类型应该具备的基本特征与功能,而具体如何去完成这些行为则由其子类通过方法重写来完成。在Java面向对象的概念中,我们知道所有的对象都是通过类来描绘的,类是对象的抽象,而对象是类的具体实例。类是抽象的,不占用内存,而对象是具体的,会占用内存空间。但是有时候并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。在Java中用abstract
关键字来修饰的类就是抽象类,当然这个关键字也可以用来修饰方法,表明该方法是抽象方法。
抽象类特性
-
抽象类不能实例化,必须要由继承它的子类来创建实例。
-
抽象方法只有方法的声明,没有方法体。抽象类中的抽象方法必须要在子类中重写。
-
抽象类中既可以有抽象方法,也可以有普通方法,普通方法可以不用重写。
-
抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
-
抽象类中可以有构造方法,是供子类创建对象时,初始化父类成员变量使用的。
-
抽象类的子类,必须重写抽象父类中所有的抽象方法,否则,编译无法通过而报错。除非该子类也是抽象类。
-
只要包含一个抽象方法的类,该类必须要定义成抽象类。
-
abstract不能用来修饰属性、构造器等结构。
-
abstract不能与final并列修饰同一个类。
-
abstract不能与private、static、final或native并列修饰同一个方法。
抽象方法
被abstract
所修饰的方法,它是没有方法体的方法。
抽象方法实现格式
【其他修饰符】 abstract 返回值类型 方法名(【形参列表】);
抽象类实现格式
【权限修饰符】 abstract class 类名{ } 【权限修饰符】 abstract class 类名 extends 父类{ }
接口与抽象类对比
特点 | 抽象类 | 接口 |
---|---|---|
继承限制 | 单继承 | 一个类可以实现多个接口,而且接口也可以继承多个接口 |
成员变量 | 有 | 只能是公共的静态的常量【public static final】(不写默认会加上) |
构造器 | 有 | 无 |
代码块 | 可以有 | 无 |
抽象方法 | 可以有 | 只能是公共的抽象方法【public abstract】 |
静态方法 | 可以有 | JDK1.8之后可以有公共的静态方法 |
默认方法 | 可以有 | JDK1.8之后可以有公共的默认方法,必须用default修饰 |
私有方法 | 可以有 | JDK1.9之后可以有私有方法 |
访问修饰符 | 抽象方法可以有public、protected和default | 接口方法默认修饰符是public,不可以使用其它修饰符 |
相同点 | 都不能直接实例化 | 都不能直接实例化 |
类
内部类
将一个类定义在另一个类里面或者一个方法里面,这样的类称为内部类。内部类拥有类的基本特征(可以继承父类,实现接口)。在我们程序设计中有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决方案变得更加完整。
内部类实现多继承
lass Father { public String handsome() { return "爸爸很帅气"; } } class Mother { public String beautiful() { return "妈妈很漂亮"; } } class Son { //内部类继承了Father类 class MyFather extends Father { //重写父类方法 public String handsome() { return "我遗传了爸爸的帅气"; } } //内部类继承了Mother类 class MyMother extends Mother { //重写父类方法 public String beautiful() { return "我遗传了妈妈的漂亮"; } } } public class Test { public static void main(String[] args) { Son son = new Son(); Son.MyFather myFather = son.new MyFather(); System.out.println(myFather.handsome()); Son.MyMother myMother = son.new MyMother(); System.out.println(myMother.beautiful()); } }
Java中内部类可分为四种:
-
成员内部类
-
局部内部类
-
匿名内部类
-
静态内部类
成员内部类
成员内部类是定义在类中的类。我们可以把成员内部类看成是外部类的一个成员,所以成员内部类可以无条件访问外部类的所有成员属性和成员方法,包括private成员和静态成员。但是外部类要访问内部类的成员属性和方法则需要通过内部类实例来访问。当成员内部类拥有和外部类同名的成员变量或者方法时,会优先访问的是成员内部类的成员,但是我们可以使用外部类 .this(如果有继承可以使用super)来访问外部类的变量和方法。
-
成员内部类中不能存在任何static的变量和方法;
-
成员内部类是依附于外部类的,所以只有先创建了外围类才能够创建内部类(静态内部类除外)
public class OuterClass { private static String name; private String str; private int size = 20; class InnerClass implements Runnable { private int size = 10; @Override public void run() { System.out.println(name); System.out.println(str); // 不用this指定默认调用的也是内部类的size属性 System.out.println(this.size); System.out.println(OuterClass.this.size); } } public static void main(String[] args) { InnerClass innerClass = new OuterClass().new InnerClass(); Thread thread = new Thread(innerClass); thread.start(); System.out.println(); } }
局部内部类
局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内。
class OuterClass { //创建返回一Comparable接口实例的方法 public Comparable getComparable() { //创建一个实现Comparable接口的内部类:局部内部类 class MyComparable implements Comparable { @Override public int compareTo(Object o) { return 0; } } //返回实现Comparable接口的实例 return new MyComparable(); } }
局部内部类特性:
-
局部内部类就像是方法里面的一个局部变量一样,是不能有 public、protected、private 以及 static 修饰符的。
-
在Java8中已经去掉要对final的修饰限制,但其实只要在匿名内部类使用了,该变量还是会自动变为final类型(只能使用,不能赋值)。
内部类在使用局部变量时,局部变量需要用final修饰原因:
-
局部变量和匿名内部类的生命周期不同。
-
匿名内部类是创建后是存储在堆中的。
-
方法中的局部变量是存储在Java栈中,当方法执行完毕后,就进行退栈,同时局部变量也会消失。
-
为了解决这个问题编译器为自动地帮我们在匿名内部类中创建了一个局部变量的备份,也就是说即使方法执结束,匿名内部类中还有一个备份,自然就不怕找不到了。
-
但是问题又来了。如果局部变量中的a不停的在变化。那么岂不是也要让备份的a变量无时无刻的变化。为了保持局部变量与匿名内部类中备份域保持一致。编译器不得不规定死这些局部域必须是常量,一旦赋值不能再发生变化了。所以为什么匿名内部类应用外部方法的域必须是常量域的原因所在了。
匿名内部类
匿名内部类通常用于创建实现某个接口或继承某个类的对象,没有类名。它可以在创建对象的同时定义类的实现,从而简化代码。匿名内部类可以访问外部类的成员变量和方法,但是如果要访问方法中的局部变量,则必须将该变量声明为final。
匿名内部类实用特性:
-
使用匿名内部类时,我们必须是继承一个类或者实现一个接口,但是两者不可兼得,同时也只能继承一个类或者实现一个接口。
-
匿名内部类中是不能定义构造函数的。
-
匿名内部类中不能存在任何的静态成员变量和静态方法。
-
匿名内部类为局部内部类,所以局部内部类的所有限制同样对匿名内部类生效。
-
匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
函数式接口由匿名内部类推出
public static void main(String[] args) { // Runnable匿名内部类 new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); } }).start(); // Lambda表达式,Runnable是函数式接口,接口用@FunctionalInterface注解标明 new Thread(() -> { System.out.println(Thread.currentThread().getName()); }).start(); }
静态内部类
静态内部类是指用static修饰的内部类。我们知道普通类是不允许声明为静态的,只要内部类才可以,被static修饰的内部类它不依赖于外部类的实例。这是因为非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外部类。
静态内部类特性:
-
静态内部类可以不依赖于外部类的实例,但是要注意它们创建对象的区别。
-
静态内部类只能访问外部类的静态变量和静态方法,否则编译会报错。
-
非静态内部类中可以调用外部类的然后成员,不管是静态的还是非静态的。
-
如果需要调用内部类的非静态方法,必须先new一个OuterClass的对象outerClass,然后通过outer。new生成内部类的对象,而static内部类则不需要。
目录