IDEA快捷键
快捷键 | 效果 |
---|---|
main | public static void main(String[] args){} |
sout | System.out.println(); |
fori | for (int i = 0; i < ; i++){} |
Ctrl+Alt+L | 格式标准化代码 |
Ctrl+/ | 自动添加单行注释 |
右键Generate | 可以生成get和set属性 |
访问修饰符
修饰符 | 范围 | 修饰对象 |
---|---|---|
public | 所有的类可以访问 | 类,接口,变量,方法 |
protected | 同一包内,所有子类 | 变量,方法 |
private | 同一类内 | 变量,方法 |
default | 同一包内 | 类,接口,变量,方法 |
Java编码
ASCII
一字节,共定义了128个字符,只使用后面7位,首位统一为0
Unicode
-
utf-8
-
单个字节编码:与
ASCII
相同,所以Unicode
兼容ASCII
- 对于N(N>1)个字节:前N位都为1,N+1位为0,其余N-1个字节,开头均为10 utf-16
- 用到在学 utf-32
- 用到在学 备注
-
Unicode
是可变长编码,能够对世界上所有字符都编码
内存
Java内存分为堆,栈,方法区。
堆
- 堆中存放被new出来的实例对象,对象中包括:类成员变量,静态标记,方法标记,class标记。
- 堆区被所有线程共享
栈
- 栈中存放正在被调用的方法与方法中的局部变量,用完即销。
- 栈中存放类的变量的引用(堆中实例对象的地址),其实就是main方法中的局部变量。
- 栈中的方法还要有调用者标记(知道是那个对象调用的该非静态变量),this标记。静态变量?
- 每个线程包含一个栈区,是私有的,不能被其他线程访问。
方法区
- 方法区只存放唯一存在的东西
- class区用来存放每个类的class
- 静态区存放每个类的静态成员变量和静态成员方法
- 方法区存放每个类的方法,包括构造函数。
加载过程
- 加载class文件到class区域,同时加载静态成员。(初始化?)
- 调用main方法到栈中,为main方法中的对象的引用(局部变量)开辟内存。
- 在堆中为实例对象开辟内存,并为对象的成员变量进行默认初始化(有初值?),同时加载方法标记,在方法区中创建某类的方法区,并将该方法区的地址传递给方法标记。(静态标记)
- 利用构造函数给成员变量显示初始化。
- 将堆中对象的地址给到栈中的对象的引用。
数据类型加载
基本数据类型
整型 | 大小 | 备注 |
---|---|---|
byte | 1个字节(8位) | 暂无 |
short | 2个字节(16位) | 暂无 |
int | 4个字节(32位) | 暂无 |
long | 8个字节(64位) | 直接赋值时要加上l或L(推荐L) |
浮点型 | 大小 | 备注 |
---|---|---|
float | 4个字节(32位) | 直接赋值时加上f或F |
double | 8个字节(64位) | 暂无 |
字符型 | 大小 | 备注 |
---|---|---|
char | 2个字节(16位) | Unicode编码,单引号赋值 |
布尔型 | 大小 | 备注 |
---|---|---|
boolean | 1位 | 只有ture与false |
引用类型
class
- 当一个类被创建时,引用变量会存放在栈中,被new出来实例对象放在堆中,栈中的引用变量会指向堆中的实例化对象
- 静态方法中,不能调用非静态方法与变量,因为静态变量与方法会首先被加载到一个内存中,而此时非静态变量与方法还没有被加载。
类的静态变量只会加载一次,即只在内存中存一次,后续被new出来的实例,只会在内存中增加非静态变量 - 类中的属性当没有直接初始化,或者没有通过构造函数初始化时,编译器会默认赋值
引用类型是null
,基本类型用默认值,int
是0
,布尔型是false
interface
暂无
enum
enum
类型变量只会在内存中创建一次,即无论有多少实例,地址都相同enum
变量可以使用==
比较,因为enum
只在内存中存一次。当然最好使用equals()
public class Main16 {
public static void main(String[] args) {
Weekday1 d1 = Weekday1.WED;
System.out.println(d1.dayValue);//3
System.out.println(d1); //输出星期三
}
}
enum Weekday1 {
MON(1, "星期一"), TUE(2, "星期二"), WED(3, "星期三"), THU(4, "星期四"), FRI(5, "星期五"), SAT(6, "星期六"), SUN(0, "星期日");
public final int dayValue; //最好用final修饰,否则在有多个实例时,容易出现混乱
private final String chinese;
//用private定义构造函数,增加代码健壮性
private Weekday1(int dayValue, String chinese) {
this.dayValue = dayValue;
this.chinese = chinese;
}
@Override
public String toString() {
return this.chinese;
}
}
类
构造方法
- 构造方法就是实例化时需要用到的方法
Person ming = new Person();
其中new Person()
就是在调用Person
类中的构造方法从而实例化一个对象 - 类中的构造方法可以自定义或者编译器自动添加
- 当用户没有自定义构造方法时,编译器会自动添加默认构造函数。
当用户自定义构造函数时,编译器就不会添加默认构造函数
public Person(){
}
//这是默认构造函数
- 可以同时定义多个构造方法(方法重载?)
编译器通过参数的参数的数量、类型、位置来确定调用哪一个构造方法
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person(String name) {
this.name = name;
this.age = 12;
}
public Person() {
}
}
静态字段、变量
- 使用
static
修饰
静态字段public static int number;
静态方法public static void setNumber(int value) {...}
- 静态字段(方法)属于类,不属于实例,所以只能用
类名.字段
来调用。也可以使用实例化名.字段
来调用,不过,编译器会自动将实例化名
修改为类名
- 静态方法不能调用非静态字段与非静态方法,也不可以使用
this
,因为this
属于实例对象。
当一个类被创建时,引用变量会存放在栈中,实际被new
出来实例对象放在堆中,栈中的引用变量会指向堆中的实例化对象。
静态方法中,不能调用非静态方法与变量,因为静态变量与静态方法会首先被加载到一个内存中,而此时非静态变量与方法还没有被加载。 - 一个类只会创建一次静态变量与方法,而非静态变量与方法会随着实例对象的创建而多次创建。
方法重载
- 一个类中可以存在方法名相同,而各自的参数不同的方法,这叫方法重载
- 主要是用于功能类似的方法,目的是好记,容易调用
int indexOf(int ch){}
int indexOf(String str){}
int indexOf(int ch, int fromIndex){}
int indexOf(String str, int fromIndex){}
可以是参数数量不同,可以是参数类型不同,可以是数量,类型都不同 - 重载的返回值可以相同,也可以不同。通常时相同的。(重载与返回值没有关系)
包装类
- 将基本数据类型包装成引用类型的类
?: Java是面对对象编程的,但是基本数据类型不是引用类型,所有将这八种数据类型封装成引用类型,弥补基本数据类的不足。
基本类型 | 对应的引用类型 |
---|---|
byte | Byte |
int | Integer |
short | Short |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
char | Character |
除了int、char是Integer、Character,其余均是同名,且首字母大写
以Integer为例
1、初始化一个Integer
int i = 100;
Integer n1 = new Integer(i); //不推荐,虽然能用,编译器会有警告,(因为有更简单的方法?)
Integer n1 = Integer.valueOf(i); //一般使用静态方法去创建一个实例
Integer n1 = Inter.valueOf("123"); //也可以直接使用String类型赋值
Integer n1 = i; //Java提供的自动装箱机制,此句等同于Integer n1 = Integer.valueOf(i);
2、valueOf() 源码,更好的理解上述使用静态方法创建实例
- valueOf在类中有多个重载函数,所以可以接受整型和String型
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
public static Integer valueOf(String s) throws NumberFormatException {
return Integer.valueOf(parseInt(s, 10));
}
3、缓存优化,为了节省内存
当使用valueOf()创建一个实例时,若值在缓存值范围内,编译器回自动扫描缓存内是否已经存在该值,若存在则直
接指向该值,而不是新创一个对象。
缓存值:
- boolean:true,false
- byte:-128~127
- char:0~127
- short:-128~127
- int:-128~127
- long:-128~127
4、不变类 (final修饰)
public final class Integer {
private final int value;
}
5、int与Integer转换
- 正常的转换
int i = 100;
Integer n = Integer.valueOf(i);
int x = n.intValue();
-java编译器可以自动帮助我们装箱拆箱
Integer n = 100; // 编译器自动使用Integer.valueOf(int)
int x = n; // 编译器自动使用n.intValue()
常用函数 | 功能 |
---|---|
Integer.valueOf() | 实例化一个对象 |
Integer.parseInt() | 将字符串转换成Int型 |
Integer.toString | 将Int转换为字符型 |
继承
?:当一个类想要使用另一个类的字段与方法时,可以直接继承过来,而不用再次编写。
方法:Java使用extends实现继承
class Person {
public String name;
public int age;
public String getName() {...}
public void setName(String name) {...}
public int getAge() {...}
public void setAge(int age) {...}
}
class Student extends Person {
// 不需要重复name和age字段/方法,
// 只需要定义新增score字段/方法:
public int score;
public int getScore() { … }
public void setScore(int score) { … }
}
- 子类继承父类所有的字段与方法,但对于private修饰的字段与方法,子类只是拥有,无法访问
- 子类不会继承父类的构造方法,只能在子类的构造方法第一行使用
super()
调用。
情况一:父类构造方法无参数时
class Person{
protected String name;
protected int age;
public Person(){}
}
class Student extends Person{
protected int score;
public Student(int score){
super();//调用父类构造函数,可省略,编译器可以自动添加。
this.score = score;
}
}
情况二:父类构造方法有参数时
class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
class Student extends Person {
protected int score;
public Student(String name, int age, int score) {
super(name, age); // 自动调用父类的构造方法
this.score = score;
}
}
多态
覆写: 子类如果定义了一个与父类方法名、参数、返回值一样的函数,那么称为覆写。
备注: 任何一个不同都不能称之为覆写
class Person {
public void run() {
System.out.println("Person.run");
}
}
class Student extends Person {
@Override //可有可无,最好有,因为编译器可以用这个检查覆写是否正确
public void run() {
System.out.println("Student.run");
}
}
子类覆写的访问权限不可以比父类中被覆写的访问权限严格。
向上转型
若Teacher为父类,Student为子类。
一个Teacher的引用类型,可以指向Student的实例
Teacher S = new Student() (值得思考)
Teacher
是父类,Student
是子类,S
是Teacher
类型的,但实例化的是Student
,S
具有Teacher
的一切字段和方法,不拥有Student
中独立于Teacher
的字段与方法,但当Student
覆写了Teacherd
方法时,S
调用的时Student
内覆写的方法。
super
当子类想要调用父类的字段或方法时,可以在子类中使用super
调用父类属性
super.fieldName
super.method()
final
final
修饰的方法不能被覆写;
final
修饰的类不能被继承;
final
修饰的字段必须在创建对象时初始化,随后不可修改。
抽象
- ?:仅仅为了子类的覆写,父类不需要有具体的实现
- 方法:abstract修饰抽象类和抽象方法
abstract class Person {
public abstract void run();
}
不需要具体的代码实现,连{}都不要
- 抽象类无法实例化
- 子类必须覆写抽象类的抽象方法,抽象类的作用更像是为子类提供了一种
规范
接口
- ?:当一个抽象类没有字段,只有抽象方法,那么可以直接将抽象类改写为接口
- 方法: interface类型,子类使用implements继承接口。
1、
- 接口内的所有方法都是public abstract,所以可以忽略不写
- 接口内不可以有字段
interface Person {
void run();
String getName();
}
2、
- 子类只能继承一个父类,但子类可以继承多个接口
class Student implements Person, Hello { // 实现了两个interface
...
}
3、
- 一个接口可以使用extents去继承另一个接口
interface Hello {
void hello();
}
interface Person extends Hello {
void run();
String getName();
}
4、
default
- 接口可以定义default方法,子类没必要一定实现default方法。
- ?:当接口想要增加新的方法时,会涉及打所有子类,使用default方法可以让部分需要的子类覆写
- default修饰的方法也会被子类继承,子类自由决定是否覆写
public class Main22 {
public static void main(String[] args) {
Student6 s6 = new Student6("小李头");
System.out.println(s6.getName()); //输出 小李头
s6.run();//输出 小李头run
}
}
interface Person9{
String getName();
default void run(){
System.out.println(getName()+"run");
}
}
class Student6 implements Person9{
public String name;
public Student6(String name){
this.name = name;
}
@Override
public String getName(){
return name;
}
}
抽象类与接口
抽象类 | 接口 | |
---|---|---|
继承 | 只能用extents继承一个class | 可以使用implements继承多个接口 |
字段 | 可以定义实例字段 | 不能定义字段 |
抽象方法 | 可以定义抽象方法 | 可以定义抽象方法 |
非抽象方法 | 可以定义非抽象方法 | 可以定义default方法 |
异常
定义
Java的异常分为Error
与Exception
- 首先,
Error
与Exception
都是继承自Throwable
Error
表示十分严重的错误,程序无法解决,JVM遇到错误直接终止程序Exception
表示程序运行时的错误, 可以被捕获并处理。
异常关系继承图
捕获异常
-
无需捕获的异常:
Error
以及子类,RuntimeException
以及子类。 -
必须捕获的异常:
Exception
以及子类,但排除子类中的RuntimeException
,这些异常也称为检查异常(Checked Exception
)。 -
异常的捕获使用
try
、catch
、finally
组合
try{
可能会产生异常的代码;
}catch (捕获的异常类型1){
捕获该异常后所做的处理
printStackTrace()可以打印异常栈
}catch (捕获异常的类型2){
捕获该异常后所做的处理
}finally{
可写可不写;
用于做善后处理,例如关闭文件,资源等等;
finally一定会执行(上述代码块包含return也会执行);
}
- 执行完上述的异常捕获后,还会在继续执行后续的代码。(有rutern?)
- catch语句可以多个,(只能捕获一个?)
- 子类的异常应该放在父类的上面,防止异常覆盖
抛出异常
throw
- 用户可以主动抛出异常。
- 创建一个
Exception
实例,使用throw
抛出。(抛出的是一个异常的实例、对象)
1、正常的抛出
void process2(String s) {
if (s==null) {
NullPointerException e = new NullPointerException();
throw e;
}
}
2、一般的写法
void process2(String s) {
if (s==null) {
throw new NullPointerException();
}
}
- 异常信息的改变
1、
当异常改变时,前面的异常会被后面的异常覆盖掉
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace(); //打印异常栈
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException();
}
}
static void process2() {
throw new NullPointerException();
}
}
2、
防止上述覆盖的情况,需要将原始的Exception信息传入到新的Exception中,新的Exception就有原始Exception信息。
public class Main {
public static void main(String[] args) {
try {
process1();
} catch (Exception e) {
e.printStackTrace();
}
}
static void process1() {
try {
process2();
} catch (NullPointerException e) {
throw new IllegalArgumentException(e); //将捕获的到的原Exception信息传入到新的Exception中
}
}
static void process2() {
throw new NullPointerException();
}
}
throws
- 当开发者认为某函数可能会产生某种异常时,可在方法后添加
throws
提醒 - 调用者调用了带有throws的方法,就必须要进行异常捕获。(仅Checked Exception)
public static int parseInt(String s, int radix) throws NumberFormatException {
if (s == null) {
throw new NumberFormatException("null");
}
...
}
自定义异常
- Java允许开发者自己定义异常
- 通常先自定义一个根异常
BaseException
,然后在派生出各种异常子类 BaseException
一般继承RuntimeException
(?)
根异常
public class BaseException extends RuntimeException {
根异常一般需要有多种构造方法,(构造方法的含义)
public BaseException() {
super();
}
public BaseException(String message, Throwable cause) {
super(message, cause);
}
public BaseException(String message) {
super(message);
}
public BaseException(Throwable cause) {
super(cause);
}
}
子类1
public class UserNotFoundException extends BaseException {
}
子类2
public class LoginFailedException extends BaseException {
}
反射
概述
- 当JVM需要加载某个类时,说首先读取对应.class文件,并在内存中创建一个该类的实例Class
- 该Class是由JVM自己创建的,开发人员无法创建,且在内存中唯一
- 反射的目的是为了获得某个实例的信息
Class长这样
public final class Class{
private Class(){}
}
获取Class实例(?)
获取方法 | 代码 |
---|---|
直接通过某个类的静态变量class 获得 | Class cls = String.class |
通过某个类的实例变量的getClass() 方法获取 | String s = "Hello"; Class cls = s.getClass(); |
通过类的完整的名字域Class.getName() 获得 | Class cls = Class.forName("java.lang.String"); |
访问字段
获取方法 | 作用 |
---|---|
Field Class对象.getField(name) | 根据字段名获取public的field(包括父类) |
Field Classl对象.getDeclaredField(name) | 根据字段名获取当前类的某个field(不包括父类) |
Field[] Class对象.getFields() | 获取所有public的field(包括父类) |
Field[] Class对象.getDeclaredFields() | 获取当前类的所有field(不包括父类) |
具体实例
import java.lang.reflect.Field;
public class Main37 {
public static void main(String[] args) throws NoSuchFieldException {
Class stdClass = Student10.class;
System.out.println(stdClass.getField("score")); // public int packge1.Student10.score
System.out.println(stdClass.getField("name")); // public java.lang.String packge1.Person12.name
System.out.println(stdClass.getDeclaredField("grade")); //private int packge1.Student10.grade
System.out.println(stdClass.getDeclaredField("age")); //NoSuchFieldException
for (Field x:stdClass.getFields()){
System.out.println(x);
}
//public int packge1.Student10.score public java.lang.String packge1.Person12.name
for (Field x:stdClass.getDeclaredFields()){
System.out.println(x);
}
//public int packge1.Student10.score, private int packge1.Student10.grade
}
}
class Student10 extends Person12{
public int score;
private int grade;
}
class Person12{
public String name;
private int age;
}
获取的Field拥有一个字段的所有信息,可以通过很多方法来直接获取这些信息
getName()
:返回字段名称,例如,"name"
getType()
:返回字段类型,也是一个Class
实例(?),例如,string.class
getModifiers()
:返回字段的修饰符(int
类型),不同的bit表示不同的含义
具体例子:
public final class String {
private final byte[] value;
}
Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]类型
int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false
获取字段值
import java.lang.reflect.Field;
public class Main39 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Object p = new Person13("xiao ming");
Class c = p.getClass();
Field f = c.getDeclaredField("name");
f.setAccessible(true); // 访问类中private字段值使用,表示一律访问
Object value = f.get(p);
System.out.println(value); //xiao ming
}
}
class Person13{
private String name; //main 无妨访问类的private字段,利用f.setAccessible(true)可以访问private
// public String name;
public Person13(String name){
this.name = name;
}
}
设置字段值
import java.lang.reflect.Field;
public class Main40 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
Person14 p = new Person14("小明"); //小明
System.out.println(p.getName());
Class c = p.getClass();
Field f = c.getDeclaredField("name");
f.setAccessible(true);//针对非public
f.set(p,"小红"); //因为只有一个Class,所以要指明是那个实例对象
System.out.println(p.getName()); //小红
}
}
class Person14{
private String name;
public Person14(String name){
this.name = name;
}
public String getName() {
return name;
}
}
获取方法
方法 | 作用 |
---|---|
Method getMethod(name, Class...) | 获取某个public 的Method (包括父类) |
Method getDeclaredMethod(name, Class...) | 获取当前类的某个Method (不包括父类) |
Method[] getMethods() | 获取所有public 的Method (包括父类) |
Method[] getDeclaredMethods() | 获取当前类的所有Method (不包括父类) |
import org.w3c.dom.ls.LSOutput;
public class Main41 {
public static void main(String[] args) throws NoSuchMethodException {
Class c = Student11.class;
System.out.println(c.getMethod("getScore")); //public java.lang.String packge1.Student11.getScore()
System.out.println(c.getMethod("getNum",String.class)); //带参数String时的用法,public int packge1.Student11.getNum(java.lang.String)
System.out.println(c.getDeclaredMethod("getGrade"));//private java.lang.String packge1.Student11.getGrade()
System.out.println(c.getDeclaredMethod("getColor",int.class)); //参数为int时的用法,private java.lang.String packge1.Student11.getColor(int)
System.out.println(c.getMethod("getName")); //public java.lang.String packge1.Person15.getName()
System.out.println(c.getMethod("getStyle")); //非public 不能利用此方法获得
System.out.println(c.getDeclaredMethod("getStyle")); // 非public,可以用此方法获得
}
}
class Person15{
public String name;
public Person15(String name){
this.name = name;
}
public String getName(){
return "getName()";
}
}
class Student11 extends Person15{
public int age;
public Student11(String name,int age) {
super(name);
this.age = age;
}
public String getScore(){
return "getScore()";
}
private String getGrade(){
return "getGrade()";
}
public int getNum(String name){
return 1;
}
private String getColor(int n){
return "getColor()";
}
public String getStyle(){
return "getStyle()";
}
}
一个Methon
包含一个方法的所有信息
getName()
:返回方法名称,例如:"getScore"
;getReturnType()
:返回方法返回值类型,也是一个Class
实例,例如:String.class
;getParameterTypes()
:返回方法的参数类型,是一个Class
数组,例如:{String.class, int.class}
;getModifiers()
:返回方法的修饰符,它是一个int
,不同的bit
表示不同的含义。
调用方法
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Main1 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
1、调用public方法
String s = "Hello,world";
Method m = String.class.getMethod("substring", int.class); //获取方法
Method m1 = String.class.getMethod("substring", int.class, int.class);//substring的重载函数
String r = (String) m.invoke(s, 6); //调用invoke就是在调用该方法,后面的可变参数要与实际调用的方法一致
String r1 = (String) m1.invoke(s, 0, 5);
System.out.println(r); // world
System.out.println(r1); // Hello
2、调用静态方法 ?静态方法的修饰符
Method m2 = Integer.class.getMethod("parseInt", String.class);
Integer n = (Integer) m2.invoke(null, "1234");//调用静态方法,第一个填写null
System.out.println(n); //1234
3、获取非public函数
Person1 p = new Person1();
Method m3 = Person1.class.getDeclaredMethod("setName", String.class);
m3.setAccessible(true);//对于非public函数,需要使用setAccessible来允许调用,也有可能会失败
m3.invoke(p, "黎明");
System.out.println(p.name); // 黎明
Method m4 = Person1.class.getMethod("hello");
m4.invoke(new Student1());// 多态覆写函数时,Person1的class对象,调用其子类中被覆写的函数。
// 遵循多态原则,总是调用实际类的覆写方法
//Student:hello
}
}
class Person1 {
String name;
private void setName(String name) {
this.name = name;
}
public void hello() {
System.out.println("Person1:hello");
}
}
class Student1 extends Person1 {
@Override
public void hello() {
System.out.println("Student:hello");
}
}
调用构造方法
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Main43 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor cons1 = Integer.class.getConstructor(int.class); //获取构造方法
Integer n1 = (Integer) cons1.newInstance(123); //调用构造方法
System.out.println(n1);
}
}
通过Class实例获取构造方法如下
getConstructor(Class...)
:获取某个public
的Constructor
getDeclaredConstructor(Class...)
:获取某个Constructor
getConstructors()
:获取所有public
的Constructor
getDeclaredConstructors()
:获取所有Constructor
注意:Constructor总是当前类的构造方法,和父类无关。
调用非public
的Constructor
时,必须首先通过setAccessible(true)
设置允许访问。setAccessible(true)
可能会失败。
获取继承关系
public class Main44 {
public static void main(String[] args) {
1、获得父类的Class
Class i = Integer.class;
Class n = i.getSuperclass(); //获得父类的Class
System.out.println(n); //class java.lang.Number
Class o = n.getSuperclass();
System.out.println(o); // class java.lang.Object
System.out.println(o.getSuperclass()); //null
2、获得某个类实现的接口
Class s = Integer.class;
Class[] is = s.getInterfaces(); // 返回当前类直接实现的所有接口,若没有实现任何接口,则返回空数组
for (Class i : is){
System.out.println(i);
// interface java.lang.Comparable
// interface java.lang.constant.Constable
// interface java.lang.constant.ConstantDesc
}
}
}
动态代理
暂时搁置
泛型
泛型就是定义一种模板
例如 ArrayList
,可以看作是一个"可变长"的数组,属于Object
类型
若直接用ArrayList
存储String
、Integer
等类型,会有几个缺点
- 需要强制转换(因为
ArrayList
是object
,输出String
类型的数据需要强制转换) - 不方便,易出错
import java.util.ArrayList;
public class Main50 {
public static void main(String[] args) {
ArrayList a = new ArrayList();
a.add("你好,这是一个测试");
System.out.println(a.get(0)); // 输出:你好,这是一个测试
String s = a.get(0); // 报错java.lang.Object无法转换为java.lang.String
ArrayList<String> s1 = new ArrayList<String>();
s1.add("这也是一个测试");
String s2 = s1.get(0);
System.out.println(s2); // 输出:这也是一个测试
}
}
所以可以使用泛型来约束ArrayList
的类型
ArrayList<T> t = new ArrayList<T>();
,将T表示任意类型- 例如:
ArrayList<String> s = new ArrayList<String>()
,这表示将ArrayList约束成String
类型的
向上转型
Java标准库中的ArrayList<T>
,实现了List<T>
的接口,它可以向上转型为List<T>
Public class ArrayList<T> implements List<T>{
.........................
}
List<T> array = new ArrayList<T>();
不能把ArrayList<Integer>
向上转型为ArrayList<Number>
或List<Number>
// 创建ArrayList<Integer>类型:
ArrayList<Integer> integerList = new ArrayList<Integer>();
// 添加一个Integer:
integerList.add(new Integer(123));
// “向上转型”为ArrayList<Number>:
ArrayList<Number> numberList = integerList;
// 添加一个Float,因为Float也是Number:
numberList.add(new Float(12.34));
// 从ArrayList<Integer>获取索引为1的元素(即添加的Float):
Integer n = integerList.get(1); // ClassCastException!
使用泛型
- 使用
ArrayList
时,如果不定义泛型类型时,泛型类型实际上就是Object - 当我们定义泛型类型
<String>
后,List<T>
的泛型接口变为强类型List<String>
- 编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型
例如List<Number> list = new ArrayList<Number>();
可以改为List<Number> list = new ArrayList<>();
泛型接口
pass
编写泛型
- 实际上就是提供了一种模板,在使用时,可以直接将T表示为自己想要的类型
某个具体例子:
public class Pair {
private String first;
private String last;
public Pair(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
}
改写泛型:
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
- 泛型
<T>
不能用于静态方法(???),需要单独改写"泛型"方法
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public T getLast() { ... }
// 静态泛型方法应该使用其他类型区分,与其他泛型区分,例如<K>
public static <K> Pair<K> create(K first, K last) {
return new Pair<K>(first, last);
}
}
- 编写多个泛型类型
public class Pair<T, K> {
private T first;
private K last;
public Pair(T first, K last) {
this.first = first;
this.last = last;
}
public T getFirst() { ... }
public K getLast() { ... }
}
泛型的擦拭法
- java虚拟机对泛型其实一无所知,所有的工作都是编译器做的
1、这是我们编写的泛型代码,也是编译器看到的代码
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
2、编译器会主动把<T>改为Object,并且根据<T>实现安全的强制转型,
所以虚拟机看到的代码是:
public class Pair {
private Object first;
private Object last;
public Pair(Object first, Object last) {
this.first = first;
this.last = last;
}
public Object getFirst() {
return first;
}
public Object getLast() {
return last;
}
}
3、编写使用泛型的代码,也是编译器看到的代码
Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();
4、虚拟机实际看到的代码,
编译器会把所有的泛型转换成Object给虚拟机执行,在需要转型的时候,会根据实际的<T>转成响应的类型
Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();
- 因为java使用擦拭法,所有在实际使用时,会有局限性
局限一:<T>不能是基本类型,例如:int
因为Object无法持有基本类型(无法强制转换等?)
Person51<int> p = new Person51<int>(1,2); // 无法通过编译
局限二:同一个泛型类的Class相等,都是Object
Person51<String> p1 = new Person51<String>("王","子恒");
Person51<Integer> p2 = new Person51<Integer>(123,456);
System.out.println(c1); // 输出:class packge1.Person51
System.out.println(c2); // 输出:class packge1.Person51
System.out.println(c1 == c2); //输出:true
局限三:无法判断泛型的类型(???)
不存在Person51<String>.class,只有唯一的Person51.class
Person51<Integer> p = new Person51<>(123,456);
Person51<String> p1 = new Person51<>("wang","ziheng");
System.out.println(p.getFirst());
System.out.println(p.getLast());
System.out.println(p instanceof Person51<String>); /* 编译报错,因为擦拭法,根本就不存在
Person51<String>这个类 */
System.out.println(p1 instanceof Person51<Integer>); //编译报错
System.out.println(p1 instanceof Person51<String>); //true, 为什么你可以???
System.out.println(p instanceof Person51<Integer>); //true
System.out.println(p instanceof Person51); //true
}
局限四:不能实例化T类型
因为擦拭法,最终T会变成Object,这样当你想要创捷Pair<String>或者Pair<Integer>的时候,
就会创建Object类型的实例,会被编译器阻止
class Pair<T> {
private T first;
private T last;
public Pair() {
// Compile error:
first = new T(); //无法通过编译
last = new T(); //无法通过编译
}
}
若要实例化T类型,需要专门的Class<T>参数
Pair<String> pair = new Pair<>(String.class);
public class Pair<T> {
private T first;
private T last;
public Pair(Class<T> clazz) {
first = clazz.newInstance();
last = clazz.newInstance();
}
}
- 不恰当的覆写方法
class Person54<T>{
public boolean equals(T t){ //无法通过编译
return true;
}
}
因为编译器擦除过后,T变成Object,会覆盖Object类的方法,编译器会阻止实际上会变成覆写的泛型方法
public class Pair<T> {
public boolean same(T t) {
return this == t;
}
}
换一个方法名,避免覆写
- 泛型的继承
PASS
extends通配符
- 当方法的形参类型时泛型时,泛型实参和泛型形参不匹配时,无法通过编译
class person55<T>{
private T first;
private T last;
public person55(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getLast() {
return last;
}
public void setLast(T last) {
this.last = last;
}
}
1、当实参泛型类型与形参泛型类型一致时,可以正常运行。
person55<Number> p = new person55<>(1,2);
int n = add(p);
System.out.println(n); //输出3
static int add(person55<Number> p ){
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
2、当实参泛型类型和形参泛型类型不一致,无法通过编译
person55<Integer> p = new person55<>(1,2);
int n = add(p); //无法通过编译
System.out.println(n);
static int add(person55<Number> p ){
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
- 使用
extends
通配符,解决参数不一致的问题
1、Person55<? extends Number>使得方法接收所有泛型类型为Number或Number子类的Pair类型
所以除了可以传入Integer类型,还可以传入,double,BigDecimal等Number的子类
person55<Integer> p = new person55<>(1,2);
int n = add(p);
System.out.println(n); //输出3
static int add(person55<? extends Number> p){
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
- 对于
person55<T>
中set
方法的情况
person55<Integer> p = new person55<>(1,2);
int n = add(p);
System.out.println(n); //输出3
static int add(person55<? extends Number> p){
Number first = p.getFirst();
Number last = p.getLast();
p.setFirst(new Integer(first.intValue()+100)); //无法通过编译
p.setLast(new Integer(last.intValue()+100)); // 无法通过编译
return p.getFirst().intValue() + p.getFirst().intValue();
}
方法参数签名setFirst(? extends Number)无法传递任何Number的子类型给setFirst(? extends Number)
原因还在于擦拭法。如果我们传入的p是Pair<Double>,显然它满足参数定义Pair<? extends Number>,然而,Pair<Double>的setFirst()显然无法接受Integer类型
唯一例外 null可以,但是会抛出异常
p.setFirst(null);
p.getFirst().intValue(); java.lang.NullPointerException
- 使用
extends
进行只读限制 (?? add remove 调用了set?)
int sumOfList(List<? extends Integer> list) {
int sum = 0;
for (int i=0; i<list.size(); i++) {
Integer n = list.get(i);
sum = sum + n;
}
return sum;
}
注意到List<? extends Integer>的限制:
- 允许调用get()方法获取Integer的引用;
- 不允许调用set(? extends Integer)方法并传入任何Integer的引用(null除外)。
方法参数类型List<? extends Integer>表明了该方法内部只会读取List的元素,不会修改List的元素(因为无法调用add(? extends Integer)、remove(? extends Integer)这些方法。换句话说,这是一个对参数List<? extends Integer>进行只读的方法
- 使用
extends
进行类型限制
public class Pair<T extends Number> { ... }
可以创建
Pair<Number> p1 = null;
Pair<Integer> p2 = new Pair<>(1, 2);
Pair<Double> p3 = null;
但不可创建非Number
或其子类型
Pair<String> p1 = null; // compile error!
Pair<Object> p2 = null; // compile error!
super通配符
- 与extends相反,super能够接受其本身和父类
class Person56<T>{
private T first;
private T last;
public Person56(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getLast() {
return last;
}
public void setLast(T last) {
this.last = last;
}
}
Person56<Number> p = new Person56<>(1.1,2.2);
Person56<Integer> p1 = new Person56<>(1,2);
setSame(p,100);
setSame(p1,100);
System.out.println(p.getFirst()+","+ p.getLast()); //输出100,100
System.out.println(p1.getFirst()+","+p1.getLast()); //输出100,100
static void setSame(Person56<? super Integer> p, Integer n){
p.setFirst(n);
p.setLast(n);
}
- 对于
getFirst()
方法
方法签名为 ? super Integer getFirst()
但无法使用Integer来接受getFirst()的值,即下列语句会报错
Integer x = p.getFirst();
因为,当使用p是Person<Number>的对象时,Integer无法持有Number的实例
因此,使用<? super Integer>通配符表示:
- 允许调用set(? super Integer)方法传入Integer的引用;
- 不允许调用get()方法获得Integer的引用。
- 唯一例外是可以获取Object的引用:Object o = p.getFirst()。
对比extends和super通配符
<? extends T>
允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外);<? super T>
允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)
PECS原则
Producer Extends Consumer Super
无限通配符
- ? 既不能读,也不能写,只能做一些null判断
static boolean isNull(Pair<?> p) {
return p.getFirst() == null || p.getLast() == null;
}
集合
collection
java.util
包提供了集合类, collection
是除了map
以外所有其他集合类的根接口,java.util
包主要提供了三种
- List:一种有序列表集合
- Set:一种保证没有重复元素的集合
- Map:一种通过键值(key-value)查找的映射集合
Java访问集合通过统一的方式实现:迭代器(Iterator
)
List
List
是一种有序数组,按照顺序存放,每个元素可以通过索引确定位置,索引从0开始
List
接口实现了ArrayList
和LinkList
等实现类
List<E>
接口了几个主要方法:
方法名 | 实现功能 |
---|---|
boolean add(E e) | 在末尾添加一个元素 |
boolean add(int index,E e) | 在指定索引位置添加一个元素 |
E remove(int index) | 删除指定索引的元素 |
boolean remove(Object e) | 删除某个元素 |
E get(int index) | 获取指定索引的元素 |
int size() | 获取链表大小 |
boolean contains(Object o) | 是否包含元素o |
int indexOf(Object o) | 返回某个元素的索引 |
ArrayList和LinkList
功能 | ArrayList | LinkList |
---|---|---|
内部实现 | 数组 | 链表 |
获取指定元素 | 速度很快 | 需要从头开始查找 |
添加元素到末尾 | 速度很快 | 速度很快 |
在指定位置添加/删除 | 需要移动元素 | 不需要移动元素 |
内存占用 | 小 | 大 |
- 通常情况下,优先使用
ArrayList
List
的特点
List
允许添加重复元素List
允许添加null
创建List
- 传统的方法:
List<e> list = new ArrayList<>();
List
的of()
方法:List<Integer> list = List.of(1,2,3);
。(注意此方法不接受null
,会报异常)
遍历List
1、get(int)方法 (不推荐,就看看)
List<String> list = List.of("apple", "pear", "banana");
for (int i=0; i<list.size(); i++) {
String s = list.get(i);
System.out.println(s);
}
2、Iterator;Iterator本身也是一个对象,但它是由List的实例调用iterator()方法的时候创建的
Iterator的两个方法
- boolean hasNext();判断是否有下一个元素
- E next();返回下一个元素
List<String> list = List.of("apple","pear","banana");
for(Iterator<String> it = list.iterator();it.hasNext();){
String s = it.next();
System.out.println(s);
}
3、for each; for each循环本省就是iterator实现访问
List<String> list = List.of("wang","li","zhang");
for(String s:list){
System.out.println(s);
}
List
和Array
的转换
pass
编写equals
方法
pass
Map
Map
是一种键值映射表Map
是一个接口,具体实现类主要是HashMap
Map
的创建:Map<K,V> map = new HashMap<>();
Map
是无序的,即不是按照存放顺序存在Map
中的
方法 | 功能 |
---|---|
put(K key,V value) | 将key 与value 关联,并放在Map 中 |
V get(K key) | 获得指定key 对应的value ,若key 不存在,则返回value |
boolean containsKey(K key) | 查询某个Key 是否存在 |
Map
中不可以存在两个相同的key
,即新存入的(key,value)
会顶掉旧的(key,value)
Map
中可以存在相同的value
遍历Map
1、keySet()方法,包含map中所有key集合
Map<String,Integer> map = new HashMap<>();
map.put("apple",43);
map.put("pear",7878);
map.put("banana",9090);
for(String key:map.keySet()) {
Integer value = map.get(key);
System.out.println(key + ":" + value); //输出 banana:9090 apple:43 pear:7878
}
2、entrySet()方法,包含map中的(key,value)
Map<String,Integer> map = new HashMap<>();
map.put("apple",43);
map.put("pear",7878);
map.put("banana",9090);
for(Map.Entry<String,Integer> entry : map.entrySet()){
String s = entry.getKey();
Integer value = entry.getValue();
System.out.println(s+" "+value); //输出banana 9090 apple 43 pear 7878
}
编写equals和hashcode
pass
使用EnumMap
- 若
key
的对象是enum
类型,可以使用EnumMap
,EnumMap
内部使用的是紧凑的数组存储value
,不由计算hashCode()
,直接定位到内部数组的索引。 - 当key的对象是enum类型的时候,尽量使用EnumMap,不但效率最高,而且没有额外的空间的浪费
import java.time.DayOfWeek;
import java.util.EnumMap;
import java.util.Map;
public class Main59 {
public static void main(String[] args) {
Map<DayOfWeek,String> map = new EnumMap<DayOfWeek, String>(DayOfWeek.class); //???
map.put(DayOfWeek.MONDAY,"星期一");
map.put(DayOfWeek.TUESDAY,"星期二");
map.put(DayOfWeek.WEDNESDAY,"星期三");
map.put(DayOfWeek.THURSDAY,"星期四");
map.put(DayOfWeek.FRIDAY,"星期五");
map.put(DayOfWeek.SATURDAY,"星期六");
map.put(DayOfWeek.SUNDAY,"星期天");
System.out.println(map);
System.out.println(map.get(DayOfWeek.MONDAY));
}
}
TreeMap
TreeMap
是SortedMap
的实现类,SortedMap
是一个接口,继承Map
接口SortedMap
保证遍历时,以key
的顺序来进行排序
import java.util.Map;
import java.util.TreeMap;
public class Main60 {
public static void main(String[] args) {
Map<String,Integer> map = new TreeMap<>();
map.put("apple",1);
map.put("pear",2);
map.put("banana",3);
for (String key: map.keySet()) {
System.out.println(key); //apple banana pear
}
}
}
- 内部Comparable的实现
Pass
使用Properties
读取配置文件
Properties
用于读写配置文件,我们只需要只要使用Properties
读写配置的接口- 配置文件的接口的特点:
Key-Value
一般都是String-String
类型的
Properties
读取配置文件一共有三步:
- 创建
Properties
实例 - 调用
load()
读取文件 - 调用
getProperty()
获取配置文件
1、从文件中读取配置文件
- 这是一个典型的配置文件:'#' 开头的是注释
# setting.properties
last_open_file=/data/hello.txt
auto_save_interval=60
- 从系统文件中读取这个
String f = "setting.properties" //这里表示文件的路径
Properties props = new Properties();
props.load(new java.io.FileInputStream(f)); //load()方法接受一个InputStream实例,它是一个字节流
String filepath = props.getProperty("last_open_file"); //获取键值,当key不存在,返回null
String interval = props.getProperty("auto_save_interval","120") //当key不存在,则返回120
2、从内存读取一个字节流
String settings = "# test" + "\n" + "course = Java" + "\n" + "last_open_date = 2021-04-18-11:25";
ByteArrayInputStream input = new ByteArrayInputStream(settings.getBytes("UTF-8"));
Properties props = new Properties();
props.load(input);
System.out.println(props.getProperty("course")); //Java
System.out.println(props.getProperty("last_open_date")); //2021-04-18-11:25
System.out.println(props.getProperty("last_open_file")); //null
System.out.println(props.getProperty("auto_save","不存在")); //不存在
写入配置文件
- 通过
setProperty()
修改了Properties
实例,可以把配置写入文件,写入配置使用store()
方法
public static void main(String[] args) throws IOException {
Properties props = new Properties();
props.setProperty("url","123");
props.setProperty("learn","Java");
props.store(new FileOutputStream("D:\\test.properties"),"12121212");
}
- 结果如下所示
编码
load(InputStream)
默认用ASCII编码读取字节流,所以会导致读到乱码。- 需要另一个重载方法
load(Reader)
读取
Properties props = new Properties();
props.load(new FileReader("settings.properties", StandardCharsets.UTF_8));
使用set
Set
用于存储不重复的元素集合,有如下方法:
调用名 | 功能 |
---|---|
boolean add(E e) | 将元素e添加至Set<E> |
boolean remove(Object e) | 删除元素e |
boolean contains(Object e) | 判断是否包含元素e |
例:
Set<String> set = new HashSet<>();
System.out.println(set.add("abc"));//输出true
System.out.println(set.add("xyz"));//输出true
System.out.println(set.add("abc"));//输出false ,因为set中已经存在了abc
System.out.println(set.contains("abc"));//输出true
System.out.println(set.contains("xyz"));//输出ture
System.out.println(set.contains("opq"));//输出false,因为set中不存在opq
最长用的Set
的实现类是HashSet
;
Set
接口并不保证有序,而SortedSet
接口则保证元素是有序的:
HashSet
是无序的,因为它实现了Set
接口,并没有实现SortedSet
接口;TreeSet
是有序的,因为它实现了SortedSet
接口。
例:
1、HashSet
Set<String> set = new HashSet<>();
set.add("apple");
set.add("pear");
set.add("banane");
set.add("orange");
System.out.println(set); //[orange, apple, pear, banane]
2、TreeSet
Set<String> set = new TreeSet<>();
set.add("apple");
set.add("pear");
set.add("banane");
set.add("orange");
System.out.println(set); //[apple, banane, orange, pear]
- 与
TreeMap
一样,使用TreeSet
要实现Comparable
接口
使用Queue
- 队列,先进先出
队列接口定义了几个方法:
int size()
:获取队列的长度boolean add(E)/boolean offer(E)
:添加元素到队尾E remove()/E poll()
:获取队首元素并从队列中删除E element()/E peek()
:获取队首元素但并不从队列中删除。
对于同一种功能有两个方法,在调用失败时,行为时不同的
抛出异常 | 返回false或null | |
---|---|---|
添加元素到队尾 | add(E e) | boolean offer(E e) |
取队首元素并删除 | E remove() | E poll() |
取队首元素但不删除 | E element() | E peek() |
使用PriorityQueue
PriorityQueue
和Queue
的区别在于,它的出队顺序与元素的优先级有关,对PriorityQueue
调用remove()
或poll()
方法,返回的总是优先级最高的元素
例:
Queue<String> q = new PriorityQueue<>();
q.offer("apple");
q.add("pear");
q.offer("banana");
System.out.println(q); // [apple, pear, banana] 猜想:还是按照队列的方式存储,只不过在取出的时候,利
// 用比较去除优先级最高的
System.out.println(q.poll()); //apple
System.out.println(q.poll()); //banana
System.out.println(q.poll()); //pear
System.out.println(q.poll()); //null
- 放入
PriorityQueue
的元素,必须实现Comparable
接口,用于比较优先级
pass
使用Deque
- 双端队列,允许两头都进,两头都出
Deque
是一个接口,并且扩展自Queue
方法如下:
调用名 | 功能 |
---|---|
addLast(E e) /offerLast(E e) | 添加元素到队尾 |
E removeFirst() / E pollFirst() | 取队首元素到并删除 |
E getFirst() / E peekFirst() | 取队首元素但不删除 |
addFirst(E e) / offerFirst(E e) | 添加元素到队首 |
E removeLast() /E pollLast() | 取队尾元素并删除 |
E getLast() / E peekLast() | 取队尾元素但不删除 |
Stack
Java中使用Deque
作为Stack
使用
调用方法如下:
push(E)
:把元素压入栈pop()
:把栈顶的元素弹出peek()
:取栈顶元素,但不弹出