可以把类看作函数的容器,或者表示自定义的数据类型。

修饰符

  1. public:可以修饰类、类方法、类变量、实例变量、实例方法、构造方法,表示可以被外部访问。

  2. private:可以修饰类、类方法、类变量、实例变量、实例方法、构造方法,表示不可以被外部访问,只能在类的内部使用

  3. static:修饰类变量和类方法,也可以修饰内部类

  4. final:修饰类变量,实例变量,表示只能被赋值一次。

  5. protect:可以被同一个包内和子类访问。

private < 默认 < protected < public 字符编码

构造方法

构造方法体现了多态

java
1
2
3
4
5
6
7
public Point() {  //构造器A
this(0, 0);
}
public Point( int x, int y){ //构造器B
this.x = x;
this.y = y;
}

构造方法可以有多个,但是和其他方法不同,构造方法特殊的地方:

  1. 名称是固定的,与类名相同。

  2. 没有返回值,也不能有返回值。构造方法隐含的返回值是实例本身。

构造器A调用了构造器B,其中this必须放在第一行。构造方法是用于初始化对象,如果要调用别的构造方法,就先调用别的,然后再根据自己需求进行更改,而如果自己先初始化了一部分,再调别的, 自己的修改将可能会被覆盖。

java
1
this(0, 0);

默认构造方法

每一个类至少都要有一个构造方法,java编译器会自动生成一个默认的构造方法,也没有具体操作。但是一旦定义了新的构造方法,java就不会再自动生成默认的构造方法。例:如果只声明上述demo中的B构造方法,那么下面语句

java
1
Point p = new Point();

就会报错,因为找不到无参构造器。

私有构造方法

构造方法可以用private修饰,用于以下几种场景:

  1. 不能创建类对象,类只能被静态访问,如Math和Array类的构造方法就是私有的。

  2. 能创建类的对象,但只能被类的静态方法调用,在一些单例场景下,对象是通过静态方法获取的,静态方法来调用私有构造器创建对象,方法中会进行判断,如果已经创建过该对象,就重用这个对象。

  3. 只是用来被其他多个构造器调用,减少重复代码。类似于将重复代码提出成为一个方法。

super

子类可以通过super调用父类的构造方法

java
1
2
3
4
5
6
7
8
9
10
11
12
public class Base {
private int number;
public Base(int number){
this.number = number;
}
}

public class Chile extends Base {
public Child(int number) {
super(number);
}
}

任何语言编译时都有一个相同的问题就是命名冲突,包就是用来解决这一问题。比如String类的完全限定名称就是java.lang.String

java API中所以类和接口都位于java包货javax下,java是标准包,javax是扩展包。

定义类的时候,应该先用关键字package声明其包名,如下:

java
1
2
3
4
package org.service;
public class index {
//类的定义
}

jar包

为了方便使用第三方代码,也是为了方便我们写的代码给其他人使用,程序大多都有打包的概念,将多个编译后的文件打包未一个文件,方便其他程序调用。

在java中,编译后的多个包的java class文件可以打包成一个文件,然后运行一下命令:

plaintext
1
jar -cvf<包名>.jar <最上层包名>

例如对前面的类打包,如果inde.class位于E:\bin\org\service\index.class,则可以到目录E:\bin下然后运行:

plaintext
1
jar -cvf index.jar org

index.jar就是jar包,其实就是一个压缩文件。

封装继承多态

封装

封装就是隐藏实现细节,提供简化接口。封装是程序设计的第一原则。

继承

使用继承一方面可以复用代码,公共的属性和方法可以放在父类中,子类只关心子类所特有的就可以了;另一方面,不同子类的对象可以更为方便的统一处理。一个类只能继承一个父类,但是可以实现多个接口。接口和抽象类

根父类Object

任何类都有一个隐藏父类object,Object没有定义属性,但是定义了一些方法。如toString(),但我们一般会对此方法重写。

重写

在子类中对父类的方法进行重写,调用子类此方法时,是调用子类新重写的方法体,而不是父类的。

java
1
2
3
4
@override
public String toString () {
return "(" + x + "," + "y" + ")";
}

重写和重载

参数签名:参数个数、类型、顺序。

重写 重载
函数名相同 函数名相同
子类重写父类的方法 同一个类内
参数签名要相同 参数签名不同

父子类型转换

向上转型:子类型的对象赋值给父类型的引用变量。

向下转型:父类型的变量赋值给子类型的引用变量。

instanceof可以检测给定的父类变量是不是某子类对象。

java
1
2
3
4
5
6
7
8
Base b = new Child();       //向上转型
Child c = (Child)b; //可以向下转型,因为b本身就是Child类型
System.out.println(b instanceof Child); //true

Base b = new Base(); //声明对象
Child c = (Child)b; //不可以向下转型,因为b本身不是Child类型
System.out.println(b instanceof Child); //false

继承权限

修饰符中,protected表示可以被同一个包中的其他类访问,也可以被子类访问。

子类重写父类方法时,不能降低父类方法的可见性。即父类是public的时候子类只能是public;父类如果是protected,子类可以是protected或者public,即子类只能升级父类方法的可见性。

继承反应的是”is-a”的关系,子类是属于父类的,子类必须能支持父类所有对外的行为。子类减少了父类的可见性,破坏了”is-a”的关系,子类可以增加父类的行为,所以可以提升可见性。

修饰符final可以防止继承。

继承破坏封装

子类和父类直接是细节依赖,子类扩展父类,仅仅知道父类能做什么是不够的,还要知道父类是怎么做的,而且父类的实现细节也不能随意修改,否则可能会影响子类。

如何避免?

  1. 避免使用继承

  2. 正确使用继承

避免使用继承

  1. 使用final关键字

  2. 优先使用组合而非继承。组合即是Child类不继承Base,在类中声明Base调用其方法。

  3. 使用接口和抽象类

多态

父类Shape 子类Circle、Line 子类ArrowLine

变量shape可以引用任何Shape子类类型的对象,这叫多态,即一种类型的变量可以引用多种实际类型的对象。

对于shape变量,他有两个类型:类型Shape称为静态类型;类型Circle、Line、ArrowLine成为动态类型。调用方法的时候,会进行动态绑定。

多态和动态绑定时计算机程序的一种重要思维方式,使得操作对象的程序不需要关注对象的实际类型,从而可以统一处理不同对象,但又能实现对每个对象的特有行为。

静态绑定

若Child类和Parent类都有某同名静态方法和静态变量,那么类外变量访问时要看访问变量的静态类型:静态类型是父类,则访问父类的静态方法和静态变量;如果静态类型是子类,则访问子类的静态方法和变量。看一个例子,这是父类代码:

java
1
2
3
4
5
6
7
public class Base {
public static String s = "父类静态变量";
public String m = "Base";
public static void test(){
System.out.println("base Static:" + s);
}
}

定义了一个public静态变量s,一个public实例变量m,一个静态方法test。这是子类代码:

java
1
2
3
4
5
6
7
public class Child extends Base {
public static String s = "子类静态变量";
public String m = "Child";
public static void test(){
System.out.println("Child Static:" + s);
}
}

子类定义了和父类重名的方法和变量。对于一个子类对象来说,他就有了两份变量和方法,在子类内部访问的时候,访问的都是子类的方法和变量,用super来访问父类的。以下是外部访问代码:

java
1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Child c = new Child(); //C的静态类型是Child,动态类型是Child
Base b = c; //B的静态类型是Base 动态类型是Child
System.out.println(b.s); //b.s访问Base的静态变量输出:父类静态变量
System.out.println(b.m); //b.m访问Base的实例变量输出:Base
b.test(); //base Static:父类静态变量
System.out.println(c.s); //c.s访问Child的静态变量输出:子类静态变量
System.out.println(c.m); //c.m访问Child的实例变量输出Child
c.test(); //Child Static:子类静态变量
}

实例变量、静态变量、静态方法、private方法,都是静态绑定的。

重载

重载是同一个类中,声明了同名函数,但是函数的参数类型或者个数不同。当调用该函数时,会匹配传入参数于之相对于的函数主体。

动态绑定

动态绑定是指在”执行期间”判断所引用的实际类型类型,根据其实际类型调用其相应的方法,new的是谁就去找谁的方法,就是动态绑定。

注:当有多个重名函数的时候,在决定调用哪个函数的过程中,首先是按照参数类型进行匹配的,即在所有重载或重写函数的版本中找到最匹配的,然后才看变量的动态类型,进行动态绑定。

例:

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Base {
public int sum(int a,int b){
System.out.println("base_int_int");
return a + b;
}
}
public class Child extends Base {
public long sum(long a,long b){
System.out.println("child_long_long");
return a + b;
}
}
public stasic void main(String[] args){
Child c = new Child();
int a = 2; int b = 3;
c.sum(a, b);
}
  1. 父类声明了两个输入类型为int的函数,子类为两个long,若调用sum函数时的两个参数是int类型,输出为base_int_int。

  2. 父类声明输入类型为int a和long b,子类为两个long,调用函数输入为两个int类型时输出base_int_long。此时父类子类的两个方法类型都不匹配,但是调用的是父类的代码,因为父类的更匹配一些。

  3. 父类和子类都是int a和long b,调用函数仍然是输入两个int类型的参数,此时父子函数类型都是一样的,则进行动态绑定,调用子类的函数,输出child_int_long。

泛型

泛型将接口的概念进一步扩展,就是广泛的类型。

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Pair<T> {
T first;
T second;
public Pair(T first,T second) {
this.first = first;this.second = second;
}
public T getfirst() {return first;}
public T getsecond() {return second;}
}

Pair<Integer> minmax = new Pair<Integer>(1,100);
Integer min = minmax.getfirst();
Integer max = minmax.getsecond();

java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Pair<U, V> {
U first;
V second;
public Pair(U first,V second) {
this.first = first;this.second = second;
}
public U getfirst() {return first;}
public V getsecond() {return second;}
}

Pair<Integer, String> pair= new Pair<Integer,String>(100, "老马");
Pair<Integer, String> pair= new Pair<>(100, "老马"); //java7后可简写
Pair<String, String> kv= new Pair<>("name", "老马");

泛型参数类型到底是什么呢?其实在java编辑器将java源代码转化为.class文件时,就会将泛型代码转化为普通的非泛型代码,将T、U、和V类型擦除,替换成Object,强调,java泛型是通过擦除实现的。那为什么不直接用Object而选择泛型呢?

  1. 更好的安全性

  2. 更好的可读性

编译无误,运行报错

使用泛型是,在程序调用中有类型错误时编译器是会有提示,但是Object不会有提示

编译报错

java
1
2
3
4
Pair pair = new Pair("老马" ,1);
Integer id = (Integer)pair.getfirst();
String name = (String)pair.getsecond();

java
1
2
3
4
Pair<String,Integer> pair = new Pair<>("老马" ,1);
Integer id = pair.getfirst(); //有编译错误
String name = pair.getsecond(); //有编译错误

泛型讲数据结构和算法与数据类型相对分离,使得一套数据结构和算法可以用于各种数据类型,而且保证数据类型安全,提高可读性。

通配符

<?> 表示所有类型

<? extends E> 表示E和E的子类型

<? super E> 表示E和E的父类型

细节和局限性

使用泛型类、方法和接口

  1. 基本类型不能用于实例化类型参数

  2. 运行时类型信息不适用于泛型(不易理解)

  3. 类型擦除可能会引发一些冲突(不易理解)

定义泛型类、方法和接口

  1. 不能通过类型参数创造对象

  2. 泛型类类型参数不能用于静态变量和方法

泛型和数组

不能创建泛型数组,因为泛型数组类型可能会和复制内容泛型类型不匹配,引起报错。

接口和抽象类

接口

使用interface声明接口,修饰符一般都是public。接口内声明的方法都没有定义方法体,java8之前接口内不能实现方法。接口方法不需要加修饰符,加不加都相当于是public abstract

  1. 一个类可以实现多个接口

  2. 一个类可以继承类并实现接口

  3. 一个接口可以继承多个接口

java
1
2
3
public class Test implements Interface1, Interface2 {}   //一个类可以实现多个接口
public class Test extends Base implements Interface1 {} //一个类可以继承类并实现接口
public interface IChild extends IBase1, IBase2 {} //一个接口可以继承多个接口

接口中可以定义变量,修饰符是public static final,可写可不写都是。变量可以通过”接口名.变量名”调用,例Inferface1.a。

instanceof用来判断一个对象是否实现了某接口,p instanceof Inferface01 //返回值是Boolean类型值

使用接口:

java
1
2
3
MyComparable p1 = new Point(2,3);
MyComparable p2 = new Point(1,2);

Point类实现了MyComparable接口,所有可以声明p1和p2是MyComparable类型的变量,引用Point类型的对象。此时p1和p2都可以调用MyComparable 接口的方法也只能调用它的方法。

java8和java9中对接口做了一些增强。java8

抽象类

抽象类是相对于具体类而言的,一般来讲,具体类有直接对应的对象,而抽象类一般是用来继承。抽象类不让创建对象。

抽象类和抽象方法需要用abstract关键词声明。

java
1
2
3
public abstract class Shape{
public abstract void draw();
}
接口 抽象类
不能用于创建对象 不能用于创建对象
都是抽象方法 可以是抽象方法,也可以写方法体
不能定义实例变量 可以定义实例变量
可以实现多个接口 只能继承一个抽象类

抽象类和接口是相互配合,而不是代替,他们经常一起使用,接口声明能力,抽象类实现默认实现,实现全部或者部分方法,一个接口经常有一个对应的抽象类。

枚举(了解)

枚举也是一种属性类型,他的取值是有限的可以枚举出来的,比如一年四季,一周七天。

java
1
2
3
4
public enum Size {
SMALL, MINDIUM, LARGE
}
Size size = Size.SMALL;

所有枚举类型都有name()方法和toString()方法返回内容一样。枚举的equals和==的返回值是一样的。

枚举类型有一个int ordinal()方法,返回枚举值在声明时的顺序,从0开始。

compareTo方法可以比较两个枚举类型的位置之差。

常用基础类

包装类

基本数据类型 byte short int long float double char boolean
对应包装类 Byte Short Integer Long Float Double Character Boolean

包装类图片.webp
8个包装类都实现了Serializable , Comparable 接口

自动拆装箱

装箱:将基本类型转化为包装类型的过程叫装箱。

拆箱:将包装类型转化为基本类型的过程叫拆箱。

JDK1.5之前没有自动装箱

java
1
2
3
4
Integer integer = new Integer(10); //手动装箱1
Integer integer = Integer.valueOf(10); /手动/装箱2

int num = integer.intValue(); //手动拆箱

JDK1.5之后有自动装箱

java
1
2
Integer one = 1;      //自动装箱
int two = one + 10; //自动拆箱

其实自动拆装箱和手动拆装箱本质上是一样的。

基本用法

equals

包装类都重写了equals方法,他的比较运算结果和==的结果是一样的。

hashcode

hashcode返回一个对象的哈希值,是一个int类型的数,一个对象的哈希值不能改变,相同对象的哈希值必须一样。

hashcode和equals的联系:如果两个对象equals为true,则hashcode也必须一样。反之则不要求,如果equals返回值为false时,hashcode可以一样也可以不一样。

String的hashcode是每个字符*31的n-1次方相加,用31的(n-1)次方可能有两个原因:1.方便生成更分散的散列。2.31*h=32*h-h,按位计算和减法计算代替乘法,加快计算速率。

comparable

包装类都继承了comparable接口,有一个compareTo的方法,当比较式在小于、等于、大于参数是对应返回-1、0、1。注:0.1和0.1*0.1比较结果并不为0。

String、StringBuffer和StringBuilder

String Stringbuffer StringBuilder
不可变 可变 可变
线程安全 线程不安全
多线程操作字符串 单线程操作字符串

String

String是被final修饰的,不能被继承,也不能被修改,JDK9之前是char[],JDK9之后是byte[],String创建之后不能修改,底层方法区维护了一个字符串常量池,实现共享。都是用unicode编码,一个字符占两个字节。

StringBuilder

StringBuilder是可变类和线程不安全字符操作类,字符串操作不会产生新的对象,每个StringBuilder对象都有一个缓冲区容量,超过该容量时会增加容量。效率高

StringBuffer

StringBuffer是可变类和线程安全字符操作类,字符串操作不会产生新的对象,每个StringBuffer对象都有一个缓冲区容量,超过该容量时会增加容量。效率低

可变类

有三个构造方法StringBuffer()``,``StringBuffer(int capacity)``,``StringBuffer(String ``str)

StringBuffer()初始容量为16,StringBuffer(int capacity)初始容量是capacity,StringBuffer(String ``str)初始容量是str.length()+16

扩容:当容量满后,计划扩容2*n+2,如果追加的长度仍然超出,则扩容为n+count,count为追加长度。

总结

  1. 如果修改次数少用String

  2. 如果修改次数多,且单线程操作用StringBuilder

  3. 如果修改次数多,且多线程操作用StringBuffer

  4. StringBuffer和StringBuilder的区别就在于StringBuffer的操作使用synchronized关键字加了锁,是线程安全的。

System类

  1. exit退出程序

  2. arraycopy复制数组

  3. currentTimeMillens返回当期时间毫秒数

  4. System.gc() gc运行垃圾回收机制 垃圾收集器

Math类

math的都是静态方法

abs 返回绝对值 pow(2,4) 返回 2的4次方
min/max 返回最大值/最小值 ceil 向上取整double类型
random [0,1)随机数 floor 向下取整
sqrt 求开方 round 四舍五入

Array类

Arrays.toString(arr) 显示数组 Arrays.sort(arr) 冒泡排序
Array.copyOf(arr,arr.length) 拷贝arr.length到新数组中,如果大于arr,则新数组加空 fill(arr,22) 用22替换所有元素
Arrays.toString(arr,new Comparator()) 可重写排序,定制排序 Array.asList(arr) 转化为List集合
binarySearch(arr,1) 二分法查找有序数组,如果不存在返回-(应该存在的位置+1) equals 重写了

大数据类

BigInteger

函数 描述 函数 描述
BigInteger.add(a) BigInteger.multiply(a)
BigInteger.subtract(a) BigInteger.divide(a)

BigDecimal

函数 描述 函数 描述
BigDecimal.add(a) BigDecimal..multiply(a)
BigDecimal.subtract(a) BigDecimal..divide(a,BigDecimal.ROUND_CEILING) 除(如果有无限循环,将保留分子精度)

日期类

第一代日期类

java.util.Date

java
1
2
3
4
5
Date d1 = new Date();                             //获取当前时间 美国格式
Date d1 = new Date(1231534); //获取通过毫秒数当前时间
SimpleDateFormat sdf = new SimpleDateFormat(""); //格式
String format = sdf.format(d1); //format:将日期转化为指定格式
Date parse = sdf.parse("格式化好的日期"); //将string转为Date

第二代日期类

java.util.Calendar

  1. Calendar是抽象类,构造器是私有的

  2. 通过getInstance()获取实例

Calendar的问题

  1. 可变性

  2. 偏移性:月份是0开始的

  3. 格式化:格式化只对Date有用,对Calendar没用

  4. 线程不安全,不能处理闰秒

第三代日期类

java.time.*

LocalDate包含日期,LocalTime包含时间,LocalDateTime包含日期+时间

格式化日期

java
1
2
DateTimeFormat dtf= DateTimeFormatter.ofPattern(格式);
String str = dtf.format(日期对象);