包(package) 是组织类的一种方式,使用包的主要目的是保证类的唯一性。
导入包中的类
Java
中已经提供了很多现成的类供我们使用。
public class Test {
public static void main(String[] args) {
java.util.Date date = new java.util.Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
上面的代码也可以写成这样:
import java.util.Date;
//import java.util.*;
public class Test {
public static void main(String[] args) {
Date date = new Date();
// 得到一个毫秒级别的时间戳
System.out.println(date.getTime());
}
}
如果需要使用 java.util
中的其他类, 可以使用 import java.util.*
:其中*
表示通配符,意味着导入这个包底下所有的类,但是Java在处理的时候,需要那个类,才会拿哪个类。而在C语言里面,通过include
关键字导入之后,就会把这个头文件里面的内容全部都拿过来。
使用 import static
可以导入包中的静态的方法和字段。
例一:
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println("hello");
}
}
例二:
import static java.lang.Math.*;
import static java.lang.System.*;
public class Test {
public static void main(String[] args) {
out.println(max(10,20));
}
}
通过静态导入的代码可读性不高,所以静态导入用的很少。
基本规则:
package
语句指定该代码在哪个包中.com.company.demo1
的包, 那么会存在一个对应的路径 com/company/demo1
来存储代码.package
语句, 则该类被放到一个默认包中.我们已经了解了类中的 public
和 private
. private
中的成员只能被类的内部使用.
如果某个成员不包含 public
和 private
关键字, 此时这个成员可以在包内部的其他类使用, 但是不能在包外部的类使用。
包的访问权限指的是只能在当前包当中进行使用,当你的成员变量不加任何访问修饰限定词的时候,它默认就是一个包访问权限。
同一个包下的成员变量可以访问,其他包下的成员变量无法从外部程序包中对其进行访问。
常见的系统包:
java.lang
:系统常用基础类(String、Object
),此包从JDK1.1
后自动导入。java.lang.reflect
:java
反射编程包;java.net
:进行网络编程开发包。java.sql
:进行数据库开发的支持包。java.util
:是java提供的工具程序包。(集合类等) 非常重要。java.io
:I/O
编程开发包。封装:不必要公开的数据成员和方法使用private
关键字进行修饰。意义:安全性。
继承:对共性的抽取。使用extends
关键字进行处理的。意义:可以对代码进行重复使用。
我们并不希望类之间的继承层次太复杂. 一般我们不希望出现超过三层的继承关系. 如果继承层次太多, 就需要考虑对代码进行重构了。
基本语法:
class 子类 extends 父类 {
}
其中Cat
这样的类, 我们称为子类, 派生类,Animal
这样被继承的类, 我们称为 父类 , 基类 或 超类。
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(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.name = "小黑";
System.out.println(cat.name);
cat.eat("猫粮");
Bird bird = new Bird();
bird.name = "圆圆";
System.out.println(bird.name);
bird.fly();
}
}
[注意]:
extends
继承父类.Java
中一个子类只能继承一个父类 (而C++/Python
等语言支持多继承).即Java当中是单继承,不能同时继承两个类以上的类,包括两个。public
的字段和方法.private
的字段和方法, 子类中是无法访问的.super
关键字得到父类实例的引用。super
:不能出现在静态方法中,其代表的是父类实例的引用。
super的两种常见用法:
public Cat(String name) {
super(name);
}
public class Bird extends Animal {
public Bird(String name) {
super(name);
}
@Override
public void eat(String food) {
// 修改代码, 让子调用父类的接口.
super.eat(food);
System.out.println("我是一只小鸟");
System.out.println(this.name + "正在吃" + food);
}
}
在这个代码中, 如果在子类的 eat
方法中直接调用 eat
(不加super
), 那么此时就认为是调用子类自己的 eat
(也就是递归了). 而加上 super
关键字, 才是调用父类的方法。即:如果子类和父类字段同名的话,则优先子类的字段,如果想要访问父类的字段,则要加上super
关键字。
子类构造的同时,要先帮助父类来进行构造。需要在子类的构造方法当中,使用super
关键字来显示的调用父类的构造方法。
super有3中表现形式:
super
的概念:表示子类访问父类的属性、方法。不查找本类,而直接调用父类定义。
this
的概念:访问本类中的属性、方法,其先查找本类,如果本类没有就调用父类。
protected 关键字:对于类的调用者来说, protected 修饰的字段和方法是不能访问的,对于类的子类和同一个包的其他类 来说, protected 修饰的字段和方法是可以访问的。
private
: 类内部能访问, 类外部不能访问。protected
: 类内部能访问, 子类和同一个包中的类可以访问, 其他类不能访问.public
: 类内部和类的调用者都能访问。也就是说:
字段如果是public
,在哪里使用都可以。
private
只有在同一个包的同一个类中才能使用。
default
在同一个包的同一个类及同一包中的不同类中可以使用。
protected
在同一个包的同一个类中,同一包中的不同类中,及不同包中得子类中可以使用。
如果一个类不想被继承,我们可以设置为final
修饰。
final
关键字修饰类, 此时表示被修饰的类就不能被继承。
1.final int a = 20;//常量,不可以被修改
2.final Class A{ //代表整个类不可以被继承
}
3.final修饰方法
和继承类似, 组合也是一种表达类之间关系的方式, 也是能够达到代码重用的效果。
组合并没有涉及到特殊的语法(诸如extends 这样的关键字), 仅仅是将一个类的实例作为另外一个类的字段.这是我们设计类的一种常用方式之一。
如一个学校:
public class Student {
...
}
public class Teacher {
...
}
public class School {
public Student[] students;
public Teacher[] teachers;
}
组合表示 has - a
语义。
如:一个学校中 “包含” 若干学生和教师.
继承表示 is - a
语义。
在上面的 “动物和猫” 的例子中, 我们可以理解成一只猫也 “是” 一种动物。
向上转型:表示往父类的方向转。
向上转型发生的时机:
在上面的例子中有:
public class Test {
public static void main(String[] args) {
//Cat cat = new Cat();
//Animal animal = cat;
Animal animal = new Cat();//父类引用 引用子类对象
}
}
public class Test {
public static void function(Animal ani){
}
public static void main(String[] args) {
//Cat cat = new Cat();
//Animal animal = cat;
Animal animal = new Cat();//父类引用 引用子类对象
Cat cat = new Cat();
function(cat);
}
}
此时形参 ani
的类型是 Animal
(基类), 实际上对应到 Cat
(父类) 的实例。
public class Test {
public static Animal func2(Animal ani){
Cat cat = new Cat();
return cat;
}
public static void main(String[] args) {
Animal animal = func2();
}
}
此时方法 func2
返回的是一个 Animal
类型的引用, 但是实际上对应到 Cat
的实例。
编译的时候不能够确定到底调用谁的方法。运行的时候,才知道了调用哪个方法。这就叫做运行时绑定也就是动态绑定。动态绑定也是多态的基础。
1、父类引用引用子类的对象。
2、运行时绑定:通过这个父类引用调用父类和子类同名的覆盖方法。
同名的覆盖方法也叫重写/覆写/覆盖:
1、方法名相同
2、参数列表相同(参数的个数+参数的类型)
3、返回值相同 (特殊:返回值也可以是协变类型)
4、父子类的的情况下
关于重写的注意事项:
static
修饰的静态方法不能重写.private
方法不能够重写.final
修饰的方法不能够重写.编译时绑定:通过函数的重载实现的。编译的时候,会根据你给的参数的个数和类型,在编译期,确定你最终调用的方法。
重载:方法名相同,参数的类型和个数不同,重载的范围是一个类。
重写:方法名,返回值类型、参数类型及个数完全相同,范围是继承关系,被覆写的方法不能拥有比父类更严格的访问控制权限。
通过父类引用只能访问父类自己的成员。
向上转型是子类对象转成父类对象, 向下转型就是父类对象转成子类对象. 相比于向上转型来说, 向下转型没那么常见,但是也有一定的用途。
比如:
public static void main(String[] args) {
Animal animal = new Bird();
Bird bird = (Bird)animal;
bird.fly();
}
向下转型不安全,不是所有的类型都具有向下转型中得方法,此时,我们可以加上instanceof
判断一下,可以判断出一个引用是否是某个类的实例。如:
public class Test {
public static void main(String[] args) {
Animal animal = new Cat();
if(animal instanceof Bird){
Bird bird = (Bird)animal;
bird.fly();
}
}
}
class Shape{
public void draw(){
System.out.println("Shape::draw()");
}
}
class Rect extends Shape{
public void draw(){
System.out.println("♦");
}
}
class Flower extends Shape{
public void draw(){
System.out.println("❀");
}
}
class Triangle extends Shape{
@Override
public void draw() {
System.out.println("△");
}
}
public class Test {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Rect rect = new Rect();
drawMap(rect);
Flower flower = new Flower();
drawMap(flower);
Triangle triangle = new Triangle();
drawMap(triangle);
}
}
当类的调用者在编写 drawMap
这个方法的时候, 参数类型为 Shape
(父类), 此时在该方法内部并不知道, 也不关注当前的 shape
引用指向的是哪个类型(哪个子类)的实例. 此时 shape
这个引用调用 draw
方法可能会有多种不同的表现
(和 shape
对应的实例相关), 这种行为就称为多态。
使用多态的好处:
if - else
public class Test {
public static void drawMap(Shape shape){
shape.draw();
}
public static void main(String[] args) {
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
Shape[] shapes = {triangle, rect, triangle, rect, flower};
for (Shape shape:shapes) {
shape.draw();
}
}
}
如果使用 if - else
则实现如下:
public static void main(String[] args) {
Rect rect = new Rect();
Flower flower = new Flower();
Triangle triangle = new Triangle();
String[] shapes = {"triangle", "rect", "triangle", "rect", "flower"};
for (String s:shapes) {
if(s.equals("triangle")){
triangle.draw();
}else if(s.equals("rect")){
rect.draw();
}else if (s.equals("flower")){
flower.draw();
}
}
class Cycle extends Shape {
@Override
public void draw() {
System.out.println("●");
}
}
在刚才的打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它设计成一个 抽象方法(abstract method)。
//抽象类
abstract class Shape{
//抽象方法
public abstract void draw();
}
注意:
final
修饰,抽象方法也不可以被final
修饰。private
修饰的。使用抽象类相当于多了一重编译器的校验,使用抽象类的场景就如上面的代码, 实际工作不应该由父类完成, 而应由子类完成. 那么此时如果不小心误用成父类了,
使用普通类编译器是不会报错的. 但是父类是抽象类就会在实例化的时候提示错误, 让我们尽早发现问题.
interface
来修饰的。interface IA{}
default
来修饰这个方法。static
的方法。public
的,因此可以省略 public
。public abstract
的。//接口是使用interface来修饰的
interface IShape{
//抽象方法,默认是public abstract的。
public abstract void func1();
//接口当中的普通方法,不能有具体的实现。非要实现,只能通过关键字`default`来修饰这个方法
default public void draw(){
System.out.println("aaa");
}
//接口当中,可以有static的方法。
public static void func(){
System.out.println("bbb");
}
}
new
来实例化的。implements
实现了。abstract
。public static final
修饰的。public
。interface IA{
//public static final int a = 10;//public static final可以省略
int A1 = 10;
void funcA();//默认为抽象方法
}
class AClass implements IA{
public void funcA(){
}
}
extends
继承一个抽象类或者普通类,但是只能继承一个类。同时,也可以通过implements
实现多个接口,接口之间使用逗号隔开就好。如:
interface IA{
public static final int a = 10;
int A = 10;
void funcA();//默认为抽象方法
}
interface IB{
void funcB();
}
class BClass {
}
class AClass extends BClass implements IA,IB{
public void funcA(){
System.out.println("A::funcA()");
System.out.println(A);
}
@Override
public void funcB() {
System.out.println("A::funcB()");
}
}
extends
来操作他们的关系,此时,这里面意为:拓展。一个接口IB1
通过 extends
来拓展另一个接口IA1
的功能。此时当一个类C
通过implements
实现这个接口IB1
的时候,此时重写的方法不仅仅是IB1
的抽象方法,还有他从IA1
接口拓展来的功能/方法。如下所示:
interface IA1{
void funcA();//默认为抽象方法
}
interface IB1 extends IA1{
void funcB();
}
class C implements IB1{
@Override
public void funcA() {
System.out.println("aaa");
}
@Override
public void funcB() {
System.out.println("bbb");
}
}
使用接口也可以实现动态绑定,比如下面的例子:
interface IShape{
// public abstract void draw();
void draw();
}
class Rect implements IShape{
public Rect() {
}
@Override
public void draw() {
System.out.println("fangPain");
}
}
class Flower implements IShape {
public void draw(){
System.out.println("hua");
}
}
class Triangle implements IShape{
@Override
public void draw() {
System.out.println("△");
}
}
class Cycle implements IShape {
@Override
public void draw() {
System.out.println("●");
}
}
public class Test {
public static void drawMap( IShape iShape){
iShape.draw();
}
public static void main(String[] args) {
Flower flower = new Flower();
Cycle cycle = new Cycle();
Triangle triangle = new Triangle();
drawMap(flower);
drawMap(cycle);
drawMap(triangle);
}
}
有的时候我们需要让一个类同时继承自多个父类. 这件事情在有些编程语言通过 多继承的方式来实现的.然而 Java 中只支持单继承, 一个类只能 extends 一个父类. 但是可以同时实现多个接口, 也能达到多继承类似的效果.
例:
class Animal{
protected String name;
public Animal(String name) {
this.name = name;
}
}
//不是所有的动物都会飞,所以不能写到Animal类当中
//如果写到别的类当中也不行,因为一个类不能继承多个类,所以用到接口
interface IFlying{
void fly();
}
interface IRunning{
void run();
}
interface ISwimming{
void swimming();
}
class Bird extends Animal implements IFlying{
public Bird(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在飞");
}
}
class Frog extends Animal implements IRunning,ISwimming{
public Frog(String name) {
super(name);
}
@Override
public void run() {
System.out.println(this.name + "正在跑");
}
@Override
public void swimming() {
System.out.println(this.name + "正在游泳");
}
}
class Duck extends Animal implements ISwimming,IRunning,IFlying{
public Duck(String name) {
super(name);
}
@Override
public void fly() {
System.out.println(this.name + "正在飞");
}
@Override
public void run() {
System.out.println(this.name + "正在跑");
}
@Override
public void swimming() {
System.out.println(this.name + "正在游泳");
}
}
public class test04 {
public static void runFunc(IRunning iRunning){
iRunning.run();
}
public static void swimmingFunc(ISwimming iSwimming){
iSwimming.swimming();
}
public static void flyingFunc(IFlying iFlying){
iFlying.fly();
}
public static void main(String[] args) {
runFunc(new Duck("鸭子"));
runFunc(new Frog("青蛙"));
swimmingFunc(new Duck("鸭子"));
flyingFunc(new Bird("小鸟"));
flyingFunc(new Duck("鸭子"));
}
}
有了接口之后, 类的使用者就不必关注具体类型, 而只关注某个类是否具备某种能力。
如果自定义的数据类型进行大小的比较一定要实现可以比较的接口。如
Comparable
这个接口有一个很大的缺点:对类的侵入性非常强。一旦写好了,不敢轻易改动。
例:
class Student implements Comparable<Student> {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
@Override
public int compareTo(Student o) {//谁调用compareTo,谁就是this
// if (this.age > o.age){
// return 1;
// }else if (this.age < o.age){
// return -1;
// }else {
// return 0;
// }
//return this.age - o.age;//从小到大排序
//return o.age - this.age;//从大到小排序
//return (int)(this.score - o.score);
return this.name.compareTo(o.name);
}
}
Comparator
灵活,对类的侵入性非常弱。
例:
class Student {
public String name;
public int age;
public double score;
public Student(String name, int age, double score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score=" + score +
'}';
}
}
class AgeComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.age - o2.age;
}
}
class ScoreComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return (int)(o1.score - o2.score);
}
}
class NameComparator implements Comparator<Student>{
@Override
public int compare(Student o1, Student o2) {
return o1.name.compareTo(o2.name);
}
}
public class test01 {
public static void main(String[] args) {
Student[] student = new Student[3];
student[0] = new Student("zHang",12,100);
student[1] = new Student("aWang",18,98);
student[2] = new Student("li",56,88);
System.out.println(Arrays.toString(student));//排序前打印
AgeComparator ageComparator = new AgeComparator();
ScoreComparator scoreComparator = new ScoreComparator();
NameComparator nameComparator = new NameComparator();
Arrays.sort(student,ageComparator);//默认是从小到大的排序
System.out.println(Arrays.toString(student));//排序后打印
}
}
Comparable、Comparator
用哪个接口取决于你的业务,一般不叫推荐比较器
Cloneable
Java
中内置了一些很有用的接口, Clonable
就是其中之一.Object
类中存在一个 clone
方法, 调用这个方法可以创建一个对象的 “拷贝”. 但是要想合法调用 clone
方法, 必须要先实现 Clonable
接口, 否则就会抛出 CloneNotSupportedException
异常。然后重写克隆方法。
Clonable
是一个空接口,属于一个标记接口,代表当前这个类是可以被克隆的。
例:
class Person implements Cloneable{
public int age;
public Money money= new Money();
public void eat(){
System.out.println("eat");
}
@Override //重写toString方法
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override//重写克隆方法
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class test01 {
public static void main1(String[] args) throws CloneNotSupportedException {
Person person = new Person();
person.age = 66;
Person person1 = (Person) person.clone();//克隆person
System.out.println(person1);//66
System.out.println("===============================");
person1.age = 199;//拷贝完之后改变person1,不会影响person的值
System.out.println(person);//66
System.out.println(person1);//199
}
}
class Money implements Cloneable{
public double m = 12.5;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
class Person implements Cloneable{
public int age;
public Money money= new Money();
public void eat(){
System.out.println("eat");
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override//重写克隆方法
protected Object clone() throws CloneNotSupportedException {
return super.clone();
// Person temp = (Person) super.clone();
// temp.money = (Money) this.money.clone();
// return temp;
}
}
public class test01 {
public static void main(String[] args) throws CloneNotSupportedException {
Person person = new Person();
Person person1 = (Person) person.clone();
System.out.println(person.money.m);//12.5
System.out.println(person1.money.m);//12.5
System.out.println("=============================");
person1.money.m = 99.9;
System.out.println(person.money.m);//99.9
System.out.println(person1.money.m);//浅拷贝 99.9
}
}
内存图:
要想实现将person1
的money
改变不影响到person
的money
,必须将Money
类也实现接口继承,并重写其中的克隆方法。
@Override//重写克隆方法
protected Object clone() throws CloneNotSupportedException {
Person temp = (Person) super.clone();
temp.money = (Money) this.money.clone();
return temp;
}
以上。