一、生成随机数的方法
第一种:new Random();
第一种需要借助java.util.Random类来产生一个随机数发生器,也是最常用的一种,构造函数有两个,Random()和Random(long seed)。第一个就是以当前时间为默认种子,第二个是以指定的种子值进行。产生之后,借助不同的语句产生不同类型的数。种子就是产生随机数的第一次使用值,机制是通过一个函数,将这个种子的值转化为随机数空间中的某一个点上,并且产生的随机数均匀的散布在空间中。以后产生的随机数都与前一个随机数有关。
Random 对象的 nextInt(int) 方法,这个方法将生成 0 ~ 参数int之间随机取值的整数
下面以获取5-10之间的随机数为例:
import java.util.Random;
public class random {
public static void main(String[] args){
Random rand = new Random();
System.out.println(rand.nextInt(6) + 5);
}
}
同理:下列为获取随机两位数,随机三位数的方法:
rand.nextInt(90) + 10; //随机两位数
rand.nextInt(900) + 100;//随机三位数
总结:想要得到某个范围内的随机数:
Random rand = new Random();
int randomFigure=rand.nextInt(MAX - MIN + 1) + MIN; // randomFigure将被赋值为一个 MIN 和 MAX 范围内的随机数 。
Random类提供的方法: API
- nextBoolean()
- 返回均匀分布的 true 或者 false
- nextBytes(byte[] bytes)
- nextDouble()
- 返回 0.0 到 1.0 之间的均匀分布的 double
- nextFloat()
- 返回 0.0 到 1.0 之间的均匀分布的 float
- nextGaussian()
- 返回 0.0 到 1.0 之间的高斯分布(即正态分布)的 double
- nextInt()
- 返回均匀分布的 int
- nextInt(int n)
- 返回 0 到 n 之间的均匀分布的 int (包括 0,不包括 n)
- nextLong()
- 返回均匀分布的 long
- setSeed(long seed)
- 设置种子
只要种子一样,产生的随机数也一样: 因为种子确定,随机数算法也确定,因此输出是确定的!
Random random1 = new Random(10000);Random random2 = new Random(10000); for (int i = 0; i < 5; i++) { System.out.println(random1.nextInt() + " = " + random2.nextInt());}
结果:
-498702880 = -498702880 -858606152 = -858606152 1942818232 = 1942818232 -1044940345 = -1044940345 1588429001 = 1588429001
第二种:Math.random();
第二种方法返回的数值是[0.0,1.0)的double型数值,由于double类数的精度很高,可以在一定程度下看做随机数,借助(int)来进行类型转换就可以得到整数随机数了。
下面以获取5-10之间的随机数为例:
public static void main(String[] args)
{
int randomFigure = (int) (5+Math.random()*6);
System.out.println(randomFigure);
}
总结:想要得到某个范围内的随机数:
(数据类型)(MIN+Math.random()*(MAX-MIN+1))。
实现原理:
当第一次调用 Math.random() 方法时,自动创建了一个伪随机数生成器 ,实际上用的是 new java.util.Random()。当接下来继续调用 Math.random() 方法时,就会使用这个新的伪随机数生成器 。
源码如下:
public static double random() {
Random rnd = randomNumberGenerator;
if (rnd == null)
rnd = initRNG(); // 第一次调用,创建一个伪随机数生成器
return rnd.nextDouble();
}
private static synchronized Random initRNG() {
Random rnd = randomNumberGenerator;
return (rnd == null) ? (randomNumberGenerator = new Random()) : rnd; // 实际上用的是new java.util.Random()
}
initRNG() 方法是 synchronized(同步)的,因此在多线程情况下,只有一个线程会负责创建伪随机数生成器 (使用当前时间作为种子),其他线程则利用该伪随机数生成器 产生随机数。
因此 Math.random() 方法是线程安全的。
第三种:currentTimeMillis();
至于第三种方法虽然不常用,但是也是一种思路。方法返回从1970年1月1日0时0分0秒(这与UNIX系统有关)到现在的一个long型的毫秒数,取模之后即可得到所需范围内的随机数。
下面以获取5-10之间的随机数为例:
public class random {
public static void main(String[] args){
long l = System.currentTimeMillis();
int randomFigure = (int)l%6+5;
System.out.println(randomFigure);
}
}
总结:想要得到某个范围内的随机数:
long l = System.currentTimeMillis();
int randomFigure = (int) (l%(MAX-MIN)+MIN);
二、变量的定义
1.整数类型变量
字节型(byte)、短整型(short)、整形(int)和长整形(long)
4中类型变量所占存储空间的大小及取值范围如表2-1所示。
在表2-1中,占用空间是指不同类型的变量占用的内存大小,例如,一个int类型的变量会占用4个字节大小的内存空间;取值范围是指变量存储的值不能超出的范围
需要注意的是,在为一个long类型的变量赋值时,所赋值的后面要加上字母L(或小写字母1)。具体示例如下:
long num =2200000000L;
//所赋的值超出了int类型的取值范围,后面必须加上字母L
//所赋的值没有超出int类型的取值范围,可加可不加
long num =198L;
//所赋的值未超出int类型的取值范围,后面可以加上字母L
long num =198;
//所赋的值未超出int类型的取值范围,后面可以省略字母L
2.浮点类型变量
浮点类型变量用于存储小数数值。
double类型所表示的浮点数比float类型更精确,两种浮点类型变量所占存储空间的大小及取值范围如表2-2所示
在表2-2中,取值范围中的E(也可写为小写字母e)表示以10为底的指数,E后面的“+”和“-”代表正指数和负指数。
在Java中,小数会被默认为double类型的值,因此在为一个float类型的变量赋值时,在所赋值的后面一定要加上字母F(或者小写字母f),而为double类型的变量赋值时,可以在所赋值的后面加上字母D(或者小写字母d),也可以不加。具体示例如下:
float f = 123.4f; //为一个float类型的变量赋值,后面必须加上字母f
double d1 =100.1; //为一个double类型的变量赋值,后面可以省略字母d
double d2 =199.3d; //为一个double类型的变量赋值,后面可以加上字母d
在程序中也可以为一个浮点数类型变量赋予一个整数数值。例如,下面的写法也是可以的。
float f =100; //声明一个float类型的变量并赋整数值
double d =100; //声明一个double类型的变量并赋整数值
3.字符类型变量
在Java中,字符类型变量用char表示,用于存储一字符。
Java中每个char类型的字符变量都会占用2个字节。
在给char类型的变量赋值时,需要用一对英文半角格式的单引号(' ')把字符括起来,如'a'。
在计算机的世界里,所有文字、数值都只是一连串的0与1,这些0与1是机器语言,人类难以理解,于是就产生了各种方式的编码,使用一个数值代表某个字符,如常用的字符编码系统ASCII。
虽然各类编码系统加起来有数百种之多,却没有一种包含足够多的字符、标点符号和常用的专业技术符号。这些编码系统之间可能还会相互冲突。也就是说,不同的编码系统可能会使用相同的数值标识不同的字符,这样在数据跨平台时就会发生错误。Unicode就是为了避免上述情况而产生的,它为每个字符制定了一个唯一的数值,因此,在任何语言、平台、程序中都可以安心地使用。Java使用的就是Unicode字符吗系统,Unicode中的小写字母a是用97表示的,在计算时,计算机会自动将字符转换为所对应的数值。
定义字符型变量的具体示例如下:
char c='a'; //为一个char类型的变量赋值字符a
char ch =97; //为一个char类型的变量赋值整数97,相当于赋值字符a
4.布尔类型变量
在Java中,使用boolean定义布尔类型变量,布尔类型变量只有true和false两个值。定义布尔类型变量的具体示例如下:
boolean flag = false; //定义一个boolean类型的比那里flag,初始值为false
flag =true; //改变变量flag的值为true
三、StringBuilder
1.追加字符串 append()
StringBuilder sb = new StringBuilder("x");
sb.append("a"); // 在当前字符串的尾部追加字符串a // xa
sb.append("b"); // 在当前字符串的尾部追加字符串a // xab
2.替换字符串 replace(int start,int end,替换的内容)
StringBuilder sb = new StringBuilder("天王盖地虎");
sb.replace(1, 3, "博爱他"); // 替换下标1-3的位置不包含3
sb.replace(0, 1, ""); // 替换下标0-1的位置不包含1
System.out.println(sb); // 替换会产生新内容
3.删除字符串 delete(int start,int end)
StringBuilder sb = new StringBuilder();
sb.append("巴山楚水凄凉地,responsebility").delete(10, 12) // 按照下标内容删除
.deleteCharAt(0); // 删除指定位置内容
System.out.println(sb);
4.指定下标新内容 insert(int start,任意数据类型)
StringBuilder sb = new StringBuilder("abcd");
// insert():在指定下标位置,插入新内容
sb.insert(2, "123")
.insert(2, true)
.insert(2, Math.PI);
5.字符串逆序 reverse()
// 字符串逆序
StringBuilder sb=new StringBuilder("甲乙丙丁");
sb.reverse(); // 逆序反转
System.out.println(sb);
6.设置分隔符
// StringJoiner joiner = new StringJoiner("#"); // 设置分隔符
StringJoiner joiner = new StringJoiner("#", "@", "@"); // 设置分隔符
文章知识点与官方知识档案匹配,可进一步学习相关知识
四、面向对象三大特性
封装其实是对外隐藏复杂细节,提供简单易用的接口,便于外界调用,从而提高系统的可扩展性、可维护性。在Java中这种隐藏或公开是通过权限修饰符来实现的。
Java中类就是对具体事物的一种封装,类中的方法等等也是一种封装。我们把数据、一系列操作数据的函数封装到方法中,然后通过权限修饰符控制哪些方法可以让外知道,哪些只能自己知道,这样就能减少核心的数据被外界获取甚至破坏。
我们最初学习Java时,往往都是把代码直接写在main方法中的,但是随着学习的深入,遇到的逻辑越来越复杂,我们发现只靠main方法是不能满足全部需要的,这时候,我们开始在类中扩展其他方法,最后通过main方法调用运行。再后来我们逐渐开始去写不同的类,甚至不同的业务模块。这时候就会发现,一个简单的封装能带来多大的好处。
封装的优点:
1.通过封装,我们可以保护代码被破坏,提高数据安全性。
2.使用者只能通过事先定制好的方法来访问数据,可以方便地加入控制逻辑限制对属性的不合理操作。
3.通过封装,我们提高了代码的复用性(有些方法、类在很多地方都能多次反复使用)
4.通过封装,带来的高内聚和低耦合,使用不同对象、不同模块之间能更好的协同,同时便于修改,增强代码的可维护性
5.高内聚 :类的内部数据操作细节自己完成,不允许外部干涉;
6.低耦合 :仅对外暴露少量的方法用于使用
比如说一个类最简单的封装就是把属性隐藏起来,只提供 get和set 方法进行操作:
public class Student {
//姓名
private String name;
//年龄
private int age;
//get方法获取年龄
public int getAge() {
return age;
}
//set方法修改年龄
public void setAge(int age) {
if(age<0 ||age>100){
return;
}
this.age = age;
}
//get方法获取姓名
public String getName() {
return name;
}
//set方法修改年龄
public void setName(String name) {
this.name = name;
}
这样在操作的时候外部通过 get和set 修改和获取属性,这样的好处是我可以在get和set方法中隐藏一些其他的处理逻辑(比如在setAge里添加一些年龄的限制条件),且属性没有对外部暴露,也可以进一步提高安全性。如果没有进行封装,那任意调用属性会导致数据的错误、混乱或安全性问题(比如年龄:如果所以调用没有验证,那可能出现负数出现)。
另外我们对一些逻辑的封装可以极大的提高此段代码的复用性,比方一个求和的方法:
public static void main(String[] args) {
//求和
int sum = 0;
for(int i=1; i<=10 ; i++){
sum = sum+i;
}
System.out.println(sum);
//封装成方法后可以随意调用,实现代码的复用,如果不封装,需要写多段求和代码才能实现
System.out.println(getSum(10));
System.out.println(getSum(100));
System.out.println(getSum(20));
}
//把求和的逻辑封装成一个方法
public static int getSum(int num){
int sum = 0;
for(int i=1; i<=num ; i++){
sum = sum+i;
}
return sum;
}
3.2、 继承
类和类之间有些也会具有一定的关系。比方说四边形,可以分为正方形、长方形、菱形,他们不但继承了四边形的特征,也具有属于自己的特征,这就是一种继承的关系。
有时候我们希望基于某一个类进行扩展,使一个新类直接拥有基类的基本特征,而不需要重复去写,这就是继承的思想。比如说手机的逐步发展,最早的大哥大只有通话功能,后来的按键手机增加则界面等操作,再到现在的智能手机,它们不是一蹴而就的,而是通过在原有的功能上再增加新功能而逐渐演变过来的,就其实就是一种继承的直观体现。继承原有的功能,增加自己新的功能,实现了拓展和复用。
在Java继承可以使用 extends 关键字来实现,其中Java规定了java.lang.Object 类作为所有的类直接或间接的父类(当类没有继承其他类时,java默认继承Object类,当类继承了其他类时,可以向上追溯,最终继承的类就是Object类)。java规定类只能继承一个类,但是一个类可以被多个类继承(一个子类只能有一个直接父类,一个父类可以有多个子类),类之间可以有多层的继承关系,直接继承的是直接父类,父类的父类就是间接父类,而最上层就是Object。
Object类:
Object类提供的方法:
子类不会继承父类的构造方法,但是会调用(子类初始化前会先初始化父类)。如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表(这就是Java要设置一个默认的无参构造的缘故,方便初始化)。
如果要初始化父类中的字段,可以在子类的构造方法中通过关键字super调用父类的构造方法;且该super语句必须在构造方法中的第一行。
如果父类构造器没有参数,则在子类的构造器中不需要使用 super 关键字调用父类构造器,系统会自动调用父类的无参构造器。
如果父类的构造器带有参数,则必须在子类的构造器中显式地通过 super 关键字调用父类的构造器并配以适当的参数列表;
使用继承时,需要注意继承是受权限修饰符影响的。
1.子类无法继承 private 修饰的属性和方法
2.子类和父类在同一包下,可以继承 default 权限的属性方法
3.子类可以对父类进行扩展,拥有自己的属性和方法
4.子类可以重写父类的方法(前提是可以继承到这个方法)(多态的体现)
虽然继承可以极大的提高代码的复用性,但是不能盲目的去继承,比如你让一个Dog类继承Person类,比如仅仅为了一个类中的某个功能,就直接使用继承。所以继承需要根据实际需要来选择是否使用。
继承中的关键字:extends、super 、this、final
1.extends:单一继承,可以让一个类继承一个父类
2.super:我们可以通过super关键字来实现对父类成员的访问,用来引用当前对象的父类。
3.this:指向自己的引用。引用自身的属性和方法。
4.final:
- 当用final修饰类时,是把类定义为不能继承的,即最终类;
- 用于修饰方法时,该方法不能被子类重写:
- 用于修饰属性时,和static一起使用,表明为一个常量,各类的所有对象共用一个值
3、 多态
多态指同一个实体同时具有多种形式。同字面意思,即一个对象在不同的情况下会有不同的体现。(主要体现在对象和方法上,在子父类中不要定义同名的属性)
3.1、对象的多态
类的多态其实就是一继承关系。
1、向上转型
向上转型其实就是父类对子类的引用。等边三角形是一种特殊的三角形,但是不管再怎么特殊它也是一个三角形。不管什么品种的狗我们都可以说它是一只动物。
这个特点其实就是设计原则中的里式替换原则的原理。子类至少是一个父类,所以父类出现的地方,其子类一定可以出现。
//狗继承与Animals ,所以可以向上转型,用Animals引用Dog类
//能引用是因为狗至少是一种动物,它有动物类所有属性和方法
Animals animals= new Dog();
向上转型的概念使用的地方很多,尤其是在框架学习阶段,会大量使用(在抽象类、接口等方面会大量的使用)。
子类中如果定义了与父类同名同参数的方法,在多态情况下,将此时父类的方法称为虚拟方法,父类根据赋给它的不同子类对象,动态调用属于子类的该方法。这样的方法调用在编译期是无法确定的(多态是在方法调用时,才会明确具体的方法)。
public class Test {
public static void main(String[] args) {
Random random = new Random();
int choose = random.nextInt(3);
System.out.println(choose);
//编译期间是不会知道实例化那个对象的,需要在运行期间确定
Animal animal = switch (choose) {
case 1 -> new Animal();
case 2 -> new Dog();
default -> new Cat();
};
//而且传递的是哪个对象就调用那个对象的say方法
animal.say();
}
}
class Animal{
public void say(){
System.out.println("动物的叫声");
}
}
class Dog extends Animal{
@Override
public void say() {
System.out.println("汪汪汪!!!");
}
}
class Cat extends Animal{
@Override
public void say() {
System.out.println("喵喵喵!!!");
}
}
如果使用了向上转型,声明为父类类型,虽然内存中实际加载的是子类对象,但是由于变量时父类的类型,会导致在编译时,只能使用父类声明的属性和方法,子类特有的属性和方法是不能调用的。所以父类还可以向下转型。
2、向下转型
向下转型是将父类转型为子类,这种转型如果直接转化,通常会出现问题(如:ClassCastException异常),所以在具体使用向下转型的时候需要使用显式类型转换。(使用的较少)
向下转型的前提:
undefined.父类对象指向的是子类对象(实际上还是得先向上转型一下),如果指向的不是子类对象,是没法向下转型的。
undefined.有了1的前提,才能使用强制类型转换进行转型
向下转型通常配合 instanceof关键字使用,用于判断一个实例对象是否属于某个类,判断一个类是否实现了某个接口。
a instanceof B :判断对象a是否是类B的一个实例(或类a是否实现了接口B)
当我们使用向下转型时,可能会出现一些问题,所以在之前需要先判断一下。
class Animals {
public void sound(){
System.out.println("动物叫声");
}
}
class Dog extends Animals{
@Override
public void sound() {
System.out.println("狗叫声");
}
public void eat(){
System.out.println("狗在吃骨头");
}
}
class Cat extends Animals{
@Override
public void sound() {
System.out.println("喵喵喵");
}
public void play(){
System.out.println("猫在玩耍");
}
}
class Test{
public static void main(String[] args) {
//向上转型
Animals a = new Dog();
// Animals a = new Cat();
a.sound();
//a.eat()方法时无法调用的,如果使用需要向下转型
//向下转型,先判断属于Dog还是Cat的实例,属于谁的实例就转型成谁
if(a instanceof Dog){
Dog dog = (Dog) a;
dog.eat();
} else if (a instanceof Cat) {
Cat cat = (Cat)a;
cat.play();
}
}
}
3.3.2、方法的多态
1、重写
重写父类的方法,方法名字、参数、返回值相同
public class Persion {
public void say(String name){
System.out.println("名字是:"+name);
}
}
public class Student extends Persion{
public void say(String name) { //重写了父类的方法,方法名和参数相同
System.out.println(name+"是一个学生");
}
}
2、重载
同一个类中的相同名字不同参数的方法,调用时根据传递的参数不同来区分是哪个方法
public class Persion{
public void say(String name , String sex){}
public void say(String name,int age){} //重载方法,名字相同,但是传递参数的类型必须有不同
//重载的参数类型不能相同
public void say(String sex,String name){} //和第一个say具有相同类型的参数,所以系统无法判定,就会出现错误
}
3、重写和重载区别
1、重写(Override)
重写是子类对父类允许访问的方法进行重写, 返回类型与被重写方法的返回类型可以不相同,但是必须是父类返回值的派生类
子类和父类在同一个包中,那么子类可以重写父类除了声明为 private 和 final 的方法外的所有方法
子类和父类不在同一个包中,那么子类只能够重写父类的声明为 public 和 protected 的非 final 方法。
访问权限不能比父类中被重写的方法的访问权限更低。
父类的成员方法只能被它的子类重写,子类能够根据需要实现父类的方法
重写方法抛出的异常范围不能大于父类。异常也有继承关系,所以子类能抛出的异常不能高于父类
参数列表与被重写方法的参数列表必须完全相同。
声明为 final 的方法不能被重写。声明为 static 的方法不能被重写,但是能够被再次声明。
构造方法不能被重写。
如果不能继承一个类,则不能重写该类的方法。
2、重载(Overload)
重载是在一个类里面,方法名字必须相同,而参数必须不同。返回类型可以相同也可以不同。
被重载的方法可以改变返回类型;
被重载的方法可以改变访问修饰符;
被重载的方法可以声明新的或更广的检查异常;
无法以返回值类型作为重载函数的区分标准。
3、方法的重写(Overriding)和重载(Overloading)是java多态性的不同表现,重写是父类与子类之间多态性的一种表现,重载可以理解成多态的具体表现形式。
方法重载是一个类中定义了多个同名的方法,而他们的参数的数量不同或数量相同而类型和次序不同,则称为方法的重载
方法重写是在子类存在方法与父类同名的方法,而且参数的个数与类型一样,就称为重写
方法重载是一个类的多态性表现,而方法重写是子类与父类的一种多态性表现。
五、访问权限修饰符
六、二维数组的遍历
forfor循环
方法解析:
//外循环遍历有几个一维数组
for(;;;){
//内循环遍历每一个一维数组的元素
for(;;;){
...
}
}
foreach循环
形式
for(元素类型 x: 遍历对象 obj){
引用了x的java的语句;
}
应用
public static void main(String[] args) {
//每个一维数组的元素 对应的数组值长度不一致 非对称数组
int [][]num= { {2,5}, {5,7,9} ,{9}, {8,9,3,56}};
//foreach遍历
for(int []tmp:num) {
//tmp是个数组
for(int i:tmp) {
System.out.print(i+" ");
}
System.out.println();
}
}
七、字符串转整数类型
1、Integer.valueOf()
此方法是返回的Integer类型
String str="123";
Integer num = Integer.valueOf(str);
2、Integer.parseInt()
此方法是返回的int类型
String str="123";
int num = Integer.parseInt(str);
八、整数转字符串
1、String.valueOf()
此方法可以将Integer和int类型的转为String类型的
int num = 8;
String str = String.valueOf(num);
Integer num = 8;
String str = String.valueOf(num);
2、toString()
int num = 8;
String str = Integer.toString(num);
注意对于Integer类型的可以直接用toString方法
Integer num = 8;
String str = num.toString();
九、抽象类
1.什么是抽象类?
undefined.类和类之间具有共同特征,将这些共同特征抽取出来,形成的就是抽象类。
undefined.如果一个类没有足够的信息来描述一个具体的对象,这个类就是抽象类。
undefined.因为类本身就是不存在的,所以抽象类无法创建对象,也就是无法实例化。
问题:既然抽象类是无法创建对象,无法实例化,那是用来干嘛的???
抽象类是用来被子类继承的。
2.抽象类属于什么类型?
抽象类属于引用数据类型
3. 抽象类怎么定义?
关键字:abstract
语法:【修饰符列表】 abstract class 类名{
类体
}
4.思考?
4.1 final和abstract能联合使用吗?
不能联合使用,因为被final关键字修饰过的类是无法继承的,而前面说到abstract就是用来被子类继承的。这两个关键字是对立的。
//会报错,因为final和abstract不能联合使用
//final abstract class Person{
//}
4.2 抽象类的子类还可以是抽象类吗?
可以是抽象类
//定义一个抽象类Person
abstract class Person{
}
//抽象类Student继承抽象父类Person
abstract class Student extends Person{
}
4.3 抽象类中有构造方法吗?
抽象类虽然不能实例化,但是抽象类有构造方法,这个构造方法是给子类提供的。因为抽象就是被用来继承的,子类继承父类,子类的构造方法中的第一句默认是super();
//定义一个抽象类Person
abstract class Person {
//父类无参构造
Person() {
}
}
//抽象类Student继承抽象父类Person
abstract class Student extends Person {
//子类无参构造
Student() {
//默认第一句就是调用父类的无参构造
super();
}
}
二:抽象方法
1.什么是抽象方法?
抽象方法就是在抽象类中没有实现的方法,没有方法体的方法。
没有方法体,以分号结尾;
方法中有abstract关键字
//定义一个抽象方法eat();
public abstract void eat();
2. 思考?
2.1 抽象类中一定要有抽象方法吗?
抽象类中不一定有抽象方法,但是有抽象方法必须得在抽象类中。 抽象方法中还可以有非抽象方法
//定义一个抽象类Person01
abstract class Person01{
//定义一个抽象方法eat();
public abstract void eat();
//非抽象方法
public void sleep(){
}
}
2.2 如何调用?
使用多态的方式调用,父类的引用指向子类的对象。
public class AbstractTest02 {
public static void main(String[] args) {
//使用多态的方式创建对象
Person01 person01 = new Ceshi();
//调用eat方法【编译看左,运行看右】
person01.eat(); //吃
}
}
//定义一个抽象类Person01
abstract class Person01{
//定义一个抽象方法eat();
public abstract void eat();
}
//非抽象类继承抽象类
class Ceshi extends Person01{
//覆盖重写抽象类中的抽象方法
@Override
public void eat() {
System.out.println("吃");
}
}
2.3 什么是面向抽象编程?
顾名思义,面向抽象编程也就是不在面向具体的类,而是面向抽象类,让设计者不在关心具体的实现。可以降低程序的耦合度,提高程序的扩展力。
结论:
3.1 非抽象类继承抽象类,必须实现/重写抽象类中的抽象方法。
实现格式: 去掉 abstract 加上 方法体
//定义一个抽象类Person01
abstract class Person01{
//定义一个抽象方法eat();
public abstract void eat();
}
//非抽象类继承抽象类
class Ceshi extends Person01{
//实现抽象类中的抽象方法
@Override
public void eat() {
}
}
3.2 抽象类不能被实例化,只有抽象类的非抽象子类可以创建对象。
public class AbstractTest03 {
public static void main(String[] args) {
// 抽象类不能实例化,如果实例化就会报错
// Test test = new Test(); //报错
}
}
//定义一个抽象类Test
abstract class Test{
}
3.3 抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
//定义一个非抽象类Test1
//class Test1{
//定义一个抽象方法method,一个类中如果有抽象方法那么这个类必须是抽象类
//public abstract void method(); //报错
//}
4. 权限修饰符
本类 同一个包下的类 不同包下的子类 不同包下的无关类
private √
默认 √ √
protected √ √ √
public √ √ √ √
5. 面试题:
java语言中凡是没有方法体的方法都是抽象方法吗?【判断题】
不对,错误的。Object类中有很多方法都没有方法体,都是以;结尾的,但是他们都不是抽象方法
例如: public native int hashCode(); 这个方法底层调用了C++写的动态链接程序,修饰符中没有abstract,有一个native,表示调用JVM本地程序。
三:思维导图
十、抽象类和接口的区别
Java中接口和抽象类的定义语法分别为interface与abstract关键字。
相同点
都不能被实例化,接口的实现类或抽象类的子类都只有实现了接口或抽象类中的方法后才能实例化。
不同点
undefined.抽象类中的抽象方法的修饰符只能为public或者protected,默认为public;接口中的方法默认使用public修饰
undefined.接口成员变量默认为public static final,必须赋初值,不能被修改。抽象类中成员变量默认default,可在子类中被重新定义,也可被重新赋值;
undefined.实现接口的关键字为implements,继承抽象类的关键字为extends。一个类可以实现多个接口,但一个类只能继承一个抽象类。所以,使用接口可以间接地实现多重继承。
undefined.接口强调特定功能的实现,而抽象类强调所属关系。
undefined.抽象类可以包含方法、构造方法,方法可以实现,但是构造方法不能用于实例化,主要用途是被子类调用。接口只有定义,不能有方法的实现,java 1.8中可以定义default方法体
十一、成员变量和局部变量的区别
一、成员变量是独立于方法外的变量,局部变量是类的方法中的变量;
- 成员变量:包括实例变量和类变量,用static修饰的是类变量,不用static修饰的是实例变量,所有类的成员变量可以通过this来引用。
- 局部变量:包括形参,方法局部变量,代码块局部变量,存在于方法的参数列表和方法定义中以及代码块中。
二、成员变量可以被public,protect,private,static等修饰符修饰,而局部变量不能被控制修饰符及 static修饰;两者都可以定义成final型。
三、成员变量存储在堆,局部变量存储在栈。局部变量的作用域仅限于定义它的方法,在该方法的外部无法访问它。成员变量的作用域在整个类内部都是可见的,所有成员方法都可以使用它。如果访问权限允许,还可以在类的外部使用成员变量。
四、局部变量的生存周期与方法的执行期相同。当方法执行到定义局部变量的语句时,局部变量被创建;执行到它所在的作用域的最后一条语句时,局部变量被销毁。类的成员变量,如果是实例成员变量,它和对象的生存期相同。而静态成员变量的生存期是整个程序运行期。
五、成员变量在累加载或实例被创建时,系统自动分配内存空间,并在分配空间后自动为成员变量指定初始化值,初始化值为默认值,基本类型的默认值为0,复合类型的默认值为null。(被final修饰且没有static的必须显式赋值),局部变量在定义后必须经过显式初始化后才能使用,系统不会为局部变量执行初始化。
六、局部变量可以和成员变量 同名,且在使用时,局部变量具有更高的优先级,直接使用同名访问,访问的是局部变量,如需要访问成员变量可以用 this.变量名 访问。
十二、构造方法的特点
十三、代码块的加载顺序
执行顺序:静态代码块 > 构造块 > 构造方法。
父类静态代码块 > 子类静态代码块 > 父类构造块 > 父类构造方法 > 子类构造块 > 子类构造方法
十四、内部类
在类的内部编写的类就叫内部类!即一个类的内部又完整的嵌套了另一个类结构,被嵌套的类称为内部类(inner class),嵌套其他类的类称为外部类(outer class)。
内部类是类的第五大成员→【提示:类的五大成员是哪些?[属性、方法、构造器、代码块、内部类]】
内部类一般来说包括这四种:成员内部类、局部内部类、匿名内部类和静态内部类。
内部类优点如下:
1.每个内部类都能独立的继承一个接口的实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。内部类使得多继承的解决方案变得完整;
2.方便将存在一定逻辑关系的类组织在一起,又可以对外界隐藏;
3.方便编写事件驱动程序;
4.方便编写线程代码。
一、成员内部类:(在类的内部方法的外部编写的类就是成员内部类)
成员内部类特点:
1.成员内部类可以无条件访问外部类的所有成员属性和成员方法(包括private成员和静态成员);
2.同名的属性名/方法名访问外部类时 → 外部类.this.成员名
Outer.this.name
成员内部类是依附外部类而存在的,也就是说,如果要创建成员内部类的对象,前提是必须存在一个外部类的对象。所以在外部类访问内部类的时候必须先实例化外部类对象
Outer outer= new outer();
Inner inner = outer.new Inner();
//或者如下一句代码:
Outer.Inner inner = new Outer().new Inner();
注意:
1.成员内部类可以使用四种权限修饰符进行修饰(四种权限修饰符:public(公有的) >protected(受保护的) > (default)(缺省/默认的) > private(私有的));
2.成员内部类中不能书写静态变量和方法。
详见案例演示:
public class Outer {
String name = "外部类的类名";
static String type = "外部类的type属性";
private int item = 1;
public static void show() {
System.out.println("掉用外部类中的show方法");
}
public void print() {
System.out.println("调用外部类中的打印方法");
}
//成员内部类 可以使用权限修饰符进行修饰
public class Inner{
//static double weight = 1.8; //成员内部类中不能使用static修饰变量和方法
String name = "内部类的类名";
public void innerShow(){
//成员内部类可以直接访问外部类的属性和方法
show();
print();
System.out.println(type);
System.out.println(item);
System.out.println("我是:" + name);
//进行特指访问时 使用类名.this.变量名进行访问
System.out.println("我是:" + Outer.this.name);
}
}
public static void main(String[] args) {
//成员内部类对象的创建步骤
//1.第一步需要实例化外部类对象
//2.第二步正常实例化内部类对象 但是new关键字要改成 外部类对象名.new
/*Outer outer = new Outer();
Inner inner = outer.new Inner();*/
//或者这样创建
Outer.Inner inner = new Outer().new Inner();
inner.innerShow();
}
}
二、局部内部类 (编写在方法的内部的类称之为局部内部类,也可以称为方法内部类)
局部内部类的特点
1.局部内部类是定义在一个方法或者一个作用域里面的类,它和成员内部类的区别在于局部内部类的访问仅限于方法内或者该作用域内;
2.局部内部类不可使用权限修饰符 静态(static)修饰符进行修饰 同局部变量相同;
3.局部内部类可以直接访问方法中的属性;
4.局部内部类 可以直接访问方法外部类中属性和方法;
5.局部内部类 创建对象 要在方法内部 局部内部类的外部声明。
详见案例演示:
public class Partial {
String name = "外部类的类名";
String type = "外部类的type属性";
private int item = 1;
public static void show() {
System.out.println("掉用外部类中的show方法");
}
public void print() {
System.out.println("调用外部类中的打印方法");
}
public void demo(){
String name = "外部类方法deme()内部的方法名";
String type = "外部类方法deme()内部的type属性";
/*编写在方法的内部的类称之为局部内部类
局部内部类不可使用权限修饰符 静态修饰符进行修饰 同局部变量相同
局部内部类与局部变量使用范围一样 在此方法内部
局部内部类可以直接访问方法中的属性 重名时使用参数传递完成访问*/
class Inner{
//局部内部类 可以访问方法外部类中属性和方法
String name = "局部类的类名";
public void showInner(String name){
show();
print();
System.out.println("我是:"+ type);
System.out.println("我是:"+ Partial.this.type);
System.out.println(item);
System.out.println("我是:" + this.name);
System.out.println("我是:" + name);
System.out.println("我是:" + Partial.this.name);
}
}
//局部内部类 创建对象 要在方法内部 局部内部类的外部声明
Inner inner = new Inner();
inner.showInner(name);
}
public static void main(String[] args) {
Partial partial = new Partial();
partial.demo();
}
}
三、匿名内部类(注意:匿名内部类只是没有类名,其他的都是具备的)
匿名内部类特点
匿名内部类不能定义任何静态成员、方法和类,只能创建匿名内部类的一个实例。一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。
详见案例演示:
// 实现关系下的匿名内部类:
interface Dao {
void show();
}
public class AnonymousDemo {
//编写回调方法 :callInner
public void callInner(){
// 接口关系下的匿名内部类
new Dao(){
//实现子类 但是没有名字 所以叫匿名内部类
@Override
public void show() {
System.out.println("接口方法...");
}
}.show();
}
}
// 测试:
public class Demo {
public static void main(String[] args) {
AnonymousDemo anonymousDemo = new AnonymousDemo();
anonymousDemo.callInner();
}
}
匿名内部类可用于给方法传递实参,演示如下:
interface Dao {
void show();
}
public class AnonymousDemo {
//编写回调方法:callInner 参数类型为接口Dao
private static void callInner(Dao d) {
d.show();
}
public static void main(String[] args) {
callInner(new Dao() {//接口回调
//实现子类 但是没有名字 所以叫匿名内部类
@Override
public void show() {
System.out.println("匿名内部类用于给方法传递实参");
}
});
}
}
或许有些难以理解,其实过程并不复杂。
说明:首先有一个接口,然后在使用的类中编写了一个方法(参数类型是接口对象),并使用接口中未实现的方法。
我们调用此方法直接构造一个接口对象传入,此时会自动生成一个此接口的子类(匿名内部类)实现接口中的方法。本质传入的类便是此时的匿名内部类。
四、静态内部类(在类中编写的以static修饰的类称为静态内部类)
静态内部类特点
1.静态内部类也是定义在另一个类里面的类,只不过在类的前面多了一个关键字static;
2.静态内部类是不需要依赖于外部类的,这点和类的静态成员属性有点类似,并且它不能使用外部类的非static成员变量或者方法;
3.静态内部类中既能声明静态成员也可以声明非静态成员。
详见案例演示:
public class Static {
static String name = "外部类的类名";
//静态内部类中不能访问外部类非静态成员
String type = "外部类的type属性";
public static class Inner{
//四种权限修饰符可以修饰静态内部类
public String name = "静态内部类的类名";
static double weight = 1.8;
String type = "静态内部类的type属性";
public void show(){
System.out.println("我是:" + weight);
System.out.println("我是:" + type);
System.out.println("我是:" + name);
//System.out.println("我是:" + Static.type);//静态内部类中不能访问外部类非静态成员
System.out.println("我是:" + Static.name);
}
}
public static void main(String[] args) {
//静态内部类可以直接实例化 不需要依附于外部类
Inner inner = new Inner();
inner.show();
}
}
十五、返回值的作用
十六、命名
标识符命名规范
在Java中,标识符是指用来标识程序中各种变量、方法、类等的名称。标识符的命名规则如下:
1.标识符必须以字母、下划线(_)或美元符号($)开头,不能以数字开头。
2.标识符可以包含字母、数字、下划线(_)或美元符号($)。
3.标识符是区分大小写的。
4.标识符不能使用Java中的关键字和保留字。
需要注意的是,虽然Java中允许使用下划线(_)和美元符号($)作为标识符的一部分,但是在实际编程中,建议使用驼峰命名法来命名标识符,这样可以提高代码的可读性和可维护性。
大小驼峰命名法
通常使用小驼峰命名法来命名变量、方法、属性等成员变量,而使用大驼峰命名法来命名类和接口。
1.大驼峰命名法(也称为大写驼峰命名法或帕斯卡命名法)是一种命名规范,它主要用于命名类、接口、枚举等 Java 元素。具体来说,大驼峰命名法是指将多个单词组合起来构成一个复合词,其中每个单词的首字母都大写,以此类推。
以下是一些使用大驼峰命名法的示例:
类名:Person, Student, Book, CustomerOrder
接口名:Runnable, Comparable, Serializable
枚举名:DayOfWeek, Color, Size
2.小驼峰命名法(也称为小写驼峰命名法)是一种命名规范,它主要用于命名变量、方法、属性等 Java 元素。具体来说,小驼峰命名法是指将多个单词组合起来构成一个复合词,其中第一个单词的首字母小写,后面的每个单词的首字母大写,以此类推。
以下是一些使用小驼峰命名法的示例:
变量名:firstName, lastName, age, emailAddress
方法名:getFirstName(), setLastName(String lastName), calculateAge(int birthYear), sendEmail(String recipient, String subject, String body)
属性名:firstName, lastName, age, emailAddress
--------------------------------------------------------------------------------
类,对象,包的命名
1.类的命名:
类名应该以大写字母开头,采用大驼峰命名法,例如:Person、Student、Account。类名应该具有描述性,能够清晰地表达类的作用和含义。
2.对象的命名:
对象的命名应该以小写字母开头,采用小驼峰命名法,例如:person、student、account。对象的命名应该具有描述性,能够清晰地表达对象的作用和含义。
3.包的命名:
包名应该全部小写,采用点(.)分隔符分隔各级包名,包名一般采用公司或组织的域名倒序命名,例如:com.example、org.apache。如果是个人或者小团队开发的项目,也可以使用自己的域名作为包名的一部分 例如:io.github.username
包名的命名应该具有唯一性,能够清晰地表达包的作用和含义。