部分内容参考自JavaGuide Java基础以及JavaGuide IO模型
文章目录
一 Java语言基础
语言特点
- 简单易学
- 面向对象
- 平台无关性
- 支持多线程
- 可靠性
- 安全性
- 支持分布式
- 支持网络编程并且很方便
- 编译与解释并存
JVM vs JDK vs JRE
-
JVM
运行 Java 字节码(.class文件)的虚拟机,实现一次编译、随处可以运行
-
JDK
Java开发工具,拥有 JRE 所拥有的一切,还有编译器(javac)和工具(如 javadoc 和 jdb),能够创建和编译程序
-
JRE
Java 运行时环境,运行已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(JVM),Java 类库,java 命令和其他的一些基础构件,但是不能用于创建新程序
Java 程序从源代码到运行流程图
- javac Main.java
- java Main
Java 和 C++的区别
- 都是面向对象的语言,都支持封装、继承和多态
- Java 不提供指针来直接访问内存,程序内存更加安全
- Java 的类是单继承的,C++ 支持多重继承;虽然 Java 的类不可以多继承,但是接口可以多继承
- Java 有自动内存管理垃圾回收机制(GC),不需要程序员手动释放无用内存
- C ++同时支持方法重载和操作符重载,但是 Java 只支持方法重载
基本数据类型
基本类型 | 位数 | 字节 | 默认值 | 包装类型 |
---|---|---|---|---|
int | 32 | 4 | 0 | Integer |
short | 16 | 2 | 0 | Short |
long | 64 | 8 | 0L | Long |
byte | 8 | 1 | 0 | Byte |
char | 16 | 2 | ‘u0000’ | Character |
float | 32 | 4 | 0f | Float |
double | 64 | 8 | 0d | Double |
boolean | 1 | false | Boolean |
Java 里使用 long
类型的数据一定要在数值后面加上 L,否则将作为整型解析
包装类型不赋值就是 Null
,而基本类型有默认值且不是 Null
数值型数据类型由低到高排列:
byte short char int long float double
- 自动类型转换:级别低->级别高
- 强制类型转换:级别高->级别低
自动装箱与拆箱
- 装箱:将基本类型用它们对应的引用类型包装起来
- 拆箱:将包装类型转换为基本数据类型
Java 基本类型的包装类的大部分都实现了常量池技术。Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在[0,127]范围的缓存数据,Boolean
直接返回 True
Or False
,两种浮点数类型的包装类 Float
,Double
并没有实现常量池技术
所有整型包装类对象之间值的比较,全部使用 equals 方法比较
注释
- 单行注释://
- 多行注释:/* */
- 文档注释:/** */
标识符与关键字
标识符规则
- 标识符由字母、数字、下划线和美元符号组成的字符串
- 第一个字符必须为:字母、下划线或$
- 标识符不能是关键字。
- 标识符不能是true、false、null
- 标识符可以有任何长度
关键字
访问控制 | private | protected | public | ||||
---|---|---|---|---|---|---|---|
类,方法和变量修饰符 | abstract | class | extends | final | implements | interface | native |
new | static | strictfp | synchronized | transient | volatile | ||
程序控制 | break | continue | return | do | while | if | else |
for | instanceof | switch | case | default | |||
错误处理 | try | catch | throw | throws | finally | ||
包相关 | import | package | |||||
基本类型 | boolean | byte | char | double | float | int | long |
short | null | true | false | ||||
变量引用 | super | this | void | ||||
保留字 | goto | const |
变量与常量
按照习惯,变量名用小写字母。如果一个名字由多个单词构成,则除第一个单词外,其它单词首字母大写
按照习惯,定名常量用大写字母,名字由多个单词组成时,用下划线连接,另外用final修饰
命令行输入输出
-
输出
System.out.print(); System.out.printf(); System.out.println();
-
输入
Scanner scanner=new Scanner(System.in); scanner.nextInt(); scanner.nextDouble(); scanner.next(); scanner.nextLine();
运算符与表达式
自增自减运算:运算符放在变量之前时(前缀),先自增/减,再赋值;运算符放在变量之后时(后缀),先赋值,再自增/减
数组
//定义
int[] array=new int[10];
//初始化,二选一
int[] array2={1,2,3};
int[] array3=new int[]{1,2,3};
//长度
int length=array.length;
//遍历,二选一
for(int i=0;i<array.length;i++) {}
for(int i:array) {}
//升序排序
Arrays.sort(array);
//查找key在升序数组首次出现的下标
int index=Arrays.binarySearch(array,key);
//判断两个数组对应元素是否相等
boolean flag=Arrays.equals(array,array2);
字符串
String类的对象是不可变对象, 即它们的值在对象创建后就不能改变了
//定义,三选一
String s1=new String("hello");
String s2="hello";
char[] charArray=['h','e'];
String s3=new String(charArray);
//拼接
String s4="hello"+"java";
//长度
int length=s1.length();
//比较是否相等
boolean flag=s1.equals(s2);
//截取子串
String s5=s1.subString(0,1);
//获取index下标的字符
char a=s1.charAt(0);
//获取str第一次出现位置
int index=s1.indexOf("he");
//去掉首尾空格
String s6=s1.trim();
String StringBuffer 和 StringBuilder 的区别是什么? String 为什么是不可变的?
-
可变性
String
类中使用 final 关键字修饰字符数组来保存字符串,是不可变的;StringBuilder
与StringBuffer
都继承自AbstractStringBuilder
类,在AbstractStringBuilder
中也是使用字符数组保存字符串char[]value
但是没有用final
关键字修饰,所以这两种对象都是可变的 -
线程安全性
String
中的对象是不可变的,也就可以理解为常量,线程安全;StringBuffer
对方法加了同步锁或者对调用的方法加了同步锁,所以是线程安全的。StringBuilder
并没有对方法进行加同步锁,所以是非线程安全的 -
性能
每次对
String
类型进行改变的时候,都会生成一个新的String
对象,然后将指针指向新的String
对象。StringBuffer
每次都会对StringBuffer
对象本身进行操作,而不是生成新的对象并改变对象引用。相同情况下使用StringBuilder
相比使用StringBuffer
仅能获得 10%~15% 左右的性能提升,但却要冒多线程不安全的风险
对于三者使用的总结:
- 操作少量的数据: 适用
String
- 单线程操作字符串缓冲区下操作大量数据: 适用
StringBuilder
- 多线程操作字符串缓冲区下操作大量数据: 适用
StringBuffer
二 Java面向对象
面向对象与面向过程区别
-
面向过程性能比面向对象高
-
面向对象易维护、易复用、易扩展
面向对象特性
-
封装
通过抽象找出具体实例中的共同特征(数据和操作), 将数据和对数据的操作封装在一起
-
继承
子类可以继承父类的数据及数据操作, 同时又可以扩展子类独有的数据及数据操作
-
多态
基于重载实现的静态多态:多个操作具有相同名称, 但是接收不同的消息类型
基于继承和覆盖实现的动态多态:同一操作被不同类型的对象所调用产生不同的行为
类的定义
public class Student {
//成员变量、数据域
public String name;
public int age;
//成员方法
public void learn(String course) {
System.out.println("learn "+course);
}
}
成员变量与局部变量区别
- 从语法形式上看,成员变量是属于类的,而局部变量是在代码块或方法中定义的变量或是方法的参数;成员变量可以被
public
,private
,static
等修饰符所修饰,而局部变量不能被访问控制修饰符及static
所修饰;但是,成员变量和局部变量都能被final
所修饰。 - 从变量在内存中的存储方式来看,如果成员变量是使用
static
修饰的,那么这个成员变量是属于类的,如果没有使用static
修饰,这个成员变量是属于实例的。而对象存在于堆内存,局部变量则存在于栈内存。 - 从变量在内存中的生存时间上看,成员变量是对象的一部分,它随着对象的创建而存在,而局部变量随着方法的调用而自动消失。
- 从变量是否有默认值来看,成员变量如果没有被赋初,则会自动以类型的默认值而赋值(一种情况例外:被
final
修饰的成员变量也必须显式地赋值),而局部变量则不会自动赋值。
构造方法
- 作用:对数据域进行初始化
- 方法名与类名相同
- 没有返回类型,没有void
- 没有明确声明构造方法时, 隐含声明一个方法体为空的无参构造方法,称为“默认构造方法”
- 一个类可以有多个构造方法, 通过方法重载(overload)实现,即多个构造方法的形式参数的个数或类型必须不同
public class Student {
//构造方法
public Student() {
System.out.println("init");
}
public Student(String name) {
System.out.println(name);
}
}
创建对象
//new 构造方法名(参数)
Student s1=new Student();
Student s2=new Student("java");
对象实例在堆内存中,对象引用存放在栈内存中,对象引用指向对象实例
使用对象
s1.name;//使用成员变量
s1.learn("java");//使用成员方法
可变参数
允许方法接受个数不定的参数,必须位于形参的最后一项,在方法中以数组形式调用
int max(int... numbers) {
int res=0;
for(int i:numbers) {
if(i>res) {
res=x;
}
}
return res;
}
包
package demo;//声明类所在的包
import java.util.Scanner;//导入包
访问权限
-
public
公共范围
-
private
私有范围,即类的内部
-
protected
保护范围,即类的内部、子类、同一包其它类
-
缺省
包范围
数据域封装
- 用private修饰数据域
- 为每个数据域定义访问器方法和修改器方法
private String name;
public void setName(String name) {
this.name=name;
}
public String getName() {
return name;
}
this关键字
-
构造方法第一句使用,调用本类的其他构造方法
public Student() { this("java"); } public Student(String name) {}
-
构造方法和实例方法中使用,代表对当前对象的引用
private String name; public Student(String name) { this.name=name; }
实例成员与静态成员
-
实例成员:引用变量.实例成员
-
静态成员:类名.静态成员
初始化器
在类中用{}定义
-
静态
初始化静态变量,用static修饰,类首次加载到内存时执行
-
实例
初始化实例变量,无修饰关键字,创建对象时执行
对于定义在它之后的静态变量,可以赋值,但是不能访问
static关键字
static 关键字主要有以下四种使用场景:
- 修饰成员变量和成员方法: 被 static 修饰的成员属于类,不属于单个这个类的某个对象,被类中所有对象共享,可以并且建议通过类名调用。被 static 声明的成员变量属于静态成员变量,静态变量 存放在 Java 内存区域的方法区。调用格式:
类名.静态变量名
类名.静态方法名()
- 静态代码块: 静态代码块定义在类中方法外, 静态代码块在非静态代码块之前执行(静态代码块—>非静态代码块—>构造方法)。 该类不管创建多少对象,静态代码块只执行一次.
- 静态内部类(static 修饰类的话只能修饰内部类): 静态内部类与非静态内部类之间存在一个最大的区别: 非静态内部类在编译完成之后会隐含地保存着一个引用,该引用是指向创建它的外围类,但是静态内部类却没有。没有这个引用就意味着:1. 它的创建是不需要依赖外围类的创建。2. 它不能使用任何外围类的非 static 成员变量和方法。
- 静态导包(用来导入类中的静态资源,1.5 之后的新特性): 格式为:
import static
这两个关键字连用可以指定导入某个类中的指定静态资源,并且不需要使用类名调用类中静态成员,可以直接使用类中静态成员变量和成员方法。
方法重载overload
发生在同一个类中(或者父类和子类之间),方法名必须相同,参数类型不同、个数不同、顺序不同,方法返回值和访问修饰符可以不同
public class Student {
public void learn(String course) {}
public void learn(int a) {}
public void learn(int a,int b) {}
}
继承
public class Person {}
public class Student extends Person {}
-
一个子类只能继承一个父类
-
继承数据域和方法
子类与父类在同一包中,继承:public, protected, 默认
子类与父类在不同包中, 继承:public, protected
不继承构造方法、private成员
-
子类构造方法第一句必须调用父类构造方法,前提是没有this语句
super();//若子类构造方法没有显式调用父类构造方法,默认第一句为super(); super(参数);
方法覆盖/重写override
子类对父类的允许访问的方法的实现过程进行重新编写
- 返回值类型、方法名、参数列表必须相同,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类。
- 如果父类方法访问修饰符为
private/final/static
则子类就不能重写该方法,但是被 static 修饰的方法能够被再次声明。 - 构造方法无法被重写
public class Person {
public void learn() {}
public static void eat() {}
}
public class Student extends Person {
@Override
public void learn() {
System.out.println("learning");
}
public static void eat() {
System.out.println("eating");
}
}
子类访问父类实例成员
- super.方法名(参数)
- super.成员变量
final关键字
- final类不能被继承,final 类中的所有成员方法都会被隐式的指定为 final 方法
- final方法不能被重写
- final 修饰的变量是常量,如果是基本数据类型的变量,则其数值一旦在初始化之后便不能更改;如果是引用类型的变量,则在对其初始化之后便不能让其指向另一个对象
重写Object类方法
- toString()
- equals()
- hashCode()
public class Account {
private String account;
private String name;
private double outstandingOfDeposits;//存款余额
@Override
public String toString() {
return "["+account+","+name+"]-"+outstandingOfDeposits;
}
@Override
public boolean equals(Object obj) {
if(this==obj) {
return true;
}
if(obj==null) {
return false;
}
if(!(obj instanceof Account)) {//判断obj是否Account的实例或其子类的实例
return false;
}
Account other=(Account)obj;//向下强制类型转换
if(!this.getAccount().equals(other.getAccount())) {
return false;
}
return true;
}
@Override
public int hashCode() {
return account.hashCode();
}
}
==和 equals 的区别
对于基本数据类型来说,==比较的是值。对于引用数据类型来说,==比较的是对象的内存地址
equals()
作用不能用于判断基本数据类型的变量,只能用来判断两个对象是否相等
equals()
方法存在两种使用情况:
- 类没有覆盖
equals()
方法 :通过equals()
比较该类的两个对象时,等价于通过“==”比较这两个对象,使用的默认是Object
类equals()
方法。 - 类覆盖了
equals()
方法 :一般我们都覆盖equals()
方法来比较两个对象中的属性是否相等;若它们的属性相等,则返回 true(即,认为这两个对象相等)。
为什么重写 equals
时必须重写 hashCode
方法
hashCode()
的作用是获取哈希码,也称为散列码;它实际上是返回一个 int 整数。这个哈希码的作用是确定该对象在哈希表中的索引位置
当你把对象加入 HashSet
时,HashSet
会先计算对象的 hashcode 值来判断对象加入的位置,同时也会与其他已经加入的对象的 hashcode 值作比较,如果没有相符的 hashcode,HashSet
会假设对象没有重复出现。但是如果发现有相同 hashcode 值的对象,这时会调用 equals()
方法来检查 hashcode 相等的对象是否真的相同。如果两者相同,HashSet
就不会让其加入操作成功。如果不同的话,就会重新散列到其他位置。这样我们就大大减少了 equals 的次数,相应就大大提高了执行速度
如果两个对象相等,则 hashcode 一定也是相同的。两个对象相等,对两个对象分别调用 equals 方法都返回 true。但是,两个对象有相同的 hashcode 值,它们也不一定是相等的 。因此,equals 方法被覆盖过,则 hashCode
方法也必须被覆盖
对象类型转换
-
向上类型转换
父类类型的对象引用可以引用一个父类对象或者任何的子类对象
Object obj=new Student();
-
向下强制类型转换
把父类类型强制转换为子类类型
Object obj=new Student(); Student stu=(Student)obj;
instanceof
对象 instanceof 类
如果对象的类是后面的类或其子类,返回true;否则返回false
也可以用对象.getClass()==类.class
抽象类
- 用abstract修饰,不能用final修饰
- 不能用new创建对象
- 构造方法不能是抽象的
public abstract class Main {
//抽象方法用abstract修饰,没有方法体
public abstract void get();
}
接口
- 包括静态常量、抽象方法、静态方法、默认方法
- 不能用new创建对象
- 一个类可以实现多个接口
- 一个接口可以继承多个父接口
public interface MyInterface {
public static final int MAX=100;
public abstract void m1();
public static void m2() {}
public default void m3() {}
}
public class Main implements MyInterface {
@Override
public void m1() {}
}
使用接口实现对象比较
-
Comparable
public class Product implements Comparable<Product> { private String id; private String name; //重写compareTo方法,比较依据是id @Override public int compareTo(Product other) { return this.id.compareTo(other.id); } }
-
Comparator
public class ProductNameComparator implements Comparator<Product> { //重写compare方法,比较依据是name @Override public int compare(Product p1, Product p2) { return p1.getName().compareTo(p2.getName()); } }
对象复制
- 实现Cloneable接口
- 重写clone, equals, hashCode方法
浅克隆
对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝
public class House implements Cloneable {
private int id;
private double area;
private Date whenBuilt;
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
return null;
}
}
}
深克隆
对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容
public class House implements Cloneable {
private int id;
private double area;
private Date whenBuilt;
public House(int id, double area) {
this.id = id;
this.area = area;
this.whenBuilt = new Date();
}
@Override
public Object clone() {
try {
House t = (House) super.clone();
t.whenBuilt = (Date) this.whenBuilt.clone();
return t;
} catch (CloneNotSupportedException e) {
return null;
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
House other = (House) obj;
if (id != other.id)
return false;
return true;
}
}
House h1=new House(1,137.00);
House h2=(House)h1.clone();