概览
1. 包
- 包 (package) 是组织类的一种方式。
- 使用包的主要目的是保证类的唯一性。
1.1 导入包中的类
java中提供了很多现成的类供我们使用
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
这种方式写法比较麻烦一些,可以使用import语句导包
import java.util.Date;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
如果需要使用java.util中的其他类,可以使用 import java.util.*,但有的情况下更建议显示的指定要导入的类名,负责容易出现冲突的状况
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
// util 和 sql 中都存在一个 Date 这样的类, 此时就会出现歧义, 编译出错
Date date = new Date();
System.out.println(date.getTime());
}
}
// 编译出错
Error:(5, 9) java: 对Date的引用不明确
java.sql 中的类 java.sql.Date 和 java.util 中的类 java.util.Date 都匹配
在这种情况下使用完整的类名:
import java.util.*;
import java.sql.*;
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
System.out.println(date.getTime());
}
}
注意:
- import 和 C++ 的 #include 差别很大。C++ 必须 #include 来引入其他文件内容, 但是 Java 不需要。
import 只是为了写代码的时候更方便。 import 更类似C++的namespace和using。 - java.long包下的内容不需要导入,编译器会自动导入。
1.2 静态导入
使用 import static 可以导入包中的静态的方法和字段。
例如:
import static java.lang.System.*;
import static java.lang.Math.*;
public class Test {
public static void main(String[] args) {
out.println("hello");
double x = 30;
double y = 40;
//静态导入的方式更方便一些
// double result = Math.sqrt(pow(x, 2) + Math.pow(y, 2));
double result = sqrt(pow(x, 2) + pow(y, 2));
System.out.println(result);
}
}
1.3 将类放到包中
基本规则:
- idea中在src上右键->新建->Packeg ,包名需要尽量指定成唯一的名字,通常会使用公司的域名的颠倒形式。
- 包名与代码路径相匹配,在文件最上方加上package语句指定该代码在哪个包中。
- 如果一个类中没有package语句,则该类被放到一个默认包中。
1.4 包的访问权限控制
如下图所示:
- private:类内部可以访问,类外不能访问
- 默认(也叫包访问权限):类内部能访问,同一个包中的类可以访问,其他类不能访问
- protected:类内部能访问,子类和同一包中的类可以访问,其他类不能访问
- public:类内部和类的调用者都能访问
1.5 常见的系统包
- java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。
- java.lang.reflect:java 反射编程包;
- java.net : 进行网络编程开发包。
- java.sql:进行数据库开发的支持包。
- java.util:是java提供的工具程序包。(集合类等) 非常重要
- java.io:I/O编程开发包
2. 继承
程序层面上:表达继承的关系使用extends
意义:
- 减少代码量,代码复用
- 降低了代码冗余
2.1 语法规则
基本语法:
class 子类 extends 父类 {}
派生类 基类
超类
- 使用 extends 指定父类。
- java中只支持单继承
- 子类继承了父类除构造方法以外所有的东西
- 一般来说不会超过三层的继承关系
注意:
如果父类写有有参的构造方法,则子类要显示的调用super()来帮助父类来构造,并且调用super()必须放在第一行
代码示例:
class Animal {
public String name;
public Animal(String name) {
this.name = name;
}
public void eat(String food) {
System.out.println(this.name + "正在吃" + food);
}
}
class Cat extends Animal {
public Cat(String name) {
// 使用 super 调用父类的构造方法.
// super表示对父类的引用
super(name);
}
}
class Bird extends Animal {
public Bird(String name) {
super(name);
}
public void fly() {
System.out.println(this.name + "正在飞 ︿( ̄︶ ̄)︿");
}
}
public class Test {
public static void main(String[] args) {
Cat cat = new Cat("小黑");
cat.eat("猫粮");
Bird bird = new Bird("圆圆");
bird.fly();
}
}
如果将父类属性由public改为private,那么子类将不能访问
2.2 protected关键字
- 对于类的调用者来说, protected 修饰的字段和方法是不能访问的。
- 对于类的子类和同一个包的其他类来说, protected 修饰的字段和方法是可以访问的。
- 同一包中的同一类、同一包中的不同类、不同包中的子类可以访问。
2.3 final关键字
- 在final修饰一个变量或者字段的时候,表示常量(不能修改)。
- final关键字也能修饰类,此时表示被修饰的类就不能被继承。
代码示例:
final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error:(3, 27) java: 无法从最终com.wx.Animal进行继承
那么由此可见,final关键字的功能是限制类被继承,用final修饰的类被继承的时候就会编译报错,平时用的String类就是final修饰的不能被继承。
3. 组合
和继承类似,组合也是一种表达类之间关系的方式。
代码示例:
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
组合并没有涉及到extends,仅仅是将一个类的实例作为另外一个类的字段,这是设计类的常用方式之一。
- 组合通常表示 has-a 语义
- 继承通常表示 is-a 语义
4.多态
父类引用子类对象,通过父类引用来调用子类中重写的方法,此时就会发生动态绑定,也叫做多态。
4.1 向上转型
将派生类的值赋值给基类,即父类引用引用子类对象
向上转型的表现形式:
- 直接赋值
在上面的例子中:
Bird bird = new Bird("圆圆");
代码也可以写成这样子:
Animal bird = new Bird("圆圆");
此时bird是一个父类的引用,指向一个子类(Bird)的实例,这种写法称为向上转型
- 方法传参
public class Test {
public static void main(String[] args) {
Bird bird = new Bird("圆圆");
feed(bird);//传递bird 使用Animal来接收
}
public static void feed(Animal animal) {
animal.eat("谷子");
}
}
// 执行结果
圆圆正在吃谷子
- 方法返回:
public class Test {
public static void main(String[] args) {
Animal animal = findMyAnimal(); //使用Animal来接收bird
}
public static Animal findMyAnimal() {
Bird bird = new Bird("圆圆");
return bird;
}
}
4.2 动态绑定
运行时多态,编译时调用父类方法,运行时调用的是子类重写的方法
- 父类引用,引用子类的对象
- 通过父类的引用,调用子类和父类重写的那个方法
注意:
- 当父类引用子类对象的时候,只能访问自己特有的,访问不到子类。
- 通过引用可以调用方法,重点看引用类型
4.3 方法重写
子类实现父类的同名方法,并且参数的类型和个数完全相同,这种情况称为覆写/重写/覆盖(Override)
注意:
- 重写和重载完全不一样,不要混淆
- 普通方法可以重写, static 修饰的静态方法不能重写
- 重写中子类的方法的访问权限不能低于父类的方法访问权限
- 重写的方法返回值类型不一定和父类的方法相同(但是建议最好写成相同, 特殊情况除外)
- 针对重写的方法,可以使用@Override来显示指定
重载和重写的区别:
小结:
-
重载:在同一个类中
- 方法名相同
- 参数列表不同
- 返回值不做要求
-
重写:
- 方法名相同
- 参数列表 相同(参数个数、参数的类型)
- 返回值也要相同
注意:
- 需要重写的方法,不能是private修饰的
- 被final所修饰的方法,不能被重写
- 需要重写方法的,对于访问修饰限定符,子类的访问修饰限定符一定要大于或等于父类的访问修饰限定符
- static修饰的静态方法不能重写
4.4 向下转型
向上转型是子类对象转成父类对象,向下转型就是父类对象转成子类对象。相比于向上转型来说,向下转型没那么常见,但是也有一定的用途。
- 父类对象赋值给子类引用
- 在使用之前使用instanceof 可以判定一个引用是否是某个类的实例. 如果是, 则返回true,此时可以进行向下转型
4.5 super和this关键字
- super:表示获取到父类实例的引用
- this:表示获取当前对象的引用
super和this的区别:
5. 抽象类
- 抽象类存在的意义就是为了被继承
- 抽象类也可以发生向上转型及多态
5.1 抽象方法
被abstract所修饰的方法,没有具体的实现
5.2 抽象类
5.2.1 语法规则
abstract class Shape {
abstract public void draw();
}
5.2.2 抽象类的作用
包含抽象方法的类称为抽象类
注意:
- 抽象类是不可以被实例化的。
- 抽象类中可以有成员方法和成员变量,比普通类多了抽象方法
- 如果一个普通类继承了一个抽象类,那么这个普通类一定要重写抽象类中的抽象方法
- 抽象方法一定要重写,不能用private,final修饰
- 抽象类继承抽象类不需要重写抽象类的方法,如果有其他类继承了这个抽象类,那么一定要重写这个抽象方法
- 抽象类也可以发生向上转型和动态绑定
6. 接口
接口是抽象类的更进一步,抽象类中还可以包含非抽象方法和字段,而接口方法中包含的方法都是抽象方法,字段只能包含静态常量。
6.1 语法规则
interface IShape {
void draw(); //抽象方法
}
- 使用interface定义一个接口
- 一个类只能继承一个父类,但是可以实现多个接口
- 所有的方法默认是public abstract
- 所有的成员变量一定是public static final的
- 接口也不可以实例化
- 类和接口的关系:implements,可以实现多个接口,每个接口之间使用逗号隔开
- 接口也可以进行向上转型,多态
- 接口之间可以继承
JDK1.8新特性:
- 如果接口中的方法被default修饰,则这个方法可以有具体实现。
6.2 Comparable接口
对自定义类型进行排序,需要实现Comparable接口,对compareTo方法进行重写
java中提供了关于对象的比较,有以下方式:
-
equals:所有类都具备的能力
理解:比较两个对象代表的是不是一个实际事物,需要通过重写equals方法才能达到 -
大小的比较
- 自然顺序 Comparable compareTo( ) ,不是所有类都具备,需要通过实现Comparable接口来表明该类具备这个能力
- 外部比较 ( 比较器) Comparator,构造一个天平,不需要比较的类具备Comparable的能力
- 理解:比较传入的两个引用指向的对象
代码示例1:实现Comparable接口
class Teacher implements Comparable<Teacher>{
public String name;
public int age;
public int height;
public int weight;
public Teacher(String name, int age, int height, int weight) {
this.name = name;
this.age = age;
this.height = height;
this.weight = weight;
}
@Override
public String toString() {
return "Teacher{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
//按照age进行比较
@Override
public int compareTo(Teacher o) {
return this.age-o.age;
}
}
public class ComparableTestDemo {
public static void main(String[] args) {
Teacher[] teachers = new Teacher[3];
teachers[0] = new Teacher("li",12,180,80);
teachers[1] = new Teacher("li1",15,175,67);
teachers[2] = new Teacher("li2",3,167,90);
System.out.println(teachers[0]+" "+teachers[1]+" "+teachers[2]);
Arrays.sort(teachers);
System.out.println(teachers[0]+" "+teachers[1]+" "+teachers[2]);
}
}
//运行结果:
Teacher{name='li', age=12} Teacher{name='li1', age=15} Teacher{name='li2', age=3}
Teacher{name='li2', age=3} Teacher{name='li', age=12} Teacher{name='li1', age=15}
代码示例2:实现Comparator接口、外部比较器
class ByHeigthComparator implements Comparator<Teacher>{
@Override
public int compare(Teacher o1, Teacher o2) {
return o1.height-o2.height;
}
}
class ByWeightComparator implements Comparator<Teacher>{
@Override
public int compare(Teacher o1, Teacher o2) {
return o1.weight-o2.weight;
}
}
public class ComparatorDemo {
public static void main(String[] args) {
Teacher[] teachers = new Teacher[3];
teachers[0] = new Teacher("li",12,180,80);
teachers[1] = new Teacher("li1",15,175,67);
teachers[2] = new Teacher("li2",3,167,90);
Comparator<Teacher> byHeight = new ByHeigthComparator();
Comparator<Teacher> byweight = new ByWeightComparator();
System.out.println(teachers[0].compareTo(teachers[1]));
System.out.println(byHeight.compare(teachers[0],teachers[1]));
System.out.println(byweight.compare(teachers[0],teachers[1]));
}
}
//运行结果:
-3
5
13
6.3 Clonable接口
Object 类中存在一个 clone 方法, 调用这个方法可以创建一个对象的 “拷贝”。但是要想合法调用 clone 方法, 必须要先实现 Clonable 接口, 否则就会抛出 CloneNotSupportedException 异常。
- 空接口:标记接口,标记当前类是可以克隆的。
- 实现Clonable接口,并且重写clone方法
代码示例:
Person类:
class Money implements Cloneable{
public double money;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Person implements Cloneable{
public String name;
public Money m ;//类中有引用类型,需要将引用类型也进行拷贝
public Person(String name) {
this.name = name;
this.m =new Money();
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
@Override
protected Object clone() throws CloneNotSupportedException {
Person person = (Person)super.clone();
person.m = (Money)this.m.clone(); //需要将 m也进行克隆
return person;
}
}
Demo类:
public class Demo {
public static void main(String[] args) throws CloneNotSupportedException {
Person person1 = new Person("name");
person1.m.money = 12.5;
Person person2 = (Person) person1.clone();
System.out.println(person2);
person1.name = "lisi";
System.out.println(person2);
System.out.println(person1.m.money);
System.out.println(person2.m.money);
person1.m.money = 18;
System.out.println(person1.m.money);
System.out.println(person2.m.money);
}
}
运行结果:
12.5
12.5
18
12.5
6.4 抽象类和接口的区别
核心区别:
- 抽象类中可以包含普通方法和普通字段, 这样的普通方法和字段可以被子类直接使用(不必重写), 而接口中不能包含普通方法, 子类必须重写所有的抽象方法。
图示: