对象在内存中存在的形式
- JDK1.8及以后,方法区又从堆内存中剥离出来了,但实现方式与之前的永久代不同,这时的方法区被叫做元空间,常量池就存储在元空间。
属性
成员变量 = 属性 = 字段
注意事项及细节
-
属性的定义语法同变量,示例:访问修饰福 属性类型 属性名;
-
属性的定义类型可以为任意类型,包含基本类型或引用类型
-
属性不赋值,有默认值,规则和数组一致。
int 0, short 0, byte 0, long 0, float 0, double 0.0, char \u0000, boolean false, String null
创建Person对象
- p1是对象名(对象引用)
- new Person()创建对象空间 (数据) 才是真正的对象
public class demo01 {
public static void main(String[] args) {
//创建Person对象
// p1是对象名(对象引用)
//new Person()创建对象空间 (数据) 才是真正的对象
Person p1 = new Person();
}
}
class Person{
int age;
String name;
double sal;
boolean isPass;
}
类与对象的内存分配机制
java 内存结构分析
- 栈: 一般存放基本数据类型(局部变量)
- 堆: 存放对象(Cat cat,数组等)
- 方法区:常量池(常量,比如字符串)类加载信息
- 示意图 [Cat(name,age,price)]
java创建对象的流程简单分析
Person p = new Person();
p.name = "jack";
p.age = 10;
- 先加载Person类信息(属性和方法信息,只会加载一次)
- 在堆中分配空间,进行默认初始化(看规则)
- 把地址赋给 p,p就指向对象
- 进行指定初始化,比如p.name = “jack” p.age = 10
练习
方法的调用机制
成员方法的好处
- 提高代码复用性
- 可以将实现的细节封装起来,然后供其他用户来调用即可
方法使用细节
- 一个方法最多有一个返回值,思考:如何返回多个结果:返回数组
public class demo02 {
public static void main(String[] args) {
A1 a = new A1();
int[] res = a.getSumAndSub(1,2);
System.out.println(res[0]);//3
System.out.println(res[1]);//-1
}
}
class A1{
public int[] getSumAndSub(int a,int b){
int[] resArr = new int[2];
resArr[0] = a + b;
resArr[1] = a - b;
return resArr;
}
}
-
返回类型可以为任意类型,包含基本类型或引用类型(数组,对象)
-
如果方法要求有返回数据类型,则方法体重最后的执行语句必须为return值;而且要求返回值类型必须和return的值类型一致或兼容
-
如果方法是void,则方法体中可以没有return语句,或者只写return。
-
方法名一定要有含义,见名知意
成员方法调用注意事项和细节
-
同一个类中的方法调用:直接调用即可。比如print(参数);
-
跨类中的方法A类调用B类方法:需要通过对象名调用。
-
跨类的方法调用和方法的访问修饰符相关
练习
- 编写类C,有一个方法:判断一个数是奇数还是偶数
public class HomeWork {
public static void main(String[] args) {
C c = new C();
if(c.isodd(1)){
System.out.println("是奇数");
}else{
System.out.println("是偶数");
}
}
}
class C{
public boolean isodd(int num){
return num % 2 != 0 ;
}
}
- 编写方法打印4行4列的符号#
public class HomeWork {
public static void main(String[] args) {
C c = new C();
c.print(4,4,'#');
}
}
class C{
public void print(int row, int col, char c){
for (int i = 0; i<row; i++){
for (int j = 0; j < col; j++) {
System.out.print(c);
}
System.out.println();
}
}
}
/*
####
####
####
####
方法传参机制(非常重要)
parameter:参数
public class MethodParameter01 {
public static void main(String[] args) {
int a = 10;
int b = 20;
D d = new D();
d.swap(a,b);
System.out.println("a="+ a + "\tb= "+ b); //a=10,b=20 swap方法创建了另一个swap栈不会影响main栈
}
}
class D{
public void swap(int a,int b){
System.out.println("交换前"+ a + "" + b); //a=10 b=,20
int temp = a;
a = b;
b = temp;
System.out.println("交换后a=" + a + "\tb=" + b);// a=20, b=10
}
}
引用数据类型的传参机制
引用了性传递的是地址(传递也是值,但是值是地址),可以通过形参影响实参。
public class MethodParameter02 {
public static void main(String[] args) {
E e =new E();
int[] arr = {1,2,3};
e.test100(arr);
System.out.println("main的arr数组:");
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i]+"\t");
}
System.out.println();
}
}
class E{
public void test100(int[] arr){
arr[0] = 200;
System.out.println("test100的数组:");
for (int i = 0; i <arr.length ; i++) {
System.out.print(arr[i] + "\t");
}
System.out.println();
}
}
/*
test100的数组:
200 2 3
main的arr数组:
200 2 3
*/
测试题
-
如果test200执行的是p=null。输出的结果是10
p = null 只是指向了空地址,并没有影响对象,输出的结果是10
-
如果test200执行的是P = new Person();…输出的结果是10
克隆对象
public class MethodParameter03 {
public static void main(String[] args) {
Person p = new Person();
p.name = "milan";
p.age = 10;
MyTools tools = new MyTools();
Person p2 = tools.copyPerson(p);
System.out.println(p.age + p.name );
System.out.println(p2.age + p2.name );
System.out.println(p == p2);
}
}
class Person{
String name;
int age;
}
class MyTools{
public Person copyPerson(Person p){
Person p2 = new Person();
p2.name = p.name;
p2.age = p.age;
return p2;
}
}
递归
递归执行机制
public class Recursion01 {
public static void main(String[] args) {
T t = new T();
t.test(4);
}
}
class T{
public void test(int n){
if(n > 2){
test(n - 1);
}
System.out.println("n=" +n);
}
}
//输出结果为
n=2
n=3
n=4
当输出n=2后开始返回,
阶乘(factorial)
public class Recursion01 {
public static void main(String[] args) {
T t = new T();
int res = t.factorial(5);
System.out.println(res);
}
}
class T{
public int factorial(int n){
if(n == 1){
return 1;
}else{
return factorial(n-1) * n;
}
}
}
/*
结果
120
分析
递归重要规则
- 执行一个方法时,就创建一个新的受保护的独立空间(栈空间)
- 方法的局部变量是独立的,不会相互影响,比如n变量
- 如果方法中使用的是引用类型变量(比如数组,对象),就会共享该引用类型的数据
- 递归必须向退出递归的条件逼近,否则就是无限递归,出现StackOverflowError,栈溢出:死龟了)
- 当一个方法执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回给谁,同时当方法执行完毕或者返回时,该方法也就执行完毕。
斐波那契
public class Recursion01 {
public static void main(String[] args) {
T t = new T();
int n = 7;
int res = t.fibonacci(n);
if(res != -1){
System.out.println("当n=" + n + "对应的斐波那契数="+ res);
}
}
}
class T{
/*
请使用递归的方式求出斐波那契数 1,1,2,3,5,8,13...给你一个整数 n,求出它的值是多
思路分析
1. 当 n = 1 斐波那契数 是 1
2. 当 n = 2 斐波那契数 是 1
3. 当 n >= 3 斐波那契数 是前两个数的和
4. 这里就是一个递归的思路
*/
public int fibonacci(int n){
if(n >= 1) {
if (n == 1 || n == 2) {
return 1;
} else {
return fibonacci(n - 1) + fibonacci(n - 2);
}
}else {
System.out.println("输入n大于等于1");
return -1;
}
}
}
/*结果
当n=7对应的斐波那契数13
猴子吃桃
public class Recursion01 {
public static void main(String[] args) {
T t = new T();
int day = 8;
int peachNum = t.peach(day);
if(peachNum != -1){//如果peach=-1就是输入有问题,所以需要不等于-1
System.out.println(peachNum);
}
}
}
class T{
/*
猴子吃桃子问题:有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!
以后每天猴子都吃其中的一半,然后再多吃一个。当到第 10 天时,
想再吃时(即还没吃),发现只有 1 个桃子了。问题:最初共多少个桃子?
思路分析 逆推
1. day = 10 时 有 1 个桃子
2. day = 9 时 有 (day10 + 1) * 2 = 4
3. day = 8 时 有 (day9 + 1) * 2 = 10
4. 规律就是 前一天的桃子 = (后一天的桃子 + 1) *2
5. 递归
*/
public int peach(int day){
if(day == 10){
return 1;
}else if(day >= 1 && day <= 9){
return (peach(day+1) +1) * 2;
}else{
System.out.println("day在1-10");
return -1;
}
}
}
老鼠出迷宫
public class maze {
public static void main(String[] args) {
//2.设定map数组元素值:0表示可以走,1表示障碍物
int[][] map = new int[8][7];
//3. 将最上面一行和最下面一行,全部设置为1
for (int i =0; i < 7; i++){
map[0][i] = 1;
map[7][i] = 1;
}
//4. 将最左边和最右边设置为1
for (int i =0; i < 7; i++){
map[i][0] = 1;
map[i][6] = 1;
}
map[3][1] = 1;
map[3][2] = 1;
//输出地图
for (int i = 0; i <map.length ; i++) {
for (int j = 0; j < map[i].length ; j++) {
System.out.print(map[i][j]+ " ");
}
System.out.println();
}
//
T1 t1 = new T1();
t1.findWay(map,1,1);
System.out.println("\n=======找路情况如下======");
for (int i = 0; i <map.length ; i++) {
for (int j = 0; j < map[i].length ; j++) {
System.out.print(map[i][j]+ " ");
}
System.out.println();
}
}
}
class T1{
//使用递归回溯的思想来解决老鼠出迷宫问题
//分析
//1. findWay方法就是专门来住熬出迷宫的路径
//2. 如果找到返回true
//3. map就是二维数组,即迷宫
//4. i,j 就是老鼠的位置,初始化的位置为(1,1)
//5. 因为我们是递归的找路,所以我先规定 map 数组的各个值的含义
// 0 表示可以走 1 表示障碍物 2 表示可以走 3 表示走过,但是走不通是死路
//6. 当 map[6][5] =2 就说明找到通路,就可以结束,否则就继续找.
//7. 先确定老鼠找路策略 下->右->上->左
public boolean findWay(int[][] map, int i,int j){
if(map[6][5] == 2){//说明已经找到
return true;
}else{
if(map[i][j] == 0){//位置等于0,表示可以走
//我们假定可以走通
map[i][j] = 2;
//使用招录策略,来确定该位置是否真的可以走通
//下 右 左 上
if(findWay(map,i+1,j)){//先走下
return true;
}else if(findWay(map, i, j+1)){//右
return true;
}else if(findWay(map, i-1, j)){//上
return true;
}else if(findWay(map, i, j-1)){//左
return true;
}else{
map[i][j] = 3;
return false;
}
}else{
return false;
}
}
}
}
//输出结果
=======找路情况如下======
1 1 1 1 1 1 1
1 2 0 0 0 0 1
1 2 2 2 0 0 1
1 1 1 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 0 0 1
1 0 0 2 2 2 1
1 1 1 1 1 1 1
汉诺塔(不会)
public class HanoiTower {
public static void main(String[] args) {
A a = new A();
a.move(5,'A','B','C');
}
}
class A{
// 方法
//num表示要移动个数,a,b,c,分别表示A塔,B塔,C塔
public void move(int num,char a,char b, char c){
//如果只有一个盘 num = 1
if(num == 1){
System.out.println(a + "->" + c);
}else {
//如果多个盘,可以看成连个,最下面和它上面的所有盘
//1. 先移动上面所有盘到b,借助c
move(num - 1, a, c, b);
//2.把最下面的这个盘移动到c
System.out.println(a + "->" + c);
move(num - 1, b ,a, c);
}
}
}
八皇后
重载
可变参数
public class VarScopeDetail {
public static void main(String[] args) {
HspMethod m = new HspMethod();
System.out.println(m.sun(1,2,10));//13
System.out.println(m.sun(1,19,10));//30
}
}
class HspMethod{
//1. int... 表示接受的是可变参数,类型是int,即可以接收多个int(0-多)
//2. 使用可变参数时,可以当做数组来使用,即nums 可以当做数组
//3. 遍历nums求和
public int sun (int... nums){
// System.out.println("接受参数的个数=" + nums.length);
int res = 0;
for (int i = 0; i < nums.length; i++) {
res += nums[i];
}
return res;
}
}
注意事项
- 可变参数的实参可以是0个或任意多个
- 可变参数的实参可以为数组
public class VarScopeDetail {
public static void main(String[] args) {
int[] arr= {1,12,2};
T t = new T();
t.f1(arr);
}
}
class T{
public void f1(int... nums){
System.out.println("长度"+ nums.length);
}
}
-
可变参数的本质就是数组
-
可变参数可以和普通类型的参数一起放在形参列表,但必须保证可变参数在最后
public void f1( String name, int... nums){
}
- 一个形参列表中只能出现一个可变参数
下面是错误的示例
作用域
- java编程中,主要的变量就是属性(成员变量)和局部变量
- 我们说的局部变量一般指在成员方法中定义的变量
- java中作用域的分类
全局变量:也就是属性,作用域整个类体,cat类:cry方法使用属性
局部变量:除了属性之外的其他变量,作用域为定义它的代码块中。 - 全局变量(属性)可以不赋值,直接使用,因为有默认值 ,局部变量必须赋值后,才能使用,因为没有默认值
class Cat{
//全局变量(属性)可以不赋值,直接使用,因为有默认值,局部变量必须赋值后,才能使用,因为没有默认值
double weight;//默认0.0
public void cry(){
//局部变量必须赋值后才能使用
int num;
String name = "jack";
System.out.println(num);//报错
System.out.println(name);
System.out.println(weight);
}
}
注意细节
- 属性和成员变量可以重名,访问时遵循就近原则
public class VarScopeDetail {
public static void main(String[] args) {
Person1 person1 = new Person1();
person1.say();
}
}
class Person1{
String name = "jack";
public void say(){
String name = "king";
System.out.println("say() name =" + name);//say() name =king
}
}
/*输出结果
say() name =king
-
在同一个作用域中,比如在同一个成员方法中,两个局部变量,不能重名
-
属性生命周期较长,伴随着对象的创建而创建,伴随着对象的销毁而销毁。局部变量,生命周期较短,伴随着它的代码块的执行而创建,伴随着代码块的结束而销毁,即在以此方法调用过程中。
-
作用域范围不同
- 全局变量/属性:可以被本类使用,或其他类使用(通过对象调用)
- 局部变量:只能在本类中对应的方法中使用
public class VarScopeDetail {
public static void main(String[] args) {
Person1 p1 = new Person1();
T t = new T();
t.test();//jack
t.test02(p1);//jack
}
}
class T{
public void test(){
Person1 p1 = new Person1();
System.out.println(p1.name);
}
public void test02(Person1 p){
System.out.println(p.name);
}
}
class Person1{
String name = "jack";
}
- 修饰符不同
- 全局变量/属性:可以加修饰符(public private protected …)
- 局部变量不可以加修饰符
构造器
说明
-
构造器的修饰符可以默认,也可以是public, protected, private
-
构造器没有返回值
-
方法名和类名字必须一样
-
参数列表和成员方法一样的规则
-
构造器的调用,由系统完成
基本介绍
-
方法名和类名相同
-
没有返回值
-
在创建对象的时候,系统会自动调用该类的构造器完成对象的初始化
public class Constructor01 {
public static void main(String[] args) {
Person p = new Person("smith",12);
System.out.println("p对象的name="+p.name);
System.out.println("p对象的age="+p.age);
}
}
class Person{
String name;
int age;
public Person(String pName, int pAge){
System.out.println("构造器被调用 完成对象的属性初始化");
name = pName;
age = pAge;
}
}
/*
构造器被调用 完成对象的属性初始化
p对象的name=smith
p对象的age=12
使用细节
-
一个类可以定义多个不同的构造器,即构造器重载
-
方法名和类名相同
-
没有返回值
-
构造器是完成对象的初始化,并不是创建对象
-
在创建对象时,系统自动的调用该类的构造方法
-
如果程序员没有定义构造器,系统会自动给类生成一个默认的无参构造器(也叫默认构造器)比如Dog(){}
-
一旦定义了自己的构造器,默认的构造器就覆盖了,就不能再使用默认的无参构造器,除非显式的定义一下Dog(){}(很重要)
public class Constructor01 {
public static void main(String[] args) {
Dog dog = new Dog();//想要继续使用无参,需要显式定义
}
}
class Dog{
public Dog(String name){
}
Dog(){}
}
对象创建的流程分析(面试)
流程分析
- 加载Person类信息(Person.class) ,只加载一次
- 在堆中分配空间
- 完成对象初始化 3.1 默认初始化 age=0,num=null 3.2显式初始化age=90,name=null 3.3构造器的初始化 age=20,num=小倩
- 把对象在堆中的地址,返回给p(p是对象名,也可以理解成是对象的引用)
this关键字
public class This01 {
public static void main(String[] args) {
Dog dog = new Dog("大状", 3);
dog.info();
System.out.println(dog.hashCode());
}
}
class Dog{
String name;
int age;
public Dog(String name,int age){
//this.name 就是当前对象的属性name
this.name = name;
this.age = age;
System.out.println(this.hashCode());
}
public void info(){
System.out.println(name+ "\t" + age +"\t");
}
}
/*
21685669
大状 3
21685669
this小结
简单的说,哪个对象调用,this就代表哪个对象
this使用细节
- this关键字可以用来访问本类的属性、方法、构造器
- this用于区分当前类的属性和局部变量
- 访问成员方法的语法:this.方法名(参数列表)
public class ThisDetail {
public static void main(String[] args) {
T t = new T();
t.f2();
}
}
class T{
public void f1(){
System.out.println("f1() 方法");
}
public void f2(){
System.out.println("f2()");
f1();
this.f1();
}
}/*
f2()
f1() 方法
f1() 方法
- 访问构造器语法:this(参数列表);注意只能在构造器中使用(即只能在构造器中访问另外一个构造器,必须放在第一句)
public class ThisDetail {
public static void main(String[] args) {
// T t = new T();
// t.f2();
T t1 = new T();
t1.f2();
}
}
class T{
public T() {
//注意:访问构造器语法:this(参数列表);必须放置第一条语句
this("jack", 100);
System.out.println("T() 构造器");
}
public T(String name, int age) {
System.out.println("T(String name, int age) 构造器");
}
public void f1(){
System.out.println("f1() 方法");
}
public void f2(){
}
}
/*
T(String name, int age) 构造器
T() 构造器
- this不能在类定义的外部使用,只能在类定义的方法中使用
public class ThisDetail {
public static void main(String[] args) {
T t = new T();
t.f1();
}
}
class T{
String name = "jack";
int num = 100;
public void f1(){
String name = "smith";
System.out.println("name=" + name + " num=" + num);
System.out.println("name=" + this.name + " num=" + this.num);
}
}
/*
name=smith num=100
name=jack num=100
作业
- 定义方法max,实现求某个double数组的最大值并返回
public class HomeWork01 {
public static void main(String[] args) {
double[] arr = {1,23.3,2};
A a = new A();
Double res = a.max(arr);
if(res != null){
System.out.println("arr的最大值" + res);
}else{
System.out.println("arr输入有误");
}
}
}
class A{
public Double max(double[] arr) {
if (arr != null && arr.length > 0) {
double max = arr[0];
for (int i = 0; i < arr.length; i++) {
if (arr[i] > max) {
max = arr[i];
}
}
return max;
} else {
return null;
}
}
}
- 定义found,实现查找某字符串是否在数组中,并返回索引,如果找不到返回-1.
public class HomeWork02 {
public static void main(String[] args) {
A02 a02 = new A02();
String[] strs = {"jack", "tom", "mary", "milan"};
int index = a02.find("mary", strs);
System.out.println(index);
}
}
class A02 {
public int find(String findString, String[] strs) {
//直接遍历
for (int i = 0; i < strs.length; i++) {
if (findString.equals(strs[i])) {
return i;
}
}
return -1;
}
}
- 编写book类,定义方法updatePrice,实现更改某本书的价格,具体价格>150则更改150,若果价格>100更改为100,否则不变
public class HomeWork03 {
public static void main(String[] args) {
Book book = new Book("笑傲江湖", 20);
book.info();
book.updatePrice();//更新价格
book.info();
}
}
class Book{
String name;
double price;
public Book(String name, double price) {
this.name = name;
this.price = price;
}
public void updatePrice(){
if (price > 150){
price = 150;
}else if(price > 100){
price = 100;
}
}
public void info(){
System.out.println("名字" + this.name +"价格=" + this.price);
}
}
- 实现数组的复制功能copyArr,输入旧数组,返回一个新数组,元素和旧数组一样
public class HomeWork04 {
public static void main(String[] args) {
int[] arr = {1,2,3};
A03 a03 = new A03();
int[] newArr = a03.copyArr(arr);
for (int i = 0; i < newArr.length; i++) {
System.out.print(newArr[i] + " ");
}
}
}
class A03{
public int[] copyArr(int[] oldarr){
int[] arrNew = new int[oldarr.length];
for (int i = 0; i < oldarr.length; i++) {
arrNew[i] = oldarr[i];
}
return arrNew;
}
}
- 定义一个圆类,定义属性:半径,提供显示圆周长功能的方法,提供显示圆面积的方法
public class HomeWork05 {
public static void main(String[] args) {
Circle circle = new Circle(1);
System.out.println("面积" + circle.area());
System.out.println("面积" + circle.len());
}
}
class Circle{
double radius;
public Circle(double radius) {
this.radius = radius;
}
public double area(){
return Math.PI * radius * radius;
}
public double len(){
return 2 * Math.PI * radius;
}
}
/*
面积28.274333882308138
面积18.84955592153876
- 编程创建一个Cale计算类,在其中定义2个变量表示两个 操作数,定义四个方法实现求和、差、乘、商(要求除数为0的话,要提示)并创建两个对象分别测试。
public class HomeWork06 {
public static void main(String[] args) {
Cale cale = new Cale(2, 0);
System.out.println("和" + cale.sum());
System.out.println("差" + cale.minus());
System.out.println("乘" + cale.mul());
Double divRes = cale.div();
if (divRes != null) {
System.out.println("除" + divRes);
}
}
}
class Cale {
double num1;
double num2;
public Cale(double num1, double num2) {
this.num1 = num1;
this.num2 = num2;
}
//和
public double sum() {
return num1 + num2;
}
public double minus() {
return num1 - num2;
}
public double mul() {
return num1 * num2;
}
//除法
public Double div() {
if (num2 == 0) {
System.out.println("不能为0");
return null;
} else {
return num1 / num2;
}
}
}
- new Test() //匿名对象,使用一次使用后就不能使用
- new Test().count1() 创建后一名对象后就调用count1()
public class HomeWork07 {
public static void main(String[] args) {
//1. new Test() 匿名对象,使用一次使用后就不能使用
//2. new Test().count1() 创建后一名对象后就调用count1()
new Test().count1();
Test test = new Test();
test.count2();
test.count2();
}
}
class Test{
int count = 9;
public void count1(){
count = 10;
System.out.println( "count=" + count);
}
public void count2(){
System.out.println("count1=" + count++);//先输出再自增
}
}
/*
count=10
count1=9
count1=10
- 看代码写结果
class Demo{
int i=100;
public void m(){
int j = i++;
System.out.println("i=" + i);//101
System.out.println("j=" + j);//100
}
}
class Test1{
public static void main(String[] args) {
Demo d1 = new Demo();
Demo d2 = d1;
d2.m();
System.out.println(d1.i);//101
System.out.println(d2.i);//101
}
}
/*
i=101
j=100
101
101
- 创建一个Employee类,属性有(名字,性别,年龄,职位,薪水),提供3个构造方法,可以初始化(1)(名字,性别,年龄,职位,薪水),(2)(名字,性别,年龄)(3)(职位,薪水),要求充分复用构造器
public class HomeWork12 {
public static void main(String[] args) {
}
}
class Employee{
String name;
char gender;
int age;
String job;
double sal;
//要求可以复用构造器,先写属性少的构造器
public Employee(String job, double sal) {
this.job = job;
this.sal = sal;
}
//名字性别年龄
public Employee(String name, char gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
//名字、性别、年龄、职位、薪水
public Employee(String name, char gender, int age, String job, double sal) {
this(name, gender, age);//使用到 前面的构造器
this.job = job;
this.sal = sal;
}
}
public class HomeWork12 {
public static void main(String[] args) {
Circle01 c= new Circle01();
PassObject po = new PassObject();
po.printAreas(c,5);
}
}
class Circle01{
double radius;
public Circle01(double radius) {
this.radius = radius;
}
public Circle01() {
}
public double findArea(){
return Math.PI * radius * radius;
}
//添加方法修改对象的半径值
public void setRadius(double radius){
this.radius = radius;
}
}
class PassObject{
public void printAreas(Circle01 c, int times){
System.out.println("radius\tarea");
for (int i = 1; i <= times; i++) {//输出1到times之间的每个整数半径值
c.setRadius(i);
System.out.println((double)i + "\t" + c.findArea());
}
}
}
/*
radius area
1.0 3.141592653589793
2.0 12.566370614359172
3.0 28.274333882308138
4.0 50.26548245743669
5.0 78.53981633974483
- 猜拳
idea使用
快捷键
运行:ctrl+alt+f10模板
file-> settings-> editor -> Live tempaltes 查看有哪些模板快捷键
包
包的三大作用
- 区分相同名字的类
- 当类很多时,可以很好的管理类
- 控制访问范围
package com.liu;
- package 关键字,表示打包
- com.liu 表示包名
命名规则
引用其他包的类
public static void main(String[] args) {
com.liu.for_.Dog dog = new com.liu.for_.Dog();
dog.say();
}
常用的包
使用细节
- package的作用是声明当前类所在的包,需要放在类的最上面,一个类中最多只有一句package
- import指令 位置放在package的下面,在类定义前面,可以有多句且没有顺序要求
访问修饰符
访问修饰符和各自的访问权限
-
private:私有的,对访问权限限制最窄的修饰符。被private修饰的属性以及方法只能被该类的对象访问。它的子类也不可以访问,更不支持跨包访问。
-
protected:及保护访问权限,是介于public和private之间的一种访问修饰。被protected修饰的属性及方法只能被类本身的方法和子类访问。(子类在不同的包中也可以访问)
-
public:及共有的,是访问权限限制最宽的修饰符。被public修饰的类、属性、及方法不仅可以跨类访问,而且可以跨包访问。
-
default:及默认的,不加任何访问修饰符。常被叫做“默认访问权限”或者“包访问权限”。无任修饰符时,只支持在同一个包中进行访问。
封装
public class test01 {
public static void main(String[] args) {
Person person = new Person();
person.setName("jackdss");
person.setAge(121);
person.setSalary(10000);
System.out.println(person.toString());
}
}
class Person{
public String name;
private int age;
private double salary;
public String getName() {
return name;
}
public void setName(String name) {
if (name.length() >= 2 && name.length()<= 6){
this.name = name;
}else{
System.out.println("名字长度不对,需要2-6个字符");
this.name = "无名";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 120 && age > 1){
this.age = age;
}else {
System.out.println("输入错误,年龄在1-120,默认年龄为18");
this.age = 18;//给默认年龄
}
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
/*
名字长度不对,需要2-6个字符
输入错误,年龄在1-120,默认年龄为18
Person{name='无名', age=18, salary=10000.0}
封装与构造器
public class test01 {
public static void main(String[] args) {
//如果自己直接使用构造器指定属性
Person smith = new Person("smith", 2000, 50000);
System.out.println("===smith===");
System.out.println(smith.toString());
}
}
class Person{
public String name;
private int age;
private double salary;
public String getName() {
return name;
}
public Person() {
}
public Person(String name, int age, double salary) {
// this.name = name;
// this.age = age;
// this.salary = salary;
//可以将set方法写在构造器中
setName(name);
setAge(age);
setSalary(salary);
}
public void setName(String name) {
if (name.length() >= 2 && name.length()<= 6){
this.name = name;
}else{
System.out.println("名字长度不对,需要2-6个字符");
this.name = "无名";
}
}
public int getAge() {
return age;
}
public void setAge(int age) {
if (age < 120 && age > 1){
this.age = age;
}else {
System.out.println("输入错误,年龄在1-120,默认年龄为18");
this.age = 18;//给默认年龄
}
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
/*
输入错误,年龄在1-120,默认年龄为18
===smith===
Person{name='smith', age=18, salary=50000.0}
继承
- 子类就会自动拥有与父类定义的属性和方法
- 父类又叫超类,基类
- 子类又叫派生类
继承原理图
带来的便利
- 代码的复用性提高了
- 代码的扩展性和维护性提高了
继承使用细节
-
子类继承了所有的属性和方法,非私有的属性和方法可以在子类直接访问, 但是私有属性和方法不能在子类直接访问,要通过父类提供公共的方法去访问
-
子类必须调用父类的构造器, 完成父类的初始化
public class Test {
public static void main(String[] args) {
Son son = new Son();
}
}
class Person{
String name;
public Person() {
System.out.println("父类构造器被调用Person()");
}
public void say(){
System.out.println("你好");
}
}
class Son extends Person{
public Son() {
System.out.println("子类构造器被调用son()");
}
public void say1(){
System.out.println( );
}
}/*运行结果
父类构造器被调用Person()
子类构造器被调用son()
- 当创建子类对象时,不管使用子类的哪个构造器,默认情况下总会去调用父类的无参构造器,如果父类没有提供无参构造器,则必须在子类的构造器中用 super 去指定使用父类的哪个构造器完成对父类的初始化工作,否则,编译不会通过(怎么理解。) [举例说明]
public class Test {
public static void main(String[] args) {
System.out.println("第一个对象");
Son son = new Son();
System.out.println("第二个对象");
Son son2 = new Son("jack");
}
}
class Person{
String name;
int age;
// public Person() {
// System.out.println("父类构造器被调用Person()");
// }
public Person(String nam, int age){
System.out.println("父类Person(String nam, int age)构造器被调用Person()");
}
public void say(){
System.out.println("你好");
}
}
class Son extends Person{
public Son() {
super("s",1);
System.out.println("子类构造器被调用son()");
}
public Son(String name) {
super("tom",40);
System.out.println("子类Son(String name)构造器被调用");
}
public void say1(){
System.out.println( );
}
}
/*
第一个对象
父类Person(String nam, int age)构造器被调用Person()
子类构造器被调用son()
第一个对象
父类Person(String nam, int age)构造器被调用Person()
子类Son(String name)构造器被调用
- 如果希望指定去调用父类的某个构造器,则显式的调用一下 : super(参数列表)
想要调用父类的构造器
public Person(String name, int age){
System.out.println("父类Person(String nam, int age)构造器被调用Person()");
}
需要在子类用super("tom",40);
public Son(String name) {
super("tom",40);
System.out.println("子类Son(String name)构造器被调用");
}
//
如果想要调用父类的构造器
public Person(String name){
System.out.println("父类Person(String nam, int age)构造器被调用Person()");
}
需要在子类用super("smith");
public Son(String name) {
super("smith");
System.out.println("子类Son(String name)构造器被调用");
}
-
super 在使用时,必须放在构造器第一行(super 只能在构造器中使用)
-
super() 和 this() 都只能放在构造器第一行,因此这两个方法不能共存在一个构造器
-
java 所有类都是 Object 类的子类, Object 是所有类的基类.
-
父类构造器的调用不限于直接父类!将一直往上追溯直到 Object 类(顶级父类)
-
子类最多只能继承一个父类(指直接继承),即 java 中是单继承机制。
思考:如何让 A 类继承 B 类和 C 类? 【A 继承 B, B 继承 C】
- 不能滥用继承,子类和父类之间必须满足 is-a 的逻辑关系
继承本质
public class ExtendsTheory {
public static void main(String[] args) {
Son son = new Son();//内存的布局
//?-> 这时请大家注意,要按照查找关系来返回信息
//(1) 首先看子类是否有该属性
//(2) 如果子类有这个属性,并且可以访问,则返回信息
//(3) 如果子类没有这个属性,就看父类有没有这个属性(如果父类有该属性,并且可以访问,就返回信息..)
//(4) 如果父类没有就按照(3)的规则,继续找上级父类,直到 Object...
System.out.println(son.name);//返回就是大头儿子
System.out.println(son.getAge());//返回的就是 39
System.out.println(son.hobby);//返回的就是旅游
}
}
class GrandPa { //爷类
String name = "大头爷爷";
String hobby = "旅游";
}
class Father extends GrandPa {//父类
String name = "大头爸爸";
private int age = 39;
public int getAge() {
return age;
}
}
class Son extends Father { //子类
String name = "大头儿子";
}
如果Father里的age是private,即使GrandPa里也有age,是public,访问age的时候一样到Father就会停止,不会继续查看GrandPa里是否有age。
练习
由于B()函数里调用了this函数,所以没有super函数,但B(String name)函数有super()。所以输出结果应该是 a,b name,b
super关键字
- 访问父类的属性,但不能访问父类的private属性,super.属性名
- 访问父类的方法,但不能访问父类的private方法,super.方法名(参数列表)
- 访问父类的构造器:super(参数列表);只能放在构造器的第一句
使用细节
- 调用父类构造器的好处(分工明确,父类属性由父类初始化,子类属性由子类初始化)
- 当子类中有父类中的成员(属性和方法)重名时,为了访问父类的成员,必须通过super。如果没有重名,使用super、this、直接访问是一样效果
public class Test01 {
public static void main(String[] args) {
B b = new B();
b.sum();
}
}
class A{
public void cal() {
System.out.println("A类cal()方法");
}
}
class B extends A{
public void sum(){
System.out.println("B类sum()方法");
//希望调用父类A的cal方法
//子类B没有cal方法,有三种方式
//cal() 和thsi.cal()的规则
//(1)先找本类,如果有,则调用
//(2) 如果没有,则找父类(如果有,并可以调用,则调用)
//(3) 如果父类没有,则继续找父类,整个规则就是一样的,直到Object类
//提示:如果查找方法的过程中,找到了但不能访问,则报错 cannot access
// 如果查找方法过程中没有找到,则提示方法不存在
cal(); //A类cal()方法
this.cal(); //A类cal()方法
//直接查找父类方法
super.cal(); //A类cal()方法
}
}
- super的访问不限于直接父类,如果爷爷类和本类中有同名的成员,也可以使用super去访问爷爷类的成员;如果多个基类中都有同名的成员,使用super访问遵循就近原则。A->B->C当然也需要遵守访问权限的相关规则
方法重写(override)
- 子类的方法的形参列表,方法名称,要和父类方法的形参列表,方法名称完全一样。
- 子类方法的返回类型和父类方法返回类型一样,或者是父类返回类型的子类比如父类返回类型是Object,子类方法返回类型是String
class Animal{
public void say(){
System.out.println("叫");
}
public Object m1(){
return null;
}
public String m2(){
return null;
}
}
class Dog extends Animal{
public void say(){
System.out.println("小狗叫");
}
public String m1(){
return null;
}
//Object不是String的子类,编译错误
// public Object m2(){
// return null;
// }
}
- 子类方法不能缩小父类方法的访问权限 public > protected > 默认
class Animal{
public void eat(){
}
protected void say(){
}
}
class Dog extends Animal{
//细节:子类方法不能缩小父类方法的访问权限
// public > protected > 默认
//会报错
protected void eat(){//报错
}
//子类访问权限比父类大,可以
public void say(){
}
}
重写重载的比较
多态
多[多种]态[状态]基本介绍
- 方法或对象具有多种形态。是面向对象的第三大特征,多态是建立在封装和继承基础之上的。
多态的具体体现
-
方法的多态
重写和重载就体现多态 -
对象的多态 (核心,困难,重点)
- 一个对象的编译类型和运行类型可以不一致
- 编译类型在定义对象时,就确定了,不能改变
- 运行类型是可以变化的
- 编译类型看定义时 = 号的左边,运行类型看 = 号的右边
Animal animal = new Dog();//animal编译类型是Animal,运行类型是Dog
animal = new Cat(); //animal的运行类型变成了Cat,编译类型仍然是Animal
多态注意事项和细节
向上转型:父类的引用指向了子类的对象
Animla animal = new Cat();
向上转型调用方法的规则如下:
(1)可以调用父类中的所有成员(需遵守访问权限)
(2)但是不能调用子类的特有的成员
(3)因为在编译阶段,能调用哪些成员,是由编译类型来决定的
(4)最终运行效果看子类(运行类型)的具体实现, 即调用方法时,按照从子类(运行类型)开始查找方法
然后调用,规则我前面我们讲的方法调用规则一致。
多态的向下转型
public class Test {
public static void main(String[] args) {
//语法:父类类型引用名 = new 子类类型();
Animal animal = new Cat();
Object obj = new Cat();//可以吗? 可以 Object 也是 Cat 的父类
//向上转型调用方法的规则如下:
//(1)可以调用父类中的所有成员(需遵守访问权限)
//(2)但是不能调用子类的特有的成员
//(3)因为在编译阶段,能调用哪些成员,是由编译类型来决定的
//animal.catchMouse();错误
//(4)最终运行效果看子类(运行类型)的具体实现, 即调用方法时,按照从子类(运行类型)开始查找方法
//然后调用,规则我前面我们讲的方法调用规则一致。
animal.eat();//猫吃鱼..
animal.run();//跑
animal.show();//hello,你好
animal.sleep();//睡
//老师希望,可以调用 Cat 的 catchMouse 方法
//多态的向下转型
//(1)语法:子类类型 引用名 =(子类类型)父类引用;
//问一个问题? cat 的编译类型 Cat,运行类型是 Cat
Cat cat = (Cat) animal;
cat.catchMouse();//猫抓老鼠
//(2)要求父类的引用必须指向的是当前目标类型的对象
Dog dog = (Dog) animal; //可以吗?,不可以,不能把猫变成狗
System.out.println("ok~~");
}
}
class Animal {
String name = "动物";
int age = 10;
public void sleep(){
System.out.println("睡");
}
public void run(){
System.out.println("跑");
}
public void eat(){
System.out.println("吃");
}
public void show(){
System.out.println("hello,你好");
}
}
class Cat extends Animal {
public void eat(){//方法重写
System.out.println("猫吃鱼");
}
public void catchMouse(){//Cat 特有方法
System.out.println("猫抓老鼠");
}
}
class Dog extends Animal {//Dog 是 Animal 的子类
}
属性没有重写之说!属性的值看编译类型
public class text02 {
public static void main(String[] args) {
Base base = new Sub();
System.out.println(base.count);
}
}
class Base{
int count = 10;
}
class Sub extends Base{
int count = 10;
}
/*
运行结果
10
instanceOf 比较操作符,用于判断对象的运行类型是否为型 XX 类型或 XX 类型的子类:
public class text02 {
public static void main(String[] args) {
BB bb = new BB();
System.out.println(bb instanceof BB);// true
System.out.println(bb instanceof AA);//true
AA aa = new BB();
System.out.println(aa instanceof AA);//true 解析:bb的运行是否是后面AA的类型,或者是否是AA类型的子类型,
System.out.println(aa instanceof BB);//true
Object obj = new Object();
System.out.println(obj instanceof AA);//false
String str = "hello";
System.out.println(str instanceof Object);//true
}
}
class AA{
}
class BB extends AA{
}
练习
动态绑定机制(非常非常重要)
- 当调用对象方法的时候,该方法会和改对象的内存地址/运行类型绑定
- 当调用对象属性时,没有动态绑定机制,哪里声明,哪里使用
public class DynamicBinding {
public static void main(String[] args) {
//a 的编译类型 A, 运行类型 B
A a = new B();//向上转型
System.out.println(a.sum());//?40 -> 30
System.out.println(a.sum1());//?30-> 20
}
}
class A {//父类
public int i = 10;
//动态绑定机制:
public int sum() {//父类 sum()
return getI() + 10;//20 + 10
}
public int sum1() {//父类 sum1()
return i + 10;//10 + 10
}
public int getI() {//父类 getI
return i;
}
}
class B extends A {//子类
public int i = 20;
// public int sum() {
// return i + 20;
// }
public int getI() {//子类 getI()
return i;
}
// public int sum1() {
// return i + 10;
// }
}
多态的应用
多态数组
public class PolyArray {
public static void main(String[] args) {
// 2 个 Student 对象和 2 个 Teacher 对象, 统一放在数组中,并调用每个对象 say 方法
Person[] persons = new Person[5];
persons[0] = new Person("jack", 20);
persons[1] = new Student("mary", 18, 100);
persons[2] = new Student("smith", 19, 30.1);
persons[3] = new Teacher("scott", 30, 20000);
persons[4] = new Teacher("king", 50, 25000);
//循环遍历多态数组,调用 say
for (int i = 0; i < persons.length; i++) {
//老师提示: person[i] 编译类型是 Person ,运行类型是是根据实际情况由 JVM 来判断
System.out.println(persons[i].say());//动态绑定机制
//这里使用 类型判断 + 向下转型.
if(persons[i] instanceof Student) {//判断 person[i] 的运行类型是不是 Student
Student student = (Student)persons[i];//向下转型
student.study(); //小伙伴也可以使用一条语句 ((Student)persons[i]).study();
} else if(persons[i] instanceof Teacher) {
Teacher teacher = (Teacher)persons[i];
teacher.teach();
} else if(persons[i] instanceof Person){
//
} else {
System.out.println("你的类型有误, 请自己检查...");
}
}
}
}
class Person{
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String say(){
return name + " " + age;
}
}
class Student extends Person{
private double score;
public Student(String name, int age, double score) {
super(name, age);
this.score = score;
}
public double getScore() {
return score;
}
public void setScore(double score) {
this.score = score;
}
@Override
public String say(){
return "学生 " + super.say() + " score=" + score;
}
public void study(){
System.out.println("学生" + getName() + "正在...");
}
}
class Teacher extends Person{
private double salary;
public Teacher(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String say() {
return "老师 " + super.say() + " salary=" + salary;
}
public void teach(){
System.out.println("老师" + getName() + "正在...");
}
}
多态参数(p317)
Object类
equals 和 ==
- **== **
- == :既可以判断基本类型,又可以判断引用类型
- == : 如果判断基本类型,判断值是否相等,用于判断基本数据类型
- == :如果判断引用类型,判断地址是否相等,即判断是否是同一个对象
- equals
- equals: 是Object类中的方法,只能判断引用类型
- 默认判断的是地址是否相等,也就是判断是不是同一个对象,子类往往重写该方法,用于判断内容是否相等。
public static void main(String[] args) {
Integer integer1 = new Integer(1000);
Integer integer2 = new Integer(1000);
System.out.println(integer1 == integer2);//false,判断地址
System.out.println(integer1.equals(integer2));//true,子类重写了equals方法,判断值
String str1 = new String("liu");
String str2 = new String("liu");
System.out.println(str1 == str2);//false ,判断地址
System.out.println(str1.equals(str2));//true,子类重写了equals方法,判断值
}
练习
- 应用实例: 判断两个 Person 对象的内容是否相等,如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false。
public class EqualsExercise01 {
public static void main(String[] args) {
Person person = new Person("jack", 10, '男');
Person person1 = new Person("jack", 10, '男');
System.out.println(person.equals(person1));
}
}
//判断两个 Person 对象的内容是否相等,
//如果两个 Person 对象的各个属性值都一样,则返回 true,反之 false
class Person{
private String name;
private int age;
private char gender;
//重写 Object 的 equals 方法
public boolean equals(Object obj){
//判断如果比较的两个对象是同一个对象,则直接返回 true
if (this == obj){
return true;
}
//类型判断
if ( obj instanceof Person){//是 Person,我们才比较
//进行 向下转型, 因为我需要得到 obj 的 各个属性
Person p = (Person)obj;
return this.name.equals(p.name) && this.age ==p.age && this.gender == p.gender;
}
return false;
}
public Person(String name, int age, char gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
}
-
看代码写结果
-
字符的本质是整数
hashCode
- 提高具有哈希结构的容器的效率
- 两个引用,如果指向同一个对象,则哈希值一定一样
- 两个引用,如果指向不同对象,则哈希值不一样(小概率也可能一样,冲突了)
- 哈希值主要根据地址号来的,不能完全将哈希值等价于地址
- 在集合中,如果需要,重写
toString方法
- 默认返回: 全类名+@+哈希值的十六进制
全类名=包名+类名 - 重写toString方法,打印或者拼接对象时,都会调用该对象的toString形式
- 当直接输出一个对象时,toString方法会默认调用,
比如System.out.println(momster);就会默认调用monster.toString();
public class ToString_ {
public static void main(String[] args) {
/*
Object的toString()源码
//getClass().getName()类的全类名(包名+类名)
//Integer.toHexString(hashCode())将对象的hashCode的值转成16进制字符串
public String toString() {
return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
*/
Monster monster = new Monster("小妖怪", "巡山", 1000);
System.out.println(monster.toString() + " hashCode=" + monster.hashCode());
System.out.println(monster);
}
}
class Monster{
private String name;
private String job;
private double sal;
public Monster(String name, String job, double sal) {
this.name = name;
this.job = job;
this.sal = sal;
}
@Override
public String toString() {
return "Monster{" +
"name='" + name + '\'' +
", job='" + job + '\'' +
", sal=" + sal +
'}';
}
}
/*
Monster{name='小妖怪', job='巡山', sal=1000.0} hashCode=460141958
Monster{name='小妖怪', job='巡山', sal=1000.0}
finalize
finalize()
- 当对象被回收时,系统自动调用该对象的finalize方法,可重写该方法做一些释放资源的操作(比如释放资源、数据据库连接等),如果不重写,就会调用Object类的finalize方法,即默认处理。
- 什么时候回收:某个对象没有引用时,jvm就认为对象是一个垃圾,就用垃圾回收机制销毁该对象,在销毁该对象前,会先调用finalize方法。
- 垃圾回收机制的调用,由系统来决定(即有自己的GC算法),也可以通过System.gc()主动触发垃圾回收机制。
提示:实际开发中,几乎不会运用finalize,所以更多是为了应付面试
断点调试
重要提示:在断电调式过程中,是运行状态,是以对象的运行类型来执行的。
快捷键
练习题
2.访问修饰符和各自的访问权限
修饰符 所在类 同一个包内其他类 其他包内子类 其他包内非子类
public √ √ √ √
protected √ √ √ ×
默认(default) √ √ × ×
private √ × × ×
-
private:私有的,对访问权限限制最窄的修饰符。被private修饰的属性以及方法只能被该类的对象访问。它的子类也不可以访问,更不支持跨包访问。
-
protected:及保护访问权限,是介于public和private之间的一种访问修饰。被protected修饰的属性及方法只能被类本身的方法和子类访问。(子类在不同的包中也可以访问)
-
public:及共有的,是访问权限限制最宽的修饰符。被public修饰的类、属性、及方法不仅可以跨类访问,而且可以跨包访问。
-
default:及默认的,不加任何访问修饰符。常被叫做“默认访问权限”或者“包访问权限”。无任修饰符时,只支持在同一个包中进行访问。