2019版尚硅谷Java入门视频教程,哔哩哔哩链接:https://www.bilibili.com/video/BV1Kb411W75N?p=5
面向对象特性,是Java学习的核心、重头戏。
l 面向对象:Object Oriented Programming
l 面向过程:Procedure Oriented Programming
Ø 二者都是一种思想,面向对象是相对于面向过程而言的。面向过程,强调的是功能行为,以函数为最小单位,考虑怎么做。面向对象,将功能封装进对象,强调具备了功能的对象,以类/对象为最小单位,考虑谁来做。
Ø 面向对象更加强调运用人类在日常的思维逻辑中采用的思想方法与原则,如抽象、分类、继承、聚合、多态等。
l 面向对象的三大特征
Ø 封装 (Encapsulation)
Ø 继承 (Inheritance)
Ø 多态 (Polymorphism)
l 程序员从面向过程的执行者转化成了面向对象的指挥者
l 面向对象分析方法分析问题的思路和步骤:
Ø根据问题需要,选择问题所针对的现实世界中的实体。
Ø从实体中寻找解决问题相关的属性和功能,这些属性和功能就形成了概念世界中的类。
Ø把抽象的实体用计算机语言进行描述,形成计算机世界中类的定义。即借助某种程序语言,把类构造成计算机能够识别和处理的数据结构。
Ø将类实例化成计算机世界中的对象。对象是计算机世界中解决问题的最终工具。
1)类及类的构成成分:属性 方法 构造器 代码块 内部类
2)面向对象编程的特征:封装性 继承性 多态性 (抽象性)
3)其它的关键字:this super package import static final abstract interface …
类(class
)和对象(object
)是面向对象的核心概念。
Ø 类是对一类事物描述,是抽象的、概念上的定义
Ø 对象是实际存在的该类事物的每个个体,因而也称为实例(instance
)。“万事万物皆对象”
1) 属性(成员变量,Field
)
2)方法(成员方法,函数,Method
)
类声明的变量被称作对象,即类是用来创建对象的模板。 类的实现包括两部分:类声明和类体
修饰符 class 类名 {
属性声明;
方法声明;
}
说明:修饰符public:类可以被任意访问。类的正文要用{}括起来,例如:
public class Person{
private int age ; //声明私有变量 age
public void showAge(int i) { //声明方法showAge( )
age = i;
}
}
此处,Something类的文件名叫OtherThing.java
class Something {
private static void main(String[] something_to_do) {
System.out.println("Do something ...");
}
}
答案: 正确。从来没有人说过Java的class名字必须和其文件名相同。但public class的名字必须和文件名相同。
Generalization,又称为泛化,is-a的关系
java类的实例化,即创建类的对象
对象是实际存在的该类事物的每个个体,因而也称实例(instance)。 “万事万物皆对象”
Ø创建对象语法: 类名 对象名 = new 类名();
Ø使用对象名.对象成员
的方式访问对象成员(包括属性和方法)
l 我们也可以不定义对象的句柄,而直接调用这个对象的方法。这样的对象叫做匿名对象。
Ø如:new Person().shout();
l 使用情况
Ø如果对一个对象只需要进行一次方法调用,那么就可以使用匿名对象。
Ø我们经常将匿名对象作为实参传递给一个方法调用。
当一个对象被创建时,会对其中各种类型的成员变量自动进行初始化赋值。除了基本数据类型之外的变量类型都是引用类型
什么是方法(method、函数):
Ø 方法是类或对象行为特征的抽象,用来完成某个功能操作。在某些语言中也称为函数或过程。
Ø 将功能封装为方法的目的是,可以实现代码重用,简化代码
Ø Java里的方法不能独立存在,所有的方法必须定义在类里。提供某种功能的实现
返回值类型:
Ø 没有返回值:void。
Ø 有返回值,声明出返回值的类型。与方法体中return 返回值
搭配使用
方法名:属于标识符,命名时遵循标识符命名规则和规范,“见名知意”
形参列表:可以包含零个,一个或多个参数。多个参数时,中间用,
隔开
返回值:方法在执行完毕后返还给调用它的程序的数据。
//方法返回的数据类型可以是Java中的任何数据类型之一,当一个方法不需要返回数据时,返回类型必须是void。
int speak(){ //无参数的方法头
return 23;
}
int add(int x,int y,int z) { //有参数的方法头
return x+y+z;
}
//类中的方法也可分为实例方法和类方法.方法声明时,方法类型前面不加关键字static的是实例方法、加关键字static的是类方法。
public class Person{
private int age;
public int getAge() { return age; } //声明方法getAge
public void setAge(int i) { //声明方法setAge
age = i; //将参数i的值赋给类的成员变量age
}
}
l 方法通过方法名被调用,且只有被调用才会执行。
l 方法调用的过程分析:
Ø 关于返回值类型:
void
:表明此方法不需要返回值
有返回值的方法:在方法的最后一定有return + 返回值类型对应的变量
注意:void
与return
不可以同时出现一个方法内。像一对“冤家”。
Ø 方法内可以调用本类的其他方法或属性,但是不能在方法内再定义方法!
Ø 没有具体返回值的情况,返回值类型用关键字void
表示,那么方法体中可以不必使用return
语句。如果使用,仅用来结束方法。
Ø 定义方法时,方法的结果应该返回给调用者,交由调用者处理。
Ø 方法中只能调用方法或属性,不可以在方法内部定义方法。
重载的概念 |
---|
在同一个类中,允许存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可。 |
重载的特点: |
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别。 |
重载示例: |
//返回两个整数的和 int add(int x,int y){return x+y;} |
//返回三个整数的和 int add(int x,int y,int z){return x+y+z;} |
//返回两个小数的和 double add(double x,double y){return x+y;} |
要求:1.同一个类中(在同一类型中定义的方法)
2.方法名必须相同
3.方法的参数列表不同(①参数的个数不同②参数类型不同)
补充:方法的重载与方法的返回值类型没有关系!
方法的返回类型和参数的名字不参与比较
方法重载是一种多态的体现。
Overloaded
的方法是可以改变返回值的类型
//定义两个int型变量的和
public int getSum(int i,int j){
return i + j;
}
//定义三个int型变量的和
public int getSum(int i,int j,int k){
return i + j + k;
}
public void method1(int i,String str){
}
public void method1(String str1,int j){
}
使用重载方法,可以为编程带来方便。
例如,System.out.println()方法就是典型的重载方法,其内部的声明形式如下:
public void println(byte x)
public void println(short x)
public void println(int x)
public void println(long x)
public void println(float x)
JavaSE 5.0 中提供了**Varargs(variable number of arguments)**机制,允许直接定义能和多个实参相匹配的形参。从而,可以用一种更简单的方式,来传递个数可变的实参。
//JDK 5.0以前:采用数组形参来定义方法,传入多个同一类型变量
public static void test(int a ,String[] books);
//JDK5.0:采用可变个数形参来定义方法,传入多个同一类型变量
public static void test(int a ,String…books);
说明:
\1. 声明格式:方法名(参数的类型名 ...参数名)
\2. 可变参数:方法参数部分指定类型的参数个数是可变多个:0个,1个或多个
\3. 可变个数形参的方法与同名的方法之间,彼此构成重载
\4. 可变参数方法的使用与方法参数部分使用数组是一致的
\5. 方法的参数部分有可变形参,需要放在形参声明的最后
\6. 在一个方法的形参位置,最多只能声明一个可变个数形参
l 方法,必须由其所在类或对象调用才有意义。若方法含有参数:
Ø形参
:方法声明时的参数(方法声明时,方法小括号内的参数)
Ø实参
:方法调用时实际传给形参的参数值(调用方法时,实际传入的参数的值)
l Java的实参值如何传入方法呢?
Java里方法的参数传递方式只有一种:值传递。 即将实际参数值的副本(复制品)传入方法内,而参数本身不受影响。
Ø 形参是基本数据类型:将实参基本数据类型变量的“数据值”传递给形参(将实参的值传递给形参的基本数据类型的变量)
Ø 形参是引用数据类型:将实参引用数据类型变量的“地址值”传递给形参(将实参的引用类型变量的值(对应的堆空间的对象实体的首地址值)传递给形参的引用类型变量。)
【典型例题1】
public static void main(String[] args) {
TestArgsTransfer tt = new TestArgsTransfer();
int i = 10;
int j = 5;
System.out.println("i:" + i + " j:" + j);//i : 10 j : 5
// 交换变量i与j的值
// int temp = i;
// i = j;
// j = temp;
tt.swap(i, j);//将i的值传递给m,j的值传递给n
System.out.println("i:" + i + " j:" + j);//i : 10 j : 5
}
//定义一个方法,交换两个变量的值
public void swap(int m,int n){
int temp = m;
m = n;
n = temp;
System.out.println("m:" + m + " n:" + n);
}
【典型例题2】
public class TestArgsTransfer1 {
public static void main(String[] args) {
TestArgsTransfer1 tt = new TestArgsTransfer1();
DataSwap ds = new DataSwap();
System.out.println("ds.i:" + ds.i + " ds.j:" + ds.j);
tt.swap(ds);
System.out.println(ds);
System.out.println("ds.i:" + ds.i + " ds.j:" + ds.j);
}
//交换元素的值
public void swap(DataSwap d){
int temp = d.i;
d.i = d.j;
d.j = temp;
System.out.println(d);//打印引用变量d的值
}
}
class DataSwap{
int i = 10;
int j = 5;
}
【典型例题3】
//参数传递的值的级别不可以高于该参数的级别 。
class Circle {
double rad;
Circle(double r) {
rad=r;
}
void changeRad(double newRad) {
rad=newRad;
}
}
class Test {
public static void main(String args[]) {
Circle cir=new Circle(10);
cir.changeRad(100);//100(int)不能高于double
}
}
l 递归方法:一个方法体内调用它自身。
l 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
l 递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
//计算1-100之间所有自然数的和
public int sum(int num){
if(num == 1){
return 1;
}else{
return num + sum(num - 1);
}
}
封装是把过程和数据包围起来,对数据的访问只能通过已定义的界面。面向对象计算始于这个基本概念,即现实世界可以被描绘成一系列完全自治、封装的对象,这些对象通过一个受保护的接口访问其他对象。
l 为什么需要封装?封装的作用和含义?
Ø 我要用洗衣机,只需要按一下开关和洗涤模式就可以了。有必要了解洗衣机内部的结构吗?有必要碰电动机吗?
l 我们程序设计追求“高内聚,低耦合”。
Ø 高内聚
:类的内部数据操作细节自己完成,不允许外部干涉;
Ø 低耦合
:仅对外暴露少量的方法用于使用。
l 隐藏对象内部的复杂性,只对外公开简单的接口。便于外界调用,从而提高系统的可扩展性、可维护性。通俗的说,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。
l Java中通过将数据声明为私有的(private),再提供公共的(public)方法:getXxx()和setXxx()实现对该属性的操作,以实现下述目的:
Ø 隐藏一个类中不需要对外提供的实现细节;
Ø 使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑,限制对属性的不合理操作;
Ø 便于修改,增强代码的可维护性;
封装性的思想:①将类的属性私有化,②提供公共的方法(setter & getter)来实现调用。
public class Animal{
private int legs;//将属性legs定义为private,只能被Animal类内部访问
public void setLegs(int i){
if (i != 0 && i != 2 && i != 4){
System.out.println("Wrong number of legs!");
return;
}
legs=i;
}
public int getLegs(){
return legs;
}
}
public class Zoo{
public static void main(String args[]){
Animal xb=new Animal();
xb.setLegs(4); //xb.setLegs(-1000);
// xb.legs = -1000; //非法
System.out.println(xb.getLegs());
}
}
1.权限从大到小为:public protected 缺省(default) private
2.四种权限都可以用来修饰属性、方法、构造器
构造方法是一种特殊方法,它的名字必须与它所在的类的名字完全相同
,而且没有类型。
构造方法的作用是在创建对象时使用,主要是用来初始化各个成员变量,以便给类所创建的对象一个合理的初始状态。
构造器的特征
Ø 它具有与类相同的名称
Ø 它不声明返回值类型。(与声明为void不同)
Ø 不能被static、final、synchronized、abstract、native修饰,不能有return语句返回值
构造器的作用
创建对象;给对象进行初始化
Ø 如:Order o = new Order(); Person p = new Person(“Peter”,15);
Ø如同我们规定每个“人”一出生就必须先洗澡,我们就可以在“人”的构造器中加入完成“洗澡”的程序代码,于是每个“人”一出生就会自动完成“洗澡”,程序就不必再在每个人刚出生时一个一个地告诉他们要“洗澡”了。
语法格式
根据参数不同,构造器可以分为如下两类:
Ø 隐式无参构造器(系统默认提供)
Ø 显式定义一个或多个构造器(无参、有参)
注意:
Ø Java语言中,每个类都至少有一个
构造器
Ø 默认构造器的修饰符与所属类的修饰符一致
Ø 一旦显式定义了构造器,则系统不再提供默认构造器
Ø 一个类可以创建多个重载的构造器
Ø 父类的构造器不可被子类继承
构造器Constructor不能被继承,因此不能重写Override,但可以被重载Overload
空参
的构造器.不再提供
。权限修饰符 类名(形参){ }
类对象的属性赋值的先后顺序: ①属性的默认初始化
②属性的显式初始化
③通过构造器给属性初始化
④通过对象.方法
的方式给属性赋值
1.构造器一般用来创建对象的同时初始化对象。
class Person{
String name;
int age;
public Person(String n , int a){ name=n; age=a;}
}
2.构造器重载使得对象的创建更加灵活,方便创建各种不同的对象。
//构造器重载举例:
public class Person{
public Person(String name, int age, Date d) {this(name,age);…}
public Person(String name, int age) {…}
public Person(String name, Date d) {…}
public Person(){…}
}
3.构造器重载,参数列表必须不同
public class Person {
private String name;
private int age;
private Date birthDate;
public Person(String name, int age, Date d) {
this.name = name;
this.age = age;
this.birthDate = d;
}
public Person(String n, int a) {
//当形参与成员变量不同名时,this可省略,如果同名必须加this表示调用成员变量
name = n;
age = a;
}
public Person(String n, Date d) {
name = n;
birthDate = d;
}
public Person(String n) {
name = n;
age = 30;
}
}
除构造方法外,其它方法分为实例方法
和类方法
。
实例方法:
即可以操作实例变量也可以操作类变量,当对象调用实例方法时,方法中的成员变量就是指分配给该对象的成员变量,
其中的实例变量和其它对象的不相同,即占有不同的内存空间;类变量和其它对象的相同,即占有相的内存空间。
类方法:
只能操作类变量,当对象调用类方法时,方法中的成员变量一定都是类变量,也就是说该对象和所有的对象共享类变量。
注意:对象访问自己的变量以及调用方法受访问权限
的限制。
l JavaBean是一种Java语言写成的可重用组件。
l 所谓javaBean,是指符合如下标准的Java类:
Ø 类是公共的
Ø 有一个无参的公共的构造器
Ø 有属性,且有对应的get、set
方法
l 用户可以使用JavaBean将功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
public class TestJavaBean{
private String name; //属性一般定义为private
private int age;
public TestJavaBean(){}
public int getAge(){
return age;
}
public void setAge(int age){
this.age = age;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
this是什么?
l 在Java中,this关键字比较难理解,它的作用和其词义很接近。
Ø 它在方法内部使用,即这个方法所属对象的引用;
Ø 它在构造器内部使用,表示该构造器正在初始化的对象。
l this 可以调用类的属性、方法和构造器
什么时候使用this关键字呢?
Ø 当在方法内需要用到调用该方法的对象时,就用this。具体的:我们可以用this来区分属性和局部变量(通过this来调用成员变量)。比如:this.name = name;
注意
Ø 可以在类的构造器中使用this(形参列表)
的方式,调用本类中重载的其他的构造器!
Ø 明确:构造器中不能通过this(形参列表)
的方式调用自身构造器
Ø 如果一个类中声明了n
个构造器,则最多有n - 1
个构造器中使用了this(形参列表)
Ø this(形参列表)
必须声明在类的构造器的首行
!使用this()
必须放在构造器的首行!
Ø 在类的一个构造器中,最多只能声明一个this(形参列表)
Ø 使用在类中,可以用来修饰属性、方法、构造器
Ø 表示当前对象或者是当前正在创建的对象
Ø 当形参与成员变量重名时,如果在方法内部需要使用成员变量,必须添加this
来表明该变量是类成员
Ø 在任意方法内,如果使用当前类的成员变量或成员方法可以在其前面添加this
,增强程序的阅读性
Ø 在构造器中使用this(形参列表)
显式的调用本类中重载的其它的构造器
Ø 它在方法内部使用,即这个方法所属对象的引用;
Ø 它在构造器内部使用,表示该构造器正在初始化的对象。
Ø this
表示当前对象,可以调用类的属性、方法和构造器
Ø 使用this
调用本类中其他的构造器,保证至少有一个构造器是不用this的。
Ø this是Java的一个关键字,表示某个对象。this可以出现在实例方法和构造方法中,但不可以出现在类方法中
。
Ø this关键字出现在类的构造方法中时,代表使用该构造方法所创建的对象.
Ø 当this关键字出现实例方法中时,this就代表正在调用该方法的当前对象。
static {
System.out.println("我是静态代码块!!!!!!所以我不能用this~" + name);
}
在Java类中使用super来调用父类中的指定操作:
Ø super可用于访问父类中定义的属性
Ø super可用于调用父类中定义的成员方法
Ø super可用于在子类构造方法中调用父类的构造器
注意:
Ø 尤其当子父类出现同名成员时,可以用super
进行区分
Ø super的追溯不仅限于直接父类
Ø super和this的用法相像,this代表本类对象的引用
,super代表父类的内存空间的标识
1.super,相较于关键字this,可以修饰属性、方法、构造器
2.super修饰属性、方法:在子类的方法、构造器中,通过super.属性
或者super.方法
的形式,显式的调用父类的指定属性或方法。尤其是,当子类与父类有同名的属性、或方法时,调用父类中的结构的话,一定要用super
3.通过super(形参列表)
,显式的在子类的构造器中,调用父类指定的构造器!
4.用super操作被隐藏
的成员变量和方法
5.子类可以隐藏从父类继承的成员变量和方法,如果在子类中想使用被子类隐藏的成员变量或方法就可以使用关键字super。比如super.x、super.play()
就是访问和调用被子类隐藏的成员变量x
和方法play()
class Person {
protected String name="张三";
protected int age;
public String getInfo() {
return "Name: " + name + "\nage: " + age;
}
}
class Student extends Person {
protected String name = "李四";
private String school = "New Oriental";
public String getSchool(){
return school;
}
public String getInfo() {
return super.getInfo() +"\nschool: " +school;
}
}
public class TestStudent{
public static void main(String[] args){
Student st = new Student();
System.out.println(st.getInfo());
}
}
输出为:
Name: 张三
age: 0
school: New Oriental
l 子类中所有的构造器默认都会访问父类中空参数的构造器
l 当父类中没有空参数的构造器时,子类的构造器必须通过this(参数列表)
或者super(参数列表)
语句指定调用本类或者父类中相应的构造器。同时,只能”二选一”,且必须放在构造器的首行
l 如果子类构造器中既未显式调用父类或本类的构造器,且父类中又没有无参的构造器,则编译出错
调用父类构造器举例
No. | 区别点 | this | super |
---|---|---|---|
1 | 访问属性 | 访问本类中的属性,如果本类没有此属性则从父类中继续查找 | 直接访问父类中的属性 |
2 | 调用方法 | 访问本类中的方法,如果本类没有此方法则从父类中继续查找 | 直接访问父类中的方法 |
3 | 调用构造器 | 调用本类构造器,必须放在构造器的首行 | 调用父类构造器,必须放在子类构造器的首行 |
4 | 特殊 | 表示当前对象 | 无此概念 |
l package
语句作为Java源文件的第一条语句,指明该文件中定义的类所在的包。(若缺省该语句,则指定为无名包)。它的格式为:package 顶层包名.子包名 ;
l 声明源文件所在的包,写在程序的第一行。每.
一次,表示一层文件目录。包对应于文件系统的目录,package语句中,用 .
来指明包(目录)的层次;包通常用小写单词
,类名首字母通常大写
。
l 包对应于文件系统的目录,package语句中,用 .
来指明包**(目录)**的层次;
l 包通常用小写单词标识。通常使用所在公司域名的倒置:com.atguigu.xxx
包的作用:
l 包帮助管理大型软件系统:将功能相近的类划分到同一个包中。比如:MVC的设计模式
l 包可以包含类和子包,划分项目层次,便于管理
l 解决类命名冲突的问题
l 控制访问权限
注意:
\1. 在源文件中使用import显式的导入指定包下的类或接口
\2. 声明在包的声明和类的声明之间。
\3. 如果需要导入多个类或接口,那么就并列显式多个import语句即可
\4. 举例:可以使用java.util.*
的方式,一次性导入util包下所有的类或接口。
\5. 如果导入的类或接口是java.lang包下的,或者是当前包下的,则可以省略此import语句。(如果导入的类是java.lang包下的,如:System String Math
等,就不需要显式的声明。)
\6. 如果在代码中使用不同包下的同名的类。那么就需要使用类的全类名的方式指明调用的是哪个类。
\7. 如果已经导入java.a包下的类。那么如果需要使用a包的子包下的类的话,仍然需要导入。(导入java.lang.*只能导入lang包下的所有类或接口,不能导入lang的子包下的类或接口)
\8. import static
组合的使用:调用指定类或接口下的静态的属性或方法(import static 表示导入指定类的static的属性或方法
)
1.java.lang
----包含一些Java语言的核心类,如String、Math、Integer、 System和Thread,提供常用功能。
java.net
----包含执行与网络相关的操作的类和接口。
java.io
----包含能提供多种输入/输出功能的类。
java.util
----包含一些实用工具类,如定义系统特性、接口的集合框架类、使用与日期日历相关的函数。
java.text
----包含了一些java格式化相关的类
java.sql
----包含了java进行JDBC数据库编程的相关类/接口
java.awt
----包含了构成抽象窗口工具集(abstract window toolkits)的多个类,这些类被用来构建和管理应用程序的图形用户界面(GUI)。
java.applet
----包含applet运行所需的一些类。
继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法。对象的一个新类可以从现有的类中派生,这个过程称为类继承
。
新类继承了原始类的特性,新类称为原始类的派生类(子类)
,而原始类称为新类的基类(父类)
。派生类可以从它的基类那里继承方法和实例变量,并且类可以修改或增加新的方法使之更适合特殊的需要。
继承
是一种由已有的类创建新类的机制。
子类继承父类的成员变量。
子类继承父类的方法。
子类继承过程中可以实现变量隐藏与方法重写。
l 为什么要有继承?
Ø 多个类中存在相同属性和行为时,将这些内容抽取到单独一个类中,那么多个类无需再定义这些属性和行为,只要继承那个类即可。
l 此处的多个类称为子类(派生类)
,单独的这个类称为父类(基类或超类)
。可以理解为:“子类 is a 父类”
l 类继承语法规则: class Subclass extends SuperClass{ }
继承的作用:
Ø 继承的出现减少了代码冗余,提高了代码的复用性。
Ø 继承的出现,更有利于功能的扩展。
Ø 继承的出现让类与类之间产生了关系,提供了多态的前提。
注意:不要仅为了获取其他类中某个功能而去继承
l 子父类是相对的概念
Ø 子类继承了父类,就继承了父类的方法和属性。
Ø 在子类中,可以使用父类中定义的方法和属性,也可以创建新的数据和方法。
Ø 在Java 中,继承的关键字用的是extends
,即子类不是父类的子集,而是对父类的扩展
。
Ø 子类不能直接
访问父类中私有的**(private)**的成员变量和方法。
子类:A
父类(或基类 SuperClass):B
**明确:**当父类中有私有的属性或方法时,子类同样可以获取得到,只是由于封装性的设计,使得子类不可以直接调用
罢了。子类不能直接访问父类中私有的(private
)的成员变量和方法。
l 子类除了通过继承,获取父类的结构之外,还可以定义自己的特有的成分。extends
:子类是对父类功能的“扩展”,明确子类不是父类的子集。
l Java只支持单继承
和多层继承
,不允许多重继承
Ø 一个子类只能有一个
父类
Ø 一个父类可以派生出多个子类
class SubDemo extends Demo{ } //ok
class SubDemo extends Demo1,Demo2...//error
Ø 如果子类和父类不在同一个包中,那么,子类继承了父类的protected、public成员变量
做为子类的成员变量,并且继承了父类的protected、public方法
为子类的方法,继承的成员或方法的访问权限保持不变
。
Ø 子类创建对象时,子类的构造方法总是先调用父类
的某个构造方法,完成父类部分的创建;然后再调用子类
自己的构造方法,完成子类部分的创建 。如果子类的构造方法没有明显地指明使用父类的哪个构造方法,子类就调用父类的不带参数
的构造方法
Ø 子类在创建一个子类对象时,不仅子类中声明的成员变量被分配了内存,而且父类的所有的成员变量也都分配了内存空间
,但子类只能操作继承的那部分成员变量 。
Ø 子类可以通过继承的方法来操作子类未继承的变量和方法
在子类中可以根据需要对从父类中继承来的方法进行改造,也称方法的重置、覆盖。在程序执行时,子类的方法将覆盖
父类的方法。
要求:
\1. 子类重写的方法必须和父类被重写的方法具有相同的方法名称、参数列表
\2. 子类重写的方法的返回值类型不能大于
父类被重写的方法的返回值类型
\3. 子类重写的方法使用的访问权限不能小于
父类被重写的方法的访问权限
Ø子类不能重写
父类中声明为private权限的方法
\4. 子类方法抛出的异常不能大于
父类被重写方法的异常
1.重写的语法规则
Ø 如果子类继承了父类的实例方法,那么子类就有权利重写
这个方法。
Ø 方法重写是指:子类中定义一个方法,这个方法的类型和父类的方法的类型一致或是父类方法的类型的子类型,且这个方法的名字、参数个数、参数的类型和父类的方法完全相同.
2.重写的目的
Ø 子类通过方法的重写可以隐藏继承的方法
,子类通过方法的重写可以把父类的状态和行为改变为自身的状态和行为。
3.重写后方法的调用
Ø 子类创建的一个对象,如果子类重写了
父类的方法,则运行时系统调用的是子类重写的方法;
Ø 子类创建的一个对象,如果子类未重写
父类的方法,则运行时系统调用的是子类继承的方法;
4.重写的注意事项
重写父类的方法时,不允许降低方法的访问权限,但可以提高访问权限
(访问限制修饰符按访问权限从高到低的排列顺序是:public、protected、友好的(default)、private
。)
子类与父类中同名同参数的方法必须同时声明为非static的(即为重写),或者同时声明为static的(不是重写)。因为static方法是属于类的
,子类无法覆盖父类的方法。
例1:
public class Person {
public String name;
public int age;
public String getInfo() {
return "Name: "+ name + "\n" +"age: "+ age;
}
}
public class Student extends Person {
public String school;
public String getInfo() { //重写方法
return "Name: "+ name + "\nage: "+ age + "\nschool: "+ school;
}
public static void main(String args[]){
Student s1=new Student();
s1.name="Bob";
s1.age=20;
s1.school="school2";
//Name:Bob age:20 school:school2
System.out.println(s1.getInfo());
}
}
Person p1=new Person();
//调用Person类的getInfo()方法
p1.getInfo();
Student s1=new Student();
//调用Student类的getInfo()方法
s1.getInfo();
这是一种“多态性”:同名的方法,用不同的对象来区分调用的是哪一个方法。
例2:
class Parent {
public void method1() {}
}
class Child extends Parent {
//非法,子类中的method1()的访问权限private比被覆盖方法的访问权限public弱
private void method1() {}
}
public class UseBoth {
public static void main(String[] args) {
Parent p1 = new Parent();
Child p2 = new Child();
p1.method1();
p2.method1();
}
}
重载
:“两同一不同”:同一个类,同一个方法名,不同的参数列表。 注:方法的重载与方法的返回值无关!构造器是可以重载的
重写
:(前提:在继承的基础之上,子类在获取了父类的结构以后,可以对父类中同名的方法进行重构
)
1.方法的重写Overriding
和重载Overloading
是Java多态性的不同表现。
2.重写Overriding是父类与子类之间多态性
的一种表现,重载Overloading是一个类中多态性
的一种表现。
3.如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写 (Overriding)
。
4.子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被屏蔽
了。
5.如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。
public class TestDog {
public static void main(String[] args) {
Dog d = new Dog();
d.setAge(10);
d.setName("小明");
d.setHostName("花花");
System.out.println("name:" + d.getName() + " age:" + d.getAge()
+ "hostName:" + d.getHostName());
System.out.println(d.toString());
}
}
// 生物
class Creator {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Creator() {
super();
System.out.println("this is Creator's constructor");
}
}
// 动物类
class Animal extends Creator {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Animal() {
super();
System.out.println("this is Animal's constructor");
}
}
// 狗
class Dog extends Animal {
private String hostName;
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public Dog() {
super();
System.out.println("this is Dog's constructor");
}
}
多态性是指允许不同类的对象对同一消息作出响应。多态的基础是继承
,多态性包括参数化多态性
和包含多态性
。
多态性语言具有灵活、抽象、行为共享、代码共享的优势,很好的解决了应用程序函数同名问题。
多态性就是指父类的某个方法被其子类重写时,可以各自产生
自己的功能行为。
多态是面向对象编程的又一重要特性。子类可以体现多态,即子类可以根据各自的需要重写的父类的某个方法,子类通过方法的重写可以把父类的状态和行为改变为自身的状态和行为。
l 多态性,是面向对象中最重要的概念,在Java中的两种体现:
1.方法的重载(overload)和重写(overwrite)
2.对象的多态性:父类的引用指向子类的对象
Ø可以直接应用在抽象类和接口上
注意:子类对象的多态性,并不使用于属性。
l Java引用变量有两个类型:编译时类型和运行时类型。编译时类型由声明该变量时使用的类型
决定,运行时类型由实际赋给该变量的对象
决定。简称:编译时,看左边;运行时,看右边。
Ø若编译时类型和运行时类型不一致,就出现了对象的多态性(Polymorphism)
Ø多态情况下,“看左边”:看的是父类的引用(父类中不具备子类特有的方法)
“看右边”:看的是子类的对象(实际运行的是子类重写父类的方法)
l 对象的多态 — 在Java中,子类的对象可以替代父类的对象使用
Ø 一个变量只能有一种
确定的数据类型
Ø 一个引用类型变量可能指向(引用)多种
不同类型的对象
Person p = new Student();
Object o = new Person();//Object类型的变量o,指向Person类型的对象
System.out.println(o);//Person@69d0a921
o = new Student(); //Object类型的变量o,指向Student类型的对象
System.out.println(o);//Student@446cdf90
子类继承父类
Ø若子类重写了父类方法,就意味着子类里定义的方法彻底覆盖
了父类里的同名方法,系统将不可能把父类里的方法转移到子类中。
Ø对于实例变量
则不存在这样的现象,即使子类里定义了与父类完全相同的实例变量,这个实例变量依然不可能覆盖
父类中定义的实例变量
多态小结
1 多态作用:Ø 提高了代码的通用性,常称作接口重用
2 多态使用的前提:①要有继承关系 ②要有方法的重写
3 成员方法:
Ø 编译时:要查看引用变量所声明的类中是否有所调用的方法。
Ø 运行时:调用实际new的对象所属的类中的重写方法。
4 成员变量:
Ø 不具备多态性,只看引用变量所声明的类。
子类可看做是特殊的父类,所以父类类型的引用可以指向子类的对象:向上转型(upcasting)。
//A类是B类的父类,当用子类创建一个对象,并把这个对象的引用放到父类的对象中时,称对象a是对象b的上转型对象,比如:
A a;
a=new B();
或
A a;
B b=new B();
a=b;
一个引用类型变量如果声明为父类的类型,但实际引用的是子类对象,那么该变量就不能再访问子类中添加的属性和方法,例如:
Student m = new Student();
m.school = “pku”; //合法,Student类有school成员变量
Person e = new Student();
e.school = “pku”; //非法,Person类没有school成员变量
//属性是在编译时确定的,编译时e为Person类型,没有school成员变量,因而编译错误。
多态性应用举例:
//方法声明的形参类型为父类类型,可以使用子类的对象作为实参调用该方法
public class Test{
public void method(Person e) {
//……
e.getInfo();
}
public static void main(Stirng args[]){
Test t = new Test();
Student m = new Student();
t.method(m); //子类的对象m传送给父类类型的参数e
}
}
上转型对象的使用
1.上转型对象不能操作
子类新增的成员变量;不能调用
子类新增的方法。
2.上转型对象可以访问
子类继承或隐藏的成员变量,也可以调用
子类继承的方法或子类重写的实例方法。
3.如果子类重写了父类的某个实例方法后,当用上转型对象调用这个实例方法时一定是调用了子类重写
的实例方法。
例题1
class 类人猿 {
void crySpeak(String s) {
System.out.println(s);
}
}
class People extends 类人猿 {
void computer(int a,int b) {
int c=a*b;
System.out.println(c);
}
void crySpeak(String s) {
System.out.println("***"+s+"***");
}
}
public class Example5_10 {
public static void main(String args[]) {
类人猿 monkey;
People geng = new People();
//monkey是People对象geng的上转型对象
monkey = geng ;
//上转型对象不能操作子类新增的成员变量;不能调用子类新增的方法。
//monkey.computer("I love this game"); //不能这样写
//上转型对象`可以访问`子类继承或隐藏的成员变量,也`可以调用`子类继承的方法或子类重写的实例方法。
//如果子类重写了父类的某个实例方法后,当用上转型对象调用这个实例方法时`一定是调用了子类重写`的实例方法。
monkey.crySpeak("I love this game");
People people=(People)monkey;
people.computer(10,10);
}
}
输出结果为
***I love this game***
100
例题2
//抽象类,封装了两个行为标准
abstract class GirlFriend {
abstract void speak();
abstract void cooking();
}
class ChinaGirlFriend extends GirlFriend {
void speak(){
System.out.println("你好");
}
void cooking(){
System.out.println("水煮鱼");
}
}
class AmericanGirlFriend extends GirlFriend {
void speak(){
System.out.println("hello");
}
void cooking(){
System.out.println("roast beef");
}
}
class Boy {
GirlFriend friend;
void setGirlfriend(GirlFriend f){
friend = f;
}
void showGirlFriend() {
friend.speak();
friend.cooking();
}
}
public class Example5_12 {
public static void main(String args[]) {
GirlFriend girl = new ChinaGirlFriend();
Boy boy = new Boy();
boy.setGirlfriend(girl);
boy.showGirlFriend();
//girl是上转型对象
girl = new AmericanGirlFriend();
boy.setGirlfriend(girl);
boy.showGirlFriend();
}
}
输出结果为
你好
水煮鱼
hello
roast beef
①与向上转型相反,即是把父类对象转为子类对象。向下转型,使用强转符:()
②为了保证不报ClassCastException
,最好在向下转型前,进行判断: instanceof
Father f1 = new Father()
Son s1 = (Son)f1; // 这就叫 downcasting (向下转型)
// 若a是A类的实例,那么a也一定是A类的父类的实例。
if (p1 instanceof Woman) {
System.out.println("hello!");
Woman w1 = (Woman) p1;
w1.shopping();
}
if (p1 instanceof Man) {
Man m1 = (Man) p1;
m1.entertainment();
}
x instanceof A
:检验x
是否为类A
的对象,返回值为boolean
型。
Ø 要求x所属的类与类A必须是子类和父类
的关系,否则编译错误。
Ø 如果x属于类A的子类B,x instanceof A
的值也为true
。
public class Person extends Object {…}
public class Student extends Person {…}
public class Graduate extends Person {…}
----------------------------------------------
public void method1(Person e) {
if (e instanceof Person)
// 处理Person类及其子类对象
if (e instanceof Student)
//处理Student类及其子类对象
if (e instanceof Graduate)
//处理Graduate类及其子类对象
}
基本数据类型的Casting:
Ø 自动类型转换
:小的数据类型可以自动转换成大的数据类型
如long g=20; double d=12.0f
Ø 强制类型转换
:可以把大的数据类型强制转换(casting)成小的数据类型
如 float f=(float)12.0; int a=(int)1200L
对Java对象的强制类型转换称为造型
Ø 从子类到父类的类型转换可以自动进行
Ø 从父类到子类的类型转换必须通过造型(强制类型转换)
实现
Ø 无继承关系的引用类型间的转换是非法
的
Ø 在造型前可以使用instanceof
操作符测试一个对象的类型
对象类型转换举例
例题1
例题2
public class ConversionTest2{
public void method(Person e) {//设Person类中没有getschool()方法
// System.out.pritnln(e.getschool()); //非法,编译时错误
if(e instanceof Student){
Student me = (Student)e; //将e强制转换为Student类型
System.out.pritnln(me.getschool());
}
}
public static void main(Stirng args[]){
Test t = new Test();
Student m = new Student();
t.method(m);
}
}
抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面。抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节。抽象包括两个方面,一是过程抽象
,二是数据抽象
。
Object类仅有一个空参的构造器 public Object(){ }
Object类中的主要方法:
NO. | 方法名称 | 类型 | 描述 |
---|---|---|---|
1 | public Object() | 构造 | 构造方法 |
2 | public boolean equals(Object obj) | 普通 | 对象比较 |
3 | public int hashCode() | 普通 | 取得Hash码 |
4 | public String toString() | 普通 | 对象打印时调用 |
equals的比较方法:
1.基本数据类型:根据基本数据类型的值判断是否相等。相等返回true,反之返回false
注:两端数据类型可以不同,在不同的情况下,也可以返回true。
2.引用数据类型:比较引用类型变量的地址值是否相等。
l 对称性:如果x.equals(y)
返回是true
,那么y.equals(x)
也应该返回是true
。
l 自反性:x.equals(x)
必须返回是true
。
l 传递性:如果x.equals(y)
返回是true
,而且y.equals(z)
返回是true
,那么z.equals(x)
也应该返回是true
。
l 一致性:如果x.equals(y)
返回是true
,只要x
和y
内容一直不变,不管你重复x.equals(y)
多少次,返回都是true
。
l 任何情况下,x.equals(null)
,永远返回是false
;x.equals(和x不同类型的对象)
永远返回是false
。
1 == 既可以比较基本类型也可以比较引用类型。对于基本类型就是比较值,对于引用类型就是比较内存地址
2 equals的话,它是属于java.lang.Object类里面的方法,如果该方法没有被重写过默认也是**==;我们可以看到String等类的equals方法是被重写过的,而且String类在日常开发中用的比较多,久而久之,形成了equals**是比较值的错误观点。
3 具体要看自定义类里有没有重写Object的equals方法来判断。
4 通常情况下,重写equals方法,会比较类中的相应属性是否都相等。
当我们输出一个对象的引用时,会调用toString()
方法。
1.public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
当我们没有重写Object类的toString()方法时,打印的就是对象所在的类,以及对象实体在堆空间的位置
2.一般我们需要重写Object类的toString()方法,将此对象的各个属性值返回。
3.像String类、Date、File类、包装类都重写了toString()方法。
基本数据类型由于不是类,不能够使用java类库里提供的大量的方法。所以在设计上,我们让每一个基本数据类型都对应一个类,同时数据存储的范围还不变。
此时相当于基本数据类型就具有了类的特点,这些类即为包装类(wrapper 或封装类)
需要掌握的:
基本数据类型、包装类、String类之间的转换!
1.基本数据类型与对应的包装类有自动装箱、自动拆箱
如:int i = 10;
Integer i1 = i;//自动装箱
int j = i1;//自动拆箱
2.基本数据类型、包装类---->String类:调用String类的重载的valueOf(Xxx xx);
String类---->基本数据类型、包装类:调用相应的包装的parseXxx(String str);
注意:String str = "123";
int i = (int)str;
这是错误的转法。
针对八种基本定义相应的引用类型—包装类(封装类)
。有了类的特点,就可以调用类中的方法,Java才是真正的面向对象
//包装类在实际开发中用的最多的在于字符串变为基本数据类型。
String str1 = "30" ;
String str2 = "30.3" ;
int x = Integer.parseInt(str1) ; // 将字符串变为int型
System.out.println(x); //30
float f = Float.parseFloat(str2) ; // 将字符串变为float型
System.out.println(f);//30.3
使用范围:static,静态的。在Java类中,可用static修饰属性、方法、代码块(或初始化块)、内部类
被修饰后的成员具备以下特点:
Ø 随着类的加载而加载
Ø 优先于对象存在
Ø 修饰的成员,被所有对象所共享
Ø 访问权限允许时,可不创建对象,直接被类调用
static修饰属性(类变量(类属性)):
1.由类创建的所有的对象,都共用这一个属性
2.当其中一个对象对此属性进行修改,会导致其他对象对此属性的一个调用。vs 实例变量(非static修饰的属性,各个对象各自拥有一套副本)
3.类变量随着类的加载而加载的,而且独一份
4.静态的变量可以直接通过类.类变量
的形式来调用
5.类变量的加载是要早于对象
。所以当有对象以后,可以对象.类变量
使用。但是类.实例变量
是不行的。
6.类变量存在于静态域
中。
static修饰方法(类方法):
1.随着类的加载而加载,在内存中也是独一份
2.可以直接通过类.类方法
的方式调用
3.内部可以调用静态的属性或静态的方法,而不能调用非静态的属性或方法
。反之,非静态的方法是可以调用
静态的属性或静态的方法
注意:
静态的结构(static的属性、方法、代码块、内部类)的生命周期要早于
非静态的结构,同时被回收也要晚于
非静态的结构
静态的方法内是不可以有this
或super
关键字的!
类变量(类属性)由该类的所有实例共享(类属性为该类各个对象之间共享的变量。)
class Person {
private int id;
public static int total = 0;
public Person() {
total++;
id = total;
}
public static void main(String args[]){
Person Tom=new Person();
Tom.id=0;
total=100; // 不用创建对象就可以访问静态成员
}
}
public class OtherClass {
public static void main(String args[]) {
Person.total = 100; // 不用创建对象就可以访问静态成员
//访问方式:类名.类属性,类名.类方法
System.out.println(Person.total); //输出100
Person c = new Person();
System.out.println(c.total); //输出101
}
}
没有对象的实例时,可以用 类名.方法名() 的形式访问由static
标记的类方法。
在static方法内部只能访问类的static属性,不能访问类的非static属性。
class Person {
private int id;
private static int total = 0;
public static int getTotalPerson() {
//id++; //非法
return total;
}
public Person() {
total++;
id = total;
}}
public class TestPerson {
public static void main(String[] args) {
System.out.println("Number of total is " +Person.getTotalPerson());
//没有创建对象也可以访问静态方法
Person p1 = new Person();
System.out.println( "Number of total is "+ Person.getTotalPerson());
}
}
输出为:
Number of total is 0
Number of total is 1
l 因为不需要实例就可以访问static方法,因此static方法内部不能有this
,也不能有super
l 重载的方法需要同时为static
的或者非static
的
l static修饰的方法不能被重写
class Person {
private int id;
private static int total = 0;
public static void setTotalPerson(int total){
this.total=total; //非法,在static方法中不能有this,也不能有super
}
public Person() {
total++;
id = total;
}
}
public class TestPerson {
public static void main(String[] args) {
Person.setTotalPerson(3);
}
}
l 设计模式是在大量的实践中总结和理论化之后优选的代码结构、编程风格、以及解决问题的思考方式。设计模免去我们自己再思考和摸索。就像是经典的棋谱,不同的棋局,我们用不同的棋谱。
l 所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例
,并且该类只提供一个取得其对象实例的方法。
l 如果我们要让类在一个虚拟机中只能产生一个对象,我们首先必须将类的构造器的访问权限设置为private
,这样,就不能用new操作符在类的外部产生类的对象了,但在类内部仍可以产生该类的对象。
l 因为在类的外部开始还无法得到类的对象,只能调用该类的某个静态方法
以返回类内部创建的对象,静态方法只能访问
类中的静态成员变量,所以,指向类内部产生的该类对象的变量也必须定义成静态的
。
//饿汉式1
class Bank{
//1.私有化构造器
private Bank(){}
//2.内部提供一个当前类的实例(创建类的对象,同时设置为private的,通过公共的来调用,体现封装性)
//4.要求此对象也为static的(此实例也必须静态化)
private static Bank instance = new Bank();
//3.提供公共的静态的方法,返回当前类的对象
public static Bank getInstance(){
return instance;
}
}
//饿汉式2
class Bank{
//1.私有化构造器
private Bank(){}
//2.创建类的对象,同时设置为private的,通过公共的来调用,体现封装性
//4.要求此对象也为static的
private static Bank instance = null;
static{
instance = new Bank();
}
//3.此公共的方法,必须为static
public static Bank getInstance(){
return instance;
}
}
class Single{
//private的构造器,不能在类的外部创建该类的对象
private Single() {}
//私有的,只能在类的内部访问
private static Single onlyone = new Single();
//getSingle()为static,不用创建对象即可访问
public static Single getSingle() {
return onlyone;
}
}
public class TestSingle{
public static void main(String args[]) {
Single s1 = Single.getSingle(); //访问静态方法
Single s2 = Single.getSingle();
if (s1==s2){
System.out.println("s1 is equals to s2!");
}
}
}
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
if(instance == null){//可能存在线程安全问题的!
instance = new Bank();
}
return instance;
}
}
class Singleton{
//1.将构造器私有化,保证在此类的外部,不能调用本类的构造器。
private Singleton(){
}
//2.先声明类的引用
//4.也需要配合static的方法,用static修饰此类的引用。
private static Singleton instance = null;
//3.设置公共的方法来访问类的实例
public static Singleton getInstance(){
//3.1如果类的实例未创建,那些先要创建,然后返回给调用者:本类。因此,需要static 修饰。
if(instance == null){
instance = new Singleton();
}
//3.2 若有了类的实例,直接返回给调用者。
return instance;
}
}
Ø 网站的计数器,一般也是单例模式实现,否则难以同步。
Ø 应用程序的日志应用,一般都使用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
Ø 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。
Ø 项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,都生成一个对象去读取。
Ø Application 也是单例的典型应用
Ø Windows的Task Manager (任务管理器)就是很典型的单例模式
Ø Windows的Recycle Bin (回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
由于单例模式只生成一个实例,减少了系统性能开销,当一个对象的产生需要比较多的资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后永久驻留内存的方式来解决。
更多关于单例模式的讲解可以去看我的其他博客,点击前往
l 由于Java虚拟机需要调用类的main()
方法,所以该方法的访问权限必须是public
,又因为Java虚拟机在执行main()方法时不必创建对象,所以该方法必须是static
的,该方法接收一个String类型的数组参数
,该数组中保存执行Java命令时传递给所运行的类的参数。
l 又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。
public static void main(String[] args){
//方法体
}
//1.main()是一个方法,是主方法,为程序的入口
//2.权限修饰符:public protected 缺省 private ---面向对象的封装性
//3.对于方法来讲:static final abstract
//4.方法的返回值:void / 具体的返回值类型(基本的数据类型 & 引用数据类型),方法内部一定要有return
//5.方法名:命名的规则:xxxYyyZzz。给方法命名时,要见名之意
//6.形参列表:同一个方法名不同的形参列表的诸多个方法间构成重载。形参 & 实参---方法的参数传递机制:值传递
//7.方法体:方法定义的是一种功能,具体的实现由方法体操作。
一个类中初始化块若有修饰符,则只能被static修饰,称为静态代码块(static block ),
当类被载入时,类属性的声明和静态代码块先后顺序被执行,且只被执行一次。
静态代码块:用static 修饰的代码块
1.可以有输出语句。
2.可以对类的属性、类的声明进行初始化操作。
3.不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
4.若有多个静态的代码块,那么按照从上到下的顺序依次执行。
5.静态代码块的执行要先于非静态代码块。
6.静态代码块随着类的加载而加载,且只执行一次。
非静态代码块:没有static修饰的代码块
1.可以有输出语句。
2.可以对类的属性、类的声明进行初始化操作。
3.除了调用非静态的结构外,还可以调用静态的变量或方法。
4.若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
5.每次创建对象的时候,都会执行一次。且先于构造器执行
class Person {
public static int total;
static {
total = 100;
System.out.println("in static block!");
}
}
public class Test {
public static void main(String[] args) {
System.out.println("total = "+ Person.total);
//当类被载入时,类属性的声明和静态代码块先后顺序被执行,且只被执行一次。
System.out.println("total = "+ Person.total);
}
}
输出:
in static block
total=100
total=100
1.声明成员变量的默认初始化
2.显式初始化、多个初始化块依次被执行(同级别下按先后顺序执行)
3.构造器再对成员进行初始化操作
4.通过 对象.属性
或 对象.方法
的方式,可多次给属性赋值
**此常量在哪里赋值:**①此常量不能使用默认初始化 ②可以显式的赋值、代码块、构造器。
变量用static final修饰:全局常量
。比如:Math 类的PI
1.final修饰类
final class A{
}
class B extends A{ //错误,不能被继承。
}
2.final修饰方法
class A{
public final void print(){
System.out.println(“A”);
}
}
class B extends A{
public void print(){ //错误,不能被重写。
System.out.println(“尚硅谷”);
}
}
3.final修饰变量——常量
class A{
private final String INFO = “atguigu”; //声明常量
public void print(){
//INFO = “尚硅谷”; //错误,final修饰的只能赋一次值
}
}
常量名要大写,内容不可修改。——如同古代皇帝的圣旨。
static final:全局常量
final关键字可以修饰类、成员变量和方法中的局部变量。
可以使用final将类声明为final类。final类不能被继承,即不能有子类。
如: final class A {
… …
}
如果用final修饰父类中的一个方法,那么这个方法不允许子类重写。
如果成员变量或局部变量被修饰为final的,就是常量
随着继承层次中一个个新子类的定义,类变得越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特征。有时将一个父类设计得非常抽象,以至于它没有具体的实例,这样的类叫做抽象类。
抽象类是用来模型化那些父类无法确定全部实现,而是由其子类提供具体实现的对象的类。
//抽象类,封装了两个行为标准
abstract class GirlFriend {
abstract void speak();
abstract void cooking();
}
1.abstract修饰类:抽象列
抽象类类有如下特点:
1.和普通的类相比,abstract类里可以有abstract方法。也可以没有。
对于abstract方法,只允许声明,不允许实现,而且不允许使用final修饰abstract方法。
2.对于abstract类,不能使用new运算符创建该类的对象,只能产生其子类,由子类创建对象。
3.如果一个类是abstract类的子类,它必须具体实现父类的所有的abstract方法。
4.不可被实例化
5.抽象类有构造器 (凡是类都有构造器)
6.抽象方法所在的类,一定是抽象类。
7.抽象类中可以没有抽象方法。
当我们设计一个类,不需要创建此类的实例时候,就可以考虑将其设置为抽象的,
由其子类实现这个类的抽象方法以后,就行实例化
2.abstract修饰方法:抽象方法
1)格式:没有方法体,包括{}.如:public abstract void eat();
2)抽象方法只保留方法的功能,而具体的执行,交给继承抽象类的子类,由子类重写此抽象方法。
3)若子类继承抽象类,并重写了所有的抽象方法,则此类是一个"实体类",即可以实例化
4)若子类继承抽象类,没有重写所有的抽象方法,意味着此类中仍有抽象方法,则此类必须声明为抽象的!
abstract class A{
abstract void m1( );
public void m2( ){
System.out.println("A类中定义的m2方法");
}
}
class B extends A{
void m1( ){
System.out.println("B类中定义的m1方法");
}
}
public class Test{
public static void main( String args[ ] ){
A a = new B( );
a.m1( );
a.m2( );
}
}
抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为方式。
解决的问题:
Ø 当功能内部一部分实现是确定的,一部分实现是不确定的。这时可以把不确定的部分暴露出去,让子类去实现。
Ø 换句话说,在软件开发中实现一个算法时,整体步骤很固定、通用,这些步骤已经在父类中写好了。但是某些部分易变,易变部分可以抽象出来,供不同子类实现。这就是一种模板模式。
举例
模板方法设计模式是编程中经常用得到的模式。各个框架、类库中都有他的影子,比如常见的有:
l 数据库访问的封装
l Junit单元测试
l JavaWeb的Servlet中关于doGet/doPost方法调用
l Hibernate中模板程序
l Spring中JDBCTemlate、HibernateTemplate等
l 接口的本质是契约,标准,规范,就像我们的法律一样,制定好后大家都要遵守。
l 为了克服Java单继承的缺点,Java使用了接口,一个类可以实现多个接口。有了接口,就可以得到多重继承的效果。
l 使用关键字interface
来定义一个接口。接口的定义和类的定义很相似,分为接口的声明
和接口体
。
1.接口声明
接口通过使用关键字interface
来声明
格式:interface 接口的名字
2.接口体
接口体中包含常量定义
和方法定义
两部分。接口体中只进行方法的声明,不许提供方法的实现,所以,方法的定义没有方法体,且用分号;
结尾。
l 接口(interface)是抽象方法和常量值的定义的集合。从本质上讲,接口是一种特殊的抽象类,这种抽象类中只包含常量和方法的定义,而没有变量和方法的实现。
l 实现接口类:
class SubClass implements InterfaceA{ }
一个类可以实现多个接口,接口也可以继承其它接口。
注意:
接口中的变量默认都是public static final
接口中默认只有abstract方法,没有普通方法,且所有方法的访问权限默认都是public
接口中的方法不能被static和final修饰,因为要重写所有接口中的方法。
接口中没有构造函数。
接口可以通过继承产生新的接口。
与继承关系类似,接口与实现类之间存在多态性
接口的主要用途就是被实现类实现。(面向接口编程)
实现接口的类中必须提供接口中所有方法的具体实现内容,方可实例化。否则,仍为抽象类。
Java提供的接口都在相应的包中,通过import语句不仅可以引入包中的类,也可以引入包中的接口,
例如:import java.io.*;
定义Java类的语法格式:先写extends,后写implements
class SubClass extends SuperClass implements InterfaceA{ }
一个类通过使用关键字implements声明自己实现一个或多个接口。
如:class A implements Printable,Addable
如果一个非抽象类实现了某个接口,那么这个类必须重写该接口的所有方法。
接口和类是并列关系,或者可以理解为一种特殊的类。从本质上讲,接口是一种特殊的抽象类,
这种抽象类中只包含常量和方法的定义(JDK7.0及之前),而没有变量和方法的实现。
接口(interface) 是与类并行的一个概念
1.接口可以看做是一个特殊的抽象类。是常量与抽象方法的一个集合,不能包含变量、一般的方法。
2.接口是没有构造器的。
3.接口定义的就是一种功能。此功能可以被类所实现(implements)。
比如:class CC extends DD implements AA
4.实现接口的类,必须要重写其中的所有的抽象方法,方可实例化。若没有重写所有的抽象方法,则此类仍为一个抽象类
5.类可以实现多个接口。----java 中的类的继承是单继承的
6.接口与接口之间也是继承的关系,而且可以实现多继承
7.接口与具体的实现类之间也存在多态性
在开发中,常看到一个类不是去继承一个已经实现好的类,而是要么继承抽象类,要么实现接口。
接口和abstract类的比较如下:
1.abstract类和接口都可以有abstract方法。
2.接口中只可以有常量,不能有变量;而abstract类中既可以有常量也可以有变量。
3.abstract类中也可以有非abstract方法,接口不可以。
No. | 区别点 | 抽象类 | 接口 |
---|---|---|---|
1 | 定义 | 包含一个抽象方法的类 | 抽象方法和全局常量的集合 |
2 | 组成 | 构造方法、抽象方法、普通方法、常量、变量 | 常量、抽象方法 |
3 | 使用 | 子类继承抽象类(extends) | 子类实现接口(implements) |
4 | 关系 | 抽象类可以实现多个接口 | 接口不能继承抽象类,但允许继承多个接口 |
5 | 常见设计模式 | 模板设计 | 工厂设计、代理设计 |
6 | 对象 | 都通过对象的多态性产生实例化对象 | |
7 | 局限 | 抽象类有单继承的局限 | 接口没有此局限 |
8 | 实际 | 作为一个模板 | 是作为一个标准或是表示一种能力 |
9 | 选择 | 如果抽象类和接口都可以使用的话,优先使用接口,因为避免单继承的局限 | |
10 | 特殊 | 一个抽象类中可以包含多个接口,一个接口中可以包含多个抽象类 |
接口回调是指:可以把实现某一接口的类创建的对象的引用赋给该接口声明的接口变量中,那么该接口变量就可以调用被类重写的接口方法。实际上,当接口变量调用被类重写的接口方法时,就是通知相应的对象调用这个方法。
//分析:
Com com;//声明接口对象
ImpleCom obj= new ImpleCom();//实现接口子类对象
com = obj; //接口回调
interface ShowMessage {
void 显示商标(String s);
}
class TV implements ShowMessage {
public void 显示商标(String s) {
System.out.println(s);
}
}
class PC implements ShowMessage {
public void 显示商标(String s) {
System.out.println(s);
}
}
public class Example6_2 {
public static void main(String args[]) {
ShowMessage sm; //声明接口变量
sm=new TV(); //接口变量中存放对象的引用
sm.显示商标("长城牌电视机"); //接口回调。
sm=new PC(); //接口变量中存放对象的引用
sm.显示商标("联想奔月5008PC机"); //接口回调
}
}
输出为:
长城牌电视机
联想奔月5008PC机
//接口的应用:工厂方法的设计模式
public class TestFactoryMethod {
public static void main(String[] args) {
IWorkFactory i = new StudentWorkFactory();
i.getWork().doWork();
IWorkFactory i1 = new TeacherWorkFactory();
i1.getWork().doWork();
}
}
interface IWorkFactory{
Work getWork();
}
class StudentWorkFactory implements IWorkFactory{
@Override
public Work getWork() {
return new StudentWork();
}
}
class TeacherWorkFactory implements IWorkFactory{
@Override
public Work getWork() {
return new TeacherWork();
}
}
interface Work{
void doWork();
}
class StudentWork implements Work{
@Override
public void doWork() {
System.out.println("学生写作业");
}
}
class TeacherWork implements Work{
@Override
public void doWork() {
System.out.println("老师批改作业");
}
}
输出为:
学生写作业
老师批改作业
代理模式是Java开发中使用较多的一种设计模式。代理设计就是为其他对象提供一种代理以控制对这个对象的访问。
//接口的应用:代理模式(静态代理)
public class TestProxy {
public static void main(String[] args) {
Object obj = new ProxyObject();
obj.action();
}
}
interface Object{
void action();
}
//代理类
class ProxyObject implements Object{
Object obj;
public ProxyObject(){
System.out.println("代理类创建成功");
obj = new ObjctImpl();
}
public void action(){
System.out.println("代理类开始执行");
obj.action();
System.out.println("代理类执行结束");
}
}
//被代理类
class ObjctImpl implements Object{
@Override
public void action() {
System.out.println("=====被代理类开始执行======");
System.out.println("=====具体的操作======");
System.out.println("=====被代理类执行完毕======");
}
}
输出为:
代理类创建成功
代理类开始执行
=====被代理类开始执行======
=====具体的操作======
=====被代理类执行完毕======
代理类执行结束
Java 8中,你可以为接口添加静态方法和默认方法。从技术角度来说,这是完全合法的,只是它看起来违反了接口作为一个抽象定义的理念。
静态方法:使用 static 关键字修饰。可以通过接口直接调用静态方法,并执行其方法体。我们经常在相互一起使用的类中使用静态方法。你可以在标准库中找到像Collection/Collections
或者Path/Paths
这样成对的接口和类。
默认方法:默认方法使用 default 关键字修饰。可以通过实现类对象来调用。我们在已有的接口中提供新方法的同时,还保持了与旧版本代码的兼容性。比如:java 8 API中对Collection、List、Comparator
等接口提供了丰富的默认方法。
l 若一个接口中定义了一个默认方法,而另外一个接口中也定义了一个同名同参数的方法(不管此方法是否是默认方法),在实现类同时实现了这两个接口时,会出现:接口冲突
。
Ø 解决办法:实现类必须覆盖
接口中同名同参数的方法,来解决冲突。
l 若一个接口中定义了一个默认方法,而父类中也定义了一个同名同参数的非抽象方法,则不会出现冲突问题。因为此时遵守:类优先原则
。接口中具有相同名称和参数的默认方法会被忽略。
接口冲突的解决方式
l 当一个事物的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务
,那么整个内部的完整结构最好使用内部类。
l 在Java中,允许一个类的定义位于另一个类的内部,前者称为内部类
,后者称为外部类
。(Java支持在一个类中声明另一个类,这样的类称作内部类,而包含内部类的类成为内部类的外嵌类
。)
l Inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称。
Ø Inner class的名字不能
与包含它的外部类类名相同;
Ø Inner class可以使用
外部类的私有数据,因为它是外部类的成员,同一个类的成员之间可相互访问。而外部类要访问内部类中的成员需要:内部类.成员或者内部类对象.成员
。
l 分类: 成员内部类(分static成员内部类
和非static成员内部类
)
局部内部类(不谈修饰符)
匿名内部类
内部类的使用规则:
声明内部类如同在类中声明方法或变量一样,一个类把内部类看作是自己的成员
。
外嵌类的类体中可以用内部类声明的对象作为外嵌类的成员
。
外嵌类的成员变量在内部类中仍然有效,内部类中的方法也可以调用外嵌类中的方法。
内部类的类体中不可以声明类变量和类方法
。
/*外嵌类和内部类在编译时,生成两个.class文件。
RedCowForm.class
RedCowForm$RedCow.class
*/
public class RedCowForm {
static String formName;
RedCow cow; //内部类声明对象
RedCowForm() {}
RedCowForm(String s) {
cow = new RedCow(150,112,5000);
formName = s;
}
public void showCowMess() {
cow.speak();
}
class RedCow { //内部类开始
String cowName = "红牛";
int height,weight,price;
RedCow(int h,int w,int p){
height = h;
weight = w;
price = p;
}
void speak() {
System.out.println("偶是"+cowName+",身高:"+height);
}
} //内部类结束
}
public class A{
private int s = 111;
public class B {
private int s = 222;
public void mb(int s) {
System.out.println(s); // 局部变量s
System.out.println(this.s); // 内部类对象的属性s
System.out.println(A.this.s); // 外层类对象属性s
}
}
public static void main(String args[]){
A a = new A();
A.B b = a.new B();
b.mb(333);
}
}
输出为:
333
222
111
l 局部内部类的特点
Ø 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的.class
文件,但是前面冠以外部类的类名和$符号,以及数字编号
。
Ø 只能
在声明它的方法或代码块中使用,而且是先声明后使用
。除此之外的任何地方都不能使用
该类。
Ø 局部内部类可以使用外部类的成员,包括私有
的。
Ø **局部内部类可以使用外部方法的局部变量,但是必须是final的。**由局部内部类和局部变量的声明周期不同所致。
Ø 局部内部类和局部变量地位类似,不能使用public,protected,缺省,private
Ø 局部内部类不能使用static
修饰,因此也不能包含静态成员
创建子类对象时,除了使用父类的构造方法外还有类体,此类体被认为是一个子类去掉类声明后的类体,称作匿名类
。
假设Bank
是类,那么下列代码就是用Bank的一个子类(匿名类)
创建对象:
new Bank () {
匿名类的类体
};
和某类有关的匿名类就是该类的一个子类,该子类没有明显的用类声明来定义,所以称做匿名类
。
和某接口有关的匿名类就是实现该接口的一个类,该子类没有明显的用类声明来定义,所以称做匿名类
。
l 匿名内部类不能定义
任何静态成员、方法和类,只能创建
匿名内部类的一个实例。一个匿名内部类一定是在new
的后面,用其隐含实现一个接口或实现一个类。
l 匿名内部类的特点
Ø 匿名内部类必须继承父类或实现接口
Ø 匿名内部类只能有一个对象
Ø 匿名内部类对象只能使用多态
形式引用
使用一个数组放着某学生5门课程的成绩,程序准备计算学生的成绩的总和。在调试程序时使用了断言语句,如果发现成绩有负数,程序立刻结束执行。
public static void main (String args[ ]) {
int [] score={-120,98,89,120,99};
int sum=0;
/*
foreach语句是java5的新特征之一,在遍历数组、集合方面,foreach为开发人员提供了极大的方便。
foreach语句是for语句的特殊简化版本,但是foreach语句并不能完全取代for语句,
然而,任何的foreach语句都可以改写为for语句版本。
foreach并不是一个关键字,习惯上将这种特殊的for语句格式称之为“foreach”语句。
从英文字面意思理解foreach也就是“for 每一个”的意思。实际上也就是这个意思。
foreach的语句格式:
for(元素类型t 元素变量x : 遍历对象obj){
引用了x的java语句;
}
*/
for(int number:score) {
assert number > 0:"负数不能是成绩";
sum=sum+number;
}
System.out.println("总成绩:"+sum);
}
使用关键字assert
声明一条断言语句,断言语句有以下两种格式:
assert booleanExpression;
assert booleanExpression:messageException;
在两种表达式中,booleanExpression表示一个boolean表达式,
messageException表示一个基本类型或者是一个对象(Object),
基本类型包括boolean,char,double,float,int,long 等
由于所有类都为Object的子类,因此这个参数可以用于所有对象。
l 异常:在Java语言中,将程序执行中发生的不正常情况称为异常
。(开发过程中的语法错误和逻辑错误不是异常)
l Java程序在执行过程中所发生的异常事件可分为两类:
Ø Error:Java虚拟机无法解决的严重问题。如:JVM系统内部错误、资源耗尽、StackOverflowError和OOM等严重情况。一般不编写针对性的代码进行处理。
ØException: 其它因编程错误或偶然的外在因素导致的一般性问题,可以使用针对性的代码进行处理。例如:
1.空指针访问
2.试图读取不存在的文件
3.网络连接中断
4.数组角标越界
对于这些错误,一般有两种解决方法:
一是遇到错误就终止程序的运行。另一种方法是由程序员在编写程序时,就考虑到错误的检测、错误消息的提示,以及错误的处理。
l 捕获错误最理想的是在编译期间,但有的错误只有在运行时才会发生。比如:除数为0,数组下标越界
等
分类:
编译时异常
运行时异常
编译时异常
是指编译器要求必须处置的异常。即程序在运行时由于外界因素造成的一般性异常。编译器要求java程序必须捕获或声明所有编译时异常。 对于这类异常,如果程序不处理,可能会带来意想不到的结果
运行时异常
是指编译器不要求强制处置的异常。一般是指编程时的逻辑错误,程序员应该积极避免其出现的异常。java.lang.RuntimeException类及它的子类都是运行时异常。
对于这类异常,可以不作处理
,因为这类异常很普遍,若全处理可能会对程序的可读性和运行效率产生影响。
| 在编写程序时,经常要在可能出现错误的地方加上检测的代码,如进行x/y
运算时,要检测分母为0
,数据为空,输入的不是数据而是字符等。过多的if-else
分支会导致程序的代码加长、臃肿,可读性差。因此采用异常处理机制。
| Java采用的异常处理机制,是将异常处理的程序代码集中在一起,与正常的程序代码分开,使得程序简洁、优雅,并易于维护。
| Java提供的是异常处理的抓抛模型。
| Java程序的执行过程中如出现异常,会生成一个异常类对象,该异常对象将被提交给Java运行时系统,这个过程称为抛出(throw)异常。
| 异常对象的生成
Ø 由虚拟机自动生成:程序运行过程中,虚拟机检测到程序发生了问题,如果在当前代码中没有找到相应的处理程序,就会在后台自动创建
一个对应异常类的实例对象并抛出——自动抛出
Ø由开发人员手动创建:Exception exception = new ClassCastException();
——创建好的异常对象不抛出对程序没有任何
影响,和创建一个普通对象一样
异常的抛出机制
| 如果一个方法内抛出异常,该异常对象会被抛给调用者方法中
处理。如果异常没有在调用者方法中处理,它继续
被抛给这个调用方法的上层方法。这个过程将一直继续下去,直到
异常被处理。这一过程称为捕获(catch)异常。
| 如果一个异常回到main()
方法,并且main()
也不处理,则程序运行终止。
| 程序员通常只能处理Exception
,而对Error
无能为力。
| 捕获异常的有关信息:
与其它对象一样,可以访问一个异常对象的成员变量或调用它的方法。
Ø getMessage()
获取异常信息,返回字符串
Ø printStackTrace()
获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void
。
//异常处理是通过try-catch-finally语句实现的。
try{
...... //可能产生异常的代码
}
catch( ExceptionName1 e ){
...... //当产生ExceptionName1型异常时的处置措施
}
catch( ExceptionName2 e ){
...... //当产生ExceptionName2型异常时的处置措施
}
finally{
...... //无论是否发生异常,都无条件执行的语句
}
try
捕获异常的第一步是用try{…}语句块选定捕获异常的范围,将可能出现异常的代码放在try语句块中。
catch (Exceptiontype e)
在catch语句块中是对异常对象进行处理的代码。每个try语句块可以伴随一个或多个catch语句,
用于处理可能产生的不同类型的异常对象。
finally
捕获异常的最后一步是通过finally语句为异常处理提供一个统一的出口,
使得在控制流转到程序的其它部分以前,能够对程序的状态作统一的管理。
不论在try代码块中是否发生了异常事件,catch语句是否执行,catch语句是否有异常,
catch语句中是否有return,finally块中的语句都会被执行。
finally语句和catch语句是任选的
如果明确知道产生的是何种异常,可以用该异常类作为catch的参数;也可以用其父类作为catch的参数。
比 如 : 可 以 用 ArithmeticException 类 作 为 参 数 的 地 方 , 就 可 以 用
RuntimeException类作为参数,
或者用所有异常的父类Exception类作为参数。但不能是与ArithmeticException类无关的异常,
如NullPointerException(catch中的语句将不会执行)。
l 一般地,用户自定义异常类都是RuntimeException
的子类。
l 自定义异常类通常需要
编写几个重载的构造器。
l 自定义异常需要提供serialVersionUID
l 自定义的异常通过throw
抛出。
l 自定义异常最重要的是异常类的名字,当异常出现时,可以根据名字
判断异常类型。
用户自定义异常类MyException,用于描述数据取值范围错误信息。用户自己的异常类必须继承现有的异常类。
| 程序(program
)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象
。
| 进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
Ø 如:运行中的QQ,运行中的MP3播放器
Ø 程序是静态的,进程是动态的
Ø 进程作为资源分配
的单位,系统在运行时会为每个进程分配不同的内存区域
| 线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
Ø 若一个进程同一时间并行执行多个线程,就是支持
多线程的
Ø 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
Ø 一个进程中的多个线程共享
相同的内存单元(内存地址空间),它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
l 单核CPU和多核CPU的理解
Ø 单核CPU,其实是一种假
的多线程,因为在一个时间单元内,也只能执行一个线程的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。
Ø 如果是多核的话,才能更好
的发挥多线程的效率。(现在的服务器都是多核的)
Ø 一个Java应用程序java.exe,其实至少
有三个线程:main()主线程
,gc()垃圾回收线程
,异常处理线程
。当然如果发生异常,会影响主线程。
l 并行与并发
Ø 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
Ø 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
。
多线程程序的优点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
何时需要多线程
l 程序需要同时执行两个或多个任务。
l 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索
等。
l 需要一些后台运行的程序时。
线程的创建和启动
| Java语言的JVM允许程序运行多个线程,它通过 java.lang.Thread类 来实现。
| Thread类的特性
Ø 每个线程都是通过某个特定Thread对象的run()方法
来完成操作的,经常把run()
方法的主体称为线程体
Ø 通过该Thread对象的start()
方法来启动这个线程,而非直接调用run()
l 构造器
Ø Thread():创建新的Thread对象
Ø Thread(String threadname):创建线程并指定线程实例名
Ø Thread(Runnable target):指定创建线程的目标对象,它实现
了Runnable接口中的run方法
Ø Thread(Runnable target, String name):创建新的Thread对象
l void start(): 启动线程,并执行对象的run()
方法
l run(): 线程在被调度时执行的操作
l String getName(): 返回线程的名称
l void setName(String name):设置该线程名称
l static Thread currentThread(): 返回当前线程。在Thread子类中就是this
,通常用于主线程和Runnable实现类
l static void yield():线程让步
Ø 暂停当前正在执行的线程,把执行机会让给优先级相同
或更高
的线程
Ø 若队列中没有同优先级的线程,忽略
此方法
l join() :当某个程序执行流中调用其他线程的join()
方法时,调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完
为止
Ø 低优先级的线程也可以获得执行
l static void sleep(long millis):(指定时间:毫秒
)
Ø 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队
。
Ø 抛出InterruptedException
异常
l stop(): 强制线程生命期结束,不推荐使用
l boolean isAlive():返回boolean
,判断线程是否还活着
l JDK1.5之前创建新执行线程有两种方法:
Ø 继承Thread类的方式
Ø 实现Runnable接口的方式
定义子类继承Thread类。
2) 子类中重写Thread类中的run方法。
创建Thread子类对象,即创建
了线程对象。
调用线程对象start
方法:启动线程,调用run
方法。
l 注意点:
\1. 如果自己手动调用run()
方法,那么就只是普通方法,没有启动多线程模式。
\2. run()
方法由JVM
调用,什么时候调用,执行的过程控制都由操作系统的CPU调度
决定。
\3. 想要启动多线程,必须调用start
方法。
\4. 一个线程对象只能调用一次start()
方法启动,如果重复调用了,则将抛出异常IllegalThreadStateException
。
this()
和super()
是你如果想用传入当前构造器中的参数或者构造器中的数据调用其他构造器或者控制父类构造器时使用的,在一个构造器中你只能使用this()
或者super()
之中的一个,而且调用的位置只能在构造器的第一行
,
在子类中如果你希望调用父类的构造器来初始化
父类的部分,那就用合适的参数来调用super()
,如果你用没有参数的super()
来调用父类的构造器(同时也没有使用this()
来调用其他构造器),父类缺省的构造器会被调用
,如果父类没有缺省的构造器,那编译器就会报一个错误
。
1)定义子类,实现Runnable
接口。
2)子类中重写Runnable接口中的run方法。
3)通过Thread类含参构造器
创建线程对象。
4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造方法中。
5)调用Thread类的start
方法:开启线程,调用Runnable子类接口的run
方法。
| 区别:
继承Thread: 线程代码存放Thread子类run
方法中。
实现Runnable:线程代码存在接口的子类的run
方法。
| 实现方式(Runnable)的好处:
1)避免了单继承的局限性
2)多个线程可以共享同一个接口实现类的对象,非常适合
多个相同线程来处理同一份资源。
Java中的线程分为两类:一种是守护线程,一种是用户线程。
Ø 它们在几乎每个方面都是相同的,唯一的区别是判断JVM
何时离开。
Ø 守护线程是用来服务用户线程的,通过在start()
方法前调用thread.setDaemon(true)
可以把一个用户线程变成一个守护线程。
Ø Java垃圾回收
就是一个典型的守护线程。
Ø 若JVM中都是
守护线程,当前JVM将退出。
| JDK中用Thread.State
枚举表示了线程的几种状态
| 要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象
来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
Ø 新建
: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
Ø 就绪
:处于新建状态的线程被start()
后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
Ø 运行
:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()
方法定义了线程的操作和功能
Ø 阻塞
:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
Ø 死亡
:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
Java对于多线程的安全问题提供了专业的解决方式:同步机制
1.同步代码块
synchronized (对象){
// 需要被同步的代码;
}
2.synchronized还可以放在方法声明中,表示整个方法为同步方法。
例如:
public synchronized void show (String name){
….
}
在Java语言中,引入了对象互斥锁
的概念,来保证共享数据操作的完整性。
每个对象都对应于一个可称为互斥锁
的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
关键字synchronized
来与对象的互斥锁联系。当某个对象用synchronized
修饰时,表明该对象在任一时刻只能
由一个线程访问。
同步的局限性:导致程序的执行效率要降低
同步方法(非静态的)的锁为this
。
同步方法(静态的)的锁为当前类本身
。
Ø 当前线程的同步方法、同步代码块执行结束
Ø 当前线程在同步代码块、同步方法中遇到break、return
终止了该代码块、该方法的继续执行。
Ø 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception
,导致异常结束
Ø 当前线程在同步代码块、同步方法中执行了线程对象的 wait() 方法,当前线程暂停,并释放锁。
|线程执行同步代码块或同步方法时,程序调用Thread.sleep()
、Thread.yield()
方法暂停当前线程的执行
|线程执行同步代码块时,其他线程调用了该线程的suspend()
方法将该线程挂起,该线程不会释放锁(同步监视器)。
Ø 应尽量避免使用suspend()
和resume()
来控制线程
死锁:
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决方法:
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
l 从JDK 5.0
开始,Java提供了更强大的线程同步机制
——通过显式定义同步锁对象来实现同步。同步锁使用Lock
对象充当。
l java.util.concurrent.locks.Lock
接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程
对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
l ReentrantLock
类实现了 Lock ,它拥有与 synchronized
相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
\1. Lock是显式锁
(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁
,出了作用域自动释放
\2. Lock只有代码块锁
,synchronized有代码块锁
和方法锁
\3. 使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock ——> 同步代码块(已经进入了方法体,分配了相应资源) ——> 同步方法(在方法体之外)
Ø wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify(
)或notifyAll()
方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
Ø notify()
:唤醒正在排队等待同步资源的线程中优先级最高者
结束等待
Ø notifyAll ()
:唤醒正在排队等待资源的所有
线程结束等待.
l 这三个方法只有在synchronized
方法或synchronized代码块
中才能使用,否则会报java.lang.IllegalMonitorStateException
异常。
l 因为这三个方法必须有锁对象调用
,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object
类中声明。
在当前线程中调用方法: 对象名.wait()
使当前线程进入等待(某对象)状态 ,直到另一线程对该对象发出 notify
(或notifyAll
) 为止。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
调用此方法后,当前线程将释放对象监控权 ,然后进入等待
在当前线程被notify
后,要重新获得监控权,然后从断点处
继续代码的执行。
在当前线程中调用方法: 对象名.notify()
功能:唤醒等待该对象监控权的一个线程。
调用方法的必要条件:当前线程必须具有对该对象的监控权(加锁)
在Java中,创建线程一般有两种方式,一种是继承Thread类,一种是实现Runnable接口。然而,这两种方式的缺点是在线程任务执行结束后,无法获取执行结果。我们一般只能采用共享变量或共享存储区以及线程通信的方式实现获得任务结果的目的。
不过,Java中,也提供了使用Callable和Future来实现获取任务结果的操作。Callable用来执行任务,产生结果,而Future用来获得结果。
Callable接口与Runnable接口是否相似,查看源码,可知Callable接口的定义如下:
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
可以看到,与Runnable接口不同之处在于,call方法带有泛型返回值V。
Future常用方法
V get() :获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
V get(Long timeout , TimeUnit unit) :获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,如果阻塞时间超过设定的timeout时间,该方法将抛出异常。
boolean isDone() :如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
boolean isCanceller() :如果任务完成前被取消,则返回true。
boolean cancel(boolean mayInterruptRunning) :如果任务还没开始,执行cancel(…)方法将返回false;如果任务已经启动,执行cancel(true)方法将以中断执行此任务线程的方式来试图停止任务,如果停止成功,返回true;当任务已经启动,执行cancel(false)方法将不会对正在执行的任务线程产生影响(让线程正常执行到完成),此时返回false;当任务已经完成,执行cancel(…)方法将返回false。mayInterruptRunning参数表示是否中断执行中的线程。
通过方法分析我们也知道实际上Future提供了3种功能:(1)能够中断执行中的任务(2)判断任务是否执行完成(3)获取任务执行完成后额结果。
l 与使用Runnable
相比, Callable
功能更强大些
Ø 相比run()
方法,可以有返回值
Ø 方法可以抛出异常
Ø 支持泛型的返回值
Ø 需要借助FutureTask
类,比如获取返回结果
l Future接口
Ø 可以对具体Runnable、Callable
任务的执行结果进行取消、查询是否完成、获取结果等。
Ø FutrueTask
是Futrue接口的唯一
的实现类
Ø FutureTask 同时实现了Runnable, Future
接口。它既可以作为Runnable被线程执行,又可以作为Future
得到Callable的返回值
很多方法与String
相同,但StingBuffer是可变长度的
。StringBuffer是一个容器
。
StringBuffer类不同于String,其对象必须使用构造器生成,有三个构造方法(构造器):
1.StringBuffer()初始容量为16的字符串缓冲区
2.StringBuffer(int size)构造指定容量的字符串缓冲区
3.StringBuffer(String str)将内容初始化为指定字符串内容
String s = new String("我喜欢学习");
StringBuffer buffer = new StringBuffer(“我喜欢学习”);
buffer.append("数学");
System类提供的public static long currentTimeMillis()
用来返回当前时间与1970年1月1日0时0分0秒
之间以毫秒为单位的时间差。
Ø 此方法适于计算时间差。
计算世界时间的主要标准有:
Ø UTC(Universal Time Coordinated)
Ø GMT(Greenwich Mean Time)
Ø CST(Central Standard Time)
表示特定的瞬间,精确到毫秒
构造方法:
Ø Date( )
使用Date类的无参数构造方法创建的对象可以获取本地当前时间。
Ø Date(long date)
常用方法
Ø getTime()
:返回自 1970 年 1 月 1 日 00:00:00 GMT 以来此 Date 对象表示的毫秒数。
Ø toString()
:把此 Date 对象转换为以下形式的 String: dow mon dd hh:mm:ss zzz yyyy
其中: dow
是一周中的某一天 (Sun, Mon, Tue, Wed, Thu, Fri, Sat
),zzz是时间标准
。
Ø 其它很多方法都过时了。
Date date = new Date(); //产生一个Date实例
//产生一个formater格式化的实例
SimpleDateFormat formater = new SimpleDateFormat();
System.out.println(formater.format(date));//打印输出默认的格式
SimpleDateFormat formater2 = new SimpleDateFormat("yyyy年MM月dd日 EEE HH:mm:ss");
System.out.println(formater2.format(date));
//实例化一个指定的格式对象
//按指定的格式输出
try {
Date date2 = formater2.parse("2008年08月08日 星期一 08:08:08");
//将指定的日期解析后格式化按指定的格式输出
System.out.println(date2.toString());
} catch (ParseException e) {
e.printStackTrace();
}
输出为:
22-2-1 下午3:03
2022年02月01日 星期二 15:03:37
Fri Aug 08 08:08:08 CST 2008
Calendar calendar = Calendar.getInstance();
// 从一个 Calendar 对象中获取 Date 对象
Date date = calendar.getTime();
//使用给定的 Date 设置此 Calendar 的时间
calendar.setTime(date);
calendar.set(Calendar.DAY_OF_MONTH, 8);
System.out.println("当前时间日设置为8后,时间是:" + calendar.getTime());
calendar.add(Calendar.HOUR, 2);
System.out.println("当前时间加2小时后,时间是:" + calendar.getTime());
calendar.add(Calendar.MONTH, -2);
System.out.println("当前日期减2个月后,时间是:" + calendar.getTime());
输出为:
当前时间日设置为8后,时间是:Tue Feb 08 15:06:01 CST 2022
当前时间加2小时后,时间是:Tue Feb 08 17:06:01 CST 2022
当前日期减2个月后,时间是:Wed Dec 08 17:06:01 CST 2021
l Java 8 吸收了Joda-Time
的精华,以一个新的开始为 Java 创建优秀的 API。新的java.time
中包含了所有关于本地日期(LocalDate
)、本地时间(LocalTime
)、本地日期时间(LocalDateTime
)、时(ZonedDateTime
)和持续时间(Duration
)的类。历史悠久的 Date
类新增了toInstant()
方法,用于把 Date 转换成新的表示形式。这些新增的本地化时间日期 API 大大简化了日期时间和本地化的管理。
新日期API
java.time
– 包含值对象的基础包
java.time.chrono
– 提供对不同的日历系统的访问
java.time.format
– 格式化和解析时间和日期
java.time.temporal
– 包括底层框架和扩展特性
java.time.zone
– 包含时区支持的类
说明:大多数开发者只会用到基础包和format包,也可能会用到temporal包。因此,尽管有68个新的公开类型,大多数开发者,大概将只会用到其中的三分之一。
l LocalDate、LocalTime、LocalDateTime
类是其中较重要的几个类,它们的实例是不可变
的对象,分别表示使用 ISO-8601
日历系统的日期、时间、日期和时间。它们提供了简单的本地日期或时间,并不包含当前的时间信息,也不包含与时区相关的信息。
Ø LocalDate
代表IOS格式(yyyy-MM-dd
)的日期,可以存储 生日、纪念日等日期。
Ø LocalTime
表示一个时间,而不是日期。
Ø LocalDateTime
是用来表示日期和时间的,这是一个最常用的类之一。
注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法,也就是公历。
l 在Java中经常会涉及到对象数组的排序问题,那么就涉及到对象之间的比较问题。
l Java实现对象排序的方式有两种:
Ø 自然排序:java.lang.Comparable
Ø 定制排序:java.util.Comparator
l System
类代表系统,系统级的很多属性和控制方法都放置在该类的内部。该类位于java.lang
包。
l 由于该类的构造器是private
的,所以无法创建该类的对象,也就是无法实例化该类。其内部的成员变量和成员方法都是static
的,所以也可以很方便的进行调用。
l 成员变量
Ø System类内部包含in、out和err
三个成员变量,分别代表标准输入流(键盘输入),标准输出流(显示器)和标准错误输出流(显示器)
。
l 成员方法
Ø native long currentTimeMillis():
该方法的作用是返回当前的计算机时间,时间的表达格式为当前计算机时间
和GMT时间(格林威治时间)1970年1月1号0时0分0秒
所差的毫秒数。
Ø void exit(int status):
该方法的作用是退出程序。其中status
的值为0
代表正常退出,非零代表异常退出。使用该方法可以在图形界面编程中实现程序的退出功能等。
Ø void gc():
该方法的作用是请求系统进行垃圾回收。至于系统是否立刻回收,则取决于系统中垃圾回收算法的实现以及系统执行时的情况。
Ø String getProperty(String key):
该方法的作用是获得系统中属性名为key
的属性对应的值。系统中常见的属性名以及属性的作用如下表所示:
java.lang.Math
提供了一系列静态方法用于科学计算;其方法的参数和返回值类型一般为double型。
abs 绝对值
acos,asin,atan,cos,sin,tan 三角函数
sqrt 平方根
pow(double a,doble b) a的b次幂
log 自然对数
exp e为底指数
max(double a,double b)
min(double a,double b)
random() 返回0.0到1.0的随机数
long round(double a) double型数据a转换为long型(四舍五入)
toDegrees(double angrad) 弧度—>角度
toRadians(double angdeg) 角度—>弧度
l 枚举类的实现
Ø JDK1.5之前需要自定义枚举类
Ø JDK 1.5 新增的 enum
关键字用于定义枚举类
l 若枚举只有一个对象,则可以作为一种单例模式的实现方式。
l 枚举类的属性
Ø 枚举类对象的属性不应允许被改动, 所以应该使用 private final
修饰
Ø 枚举类的使用private final
修饰的属性应该在构造器
中为其赋值
Ø 若枚举类显式的定义了带参数的构造器
, 则在列出枚举值时也必须对应的传入参数
l Enum类的主要方法:
Ø **values()**方法:返回枚举类型的对象数组。该方法可以很方便地遍历所有的枚举值。
Ø valueOf(String str):可以把一个字符串转为对应的枚举类对象。要求字符串必须是枚举类对象的名字
。如不是,会有运行时异常:IllegalArgumentException
。
Ø toString():返回当前枚举类对象常量的名称
l 和普通 Java 类一样,枚举类可以实现一个或多个接口
l 若每个枚举值在调用实现的接口方法呈现相同的行为方式,则只要统一实现该方法即可。
l 若需要每个枚举值在调用实现的接口方法呈现出不同的行为方式,则可以让每个枚举值分别来实现该方法
l 从 JDK 5.0 开始,Java 增加了对元数据(MetaData
) 的支持,也就是 Annotation
(注解)
l Annotation
其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时
被读取, 并执行相应的处理。通过使用 Annotation
, 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。代码分析工具、开发工具和部署工具可以通过这些补充信息进行验证或者进行部署。
l Annotation 可以像修饰符一样被使用, 可用于修饰包,类, 构造器, 方法, 成员变量, 参数, 局部变量的声明, 这些信息被保存在 Annotation 的 name=value
对中.
l Annotation 能被用来为程序元素(类, 方法, 成员变量等) 设置元数据
l 在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告
等。在JavaEE/Android中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗代码和XML配置等。
l 未来的开发模式都是基于注解的,JPA
是基于注解的,Spring2.5以上都是基于注解的,Hibernate3.x以后也是基于注解的,现在的Struts2有一部分也是基于注解的了,注解是一种趋势,一定程度上可以说:框架 = 注解 + 反射 + 设计模式
。
Ø @Override: 限定重写父类方法, 该注解只能用于方法
Ø @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
Ø @SuppressWarnings: 抑制编译器警告
Ø Servlet3.0提供了注解(annotation),使得不再需要在web.xml
文件中进行Servlet
的部署。
Ø spring框架中关于事务
的管理
l JDK 的元 Annotation
用于修饰其他 Annotation 定义
l JDK5.0提供了4个标准的meta-annotation
类型,分别是:
ØRetention
ØTarget
ØDocumented
ØInherited
元数据的理解:String name = “atguigu”;
至此,我们的Java的学习(中)
就讲解完成了。下篇我们将介绍泛型、IO流、网络编程、反射机制、Java8、9、10、11新特性以及常见面试题
,源码素材可以通过关注我的微信公众号 我爱学习呀嘻嘻
,回复关键字Java的学习源码素材
进行获取哦。
Java的学习(下):泛型、IO流、网络编程、反射机制、Java8、9、10、11新特性以及常见面试题
Java的学习(上):Java概述、基本语法、数组