1 Day06–面向对象1
1.1 面向对象
1.1.1 概念
推荐看的书:Thinking in java
概念:所谓的面向对象是一种编程思想,通过这种思想可以把生活中的复杂事情变得简单化,从原来的执行者变成了指挥者,面向对象是基于面向过程而言的。
面向过程的结构化设计的优缺点:
1).缺点:缺乏对数据的封装。
2).缺点:数据与方法(操作数据的)难以分离。
3).优点:性能比较高。
面向对象设计的优缺点:
1).优点:易维护,易扩展,易复用。
2).缺点:性能比面向过程低。
面向过程强调的是过程,例如:
1、打开冰箱 2、把大象放进去 3、关上冰箱
面向对象强调结果,例如:
1、饿了,去平台点餐,这个动作就是面向对象。你没有去市场买菜洗菜做饭。。。只要有app就可以了。
2、衣服脏了,直接甩给女票去处理等着穿干净的就可以了。你没有关注中间的过程。。只要找好对象就可以了。
3、面试官问什么是面向对象?你答万物皆对象!!不建议因为你还没到这个深度,最好举例。就像是,你说空即是色色即是空—信你个鬼。
我们经常说的面向对象的编程实现(OOP,Object Oriented Programming)
面向对象的本质是:以类的方式组织代码,以对象的组织封装数据。
1.1.2 三大特征
1.封装: 封装实现了软件部件的“高内聚、低耦合”,防止程序因依赖而带来的变动影响。
(1)类:封装的是对象的属性和行为
(2)方法:封装的是特定的业务逻辑功能实现
(3)访问控制修饰符:封装的是具体的访问权限
2.继承 : 继承提高了软件的可重用性和可扩展性。
(1)作用:便于代码的复用,减少代码冗余,提高程序可维护性和可扩展性
(2)超类:所有派生类所共有的属性和行为
接口:部分派生类所共有的行为
派生类:派生类所特有的属性和行为
(3)类与类直接是单继承,接口与接口之间是多继承,类与接口是多实现
3.多态: 多态增强了软件的灵活性和可扩展性。
(1)分类:
(1.1)行为的多态(可举例)
(1.2)对象的多态(可举例)
(2)向上造型、强制类型转换、instanceof判断
(3)多态的表现形式
(3.1)重写:根据对象的不同来表现多态
(3.2)重载:根据参数的不同来表现多态
1.1.3 扩展:值传递和引用传递
规范:一个项目只有应该只有一个主启动类
java是值传递。
package com.shuai;
public class Demo01 {
//值传递
public static void main(String[] args) {
int a = 1;
System.out.println(a);//1
change(a);
System.out.println(a);//1 没有修改成功
}
public static void change(int a) { //返回值为空,就没有返回值
a=10;
}
}
1.2 类和对象
1.2.1 类
1、Java语言最基本单位就是类,类似于类型。
2、类是一类事物的抽象。(类别/类型,代表一类个体)
3、可以理解为模板或者设计图纸。
4、同一个包中的类可以相互使用,但不能同名(必须保存后)。
5、语法 : class 类名{ }
6、类中一般包含:
- 所有对象所共有的属性/特征 --------成员变量
- 所有对象所共有的行为 -------方法
同包中的类可以直接访问,不同包中的类不能直接访问,想访问:
- 先import声明一个全称的类(包名+类名),再访问类(就是new一个对象)
- 类的全称访问-------太繁琐、不建议(声明一个写一个太麻烦)
注意:一个.java文件中可以有多个class,但是要求,被public修饰的类只能有一个,并且这个类名就是文件名(区分大小写,也可以没有一个public修饰的类)。不同的文件可以有多个public.褚提时可能是不同的文件写在一块所以可以写多个public
1.2.2 对象
对象:真实存在的单个的个体。
每个对象具有三个特点:对象的状态,对象的行为和对象的标识。
1、对象的状态用来描述对象的基本特征。
2、对象的行为用来描述对象的功能。
3、对象的标识是指对象在内存中都有一个唯一的地址用来和其他对象区分开来。
4、类是一类事物的抽象,对象是具体的实现。
5、语法: new 类名();
6、每次new都会创建一个新的对象。
7、使用new关键字创建对象分配空间后会干2件事:默认的初始化(给变量赋默认值)和调用构造方法。
调用成员变量和方法(创建对象时):
引用类型的名字.局部变量名 = 赋值;
引用类型的名字.方法调用的方式;
为什么要创建对象:因为访问,访问就是访问类里面的东西,变量和方法,因为只有实例化之后,才能将这个对象放到内存中,然后才能在规定的范围内来调用
1.2.3 类和对象的关系
1、计算机语言是用来描述现实世界事物的。属性+行为
2、那怎么通过java语言描述呢?通过类来描述事物,把事物的属性当做成员变量
,把行为当做成员方法
。
3、一个类可以创建多个对象。
总结:类是对象的模板,对象是类的具体事例。
以手机举例:
属性:颜色,尺寸,品牌,价格。
方法:打电话,发短信,听音乐。
类:手机类,抽取相同的属性和行为
对象:可以按照模板生产很多个手机,比如1号手机对象,包含特有的成员变量和成员方法
1.3 类和对象的创建和使用
1.3.1 练习1:类的创建使用
通过class关键字创建类,通过new关键字创建对象。
1).
public class Test1_CreateObject {
public static void main(String[] args) {
//创建对象测试
//3,通过关键字new来创建对象
//new Phone()匿名对象,一次只干一个活
//new Phone().call(); 只能干一件事
//4,p是Phone类型是引用类型的变量,引用了,,,内存中的地址值
Phone p = new Phone();
//引用类型的创建,引用类型的默认值是null,引用类型与它需要实例的对象类名一致。
//p代表了Phone对象,真的能用模板定义的功能吗???
//调用方法
p.call();
p.message();
p.music();
//设置属性值
p.color="green"; //调用的时候改值
p.size=6;
p.pinpai="HUAWEI";
p.price=20000;
//调用属性
System.out.println(p.color);//null --green
System.out.println(p.size);//0 -- 6
System.out.println(p.pinpai);//null --HUAWEI
System.out.println(p.price);//0.0 --20000.0
}
}
//1,创建手机类,用来描述手机事物
//2,通常描述:事物的特征+事物的行为
class Phone{
// 事物的特征 -- 成员变量/成员属性
// 特征:颜色,尺寸,品牌,价格
String color;
int size;
String pinpai;
double price;
// 事物的行为 -- 成员方法/成员函数
// 行为/功能:打电话,发短信,听音乐
//修饰符 返回值 方法名(参数列表){方法体}
public void call() {
System.out.println("call()...");
}
public void message() {
System.out.println("message()...");
}
public void music() {
System.out.println("music()...");
}
}
2).
package cn.tedu.oop;
//练习
public class Test2_Car {
public static void main(String[] args) {
//2,创建Car对象测试
Car c = new Car();
//调用功能
c.run();
c.stop();
//设置属性值
c.color="red";
c.model="BMW5";
c.pinpai="BMW";
//直接输出变量
System.out.println(c.color);
System.out.println(c.model);
System.out.println(c.pinpai);
}
}
//1,创建Car类,用来描述汽车事物
class Car{
// -- 特征+行为
String color;
String model;
String pinpai;
public void run() {
System.out.println("正在飞");
}
public void stop() {
System.out.println("停车");
}
}
1.3.2 对象在内存中的存储
Java把内存分成5大区域,内存由JVM管理的,我们重点关注栈和堆。
- 一般来讲局部变量存在栈中,方法执行完毕内存就被释放(栈比堆小)
- 对象(new出来的东西)存在堆中,对象不再被使用时,内存才会被释放
- 每个堆内存的元素都有地址值
- 对象中的属性都是有默认值的
- 地址值都是唯一的不会重重复(存地址值,占用内存空间开销小)
- 栈是:先进后出原则(类似于子弹上膛) 堆:没有这个原则,放到哪去都可以。
- 内存由JVM管理的:先加载类,在加载.class会在方法区分配,再分配堆,在分配栈(栈和堆几乎同时开始)
- 对象完成初始化才有地址值
- 栈:方法执行完后释放。堆:当没有任何引用时调用时释放内存。
栈,堆,方法区各存什么?
栈:局部变量(包括方法的参数,也叫局部变量),保存一个对应的对象的地址值 。
堆:new出来的对象(包括实例变量和方法)。
方法区:.class字节码文件(静态方法和静态变量)类里面,只被加载一次。
1.3.3 单一对象内存图
名词: 出栈
1.3.4 练习2:创建多对象
package cn.tedu.oop;
//测试多个对象的创建和使用
public class Test3_Person {
public static void main(String[] args) {
//创建对象测试 (addr:地址 brand:品牌)
Person p = new Person();
p.game(); //调用方法的时候可以传参赋值
p.code();
//设置属性值:在调用属性的时候赋值
p.name="rose";
p.age=20;
p.gender=1;
p.address="北京";
//打印
System.out.println(p.name);
System.out.println(p.age);
System.out.println(p.gender);
System.out.println(p.address);
/*如果创建了一个类的多个对象,则每个对象都独立的拥有一套类的属性。(非static的)
意味着:如果我们修改一个对象的属性a,则不影响另外一个对象属性a的值。*/
Person p2 = new Person();
p2.code();
p2.game();
//TODO 设置属性值
System.out.println(p2.name);//null
System.out.println(p2.age);
System.out.println(p2.gender);
System.out.println(p2.address);
//p3是引用类型的变量,这里没有new,在堆内存中就不会开辟新空间。
//p3保存了p2保存着的地址值
Person p3 = p2;
}
}
//创建Person类
class Person{
//特征
String name;
int age;
int gender;//0男1女
String address;
//行为
public void code() {
System.out.println("敲代码");
}
public void game() {
System.out.println("吃鸡");
}
}
1.3.5 多对象内存图
1、变量p和变量p2不是一片空间,p1需要开辟新的空间
2、Person p2=new Person,这时只要有new,就会新开辟空间在堆内存中存入对象。
1.4 封装
1.4.1 概述
封装是指隐藏对象的属性和实现细节,仅仅对外提供公共的访问方式。
好处:
1、提高安全性
2、提高重用性
案例:
1、类
2、方法
1.4.2 private关键字
是一个权限修饰符,用于修饰成员变量和成员函数,被私有化的成员只能在本类中访问。想要修改只能访问对外提供公共的get(获取值)和set(赋值)方法,之前直接可以通过对象.资源
来调用赋值,现在只能通过set 方法或者构造方法进行赋值操作,当然单单只是调用还是对象.资源
进行调用。
封装,利用private关键字实现,目的是提高安全性,本类。
比如:一个public int age.外界可以随意调用它赋值若为10000岁,显然不合常理。想要使用private修饰的变量只能通过访问public修饰的方法来进行调用,在方法中我们可以添加一些限制条件,而在变量中没法加。
一般封装 : 属性(成员变量)私有化 ,行为(方法)公开化。
1.4.3 练习1:封装学生
创建学生类,创建学生对象测试
package cn.tedu.oop;
//测试封装
public class Test4_Private {
public static void main(String[] args) {
//创建学生对象测试
Student s = new Student();
//调用功能
// s.study();//由于study()被private修饰了,除了自己的类,别的类都用不了
s.sleep();
// System.out.println(s.name);//由于name被private修饰了,除了自己的类,别的类都用不了
//3,如果我就是想要修改已经被private的name属性的值? --访问公共的set()
// s.name ="jack";//无法访问
s.setName("jack"); //调用set方法赋值88888888888
//4,如果外界就是想要获取被private的name属性的值? --访问公共的get()
// System.out.println(s.name) ;//无法访问
System.out.println(s.getName());//和平常的输出不一样,这个是直接输出的方法。(因为get方法有返回值一般前边是定义一个变量来接受,所以说这个地方可以直接输出方法)
}
}
//创建学生类
class Student{
//成员属性
//1.1,封装:成员变量,封装好的变量,外界无法直接访问,需要间接访问公共的set()设置值,get()获取值 有快捷键:source------get and set
private String name;
//3.1,对外提供一个公共的修改方法 -- setXxx() 没有返回值
public void setName(String n) {
//拿到n之后,需要把n的值赋值给name
name = n;
}
//4.1,对外提供一个公共的获取改方法 -- getXxx() 有返回值
public String getName(){
//4.2,通过return关键字,把name属性的值,返回给调用位置
return name;
}
//TODO 封装以下三个属性,并提供set()/get()
private String subject;
public void setSubject(String s) {
subject = s ;
}
public String getSubject() {
return subject;
}
private int sno;
private int age;
//eclipse自动生成代码:右键-source-setters and getters-select all-ok
public int getSno() {
return sno;
}
public void setSno(int sno) {
this.sno = sno;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
//成员方法
//1,封装:利用private关键字实现,目的就是提高代码的安全性,被private之后,资源只能在本类中看见
private void study() {
System.out.println("正在学习");
}
public void sleep() {
//2,如果外界还是想执行study(),可以访问公共的sleep(),间接实现访问study()
study();
System.out.println("正在睡觉");
}
}
1.5 拓展
1.5.1 创建对象的流程
Person p = new Person();//短短这行代码发生了很多事情
1.把Person.class文件加载进内存
2.在栈内存中,开辟空间,存放变量p
3.在堆内存中,开辟空间,存放Person对象
4.对成员变量进行默认的初始化
5.对成员变量进行显示初始化
6.执行构造方法(如果有构造代码块,就先执行构造代码块再执行构造方法)
7.堆内存完成
8.把堆内存的地址值赋值给变量p ,p就是一个引用变量,引用了Person对象的地址值
1.5.2 匿名对象
没有名字的对象,是对象的简化表示形式。
使用场景:当被调用的对象只调用一次时。
优点:节省内存,效率高。因为它调用一次后立刻释放内存
缺点:局限性,只能用一次它的属性。
Demo d = new Demo();
d.sleep();
d.game();
//这个d就是对象的名字。
也可以写成:
new Demo().show();//创建了一个对象调方法
new Demo().game();//又创建了一个对象调方法
1.5.3 声明对象类型的数组
package com.atguigu.exer;
/*
* 对象数组题目:
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。
创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息。
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
提示:
1) 生成随机数:Math.random(),返回值类型double;
2) 四舍五入取整:Math.round(double d),返回值类型long。
*
*
*
*
*/
public class StudentTest {
public static void main(String[] args) {
// Student s1 = new Student();
// Student s1 = new Student();
// Student s1 = new Student();
// Student s1 = new Student();
// Student s1 = new Student();
// Student s1 = new Student();
//声明Student类型的数组
Student[] stus = new Student[20]; //String[] arr = new String[10];
for(int i = 0;i < stus.length;i++){
//给数组元素赋值
stus[i] = new Student();
//给Student对象的属性赋值
stus[i].number = (i + 1);
//年级:[1,6]
stus[i].state = (int)(Math.random() * (6 - 1 + 1) + 1);
//成绩:[0,100]
stus[i].score = (int)(Math.random() * (100 - 0 + 1));
}
//遍历学生数组
for(int i = 0;i <stus.length;i++){
// System.out.println(stus[i].number + "," + stus[i].state
// + "," + stus[i].score);
System.out.println(stus[i].info());
}
System.out.println("********************");
//问题一:打印出3年级(state值为3)的学生信息。
for(int i = 0;i <stus.length;i++){
if(stus[i].state == 3){
System.out.println(stus[i].info());
}
}
System.out.println("********************");
//问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
for(int i = 0;i < stus.length - 1;i++){
for(int j = 0;j < stus.length - 1 - i;j++){
if(stus[j].score > stus[j + 1].score){
//如果需要换序,交换的是数组的元素:Student对象!!!
Student temp = stus[j];
stus[j] = stus[j + 1];
stus[j + 1] = temp;
}
}
}
//遍历学生数组
for(int i = 0;i <stus.length;i++){
System.out.println(stus[i].info());
}
}
}
class Student{
int number;//学号
int state;//年级
int score;//成绩
//显示学生信息的方法
public String info(){
return "学号:" + number + ",年级:" + state + ",成绩:" + score;
}
}
优化写法:封装成数组工具类
package com.atguigu.exer;
/*
* 4. 对象数组题目:
定义类Student,包含三个属性:学号number(int),年级state(int),成绩score(int)。
创建20个学生对象,学号为1到20,年级和成绩都由随机数确定。
问题一:打印出3年级(state值为3)的学生信息。
问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
提示:
1) 生成随机数:Math.random(),返回值类型double;
2) 四舍五入取整:Math.round(double d),返回值类型long。
*
*
* 此代码是对StudentTest.java的改进:将操作数组的功能封装到方法中。
*
*/
public class StudentTest1 {
public static void main(String[] args) {
//声明Student类型的数组
Student1[] stus = new Student1[20];
for(int i = 0;i < stus.length;i++){
//给数组元素赋值
stus[i] = new Student1();
//给Student对象的属性赋值
stus[i].number = (i + 1);
//年级:[1,6]
stus[i].state = (int)(Math.random() * (6 - 1 + 1) + 1);
//成绩:[0,100]
stus[i].score = (int)(Math.random() * (100 - 0 + 1));
}
StudentTest1 test = new StudentTest1();
//遍历学生数组
test.print(stus);
System.out.println("********************");
//问题一:打印出3年级(state值为3)的学生信息。
test.searchState(stus, 3);
System.out.println("********************");
//问题二:使用冒泡排序按学生成绩排序,并遍历所有学生信息
test.sort(stus);
//遍历学生数组
test.print(stus);
}
/**
*
* @Description 遍历Student1[]数组的操作
* @author shkstart
* @date 2019年1月15日下午5:10:19
* @param stus
*/
public void print(Student1[] stus){
for(int i = 0;i <stus.length;i++){
System.out.println(stus[i].info());
}
}
/**
*
* @Description 查找Stduent数组中指定年级的学生信息
* @author shkstart
* @date 2019年1月15日下午5:08:08
* @param stus 要查找的数组
* @param state 要找的年级
*/
public void searchState(Student1[] stus,int state){
for(int i = 0;i <stus.length;i++){
if(stus[i].state == state){
System.out.println(stus[i].info());
}
}
}
/**
*
* @Description 给Student1数组排序
* @author shkstart
* @date 2019年1月15日下午5:09:46
* @param stus
*/
public void sort(Student1[] stus){
for(int i = 0;i < stus.length - 1;i++){
for(int j = 0;j < stus.length - 1 - i;j++){
if(stus[j].score > stus[j + 1].score){
//如果需要换序,交换的是数组的元素:Student对象!!!
Student1 temp = stus[j];
stus[j] = stus[j + 1];
stus[j + 1] = temp;
}
}
}
}
}
class Student1{
int number;//学号
int state;//年级
int score;//成绩
//显示学生信息的方法
public String info(){
return "学号:" + number + ",年级:" + state + ",成绩:" + score;
}
}
2 Day07–面向对象2
2.1 构造方法(构造器,构建器,构造函数,constructor)
2.1.1 概念
- 构造方法是一种特殊的方法,它是一个与类同名且没有返回值的位置(连void都没有)。普通方法也可以与类名相同,但一般不这么写。
- 对象的创建就是通过构造方法来完成,
- 其功能主要是:1.完成对象的创建 。2.可以进行对象属性的初始化 。
- 当类实例化一个对象时会自动调用构造方法。
- 构造方法和其他方法一样也可以重载,但不能重写。(因为:1.构造方法不能被继承:如果继承则与子类类名相同。2.是因为子类中有super自动调用父类的构造方法)
- 若自己没有定义构造方法,则默认提供了一个无参构造方法(所以任何一个类都有构造方法) 若自己定义了构造方法,则不再默认提供,所以建议手动写一个无参构造。
- 构造方法的作用:为了方便外界创建对象。
idea生成构造方法快捷键:alt+insert.
2.1.2 形式
修饰符 类名([参数列表]){//可以无参也可以有参
代码……
//没有返回值
}
2.1.3 练习1:构造方法创建对象
package cn.tedu.constructor;
//测试构造方法的使用
public class Test5_Constructor {
public static void main(String[] args) {
//创建Teacher测试
//1,当创建对象时,会自动调用构造方法???--会自动调用,调用了默认就会存在的无参构造
Teacher t = new Teacher();
//3.1,创建对象时,触发含参构造,根据不同的参数类型来自动调用构造方法.
Teacher t1 = new Teacher("tony");
}
}
//创建Teacher类
class Teacher{
//2,提供构造方法:修饰符 类名([参数列表]){}
//4,无参构造 -- 默认就会存在 -- 前提是:没有含参构造时,才存在。如果只提供了含参构造,无参构造就真没了。
public Teacher() {
System.out.println("无参构造...");
}
//3,构造方法是一个特殊的方法,特殊在没有返回值,方法名=类名。但是可以存在方法的重载现象
//准备重载的构造方法
public Teacher(String n) {
System.out.println("含参构造..."+n);
}
}
2.1.4 练习2:构造方法初始化赋值
package cn.tedu.constructor;
//这个类用来测试构造方法赋值
public class Test6_Constructor2 {
public static void main(String[] args) {
//自动触发无参构造
Student2 s = new Student2();
//触发含参构造,在创建对象时给成员变量赋值。
Student2 s2 = new Student2(2);
}
}
//创建Student2类
class Student2{
//成员变量
int age;
//提供构造方法
public Student2() {
System.out.println("无参构造");
}
//提供重载的构造方法
public Student2(int a) { //a在方法里面是局部变量
//1,构造方法可以用来给变量赋值
//流程:当含参的方式创建对象时,会自动触发含参构造。把参数2给a赋值,a拿到值之后再给成员变量age赋值
age = a;
System.out.println("成员变量age:"+age);
System.out.println("含参构造");
}
}
2.1.5 总结:属性赋值的先后顺序
package com.cn.ca;
/*
* 总结:属性赋值的先后顺序
*
*
* ① 默认初始化(初始化:第一次赋值)
* ② 显式初始化
* ③ 构造器中初始化
*
* ④ 通过"对象.方法"(加了private封装控制权限了) 或 "对象.属性"的方式,赋值
*
* 以上操作的先后顺序:① - ② - ③ - ④
*
*/
public class UserTest {
public static void main(String[] args) {
User u = new User();
System.out.println(u.age);
User u1 = new User(2);
u1.setAge(3);
u1.setAge(5);
System.out.println(u1.age);
}
}
class User{
String name;//默认初始化
int age = 1;//显示初始化
public User(){
}
public User(int a){//创建对象,属性初始化
age = a;
}
public void setAge(int a){
age = a;
}
}
2.2 JavaBean可重用组件
- JavaBean是一种Java语言写成的可重用组件。
- 好比你做了一个扳手,这个扳手会在很多地方被拿去用。这个扳手也提供多种功能(你可以拿这个扳手扳、锤、撬等等),而这个扳手就是一个组件。
- 所谓JavaBean,是指符合如下标准的Java类:
- 类是公共的
- 有一个无参的公共的构造器
- 有属性,且有对应的get、set方法
- 用户可以使用JavaBean的功能、处理、值、数据库访问和其他任何可以用Java代码创造的对象进行打包,并且其他的开发者可以通过内部的JSP页面、Servlet、其他JavaBean、applet程序或者应用来使用这些对象。用户可以认为JavaBean提供了一种随时随地的复制和粘贴的功能,而不用关心任何改变。
- 《Think in Java》中提到,JavaBean最初是为Java GUI的可视化编程实现的。你拖动IDE构建工具创建一个GUI 组件(如多选框),其实是工具给你创建Java类,并提供将类的属性暴露出来给你修改调整,将事件监听器暴露出来。
- 示例
public class JavaBean {
private String name; // 属性一般定义为private
private int age;
public JavaBean() {
}
public int getAge() {
return age;
}
public void setAge(int a) {
age = a;
}
public String getName() {
return name;
}
public void setName(String n) {
name = n;
}
}
2.3 this关键字
2.3.1 概念
说明:
1.this代表本类对象的一个引用对象。
2.this只用在2个地方:一个是,成员变量名和局部变量名相同时,用this指代成员变量用来区分成员变量和局部变量的区别。另一个是,用来构造方法间进行调用另一个构造方法在第一行使用。
注意:
1.当成员变量和局部变量名不同时可以省略this。
2.在构造函数中,this()必须放在第一行。
2.3.2 形式
name=name;
age=age;
//解释:其实是想把Student类的局部变量name的值赋值给成员变量,相当于你想操作是这样的:
Student.name=name;
//但是你不能直接写类名,这时候就用代表本类的对象this来完成。代码变成了:
this.name=name;
语法:
1)this.成员变量名-----------访问成员变量(掌握)
this.name=name;
2)this.方法名()------------调用方法(不用掌握)
因为在类中没有相同的方法,所以不用写this来区分。
3)this()------------------调用构造方法(只能重载类名相同)根据它的参数列表
(一个构造方法可以通过this关键字调用另外一个重载的构造方法)
2.3.3 练习1:当变量名相同时
当局部变量和成员变量同名时,用于区分。
如果附近有同名变量,会遵从变量的就近原则,那么怎么调用成员变量呢?
package cn.tedu.thisdemo;
//测试this关键字
public class Test2_This {
public static void main(String[] args) {
//创建Student对象测试
Student s = new Student();
s.show();
}
}
代码体现1:
//创建Student类
class Student{
int count ;//成员变量
int sum = 20;//成员变量
public void show() {
int sum = 10;//局部变量
System.out.println(sum);//10,就近原则
System.out.println(count);//0
// System.out.println(new Student().sum);//20
//1,this关键字代表的是本类对象的一个引用就相当于Student this = new Student();8888888888888888888
//2,当成员变量和局部变量同名时(如:sum),可以使用this来区分。this调用的是本类的成员变量。888888888888888888888888(当成员变量和局部变量名不同时可以省略this)
//(因为局部变量在方法里,想要调用先调用方法才行,所以它调用的变量是成员变量)
System.out.println(this.sum);//20
}
}
代码体现2:
class Student2{
String name;
//创建对象时,给成员变量name赋值
public Student2(String name) {
// name = name;//没有成功的给成员变量name赋值,因为等号左右两边都是局部变量 //就近原则
this.name = name;//等号左边使用的是成员变量,右边是局部变量
//注意:变量只要在方法上就是局部变量,而不是在{ }里才是。
System.out.println(name);
System.out.println(this.name);
}
}
2.3.4 练习2:构造方法间的调用
package cn.tedu.thisdemo;
//this在构造方法间调用
//总结:this只用在2个地方:一个是,成员变量名和局部变量名相同时,用this指代成员变量用来区分成员变量和局部变量的区别。另一个是,用来构造方法间进行调用另一个构造方法在第一行时用
//1,this可以在构造方法间互相调用
//2,如果在构造方法里出现了this关键字,必须放在第一条语句的位置
//构造方法之间不能同时相互调用,否则会出现死循环,报错。也不能自己调自己。
public class Test3_This2 {
public static void main(String[] args) {
//无参创建对象测试
Teacher t = new Teacher();
//含参创建对象测试
Teacher t2 = new Teacher("jack");
}
}
//创建Teacher类
class Teacher{
//提供构造方法
public Teacher() {
//在 无参构造 中访问 含参构造
// this("jack");
System.out.println("无参构造");
}
public Teacher(String name) {
//在 含参构造 中访问 无参构造
this();
System.out.println("含参构造"+name);
}
}
2.3.5 练习3:综合练习
package com.atguigu.java2;
/*
* this关键字的使用:
* 1.this可以用来修饰、调用:属性、方法、构造器
*
* 2.this修饰属性和方法:
* this理解为:当前对象 或 当前正在创建的对象
*
* 2.1 在类的方法中,我们可以使用"this.属性"或"this.方法"的方式,调用当前对象属性或方法。但是,
* 通常情况下,我们都选择省略"this."。特殊情况下,如果方法的形参和类的属性同名时,我们必须显式
* 的使用"this.变量"的方式,表明此变量是属性,而非形参。
*
* 2.2 在类的构造器中,我们可以使用"this.属性"或"this.方法"的方式,调用当前正在创建的对象属性或方法。
* 但是,通常情况下,我们都选择省略"this."。特殊情况下,如果构造器的形参和类的属性同名时,我们必须显式
* 的使用"this.变量"的方式,表明此变量是属性,而非形参。
*
* 3. this调用构造器
* ① 我们在类的构造器中,可以显式的使用"this(形参列表)"方式,调用本类中指定的其他构造器
* ② 构造器中不能通过"this(形参列表)"方式调用自己
* ③ 如果一个类中有n个构造器,则最多有 n - 1构造器中使用了"this(形参列表)"
* ④ 规定:"this(形参列表)"必须声明在当前构造器的首行
* ⑤ 构造器内部,最多只能声明一个"this(形参列表)",用来调用其他的构造器
*
*
*/
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person();
p1.setAge(1);
System.out.println(p1.getAge());
p1.eat();
System.out.println();
Person p2 = new Person("Jerry",20);
System.out.println(p2.getAge());
}
}
class Person{
private String name;
private int age;
public Person(){
// this.eat();
String info = "Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)";
System.out.println(info);
}
public Person(String name){
this();
this.name = name;
}
public Person(int age){
this();
this.age = age;
}
public Person(String name,int age){
this(age);
this.name = name;
//this.age = age;
//Person初始化时,需要考虑如下的1,2,3,4...(共40行代码)
}
public void setName(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return this.age;
}
public void eat(){
System.out.println("人吃饭");
this.study();
}
public void study(){
System.out.println("人学习");
}
}
2.4 Package和import关键字使用
package com.atguigu.java2;
import java.lang.reflect.Field;
import java.util.*;
import com.atguigu.exer4.Account;
import com.atguigu.exer4.Bank;
import com.atguigu.java2.java3.Dog;
import static java.lang.System.*;
import static java.lang.Math.*;
/*
* 一、package关键字的使用
* 1.为了更好的实现项目中类的管理,提供包的概念
* 2.使用package声明类或接口所属的包,声明在源文件的首行(这里的首行要求不严格:指
* 的是声明的第一条语句,在它之前有换行的空格也算是首行)
* 3.包,属于标识符,遵循标识符的命名规则、规范(xxxyyyzzz)、“见名知意”
* 4.每"."一次,就代表一层文件目录。
*
* 补充:同一个包下,不能命名同名的接口、类。
* 不同的包下,可以命名同名的接口、类。
*
* 二、import关键字的使用
* import:导入
* 1. 在源文件中显式的使用import结构导入指定包下的类、接口
* 2. 声明在包的声明和类的声明之间
* 3. 如果需要导入多个结构,则并列写出即可
* 4. 可以使用"xxx.*"的方式,表示可以导入xxx包下的所有结构
* 5. 如果使用的类或接口是java.lang包下定义的,则可以省略import结构
* 6. 如果使用的类或接口是本包下定义的,则可以省略import结构
* 7. 如果在源文件中,使用了不同包下的同名的类,则必须至少有一个类需要以全类名的方式显示。
* 8. 使用"xxx.*"方式表明可以调用xxx包下的所有结构。但是如果使用的是xxx子包下的结构,则仍需要显式导入
*
* 9. import static:导入指定类或接口中的静态结构:属性或方法。
*/
public class PackageImportTest {
public static void main(String[] args) {
String info = Arrays.toString(new int[]{1,2,3});
Bank bank = new Bank();
ArrayList list = new ArrayList();
HashMap map = new HashMap();
Scanner s = null;
System.out.println("hello!");
Person p = new Person();
Account acct = new Account(1000);
//全类名的方式显示
com.atguigu.exer3.Account acct1 = new com.atguigu.exer3.Account(1000,2000,0.0123);
Date date = new Date();
java.sql.Date date1 = new java.sql.Date(5243523532535L);
Dog dog = new Dog();
Field field = null;
out.println("hello");
long num = round(123.434);
}
}
2.5 继承
2.5.1 概念
继承是面向对象最显著的一个特性。
继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力.(类于类之间)
Java继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类/超类/基类。(子类又叫做派生类)
优点:这种技术使得复用以前的代码非常容易,能够大大缩短开发周期,降低开发费用。
例:
// A: 子类 C:父类
class A extends c{ //原来的eat()拿走了
}//注意:继承的语法,第二个类不需要再使用class
class B extends c{ //原来的eat()拿走了
}
class c{
public void eat(){
syso("eat");
}
}
2.5.2 特点
- 使用extends关键字
- 相当于子类把父类的功能复制了一份(包括变量和方法,私有资源的也可以被继承只不过因为封装性的影响子类不能直接调用,通过创建对象也不能改变,只能通过在公共的方法间接调用私有的资源。)
- 一旦子类A继承父类B以后,子类A中就获取了父类B中声明的所有的属性和方法。特别的,父类中声明为private的属性或方法,子类继承父类以后,仍然认为获取了父类中私有的结构。只有因为封装性的影响,使得子类不能直接调用父类私有的结构而已。
- java只支持单继承(一个超类可以有多个派生类,一个派生类只能继承于一个超类)
- 继承可以传递(爷爷,儿子,孙子的关系)
- 子类继承父类以后,就获取了直接父类以及所有间接父类中声明的属性和方法
- 不能继承父类的私有成员
- 继承多用于功能的修改,子类可以拥有父类的功能的同时,进行功能拓展
- 是is a 的关系
- java规定:在构造派生类之前必须先构造超类(先有父亲在有儿子)
- 构造方法不能被继承。
2.5.3 Object类理解
- 如果我们没有显式的声明一个类的父类的话,则此类继承于java.lang.Object类
- 所有的java类(除java.lang.Object类之外)都直接或间接的继承于java.lang.Object类
- 意味着,所有的java类具有java.lang.Object类声明的功能。
- 因为Object类属于lang包下所以不用导包。
2.5.4 入门案例
package cn.tedu.extendsdemo;
//测试继承的入门案例
public class Test4_Extends {
public static void main(String[] args) {
//创建父类对象测试,提高代码的复用性/高内聚,父类写子类的共性
Father f = new Father();
f.eat();
// System.out.println(f.sum);
//创建子类对象测试
Son s = new Son();
//5,子类可以使用父类的所有功能,除了private的
s.eat();
// System.out.println(s.sum);
//6,继承具有传递性,爷爷的功能,孙子类里也能用
s.game();
System.out.println(s.count);
}
}
class Yeye{ //没有指明默认继承Lang包下的Object类
int count ;
public void game() {
System.out.println("下象棋");
}
}
//创建父类
//!!耦合性 --继承就是一种强耦合性!!!程序中耦合性越低越好,降低程序的耦合性。 888888888888888888888888(后续用接口)
class Father extends Yeye{
//4,如果父类中,资源被private修饰,这个资源子类无法继承
private int sum =10;
public void eat() {
System.out.println("爸爸在吃猪肉");
}
}
//创建子类
//1,用extends关键字表示继承关系
//3,Java只支持单继承,一个子类只能继承于一个父类
class Son extends Father{
//2,继承后,子类就能使用父类的功能,就相当于子类把父类的功能复制了一份
} //继承不会改变源码,而是拓展
2.5.5 测试:私有的属性 方法也能被继承
package com.cn.extend;
public class Test {
public static void main(String[] args) {
Student stu = new Student();
stu.setAge(15);//吃饭
stu.eat();//睡觉
/* 说明:此时使用的对象是子类student,通过公共的方法调用私有的资源,
* 而私有资源定义在父类person中,那么此时在子类中必定继承了私有资源
* 只不过这些私有的资源由于封装性无法直接使用
*
*/
}
}
class Person {
String name;
private int age;
public void eat(){
System.out.println("吃饭");
sleep();
}
private void sleep(){
System.out.println("睡觉");
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
class Student extends Person{
private String name = "小明";
}
2.5.6 成员变量的使用
package cn.tedu.extendsdemo;
//测试继承中成员变量的用法
//可以使用从父类中继承过来的,也可以用自己特有的
public class Test5_UseExtends {
public static void main(String[] args) {
//TODO 创建子类对象测试
Erzi zi = new Erzi();
System.out.println(zi.count);//20
System.out.println(zi.sum);
}
}
//创建父类
class BaBa{
int count = 10;
}
//创建子类
class Erzi extends BaBa{
int count = 20;
int sum = 20;
}
2.5.7 成员方法的使用
package cn.tedu.extendsdemo;
//测试继承中成员方法的用法
public class Test5_UseExtends {
public static void main(String[] args) {
//TODO 创建子类对象测试
Erzi zi = new Erzi();
//可以使用父类的功能,也可以使用自己特有的功能
System.out.println(zi.count);//20
System.out.println(zi.sum);
//2,如果子类没有发生方法重写,eat()使用父类的功能。
//如果子类发生了方法重写,使用的就是重写之后,也就是子类的eat()的功能。888888888888
zi.eat();//爸爸在吃肉 --》儿子在喝汤
zi.code();//可以使用特有的方法
}
}
//创建父类
class BaBa{
int count = 10;
public void eat() {
System.out.println("爸爸在吃肉");
}
}
//创建子类
class Erzi extends BaBa{
int count = 20;
int sum = 20;
//1,方法重写:出现的原因是:当父类的功能需要修改时,我们不能直接打开父类代码修改源码!!我可只能功能扩展
//扩展的过程:先发生继承关系,然后发生方法重写的现象。要求子类的方法声明和父类一模一样。
public void eat() {
System.out.println(count);//20
System.out.println(super.count);//10,通过super调用父类的功能
System.out.println("儿子在喝汤");
}
//提供子类特有方法
public void code() {
System.out.println("儿子在敲代码");
}
}
2.5.8 构造方法的使用(构造方法不能被继承)
1、子类创建对象时,默认会去访问父类的无参构造方法
2、在构造方法的第一行,都有一条默认的语句:super();
3、父类没有无参构造时,可以用super调用父类的其他构造
package cn.tedu.extendsdemo;
//测试继承中构造方法的使用
public class Test6_UseExtends2 {
public static void main(String[] args) {
//TODO 创建子类对象测试
Zi zi = new Zi();
}
}
//创建父类
class Fu{
//如果父类中,只提供含参构造,这时,无参构造就会被覆盖,没了!
//建议提供一个无参构造。
public Fu(String n) {
System.out.println("Fu()...");
}
}
//创建子类
class Zi extends Fu{
public Zi() {
//0,super关键字常用于子类中,当子类要使用父类功能时,通过super代表父类对象的引用。
//1,子类创建对象时,默认就会存在super(),也就默认就会去访问父类的无参构造。
// super();//父类没有无参构造了!!
//2,当父类中不提供无参构造时,只能通过super调用父类的含参构造
//3,super关键字如果在构造方法中使用,必须是第一条语句。
super("tony");
System.out.println("Zi()...");
}
}
2.6 super关键字
说明:
1、通过super关键字可以使用父类的内容
2、super代表父类的一个引用对象
3、如果在构造方法中使用,必须是第一条语句
语法:
1)super.成员变量名------访问超类的成员变量(用于:子父类定义同名的变量,想要在子类中使用父类的变量时使用。父子类定义2个相同名字的变量不会覆盖,变量没有覆盖功能。)
2)super.方法名()-------调用超类的方法(用于:当子类重写了父类中的方法以后,我们想在子类的方法中调用父类中被重写的方法时,则必须显式的使用"super.方法"的方式,表明调用的是父类中被重写的方法。)
3)super()--------------调用超类的构造方法
问题:为什么在实例化子类的对象的时候会调用先调用父类的构造函数?
答:因为子类继承父类之后,获取到了父类的内容(属性/字段),而这些内容在使用之前必须先初始化,所以必须先调用父类的构造函数进行内容的初始化.
注意:
- super只能出现在子类的方法或者构造方法中。
- 在子类构造方法只能有一个super。
- super当在子类的构造方法中调用父类的构造方法,则必须位于子类构造方法的第一行。(在普通方法无这一要求)
- 即使不写默认处于子类构造方法中第一行,来调用父类的无参构造。如果父类重载类构造方法,它的子类super需要根据传参的不同进行调用。
- 在子类的构造方法中,可以调用父类的构造方法,也可以调用父类的普通方法。在子类的普通方法只能调用父类普通方法,不能调用构造方法。
2.6.1 子类对象实例化的全过程
子类对象实例化的全过程:
-
从结果上来看:(继承性)
子类继承父类以后,就获取了父类中声明的属性或方法。
创建子类的对象,在堆空间中,就会加载所有父类中声明的属性。 -
从过程上来看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调其父类的构造器,进而调用父类的父类的构造器,…
直到调用了java.lang.Object类中空参的构造器为止。正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用。
明确:虽然创建子类对象时,调用了父类的构造器,但是自始至终就创建过一个对象,即为new的子类对象。
2.7 this与super的区别
代表的对象不同:
- this:代表本类对象的引用。
- super:代表父类对象的引用。
前提:
3. this: 没有继承也可以使用。
4. super: 只有在继承条件才可以使用。
调用构造方法时:
5. this: 本类的构造
6. super: 父类的构造
注意事项:this和super不可以同时出现在同一个构造方法里,他们两个只要出现都得放在构造方法第一行,同时出现的话,到底第一行放谁呢。
2.8 方法的重写
前提:发生在父子类中。
遵循两同两小一大原则:
两同:方法名相同,参数列表相同(注意:参数列表的相同不相同只和形参类型有关,和形参名字没有关系。)
两小:
1: 子类的返回值类型小于或等于父类的
1.1void和基本类型时,必须相同
1.2引用类型时:小于或等于父类的
2:子类抛出的异常小于或等于父类的
一大:子类的访问权限大于或等于父类的
注意:
- 重写指的是方法的重写,属性没有重写功能(重载也是)。
- 重写方法体也可以相同,类似于重载只不过没啥意义,一般都是不同。
- 父类中的私有方法不能被重写,因为私有的不能被继承。
- static方法不能被重写,属于类,它不属于实例。
- 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为static的(不是重写,因为跟对象有关的才是重写,静态资源属于类不叫重写)。
- final修饰的方法不能被重写。
- 构造方法不能被重写。
- 重写方法的标识为@Override注解:有特定的小功能,标志了这个方法重写了父类的功能。
为什么要进行重写???
当父类的功能,子类不需要或不一定满足时就需要重写。
2.8.1 重写入门案例
package cn.tedu.extendsdemo;
//测试继承中成员方法的用法
public class Test5_UseExtends {
public static void main(String[] args) {
//TODO 创建子类对象测试
Erzi zi = new Erzi();
//可以使用父类的功能,也可以使用自己特有的功能
System.out.println(zi.count);//20
System.out.println(zi.sum);
//2,如果子类没有发生方法重写,eat()使用父类的功能。
//如果子类发生了方法重写,使用的就是重写之后,也就是子类的eat()的功能。888888888888
zi.eat();//爸爸在吃肉 --》儿子在喝汤
zi.code();//可以使用特有的方法
}
}
//创建父类
class BaBa{
int count = 10;
public void eat() {
System.out.println("爸爸在吃肉");
}
}
//创建子类
class Erzi extends BaBa{
int count = 20;
int sum = 20;
//1,方法重写:出现的原因是:当父类的功能需要修改时,我们不能直接打开父类代码修改源码!!我可只能功能扩展
//扩展的过程:先发生继承关系,然后发生方法重写的现象。要求子类的方法声明和父类一模一样。
public void eat() {
System.out.println(count);//20
System.out.println(super.count);//10,通过super调用父类的功能
System.out.println("儿子在喝汤");
}
//提供子类特有方法
public void code() {
System.out.println("儿子在敲代码");
}
}
2.9 拓展
2.9.1 重写与重载的区别(Overload和Override的区别)
1、重载:是指同一个类中的多个方法具有相同的名字,但这些方法具有不同的参数列表,即参数的数量或参数类型不能完全相同
2、重写:是存在子父类之间的,子类定义的方法与父类中的方法具有相同的方法名字,相同的参数表和相同的返回类型
3、重写是父类与子类之间多态性的一种表现
4、重载是一类中多态性的一种表现
2.9.2 继承的内存结构
3 Day08–面向对象3
3.1 static
3.1.1 概念
1、是java中的一个关键字
2、用于修饰成员(成员变量和成员方法,静态代码块),不能修饰局部资源,不能修饰构造方法。
3.1.2 特点
1、可以修饰成员变量,成员方法,静态代码块。不可以修饰类,静态内部类除外。
2、随着类的加载而加载,优先于对象加载
3、只加载一次,就会一直存在,不再开辟新空间
4、全局唯一,全局共享
5、可以直接被类名调用(静态资源都可以通过类名点调用)
6、静态方法可以被子类继承,但不能被子类重写。
注意: 静态属性、静态方法和私有的属性都可以被继承和隐藏而不能被重写,因此不能实现多态,不能实现父类的引用可以指向不同子类的对象。非静态方法可以被继承和重写,因此可以实现多态。静态方法和属性是属于类的,调用的时候直接通过类名.方法名完成调用,不需要继承即可调用 。
问题:静态只能调用静态,非静态可以调用静态和非静态,为什么?
因为:静态资源随着类的加载而加载,优先于对象加载,类加载完成时对象还没有创建,所以不能调用。
同理static不能和this或者super共用:this,super代替的是对象,有static时可能还没有对象 ,main方法也是静态方法所以不写this,super。
调用关系总结:
同一个类之间的调用不用创建对象,不同类之间资源调用通过创建对象或是用静态资源。
非静态资源(实例变量和普通方法,不包括局部变量,因为局部变量想要调用都要先调用方法)只能通过创建对象来使用,静态资源(静态变量和静态方法)可以通过创建对象也可以通过类名.直接调用(静态资源推荐使用类名调用)。
3.1.3 入门案例
package cn.tedu.staticdemo;
//static一般放在返回值类型之前。
//这个类用来测试静态入门案例
public class Test1_Static {
public static void main(String[] args) {
//2,静态资源优先于对象加载,会优先加载进内存
//静态资源访问:有2种。
//1:通过new对象进行访问·(但是不推荐用)
//2:直接通过类名.访问
//非静态资源只能有一种:通过对象
Person.game();
System.out.println(Person.age);
//TODO 创建Person对象测试
Person p = new Person();
p.eat();
System.out.println(p.name);
//1.1, 静态资源,可以通过对象访问
p.game();
System.out.println(p.age);
//1, 静态资源,还可以通过类名访问
Person.game();
System.out.println(Person.age);
//3,静态资源,在多个对象间,是共享的
Person p1 = new Person();
Person p2 = new Person();
p1.age=10;
System.out.println(p2.age);//10
}
}
//创建Person类
class Person{
//普通资源
String name;
public void eat() {
System.out.println("eat()...");
}
//静态资源 -- 使用static修饰
static int age;
static public void game () {
System.out.println("game()...");
}
}
3.1.4 综合案例
package com.atguigu.java1;
/*
* static关键字的使用
*
* 1.static:静态的
* 2.static可以用来修饰:属性、方法、代码块、内部类
*
* 3.使用static修饰属性:静态变量(或类变量)
* 3.1 属性,按是否使用static修饰,又分为:静态属性 vs 非静态属性(实例变量)
* 实例变量:我们创建了类的多个对象,每个对象都独立的拥有一套类中的非静态属性。当修改其中一个对象中的
* 非静态属性时,不会导致其他对象中同样的属性值的修改。
* 静态变量:我们创建了类的多个对象,多个对象共享同一个静态变量。当通过某一个对象修改静态变量时,会导致
* 其他对象调用此静态变量时,是修改过了的。
* 3.2 static修饰属性的其他说明:
* ① 静态变量随着类的加载而加载。可以通过"类.静态变量"的方式进行调用
* ② 静态变量的加载要早于对象的创建。
* ③ 由于类只会加载一次,则静态变量在内存中也只会存在一份:存在方法区的静态域中。
*
* ④ 类变量 实例变量
* 类 yes no
* 对象 yes yes
*
* 3.3 静态属性举例:System.out; Math.PI;
*
* 4.使用static修饰方法:静态方法
* ① 随着类的加载而加载,可以通过"类.静态方法"的方式进行调用
* ② 静态方法 非静态方法
* 类 yes no
* 对象 yes yes
* ③ 静态方法中,只能调用静态的方法或属性
* 非静态方法中,既可以调用非静态的方法或属性,也可以调用静态的方法或属性
*
* 5. static注意点:
* 5.1 在静态的方法内,不能使用this关键字、super关键字
* 5.2 关于静态属性和静态方法的使用,大家都从生命周期的角度去理解。
*
* 6. 开发中,如何确定一个属性是否要声明为static的?
* > 属性是可以被多个对象所共享的,不会随着对象的不同而不同的。
* > 类中的常量也常常声明为static
*
* 开发中,如何确定一个方法是否要声明为static的?
* > 操作静态属性的方法,通常设置为static的
* > 工具类中的方法,习惯上声明为static的。 比如:Math、Arrays、Collections
*/
public class StaticTest {
public static void main(String[] args) {
Chinese.nation = "中国";
Chinese c1 = new Chinese();
c1.name = "姚明";
c1.age = 40;
c1.nation = "CHN";
Chinese c2 = new Chinese();
c2.name = "马龙";
c2.age = 30;
c2.nation = "CHINA";
System.out.println(c1.nation);
//编译不通过
// Chinese.name = "张继科";
c1.eat();
Chinese.show();
//编译不通过
// Chinese.eat();
// Chinese.info();
}
}
//中国人
class Chinese{
String name;
int age;
static String nation;
public void eat(){
System.out.println("中国人吃中餐");
//调用非静态结构
this.info();
System.out.println("name :" +name);
//调用静态结构
walk();
System.out.println("nation : " + nation);
}
public static void show(){
System.out.println("我是一个中国人!");
//不能调用非静态的结构
// eat();
// name = "Tom";
//可以调用静态的结构
System.out.println(Chinese.nation);
walk();
}
public void info(){
System.out.println("name :" + name +",age : " + age);
}
public static void walk(){
}
}
3.1.5 静态方法内存图
内存:由JVM管理的
1.堆:所有new出来的对象(包括 new出来的对象和成员变量)
2.栈:局部变量(包括方法的参数 基本类型的数值 引用类型的地址)
3.方法区:.class字节码文件,静态方法区,常量池。
3.2 代码块
如果成员变量想要初始化的值不是一个硬编码的常量值
,而是需要通过复杂的计算或读取文件、或读取运行环境信息等方式才能获取的一些值
,该怎么办呢?此时,可以考虑代码块(或初始化块)。
-
代码块(或初始化块)的
作用
:- 用来初始化类或对象的信息(即初始化类或对象的成员变量)
-
代码块(或初始化块)的
分类
:-
一个类中代码块若有修饰符,则只能被static修饰,称为静态代码块(static block)
-
没有使用static修饰的,为非静态代码块。
-
3.2.1 静态代码块
如果想要为静态变量初始化,可以直接在静态变量的声明后面直接赋值,也可以使用静态代码块。
1) 语法格式
在代码块的前面加static,就是静态代码块。
【修饰符】 class 类{
static{
静态代码块
}
}
2) 静态代码块的特点
-
内部可以声明变量、调用属性或方法、编写输出语句等操作。
-
作用:可以对类的属性、类的声明进行初始化操作。(开发中一般用于静态属性的赋值)
-
注意:不可以对非静态的属性初始化。即:不可以调用非静态的属性和方法。
-
若有多个静态的代码块,那么按照从上到下的顺序依次执行。
-
静态代码块的执行要先于非静态代码块。
-
静态代码块随着类的加载而加载,由于类的加载只会执行一次,进而静态代码块的执行,也只会执行一次。
package com.atguigu.keyword;
public class Chinese {
// private static String country = "中国";
private static String country;
private String name;
{
System.out.println("非静态代码块,country = " + country);
}
static {
country = "中国";
System.out.println("静态代码块");
}
public Chinese(String name) {
this.name = name;
}
}
package com.atguigu.keyword;
public class TestStaticBlock {
public static void main(String[] args) {
Chinese c1 = new Chinese("张三");
Chinese c2 = new Chinese("李四");
}
}
3.2.2 非静态代码块
1) 语法格式
【修饰符】 class 类{
{
非静态代码块
}
【修饰符】 构造器名(){
// 实例初始化代码
}
【修饰符】 构造器名(参数列表){
// 实例初始化代码
}
}
2) 非静态代码块的作用
和构造器一样,也是用于实例变量的初始化等操作。
3) 非静态代码块的意义
抽取构造方法中代码的共性:
- 如果多个重载的构造器有公共代码,并且这些代码都是先于构造器其他代码执行的,那么可以将这部分代码抽取到非静态代码块中,减少冗余代码。
4) 非静态代码块的执行特点
-
内部可以声明变量、调用属性或方法、编写输出语句等操作。
-
可以对类的属性、类的声明进行初始化操作。(开发中一般用于非静态属性的赋值)
-
非静态代码块内部可以调用静态的结构(即静态的属性、方法),也可以调用非静态的结构(即非静态的属性、方法)
-
若有多个非静态的代码块,那么按照从上到下的顺序依次执行。
-
每次创建对象的时候,都会执行一次。且先于构造器执行。
3.2.3 举例
1) 举例1:体会非静态代码块的使用场景
(1)声明User类,
-
包含属性:username(String类型),password(String类型),registrationTime(long类型),私有化
-
包含get/set方法,其中registrationTime没有set方法
-
包含无参构造,
- 输出“新用户注册”,
- registrationTime赋值为当前系统时间,
- username就默认为当前系统时间值,
- password默认为“123456”
-
包含有参构造(String username, String password),
- 输出“新用户注册”,
- registrationTime赋值为当前系统时间,
- username和password由参数赋值
-
包含public String getInfo()方法,返回:“用户名:xx,密码:xx,注册时间:xx”
(2)编写测试类,测试类main方法的代码如下:
public class UserTest {
public static void main(String[] args) {
User u1 = new User();
System.out.println(u1.getInfo());
User u2 = new User("Tom","654321");
System.out.println(u2.getInfo());
System.out.println();
User1 u3 = new User1();
System.out.println(u3.getInfo());
}
}
如果不用非静态代码块,User类是这样的:
public class User {
private String userName;
private String password;
private long registrationTime;//注册时间
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getRegistrationTime() {
return registrationTime;
}
public User(){
System.out.println("新用户注册");
registrationTime = System.currentTimeMillis();//获取系统当前时间 (距离1970-1-1 00:00:00的毫秒数)
userName = System.currentTimeMillis() + "";
password = "123456";
}
public User(String userName,String password){
System.out.println("新用户注册");
registrationTime = System.currentTimeMillis();
this.userName = userName;
this.password = password;
}
public String getInfo(){
return "用户名:" + userName + ", 密码:" + password + ",注册时间:" + registrationTime;
}
}
如果提取构造器公共代码到非静态代码块,User类是这样的:
构造方法中相同的代码可以提取到非静态代码块中
public class User1 {
private String userName;
private String password;
private long registrationTime;//注册时间
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public long getRegistrationTime() {
return registrationTime;
}
{
System.out.println("新用户注册");
registrationTime = System.currentTimeMillis();//获取系统当前时间 (距离1970-1-1 00:00:00的毫秒数)
}
//代码块的使用
public User1(){
// System.out.println("新用户注册"); 构造方法中相同的代码可以提取到非静态代码块中
// registrationTime = System.currentTimeMillis();
userName = System.currentTimeMillis() + "";
password = "123456";
}
public User1(String userName,String password){
// System.out.println("新用户注册");
// registrationTime = System.currentTimeMillis();
this.userName = userName;
this.password = password;
}
public String getInfo(){
return "用户名:" + userName + ", 密码:" + password + ",注册时间:" + registrationTime;
}
}
2) 举例2:体会静态代码块的使用场景
/**
* 给静态属性赋值:
* 考虑1:显示赋值 不行
* (成员变量不能出现语句,对于复杂的需要计算的变量值不能直接显示的赋值)
* 考虑2:静态代码块 可以
* 考虑3:构造方法 不行
* (放在构造器中,每造一个对象都会执行一遍,静态的只需要执行一次就够了,没必要)
* 考虑4:放在一个静态方法中,通过set方法进行间接调用: 不行
* (太晚了,现在希望类加载完就执行静态方法)
*/
public class tst {
private static DataSource dataSource = null;
static{
InputStream is = null;
try {
is = DBCPTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
Properties pros = new Properties();
pros.load(is);
//调用BasicDataSourceFactory的静态方法,获取数据源。
dataSource = BasicDataSourceFactory.createDataSource(pros);
} catch (Exception e) {
e.printStackTrace();
}finally{
if(is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
3.2.4 小结:
1) 实例变量赋值位置、顺序(先赋值在声明)
-
可以给类的非静态的属性(即实例变量)赋值的位置有:
① 默认初始化 (默认值)
② 显式初始化 或 ⑤ 代码块中初始化 (谁放上面先执行谁)
③ 构造器中初始化
④ 有了对象以后,通过"对象.属性"或"对象.方法"的方法进行赋值 -
执行的先后顺序:
① - ②/⑤ - ③ - ④
public class FieldTest {
public static void main(String[] args) {
Order o1 = new Order();
System.out.println(o1.orderId);//1
}
}
class Order{
/**
* 一般变量是先声明后赋值,为什么写在代码块中可以先
* 赋值,然后在下面中再声明呢???
*
* 因为:orderId在整个类的加载过程中,在其中的某个环节已经提前加载过了,并且做了
* 个默认赋值为0,这个属性已经有值了,所以之后在代码块中赋值也不会报错。
*/
{
orderId = 2;
}
int orderId = 1;
public Order(){
super();
// orderId = 3;
}
public Order(int orderId){
this.orderId = orderId;
}
}
2) 选哪个位置对实例变量进行赋值
给实例变量赋值的位置很多,开发中如何选?
-
显示赋值:比较适合于每个对象的属性值相同的场景
-
构造器中赋值:比较适合于每个对象的属性值不相同的场景
3.2.5 关于字节码文件中的的简单说明
idea中使用了这个插件可以反编译看到字节码文件,其中<init>方法
对应着一个类的构造器
(超纲)关于字节码文件中的<init>的简单说明:(通过插件jclasslib bytecode viewer查看)
> <init>方法在字节码文件中可以看到。每个<init>方法都对应着一个类的构造器。(类中声明了几个构造器就会有几个<init>)
> 编写的代码中的构造器在编译以后就会以<init>方法的方式呈现
> <init>方法内部的代码包含了实例变量的显示赋值、代码块中的赋值和构造器中的代码。
> <init>方法用来初始化当前创建的对象的信息的。`
3.2.6 局部代码块
1、在方法里面(普通方法和构造方法都可以)的代码块
2、通常用于控制变量的作用范围,出了括号就失效
3、变量的范围越小越好,成员变量会有线程安全问题
4、方法调用时触发
5、方法调用时调用局部代码块。
6. { }
3.2.7 测试:构造,局部代码块加载顺序
构造代码块->构造方法->局部代码块
package cn.tedu.block;
//测试代码块
//1,创建对象时,会自动触发构造方法。如果有构造代码块会先执行代码块再执行构造方法,
//若局部代码块写在构造方法里面,构造方法和局部代码块的顺序是看程序写代码的顺序执行。
//若写在普通方法里面,则是调用的时候用(它则是最后用,因为是先创建对象,在通过引用调用方法)。
//总结:
//1,构造代码块:创建对象时触发,在类里方法外,用来抽取构造方法的共性
//2,局部代码块:方法调用时触发,在方法里,用来控制变量的作用范围
//若没有创建对象,构造代码块都不会自动执行
//2.每次创建对象都会调用构造代码块和构造方法
public class Test1_Block {
public static void main(String[] args) {
//TODO 创建Person对象测试
new Person();
//2,每次创建对象都会调用构造代码块和构造方法
new Person();
new Person("name");
new Person(100).sleep();//触发局部代码块
}
}
//创建Person类
class Person{
String country;//为了让每个构造方法都使用,提高作用范围
//b,提供构造代码块 : 位置:在类里方法外
{
//3,构造代码块:用来提取构造方法的共性
country = "中国人";
}
//a,提供构造方法
public Person() {
System.out.println("无参构造...,我的国籍是:"+country);
}
//c,提供重载构造方法
public Person(String c) {
System.out.println("含参构造...,我的国籍是:"+country);
}
public Person(int a) {
System.out.println("含参构造...,我的国籍是:"+country);
}
//提供普通方法
public void sleep() {
//局部代码块: 位置:在方法里 + 作用:控制变量的作用范围
{
int i = 10;
System.out.println("局部代码块..."+i);
}
// System.out.println(i);
}
}
3.2.8 测试静态,构造,局部代码块加载顺序
执行顺序是:静态代码块最快,因为它是类加载的时候被自动调用,且只调用一次其次是构造代码块,构造方法,局部代码快。
package cn.tedu.block;
//测试代码块
//总结:
//1, 触发时间节点:调用方法时,会先进入类里面,然后会自动调用静态块(即,类被第一次加载时)
//2,位置 :类里面,方法外
//3,作用/功能:只被加载一次的时候用。
//执行顺序是:静态代码块最快,因为它是类加载的时候被自动调用,且只调用一次其次是构造代码块,构造方法,局部代码快。
public class Test3_Block {
public static void main(String[] args) {
//TODO 创建对象测试
TestBlock tb = new TestBlock();
tb.show();//触发局部代码块
TestBlock tb2 = new TestBlock();
}
}
//创建TestBlock类
class TestBlock{
//1,提供构造代码块
{
System.out.println("构造代码块");
}
//2,提供静态代码块:加载的早,而且只加载一次
static{
System.out.println("静态代码块");
}
//3,提供构造方法
public TestBlock() {
System.out.println("构造方法");
}
//4,提供局部代码块
public void show() {
{
System.out.println("局部代码块");
}
}
}
3.2.9 总结: 静态代码块,构造代码块,局部代码块
静态代码块 static{ }:在类加载时就加载,并且只被加载一次,一般用于项目的初始化,类里方法外。
构造代码块 { }:在创建对象时会自动调用,每次创建对象都会被调用,通常用于抽取构造方法中的共性代码,可以在创建对象时,对对象的属性等进行初始化类里方法外。
局部代码块 { }:调用方法时调用,用于控制变量的作用范围,方法中。
3.2.10 拓展: 静态代码块和静态方法区别
- 如果有些代码必须在项目启动的时候执行,需要使用静态代码块,这种代码是主动执行的;
- 如果需要在项目启动的时候初始化,在不创建对象的情况下,其他程序来调用的时候,需要使用静态方法,这种代码是被动执行的。静态方法在类加载的时候加载,可以用类名直接调用。比如:main方法就必须是静态的 ,这是程序入口。
两者的区别:静态代码块是自动执行的,静态方法是被调用的时候才执行的。
3.3 final
3.3.1 概念
final:是个修饰符,可用于修饰方法、类、变量
3.3.2 特点
-
被final修饰的类,不能被继承
- 好处:使一个类不能被继承的意义在于,可以保护类不被继承修改,可以控制滥用继承对系统造成的危害
- 如:String类、System类、StringBuffer类
-
被final修饰的方法,不能被重写,但可以重载。
- 如:Object类中getClass();
-
被final修饰的变量是个常量,值不能被更改。
- 常量命名规范:推荐所有字母大写,多个单词用下划线(“_”)隔开
- 对于基本类型和引用类型:
- 基本类型:数据值不可改变
- 引用类型:地址值不可改变,但是内容可以改变。如一个对象中的属性值可以修改。
- 对于成员变量和局部变量:
- 成员变量:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
- 局部变量:尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值以后,就只能在方法体内使用此形参,但不能进行重新赋值。
常量的定义形式: final 数据类型 常量名 = 值;
,一般为了方便外界调用,都会被static修饰,可以直接被类名.访问。最终形式为全局常量:static final 数据类型 常量名 = 值;
。
常量使用场景:通常用于记录系统的配置信息
使用常量记录系统配置信息的优势、执行原理:
3.3.3 入门案例
package com.atguigu.java3;
/*
* final:最终的
*
* 1. final可以用来修饰的结构:类、方法、变量
*
* 2. final 用来修饰一个类:此类不能被其他类所继承。
* 比如:String类、System类、StringBuffer类
*
* 3. final 用来修饰方法:表明此方法不可以被重写
* 比如:Object类中getClass();
*
* 4. final 用来修饰变量:此时的"变量"就称为是一个常量
* 4.1 final修饰成员变量:可以考虑赋值的位置有:显式初始化、代码块中初始化、构造器中初始化
* 4.2 final修饰局部变量:
* 尤其是使用final修饰形参时,表明此形参是一个常量。当我们调用此方法时,给常量形参赋一个实参。一旦赋值
* 以后,就只能在方法体内使用此形参,但不能进行重新赋值。
*
* static final 用来修饰属性:全局常量
*/
public class FinalTest {
final int WIDTH = 0;
final int LEFT;
final int RIGHT;
// final int DOWN; //成员变量有默认值,但是被final修饰后会报错,可以显示的赋值。
{
LEFT = 1; //可以在代码块中赋值
}
public FinalTest(){
RIGHT = 2;//多个构造器都要赋值否则报错
}
public FinalTest(int n){
RIGHT = n; //可以在构造方法中赋值
}
// public void setDown(int down){
// this.DOWN = down;
// }
public void doWidth(){
// width = 20;
}
public void show(){
final int NUM = 10;//常量
// NUM += 20;
}
public void show(final int num){
// num = 20;//编译不通过
System.out.println(num);
}
public static void main(String[] args) {
int num = 10;
num = num + 5;
FinalTest test = new FinalTest();
// test.setDown(3);
test.show(10);
}
}
final class FinalA{
}
//class B extends FinalA{
//
//}
//class C extends String{
//
//}
class AA{
public final void show(){
}
}
class BB extends AA{
// public void show(){
//
// }
}
3.4 多态
3.4.1 概念
多态指同一个实体同时具有多种形式。它是面向对象程序设计(OOP)的一个重要特征。主要是指同一个对象,在不同时刻,代表的对象不一样,指的是对象的多种形态。
对象的多态性:父类引用指向子类对象(或子类的对象赋给父类的引用)。
举例1:
// 可以指向不同的子类对象,体现多态性
Animal a = new Dog();
//父类 子类
Animal a = new Cat();
举例2:
水,在不同时刻可以有多种形态,包括水蒸气,冰,水。
Java怎么体现多态呢?狗有两种形态:狗和小动物
class Animal {
public void eat(){
}
}
class Dog extends Animal{
}
class Test1{
main(){
//创建子类对象测
Dog d = new Dog();//小狗就是小狗
//创建多态对象测试
Animal a = new Dog();//小狗是小动物(是父类类型就是多态)
//父类引用 ,指向 子类对象
//编译ctrl+s看左边,运行ctrl+f11看右边
}
}
3.4.2 特点
1、 多态的前提1:2个类有继承关系
2、 多态的前提2:要有方法的重写
3、多态指的是方法的多态,属性没有多态(因为属性不存在重写)。
4、属性:调用的是父类的属性。
5、方法:调用了父类的方法声明,输出的是子类的重写父类方法的方法体(静态方法除外)
6、静态方法 :可以被继承和隐藏但不能重写,因此不能实现多态。
7、 父类引用指向子类对象,如:Animal a = new Dog(); - - 大类型到小类型,又叫作向上转(造)型
8、pereson p = new Son();能调用什么永远只和等号左边有关系,和右边没关系
9、 多态中,编译看左边(能调用什么看左边),运行看右边(能输出什么看右边)
我们写的代码就是.java源文件, ctrl +s会自动将.java文件编译为.class字节码文件。
3.4.3 入门案例
package cn.tedu.duotai;
//测试多态的入门案例
public class Test5_Duotai {
public static void main(String[] args) {
//TODO 创建父类对象测试
Animal a = new Animal();
a.eat();//就是使用父类自己的功能
//TODO 创建子类对象测试
Dog d = new Dog();
d.eat();//重写前,用父类的。重写后,执行的就是子类的。
//TODO 创建多态对象测试
Animal an = new Dog();//口诀1:父类引用 指向 子类对象
//口诀2:编译看左边,运行看右边
//编译看左边:想要能够保存成功,只能使用左边也就是父类提供的方法声明部分
//运行看右边:是指发生了重写后,执行结果以子类为准
an.eat();//调用了父类的方法声明,输出的是子类的重写父类方法的方法体。 888888888888888
//多态主要用来统一调用标准:所有方法的调用向父类看齐
}
}
//1, 多态的前提:继承+重写
//创建父类
class Animal{
public void eat() {
System.out.println("爸爸在吃肉");
}
}
//创建子类
class Dog extends Animal{
@Override //它会检查是否写了方法重写,2者一块出现
public void eat() {
System.out.println("儿子在喝汤");
}
//子类特有方法,多态对象不能调用!!!!想用,可以创建子类对象用
public void eat2() {
System.out.println("儿子在喝汤");
}
}
3.5 多态的好处
- 多态可以让我们不用关心某个子类对象到底是什么具体类型统一用父类来接收,就可以使用该子类对象的某些方法。这样可以写出通用的代码,做出通用的编程。(即:统一调用标准,标准就是父类。)
- 提高了程序的扩展性和可维护性
3.5.1 测试:多态的好处
package com.cn.extend;
import java.sql.Connection;
//多态性的使用举例一:
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
/* 情景:想要使用子类特有的方法。
* 有多态 :只需要定义一个方法,方法的参数是父类对象类型,调用时传递不同的子类对象来调用不同子类特有方法。
* 没有多态:需要定义多个重载的方法,每个方法定义对应的子类对象类型,调用时传递不同的子类对象来调用不同子类特有方法。
*/
public void func(Animal animal){//Animal animal = new Dog(); 有多态
animal.eat();
animal.shout();
if(animal instanceof Dog){
Dog d = (Dog)animal;
d.watchDoor();
}
}
// public void func(Dog dog){ 没有多态每次需要定义重载的方法 Dog dog = new Dog();
// dog.eat();
// dog.shout();
// }
// public void func(Cat cat){ Cat cat = new Cat();
// cat.eat();
// cat.shout();
// }
}
class Animal{
public void eat(){
System.out.println("动物:进食");
}
public void shout(){
System.out.println("动物:叫");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃骨头");
}
public void shout(){
System.out.println("汪!汪!汪!");
}
public void watchDoor(){
System.out.println("看门");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵!喵!喵!");
}
}
//举例二:
class Order{
public void method(Object obj){//只需要声明一个方法接收它的本身或子类对象类型。
}
}
//举例三:
class Driver{
/* conn = new MySQlConnection(); conn = new OracleConnection();
* 比如Connection是操作数据库连接对象Api的统一父类对象类型,通过父类的引用调用方法操作数据库
* 的步骤是固定的几步都是调用这几个方法。
* 现在连接的是mysql只需要调用方法时传递mysql的连接对象,
* 变为连接的是oracle数据库的对象,只需要在调用方法时传递oracle的对象即可,
* 连方法中的代码都不用改变(因为多态调用的是父类方法的声明,输出的是子类重写父类方法的方法体)。
*
*/
public void doData(Connection conn){
//规范的步骤去操作数据
// conn.method1();
// conn.method2();
// conn.method3();
}
}
3.6 向上转型与向下转型
首先,一个对象在new的时候创建是哪个类型的对象,它从头至尾都不会变。即这个对象的运行时类型,本质的类型用于不会变。但是,把这个对象赋值给不同类型的变量时,这些变量的编译时类型却不同。
3.6.1 为什么要类型转换
因为多态,就一定会有把子类对象赋值给父类变量的时候,这个时候,在编译期间
,就会出现类型转换的现象。
但是,使用父类变量接收了子类对象之后,我们就不能调用
子类拥有,而父类没有的方法了。这也是多态给我们带来的一点"小麻烦"。所以,想要调用子类特有的方法,必须做类型转换,使得编译通过
。
-
向上转型:当左边的变量的类型(父类) > 右边对象/变量的类型(子类),我们就称为向上转型
- 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,
不能调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型,所以执行的方法是子类重写的方法体。
- 此时,一定是安全的,而且也是
自动完成
的
- 此时,编译时按照左边变量的类型处理,就只能调用父类中有的变量和方法,
-
向下转型:当左边的变量的类型(子类)<右边对象/变量的编译时类型(父类),我们就称为向下转型
- 此时,编译时按照左边变量的类型处理,
就可以调用子类特有的变量和方法了
- 但是,运行时,仍然是对象本身的类型
- 不是所有通过编译的向下转型都是正确的,可能会发生ClassCastException,为了安全,可以通过isInstanceof关键字进行判断
- 此时,编译时按照左边变量的类型处理,
3.6.2 如何向上或向下转型
- java中认为超类大,派生类小。
- 向上转型:自动完成
- 小到大自动转
- 向下转型:(子类类型)父类变量
- 大到小强转
3.6.3 instanceof关键字
为了避免ClassCastException的发生,Java提供了 instanceof
关键字,给引用变量做类型的校验。如下代码格式:
//检验对象a是否是数据类型A的对象,返回值为boolean型
对象a instanceof 数据类型A
- 说明:
- 只要用instanceof判断返回true的,那么强转为该类型就一定是安全的,不会报ClassCastException异常。
- 如果对象a属于类A的子类B,a instanceof A值也为true。
- 要求对象a所属的类与类A必须是子类和父类的关系,否则编译错误。
代码:
package com.atguigu06.polymorphism;
/**
* ClassName: PersonTest1
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 17:03
* @Version 1.0
*/
public class PersonTest1 {
public static void main(String[] args) {
Person p1 = new Man();
//不能直接调用子类特有的结构
// p1.earnMoney();
// System.out.println(p1.isSmoking);
//
//向下转型:使用强转符。
Man m1 = (Man)p1;
m1.earnMoney();
System.out.println(m1.isSmoking);
System.out.println(p1 == m1);//true,p1和m1指向堆空间中的同一个对象。
/*
* 向下转型可能会出现:类型转换异常(ClassCastException)
* */
Person p2 = new Woman();
// Man m2 = (Man)p2;
// m2.earnMoney();
/*
* 1. 建议在向下转型之前,使用instanceof进行判断,避免出现类型转换异常
* 2. 格式: a instanceOf A : 判断对象a是否是类A的实例。
* 3. 如果a instanceOf A 返回true,则: (即:A可以转,那么A的父类也可以转)
* a instanceOf superA 返回也是true。其中,A 是superA的子类。
* */
if(p2 instanceof Man){ //p2看的是右边具体的实体 new Woman();
Man m2 = (Man)p2;
m2.earnMoney(); //不能输出
}
if(p2 instanceof Woman){
System.out.println("Woman"); //可以
}
if(p2 instanceof Person){
System.out.println("Person"); //可以
}
if(p2 instanceof Object){
System.out.println("Object"); //可以
}
}
}
用到的继承关系类:
package com.atguigu06.polymorphism;
/**
* ClassName: Person
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 16:20
* @Version 1.0
*/
public class Person {
String name;
int age;
int id = 1001;
public void eat(){
System.out.println("人吃饭");
}
public void walk(){
System.out.println("人走路");
}
}
--------------------------------
package com.atguigu06.polymorphism;
/**
* ClassName: Man
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 16:21
* @Version 1.0
*/
public class Man extends Person{
boolean isSmoking;
int id = 1002;
@Override
public void eat(){
System.out.println("男人多吃肉,长肌肉");
}
@Override
public void walk(){
System.out.println("男人笔挺的走路");
}
public void earnMoney(){
System.out.println("男人挣钱养家");
}
}
--------------------------------
package com.atguigu06.polymorphism;
/**
* ClassName: Woman
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 16:22
* @Version 1.0
*/
public class Woman extends Person{
boolean isBeauty;
@Override
public void eat(){
System.out.println("女人应该少吃,减肥");
}
@Override
public void walk(){
System.out.println("女人窈窕的走路");
}
public void goShopping(){
System.out.println("女人喜欢逛街...");
}
}
3.7 main方法的用法
由于JVM需要调用类的main()方法,所以该方法的访问权限必须是public,又因为JVM在执行main()方法时不必创建对象,所以该方法必须是static的,该方法接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数。
又因为main() 方法是静态的,我们不能直接访问该类中的非静态成员,必须创建该类的一个实例对象后,才能通过这个对象去访问类中的非静态成员,这种情况,我们在之前的例子中多次碰到。
3.7.1 案例1:测试main方法的作用
package com.atguigu.java2;
/*
* main()方法的使用说明:
* 1. main()方法作为程序的入口,格式是固定的。
* 2. main()方法也是一个普通的静态方法
* 3. main()方法可以作为我们与控制台交互的方式。(之前:使用Scanner)
*
*
*
*/
public class MainTest {
public static void main(String[] args) {//入口
Main.main(new String[100]);
MainTest test = new MainTest();
test.show();
}
public void show(){
}
}
class Main{
public static void main(String[] args) {
for(int i = 0;i < args.length;i++){
args[i] = "args_" + i;
System.out.println(args[i]);
}
}
}
3.7.2 案例2:与控制台的交互方式
Scanner或者使用main()的形参进行传值
1)在idea中
命令行参数用法举例
public class CommandPara {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
System.out.println("args[" + i + "] = " + args[i]);
}
}
}
//运行程序CommandPara.java
java CommandPara "Tom" "Jerry" "Shkstart"
//输出结果
args[0] = Tom
args[1] = Jerry
args[2] = Shkstart
IDEA工具:
(1)配置运行参数
(2)运行程序
笔试题:
//此处,Something类的文件名叫OtherThing.java
class Something {
public static void main(String[] something_to_do) {
System.out.println("Do something ...");
}
}
//上述程序是否可以正常编译、运行?
2)在eclipse中
1.首先运行main方法生成字节码文件
2.选中Run Configurations
3.此时会自动定位到刚才运行的程序
4.添加参数
5.运行结果
3)在Dos命令窗口
1.复制.java文件并去掉代码中的包名—>放在磁盘目录中
2.打开命令行窗口
3.输入参数运行
3.8 拓展
3.8.1 静态变量和实例变量的区别
在语法定义上的区别:静态变量前要加static关键字,而实例变量前则不加。
在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了,当然通过对象也可以。
总之,实例变量必只能通过创建对象来使用,静态变量可以通过创建对象也可以通过类名.直接调用(静态变量推荐使用类名调用)。
4 Day09–面向对象4
4.1 访问控制符
用来控制一个类
,或者类中的成员变量
,方法
的访问范围。(不能用于局部变量
)
修饰符 | 类 | 包 | 同包/不同包子类 | 同一个工程任意位置 |
---|---|---|---|---|
public | √ | √ | √ | √ |
protected | √ | √ | √ | |
default | √ | √ | ||
private | √ | |||
解释: |
- public:公开的,同一个项目的任意位置,任意类中
- protected:受保护的,本类、同包类、同包子类 /不通包子类。注意不通包只能在子类中使用,同包只要是同一个包下都能使用。
- 默认的:什么也不写,本类、同包类(同一个包中的类),但是不能把default写出来
- private:私有的,本类
注意:
- 像这种修饰符public,protected,default,private,static,final,abstract不区分先后顺序,比如修饰方法只需要在返回值类型即可。
- 类的访问修饰符只能
默认的
或者是public
(一个.java文件中public修饰的类只能有一个,且类名与文件名相同) - 类中成员(变量,方法)的访问修饰符如上4种都可以
- 习惯一般数据(成员变量)私有化(private)行为(方法)公开化(public)) 。
- 如果是继承关系中的父类,里面是权限至少是 protected,因为要使用它的派生类要调用父类资源。
4.2 抽象类
4.2.1 概念
Java中可以定义没有方法体的方法,该方法由其子类来具体的实现。该没有方法体的方法我们称之为抽象方法,含有抽象方法的类我们称之为抽象类。
抽象类可以理解为是一个只有方法声明没有方法体的特殊类。
公式:
//抽象方法,没有方法体,大括号也没有,有一个”;”。
修饰符 abstract 返回值类型 方法名(参数列表);
抽象类的意义:可以把代码共性进行抽取,子类继承进行扩展,提高代码开发效率。
举例:水果,东西。。
class A{
public void eat(){//声明一样,可以提取
syso("eat...B") }
}
class B{
public void eat(){//声明一样,可以提取
syso("eat…A") }
}
abstract class C{
public abstract void eat();
}
4.2.2 特点
- 通过java关键字abstract实现。
- 可以修饰方法或者类。
- 含有抽像方法的类一定是抽象类,抽象类不一定含有抽象方法(因为可以由子类去实现)。
- 抽象类中可以没有抽象方法(由子类去实现),可以有抽象方法,也可以有普通方法,必有构造方法(用于子类实例化),可以有成员变量,常量。
- 如果类中有抽象方法,那该类必须定义为一个抽象类。
- 子类继承了抽象类以后,(1)要么还是一个抽象类,(2)要么就把
所有
父类的抽象方法都重写。 - 多用于多态中。
- 抽象类不可以被new实例化对象但是可以new数组。
抽象类为什么有构造方法:没有的话你的子类将无法编译,因为在任何构造函数中的第一条语句隐式调用super()。
4.2.3 abstract使用上的注意点
- abstract不能用来修饰:属性、构造器等结构。
- 如果修饰构造器就没有方法体了,子类还带重写,构造器只能重载不能重写。
- abstract不能用来修饰私有方法、静态方法、final的方法、final的类。
- abstract修饰的方法肯定要在子类中重写,私有方法在子类中可以被继承和隐藏但是不能重写。
- 同样static修饰的方法不能被重写(即便父接口和子类是同名同参的方法也不叫重写,因为重写是跟对象关联的)
- 同样final修饰的方法不能被重写,规定。
- abstract修饰的类肯定是希望被继承的,因为它本身不能创建对象而是通过子类来创建对象,final修饰的类不能被继承所以不能连用。
4.2.4 总结:不能同时出现的情况
- final 和abstract不能同时出现因为fianl是固定的不能被继承,而
abstract 一般不单独使用,和派生类一起使用 - this 和super不能连用,因为在构造方法都在第一行
- static 和this super不能同时出现,因为有static时还没有创建对象。(main方法也是静态方法所以不能有this,super)
注意:修饰符 final static abstract 几乎都在void之前写
4.2.5 入门案例
package cn.tedu.abstractdemo;
//测试抽象类的入门案例
public class Test1_Abstract {
public static void main(String[] args) {
//TODO 创建多态对象测试
//7,抽象类不能被实例化new 所以我们可以用多态实例它普通子类的对象
// Animal a = new Dog();
Animal a = new Cat();
a.eat();//狗吃肉
}
}
//创建父类
//3,如果类里有抽象方法,这个必须声明成一个抽象类
abstract class Animal{
//1, 父类提供的方法,如果子类要改,改的是方法体,但是要求方法声明不许改!!!(及:不用父类的方法体,甚至要覆盖它,可以不写父类的方法体就是抽象方法)
//这时,我们能不能只提供方法声明,不提供方法体 -- 可以,此时的方法就没有了方法体,称为抽象方法
//2, 通过abstract来修饰成抽象的
abstract public void eat() ;
//4,再提供一个抽象方法 和 普通方法
abstract public void game();
public void chiji() {
System.out.println("正在吃鸡");
}
}
//创建子类
//5,子类继承抽象后,子类可以把所有抽象方法 全都重写,那就是一个普通子类
class Cat extends Animal{
//重写所有抽象方法
// 注解:1,说明发生了方法的重写2.检出这个地方必须会发生重写,只有这个注解不写方法会报错
@Override //快捷键 @ alt+ /
//也可以直接,重写的方法名+alt+/
public void eat() {
System.out.println("猫吃鱼");
}
@Override
public void game() {
System.out.println("玩毛线");
}
}
//6,子类继承抽象后,没有把所有抽象方法 全都重写,那就是一个抽象子类
abstract class Dog extends Animal{
//重写抽象方法
@Override
public void eat() {
System.out.println("狗吃肉");
}
}
4.3 abstract关键字的使用
package com.cn.per;
/*
* abstract关键字的使用
* 1.abstract:抽象的
* 2.abstract可以用来修饰的结构:类、方法
*
* 3. abstract修饰类:抽象类
* > 此类不能实例化
* > 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
* > 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
*
*
* 4. abstract修饰方法:抽象方法
* > 抽象方法只有方法的声明,没有方法体
* > 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
* > 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
* 若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
*/
public class AbstractTest {
public static void main(String[] args) {
//一旦Person类抽象了,就不可实例化
// Person p1 = new Person();
// p1.eat();
}
}
abstract class Creature{
public abstract void breath();
}
abstract class Person extends Creature{
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//不是抽象方法:
// public void eat(){
//
// }
//抽象方法
public abstract void eat();
public void walk(){
System.out.println("人走路");
}
}
class Student extends Person{
public Student(String name,int age){
super(name,age);
}
public Student(){
}
public void eat(){
System.out.println("学生多吃有营养的食物");
}
@Override
public void breath() {
System.out.println("学生应该呼吸新鲜的没有雾霾的空气");
}
}
4.4 抽象类的用法
4.4.1 构造方法
抽象类也有构造方法,但是不能本身实例化。
那抽象类的构造函数有啥用?
一般用于给子类实例化。(因为子类有super默认调用父类构造方法。)
package cn.tedu.abstractdemo;
//测试抽象类构造方法的用法
public class Test2_UseAbstract {
public static void main(String[] args) {
//3,抽象类不能被实例化,那提供构造方法有啥用??--用来创建子类对象。
//原因是:子类创建对象时,构造方法里默认就会存在super()
// Animal2 a = new Animal2();
//TODO 创建多态对象测试
Animal2 a = new Dog2();
}
}
//创建父类
abstract class Animal2{
//1,可以有构造方法 -- 作用:用来创建对象
public Animal2() { //最好手动提供一个无参构造
System.out.println("Animal2()...");
}
}
//创建子类
class Dog2 extends Animal2{
public Dog2() {
super();//2,默认就存在,会自动调用父类的构造方法
System.out.println("Dog2()...");
}
}
4.4.2 抽象类的成员变量和常量
既可以有变量,也可以有常量。
package cn.tedu.abstractdemo;
//测试抽象类的成员变量和常量的用法
public class Test2_UseAbstract {
public static void main(String[] args) {
//3,抽象类不能被实例化,那提供构造方法有啥用??--用来创建子类对象。88888888888888888
//原因是:子类创建对象时,构造方法里默认就会存在super()
// Animal2 a = new Animal2();
//TODO 创建多态对象测试
Animal2 a = new Dog2();
System.out.println(a.sum);//10
System.out.println(a.NAME);//xiongda
System.out.println(Animal2.NAME);//xiongda
}
}
//创建父类
abstract class Animal2{
//a,抽象类里可以提供成员变量
int sum = 10;
//b,抽象类里可以提供成员常量
static final String NAME="xiongda";
//1,提供构造方法 -- 作用:用来创建对象
public Animal2() {
System.out.println("Animal2()...");
}
}
//创建子类
class Dog2 extends Animal2{
public Dog2() {
super();//2,默认就存在,会自动调用父类的构造方法
System.out.println("Dog2()...");
}
}
4.4.3 抽象类的成员方法
- 抽象类里,既可以有普通方法,有可以有抽象方法。
- 抽象类里可以都是普通方法吗? – 可以
- 抽象类里如果都是普通方法,为什么要被修饰成抽象类呢?—不让外界new(不让外界实例化对象)
- 抽象类里可以有普通方法吗? – 可以
- 抽象类可以实例化吗? – 不可以
- 抽象类是一个特殊的类,特殊在哪儿? – 可以包含抽象方法
- 怎么去决定,给抽象类提供的是普通方法还是抽象方法呢?–看要不要给方法体
- 测试:
package cn.tedu.oop;
//测试 抽象类 成员方法
public class Test1_UseAbstract {
public static void main(String[] args) {
//创建多态对象测试
Fu fu = new Zi();
fu.eat();
fu.sleep();
fu.game();
}
}
//2, 如果类中包含抽象方法,那么,这个类必须修饰成 抽象类
//3, 抽象类是一个特殊的类 ,比较灵活. 特殊在 : 抽象类里可以有抽象方法 , 也可以有普通方法 .
//到底是普通方法还是抽象方法,看你要不要提供方法体了.
abstract class Fu{
//4, 这个方法是最终方法,不能被子类重写!!
final public void eat() {
System.out.println("爸爸在吃肉");
}
//1 , sleep被子类继承,并且发生了方法重写,也就是想改父类的方法体.---父类就不提供方法体了,就变成了抽象方法
abstract public void sleep() ;
abstract public void game() ;
}
//5 , 子类继承抽象类以后,可以 重写所有的抽象方法 , 否则 , 子类就包含着抽象方法是一个抽象子类
class Zi extends Fu{
@Override
public void sleep() {
System.out.println("Zi...sleep()");
}
@Override
public void game() {
System.out.println("Zi...game()");
}
}
4.5 抽象类的匿名子类
package com.cn.per;
/*
* 抽象类的匿名子类:凡是匿名的作用都是为了省事,只用一次。
* 匿名子类:不用在创建类继承抽象父类后再重写方法,直接在new对象的同时重写方法,简化了代码。
*
*/
public class PersonTest {
public static void main(String[] args) {
method(new Student());//普通类的匿名对象
Worker worker = new Worker();
method1(worker);//普通类的普通对象
method1(new Worker());//普通类的匿名对象
System.out.println("********************");
//创建了一匿名子类的普通对象:p
/*
* 省略class Worker extends Person{......},
* 直接创建对象的时候重写抽象父类的抽象方法即可,不用在单独的创建类继承后再重写抽象方法。
*
* 格式:用父类接收、new的是父类(实际上不是,只是看着像)
*
* 注意1:这里是创建了没有名字的子类对象,不是父类的对象Person。
* person是抽象类也不能够创建对象。
*
* 解释:
* 匿名对象:就是前面不使用变量来接收new的对象
* 匿名子类/实现类:就是在new的对象后面加上大括号,在里面重写父类/父接口的方法
*/
Person p = new Person(){
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸");
}
};
method1(p);
System.out.println("********************");
//创建匿名子类的匿名对象 new的是父类(实际上不是,只是看着像)
method1(new Person(){
@Override
public void eat() {
System.out.println("吃好吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸新鲜空气");
}
});
} //main方法的结束符
public static void method1(Person p){
p.eat();
p.breath();
}
public static void method(Student s){
}
}
class Worker extends Person{ //用了匿名子类的对象这一步可省略
@Override
public void eat() {
}
@Override
public void breath() {
}
}
4.6 抽象类的应用:模板方法的设计模式
package com.atguigu.java;
/*
* 抽象类的应用:模板方法的设计模式
*
*/
public class TemplateTest {
public static void main(String[] args) {
SubTemplate t = new SubTemplate();
t.spendTime();
}
}
abstract class Template{
//计算某段代码执行所需要花费的时间
public void spendTime(){
long start = System.currentTimeMillis();
this.code();//不确定的部分、易变的部分
long end = System.currentTimeMillis();
System.out.println("花费的时间为:" + (end - start));
}
public abstract void code();
}
class SubTemplate extends Template{
@Override
public void code() {
for(int i = 2;i <= 1000;i++){
boolean isFlag = true;
for(int j = 2;j <= Math.sqrt(i);j++){//开方
if(i % j == 0){
isFlag = false;
break;
}
}
if(isFlag){
System.out.println(i);
}
}
}
}
4.7 不让外界实例化对象的方式(2种)
- 把你的类修饰成抽象类
- 把你的构造方法私有化
4.8 给成员变量赋值的方式(2种)
1.调用setXxx()
2.利用构造方法赋值(创建对象时会调用构造方法,在构造方法里面把产传递得值赋给成员变量)
原因:不直接赋值是因为写死了代码不灵活。
5 Day10–面向对象5
5.1 接口(default新特性)
5.1.1 概念
Java里面由于不允许多重继承,所以如果要实现多个类的功能,则可以通过实现多个接口来实现。
Java接口
和Java抽象类
代表的就是抽象类型,就是我们需要提出的抽象层的具体表现。OOP面向对象
的编程,如果要提高程序的复用率,增加程序的可维护性,可扩展性,就必须是面向接口的编程,面向抽象的编程,正确地使用接口、抽象类这些有用的抽象类型做为java结构层次上的顶层。
语法:
interface 接口名{
代码…
}
abstract class 类名{
代码…
} //2单词变成了一个
5.1.2 特点
- 接口中的方法都是公共的抽象方法,变量都是公共的常量,没有构造方法(接口中的资源都是公共的)。
- 通过interface关键字创建接口
- 通过implements让子类来实现 (抽象类是extends继承)
- 可以理解成,接口是一个特殊的抽象类
- 接口突破了java的单继承的局限性
- 接口和类之间可以多实现,接口和接口之间可以多继承(“,”隔开),还能是继承的同时多实现,但是不能是同时多继承多实现 或 改变顺序先实现在继承。(总结:同种类型之间是继承,不同是实现关系。)
- 接口是对外暴露的规则,是一套开发规范
- 接口提高了程序的功能扩展,降低了耦合性(耦合性就是程序的关联性,关联性越低越好)
注意:
- 接口继承接口怎么调用? 因为接口是一个特殊的抽象类不能实例化,所以我们只能在创建一个普通的子类来实现这个接口,在实例化这个实现类。
- 要注意重写接口里的抽象方法的权限,因为是默认为public 所以子类的权限至少等于为public(2同2小一大原则)。
- 接口的default新特性: jdk从1.8开始接口中可以有普通方法(静态方法和默认方法),用static或者默认的default修饰(default不省略) 。
- 接口存在普通方法的意义:如果抽象类有很多抽象方法那么子类都要重写太麻烦,用普通方法想重写就重写方便。
- 子类继承抽象父类,重写父类方法的重写,叫方法的重写。
子类实现接口,重写接口方法的重写,叫方法的实现。当然你都叫做重写也没事,只是不精确。
5.1.3 入门案例1
package cn.tedu.interfacedemo;
//接口的入门案例
public class Test4_Inter {
public static void main(String[] args) {
//TODO 创建多态对象测试
//5,接口可以创建对象吗??? --- 不能!!!和抽象类一样,不能实例化
// Inter i = new Inter();
Inter i = new InterImpl();
i.delete();
i.save();
}
}
//创建接口
//1,通过interface关键字定义接口
interface Inter{
//2,接口里可以有普通方法吗???---不可以!!!接口里都是抽象方法
abstract public void delete();
abstract public void save();
}
//3,实现类,想要使用接口里的功能,需要 实现 接口,用implements关键字
//4, 实现类实现了接口以后,可以是一个普通实现类,就要求重写所有抽象方法
class InterImpl implements Inter{
//重写所有抽象方法(注意抽象方法有分号,没有方法体,连{}也没有)
//重写的方法把它变为普通的方法,把abstract去掉,写方法体,注意分号。
@Override
public void delete() {
System.out.println("delete()...");
}
@Override
public void save() {
System.out.println("save()...");
}
}
//实现类一般都是在接口上加 Impl
//6, 实现类实现了接口以后,如果没有全部重写抽象方法,就是个抽象子类
abstract class InterImpl2 implements Inter{
}
5.1.4 入门案例2
package com.atguigu.java1;
/*
* 接口的使用
* 1.接口使用interface来定义
* 2.Java中,接口和类是并列的两个结构
* 3.如何定义接口:定义接口中的成员
*
* 3.1 JDK7及以前:只能定义全局常量和抽象方法
* >全局常量:public static final的.但是书写时,可以省略不写
* >抽象方法:public abstract的
*
* 3.2 JDK8:除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法(略)
*
* 4. 接口中不能定义构造器的!意味着接口不可以实例化
*
* 5. Java开发中,接口通过让类去实现(implements)的方式来使用.
* 如果实现类覆盖了接口中的所有抽象方法,则此实现类就可以实例化
* 如果实现类没有覆盖接口中所有的抽象方法,则此实现类仍为一个抽象类
*
* 6. Java类可以实现多个接口 --->弥补了Java单继承性的局限性
* 格式:class AA extends BB implements CC,DD,EE
*
* 7. 接口与接口之间可以继承,而且可以多继承
*
* *******************************
* 8. 接口的具体使用,体现多态性
* 9. 接口,实际上可以看做是一种规范
*
* 面试题:抽象类与接口有哪些异同?
*
*/
public class InterfaceTest {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);
System.out.println(Flyable.MIN_SPEED);
// Flyable.MIN_SPEED = 2;
Plane plane = new Plane();
plane.fly();
}
}
interface Flyable{
//全局常量
public static final int MAX_SPEED = 7900;//第一宇宙速度
int MIN_SPEED = 1;//省略了public static final
//抽象方法
public abstract void fly();
//省略了public abstract
void stop();
//Interfaces cannot have constructors
// public Flyable(){
//
// }
}
interface Attackable{
void attack();
}
class Plane implements Flyable{
@Override
public void fly() {
System.out.println("通过引擎起飞");
}
@Override
public void stop() {
System.out.println("驾驶员减速停止");
}
}
abstract class Kite implements Flyable{
@Override
public void fly() {
}
}
class Bullet extends Object implements Flyable,Attackable,CC{
@Override
public void attack() {
// TODO Auto-generated method stub
}
@Override
public void fly() {
// TODO Auto-generated method stub
}
@Override
public void stop() {
// TODO Auto-generated method stub
}
@Override
public void method1() {
// TODO Auto-generated method stub
}
@Override
public void method2() {
// TODO Auto-generated method stub
}
}
//************************************
interface AA{
void method1();
}
interface BB{
void method2();
}
interface CC extends AA,BB{
}
5.1.5 演示接口是一种规范和匿名实现类对象
接口的匿名实现类和抽象类的匿名子类用发相同。
package com.atguigu.java1;
/*
* 接口的使用
* 1.接口使用上也满足多态性
* 2.接口,实际上就是定义了一种规范
* 3.开发中,体会面向接口编程!
*
*/
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
//1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
//2. 创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
//3. 创建了接口的匿名实现类的非匿名对象
USB phone = new USB(){
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机结束工作");
}
};
com.transferData(phone);
//4. 创建了接口的匿名实现类的匿名对象
com.transferData(new USB(){
@Override
public void start() {
System.out.println("mp3开始工作");
}
@Override
public void stop() {
System.out.println("mp3结束工作");
}
});
}
}
class Computer{
public void transferData(USB usb){//USB usb = new Flash();
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
//常量:定义了长、宽、最大最小的传输速度等
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
5.2 接口的用法
5.2.1 构造方法
接口里是没有构造方法的,在创建实现类的对象时默认的super(),是调用的默认Object的无参构造。
public interface Inter2 {
//接口里可以有构造方法吗???--没有!!,抽象类有
// public Inter2() {}
}
5.2.2 成员变量
- 接口里面的资源都是公共的,用public修饰。
- 接口的变量默认都是静态常量。
- 总结:接口中的变量都是
公共的 静态 常量
。
例:
public static final int age;
等价于:
int age; //接口中会默认自动拼接 public static final
5.2.3 接口的成员方法
- 接口里面的资源都是公共的,用public修饰。
- 接口里面的方法,默认都是抽象的,即使不写也会默认拼接,只不过不会显示。
- 总结:接口中的方法都是
公共的 抽象 方法
,即使不写也会默认拼接,只不过不会显示。
例:
public abstract void save();
等价于:
void save(); //会默认拼接 public abstract
5.3 接口的复杂用法
Java中单继承的局限性通过接口可以解决:接口可以多继承也可以多实现,甚至可以继承的同时多实现。
package cn.tedu.interfacedemo;
//这个类用来测试接口的复杂用法:多继承多实现
public class Test4_ComInter {
public static void main(String[] args) {
Interface1 in = new Interface1Impl();
in.save();
in.update();
}
}
//创建接口1
interface Interface1{
void save();
void update();
}
//创建接口2
interface Interface2{
void get();
void delete();
}
//1、打破了java单继承的局限性,因为接口之间可以多继承,多个接口之间逗号隔开
interface Interface3 extends Interface1,Interface2{
void add();
}
//3、接口还可以多实现吗??---可以多实现,只不过接口之间逗号隔开
class ManyImpl implements Interface1,Interface2{
public void save() {}
public void update() {}
public void get() {}
public void delete() {}
}
//4、接口可以继承的同时,多实现?? --
class MoreImple extends ManyImpl implements Interface1,Interface2 {
}//注意:只能是先继承在多实现。顺序不能变,并且不能多继承多实现。
//2、创建实现类,使用3号接口的功能,需要重写几个方法呢??---同时重写1号和2号和3号接口里的所有功能
class Interface3Impl implements Interface3{
@Override
public void save() { }
@Override
public void update() { }
@Override
public void get() { }
@Override
public void delete() { }
@Override
public void add() { }
}
//TODO 创建实现类
class Interface1Impl implements Interface1{
@Override
public void save() {
System.out.println("save()...");
}
@Override
public void update() {
System.out.println("update()...");
}
}
5.4 接口jdk1.8/1.9的新特性
在JDK8.0 之前,接口中只允许出现:
-
公共的静态的常量:其中
public static final
可以省略 -
公共的抽象的方法:其中
public abstract
可以省略
理解:接口是从多个相似类中抽象出来的规范,不需要提供具体实现
在JDK8.0 时,接口中允许声明默认方法
和静态方法
:
-
公共的默认的方法:其中public 可以省略,建议保留,但是default不能省略
-
公共的静态的方法:其中public 可以省略,建议保留,但是static不能省略
变量和以前一样。
在JDK9.0 时,接口又增加了:
- 私有方法
变量和以前一样。
除此之外,接口中没有构造器,没有初始化块,因为接口中没有成员变量需要动态初始化。
5.4.1 测试(子类中调用父接口中的方法)
继承结构中的接口和类:
CompareA:父接口
package com.atguigu08._interface.jdk8;
/**
* ClassName: CompareA
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 9:33
* @Version 1.0
*/
public interface CompareA {
//属性:声明为public static final (jdk8之前,jdk8,jdk9都没有发生变化)
//方法:jdk8之前:只能声明抽象方法
//方法:jdk8中:静态方法
public static void method1(){
System.out.println("CompareA:北京");
}
//方法:jdk8中:默认方法
public default void method2(){
System.out.println("CompareA:上海");
}
public default void method3(){
System.out.println("CompareA:广州");
}
public default void method4(){
System.out.println("CompareA:深圳");
}
//jdk9新特性:定义私有方法
//作用:私有方法实现类不能继承,就是接口自己用的。
// 接口中的很多默认方法中有一些共性代码,把这些
// 共性代码抽取到一个方法中,这个方法也不对外暴漏了
// 定义成私有的。
private void method5(){
System.out.println("我是接口中定义的私有方法");
}
}
CompareB:父接口
package com.atguigu08._interface.jdk8;
/**
* ClassName: CompareB
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 9:39
* @Version 1.0
*/
public interface CompareB {
public default void method3(){
System.out.println("CompareB:广州");
}
}
SuperClass:父类
package com.atguigu08._interface.jdk8;
/**
* ClassName: SuperClass
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 9:43
* @Version 1.0
*/
public class SuperClass {
public void method4(){
System.out.println("SuperClass:深圳");
}
}
SubClass:子类
package com.atguigu08._interface.jdk8;
import org.junit.Test;
/**
* ClassName: SubClass
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 9:34
* @Version 1.0
*/
public class SubClass extends SuperClass implements CompareA,CompareB{
@Override
public void method2() {
System.out.println("SubClass:上海");
}
@Override
public void method3(){
System.out.println("SubClass:广州");
}
@Override
public void method4(){
System.out.println("SubClass:深圳");
}
public void method(){
//知识点5:如何在子类(或实现类)中调用父类或接口中被重写的方法
// (子类中重写了父类的方法,这里调用的是父类或父接口中的方法)
method4();//调用自己类中的方法
super.method4(); //调用父类中的
method3();//调用自己类中的方法
//调用本类所实现接口中的方法,因为方法是非静态的所以不能使用类名调用。
CompareA.super.method3(); //调用接口CompareA中的默认方法
CompareB.super.method3(); //调用接口CompareB中的默认方法
}
}
main方法测试类:
SubClassTest:
package com.atguigu08._interface.jdk8;
/**
* ClassName: SubClassTest
* Description:
*
* @Author 尚硅谷-宋红康
* @Create 9:34
* @Version 1.0
*/
public class SubClassTest {
public static void main(String[] args) {
//知识点1:接口中声明的静态方法只能被接口来调用,不能使用其实现类进行调用。
CompareA.method1(); //对 CompareA:北京
// SubClass.method1(); 错
//知识点2:接口中声明的默认方法可以被实现类继承,实现类在没有重写此方法的情况下,默认调用接口中声明的
//默认方法。如果实现类重写了此方法,则调用的是自己重写的方法。
SubClass s1 = new SubClass();
s1.method2(); //SubClass:上海
//知识点3:类实现了两个接口,而两个接口中定义了同名同参数的默认方法。则实现类在没有重写此两个接口
//默认方法的情况下,会报错。 ---->接口冲突
//要求:此时实现类必须要重写接口中定义的同名同参数的方法。
s1.method3(); //SubClass:广州
//知识点4:子类(或实现类)继承了父类并实现了接口。父类和接口中声明了同名同参数的方法。(其中,接口中的方法
//是默认方法)。默认情况下,子类(或实现类)在没有重写此方法的情况下,调用的是父类中的方法。--->类优先原则
// 重写之后调用的肯定重写后子类的方法。
s1.method4(); //SubClass:深圳
//知识点5:详情查看SubClass
s1.method();
}
}
5.5 接口的应用1:代理模式
package com.atguigu.java1;
/*
* 接口的应用:代理模式
* 以下代码为:静态代理模式
*
*/
public class NetWorkTest {
public static void main(String[] args) {
Server server = new Server();
// server.browse();
ProxyServer proxyServer = new ProxyServer(server);
proxyServer.browse();
}
}
interface NetWork{
public void browse();
}
//被代理类
class Server implements NetWork{
@Override
public void browse() {
System.out.println("真实的服务器访问网络");
}
}
//代理类
class ProxyServer implements NetWork{
private NetWork work;
public ProxyServer(NetWork work){
this.work = work;
}
public void check(){
System.out.println("联网之前的检查工作");
}
@Override
public void browse() {
check();
work.browse();
}
}
5.6 接口的应用2:工厂模式
工厂模式:实现了创建者与调用者的分离,即将创建对象的具体过程屏蔽隔离起来,达到提高灵活性的目的。
工厂:为了创建对象用的。
工厂模式的分类:
- 简单工厂模式:用来生产同一等级结构中的任意产品。(对于增加新的产品,需要修改已有代码)
- 工厂方法模式:用来生产同一等级结构中的固定产品。(支持增加任意产品)
- 抽象工厂模式:用来生产不同产品族的全部产品。(对于增加新的产品,无能为力;支持增加产品族)
5.7 初识设计模式
Java中有23 种设计模式,本质是面向对象设计原则的实际运用,是对类的封装性、继承性和多态性,以及类的关联关系和组合关系的充分理解。
当然,软件设计模式只是一个引导,在实际的软件开发中,必须根据具体的需求来选择。
1、对于简单的程序,可能写一个简单的算法要比引入某种设计模式更加容易。
2、但是对于大型项目开发或者框架设计,用设计模式来组织代码显然更好。
5.7.1 单例设计模式概念
单例模式可以说是大多数开发人员在实际中使用最多的,常见的Spring默认创建的bean就是单例模式的。
单例模式有很多好处,比如可节约系统内存空间,控制资源的使用。
其中单例模式最重要的是确保对象只有一个。
简单来说,保证一个类在内存中的对象就一个。
RunTime就是典型的单例设计,我们通过对RunTime类的分析,一窥究竟。
5.7.2 源码剖析
/**
* Every Java application has a single instance of class
* <code>Runtime</code> that allows the application to interface with
* the environment in which the application is running. The current
* runtime can be obtained from the <code>getRuntime</code> method.
* <p>
* An application cannot create its own instance of this class.
*
* @author unascribed
* @see java.lang.Runtime#getRuntime()
* @since JDK1.0
*/
RunTime.java
package java.lang;
public class Runtime {
//1、创建静态的全局唯一的对象
private static Runtime currentRuntime = new Runtime();
//2、私有构造方法,
/** Don't let anyone else instantiate this class */
private Runtime() {}
//3、通过自定义的静态方法获取实例
public static Runtime getRuntime() {
return currentRuntime;
}
}
5.7.3 饿汉式
目的:控制外界创建对象的个数只能创建1个对象。
开发步骤:
1、私有化构造方法
2、在类的内部创建好对象
3、对外界提供一个公共的get(),返回一个已经准备好的对象。因为现在构造方法是私有的所以外界不能创建对象调用方法,只能声明为静态的方法通过类名进行调用。
4、没有线程安全问题
package cn.tedu.single;
//测试单例设计模式
public class Test8_Single {
public static void main(String[] args) {
Single s = Single.get();
Single s1 = Single.get();
//get()多少次,内存中使用的都是同一个对象
System.out.println(s);//cn.tedu.single.Single@15db9742
System.out.println(s1);//cn.tedu.single.Single@15db9742
}
}
class Single{
// 1、私有化构造方法,不让外界直接new
private Single() {}
// 2、在类的内部,创建好对象
//static :静态只能调用静态,因为方法是静态的调用这对象属性也只能是静态的。
static private Single s = new Single();
// 3、对外界提供一个公共的get(),返回一个已经准备好的对象
//static是为了外界不通过对象访问而是通过类名直接方法
static public Single get(){
//注意:静态只能调用静态
return s;
}
}
5.7.4 懒汉式
懒汉式有线程安全问题—》在day05–java高级编程—》同步锁中解决。
注意:面试时要求写一个单例设计模式:要么写饿汉式,要么写线程安全的懒汉式,不要写线程不安全的懒汉式。
class Single{
// 1、私有化构造方法,不让外界直接new
private Single() {}
// 2、在类的内部,创建好对象
//static :静态只能调用静态
static private Single s = null;
// 3、对外界提供一个公共的get(),返回一个已经准备好的对象
//static是为了外界不通过对象访问而是通过类名直接方法
static public Single get(){
//注意:静态只能调用静态
if(s==null){
/*会有线程安全问题:假如现在多个线程,每个线程调用run方法,各个run方法有调用这个get方法。
当第一个线程进入到这个get方法后,第一次进入对象为空,此时可能发生阻塞第二个线程进来判断为空
也需要创建对象。这样就创建了2个对象不合理。s相当于共享数据。
*/
//如何解决?查看在day05--java高级编程---》同步锁中解决。
s = new Single();
}
return s;//是null说明还没有创建对象,不是null说明创建好了对象直接返回,可以保证只创建一次对象。
}
}
5.7.5 饿汉式和懒汉式的区别
饿汉式:
- 坏处:对象加载时间过长。
- 好处:饿汉式是线程安全的
懒汉式:
- 好处:延迟对象的创建。
- 目前的写法坏处:线程不安全。—>到多线程内容时,再修改( 加上synchronized锁 )
5.7.6 单例设计模式-应用场景
5.8 拓展
5.8.1 abstract注意事项
抽象方法要求子类继承后必须重写。那么,abstract关键字不可以和哪些关键字一起使用呢?以下关键字,在抽象类中,用是可以用的,只是没有意义了。
1、private:被私有化后,子类无法重写,与abstract相违背。
2、static:静态的,优先于对象存在。抽象方法需要子类重写,而静态的方法是无法被重写的,因此二者是矛盾的。
3、final:被final修饰后,无法重写,与abstract相违背。
5.8.2 接口和抽象类的区别
1、抽象类和接口都不能直接实例化,如果要实例化,抽象类变量必须指向实现所有抽象方法的子类对象,接口变量必须指向实现所有接口方法的类对象。
2、抽象类要被子类继承,接口要被类实现。
3、接口只能做方法申明,抽象类中可以做方法申明,也可以做方法实现
4、接口里定义的变量只能是公共的静态的常量,抽象类中的变量是普通变量。
5、抽象类里的抽象方法必须全部被子类所实现,如果子类不能全部实现父类抽象方法,那么该子类只能是抽象类。同样,一个实现接口的时候,如不能全部实现接口方法,那么该类也只能为抽象类。
6、抽象方法只能申明,不能实现,接口是设计的结果 ,抽象类是重构的结果
7、抽象类里可以没有抽象方法,如果要扩展抽象类的新方法,子类将很容易的就能得到这些新方法。
8、如果一个类里有抽象方法,那么这个类只能是抽象类
9、抽象方法要被实现,所以不能是静态的,也不能是私有的。
10、接口可继承接口,并可多继承接口,但类只能单根继承。
5.8.3 了解软件设计的开闭原则OCP
开放功能扩展,关闭源码修改等。
开闭原则的英文全称是Open Close Principle,缩写是OCP,它是Java世界里最基础的设计原则,它指导我们如何建立一个稳定的、灵活的系统。
开闭原则的定义是:软件中的对象(类、模块、函数等)应该对于扩展是开放的,但是对于修改是封闭的。
开闭原则,是一种设计模式,随着面向对象程序设计的思想,应运而生。
开,指的是可以在源代码的基础上进行扩展,比如继承,接口,抽象类等。在JAVA中,之所以用继承,是在可以直接调用类库的前提下,对其功能进行扩展。不需要应用者去了解封装类的内部逻辑就可以做开发。
闭:指不允许对原有的代码进行修改。以免影响其他现有功能,造成功能瘫痪。
5.8.4 打桩和DeBug
当程序的运行结果和你说预期的结果不同时,如何调试代码?
(1)打桩: System.out.println(数据);
(2)Debug调试工具:略,详情查看博客杂谈分栏下。
5.8.5 Math类中提供了三个取正有关的方法:ceil,floor,round
写法:
System.out.println(Math.ceil(11.5));//12.0
(1)ceil的英文意义是天花板,该方法表示向上取整
(2)floor的英文意义是地板,该方法就表示向下取整
(3)round方法:它表示“四舍五入”(即: +0.5向下取整)