Java基础1.0
Java基础
Java概述
编程
编程是让计算机解决某个问题而是用的某种程序设计语言编写程序代码并获得结果的过程。人们把解决问题的思路、方法、手段以计算机理解的形式告诉计算机,使得计算机能够按照指令完成特定的任务,这种人机交互的过程视为编程。
Java概述
Java是面向对象的程序设计语言,具有功能强大和简单易用的特点。
jdk1.5后的三大版本
- Java SE(J2SE标准版):允许开发和部署到桌面、服务器、嵌入式环境和实时环境使用的Java应用程序,Java SE支持Java Web服务开发的类,并为Java EE和Java ME提供基础。
- Java EE(J2EE企业版):帮助开发和部署健壮的、可移植、可伸缩且安全的服务器端Java应用程序。Java EE是在Java SE的基础上构建的,提供Web服务、组件模型、管理和通信API,可以用来实现企业级的面向服务体系结构(SOA)和Web2.0应用程序。
- Java ME(J2ME微型版):在移动设备和嵌入式运行的应用程序提供一个健壮且灵活的环境。基于Java ME规范的应用程序只需编写一次就可以应用于血多设备。
JVM、JRE和JDK的关系
- JVM:Java虚拟机,用于运行Java程序,不同平台有不同的虚拟机。
- JRE:包括JVM和Java程序所需的核心类库等,核心类库主要是java.lang,包含了程序必不可少的系统类,如数据类型、基本数学函数、数学运算、异常等,只要有JRE就能运行一个开发好了的java程序。
- JDK:包含Java开发工具和JRE。开发工具包括:编译工具(javac.exe)和打包工具(jar.exe)
跨平台性
- 跨平台性是指:Java只要编译一次就可以在多个平台运行
- 实现原理:Java程序通过虚拟机运行,只要平台上搭建了java虚拟机,就可以在平台上运行Java程序。
Java语言的特点
- 简单易用
- 面向对象(封装、继承、多态)
- 平台无关
- 支持网络编程
- 支持多线程(同一时间并行执行多个任务)
- 健壮性(强转、异常、垃圾回收)
- 安全性
字节码
字节码: Java文件通过编译生成的class文件是字节码文件。
字节码的好处: 字节码不专门对应某种机器,使Java程序可移植,一定程度上提高了Java程序的效率。
Java程序运行过程:
Java源代码---->编译器---->jvm可执行的Java字节码(即虚拟指令)---->jvm---->jvm中解释器----->机器可执行的二进制机器码---->程序运行。
Java的主类,应用程序和小程序的主类
应用程序主类是main方法,小程序主类是继承JApplet或Applet的子类;应用程序的主类不一定是public修饰,但小程序的主类一定是public修饰。
应用程序和小程序的区别
应用程序在主线程启动,小程序嵌套在浏览器页面运行,用init或run方法启动
基础语法
数据类型
定义: Java是强类型语言,每一种数据都定义了数据类型,并且有不同的存储空间。
分类:
- 基本数据类型
- 数值型
1.1 整数类型:byte,short,int,long
1.2 浮点型:float,double - 字符型:char
- 布尔型:boolean
- 引用类型:除了基本数据类型的都是引用类型,如:String, StringBuffer, List, ArrayList, HashSet等
Java基本数据类型图
类型 | 类型名称 | 关键字 | 占用内存 | 取值范围 | 成员变量默认值 |
---|---|---|---|---|---|
整型 | 字节型 | byte | 1字节 | -27 ~ 27-1 | 0 |
整型 | 短整型 | short | 2字节 | -215 ~ 215-1 | 0 |
整型 | 整型 | int | 4字节 | -231 ~ 231-1 | 0 |
整型 | 长整型 | long | 8字节 | -263 ~ 263-1 | 0L |
浮点型 | 单精度类型 | float | 4字节 | -3.403E38 ~ 3.403E38 | 0.0F |
浮点型 | 双精度浮点型 | double | 8字节 | -1.798E308 ~ 1.798E308 | 0.0D |
字符型 | 字符号型 | char | 2字节 | 一个字符 | ‘\u0000’ |
布尔型 | 字节型 | boolean | 1字节 | true 或 false | true |
switch的作用类型
switch(expr)中的expr:
在Java5前,可以是byte,short,int,char
在Java5后,新增了枚举类型enum
在Java8后,新增了字符串类型String
算术题
-
2 << 3
的含义是2*23,2 >> 3
的含义是2/23 -
Math.round(i)
是四舍五入取值 -
float f = 3.4;
定义是为不正确,带小数点数字的定义默认是double类型,double强制转换为float,应为float f = 3.4F;
-
short s1 = 1; s1 = s1 + 1;
不正确,1默认为int类型,s1+1也是int类型,需要强转;short s1 = 1; s1 += 1;
正确,隐含强转。
编码
Java的编码方案为Unicode(标准码)。Unicode为每一个字符定制一个唯一数值,因此在任何平台,程序都可以使用。
访问修饰符
修饰符 | 当前类 | 同包 | 子类 | 其他包 |
---|---|---|---|---|
private | √ | × | × | × |
default or 不写修饰符 | √ | √ | × | × |
protected | √ | √ | √ | × |
public | √ | √ | √ | √ |
注意: private 和 protected 不可以修饰类(外部类)
关键字
goto
goto属于Java的保留字,目前没有使用
final
- final修饰的类不可被继承
- final修饰的方法不可被重写
- final修饰的变量不可被修改
final、finally、finalize区别
- final用于修饰类、方法和变量,特点如上述
- finally作用于try-catch代码块中,无论是否出现异常都会运行fianlly里的代码,一般放关闭资源的方法。
- finalize是Object中的一个方法,一般由垃圾回收器调用,用于判断对象是否可回收。
this
this的三种用法:
- 普通的直接引用,指向当前对象本身;
- 区分形参和成员名字:
class getPerson{
private String name;
public Person(String name){
this.name = name;
}
}
- 引用当前构造函数
class Person{
private String name;
private int age;
public Person(){
}
public Person(String name){
this.name = name;
}
public Person(String name, int age){
this(name);
this.age = age;;
}
}
super
super的三种用法:
- 普通的直接引用,super.xxx表示引用父类的成员
- 在子类中区分父类的成员变量或方法
- 引用父类的构造函数
this和super的区别
- super引用直接父类的方法或参数
- this代表当前对象名
- super()和this()类似,super()调用父类的构造函数,this()调用本类的其他构造函数。
- super()和this()都要放在构造函数的第一行
- super()和this()不能同时出现在同一个构造函数
- super()和this()不能在static环境使用
- 严格来说,this是指向本对象的指针,super是关键字
static
意义
- 创建独立于对象外的变量和方法,以至于即使没有创建对象也能使用其中的属性和方法。
- 形成静态代码块提高系统性能,因为被static修饰的代码块只会在类加载的时候执行一次。
特点
- 被static修饰的变量被类的实例对象共享
- 只在第一次类加载的时候被初始化一次
- 只在类加载的时候分配空间,创建类对象时候不需要
- 优先于对象存在,即没有创建对象也可以访问static修饰的变量或方法。
面向对象
概述
1. 面向对象和面向过程的区别
- 面向过程:面向过程是具体化、流程化的,解决一个问题需要一步步分析再一步步实现。性能高,主要用于单片机、嵌入式开发、Linux/Unix开发。维护性、复用性、扩展性低。
- 面向对象:面向对象的模型化的,把解决问题的方法抽象成一个对象。易维护、易复用、易扩展,可以设计出低耦合系统,更加易于维护,但性能比面向过程低。
2. 面向对象的特征
- 抽象:把有对象的共同特征总结出来构造类的过程。
- 封装:把对象属性私有化
- 继承:增加新的数据或功能
- 多态:程序定义的引用变量在程序运行的时候才能确定指向的实例对象和确定引用方法。
3. Java面向对象编程特征
- 封装:隐藏对象的属性和实现细节,只对外提供公共的访问接口
- 继承:子类继承父类
- 多态:重载(实现接口并覆盖接口的同一方法,编译时的多态,前绑定)和重写(子类重写父类方法,运行时的多态,后绑定)
4. 面向对象五大原则
- 单一职责原则:一个类只有一个功能
- 开放封闭原则:对类的扩展开放,对类的修改封闭
- 里式替换原则:父类可以实现的功能,调用子类也能实现
- 依赖倒置原则:高级模块不能依赖低级模块,而是依赖抽象,功能实现也要依赖抽象
- 接口分离原则:对于不同的用户需求可以设置不同接口
5. 重载和重写的区别
重载:发生在同一类中,方法名相同,参数列表不同,与返回值和访问修饰符无关。
重写:发生在父子类中,方法名、参数列表不需相同,返回时小于等于父类,异常小于等于父类,访问修饰符大于等于父类(里式替换原则);如果父类访问修饰符为private则子类不是重写。
类与接口
抽象类与接口
抽象类是对子类行为的抽象,接口是抽象方法的集合。从设计上来说,抽象类是一种模板设计,接口是行为的规范。
相同点:
- 接口和抽象类都不能被实例化
- 位于继承的顶端,用于继承和实现
- 都包含抽象方法,子类必须覆盖这些抽象方法
不同点:
参数 | 抽象类 | 接口 |
---|---|---|
声明 | 用abstract声明 | 用interface声明 |
实现 | 用extend继承 | 用implement实现 |
构造器 | 有 | 无 |
访问修饰符 | 任意 | 不能用protected和private |
多继承 | 只允许继承一个抽象类 | 可以实现多个接口 |
字段声明 | 任意 | 默认是static和final修饰 |
选择原则
- 行为模型总是通过接口而不是抽象类,因此优先考虑接口
- 选择抽象类的情况:需要定义子类行为又要提供通用功能。
对象实例和对象引用
关键字new创建对象实例,对象引用指向对象实例。一个对象引用可以指向一个或0个对象实例,一个对象实例可以被n个对象引用指向。
变量和方法
成员变量和局部变量的区别
变量: 在程序执行过程中,在某个范围内可以改变的量。变量本质上是内存中的一小块区域。
成员变量: 类内、方法外的变量
局部变量: 方法内的变量
二者区别
成员变量 | 局部变量 | |
---|---|---|
作用域 | 整个类有效 | 所在方法有效 |
存储位置 | 存在堆内存中 | 存在栈内存中 |
生命周期 | 随对象存亡而存亡 | 在方法被调用或语句执行出现,方法调用完或语句结束后就自动释放 |
初始值 | 有 | 没有,使用前必须赋值 |
使用原则: 就近原则
定义无参构造方法的作用
Java子类若没有使用super()调用父类有参构造方法,就会默认调用父类的无参构造方法,若父类中没有无参构造方法,编译就会出错。
Java中调用子类的构造方法之前会调用父类的无参构造方法,其原因是?
帮助子类初始化。
构造方法的特性
- 名字与类名相同
- 无返回值,但不能用void修饰
- 生成类的对象时自动执行,无需调用
静态变量和实例变量的区别
- 静态变量:不属于任何实例对象,只属于类;内存只有一份,在类加载过程中,JVM只会为它分配一次内存。
- 实例变量:属于实例对象;对象创建几次就分配几次内存。
静态变量和普通变量的区别
- 静态变量:被所有对象实例共享,内存只有一个副本,仅在类加载时被初始化。
- 非静态变量:某个对象拥有,存在多个副本,创建对象时被初始化,而且各个副本互不影响。
静态方法和实例方法的区别
- 调用方式:静态方法调用可以用
类名.方法名
或者实例名.方法名
;实例方法只能用后者调用。 - 可用变量:静态方法只能调用静态变量和静态方法;实例方法可调用静态成员,也可调用非静态成员。
内部类
含义
将一个类的定义放在另一个类的内部。内部类本身是类的一个属性,与其他属性定义一致。主要分为:成员内部类、局部内部类、匿名内部类和静态内部类
静态内部类
定义在类内部的静态类,用static修饰的内部类
public class Outer{
private static int i = 1;
static class StaticInner{
public void getI(){
System.out.println("i = " + i);
}
}
}
静态内部类可以访问外部类的静态变量,不可访问非静态变量;静态内部类的创建方式外部类.内部类 对象名 = new 外部类.内部类();
,具体如下:
Outer.StaticInner inner = new Outer.StaticInner();
inner.getI();
成员内部类
定义在类内的非静态类
public class Outer{
private static int i = 1;
private int j = 2;
class Inner{
public void getIJ(){
System.out.println("i = " + i);
System.out.println("j = " + j);
}
}
}
成员内部类可以访问外部类的所有变量和方法。成员内部类依赖于外部类的实例,创建实例的方式是外部类.内部类 对象名 = 外部类对象名.new 内部类();
Outer outer = new Outer();
Outer.Inner inner = outer.newInner();
inner.getIJ();
局部内部类
定义在方法内的类
public class Outer{
private static int i = 1;
private int j = 2;
public void function(){
int k = 3;
class Inner{
public void getIJK(){
System.out.println("i = " + i);
System.out.println("j = " + j);
System.out.println("k = " + k);
}
}
Inner inner = new Inner();
inner.getIJK();
}
public void staticFunction(){
int k = 3;
class Inner{
public void getIJK(){
System.out.println("i = " + i);
//System.out.println("j = " + j);
System.out.println("k = " + k);
}
}
Inner inner = new Inner();
inner.getIK();
}
}
局部内部类的非静态类可以访问方法内和类内的全部成员;静态类库访问方法内的成员和类内的静态成员。其定义是在方法内直接用new定义。
匿名内部类
匿名内部类是没有名字的内部类,开发中使用较多,实现方式为new 接口(){ 实现 }
public class Outer{
public void text(final int i) {
new Service(){
public void sayHello() {
for(int j = 1; j <= i; j++) {
System.out.println("This is " + j + " time say hello~");
}
}
}.sayHello();
}
interface Service{
void sayHello();
}
}
匿名内部类特点
- 必须继承内部类或实现一个接口
- 不能定义任何静态变量或静态方法
- 当所在方法的形参需要匿名内部类使用时,必须用final修饰
- 匿名内部类不能是抽象的,必须实现抽象方法
内部类的优点
- 内部类可以访问外部类的一切变量,包括静态和非静态,私有和共有。
- 内部类不被同一包的其他类可见,具有封装性
- 在内部类实现“多继承”,优化了单继承的缺陷
- 匿名内部类方便定义回调
内部类的应用场景
- 多算法场合
- 非面向对象的语句块
- 优化代码,是代码更加灵活和具有扩展性
- 只有外部类会用到这个类的时候
局部内部类和匿名内部类访问局部变量,变量需要加上final的原因?
原因在于生命周期的不一致。局部变量存储在栈区,当方法结束时,一般的局部变量的内存会被释放,但内部类对其的引用仍然存在,内部类调用局部变量时就会出错。
对象相等判断
==和equals
==:基本数据类型是比较值,引用数据类型是比较内存地址
equals:Object的equals方法是等价于==;若equals被重写过则引用类型有可能是比较值。
另外:String的equals的方法 被重写过,比较的是两个方法的值;创建String对象直接赋值时,虚拟机会在常量池中查找有没有值相同的对象,若有则直接赋给值相同的引用
hashCode与equals
1. hashCode的含义
hashCode()的作用是获取哈希吗,也就是散列码,实际上返回的是int类型的值。哈希码的作用是确定对象在哈希表的索引位置。hashCode()定义在java.lang中,意味着任何类都包含hashCode()这个函数。
2. HashSet如何检查重复
把对象加入到HashSet中,程序会检查对象的HashCode是否与当前HashSet中的某个对象的HashCode相等,如果无相等,则允许加入,如果相等,则调用equals方法检查两对象是否真的相等,若相等,则不允许加入。
注意:
- 两个对象相等,其hashcode也是相同的。
- 两个对象有相同的hashcode,二者不一定相等。
- 被equals()覆盖过的方法也会被hashCode()覆盖。
- hashCode()默认对堆上的数对象产生独特值。
3. 对象相等与引用相等
对象相等:两个对象存放的内容相等。
引用相等:两个对象指向的内存地址相等。
值传递
Java方法调用只支持参数的值传递。
- 按值传递:引用的是对象的值的拷贝,不会改变原对象的值
- 按引用传递:引用的是对象的地址,可能会改变原对象的值
Java包
JDK常用的包
- java.lang:系统基础类
- java.io:输入输出流相关包
- java.nio:完善io功能,提高io性能
- java.net:网络相关
- java.util:系统辅助类相关,特别是集合类
- java.sql:数据库操作的类
import java与javax区别
本质上无区别。先前JavaAPI所必须的包是java开头的,javax包作为扩展API使用,随着时间推移,javax逐渐成为了Java API的组成部分。
IO流
IO流的分类
- 按流向分为:输入流、输出流
- 按操作单元分为:字符流、字节流
- 按流的角色分为:节点流、处理流
JavaIO流的40多个类主要由以下四个抽象类派生出来的:
InputStream/Reader:所有输入流的基类,前者是字节流,后者是字符流
OutputStream/Writer:所有输出流的基类,前者是字节流,后者是字符流
- 按操作方式分类
操作方式\操作对象 | 文件操作 | 管道操作 | 数组操作 | 缓冲操作 | 基本数据类型操作 | 对象序列化操作 | 转化控制 | 打印控制 |
---|---|---|---|---|---|---|---|---|
Reader - 字符读入 | FileReader | PipedReader | CharArrayReader | BufferReader | InputStreamReader | |||
Writer - 字符写出 | FileWriter | PipedWriter | CharArrayWriter | BufferWriter | OutputStreamWriter | PrintWriter | ||
InputStream -字节读入 | FileInputStream | PipedInputStream | ByteArrayInputStream | BufferInputStream | DataInputStream | ObjectInputStream | ||
OutputStream -字节写出 | FileOutputStream | PipedOutputStream | ByteArrayOutputStream | BufferOutputStream | DataOutputStream | ObjectOutputStream | PrintStream |
BIO、NIO、AIO区别
- BIO:Block IO 同步阻塞式IO,传统IO,数据的读入写出都是在同一线程内等待完成,特点是模式简单,并发量低。
- NIO:Non IO 同步非阻塞IO,传统IO的升级,客户端和服务端通过Channel(通道)通信,实现了多路复用。支持面向缓冲,基于通道的IO操作方法,与Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种套接字通道的实现,两种套接字通道均可以实现堵塞和非堵塞通信模式。
- AIO:Asynchronous IO 异步IO,是NIO的升级,也叫NIO2,实现了异步非堵塞IO,异步IO基于事务和回调机制,应用操作后会直接返回。
应用: 低负载、低并发的情况下使用BIO提高开发效率和可维护性,高负载、高并发的情况下使用NIO。
反射
反射机制
在程序运行中,可以得到类的所有方法和属性;对于任意一个对象都可以调用它的方法和属性;这种动态获取信息以及动态调用对象的方法的功能称为反射机制。
静态编译: 编译时确定类型,绑定对象
动态编译: 运行时确定类型,绑定对象
反射机制的优缺点
优点:运行期类型判断,动态加载类,提高代码灵活度。
缺点:性能低,效率比直接运行Java程序低,因为反射相当于一系列的解释操作,要通知JVM要做的事情。
反射机制的应用场景
- 反射是框架设计的灵魂,如Spring、Hibernate框架。
- 模块化开发
- 动态代理设计模式
获取反射的3钟方法
- 通过new对象反射
- 通过路径反射
- 通过类名反射
//Student为另一个查找类
//1. 通过new一个对象
Student student = new Student();
Class stu1 = student.getClass();
System.out.println(stu1.getName());
//2. 通过相对路径
Class stu2 = Class.forName("com.java.reflex.Student");
System.out.println(stu2.getMethods().length);
//3. 通过直接获取类
Class stu3 = Student.class;
System.out.println(stu3.getName());
常用API
String相关
1. 字符型常量和字符串常量
- 形式上,字符常量是单引号定义的一个字符,字符串常量是双引号定义的若干字符
- 定义上,字符常量指的是ASCII值,字符串常量字符串存储的地址
- 内存大小上,字符常量占一个字节,字符串常量占若干字节(至少一个字符结束标志)
2. 字符串常量池
字符串常量池在堆内存中, 专门存储字符串,提高内存使用率,避免开辟多块空间存储相同的字符串。在创建字符串时,JVM先在字符串常量池检索是否有与当前字符串值相同的字符串,若有,则返回常量池那个字符串的引用;若无,则开辟空间存储这个字符串,再返回引用。
3. String的特性
//String值定义的源码
private final char value[];
- 不变性:String是只读字符串,对String的任何操作都是创建一个新的对象,再把对象引用回先前的字符串,不变模式的主要作用在于,当多线程共享并频繁访问的时候保证数据的一致性。
- 常量池优化:String创建后,会在字符串常量池进行缓存,当下次创建相同对象时直接返回引用。
- final:使用final来定义String类,因此String不可被继承,提高系统安全性。
4. String对象两种创建方式的区别
-
String str = "hello";
的内存会分配到常量池中; -
String str = new String("world");
内存会分配到堆内存中。
5. String常用的方法
- indexOf():返回指定字符的索引。
- charAt():返回指定索引的字符。
- replace():字符串替换
- trim():消除空格
- split():分割字符串
- getBytes():获取字节码
- length():获取字符串长度
- toLowerCase():字符串全部转化为小写
- toUpperCase():字符串全部转化为大写
- substring():截取字符串
- equals():判断字符串值相等
- reverse():反转字符串
5. 使用HashMap的时候,使用Spring作key的好处
HashMap的实现主要是通过hashcode确定value的存储位置,Spring具有不可变性,所以当String创建时,hashcode会被缓存下来,不需要再次计算,因此比其他数据类型快。
6. String和StringBuffer、StringBuilder的区别是?
可变性(只有String不可变)
- String的value由final修饰,是不可变的;
- StringBuffer和StringBuilder继承AbstractStringBuilder,其value是可变的
线程安全(只有StringBuilder不安全)
- String的对象不可变,线程安全。
- StringBuffer对公共父类的方法添加了同步锁,所以线程安全。
- StringBuilder对公共父类的方法没有添加同步锁,所以线程不安全
性能
StringBuilder > StringBuffer(对对象本身操作) > String(操作需要生成新对象)
总结
操作少量数据使用String,
单线程操作字符串缓冲区的大量数据使用StringBuilder
多线程操作字符串缓冲区的大量数据使用StringBuffer
包装类
自动装箱和拆箱
装箱: 将基本类型使用它们对应的引用类型包装起来
拆箱: 将包装类型转换为基本数据类型
原始类型: byte, short, int, long, float, double, char, boolean
包装类型: Byte,Short,Integer,Long,Float,Double,Character,Boolean
包装类的存在原因:Java为了编程方便引入了基本数据类型,为了把基本数据类型看待为对象,引入了包装类
包装类的比较
对于对象引用的类型(包装类):== 比较的是对象的内存地址
对于基本数据类型:== 是比较值
举例:Integer定义的值在-128~127之间,自动装箱时不会new新的对象,而是直接引用常量池中的Integer对象,超过范围的数值即使相等,判断结果也是false。
Integer a = new Integer(1);
Integer b = new Integer(1);
Integer c = 1;
int d = 1;
System.out.println(a == b); //false,比较内存地址,在堆的不同地方
System.out.println(a == c); //false,比较内存地址,前者在堆,后者在内存池
System.out.println(a == d); //a、c拆箱成int类型,再比较值
System.out.println(c == d);