文章目录
Java类及类对象
类和对象
类(Class)和对象(Object)是面向对象的核心概念
- 类是对一类事物的描述,是抽象的、概念上的定义
- 对象是实际存在的该类事务的每个个体,因此也称为实例(instance).
// 下面我们构造一个涵盖大部分知识点的类
class Person {
// 属性,或成员变量
String name;
boolean isMarried;
// 构造器
public Person() {}
public Persion(String n,boolean im){
name = n;isMarried = im;
}
// 方法,或函数
public void walk(){
System.out.println("人走路...")
}
public String display() {
return "名字是: " + name + ",Married:" + isMarried;
}
// 代码块
{
name = "HanMeiMei";
age = 17;
isMarried = true;
}
// 内部类
class pet {
String name;
float weight;
}
}
// 测试类
public class PersonTest {
// 这个main方法是作为程序的入口
public static void main(String[] args) {
// 创建Person类的对象
Person p1 = new Person();
// 调用对象的结构: 属性、方法
// 调用属性: "对象.属性"
p1.name = 'TOM';
p1.isMale = true;
System.out.println(p1.name);
// 调用方法: "对象.方法"
p1.eat();
p1.sleep();
p1.talk("Chinese");
// ************************
Person p2 = new Person();
// 这里注意,每次new一个新对象的时候,p1 和 p2是不同的,他们在堆空间是两个不同的实体,虽然对
// p1这个实体的name属性赋值了,但是p2作为一个全新的实体
// 他的name就是一维数组的默认初始化值null
// 这个时候需要复习一下前面的东西,关于对象的初始值,参照之前的对string类型的案例,引用类型
// 的默认值
System.out.println(p2.name);//Tom? null? 报错?
System.out.println(p2.isMale); // false
// 基本数据类型的初始值可以畸形的记住为0,引用数据类型的初始值可以畸形的记为null
// 这里是将p1变量保存的对象地址值赋给P3,导致P1和P3指向堆空间的同一个对象实体
Person p3 = p1;
System.out.println(p3.name);// Tom
p3.age = 10;
System.out.println(p1.age);// 10
}
}
class Person{
// 属性
String name;
int age = 1;
boolean isMale;
// 方法
public void eat(){
System.out.println("人可以吃饭");
}
public void sleep(){
System.out.println("人可以睡觉");
}
public void talk(String language){
System.out.println("人可以说话,使用的是: " + language);
}
}
内存解析
类中属性的使用
属性(成员变量) VS 局部变量
- 相同点
- 定义变量的格式,数据类型 变量名 = 变量值
- 先声明后使用
- 都有对应的作用域
- 不同点:
-
在类中声明位置的不同
属性: 直接定义在类的一对{}内
局部变量: 什么在方法内、方法形参、代码块内、构造器形参、构造器内部的变量
-
权限修饰符的不同
属性: 可以在声明属性时,指明其权限,使用权限修饰符。
常用的权限修饰符: private、public、缺省、protected
目前不加任何的情况下,默认是缺省
局部变量是不可以使用权限修饰符
-
默认初始化值得情况
属性: 类得属性,根据其类型,都有默认初始化值
整型(byte\short\int\long),0
浮点型(float\double),0.0
字符型(char) , 0 (或者’\u0000’)
布尔型(boolean),false
引用数据类型(类、数组、接口), null
局部变量:没有默认初始化值
意味着局部变量需要显式赋值特别地,形参在调用时我们赋值即可。
-
在内存中加载的位置
属性: 加载到堆空间中(非static)
局部变量: 加载都栈空间中。
-
public class UserTest {
}
class User{
String name;
int age;
boolean isMale;
// 下方括号内部的参数叫做形参,是局部变量的一种
public void talk(String language){
System.out.println("我们使用" + language + "进行交流");
}
public void eat(){
String food = "烙饼"; // 局部变量
System.out.println("北方人喜欢吃 " + food);
}
}
类中方法的使用
方法: 描述类应该具有的功能 比如Math类中的sqrt()\random()… Scanner类,nextXxxx() … Arrays类,sort()\binarySearch() \ toString() \
下面我们就自己写一个方法:
/**
* 举例:
* 下方的Customer类中的
* public void eat() -- void没有返回值的意思
* public void sleep(int hour)
* public String getName() -- String 返回值类型为String
* public String getNation(String nation)
*
* 方法的声明:权限修饰符 返回值类型 方法名(形参列表) {
* 方法体
* }
* static final abstract 来修饰的方法,后面再重点说
*
* 最后一个老生常谈的问题: 方法名见名知意
*
*/
public class CustomerTest {
}
class Customer{
// 属性
String name;
int age;
boolean isMale;
// 方法
public void eat() {
System.out.println("客户吃饭");
}
public void sleep(int hour){
System.out.println("休息了" + hour + "个小时");
}
public String getName() {
return name;
}
public String getNation(String nation){
String info = "我的国籍是" + nation;
return info;
}
}
关于权限修饰符:
java规定的4中权限修饰符: private、public、缺省、protected比如:
public class Test {
// 新建一个类型为A的对象
A test1 = new A();
// 通过test1这个对象来调用public方法
test1.a();
// 通过test1这个对象来调用private方法(错误)
test1.b();
}
class A {
// 这是用public修饰的a方法
public void a(){
System.out.println("执行a方法");
}
private void b() {
System.out.println("执行b方法");
}
}
关于类方法的返回值类型:
有返回值 vs 没返回值
有返回值: 如果方法有返回值,则必须在方法声明时,指定返回值的类型,同时,方法中需要使用return关键字来返回指定类型的变量或常量。
没有返回值: 则方法声明时使用void来表示。通常void里面不需要使用return了,但是,如果你发病了,用了也只能"return;"来表示结束次方法的意思。
最后一个注意点: 方法中不能定义方法:
class A {
public void a(){
// 注意一下,不能这么搞
public void swim() {
}
}
}
课堂练习
- 编写教师类和学生类,并通过测试类创建对象进行测试:
student类:
属性: name(String)、age(int)、major(String)、interests(String)
方法: say(返回学生的个人信息)
Teacher类:
属性: name(String)、age(int)、teachAge(int)、course(String)
方法: say(返回学生的个人信息)
- 创建一个Person类,其定义如下:
创建一个Person类的对象,设置该对象的name、age、sex属性,调用study方法,输出字符串“studying”,调用showAge()方法显示age值,调用addAge() 方法给对象的age属性值增加2岁。
创建第二个对象,执行上述操作,体会同一个类不同对象之间的关系。
public class Person {
// 属性
String name;
int age;
int sex;
// 方法
public void study(){
System.out.println("studying");
}
public void showAge() {
System.out.println("age:" + age);
}
public int addAge(int i){
age += i;
return age;
}
}
匿名对象
public class InstanceTest {
public static void main(String[] args) {
// 有名对象
Phone p = new Phone();
// 匿名对象
new Phone().sendEmail();//一次性。
}
}
class Phone{
double price;// 价格
public void sendEmail() {
System.out.println("发送邮件");
}
public void playGame() {
System.out.println("玩游戏");
}
}
再谈方法
方法的重载
重载的概念:
在同一个类中,运行存在一个以上的同名方法,只要它们的参数个数或者参数类型不同即可
重载的特点:
与返回值类型无关,只看参数列表,且参数列表必须不同。(参数个数或参数类型)。调用时,根据方法参数列表的不同来区别
重载示例:
// 返回两个整数的和
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;}
这个地方需要注意的是,python中是不存在这种形式的重载的,我本人是第一时间没有反应过来还有这么神奇的用法这里着重的记忆了一下
// 举例 Arrays类中重载的sort() /binarySearch()
public class OverLoadTest {
public void getSum(int i,int j){
System.out.println(i + j);
}
public void getSum(double i,double j) {
System.out.println(i+j);
}
// 问题来了 注意下方的表达方式
public void getSum(String s,int i){
}
public void getSum(int i,String s){
// 注意上面这个方法和下面这个方法的差别,这个参数只是换了个位置,他算不算重载? 注意嗷! 算,
// 掉方法的时候你传参都有顺序的要求的,肯定算啦。
}
// 下面这个算不算方法重载? 不算嗷猪批。同一个类,同一个方法名是满足了,但是你还给我同参数
// 那就肯定不是重载了,会编译报错。
// public int getSum(int i,int j) {
// return 0;
// }
public static void main(String[] args){
OverLoadTest test = new OverLoadTest();
test.getSum(1,2);
}
}
总结: 判断是否是重载,跟方法的权限修饰符、返回值类型、形参变量名、方法体都没有关系!
以下与void show(int a,char b, double c){} 构成重载的有;
void show(int x, char y,double z){} // 不是 名字一致,参数一致
int show (int a,double c,char b){} // 是
void show (int a,double c,char b){} // 是
boolean show(int c,char b){} // 是
void show(double c){} // 是
double show(int x,char y,double z){} // 不是
void shows(){double c} // 不是
可变形参的方法
联想了一下,应该和python的*arg 和 ** kwargs这种差不多吧。。。
可变个数形参是jdk5.0新增的内容。如下
public class MethodArgsTest {
public static void main(String [] a){
MethodArgsTest test = new MethodArgsTest();
test.show(12);
test.show("hello");
test.show("hello","hello","java");
}
public void show (int i){
}
public void show(String s){
}
public void show(String ... strs){
// 这里的strs就是可变个数的形参。
}
}
当调用可变个数形参的方法时,传入的参数可以是: 0个、1个、2个,。。。
问题来了 怎么去调用呢?
public void show(String ... strs) {
System.out.println(strs);
System.out.println(strs.length);
System.out.println(strs[0]);
}
// 这也去自己试验一下
注意上面这个方法和
//可变个数形参的方法和该方法之间不构成重载
public void show(String[] args){
}
可变个数形参只能存在一个,且只能声明到最后,编译原理相关,死记懒得解释。
方法参数的值传递机制
这个怎么说哩python里面会专门来区分值传递和引用传递的。我在博客里面也专门说过python这两者的区别,这里也稍微过一个java的。
java里面方法的参数传递只有一种: 值传递。即将实际参数值的复制品传入方法内,而参数不受影响。
形参是基本数据类型: 将实参基本数据类型变量的"数据值"传递给形参
形参是引用数据类型: 将实参引用数据类型变量的"地址值"传递给形参
public class ValueTransferTest {
public static void main(String[] args) {
System.out.println("************基本数据类型************")
int m = 10;
int n = m;
System.out.println("m = " + m + ",n = " + n);
n = 20;
System.out.println("m = " + m + ",n = " + n);
System.out.println("************引用数据类型************");
Order o1 = new Order();
o1.orderId = 1001;
Order o2 = o1;// 赋值操作做了之后,o1,o2的地址值相同,都指向了堆空间中同一个对象实体;
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " + o2.orderId);
o2.orderId = 1002;
System.out.println("o1.orderId = " + o1.orderId + ",o2.orderId = " + o2.orderId);
}
}
class Order{
int orderId;
}
方法形参的传递机制: 值传递
public class ValueTransferTest1 {
public static void main(String[] args) {
int m = 10;
int n = 20;
ValueTransferTest1 test = new ValueTransferTest1();
test.swap(m,n);
System.out.println("调用swap方法结束后,a的值是" + a + ";b的值是" + b);
}
public void swap(int m,int n){
int tem = m;
m = n;
n = temp;
System.out.println("swap方法里,a的值是" + a + ";b的值是" + b);
}
}
这个例子,不管是当初在学c还是学python的时候都学过,其实当时的学到这里的时候有个最大的问题就是,我根本没把这个案例和需要理解的知识点联系起来,就只是单纯的把这种情况记忆下来了,就记成了,不管调用的方法做了什么操作,外面这两个变量的值都不变,能应付考试,但是没有实际理解。就因为不愿意深入思考,所以当碰到下面这种情况,就懵了。
public class ValueTransferTest2 {
public static void mian(String[] args){
Data data = new Data();
data.m = 10;
data.n = 20;
System.out.println("m = " + data.m + ",n = " + data.n); // m = 10,n = 20
ValueTransferTest2 test = new ValueTransferTest2();
test.swap(data); // 这里要注意的是,其实呢,本质上他传递的还是值,只不过,这里传递的是地址值
// 也就是说把对象的地址值传递过去了,但是同一地址值,结果指向同一个对象。
System.out.println("m = " + data.m + ",n = " + data.n);//m = 20,n = 10
}
public void swap(Data data){
int temp = data.m;
data.m = data.n;
data.n = temp;
}
}
class Data {
int m;
int n;
}
习题:
public class TransferTest3{
public static void mian(String args[]){
TransferTest3 test = new TransferTest3();
test.first();
}
public void first() {
int i = 5;
Value v = new Value();
v.i = 25;
second(v,i);
System.out.println(v,i);
}
public void second(Value v,int i){
i = 0;
v.i = 20;
Value val = new Value();
v = val;
System.out.println(v.i + " " + i);
}
}
class Value{
int i = 15;
}
网红题:
public class Test{
public static void mian(String[] args){
int a = 10;
int b = 10;
method(a,b);// method调用之后,打印出a = 100,b = 200
System.out.println("a = " + a);
System.out.println("b = " + b);
}
}
// 单独拿出来说的两个
int[] arr = new int[]{1,2,3};
System.out.println(arr);// 这个会输出地址值
char[] arr1 = new char[]{"1","2","3"};
System.out.println(arr1)// 这个会输出123
递归方法
- 递归方法,一个方法体内调用它自身
- 方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制,递归一定要向已知方向递归,否则这种递归就变成了无穷递归,
public class RecursionTest {
public static void mian(String[] args){
// 计算1-100之间所有自然数的和
}
public int getSum(int n){
if(n == 1){
return 1;
}else{
return n + getSum(n-1);
}
}
}
这个我也不知道该怎么说,就会了就是会了:
入门题: 汉诺塔
public class HanioTest {
public static void main(String[] args) {
int n = 4;
char a = 'A',b = 'B',c = 'C';
hanio(n,a,b,c);
}
/**
*
* @param n 一共需要移动的盘子
* @param a 盘子移动的起始柱子
* @param b 借助的柱子
* @param c 盘子需要移动到的目标柱子
*/
public static void hanio(int n, char a,char b,char c){
if(n == 1){
move(n,a,c);
}else{
//三步曲,注意观察,a,b,c三个的位置变化
//1.把 n-1 个盘子看成一个整体,借助 C 从 A 移动到 B
hanio(n-1,a,c,b);
//2.把第 n 个盘子从 A 移动到 C
move(n,a,c);
//3.再把 n-1 盘子整体,借助 A 从 B 移动到 C
hanio(n-1,b,a,c);
}
}
public static void move(int n , char a, char b){
System.out.println("把第"+ n +"个盘子从"+ a +"移到"+ b);
}
}
封装和隐藏
public class AnimalTest {
public static void main(String[] args) {
Animal a = new Animal();
a.name = "大黄";
a.age = 1;
a.setLegs(6);
a.show();
}
}
class Animal{
String name;
int age;
int legs;
public void setLegs(int l){
if ( l >= 0 && l % 2 == 0){
legs = l;
} else{
legs = 0;
}
}
public int getLegs(){
return legs;
}
public void eat(){
System.out.println("动物进食");
}
public void show(){
System.out.println("name = " + name + ",age = " + age + ",legs = " + legs);
}
}
注意这里 我们是通过了一个方法去专门去设置这个动物的腿的数量,只有调这个方法去设置动物腿数的时候太不合理,因为setLegs方法做了个判断,但是保不齐有的傻逼他之后再去使用a.legs = -6;所以我们引入了一个关键词private。这样,这个傻逼就再也没办法用a.legs去设置一些奇奇怪怪的数量了。需要学习的是,加了这个权限修饰符之后,这个类中是可以使用的,但是出了这个类,那不好意思,你只能通过我给你提供的接口,也就是setLegs去给legs设置值了,用getLegs方法去获取值。学习过程中,感觉不到这个private的作用,等实际开发中就知道,基本上都是这种。
封装性的体现: — 不等同于封装性
我们将类的属性私有化(private),同时提供公共的(public)方法来获取(getXxxx)和设置(setXxxx)此属性的值
java规定的4种权限(从小到大): private、缺省、protected、public
修饰类: 只能使用缺省、private
修饰符 | 类内部 | 同一个包 | 不同包的子类 | 同一个工程 |
---|---|---|---|---|
private | 1 | |||
缺省 | 1 | 1 | ||
protected | 1 | 1 | 1 | |
public | 1 | 1 | 1 | 1 |
类的构造器
参考python的__init__
构造器的作用
实际使用中我们通过 Test a = new Test();的语句来构建一个对象。实际上这里我们使用的是new + 构造器来创建对象。说明实际就算我们没有定义类的构造器,系统会默认给我们提供一个空参的构造器。那如何去定义一个构造器呢,但是我们要注意一点,一旦我们显示定义了类的构造器之后,系统就不在提供默认的构造器
定义构造器的方法:
权限修饰符 类名(形参列表){}
class Person{
String name;
int age;
// 构造器
public Person(){
System.out.println("Person() ...")
}
// 构造器重载
public Person(String n){
name = n;
}
}
JavaBean
JavaBean是一种java语言写成的可重用组件,这个比较重要,后期学spring的时候可以看到。所谓的JavaBean是符合如下标准的类:
类是公共的
有一个无参的公共的构造器
有属性,且有对应的get、set方法。这点很重要。
UML类图
就记住一点: + 表示public - 表示private #表示protected
This的使用
public class PersionTest {
public static void main(String[] args){
Person p1 = new Person();
p1.setName("熊");
System.out.println(p1.getName());
}
}
class Person{
private String name;
private int age;
public void setName(String n){
name = n;
}
// 注意这里如果我改一种写法
// public void setName(String name){
// name = name;
// }
// 试验之后,调用getset方法之后,输出是0,为啥? 因为这时候set方法里面的name
// 不是这个类的属性name。等于说你在set的时候其实没有给这个属性set值。
// 于是就引入了this关键字,来代表本类,在这里的作用有点类似python类里面的self。
public void getName(){
return name;
}
public void setAge(int a){
age = a;
}
public void getAge(){
return age
}
}
其实呢,this可以理解为外部使用这个类的时候的当前对象,当在通过一个对象来调用某个方法或方法的时候我们通常在外部采用"对象.属性"或者"对象.方法"的方式来调用。那么在类的内部呢,在类方法中,我们可以使用"this.属性"或者"this.方法"的方式,调用当前对象属性或者方法。但是通常我们都省略了this。
在构造器中,我们可以也可以通过this.属性或者this.方法的方式来调用正在创建的对象的属性或者方法。
this调用构造器的情况:
class Person{
private String name;
private int age;
public Person(){
this.name = "";
this.age = "1";
// a操作
// b操作
// c
// d
}
public Person(String n){
// 这时候我需要给age赋值,可以直接在这里加上一句,this.age,但是如果 同时我还需要加上 a\b\c\d操作呢
// 可以看到,不管是给age赋值,还是执行a\b\c\d操作,都在上面这个无参构造器中,是有且存在的,那怎么办呢?
// this() 这种方式,就直接调用了构造器,如果有很多构造器怎么去区分呢? 我们知道构造器名字是一样的
// 区别在于有没有参数的不同,所以通过后面括号里面的参数来区分不同的构造器。 注意构造器本身不能调自己
this();
this.name = n;
}
}
注意: 调用this()这种方式使用时,必须声明在构造器的首行。
package\import关键字的使用
package关键字的使用
为了更好的实现项目中类的管理,提供包的概念
包属于标识符,遵循标识符的命名规则,规范、见名知意
每’.'一次代表一层文件目录,补充,同一个包下不能命名同一个接口或类
大概过一次JDK中主要的包:
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)
import导入
- 在源文件中使用import结构导入指定包下的类、接口
- 声明在包的声明和类的声明之间
- 如果需要导入多个结构,则并列写出即可
- 可以使用 'xxx.*'导入xxx包下的所有结构
- 如果使用的使类或接口是java.lang包下定义的,就可以省略import结构
- 如果是同包下使用类,可以省略import结构
- 如果在源文件种,使用了不同包下的同名得类,则必须至少有一个类需要使用以全类名得方式显示(我记得python是可以取一个别名的…)
- xxx.*可以导入xxx包下的所有结构,但是不能导入xxx包的子包里面的东西。
- import static: 导入指定类或接口中的静态结构。
MVC设计模式
MVC是常用的设计模式之一,将整个程序分为了三个层次: 视图模型层,控制器层,数据模型层。这种将程序输入输出、数据处理、以及数据的展示分离开来的设计模式使程序结构变的灵活而且清晰。同时也描述了程序各个对象间的通信方式,降低了程序间的耦合性。
模型层(model主要处理数据):
数据对象封装: model.bean/domain
数据库操作类: model.dao
数据库 model.db
控制层 controller 处理业务逻辑
应用界面相关
存放fragment
显示列表的适配器
服务相关的
抽取的基类
项目一
到时候把git地址贴上
继承性
先写一个类
public class Person {
String name;
int age;
public Person(String name, int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃饭");
}
public void sleep() {
System.out.println("睡觉")
}
}
再来一个类
public class Student {
String name;
int age;
public major;
public Student() {
}
public Student(String name,int age,String major) {
this.name = name;
this.age = age;
this.major = major;
}
// 这个时候我们想给学生这个类加上吃饭、睡觉、学习的方法,我们发现一个问题
// 这个类要加上的方法在person类中吃饭和睡觉都存在。怎么去处理呢?
}
这个时候,其实类和类之间也是有关联关系的,比如我们可以看到,person这两个类,最基本的两个方法,一个吃饭,一个睡觉,这两个方法在学生这个类中都有体现,比较类是我们现实生活中的父亲与儿子的关系,至于为啥说是父亲与儿子,不说母亲与女儿,这是为了更好的打拳(田园女权给爷爬)。所以我们可以默认为person这个类为父类,student为子类。这样一来我们可以改写成这样。
public class Student extends Person{
String major;
public Student() {
}
public Student(String name,int age,String major){
this.name = name;
this.age = age;
this.major = major;
}
// 因为父类中含有eat和sleep方法所以只需要自己定义一个
public void study() {
System.out.println("学习");
}
}
那么继承的特性我们看到了,继承有什么好处呢?
- 减少了代码的冗余,提高了代码的复用性。
- 便于功能的拓展
- 为多态提供了前提
继承的格式: class A extends B{}
A: 子类(派生类),subclass(本来我以为记这些东西没啥用,但是我反过头过来复习的时候发现,有时候有些方法,取名就是根据这个来的,记一下可能学习那个方法的时候增加亲切感。)
B:父类,超类、基类、superclass
体现: 一旦子类A继承父类B中,子类A中就获取了父类B中声明的所有的结构: 属性、方法
特别的父类中声明为private的属性或方法,子类继承父类以后,任然认为获取了父类中私有的结构。只是因为封装性的影响,使得子类不能直接调用父类的结构而已。
子类继承父类以后,还可以声明自己特有的属性或方法: 实现功能的拓展。子类就是后浪。
extends: 延展、拓展
java中关于继承的规定
java只支持单继承和多继承,不允许多重继承(这里可以说一下,python是支持多继承的,python是存在一个砖石继承的问题)
- 一个子类只能有一个父类
- 一个父类可以派生多个子类
Object类
如果我们没有显式声明一个类的父类的话,则此类继承于Java.lang.Object类,所有的类除开Object类以外都直接或间接的继承于java.lang.Object类,意味着所有的java类具有java.lang.Object类声明的功能。
方法的重写
定义: 在子类方法中可以根据需要对从父类中继承来的方法进行改造,也称为方法的重置、覆盖在程序执行时,子类的方法将覆盖父类的方法。
要求:
- 子类重写方法必须和父类有相同的方法名称和参数列表
- 子类重写的方法的返回值类型不能大于父类被重写方法的返回值类型
- 子类重写方法使用权限不能小于父类方法的访问权限,子类不能重写父类中声明为private权限的方法
- 子类方法抛出的异常不能大于父类被重写方法的异常。
public class Person {
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("吃饭");
}
public void walk(int distance){
System.out.println("走路,走的距离是: " + distance + "公里");
}
}
public class Student extends Person{
String major;
public Student(){
}
public Student(String major) {
this.major = major;
}
public void study(){
System.out.println("学习的专业是: " + major);
}
public void eat() {
System.out.println("学生应该多吃有营养的食物");
}
}
子类重写方法使用权限不能小于父类方法的访问权限,子类不能重写父类中声明为private权限的方法
先来复习一下权限修饰符 private、缺省、protect、public,现在我们看Student和Person有相同的方法eat(),那么我们就算是在Student类中重写了Person的eat方法,如果这时候我们把Student类中eat方法的public给删除了,这时候eat的权限修饰符就变成了缺省,可以看到会报错,所以我们在重写方法的时候要注意,不能拿小的权限去重写大的权限,因为他本身其实算是一种覆盖,小的怎么能盖的住大的呢?那现在父类中的eat方法已经是public,所以你子类已经没得选了,要重写就只能写同样是public权限的方法了。那如果父类中的eat是private权限,这就是最小的权限了,那我能不能拿缺省、protect、public这些权限去覆盖去重写呢? java里面规定,不能重写父类中声明为private权限的方法。
有一个情况,父类中调用了在子类中被重写的方法,执行的是子类被重写的结果。这个是重写的特性。得记清了。
子类重写的方法的返回值类型不能大于父类被重写方法的返回值类型
父类被重写的方法返回值类型是void,则子类重写的方法的返回值类型也只能是void
父类被重写的方法的返回值类型是A类型,则子类重写的方法的返回值类型可以是A类或A类的子类。 — 见下方info方法,父类的返回值类型是Object类,但是String是Object类的子类所以可以返回。
父类被重写的方法的返回值类型是基本数据类型(比如: double),则子类重写的方法的返回值类型必须是相同的基本数据类型(也必须是double):—见下方的info1的案例。 这里别整什么自动类型提升那套嗷,返回值类型是返回值类型,类型提升是类型提升。
public class Person {
public Object info(){
return null;
}
public double info1(){
return 1.0;
}
}
public class Student extends Person{
public String info(){
}
// public int info1(){
// return 1;
// }
}
异常
子类重写的方法抛出的异常类型不大于父类被重写的方法抛出的异常类型— 这个比较像返回值类型的那个子类的说法,子类抛出的异常是父类抛出异常的子类比如Exception应该是最大的异常,如果父类抛出这个异常,那子类中就不能大于这个异常,可以用runtimeException这种的。
最后一个事: 子类和父类中的同名同参数的方法要么都声明为非static的(考虑重写),要么都声明为非static(不考虑重写)
方法重写的练习题
- 如果现在父类的一个方法定义成private访问权限,在子类中将此方法声明为default访问权限,那么这样的方法还叫重写吗? 看看上面说的private能不能重写。
4种不同的权限修饰符
public class Order {
private int orderPrivate;
int orderDefault;
protected int orderProtected;
public int orderPublic;
private void methodPrivate(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
void methodDefault(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
protected void methodProtected(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
public void methodPublic(){
orderPrivate = 1;
orderDefault = 2;
orderProtected = 3;
orderPublic = 4;
}
}
我们在同一个包A中新建一个文件
public class OrderTest {
Order order = new Order();
order.orderDefault = 1;
order.orderProtected = 2;
order.orderPublic = 3;
order.methodDefault();
order.methodProtected();
order.methodPublic();
// 同一个包中的其他类,不可以调用Order类中私有的属性、方法
// order.orderPrivate = 4;
// order.methodPrivate();
}
我们在不同包B里面创建一个文件
import xxx.xxx.xxx.Order;
public class SubOrder extends Order {
public void method(){
// 先想想那些属性那些方法可以使用。
orderProtected = 1;
orderpublic = 2;
methodProtected();
methodPublic();
// 在不同包的子类中,不能调用Order类中声明为private和缺省default
// orderDefault = 3;
// orderPrivate = 4;
// methodDefault();
// methodPrivate();
}
}
接下来在B包中新建一个不是Order类的子类的文件
public class OrderTest{
public static void main(String[] args){
Order order = new Order();
order.orderPublic = 1;
order.methodPublic();
// 不同包下的普通类(非子类)要使用Order类,不可以调用声明为private、缺省、protected的属性和方法
// order.orderPrivate = 2;
// order.orderDefault = 3;
// order.orderProtected = 4;
// order.methodPrivate();
// order.methodDefault();
// order.methodProtected();
}
}
super关键字的使用
- super理解为:父类的
- super可以用来调用: 属性、方法、构造器
- super的使用
public class Person {
String name;
int age;
int id = 1001;
public Person(){
}
public Person(String name){
this.name = name;
}
public Person(String name,int age){
this(name);
this.age = age;
}
public void eat() {
System.out.println("人,吃饭");
}
public void walk(){
System.out.println("人,走路");
}
}
public class Student extends Person{
String major;
int id = 1002;
public Student(){
}
public Student(String major) {
this.major = major;
}
public void eat() {
System.out.println("学生: 多吃有营养的食物");
}
public void study(){
System.out.println("学生: 学习知识");
}
public void show(){
//
System.out.println("name = " + this.name + ",age = " + super.age);
System.out.println("id = " + id);
System.out.println("id = " + super.id)
}
}
这里需要注意一点,属性和方法不一样,方法同名会被覆盖,属性就不会了。
public class SuperTest {
public static void main(String[] args) {
Student s = new Student();
s.show();// 这里输出的是1002也就是子类中的这个id,那么如果我们想调用父类中的id怎么办呢?
// 我们在子类中加一个super.id这种就直接去调用了父类的id
}
}
super调用构造器
我们可以在子类的构造器中显式的使用"super(形参列表)"的方式,调用父类中声明的指定的构造器,super 构造器的使用必须声明在子类的首行
我们在类的构造器中,针对this(形参列表)或super(形参列表)只能二选一,都只能放首行,这话正常人应该品一下就品出来了吧。
当我们在构造器的首航,没有显式的声明"this(形参列表)"或者super(形参列表)则默认调用的是父类中的空参构造器: super()
子类对象的实例化过程
从结果上看:
子类继承父类以后,就获取了父类中声明的属性或方法。创建子类的对象在堆空间中就会加载所有父类中声明的属性
从过程上看:
当我们通过子类的构造器创建子类对象时,我们一定会直接或间接的调用其父类的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中空参的构造器为止,正因为加载过所有的父类的结构,所以才可以看到内存中有父类中的结构,子类对象才可以考虑进行调用
明确: 虽然创建子类对象时,调用了父类的构造器,但自始至终就创建过一个对象,即为new的子类对象。
多态
什么是多态
public class Person {
String name;
int age;
public void eat(){
System.out.println("人,吃饭");
}
public void walk() {
System.out.println("人: 走路");
}
}
public class Man extends Person{
boolean isSmoking;
public void earnMoney(){
System.out.println("男人负责挣钱养家");
}
public void eat() {
System.out.println("男人多吃肉,长肌肉");
}
public void walk(){
System.out.println("男人霸气的走路");
}
}
public class Woman extends Person{
boolean isBeauty;
public void goShopping(){
System.out.println("女人喜欢购物");
}
public void eat(){
System.out.println("女人少吃,为了减肥");
}
public void walk() {
System.out.println("女人窈窕的走路");
}
}
public class PersonTest {
public static void main (String[] args) {
Person p1 = new Person();
p1.eat();
Man man = new Man();
man.eat();
man.age = 25;
man.earnMoney();
// **** 多态 ****
// 注意这里 我们声明了一个Person,但是new了一个Man()但是没报错为啥?
// 这就是接下来要说的 多态性: 父类的引用指向子类的对象
Person p2 = new Man();
// 问题来了,下面这个eat() 方法到底调用的是哪个的eat,是父类的还是子类重写后的。
p2.eat();
// 答案是子类的。
// 现在有个问题
p2.earnMoney();
// 这个能调吗? 不能哟,因为这个方法始终来说它还是没有在Person类里面声明过。
}
}
我记得当初学python的时候,说过一种类型,鸭子类型,这个其实也体现了多态性,而且具体描述出了为啥python没有严格的多态性的现象。
何为多态性: 父类的引用指向子类的对象,
多态的使用: 当调用子类同名同参数的方法时,实际执行的是子类重写父类的方法,——虚拟方法的调用。
到这里来的时候,其实我自己试了一下去用idea点到这个P2.eat()进去看,我发现点进去的是Person的方法,所以,其实在编译的时候,p2表现为Person类,但是在运行的时候,它实际变现又是为一个Man()类的状态。所以现在我们把这个程序分为了两个状态,一个编译时的状态,和一个运行时的状态。
多态的使用(虚拟方法的调用):
有了对象的多态性之后,我们在编译期,只能调用父类中声明的方法。(所以我们上方有一个问题就是在是用p2.earnMoney()方法的时候会报错)但是在运行期,我们实际执行的是子类重写父类的方法。总结: 编译看左边,运行,看右边。
总结:
多态性的使用前提:
- 要有类的继承关系。
- 要有方法的重写
为什么要有多态?
// 举例一
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
// 这里发现有多妙了吗? 方法里面虽然声明的是一个Animal类型的形参,但是我这里传参传的却是
// 一个Dog类的实参,但是放进去之后我又确实可以调用Dog的方法,这里我们可以看到,要实现这个效果
// 重写和继承缺一不可
}
public void func(Animal animal){
animal.eat();
animal.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("汪汪汪")
}
}
class Cat extends Animal {
public void eat() {
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵喵喵")
}
}
// 举例二:
// 可以去参考equals方法,它由于参数的原因,可以适用大部分的参数
class Order {
public void method(Object obj) {
}
}
// 举例三:
// 当我们要去连接数据库的时候,如果所有类型的数据库的连接对象,都继承与Connection,当然这个是个接口了。。。
// 那是不是我们可以通过 conn = new MySQLConnection()
class Driver{
public void doData(Connection connection){
}
}
多态性不适用于属性
对象的多态性只适用于方法,不适用于属性,比如如果上方案例中,如果Person方法中有一个属性id = 1;下方的Man子类中也有一个id = 2; 那么如果我们还是通过Person p1 = new Man()这种形式去生成对象,那么System.out.println(p1.id)输出的值为1,我个人的理解是,属性没有被覆盖,重写过程中算是一种方法的覆盖。
虚拟方法调用的再理解
正常方法的调用:
Person e = new Person();
e.getInfo();
Student e = new Student();
e.getInfo();
虚拟方法调用:
子类中定义了与父类同名同参的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的
Person e = new Student();
e.getInfo();// 调用Student类的getInfo方法
编译时类型和运行时类型:
编译时e为Person类型,而方法的调用是在运行时确定的,所以调用的是Student类的getInfo()方法。—-动态绑定
instanceof操作符
有了对象的多态性之后,内存中实际上是加载了子类特有的属性和方法的。但是由于变量声明为父类类型,导致编译时,只能调用父类中声明的属性和方法。子类特有的属性和方法不能调用。
那么问题来了,我们怎么才能调用子类特有的属性和方法呢?
Person p2 = new Man();
Man m1 = (Man)p2;
是用强制类型转换这样就可以把编译中认定为Person类的p2转换为Man类型。
在强转的时候要注意一点,(Man)p2之后,p2就已经转换为Man类型了,这时候如果再操作
(Woman)p2就会报错。可能会出现ClassCastException
那么为了出现这种问题,引入了一个关键字instanceof
instanceof关键字的使用
a instanceof A: 判断对象a是否是类A的实例。如果是返回true;如果不是返回false
if(p2 instanceof Woman){
Woman w1 = (Woman)p2;
w1.goShopping();
System.out.println("******Woman******");
}
使用场景: 为了避免在向下转型时出现ClassCastException的异常,我们在向下转型之前,先进行instanceof的判断,一旦返回true,就进行向下转型,如果返回false,不进行向下转型。
注意一点: 如果类B是类A的父类,如果a instanceof A返回的是true,那么a instanceof B也会返回true;
总结小考:
- 什么是多态? 什么是虚拟方法调用?
- 一个类可以有几个父类?一个父类可以有多少个子类? 子类能获取直接父类的父类中的结构吗?子类能否获取父类中private权限的属性或方法
- 方法的重写的具体规则有哪些?
- super调用构造器,有哪些具体的注意点
- 在下面的代码结构中: 使用关键字: this、super;方法的重写;继承;
Object类的使用
这块我有一个同事,在面试金山的java测试工程师的时候,有问到类似的问题,所以我这块印象还挺深的。
object类是所有java类的父类
如果在类的声明中未使用extends关键字指明其父类,则默认父类为java.lang.Object类
Object类中的功能(属性、方法)都具有通用性
Object类只声明了一个空参构造器。
clone()\equals(Object obj)\toString() \ getClass() \ hashCode() \
finalize() 一般不主动调用这个,就是一个垃圾回收的函数,在对象被销毁之前调用,和python的__del__比较像。
wait()\ notify()\notifyAll() 多线程。
equals()
这个问题,经常被问的就是 == 和equals的区别
先回顾一下 == 的作用:
基本类型中:比较两个变量保存的数据是否相等。(不一定类型要相同)
引用数据变量: 比较两个对象的地址值是否相同。即两个引用是否指向同一个对象实体
equals:
是一个方法,而非运算符。所以基本数据类型就别考虑用这个了。不同于python的一切接对象,java的基本数据类型不能点出方法来。因此equals只适用于引用数据类型。
注意一点,在Objects的源码中:
public boolean equals(Object obj) {
return (this == obj);
}
他本身就其实还是用的 == 来做的判断,所以在Object的子类没有重写equals方法的时候,调用这个equals方法,本质上和==的效果是一样的。
那原生方法中,String、Date、File、包装类等里面是有一个equals的,他们其实都重写了object类中的equals方法,他们重写的原则的是遵从着从内容上看两者是不是相等。
那如果我们自己也要达到这种效果,我们怎么去达到上述类的equals方法的效果呢?
有一个便捷方法,直接编译器生成。
toString()
public class ToStringTest {
public static void main(String[] args) {
Customer cust1 = new Customer("Tom", 21);
System.out.println(cust1.toString());
System.out.println(cust1);
}
}
我们发现,上面两个输出值是一样的,其实当我们输出一个对象的引用时,实际上调用当前对象的toString()方法
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
其实println方法中,默认是调用了toString方法的,有兴趣可以自己去点一下看看。
String str = new String("我是你的爹");
System.out.println(str);//输出的是什么?为什么会输出这个结果?
// 答案是输出"我是你的爹",但是按我们上面的说法,println会调用toString,而Object类中的toString方法
// 结果是那样的,那是咋回事,这里就直接输出值了呢?
其实String、Date、File包装类等都重写了Object类中的toString()方法。
单元测试
哎,看到这个内容,我挺心酸的。
Java中的JUnit单元测试:
不多说了,这块还不会就有点对不起自己曾经的那些工作经验了。
包装类(Wrapper)的使用
针对八种数据类型 java推出了八种包装类。
基本数据类型 | 包装类 | 父类 |
---|---|---|
byte | Byte | Number |
short | Short | Number |
int | Integer | Number |
long | Long | Number |
float | Float | Number |
double | Double | Number |
boolean | Boolean | |
char | Character |
为了java真正的面向对象,所以提出了包装类的思想。实际上就是把原本的数据类型,用了一个类包装起来了,比如
private final int value;
这样把原有的int i = 3;这种值,直接作为这个类的属性给赋值进去。与此同时也给各个类提供一些更加丰富的功能。
基本类型、包装类与String类之间的转换
基本数据类型和包装类之间的转换
/**
* 包装类的使用:
* java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征。
*
* 需要掌握的是,基本数据类型和包装类以及String三者之间的数据转换。
*/
public class WrapperTest {
// 基本数据类型 -- 》 包装类: 调用包装类的构造器
@Test// 这个是JUnit的测试注解
public void test1() {
int num1 = 10;
Integer in1 = new Integer(num1);
System.out.println(in1.toString());
Inter in2 = new Integer("123");
System.out.println(in2.toString());
Float f1 = new Float(12.3F);
Boolean b1 = new Boolean(true);
Boolean b2 = new Boolean("true");
Boolean b3 = new Boolean("true123");
System.out.println(b3);// flase
}
}
包装类和基本数据类型之间的转换
public class WrapperTest2 {
// 包装类 ---》 基本数据类型
@Test
public void test2(){
Integer in1 = new Integer(12);
// 这样一来 就把值给获取出来了
int i1 = in1.intValue();
}
}
自动装箱和自动拆箱
JDK5.0的时候出了一个新的特性,自动装箱与自动拆箱
public class WrapperTest3 {
@Test
public void test3(){
int num1 = 10;
method(num1);
// 正常来说,基本数据类型是不属于Object类的子类的,但是这里直接这么传参过去也不报错。
// 为啥呢,这里涉及到的就是JDK5.0之后退出的新特性,自动装箱,比如下方的
int num2 = 10;
Integer in1 = num2;
boolean b1 = true;
Boolean b2 = b1;
// 自动拆箱
int num3 = in1;
}
public void method(Object obj){
}
}
基本数据类型、包装类 转换成String类型
public class WrapperTest4 {
@Test
public void test4(){
int num1 = 10;
// 方式一: 直接相加
String str1 = num1 + "";
// 方式2: 调用String的valueOf
float f1 = 12.3f;
String str2 = String.valueOf(f1); // "12.3"
Double d1 = new Dobule(12.4);
String str3 = String.valueOf(d1);
}
}
String类型转成基本数据类型、包装类
public class WrapperTest5 {
@Test
public void test5(){
String str1 = "123";
// 调用包装类的parseXxx()
Integer.parseInt(str1);
String str2 = "true";
boolean b1 = Boolean.parseBoolean(str2);
System.out.println(b1);
}
}
包装类的面试题
public void test1(){
object o1 = true?new Integer(1) : new Double(2.0);
System.out.println(o1);//1.0
//为啥呢? 因为三目运算符要求后面两个值的类型一致,所以会有一个自动类型提升。
// 也就是Integer会转换成Double自然输出也就是1.0了。
}
public void test3(){
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j); //false
Integer m = 1;
Integer n = 1;
System.out.println(m == n);// true
Integer x = 128;
Integer y = 128;
System.out.println(x == y);// false
}
// 这里涵盖了一个Inter里面的一个内部类产生的一个IntegerCache,其实python中也有这样的一个
// 概念最小整数池,也就是说在这个池内部而去产生的自动装箱的变量,其实本质上就是同一个。
关键字static
这个东西怎么说,刚开始我觉得很难理解,看到这里来的时候,突然想起来python的类变量和实例变量,当时学的时候记住了一句话,所有的实例共享一个类变量,所有实例有自己的实例变量,就有那种提取公因式的感觉,这么对比一像,一切都豁然开朗了。
static修饰属性
/**
* static: 静态的
* static可以用来修饰: 属性、方法、代码块、内部类
*
*/
public class StaticTest {
public static void main(String[] args) {
Chinese c1 = new Chinese();
// 每个人出生的时候都有他自己的名字和年龄
c1.name = "小张";
c1.age = 12;
// 所以当我们又出生了一个人的时候
Chines c2 = new Chinese();
c2.name = "小熊";
c2.age = 10;
// 所以我们看到,每个类变量,都有他自己独有的属性,这些age、name各个实例之间都是独立的
// 每个人都是他自己的独一份。
// 这个时候我们对c1去设置一个国际nation字段
c1.nation = "CHN";
System.out.println(c2.nation);
// 按我们的想法,c2的nation没有赋值,那是不是应该就是null呢?
// 实际情况不是的,这里的nation在chinese类中被声明为Static,也就是说,这个static是一个
// 静态变量,就是共享的一个变量。如果对这个内存关系不知道再去看看上方的内存关系图。
// 可以认为静态变量放到了方法区,
}
}
//中国人
class Chinese{
String name;
int age;
static String nation;
}
static修饰方法
类似python的@staticmethod这个注解
随着类的加载可以通过"类.方法"的方式来调用这个方法
class Chinese{
String name;
int age;
static String nation;
public void eat(){
System.out.println("中国人吃饭");
}
public static void show(){
System.out.println("我是一个中国人!");
}
}
静态方法中,只能调用静态的方法或是属性
非静态方法中,既可以调用非静态的方法或属性,可以调用静态的方法或属性
这个其实好像没那么好理解,如果真的有人看我这个笔记,恰好也是学过python的,那我这里做个解释python的类方法默认是有一个self参数的,对象在调用类方法的时候,会隐式的传这个self参数(类似java的this),也就是这个对象本身。但是python的静态方法是不含有这个self,也就是说,如果你连这个对象本身都没有传入进去,你怎么可能去调用那些对象的属性和方法呢?自然可以说通静态方法只能调用静态的方法或者属性。这是我个人通过python的一套理解。
还有一种是涉及到类的生命周期,类在加载的时候static已经生效了,但是此时你对象还没有诞生,自然你不能通过static去调用还没诞生的对象的方法和属性了。
大部分工具类都习惯性用static。因为你没必要去专门声明个对象了,再去调用这些对象的方法,直接通过类调用不香吗?
设计模式
创建型的: 工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式
结构型模式: 适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式
行为模式: 策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式
单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个软件中,对某个类只能存在一个对象实例。
饿汉式的实现
public class SingletonTest1 {
public static void main(String[] args) {
Bank bank1 = Bank.getInstance();
Bank bank2 = Bank.getInstance();
System.out.println(bank1 == bank2);// true
}
}
// 我希望现在银行类只创建一个
class Bank{
// 1. 私有化类的构造器
private Bank(){
}
// 2. 内部创建类的对象 == (个人感觉如果后面暴力反射,应该还是可以创建多个对象,加个final好一点)
private static Bank instance = new Bank();
// 3. 提供公共的方法返回类的对象,且必须为static的,不然你外部咋调
public static Bank getInstance() {
return instance;
}
}
懒汉式的实现
class Order{
private Order(){
}
private static Order instance = null;
public static Order getInstance(){
if(instance == null){
instance = new Order();
}
return instance;
}
}
// 这个线程不安全,比如A线程和B线程在调用getInstance方法的时候,同时判断了instance,
// 此时instance就是为null 这样一来就都创建了一个新的实例。
名字怎么来的,想想人类的懒逼吃饭,是不是啥时候想吃饭了啥时候去做饭,你看这个第二个实现,是不是每次在getInstance才去new这个Order,饿汉式就是饿怕了,每次上来就给你把饭做好,要吃就直接拿。懒汉式千万记住,线程不安全。
懒汉和饿汉的区别
懒汉式延迟加载对象,在需要这个对象之前,一直是保持着null的状态,不会占用内存。(上方的这个写法是不安全的)
饿汉式就是类刚加载的时候,就一直存在,在整个类的生命周期中,就一直存在。(线程安全)
理解main方法的语法
- main() 方法作为程序的入口
- 它其实也只是一个普通的静态方法
- main()方法里面有形参,可以作为与控制台交互的一种方式(以前使用Scanner)
这个就是说,你可以通过在在启动的时候,给main方法传参,现在公司的项目启动一般都会在
这个地方去输入值,这个就可以通过String[] args的形参去接收。
类中的代码块
代码块的作用:
用来初始化类、初始化对象
代码块如果使用修饰,只能使用static
静态代码块:
内部可以有输出语句
当我们在执行这个的时候发现,静态代码块是随着类的加载而执行,也就是说,我们就算没有通过类.方法的形式去调用,也能触发,而且只执行一次
如果一个类中定义了多个静态代码块,则按声明的先后顺序执行
非静态代码块:
内部可以有输出语句
随着对象的创建而执行
而且没创建一个对象,就执行一次非静态代码块
作用: 可以在创建对象时,对对象的属性等进行初始化
public class BlockTest {
public static void main(String[] args) {
String desc = Person.desc;
}
}
class Person{
// 属性
String name;
int age;
static String desc = "我是你爹";
// 构造器
public Person(){
}
// 构造器
public Person(String name,int age){
this.name = name;
this.age = age;
}
// 代码块
{
System.out.println("hello,block")
}
// static 静态代码块
static {
System.out.println("hello,static block");
}
// 方法
public void eat() {
System.out.println("吃饭");
}
public String toString() {
return "Person [name=" + name + ",age=" + age + "]";
}
}
代码块的使用场景
// 我们举例是数据库连接池的例子
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();
}
}
}
}
代码块题目
class Root {
static{
System.out.println("Root的静态初始化块");
}
{
System.out.println("Root的普通初始化块");
}
public Root() {
System.out.println("Root的无参构造器");
}
}
class Mid extends Root{
static{
System.out.println("Mid的静态初始化块");
}
{
System.out.println("Mid的普通初始化块");
}
public Mid () {
System.out.println("Mid的无参构造器");
}
public Mid(String msg){
this();
System.out.println("Mid的带参数构造器,其参数值:" + msg);
}
}
class Leaf extends Mid{
static{
System.out.println("Leaf 的静态初始化块");
}
{
System.out.println("Leaf 的普通初始化块");
}
public Leaf() {
Super("熊")
System.out.println("Leaf的无参构造器");
}
}
public class LeafTest{
public static void main(String[] args) {
new Leaf();
}
}
总结: 由父及子,静态先行。
class Order{
int orderId = 3;
{
orderId = 4;
}
}
final修饰符
- final可以用来修饰的结构: 类、方法、变量
final修饰类
final class FinalA{
// 表示这个类是个最终的了。这个类就不能被其他类继承了
// 我们比较常见的: String类、System类、StringBuffer类。
}
final修饰方法
// 表明此方法不能被重写,比如比较常用的Object类中的getClass()
class AA{
public final void show() {
}
}
class BB extends AA {
public void show(){
// 这个方法会报错无法被重写了。
}
}
final用来修饰变量
final修饰属性
public class FinalTest{
// 显式赋值
final int width = 0;
// 代码块中赋值
final int left;
{
left = 0;
}
// 构造器中赋值
final int right;
public FinalTest(){
right = 2;
}
}
final修饰局部变量
public void show(final int num) {
// num = 10;这个编译不会通过
// 就是说我们呢能在这个方法体内部使用这个形参,但是不能去重新给她赋值。
}
abstrace关键字
抽象类
随着继承层次中一个个新子类的定义,类变的越来越具体,而父类则更一般,更通用。类的设计应该保证父类和子类能够共享特性。有时将一个父类设计得非常抽象,以至于它没有具体得实例,这样得类叫做抽象类。
比如人这个类,我们衍生出很多子类,学生、老师、官员、老板等等,后面我们发现,我们似乎用具体的子类比较多,甚至都不在使用人这个父类了,那我们就给人这个类去定义为一个抽象类。
// abstract关键字的使用: 可以用来修饰结构: 类、方法
public class AbstractTest{
Person p1 = new Person();
p1.eat();
// 如果把Person加上abstract class Person
// 那么就不能进行Person p1 = new Person();的操作了。
}
class Person {
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
public void eat(){
System.out.println("人吃饭");
}
public void walk(){
System.out.println("人走路");
}
}
class Student extends Person{
}
当我们用abstract修饰类之后,这个类就不能实例化了。虽然它不能实例化了,你不能认为它的构造器就没用了,子类对象还是能通过super的方法去调用这个构造器的。所以记住抽象类中一定有构造器,便于子类实例化时调用。开发中都会提供抽象类的子类,让子类对象实例化,完成相关操作。
抽象方法
abstract class Person{
// 修饰方法
public abstract void eat();
}
抽象方法只有方法的声明,没有方法体。那问题来了,只有声明,没有体,你还执行吗?还有个问题,我是不是可以通过类去调用这个抽象方法,和static的方法一样? 答案是不行。如果我们在一个类中存在抽象方法,那这个类必须要是抽象类。如果一个类式抽象类,它其中的方法可能不是抽象方法。
那我们在子类继承抽象类的时候,这个抽象方法怎么办?我们需要去子类重写这个抽象方法,或者你子类本身也是个抽象类。直接父类和间接父类的方法都要重写。
abstract使用上的注意点
- abstract不能用来修饰: 属性、构造器等结构
- abstract不能用来修饰私有方法(私有方法都无法被继承,怎么去重写啊)、静态方法(静态方法不能被覆盖,也重写不了)、final的类。总之记住一点: abstract的使用是为了继承和重写,不能满足这个两个条件的修饰符都不考虑和abstract共存
思考题:
- 为什么抽象类不可以使用final关键字声明
- 一个抽象类中可以定义构造器吗?
- 是否可以这样理解: 抽象类就是比普通类多定义了抽象方法,除了不能直接进行类的实例化操作之外,并没有任何不同?
- 编写一个employee类,声明为抽象类,包含如下三个属性: name,id,salary…提供构造器和抽象方法work()
public abstract class Employee {
private String name;
private int id;
private double salary;
public Employee(){
}
public Employee(String name,int id,double salary) {
super();
this.name = name;
this.id = id;
this.salary = salary;
}
public abstract void work();
}
public class Manager extends Employee {
private double donus;
public void work(){
System.out.println("管理员工");
}
}
抽象类的匿名子类对象
首先要区分一个概念,叫匿名对象,当初我们把mehod(new Student());这种情况称为传入了一个匿名对象,这个对象是没有和变量绑定的,这里说的匿名抽象子类是另外的一种情况;
先说,Person类是一个抽象类,存在eat和walk的抽象方法
public class PersonTest{
public static void main(String[] args) {
Person p = new Person(){
@Override
public void eat(){
}
@Override
public void walk(){
}
}
// 以上可以认为,是在new这个Person类的过程中,重写了两个抽象方法,这样我个人做的
// 猜测是,Person类作为一个抽象类,本身它不能new对象的原因可以认为是,它里面的抽象方法
// 没有具体的实现代码,但是你后面补充了这一块的代码块相当于重写了这个抽象方法,于是就新建成功了。
// 然后新建成功之后,它的类方法都不一样了,你还能叫他是Person类吗,只能算是Person的子类,所以我们
// 用了多态的形式,让这个新建的类复制给他了他的绝对父类。
}
}
模板方法设计模式
抽象类的体现就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会保留抽象类的行为模式。
class Template {
public void spendTime(){
long strat = System.currentTimeMillis();
code();// 这就是不确定易变的部分
long end= System.currentTimeMillis();
System.out.println("花费的时间为:"+(end-start));
}
public abstract void code();
}
public class TemplateTest {
public static void main(String[] args) {
SubTemplate t = new SubTemplate();
t.spendTime();
}
}
public SubTemplate extends Template {
@Override
public void code(){
for(int i = 2;i <= 100; i++){
for (int j = 2;j <= Math.sqrt(i); j++){
if(i % j == 0){
isFlag = false;
break;
}
}
if(ifFlag){
System.out.println(i);
}
}
}
}
整个流程其实整体布局上像一个钩子,头和尾固定好了,中间这部分写成抽象方法,你需要钩什么上去,你就放重写的时候改你需要的内容就好。
练习:
编写工资系统,实现不同类型员工(多态)的按月发放工资,如果当月出现某个Employ对象的生日,则将该员工工资增加100元。
试验说明:
1.定义一个Employee类,该类包含:
private成员变量: name\number\birthday,其中birthday为MyDate类的对象;
abstract方法earnings();
toString()方法输出对象的name,number和birthday。
- MyData类包含:
private成员变量year,month,day;
toDateString()方法返回日期对应的字符串: xxxx年xx月xx日
- 定义SalariedEmployee类继承Employee类实现按月计算工资的员工处理。该类包括:
private成员变量monthlySalary;
实现父类的抽象方法earnings(),该方法返回monthlySalary值,
toString方法输出员工类型信息以及员工的name,number,birthday。
- 参照SalariedEmployee类定义HourEmployee类,实现按小时计算工资的员工处理
private成员变量wage和hour;
实现父类的抽象方法earings(),该方法返回wage*hour值;
toString() 方法输出员工类型信息以及员工的name,number,birthday
- 定义PayrollSystem类,创建Employee变量数组并初始化,该数组存放各类雇员对象的引用。利用循环结构遍历数组,输出各个对象的类型,name,number,birthday,以及该对象生日。当键盘输入本月月份时,如果本月是某个Employee对象的生日,还有输出增加工资的信息
接口(interface)
首先我们得知道一个概念,我们上方说的类是不支持多个父类去衍生一个子类的,java不支持多继承,但是有了接口就可以得到多重继承的效果。
接口就是规范,也就是说“如果你是/你要。。。则必须能。。。"的思想。继承是一个"是不是"的关系,而接口实现则是”能不能“的关系
接口的本质是契约,标准,规范,就像我们的法律制定好后大家都要遵守。
再举个例子原本意义上的类的继承,子父类这种关系,比如球类运动员,篮球运动员,这些我们都可以把他们称为运动员,那父类就是运动员,子类就是球类运动员,篮球运动员。同样的,大学生和高中生,我们都可以说他是学生,那父类就是学生,子类就是大学生和高中生。但是不管是球类运动员还是大学生这些子类都有一个共同点,都有学习的技能,我们再去说学生或者运动员是个一个技能就说不过去了,于是我们就说是一个接口,而学生和运动员都在自己的子类去实现。
接口(interface)的定义
// java中接口和类是并列的两个结构
// jdk7及以前: 只能定义全局常量和抽象方法
// 全局常量: public static final的,但是可以忽略不写,可是他还是存在
// 抽象方法: public abstract的,一样的也可以删除省略掉
// jdk8: 除了定义全局常量和抽象方法之外,还可以定义静态方法、默认方法
public class InterfaceTest {
System.out.println(Flyable.MAX_SPEED);
}
interface Flyable{
// 全局常量
public static final int MAX_SPEED = 7900;
public static final int MIN_SPEED = 1;
// 抽象方法
public abstract void fly();
// 省略了public abstract
void stop();
}
// 千万注意,接口中不能定义构造器。也就是说接口无法实例化
// 那这个接口我们怎么去实现呢?
class Plane implements Flyable{
// 如果实现类覆盖了接口中的所有抽象方法,则此类可以实例化
// 和抽象类比较像。
public void fly() {
System.out.println("通过引擎起飞");
}
public void stop(){
System.out.println("驾驶员减速");
}
}
类实现多个接口
interface Flyable{
// 全局常量
public static final int MAX_SPEED = 7900;
public static final int MIN_SPEED = 1;
// 抽象方法
public abstract void fly();
// 省略了public abstract
void stop();
}
interface Attackable{
void attack();
}
class Bullet extends Object implements Flyable,Attackable{
@Override
public void attack(){
}
@Override
public void fly(){
}
@Override
public void stop(){
}
}
除了一个类可以实现多个接口之外,接口之间可以多继承
interface aa{
void method1();
}
interface bb{
void method2();
}
interface cc extends aa,bb{
}
接口的具体使用
面试题:
抽象类与接口有哪些异同?
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer;
// 1. 创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);// 这里就体现了一个多态性,形参usb是一个USB类型的,但是传参传的是Flash类型的
// 2.创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
// 3.创建了接口的匿名实现类的非匿名对象,整体上和抽象类比较像
USB Phone = new USB(){
public void start(){
System.out.println("手机开启工作");
}
public void stop() {
System.out.println("手机结束工作")
}
}
// 4. 创建接口的匿名实现类的匿名对象
com.transferData(new USB(){
public void start(){
System.out.println("手机开启工作");
}
public void stop() {
System.out.println("手机结束工作")
}
} );
}
}
class Computer {
public void transferData(USB usb){
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
// 定义了长宽最大最小的传输速度
void start();
void stop();
}
class Flash implements USB{
public void start(){
System.out.println("U盘开启工作");
}
public void stop() {
System.out.println("U盘结束工作")
}
}
class Printer implements USB{
public void start(){
System.out.println("打印机开启工作");
}
public void stop() {
System.out.println("打印机结束工作")
}
}
接口应用
代理模式(静态)
后面框架基本都是用这个的。。我是先为了工作,所以spring框架是先看完了在看的这个基础,情况比较特殊,但是这个东西确实很重要
public class NetworkTest {
}
interface Network{
public void browse();
}
// 被代理类
public 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("联网之前的检查工作")
}
public void browse(){
check();
work.browse();
}
}
这个我本来理解出现了偏差,觉得其中基本模式和python的装饰器好像挺像的,但是是有差别的,python的装饰器是一种语法糖,他更多的是强调让原函数的功能更加多,这个代理模式是让自己的功能去放到其他的类里面去,然后再加上一些功能。本身运作者是其他类,但是装饰器是强调自己。
工厂模式
这里解释起来太占篇幅了,到时候这个独立开一章吧
简单工厂模式
工厂方法模式
抽象工厂模式
接口两道笔试题
interface A{
int x = 0;
}
class B{
int x = 1;
}
class C extends B implements A {
public void pX(){
System.out.println(x);// ?报错? 0 ? 1?// 答案: 编译不通过
}
}
interface Playable{
void play();
}
interface Bounceable{
void play();
}
interface Rollable extends Playable,Bounceable {
Ball ball = new Ball("Pingpong");
}
class Ball implements Rollable{
private String name;
public String getName(){
return name;
}
public Ball(String name){
this.name = name;
}
public void play(){
ball = new Ball("Football");
System.out.println(ball.getName())
}
}
// 两个问题,play实现的是那个接口的方法?都实现了
// Ball类里面的这个play方法如果去重写,你是否能new Ball("Footabll")?不能,接口的属性是final static的 final的嗷
java8中接口新特性
public interface CompareA{
public static void method1(){
System.out.println("北京");
}
// 特么为啥public 和default可以共存???
public default void method2(){
System.out.println("上海")
}
default void method3(){
System.out.println("上海")
}
}
public class SubClassTest{
public static void main(String[] args) {
SubClass s = new CompareA;
s.method2();
s.method3();
// s.method1(); 会说你没有定义
// 知识点1: 接口中定义的静态方法,只能通过接口调用。
CompareA.method1();
// 知识点: 如果子类(或实现类)继承的父类和实现的接口中声明了同名同参数的方法
// 那么子类在没有重写此方法的情况下,默认调用的是父类中的同名同参数的方法 -- 》 类优先原则(针对方法)
// 知识点: 如果实现类实现多个接口,而这多个接口中定义了同名同参的默认方法
// 那么在实现类没有重写此方法的情况下,报错 -- 》 接口冲突
// 一类实现多接口的切接口存在同名的情况。如何在子类(实现类)的方法中调用父类、接口中被重写的方法
// CompareA.super.methods();
}
}
class SubClass implements CompareA{
}
内部类
当一个事务的内部,还有一个部分需要一个完整的结构进行描述,而这个内部的完整的结构又只为外部事物提供服务,那么整个内部的完整结构最好使用内部类
在java中,运行一个类的定义位于另一个类的内部,前者称为内部类,后者称为外部类。
inner class一般用在定义它的类或语句块之内,在外部引用它时必须给出完整的名称
比如: 一个Person类,每个人都有一个大脑,我们如果用单独的大脑类型的属性去定义,那这个大脑类和Person类就属于同一级的,不能完美的体现他们之间的关系。
public class InnerClassTest{
}
class Person{
// 静态成员内部类
static class Dog{
}
// 非静态
class Bird{
}
public void method(){
// 局部内部类
class AA{
}
}
{
// 局部内部类
class BB{
}
}
// 构造器
Public Person(){
// 局部内部类
class CC{
}
}
}
成员内部类
class Person{
class Bird{
// 这里面基本上就和普通类差不多了,属性、方法、构造器都可以用
String name;
Public Bird(){
}
Public void sing(){
System.out.println("我是一只鸟");
}
}
}
可以被final修饰,表示此类不能被继承。言外之意,不使用final就可以被继承
调用
public class Test{
public static void main(String[] args){
// 非静态
Person.Dog dog = new Person.Dog();
// 静态
Person p = new Person();
Person.Bird bird = p.new Bird();
}
}
class Person{
class Bird{
// 这里面基本上就和普通类差不多了,属性、方法、构造器都可以用
String name;
Public Bird(){
}
Public void sing(){
System.out.println("我是一只鸟");
}
}
}
其他的,我实在有点犯懒,写不动了,但是这个用的很少,了解一下他的用法,混个眼熟就行了。