目录
(1)创建类(class)与实例(instance)
(2)方法
(3)构造方法
(4)方法重载
(5)继承
(6)多态
覆写Object方法
final
(7)抽象类
抽象类
面向抽象编程
(8)接口
接口继承
default方法
(9)静态字段和静态方法
静态方法
(10)包
(11)作用域
最佳实践
小结
(12)内部类
(13)class与jar
(14)模块
笔记内容整理来自:
(1)创建类(class)与实例(instance)
在OOP中,class和instance是“模版”和“实例”的关系;
定义class就是定义了一种数据类型,对应的instance是这种数据类型的实例;
class定义的field,在每个instance都会拥有各自的field,且互不干扰;
通过new操作符创建新的instance,然后用变量指向它,即可通过变量来引用这个instance;
访问实例字段的方法是变量名.字段名;
指向instance的变量都是引用变量。
package test10;
//创建一个类:
class Person
{
String name;
String Address;
int age;
Person(String name,String Address,int age){
this.name = name;
this.Address = Address;
this.age = age;
}
public String toString()
{
return "["+"姓名:"+name+" "+"年龄:"+age+" "+"地址:"+Address+"]";
}
}
public class Main {
public static void main(String[] args) {
//创建一个类的实例:
Person per = new Person("Jack","Eglish",18);
System.out.print(per);
}
}
一个class可以包含多个字段(field),字段用来描述一个类的特征。上面的Person类,我们定义了两个字段,一个是String类型的字段,命名为name,一个是int类型的字段,命名为age。因此,通过class,把一组数据汇集到一个对象上,实现了数据封装。
public是用来修饰字段的,它表示这个字段可以被外部访问。
(2)方法
-
方法可以让外部代码安全地访问实例字段;
-
方法是一组执行语句,并且可以执行任意逻辑;
-
方法内部遇到return时返回,void表示不返回任何值(注意和返回null不同);
-
外部代码通过public方法操作实例,内部代码可以调用private方法;
-
理解方法的参数绑定。
package test10;
//创建一个类:
class Person
{
public String name;
public String Address;
private int age;
Person(String name,String Address,int age){
this.name = name;
this.Address = Address;
this.age = age;
}
public String toString()
{
MoveHomeToBeijing();//Private类型,只能在类体中执行
return "["+"姓名:"+name+" "+"年龄:"+age+" "+"地址:"+Address+"]";
}
private void MoveHomeToBeijing()
{
this.Address = "Beijing";
}
}
public class Main {
public static void main(String[] args) {
//创建一个类的实例:
Person per = new Person("Jack","Eglish",18);
//per.MoveHomeToBeijing();//因为MoveHomeToBejing是private类型只能在类体中调用,所以此处代码无法执行。
System.out.println(per.name);//Jack,因为name被public修饰,所以类外可访问
//System.out.println(per.age);//编译报错,因为age被private修饰,所以类外无法访问
System.out.print(per);//[姓名:Jack 年龄:18 地址:Beijing]
}
}
(3)构造方法
实例在创建时通过new操作符会调用其对应的构造方法,构造方法用于初始化实例;
没有定义构造方法时,编译器会自动创建一个默认的无参数构造方法;
可以定义多个构造方法,编译器根据参数自动判断;
可以在一个构造方法内部调用另一个构造方法,便于代码复用。
没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false:
在Java中,创建对象实例的时候,按照如下顺序进行初始化:
-
先初始化字段,例如,int age = 10;表示字段初始化为10,double salary;表示字段默认初始化为0,String name;表示引用类型字段默认初始化为null;
-
执行构造方法的代码进行初始化。
因此,构造方法的代码由于后运行,所以,new Person("Xiao Ming", 12)的字段值最终由构造方法的代码确定。
一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…)。
-
方法可以让外部代码安全地访问实例字段;
-
方法是一组执行语句,并且可以执行任意逻辑;
-
方法内部遇到return时返回,void表示不返回任何值(注意和返回null不同);
-
外部代码通过public方法操作实例,内部代码可以调用private方法;
-
理解方法的参数绑定。
实例在创建时通过new操作符会调用其对应的构造方法,构造方法用于初始化实例;
没有定义构造方法时,编译器会自动创建一个默认的无参数构造方法;
可以定义多个构造方法,编译器根据参数自动判断;
可以在一个构造方法内部调用另一个构造方法,便于代码复用。
没有在构造方法中初始化字段时,引用类型的字段默认是null,数值类型的字段用默认值,int类型默认值是0,布尔类型默认值是false:
在Java中,创建对象实例的时候,按照如下顺序进行初始化:
先初始化字段,例如,int age = 10;表示字段初始化为10,double salary;表示字段默认初始化为0,String name;表示引用类型字段默认初始化为null;
执行构造方法的代码进行初始化。
因此,构造方法的代码由于后运行,所以,new Person("Xiao Ming", 12)的字段值最终由构造方法的代码确定。
一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…)。
package test10;
public class Main {
public static void main(String[] args) {
Person per = new Person("Kangkang");
System.out.print(per);//[姓名:Kangkang 年龄:17 地址:Beijing]
}
}
//创建一个类:
class Person
{
private String name;
private String Address;
private int age;
Person(String Address,int age){//有参构造方法1
this.Address = Address;
this.age = age;
}
Person(String name)//有参构造方法2
{
this("Beijing",17);
this.name = name;
}
Person()//无参构造方法
{
this.age = 18;//永远18岁
}
private void MoveHomeToBeijing()
{
this.Address = "Beijing";
}
public String toString()
{
MoveHomeToBeijing();//Private类型,只能在类体中执行
return "["+"姓名:"+name+" "+"年龄:"+age+" "+"地址:"+Address+"]";
}
}
(4)方法重载
方法重载是指多个方法的方法名相同,但各自的参数不同;
重载方法应该完成类似的功能,参考String的indexOf();
重载方法返回值类型应该相同。
方法名相同,但各自的参数不同,称为方法重载(Overload)。
注意:方法重载的返回值类型通常都是相同的。
方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。
举个例子,String类提供了多个重载方法indexOf(),可以查找子串:
-
int indexOf(int ch):根据字符的Unicode码查找;
-
int indexOf(String str):根据字符串查找;
-
int indexOf(int ch, int fromIndex):根据字符查找,但指定起始位置;
-
int indexOf(String str, int fromIndex)根据字符串查找,但指定起始位置。
package test10;
public class Main {
public static void main(String[] args) {
String str = "Hello, my friend, thanking you for reading my blog!";
int n1 = str.indexOf('H');
System.out.println(n1);//0
int n2 = str.indexOf("Hello");
System.out.println(n2);//0
int n3 = str.indexOf('f',3);
System.out.println(n3);//10
int n4 = str.indexOf("you",4);
System.out.println(n4);//27
}
}
(5)继承
-
继承是面向对象编程的一种强大的代码复用方式;
-
Java只允许单继承,所有类最终的根类是Object;
-
protected允许子类访问父类的字段和方法;
-
子类的构造方法可以通过super()调用父类的构造方法;
-
可以安全地向上转型为更抽象的类型;
-
可以强制向下转型,最好借助instanceof判断;
-
子类和父类的关系是is,has关系不能用继承。
继承树:
┌───────────┐
│ Object │
└───────────┘
▲
│
┌───────────┐
│ Person │
└───────────┘
▲
│
┌───────────┐
│ Student │
└───────────┘
方法重载是指多个方法的方法名相同,但各自的参数不同;
重载方法应该完成类似的功能,参考String的indexOf();
重载方法返回值类型应该相同。
方法名相同,但各自的参数不同,称为方法重载(Overload)。
注意:方法重载的返回值类型通常都是相同的。
方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。
举个例子,String类提供了多个重载方法indexOf(),可以查找子串:
-
int indexOf(int ch):根据字符的Unicode码查找;
-
int indexOf(String str):根据字符串查找;
-
int indexOf(int ch, int fromIndex):根据字符查找,但指定起始位置;
-
int indexOf(String str, int fromIndex)根据字符串查找,但指定起始位置。
继承是面向对象编程的一种强大的代码复用方式;
Java只允许单继承,所有类最终的根类是Object;
protected允许子类访问父类的字段和方法;
子类的构造方法可以通过super()调用父类的构造方法;
可以安全地向上转型为更抽象的类型;
可以强制向下转型,最好借助instanceof判断;
子类和父类的关系是is,has关系不能用继承。
继承树:
┌───────────┐ │ Object │ └───────────┘ ▲ │ ┌───────────┐ │ Person │ └───────────┘ ▲ │ ┌───────────┐ │ Student │ └───────────┘
package test10;
public class Main
{
public static void main(String[] args)
{
Student stu = new Student("007");
System.out.println(stu);//Jack 007 18
//向上转型:理解继承树,再来看这个就不难。在继承树中越往上的辈分越大,向上转型就是用父类对象创建子类实例。
Person per = new Student("008");
System.out.println(per);//Jack 008 18
//因为Student继承自Person,因此,它拥有Person的全部功能。Person类型的变量,如果指向Student类型的实例,对它进行操作,是没有问题的!
//这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。
//向下转型:用子类对象强制创建父类实例。这想想都很难,有一种大逆不道的感觉……
Student stu1 = (Student)new Person("Kangkang",18);
System.out.println(stu1);//【空】
//不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
//因此,向下转型很可能会失败。失败的时候,Java虚拟机会报ClassCastException。
//利用instanceof,在向下转型前可以先判断:
Person p = new Student("001");
if (p instanceof Student) {
// 只有判断成功才会向下转型:
Student s = (Student) p; // 一定会成功
}else
{
System.out.print("强转失败");
}
}
}
class Person//正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。
{
protected String name;//承有个特点,就是子类无法访问父类的private字段或者private方法。
protected int age;//这使得继承的作用被削弱了。为了让子类可以访问父类的字段,我们需要把private改为protected。用protected修饰的字段可以被子类访问
Person(String name,int age)
{
this.name = name;
this.age = age;
}
}
class Student extends Person//正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。
{
String name;
String Id;
Student(String Id)
{
super("Jack",18);//调用父类构造函数。
this.name = super.name;//super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName
this.Id = Id;
}
public String toString()
{
return name+" "+Id+" "+age;
}
}
(6)多态
-
子类可以覆写父类的方法(Override),覆写在子类中改变了父类方法的行为;
-
Java的方法调用总是作用于运行期对象的实际类型,这种行为称为多态;
-
final修饰符有多种作用:
-
final修饰的方法可以阻止被覆写;
-
final修饰的class可以阻止被继承;
-
final修饰的field必须在创建对象时初始化,随后不可修改。
Override(方法重写)和Overload(方法重载)区别:
Override和Overload不同的是,如果方法签名不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override。
package test10;
public class Main
{
public static void main(String[] args)
{
Car c = new Car();
c.run();//车在跑。如果在Car类中没有run方法,则此处调用输出人在跑。
System.out.println(c.run("法拉利"));//法拉利在跑
}
}
class Person
{
void run()
{
System.out.println("人在跑");
}
}
class Car extends Person
{
void run()//run方法重写
{
System.out.println("车在跑");
}
String run(String Carname)//run方法的重载
{
return Carname + "在跑";
}
}
多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。
多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。
观察下面代码,在方法 totalApples 中,参数只有AppleTree,所以totalApples只需要和AppleTree打交道就能算出所有苹果总数,而不需要和Monkey和Pig打交道。因为Monkey和Pig都是由AppleTree派生出的,也是AppleTree的另一种形态。
package test10;
public class Main
{
public static void main(String[] args)
{
//由苹果树派生出的类
AppleTree []at = new AppleTree[] {
new AppleTree(10),
new Monkey(5),
new Pig(2)
};
System.out.println(totalApples(at));//一共有17个苹果
}
public static int totalApples(AppleTree... at)
{
int total = 0;
for(AppleTree a : at)
{
total += a.getApple();
}
return total;
}
}
//1、先定义一个苹果树
class AppleTree
{
protected int apples;
public AppleTree(int apples)
{
this.apples = apples;
}
public int getApple()
{
return apples;
}
}
//2、由苹果树派生出猴子有多少个苹果
class Monkey extends AppleTree
{
public Monkey(int apples)
{
super(apples);
}
//Override
public int getApple()
{
if(apples > 0)
return apples;
return 0;
}
}
//3、由苹果树派生出猪有多少个苹果
class Pig extends AppleTree
{
public Pig(int apples)
{
super(apples);
}
//Override
public int getApple()
{
if(apples > 0)
return apples;
return 0;
}
}
覆写Object方法
因为所有的class最终都继承自Object,而Object定义了几个重要的方法:
- toString():把instance输出为String;
- equals():判断两个instance是否逻辑相等;
- hashCode():计算一个instance的哈希值。
- 在必要的情况下,我们可以覆写Object的这几个方法。例如:
//复写Object中的toString方法
public String toString()
{
return "这棵树上有"+apples+"个苹果。";
}
//复写Object中的equals方法
public boolean equals(Object o)
{
if(o instanceof AppleTree)
{
AppleTree p = (AppleTree) o;
return true;
}
return false;
}
//复写Object中的hashCode方法
public int hashCode()
{
return this.hashCode();
}
*在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。
class Person {
protected String name;
public String hello() {
return "Hello, " + name;
}
}
class Student extends Person {
@Override
public String hello() {
// 调用父类的hello()方法:
return super.hello() + "!";
}
}
final
继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override:
class Person {
protected String name;
public final String hello() {
return "Hello, " + name;
}
}
class Student extends Person {
// compile error: 不允许覆写
@Override
public String hello() {
}
}
*如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承
*对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改。
*
可以在构造方法中初始化final字段:
class Person {
public final String name;
public Person(String name) {
this.name = name;
}
}
这种方法更为常用,因为可以保证实例一旦创建,其final字段就不可修改。
(7)抽象类
如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法。
把一个方法声明为abstract,表示它是一个抽象方法,本身没有实现任何方法语句。因为这个抽象方法本身是无法执行的,所以,Person类也无法被实例化。编译器会告诉我们,无法编译Person类,因为它包含抽象方法。
必须把Person类本身也声明为abstract,才能正确编译它:
abstract class Person {
public abstract void Hello();
}
抽象类
如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。
因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。
使用abstract修饰的类就是抽象类。我们无法实例化一个抽象类:
Person p = new Person(); // 编译错误
无法实例化的抽象类有什么用?
因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。
例如,Person类定义了抽象方法Hello(),那么,在实现子类Student的时候,就必须覆写Hello()方法:
abstract class Person {
public abstract void Hello();
}
class Student extends Person {
@Override
public void Hello()
{
}
}
面向抽象编程
当我们定义了抽象类Person,以及具体的Student、Teacher子类的时候,我们可以通过抽象类Person类型去引用具体的子类的实例:
Person s = new Student();
Person t = new Teacher();
这种引用抽象类的好处在于,我们对其进行方法调用,并不关心Person类型变量的具体子类型:
// 不关心Person变量的具体子类型:
s.run();
t.run();
同样的代码,如果引用的是一个新的子类,我们仍然不关心具体类型:
// 同样不关心新的子类是如何实现run()方法的:
Person e = new Employee();
e.run();
这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
面向抽象编程的本质就是:
-
上层代码只定义规范(例如:abstract class Person);
-
不需要子类就可以实现业务逻辑(正常编译);
-
具体的业务逻辑由不同的子类实现,调用者并不关心。
-
通过abstract定义的方法是抽象方法,它只有定义,没有实现。抽象方法定义了子类必须实现的接口规范;
-
定义了抽象方法的class必须被定义为抽象类,从抽象类继承的子类必须实现抽象方法;
-
如果不实现抽象方法,则该子类仍是一个抽象类;
-
面向抽象编程使得调用者只关心抽象方法的定义,不关心子类的具体实现。
(8)接口
Java的接口(interface)定义了纯抽象规范,一个类可以实现多个接口;
接口也是数据类型,适用于向上转型和向下转型;
接口的所有方法都是抽象方法,接口不能定义实例字段;
接口可以定义default方法(JDK>=1.8)。
在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。
如果一个抽象类没有字段,所有方法全部都是抽象方法:
abstract class Person {
public abstract void run();
public abstract void jump();
public abstract void sing();
}
就可以把该抽象类改写为接口:interface。
在Java中,使用interface可以声明一个接口:
interface class Person {
void run();
void jump();
void sing();
}
所谓interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)。
*当一个具体的class去实现一个interface时,需要使用implements关键字。
*在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface
接口继承
一个interface可以继承自另一个interface。interface继承自interface使用extends,它相当于扩展了接口的方法。例如:
interface Hello {
void hello();
}
interface Person extends Hello {
void run();
String getName();
}
此时,Person接口继承自Hello接口,因此,Person接口现在实际上有3个抽象方法签名,其中一个来自继承的Hello接口。
default方法
在接口中,可以定义default方法。
实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段。
package test10;
public class Main
{
public static void main(String[] args)
{
Person2 p2 = new Student("小明");
p2.speak();
}
}
//创建一个接口
interface Person
{
void run();
void jump();
void sing();
}
//接口方法扩展
interface Person2 extends Person
{//显然,Person2 是Person接口的升级版,他比Person又新添了两个方法。
void eat();
void drink();
String getName();
default void speak()
{
System.out.println(getName()+"speak");
}
}
class Student implements Person2
{
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public void run() {};
public void jump() {};
public void sing() {};
public void eat() {};
public void drink() {};
}
(9)静态字段和静态方法
-
静态字段属于所有实例“共享”的字段,实际上是属于class的字段;
-
调用静态方法不需要实例,无法访问this,但可以访问静态字段和其他静态方法;
-
静态方法常用于工具类和辅助方法。
在一个class中定义的字段,我们称之为实例字段。实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。
还有一种字段,是用static修饰的字段,称为静态字段:static field。
实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。
package test10;
public class Main
{
public static void main(String[] args)
{
Student stu = new Student("kk",11);
Student2 stu2 = new Student2("cc",12);
stu.score = 100;
System.out.println(stu.score);//100
System.out.println(stu2.score);//100:因为此处的static修饰的score是静态字段,在对象实例中共享
}
}
class Person
{
protected String name;
protected int age;
static int score;
}
class Student extends Person
{
Student(String name, int age)
{
this.name = name;
this.age = age;
}
}
class Student2 extends Person
{
Student2(String name, int age)
{
this.name = name;
this.age = age;
}
}
静态方法
有静态字段,就有静态方法。用static修饰的方法称为静态方法。
调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。静态方法类似其它编程语言的函数。
因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。
通过实例变量也可以调用静态方法,但这只是编译器自动帮我们把实例改写成类名而已。
通常情况下,通过实例变量访问静态字段和静态方法,会得到一个编译警告。
静态方法经常用于工具类。例如:
-
Arrays.sort()
-
Math.random()
静态方法也经常用于辅助方法。注意到Java程序的入口main()也是静态方法。
-
子类可以覆写父类的方法(Override),覆写在子类中改变了父类方法的行为;
-
Java的方法调用总是作用于运行期对象的实际类型,这种行为称为多态;
-
final修饰符有多种作用:
-
final修饰的方法可以阻止被覆写;
-
final修饰的class可以阻止被继承;
-
final修饰的field必须在创建对象时初始化,随后不可修改。
-
Override(方法重写)和Overload(方法重载)区别:
Override和Overload不同的是,如果方法签名不同,就是Overload,Overload方法是一个新方法;如果方法签名相同,并且返回值也相同,就是Override。
多态是指,针对某个类型的方法调用,其真正执行的方法取决于运行时期实际类型的方法。
多态的特性就是,运行期才能动态决定调用的子类方法。对某个类型调用某个方法,执行的实际方法可能是某个子类的覆写方法。
观察下面代码,在方法 totalApples 中,参数只有AppleTree,所以totalApples只需要和AppleTree打交道就能算出所有苹果总数,而不需要和Monkey和Pig打交道。因为Monkey和Pig都是由AppleTree派生出的,也是AppleTree的另一种形态。
覆写Object方法
因为所有的class最终都继承自Object,而Object定义了几个重要的方法:
- toString():把instance输出为String;
- equals():判断两个instance是否逻辑相等;
- hashCode():计算一个instance的哈希值。
- 在必要的情况下,我们可以覆写Object的这几个方法。例如:
*在子类的覆写方法中,如果要调用父类的被覆写的方法,可以通过super来调用。
final
继承可以允许子类覆写父类的方法。如果一个父类不允许子类对它的某个方法进行覆写,可以把该方法标记为final。用final修饰的方法不能被Override:
*如果一个类不希望任何其他类继承自它,那么可以把这个类本身标记为final。用final修饰的类不能被继承
*对于一个类的实例字段,同样可以用final修饰。用final修饰的字段在初始化后不能被修改。
*
可以在构造方法中初始化final字段:
class Person {
public final String name;
public Person(String name) {
this.name = name;
}
}
这种方法更为常用,因为可以保证实例一旦创建,其final字段就不可修改。
如果父类的方法本身不需要实现任何功能,仅仅是为了定义方法签名,目的是让子类去覆写它,那么,可以把父类的方法声明为抽象方法。
把一个方法声明为abstract,表示它是一个抽象方法,本身没有实现任何方法语句。因为这个抽象方法本身是无法执行的,所以,Person类也无法被实例化。编译器会告诉我们,无法编译Person类,因为它包含抽象方法。
必须把Person类本身也声明为abstract,才能正确编译它:
abstract class Person { public abstract void Hello(); }
抽象类
如果一个class定义了方法,但没有具体执行代码,这个方法就是抽象方法,抽象方法用abstract修饰。
因为无法执行抽象方法,因此这个类也必须申明为抽象类(abstract class)。
使用abstract修饰的类就是抽象类。我们无法实例化一个抽象类:
Person p = new Person(); // 编译错误
无法实例化的抽象类有什么用?
因为抽象类本身被设计成只能用于被继承,因此,抽象类可以强迫子类实现其定义的抽象方法,否则编译会报错。因此,抽象方法实际上相当于定义了“规范”。
例如,Person类定义了抽象方法Hello(),那么,在实现子类Student的时候,就必须覆写Hello()方法:
abstract class Person { public abstract void Hello(); } class Student extends Person { @Override public void Hello() { } }面向抽象编程
当我们定义了抽象类Person,以及具体的Student、Teacher子类的时候,我们可以通过抽象类Person类型去引用具体的子类的实例:
Person s = new Student(); Person t = new Teacher();这种引用抽象类的好处在于,我们对其进行方法调用,并不关心Person类型变量的具体子类型:
// 不关心Person变量的具体子类型: s.run(); t.run();同样的代码,如果引用的是一个新的子类,我们仍然不关心具体类型:
// 同样不关心新的子类是如何实现run()方法的: Person e = new Employee(); e.run();这种尽量引用高层类型,避免引用实际子类型的方式,称之为面向抽象编程。
面向抽象编程的本质就是:
上层代码只定义规范(例如:abstract class Person);
不需要子类就可以实现业务逻辑(正常编译);
具体的业务逻辑由不同的子类实现,调用者并不关心。
通过abstract定义的方法是抽象方法,它只有定义,没有实现。抽象方法定义了子类必须实现的接口规范;
定义了抽象方法的class必须被定义为抽象类,从抽象类继承的子类必须实现抽象方法;
如果不实现抽象方法,则该子类仍是一个抽象类;
面向抽象编程使得调用者只关心抽象方法的定义,不关心子类的具体实现。
(8)接口
Java的接口(interface)定义了纯抽象规范,一个类可以实现多个接口;
接口也是数据类型,适用于向上转型和向下转型;
接口的所有方法都是抽象方法,接口不能定义实例字段;
接口可以定义default方法(JDK>=1.8)。
在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。
如果一个抽象类没有字段,所有方法全部都是抽象方法:
abstract class Person {
public abstract void run();
public abstract void jump();
public abstract void sing();
}
就可以把该抽象类改写为接口:interface。
在Java中,使用interface可以声明一个接口:
interface class Person {
void run();
void jump();
void sing();
}
所谓interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)。
*当一个具体的class去实现一个interface时,需要使用implements关键字。
*在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface
接口继承
一个interface可以继承自另一个interface。interface继承自interface使用extends,它相当于扩展了接口的方法。例如:
interface Hello {
void hello();
}
interface Person extends Hello {
void run();
String getName();
}
此时,Person接口继承自Hello接口,因此,Person接口现在实际上有3个抽象方法签名,其中一个来自继承的Hello接口。
default方法
在接口中,可以定义default方法。
实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段。
package test10;
public class Main
{
public static void main(String[] args)
{
Person2 p2 = new Student("小明");
p2.speak();
}
}
//创建一个接口
interface Person
{
void run();
void jump();
void sing();
}
//接口方法扩展
interface Person2 extends Person
{//显然,Person2 是Person接口的升级版,他比Person又新添了两个方法。
void eat();
void drink();
String getName();
default void speak()
{
System.out.println(getName()+"speak");
}
}
class Student implements Person2
{
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
@Override
public void run() {};
public void jump() {};
public void sing() {};
public void eat() {};
public void drink() {};
}
(9)静态字段和静态方法
-
静态字段属于所有实例“共享”的字段,实际上是属于class的字段;
-
调用静态方法不需要实例,无法访问this,但可以访问静态字段和其他静态方法;
-
静态方法常用于工具类和辅助方法。
在一个class中定义的字段,我们称之为实例字段。实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。
还有一种字段,是用static修饰的字段,称为静态字段:static field。
实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。
package test10;
public class Main
{
public static void main(String[] args)
{
Student stu = new Student("kk",11);
Student2 stu2 = new Student2("cc",12);
stu.score = 100;
System.out.println(stu.score);//100
System.out.println(stu2.score);//100:因为此处的static修饰的score是静态字段,在对象实例中共享
}
}
class Person
{
protected String name;
protected int age;
static int score;
}
class Student extends Person
{
Student(String name, int age)
{
this.name = name;
this.age = age;
}
}
class Student2 extends Person
{
Student2(String name, int age)
{
this.name = name;
this.age = age;
}
}
静态方法
有静态字段,就有静态方法。用static修饰的方法称为静态方法。
调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。静态方法类似其它编程语言的函数。
因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。
通过实例变量也可以调用静态方法,但这只是编译器自动帮我们把实例改写成类名而已。
通常情况下,通过实例变量访问静态字段和静态方法,会得到一个编译警告。
静态方法经常用于工具类。例如:
-
Arrays.sort()
-
Math.random()
静态方法也经常用于辅助方法。注意到Java程序的入口main()也是静态方法。
Java的接口(interface)定义了纯抽象规范,一个类可以实现多个接口;
接口也是数据类型,适用于向上转型和向下转型;
接口的所有方法都是抽象方法,接口不能定义实例字段;
接口可以定义default方法(JDK>=1.8)。
在抽象类中,抽象方法本质上是定义接口规范:即规定高层类的接口,从而保证所有子类都有相同的接口实现,这样,多态就能发挥出威力。
如果一个抽象类没有字段,所有方法全部都是抽象方法:
abstract class Person {
public abstract void run();
public abstract void jump();
public abstract void sing();
}
就可以把该抽象类改写为接口:interface。
在Java中,使用interface可以声明一个接口:
interface class Person {
void run();
void jump();
void sing();
}
所谓interface,就是比抽象类还要抽象的纯抽象接口,因为它连字段都不能有。因为接口定义的所有方法默认都是public abstract的,所以这两个修饰符不需要写出来(写不写效果都一样)。
*当一个具体的class去实现一个interface时,需要使用implements关键字。
*在Java中,一个类只能继承自另一个类,不能从多个类继承。但是,一个类可以实现多个interface
接口继承
一个interface可以继承自另一个interface。interface继承自interface使用extends,它相当于扩展了接口的方法。例如:
interface Hello {
void hello();
}
interface Person extends Hello {
void run();
String getName();
}
此时,Person接口继承自Hello接口,因此,Person接口现在实际上有3个抽象方法签名,其中一个来自继承的Hello接口。
default方法
在接口中,可以定义default方法。
实现类可以不必覆写default方法。default方法的目的是,当我们需要给接口新增一个方法时,会涉及到修改全部子类。如果新增的是default方法,那么子类就不必全部修改,只需要在需要覆写的地方去覆写新增方法。
default方法和抽象类的普通方法是有所不同的。因为interface没有字段,default方法无法访问字段,而抽象类的普通方法可以访问实例字段。
静态字段属于所有实例“共享”的字段,实际上是属于class的字段;
调用静态方法不需要实例,无法访问this,但可以访问静态字段和其他静态方法;
静态方法常用于工具类和辅助方法。
在一个class中定义的字段,我们称之为实例字段。实例字段的特点是,每个实例都有独立的字段,各个实例的同名字段互不影响。
还有一种字段,是用static修饰的字段,称为静态字段:static field。
实例字段在每个实例中都有自己的一个独立“空间”,但是静态字段只有一个共享“空间”,所有实例都会共享该字段。
package test10;
public class Main
{
public static void main(String[] args)
{
Student stu = new Student("kk",11);
Student2 stu2 = new Student2("cc",12);
stu.score = 100;
System.out.println(stu.score);//100
System.out.println(stu2.score);//100:因为此处的static修饰的score是静态字段,在对象实例中共享
}
}
class Person
{
protected String name;
protected int age;
static int score;
}
class Student extends Person
{
Student(String name, int age)
{
this.name = name;
this.age = age;
}
}
class Student2 extends Person
{
Student2(String name, int age)
{
this.name = name;
this.age = age;
}
}
静态方法
有静态字段,就有静态方法。用static修饰的方法称为静态方法。
调用实例方法必须通过一个实例变量,而调用静态方法则不需要实例变量,通过类名就可以调用。静态方法类似其它编程语言的函数。
因为静态方法属于class而不属于实例,因此,静态方法内部,无法访问this变量,也无法访问实例字段,它只能访问静态字段。
通过实例变量也可以调用静态方法,但这只是编译器自动帮我们把实例改写成类名而已。
通常情况下,通过实例变量访问静态字段和静态方法,会得到一个编译警告。
静态方法经常用于工具类。例如:
Arrays.sort()
Math.random()
静态方法也经常用于辅助方法。注意到Java程序的入口main()也是静态方法。
package test10;
public class Main
{
public static void main(String[] args)
{
Person p = new Person("KK",18);
p.setScore(100);
System.out.print(p);
}
}
class Person
{
private String name;
private int age;
private static int score;
Person(String name, int age)
{
this.name = name;
this.age = age;
}
public static void setScore(int socres)
{
score = socres;
}
public String toString()
{
return name+" "+age+" "+" "+score;
}
}
(10)包
Java内建的package机制是为了避免class命名冲突;
JDK的核心类使用java.lang包,编译器会自动导入;
JDK的其它常用类定义在java.util.*,java.math.*,java.text.*,……;
包名推荐使用倒置的域名,例如org.apache。
(11)作用域
最佳实践
如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。
把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。
一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。
小结
Java内建的访问权限包括public、protected、private和package权限;
Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;
final修饰符不是访问权限,它可以修饰class、field和method;
一个.java文件只能包含一个public类,但可以包含多个非public类。
(12)内部类
Java的内部类可分为Inner Class、Anonymous Class和Static Nested Class三种:
-
Inner Class和Anonymous Class本质上是相同的,都必须依附于Outer Class的实例,即隐含地持有Outer.this实例,并拥有Outer Class的private访问权限;
-
Static Nested Class是独立类,但拥有Outer Class的private访问权限。
Inner Class创建实例:
Outer.Inner inner = outer.new Inner();
创建匿名类实例:
Runnable r = new Runnable() {
// 实现必要的抽象方法...
};
(13)class与jar
JVM通过环境变量classpath决定搜索class的路径和顺序;
不推荐设置系统环境变量classpath,始终建议通过-cp命令传入;
jar包相当于目录,可以包含很多.class文件,方便下载和使用;
MANIFEST.MF文件可以提供jar包的信息,如Main-Class,这样可以直接运行jar包。
(14)模块
Java 9引入的模块目的是为了管理依赖;
使用模块可以按需打包JRE;
使用模块对类的访问权限有了进一步限制。
笔记内容整理来自:
Java内建的package机制是为了避免class命名冲突;
JDK的核心类使用java.lang包,编译器会自动导入;
JDK的其它常用类定义在java.util.*,java.math.*,java.text.*,……;
包名推荐使用倒置的域名,例如org.apache。
最佳实践
如果不确定是否需要public,就不声明为public,即尽可能少地暴露对外的字段和方法。
把方法定义为package权限有助于测试,因为测试类和被测试类只要位于同一个package,测试代码就可以访问被测试类的package权限方法。
一个.java文件只能包含一个public类,但可以包含多个非public类。如果有public类,文件名必须和public类的名字相同。
小结
Java内建的访问权限包括public、protected、private和package权限;
Java在方法内部定义的变量是局部变量,局部变量的作用域从变量声明开始,到一个块结束;
final修饰符不是访问权限,它可以修饰class、field和method;
一个.java文件只能包含一个public类,但可以包含多个非public类。
(12)内部类
Java的内部类可分为Inner Class、Anonymous Class和Static Nested Class三种:
-
Inner Class和Anonymous Class本质上是相同的,都必须依附于Outer Class的实例,即隐含地持有Outer.this实例,并拥有Outer Class的private访问权限;
-
Static Nested Class是独立类,但拥有Outer Class的private访问权限。
Inner Class创建实例:
Outer.Inner inner = outer.new Inner();
创建匿名类实例:
Runnable r = new Runnable() {
// 实现必要的抽象方法...
};
(13)class与jar
JVM通过环境变量classpath决定搜索class的路径和顺序;
不推荐设置系统环境变量classpath,始终建议通过-cp命令传入;
jar包相当于目录,可以包含很多.class文件,方便下载和使用;
MANIFEST.MF文件可以提供jar包的信息,如Main-Class,这样可以直接运行jar包。
(14)模块
Java 9引入的模块目的是为了管理依赖;
使用模块可以按需打包JRE;
使用模块对类的访问权限有了进一步限制。
笔记内容整理来自:
Java的内部类可分为Inner Class、Anonymous Class和Static Nested Class三种:
-
Inner Class和Anonymous Class本质上是相同的,都必须依附于Outer Class的实例,即隐含地持有Outer.this实例,并拥有Outer Class的private访问权限;
-
Static Nested Class是独立类,但拥有Outer Class的private访问权限。
Inner Class创建实例:
Outer.Inner inner = outer.new Inner();
创建匿名类实例:
Runnable r = new Runnable() {
// 实现必要的抽象方法...
};
JVM通过环境变量classpath决定搜索class的路径和顺序;
不推荐设置系统环境变量classpath,始终建议通过-cp命令传入;
jar包相当于目录,可以包含很多.class文件,方便下载和使用;
MANIFEST.MF文件可以提供jar包的信息,如Main-Class,这样可以直接运行jar包。
(14)模块
Java 9引入的模块目的是为了管理依赖;
使用模块可以按需打包JRE;
使用模块对类的访问权限有了进一步限制。
笔记内容整理来自:
Java 9引入的模块目的是为了管理依赖;
使用模块可以按需打包JRE;
使用模块对类的访问权限有了进一步限制。
文章 - 廖雪峰的官方网站 (liaoxuefeng.com)https://www.liaoxuefeng.com/category/895882450960192



