JavaSE_第二周
安装eclipse
创建工程:
创建工作空间:workspase
启动快捷键:指定workspase
工程名称
finish结束
设置编码:window -> preference -> general -> workspace,设置utf-8
面板的设置:
package explorer|project explorer
outline:查看类结构(方法...)
console:结果显示的面板
创建包:
包名命名使用反向域名
在src根下创建包
创建类:
在指定的包下创建类
可同时勾选main方法
运行:run as -> java application
eclipse快捷键
整行删除:ctrl + D
alt + 上下箭头: 向上或向下移动代码
ctrl + alt + 上下箭头:向上或向下复制代码
syso或sout->alt+/提示快捷键
自动导包:ctrl+shift+o
数组
概念:
可以同时存储多个相同类型的数据
特点:
固定长度,元素的数据类型统一
定义:
1)数据类型[] 数组名;
数组名 = new 数据类型[长度]
2)数据类型[] 数组名 = new 数据类型[长度]
3)数据类型[] 数组名 = new 数据类型[]{元素1,元素2,元素3...}
4)数据类型[] 数组名 = {元素1,元素2,元素3...}
声明时也可以把中括号放到数组名后面,例如:数据类型 数组名[] = new 数据类型[长度]
访问:
赋值和取值
数组名[下标] = 值;
有效下标范围:0~数组长度-1
访问无效下标,会导致数组下标越界异常(java.lang.ArrayIndexOutOfBoudsException)
数组的长度:length属性
数组的遍历
//普通for循环
public class TestArray2{
public static void main(String[] args){
int[] numbers = new int[5];
//赋值
numbers[0] = 111;
numbers[1] = 222;
numbers[2] = 333;
numbers[3] = 444;
numbers[4] = 555;
//遍历
for(int i = 0; i < numbers.length; i++){
System.out.println(numbers[i]);
}
}
}
//增强for循环遍历数组
for(数据类型 变量 : 数组){
//循环每次都将元素赋值给该变量
}
数组元素的默认值
public class TestArray3{
public static void main(String[] args){
/*
整型:0
浮点型:0.0
布尔类型:false
字符类型:"\u0000" (显示时显示空白)
String型:null空值
注:null不能使用,否则会造成空指针异常(java.lang.NullPointException)
*/
int[] nums = new int[4];
for(int i = 0; i < nums.length; i++){
System.out.println(nums[i]);//0
}
double[] scores = new double[4];
for(int i = 0; i < scores.length; i++){
System.out.println(scores[i]);//0.0
}
boolean[] bools = new boolean[4];
for(int i = 0; i < bools.length; i++){
System.out.println(bools[i]);//false
}
//字符默认值:\u0000 显示时显示一个空白
char[] cs = new char[3];
for(int i = 0; i < cs.length; i++) {
System.out.println(cs[i]);
}
String[] names = new String[5];
names[0] = "Tom";
names[1] = "Jack";
names[2] = "Amy";
names[3] = "marry";
for(int i = 0; i < names.length; i++){
System.out.println(names[i]);//null
}
}
}
数组的扩容
创建数组时,必须显示指定长度,在创建之后不可更改长度
扩容的思路:
创建大于原数组长度的新数组
将原数组中的元素依次复制到新数组中
复制的方式:
1)循环将原数组中所有元素逐一赋值给新数组;
2)System.arraycopy(原数组,原数组起始,新数组,新数组起始,长度);
3)java.util.Arrays.copyOf(原数组,新长度);//返回带有原值的新数组
地址的替换
数组作为引用类型之一,其变量中存储的是数组的地址
完成元素复制后,需将新数组地址赋值给原变量进行替换
public class TestCopyArray{
public static void main(String[] args){
int[] nums = new int[5];
nums[0] = 11;
nums[1] = 22;
nums[2] = 33;
nums[3] = 44;
nums[4] = 55;
//1.创建比原数组大的新数组
int[] newNums = new int[nums.length*2];
//2.复制原数组中的所有数据到新数组中
for(int i = 0; i < nums.length; i++){
newNums[i] = nums[i];
}
//遍历原数组
for(int i = 0; i < nums.length; i++){
System.out.print(nums[i] + "\t");
}
System.out.println();
//遍历新数组
for(int i = 0; i < newNums.length; i++){
System.out.print(newNums[i] + "\t");
}
}
}
public class TestCopyArray2{
public static void main(String[] args){
int[] nums = new int[5];
nums[0] = 11;
nums[1] = 22;
nums[2] = 33;
nums[3] = 44;
nums[4] = 55;
//复制
//1.创建新数组
int[] newNums = new int[nums.length*2];
//2.使用System.arraycopy(原数组,原数组起始,新数组,新数组起始,长度)
System.arraycopy(nums,0,newNums,0,nums.length);
//遍历新数组
for(int i = 0; i < newNums.length; i++){
System.out.print(newNums[i] + "\t");
}
}
}
//import java.util.Arrayss;
public class TestCopyArray3{
public static void main(String[] args){
int[] nums = new int[5];
nums[0] = 11;
nums[1] = 22;
nums[2] = 33;
nums[3] = 44;
nums[4] = 55;
//1.创建新数组,复制元素
// java.util.Arrays.copyOf(原数组,新长度)
int[] newNums = java.util.Arrays.copyOf(nums,nums.length*2);//将带有原值的新数组返回给我
//2.遍历
for(int i = 0; i < newNums.length; i++){
System.out.print(newNums[i] + "\t");
}
}
}
数组类型的参数和返回值
传递参数时,基本数据类型传递的是变量中的值;引用数据类型传递的是变量中的地址
public class TestCompare{
public static void main(String[] args){
//1.基本数据类型的传递,是“值”的传递,一方改变,不会影响另一方
//2.引用数据类型的传递,是“地址”的传递,一方改变,会影响另一方
int i = 10;
m1(i);
System.out.println(i);//10
byte b = 123;
b = m2(b);
System.out.println(b);//124
int[] nums = {1,2,3,4};
m3(nums);
System.out.println(nums[0] + "\t" + nums[1] + "\t" + nums[2] + "\t" + nums[3]);//688...
int[] numbers = {5,4,3,2,1};
m4(numbers);
System.out.println(numbers.length);//没有变
int[] arrays = {111,222,333};
arrays = m5(arrays);
System.out.println(arrays.length);//6
}
public static void m1(int n){
n = n * 2;
System.out.println(n);//20
}
public static byte m2(byte c){
c++;
return c;
}
public static void m3(int[] arr){
arr[0] = 688;
}
public static void m4(int[] arr){
int[] numbers = java.util.Arrays.copyOf(arr,arr.length*2);
}
public static int[] m5(int[] arr){
int[] newNums = new int[arr.length*2];
System.arraycopy(arr,0, newNums,0,arr.length);
return newNums;
}
}
调用数组类型返回值的方法时,方法执行后,返回的是数组的地址
排序
冒泡排序:两两相邻位置的元素相比较,如果顺序不符合,则交换两位置的元素
public class TestBubble{
public static void main(String[] args){
int[] nums = {4,3,5,2,1};
//冒泡排序:相邻的两个数值比较大小,互换位置
//轮次:数组长度 - 1
//单轮次数:(数组长度 - 1)基础上做逐级递减
//外层:length-1; 内层:length -1 -i;
for(int i = 0; i < nums.length - 1; i++){//外层循环:控制比较轮次(nums.length -1)
for(int j = 0; j < nums.length - 1 - i; j++){//内层循环:单轮当中的比较次数
if(nums[j] > nums[j + 1]){
int temp = nums[j];
nums[j] = nums[j + 1];
nums[j + 1] = temp;
}
}
for(int k = 0; k < nums.length; k++){
System.out.print(nums[k] + "\t");
}
System.out.println();
}
}
}
选择排序:固定值与其他值依次比较大小,互换位置
public class TestSelect{
public static void main(String[] args){
//选择排序
//固定值与其他值依次比较,互换位置
int[] nums = {4,3,5,2,1};
for(int i = 0; i < nums.length - 1; i++){
for(int j = i + 1; j < nums.length; j++){
if(nums[i] > nums[j]){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
for(int k = 0; k < nums.length; k++){
System.out.print(nums[k] + "\t");
}
System.out.println();
}
}
}
JDK排序:java.util.Arrays.sort(数组名); //jdk提供(升序)
public class TestJDKSort{
public static void main(String[] args){
int[] nums = {4,3,5,2,1};
java.util.Arrays.sort(nums);//默认升序
//手工完成元素的倒置
for(int i = 0; i < nums.length / 2; i++){
int temp = nums[i];
nums[i] = nums[nums.length - 1 - i];
nums[nums.length - 1 - i] = temp;
}
for(int k = 0; k < nums.length; k++){
System.out.print(nums[k] + "\t");
}
System.out.println();
}
}
可变长参数
概念:可接受多个同类型的实参,个数不限,使用方式与数组
语法:数据类型…形参名//必须定义在形参列表的最后,且只能有一个
可变长参数一般作为数组使用
public class TestChangeLength{
public static void main(String[] args){
int[] numbers = {1,2,3,4,5};
//1.支持传递数组类型的实参
// method(numbers);
//2.支持传递零散数据的实参
// method(5,6,7,8,9);//也支持0个参数
int[] newArray = expand(10,numbers);
method(newArray);
}
//函数的参数是"可变长参数"
public static void method(int... arr){
System.out.println("---method executed---");
for(int i = 0; i < arr.length; i++){
System.out.print(arr[i] + "\t");
}
System.out.println();
}
public static int[] expand(int length, int... arr){// 规则:必须在形参列表最后,且只能有一个,支持0~N个参数
int[] newNums = new int[length];
System.arraycopy(arr,0,newNums,0,arr.length);
return newNums;
}
}
二维数组
概念:
一维数组中的一维数组;数组中的元素还是数组
二维数组的内存分配
高维数组中的每一个元素保存了低维数组的地址,访问array[0]等价于访问0x0000A111
二维数组的赋值与访问
public class Test2DArray{
public static void main(String[] args){
int[][] nums = new int[3][5];
nums[0][0] = 10;
nums[0][3] = 20;
nums[1][2] = 30;
nums[1][4] = 40;
nums[2][1] = 50;
nums[2][3] = 60;
for(int i = 0; i < nums.length; i++){//外层控制行数
for(int j = 0; j < nums[i].length; j++){
System.out.print(nums[i][j] + "\t");
}
System.out.println();
}
System.out.println(nums.length);//nums变量中,所持有的地址对应的数组长度为多少
System.out.println(nums[0].length);//nums[0] 高维数组中的第一个元素所指向的低维数组的长度
//高维数组中的每一个元素,保存了低维数组的地址,访问array[0]相当于访问0x0000A001
System.out.println(nums[0]);
System.out.println(nums[1]);
System.out.println(nums[2]);
}
}
junit单元测试
定义方法:公有无参无返回的方法
添加注解:@Test
引入junit4环境:ctrl+1 -> add libary添加环境依赖
执行:run as --> junit test
如果需要断言结果时:Assert.assertEquals(期望值,实际值);
package com.qf.test;
import java.util.Arrays;
import org.junit.Assert;
import org.junit.Test;
import com.qf.demo.Demo1;
public class Test1 {
@Test
public void test1(){
int[] nums = {43,23,66,56,7,89,32};
System.out.println("最小值是:" + Demo1.minNum(nums));
//使用现有的工具类
System.out.println(Arrays.toString(nums));
//如果需要断言结果时:Assert.arrertEquals(期望值,实际值);
Assert.assertEquals(6, Demo1.minNum(nums));
}
}
debug运行
打断点:在需要监控的行首打断点
运行模式:debug模式,debug as --> junit test
F5 :进入方法
F6:执行下一句
F7:跳出方法
F8:执行下一个断点,如果没有下一个段断点,则执行结束
面向对象
面向对象思想:
面向对象思想:一切客观存在的事物都是对象,万物皆对象
任何对象,一定具有自己的特征和行为
特征:称为属性,一般为名词,代表对象有什么
行为:称为方法,一般为动词,代表对象能做什么
类的抽取:在现实中的一组相同或类似的对象中,提取共性的特征和行为,保存在程序中的模板里
类的定义
类由属性和方法构成
属性:field、全局变量、字段、实例成员属性;
位置:定义在类内,方法外;
语法:数据类型 属性;
注:都有默认值(0/0.0/false/null)
方法:method、实例方法
public 返回类型 方法名(参数列表){//方法体}
对象的创建
创建对象:实例化过程;类-抽象概念;对象-具体概念;一个类可以创建无数个对象;
使用new关键字创建对象---在内存中开辟内存空间;由引用变量指向内存空间;
例:User u=new User(); 此时u就是引用变量,new User()代表内存中开辟空间;u存的是地址
类与对象的关系
类:定义了对象应具有的特征和行为,类是对象的模板
对象:拥有多个特征和行为的实体,对象是类的实例
实例变量与局部变量的区别
局部变量 全局变量(实例变量)
定义位置 方法或方法内的结构当中 类的内部,方法的外部
默认值 无默认值 字面值(与数组相同)
使用范围 从定义行到包含其结构结束 本类有效
命名冲突 不允许与局部变量重名 不允许与实例变量重名,可与局部变量重名;局部变量优先
public class Demo2 {
public static void main(String[] args) {
int[] a = {1,2,3,4,5};
expand(a);
changeArray(a);
printArray(a); // 10 2 3 4 5
}
public static void expand(int[] a){ //a 是局部变量
int[] newArray = new int[a.length * 2];
System.arraycopy(a, 0, newArray, 0, a.length);
a = newArray; //局部变量在方法结束后销毁了
}
public static void changeArray(int[] a){
a[0] = 10;
}
public static void printArray(int[] a){
for(int i = 0; i<a.length; i++){
System.out.print(a[i] + "\t");
}
System.out.println();
}
}
方法的重载
方法重载: 在一个类中定义多个相同名称的方法
要求:
方法名称相同
参数列表不同(类型,个数,顺序)
与访问修饰符,返回值无关
好处:
屏蔽用户的使用差异,方便
构造方法(构造器)
构造方法:类中的特殊方法,主要用于创建对象
特点:
名称与类名完全相同(包括大小写)
没有返回值类型
创建对象时(new对象时),触发构造方法的调用,不可通过句点手工调用
【注】如果没有在类中显示定义构造方法,则编译器默认提供无参构造方法
如果已经手动添加过有参构造方法,则无参构造方法不再默认提供,结合需求,自行添加(建议:手动添加 无参构造方法)
对象创建过程
Student student=new Student(); //调用无参构造器,并且student引用变量指向了内存地址;
new Student() 触发对象的创建
对象的创建过程:
内存中开辟对象空间
为各个属性赋予初始值
执行构造方法中的代码
[将对象的地址赋值给变量]
构造方法的重载
构造方法也可以重载,遵循重载的原则
class Student{
String age;
Student(){
}
Student(String name){
//重载的构造方法
//给同类型属性赋值
}
}
this关键字
this 代表" 当前实例 ", 既是模板中的当前对象,模板服务于哪个对象,this就指向那个对象
this关键字:
第一种用法:调用本类中的实例属性,实例方法,例如:this.name,this.run();
第二种用法:调用本类中的其他构造方法,如:this(),this(实参)
注:this 用于区分局部变量与全局变量同名的情况;此时全局变量前的this不能省;
this([实参]) 必须在构造方法首行,仅可在构造方法中,不能在普通方法中
public class T{
int age=10;
public void test(){
int age=20;
Sytem.out.println(age);//20 局部变量优先
Sytem.out.println(this.age);//10 用this指代的是全局变量
}
}
面向对象三大特性
封装
概念:
尽可能隐藏对象的内部实现细节,控制对象的修改及访问的权限
访问修饰符
private可将属性修饰为私有,可达到本类可见的效果
公共访问方法
get/set方法是外界访问私有属性的唯一渠道,方法内部可对数据进行过滤
(可在set方法中添加过滤条件)
提供公共访问方法,以保证数据正常录入。
public class TestEncapsulation {
public static void main(String[] args) {
Student s1 = new Student();
s1.name = "Tom";
s1.setAge(20);//调用方法完成赋值操作
s1.sex = "male";
s1.score = 99.0;
System.out.println(s1.getAge());//调用方法完成取值操作
}
}
class Student{
String name;//属性也称字段
private int age;//私有属性,本类可见,其他位置不可见
String sex;
double score;
public Student() {}
/**
* 为age 属性赋值的方法
* */
public void setAge(int age) {
if(age > 0 && age < 153) {//合法区间
this.age = age;
}else {
this.age = 18;//录入非法数据时的默认值
}
}
/**
* 为age属性取值的方法
*
* */
public int getAge() {
return this.age;
}
}
退出for嵌套switch
//当for中嵌套switch语句,可直接退出的方法
outer:for(int i = 0; i < 5; i++) {
System.out.println("当前值:" + i);
switch(i) {
case 3:
System.out.println("满足条件,退出");
break outer;
}
}
继承
程序中的继承
程序中的继承是类与类之间的特征和行为的一种赠与与获得
类与类之间必须满足“is a”的关系
父类的选择
父类的选择:功能越精细,重合点越多,越接近直接父类
父类的抽象
父类的抽象:根据程序需要使用到的多个具体类,进行共性的抽取,进而定义父类
在一组相同或类似的类中,抽取出共性的特征和行为,定义在父类中,实现重用
语法
class 子类 extends 父类{}//定义子类时,显示继承父类
应用
产生继承关系后,子类可以使用父类中的属性和方法,也可定义子类独有的属性和方法
好处
既提高代码复用性,又提高代码的可扩展性
特点
Java 为单继承,一个子类只能有一个直接父类,但可以多级继承,属性和方法逐级叠加
不可继承
父类的构造方法,子类不可继承
父类有private修饰的属性和方法,不可继承(不可见)
父子类由default修饰的属性和方法,子类不在同一包时不可继承(不可见)
子类可以**调用**父类的属性和方法、构造器;使用的是super关键字
子类**继承**父类的属性和方法,**不能继承**构造方法;
子类还可以将父类方法进行**重写**
子类继承父类的非私有成员,至于调用时需要看属性是默认还是protected
子类构造器中第一句话**默认是super()**,如果父类没有无参构造器,此时需要由子类调用现在的父类带参构造器(根据实参列表去匹配)
Super关键字
当父类的属性或方法存在重名(属性遮蔽,方法覆盖)时,可用super加以区分
super关键字
第一种用法:在子类中,可以通过“super.”的形式访问父类的属性和方法,可以解决一定的属性遮蔽,方法 覆盖后的父类成员调用问题
第二种用法:super()表示在子类构造方法的首行,调用父类的无参构造方法(默认隐式存在)
注:子类构造器中第一句话默认是super();
如果父类中没有无参构造器,则需要在子类构造器中第一句话调用现有构造器
;
class Father{
int age=20;
public void test(){
}
public Father(int age){
}
}
class Son extends Father{
int age=10;
public Son(){
super(age);
}
public void test(){
}
public void test1(){
System.out.println(age);//子类属性
System.out.println(this.age);//子类属性
System.out.println(super.age);//父类属性
}
public void test2(){
int age=5;
System.out.println(age);//局部变性
System.out.println(this.age);//子类属性
System.out.println(super.age);//父类属性
}
public void test3(){
test();
}
}
this与super
this 和 super 使用在方法中时,都要求在首行
当子类构造中使用了this() 或this(实参) ,即不再同时书写super() 或super(实参),会由this()指向的构造方法完成super()的调用
访问修饰符
private:只能限于本类内使用
默认:限于同包内使用
protected:限于跨包后的子类中使用
public:没有限制,处处可用;
注:修饰类时只能使用public和默认;
四个修饰符都可以应用到属性、方法、构造方法的定义中
方法的重写
方法的覆盖/重写(override):
当父类提供的方法无法满足子类需求时,可以在子类中定义与父类相同的方法进行覆盖
原则:
方法名称,参数列表,返回值类型必须与父类相同
访问修饰符可与父类相同或比父类更宽泛
执行机制:子类覆盖父类方法后,优先执行子类覆盖后的版本
public class TestOverride {
public static void main(String[] args) {
Dog dog1 = new Dog();
dog1.eat();//覆盖后,优先执行子类覆盖后的版本
dog1.sleep();
Cat cat1 = new Cat();
cat1.eat();
}
}
class Animal{
String breed;
int age;
String sex;
public void eat() {
System.out.println("动物在吃...");
}
void sleep() {
System.out.println("动物在睡...");
}
}
class Dog extends Animal{
String furColor;
//在子类中定义与父类相同的方法进行覆盖
public void eat() {
System.out.println("狗在吃骨头");
}
public void sleep() {
System.out.println("狗在睡...");
}
public void run() {}
}
class Cat extends Animal{
// public void eat() {}
}
继承关系下的对象创建
继承关系下,构建子类对象时,会先构建父类对象;
由"父类共性" + '“子类独有” 组合成一个完整的子类对象
继承关系下的对象创建流程
1.构建父类对象
2.初始化自身属性
3.执行自身构造方法中的逻辑代码
public class TestCreateSort {
public static void main(String[] args) {
new C();//field1 field2 field3
}
}
class A{//extends Object
String field1 = "A的属性";//1.初始化属性
public A() {
System.out.println("A的构造方法被执行 " + field1);//2.执行构造方法中的逻辑代码
}
}
class B extends A{
String field2 = "B的属性";//2.初始化属性
public B() {
super();//1.调用父类的构造方法(默认调用父类无参的构造方法)隐式存在
System.out.println("B的构造方法被执行 " + field2);//3.执行构造方法中的逻辑代码
}
}
class C extends B{
String field3 = "C的属性";//2.初始化属性
public C() {
super();//1.调用父类的构造方法(默认调用父类无参的构造方法)隐式存在
System.out.println("C的构造方法被执行 " + field3);//3.执行构造方法中的逻辑代码
}
}
多态
概念
一种事物多种不同的表现形态,
父类引用指向子类对象,从而产生多种形态
构成多态的前提:二者具有直接或间接的继承关系时,父类引用指向子类对象,即形成多态
父类引用仅可调用父类所声明的属性和方法,不可调用子类独有的属性和方法
多态中的方法重写
思考:如果子类重写了父类中的方法,以父类类型引用调用此方法时,优先执行父类中的方法还是子类中的方法?
实际运行过程中,依旧遵循重写原则,如果子类重写了父类中的方法,执行子类中重写后的方法,否则执行父类中的方法
多态中的成员访问特点
1)成员变量----->编译看左边,运行看左边 Fu f = new Zi() ; 向上转型
输出:f.num
2)成员方法----->编译看左边,运行看右边 ----存在方法重写
输出:f.show() ;
3)静态的成员方法----->静态方法算不上方法重写,静态的东西是直接和类相关的,随着类的加载而加载
优先于对象存在! 推荐访问:变量/方法---(静态)----类名.变量名/方法名()
4)构造方法------->对数据进行初始化
存在继承关系,分层初始化---- 先让父类进行初始化,然后再是子类进行初始化!
多态的应用
方法重载可以解决接受不同对象参数的问题,但缺点也比较明显。
首先,随着子类的增加,类需要继续提供大量的方法重载,多次修改并重新编译源文件,其次,每一个方法与某一种具体类型形成了密不可分的关系,耦合太高
场景一:使用父类作为方法形参实现多态,使方法参数的类型更为广泛
场景二:使用父类作为方法返回值实现多态,使方法可以返回不同子类对象
作用:屏蔽子类间的差异,灵活,耦合度低
多态的好处和弊端
多态的好处:提高了代码的维护性,提高了代码的扩展性。
弊端:不能使用子类中的特有功能
解决方案:
1)具体类(子类) 创建具体类 ----
Cat c = new Cat() ;
内存在中---又在创建一个新的对象,开销资源太大,占用内存空间!
2)将父类的引用强制子类的引用(符号强制类型转换的语法)
Cat c2 = (Cat)a ; (推荐这种方式)
向上转型(装箱)
向上转型(装箱):父类引用中保存真实子类对象成为向上转型(即多态核心概念)
子类转为父类---自动转换
注:仅可调用父类中所声明的属性和方法
向下转型(拆箱)
向下转型(拆箱) :将父类引用中的真实子类对象强转回子类本身类型,称为向下转型
父类转为子类---强制转换
注:只有转换回子类真实类型,才可调用子类独有的属性和方法
类转换异常
向下转型时,如果父类引用中的子类对象的类型与目标类型不匹配,则会发生类型异常 java.lang.ClassCastException
instanceof关键字
向下转型时应判断引用中的对象的真实类型,保证类型转换的正确性
语法:父类引用 instanceof 类型 //返回boolean 类型结果
含义:判断引用变量是否是后者类型的一个实例,返回boolean;
package com.qf.day14.t3.polymorphic;
/**
* 多态两种应用场景:
* 场景一:使用父类作为方法形参,实现多态,使方法类型的参数更为宽泛
*
* 场景二: 使用父类作为方法返回值实现多态,使方法可以返回不同子类对象
* 多态的作用:
* 屏蔽子类间的差异,灵活,耦合度低
*
* */
public class TestApplyPolymorphic {
/**
* 向上转型(装箱):父类引用中保存真实子类对象成为向上转型(即多态核心概念)
*
* 注:仅可调用父类中所声明的属性和方法
*
* 向下转型(拆箱) :将父类引用中的真实子类对象强转回子类本身类型,称为向下转型
*
* 注:只有转换回子类真实类型,才可调用子类独有的属性和方法
*
* 向下转型时,如果父类引用中的子类对象的类型与目标类型不匹配,则会发生类型异常 java.lang.ClassCastException
*/
public static void main(String[] args) {
Car car = new Car();//自身类型引用指向自身类型对象
car.type = "小汽车";
car.speed = 120;
car.price = 300000;
car.brand = "BMW";//独有属性
//car.run();
Vehicle veh = new Car();//父类引用指向子类对象
veh.type = "小汽车";
veh.speed = 100;
veh.price = 200000;
//veh.run();//多态场景下,父类引用调用方法,如果被子类覆盖过,优先执行子类覆盖后的版本
Bus bus = new Bus();
bus.type = "公交车";
bus.speed = 40;
bus.price = 1000000.0;
bus.seatNum = 24;
Bicycle bic = new Bicycle();
bic.type = "自行车";
bic.price = 2000.0;
bic.speed = 20;
bic.color = "红色";
Employee emp = new Employee();
emp.name = "Tom";
emp.goHome(bic);
emp.goHome(car);
emp.goHome(bus);
//调用员工买车的方法(新)
Employee emp2 = new Employee();
emp2.name = "jack";
Vehicle myVehicle = emp2.buyVehicle(50);
if(myVehicle != null) {
emp2.goHome(myVehicle);
}else {
System.out.println("钱不够,没买成...-_-");
}
/**
* 向下转型时应判断引用中的对象的真实类型,保证类型转换的正确性
*
* instanceof 关键字
*
* 语法:父类引用 instanceof 类型 //返回boolean 类型结果
*/
//拆箱:将父类引用中的真实子类对象,转回其本身类型
if(myVehicle instanceof Car) {//判断myVehicle引用中的真实子类对象,是否为Car类型
Car myCar = (Car)myVehicle;
System.out.println(myCar.brand);
}else if(myVehicle instanceof Bus) {
Bus myBus = (Bus)myVehicle;
System.out.println(myBus.seatNum);
}else if(myVehicle instanceof Bicycle) {
Bicycle myBicycle = (Bicycle)myVehicle;
System.out.println(myBicycle.color);
}
}
}
//员工
class Employee{
String name;
//回家(父类类型作为方法形参,实现多态)
public void goHome(Vehicle veh) {
System.out.print(name + "正在乘坐");
veh.run();
}
/*public void goHome(Bus bus) {
System.out.print(name + "正在乘坐");
bus.run();
}
public void goHome(Bicycle bic) {//藕合高(模块与模块之间的关联程度)
System.out.print(name + "正在乘坐");
bic.run();
}
public void goHome(Car car) {
System.out.print(name + "正在乘坐");
car.run();
}*/
//员工买车(新)
public Vehicle buyVehicle(int money) {//单位:万
Vehicle veh = null;//方法返回值
if(money > 100) {
Bus bus = new Bus();
bus.type = "公交车";
bus.speed = 50;
bus.price = 1100000;
bus.seatNum = 24;
veh = bus;
}else if(money > 30) {
Car car = new Car();
car.type = "小汽车";
car.speed = 125;
car.price = 301000;
car.brand = "Benz";
veh = car;
}else if(money > 1){
Bicycle bic = new Bicycle();
bic.type = "自行车";
bic.speed = 25;
bic.color = "红色";
bic.price = 2100;
veh = bic;
}
return veh;
}
}
//交通工具类
class Vehicle{
String type;
int speed;
double price;
public void run() {
System.out.println("一辆价值" + price + "RMB的" + type + "正以" + speed + "km/h的速度前进...");
}
}
class Car extends Vehicle{
String brand;
public void run() {
System.out.println("一辆价值" + price + "RMB的" + brand + "品牌的" + type + "正以" + speed + "km/h的速度前进...");
}
}
class Bus extends Vehicle{
int seatNum;
public void run() {
System.out.println("一辆价值" + price + "RMB的" + seatNum + "个座位的" + type + "正以" + speed + "km/h的速度前进...");
}
}
class Bicycle extends Vehicle{
String color;
public void run() {
System.out.println("一辆价值" + price + "RMB的" + color + "的" + type + "正以" + speed + "km/h的速度前进...");
}
}
三个修饰符
abstract
abstract 的意思:抽象的,似是而非的,像却又不是,具备某种对象的特征,但不完整
抽象类
abstract 修饰类,意为 “不够完整,不够具体,不该独立存在”
抽象类的作用:
1.可被子类继承,提供共性的属性和方法
2.可声明为引用,可更纯粹的使用多态
抽象类构造方法的作用:
构建子类对象时,先构建父类对象(父类共性 + 子类独有 = 完整的子类对象)
abstract 修饰类:不能new对象,但可以声明引用
抽象方法
abstract 修饰方法:只有方法声明,没有方法实现(需包含在抽象类中)
注:抽象类中不一定有抽象方法,但有抽象方法的类一定是抽象类
子类继承抽象类后,必须覆盖父类所有的抽象方法,否则子类还是抽象类
public class TestAbstract {
public static void main(String[] args) {
// new Animal();//不该被创建成实例独立存在
Animal a = new Dog();//构建子类对象时,先构建父类对象(父类共性 + 子类独有 = 完整的子类对象)
a.eat();
}
}
abstract class Animal{//使用abstract修饰为抽象类,不能new对象(不能独立存在)含义:不够完整,不够具体,不该独立存在
String breed;
int age;
String sex;
public Animal() {
System.out.println("---Animal() Executed---");
}
//抽象方法(只有方法声明 ,没有方法实现,意为不够完整不够具体),强制子类必须实现该抽象方法,提供完整的具体的版本
public abstract void eat();
public void sleep() {
System.out.println("动物在睡...");
}
}
class Dog extends Animal{//抽象类 可以被子类继承,可以为子类提供共性
public Dog() {
super();
System.out.println("---Dog() Executed---");
}
@Override
public void eat() {
System.out.println("狗在吃骨头...");
}
}
static
静态和实例的区别
实例属性是每个对象各自持有的独立空间(多份) ,对象单方面的修改,不会影响其他对象
静态属性是整个类共同持有的共享空间(一份) ,任何对象修改,都会影响其他对象
Demo d=new Demo();
d在栈内存区;
new Demo()在堆内存区;并且每创建一次对象,就会在堆内存区中有一个地址;这个地址就是该对象的属性和方法等成员
类内的static属性方法在方法区;属于类,而不是某一个对象,所有对象共享该区的属性和方法
静态的概念
static 可以修饰属性和方法,即为静态属性(类属性)和静态方法(类方法)
静态成员是全类所有对象共享的,全类只有一份,不因创建多个对象而改变
不必创建对象,也可通过类名,直接访问静态成员
经验:访问静态属性和方法时,可直接通过“类名.属性名” 和“类名.方法名”(推荐)
静态的特点
I. 静态方法允许直接访问静态成员
II. 静态方法不能直接访问非静态成员
III. 静态方法不允许使用this和super关键字
IV. 静态方法可以继承,不能覆盖,没有多态
类加载
概念
JVM首次使用某个类时,将该类的.class文件加载到内存中,进行保存
加载时机
1).创建对象
2).创建子类对象
3).调用静态的属性和方法
4).class.forName("全限定名"),
动态代码块
创建对象时,触发动态代码块的执行
执行地位:初始化属性之后,构造方法之前
作用:可为实例属性赋值,或必要的初始行为
静态代码块
类加载时触发代码块的执行(仅一次)
执行地位:静态属性初始化之后
作用:可为静态属性赋值,或必要的初始行为
final
最终类
final修饰类,该类不能被继承
如:String,Math,System均为final修饰的类,不能被继承
最终方法
final修饰方法:此方法不能被覆盖
意为最终方法,不支持子类以覆盖的形式修改
最终常量
final修饰变量:此变量值不能被修改--常量 (无初始值,只允许赋值一次)
1).局部变量:显示初始化
2).实例常量不在提供默认值,必须手动赋予初始值
实例常量的赋值:显示初始化,动态代码块,构造方法
要求:
a).实例常量赋值:在构造方法完成之前,为实例常量赋值即可
b).如果在构造方法中为实例常量赋值,必须保证所有的构造方法度可正确赋值
3).静态常量不在提供默认值,必须手动赋予初始值
静态常量赋值:显示初始化,静态代码块
要求:
a).静态常量赋值:在类加载完成之前(通过类名调用之前),为静态常量赋值即可
不同常量类型的特点:
基本数据类型常量:值不可变
引用数据类型常量:地址不可变
properties文件的加载
创建java的Properties对象: Properties p=new Properties();
加载后缀为properties的文件:p.load(类名.class.getClassLoader().gerResourceAsStream("properties文件"));
读取文件中的名称对应的值:p.getProperty(properties文件中的)