JavaSE学习笔记
一、java的基本语法
变量运算规则
编码情况1中l后面没有加L,默认是int变量
编码情况2中b1=b+1中的1默认是int型变量,会出错
string类型
string里面可以从0个字符开始赋值,但是char的‘’里面至少有一个字符
如果“+”前后有一个数据类型是string类型,那么就是在做连接运算,如果两个都不是string类型,那就是在做加法运算
输出结果:
像下面的\t注意要用双引号,不能用单引号,不然就成了加法运算而不是连接运算
第二种全是char型变量,相加会输出int型变量,第四种前两个变量相加会输出int型变量
不可以把string类型的强制转为int型,只可以做连接运算
string类型在常量池中,重新定义一个对象,如果常量池有了,那么它会复用已存在的
String s1 = "BB";
String s2 = "BB";
System.out.println(s1 == s2);//true
之前用==判断String 不是判断常量池的内容 是用两个String类型的对象进行比较 那时候比较的是对象地址值 而不是内容!
注意区分字符串常量和字符串对象,一个指向常量池,一个指向堆中
所以后面在重写类的equals时注意比较string时要用string类自己重写的equals
//正确的:
return this.orderId == order.orderId &&
this.orderName.equals(order.orderName);
//错误的:
// return this.orderId == order.orderId &&
// this.orderName == order.orderName;
注意:在JDK6.0及之前版本,字符串常量池存放在方法区中在JDK7.0版本以后,字符串常量池被移到了堆中了
运算符
自增运算符不会改变本身变量的数据类型
编译失败的2是int型
*=也不会改变数据类型,也是输出int型,会截断输出0
最后一个是12的原因:前一个操作n已经变成了11,最后一个++n,先自增,所以+的是12
区别位运算符和逻辑运算符:位运算符前后都是数,逻辑运算符前后都是布尔型
==:对于引用数据类型来讲,比较的是两个引用数据类型变量的地址值是否相同
三元运算符
:前后的两个类型要求是一致的
Integer与Double在三元运算符中先转换成int与double基本数据类型去做比较,返回的结果是double1.0会再转化成包装类赋给o1
public void test1() {
Object o1 = true ? new Integer(1) : new Double(2.0);
System.out.println(o1);// 1.0,自动类型提升,所以输出1.0
}
if-else
()里面不要这样写,80<score会生成一个bool类型,会报错
else if里面的条件表达式正确时,不会再执行下面的if-else了
else遵循就近原则,所以输出的是x
“是”.equals(str)函数判断.前面的字符是否和()里面的字符一样
switch case
一些合并的写法
三元表达式-if-switch之间的关系
都可以转换成if表达式,但是反之不成立
下面代码里面如果没有break的话,前面定义的money和info会延续到后面继续使用,一般来说支出金额要重新定义一个名字,eg:addMoney,minusMoney
循环
for循环里面的i只在for循环里面有效
二、数组
动态定长度,静态定元素
char数组:元素的默认初始化值是0,非‘0’,打印出来的效果跟空格一样,实际上并不是空格
空格对应的数值是32,空字符对应的数值才是0,字符’0’对应的数值是48
字符串属于引用类型
放在main函数中的变量都叫局部变量,局部变量放在栈当中
二维数组
定义时也可以像下面这样定义
下面是一些错误例子,前面的两个括号里面不能写数字,也不能动静态初始化混用
总结:
数组赋值是新开辟一个数组,进行每一个元素的赋值,而不是直接用新数组=旧数组来操作,这样只是指向地址相同
三、面向对象
值传递
和数组的情况类似,这种是地址赋值,修改属性值时其他指向同一个地址的类的属性值也会变化
arr数组调用的是println(Object)函数,输出地址值
arr数组调用的是println(char[])函数,这个函数的方法体里面是遍历输出的代码,所以不会输出地址值
这段代码输出的是hello,虽然string是引用数据类型,但是它比较特殊
原因:
string是引用数据类型,在常量池中创建了一个char型数组存放了hello,再将这个hello的地址值赋给了s1,接着将s1赋给了s,s和s1指向同一块地址,此时s1也指向hello的地址,因为string的底层是用char数组进行存放的,所以在给s赋值“hi~~”时,又会创建一个新的char数组,来存放“hi”将这个新char数组的地址赋给s,所以s1的指向依旧是hello,最终输出hello
封装性
封装性的体现:
四种权限也可以体现为封装性的一种
综合练习一:customer和account
Account是一个public的类,可以在另一个类中定义成一个属性,参考string,string也是一个类,它也可以这样定义
customer手上有账号account,每一个用户对应一个账户,所以在用户类里面又定义了账户类,通过用户类去操作账户类
import关键字
import static组合的使用:调用指定类或接口下的静态的属性或方法
表示import的是system下的一些静态属性或者方法原来的代码:
原来的代码:
out前面删掉了string
就可以改写成下面的写法
out是string类中定义的一个属性
import 和import static的区别:
import的落脚点是类
import static的落脚点是类中的一个结构
继承性
子类会继承父类中的所有属性和方法,但是能不能访问又是另外一回事了
方法重写
编译器认为int…和int[]是一样的,在方法重写时要注意
子类重写的方法的返回值类型必须是相同的基本数据类型(比如:double)
四种访问权限修饰符
总结:
1、对于在同一个包下的父类中的属性和方法,除了private修饰的,其他的都可以被子类调用;
2、对于不同包下的父类中的属性和方法,除了private和缺省修饰的,其他的都可以被子类调用;
3、对于不同包下的普通类(非子类),只能调用public
super
子类中的多个构造器中至少有一个构造器中用super(形参列表)
子类对象实例化
1).为什么super(…)和this(…)调用语句不能同时在一个构造器中出现?
因为二者都必须出现在首行
2).为什么super(…)或this(…)调用语句只能作为构造器中的第一句出现?
无论通过哪个构造器创建子类对象,需要保证先初始化父类。
目的:当子类继承父类侯,“继承”父类中所有的属性和方法,因此子类有必要知道父类如何为对象进行初始化
多态性
对象的多态性:在编译期,只能调用父类中声明的方法,但在运行期,实际执行的是子类重写父类的方法。多态是运行时行为,因为方法调用在编译器是无法确定的
属性不存在多态性
属性(编译和运行都看左边)
多态性的使用举例:
好处:减少了方法的重载
package com.atguigu.java4;
import java.sql.Connection;
//多态性的使用举例一:
public class AnimalTest {
public static void main(String[] args) {
AnimalTest test = new AnimalTest();
test.func(new Dog());
test.func(new Cat());
}
public void func(Animal animal){//Animal animal = new Dog();
animal.eat();
animal.shout();
if(animal instanceof Dog){
Dog d = (Dog)animal;
d.watchDoor();
}
}
// public void func(Dog dog){
// dog.eat();
// dog.shout();
// }
// public void func(Cat cat){
// cat.eat();
// cat.shout();
// }
}
class Animal{
public void eat(){
System.out.println("动物:进食");
}
public void shout(){
System.out.println("动物:叫");
}
}
class Dog extends Animal{
public void eat(){
System.out.println("狗吃骨头");
}
public void shout(){
System.out.println("汪!汪!汪!");
}
public void watchDoor(){
System.out.println("看门");
}
}
class Cat extends Animal{
public void eat(){
System.out.println("猫吃鱼");
}
public void shout(){
System.out.println("喵!喵!喵!");
}
}
//举例二:
//只要是一个对象都可以调用这个方法
class Order{
public void method(Object obj){
}
}
//举例三:
class Driver{
public void doData(Connection conn){//conn = new MySQlConnection(); / conn = new OracleConnection();
//规范的步骤去操作数据
// conn.method1();//实际调用的是子类中重写的方法
// conn.method2();
// conn.method3();
}
}
多态性练习:重写方法
package com.atguigu.exer;
//考查多态的笔试题目:
public class InterviewTest1 {
public static void main(String[] args) {
Base1 base = new Sub1();
base.add(1, 2, 3);//sub_1,调用的是重写的方法
Sub1 s = (Sub1)base;//向下转型后就认为这是一个sub1类
s.add(1,2,3);//sub_2,向下转型后确定的参数类型优先调用
}
}
class Base1 {
public void add(int a, int... arr) {
System.out.println("base1");
}
}
class Sub1 extends Base1 {
//这个方法才是父类方法的重写,下面那个并不是
public void add(int a, int[] arr) {
System.out.println("sub_1");
}
public void add(int a, int b, int c) {
System.out.println("sub_2");
}
}
向下转型
目的是为了调用子类中特有的属性和方法
功能少的不可能转化为功能多的,new的父类对象不能强转为子类对象
创建子类时会加载所有的父类,转型只能转为已经加载了的类,因为对象obj本质是woman类,加载woman类时就已经加载了person、object类
同一个父类的各个子类之间不可以互相转换
//练习:
//问题一:编译时通过,运行时不通过
//举例一:
// Person p3 = new Woman();
// Man m3 = (Man)p3;
//举例二:
// Person p4 = new Person();
// Man m4 = (Man)p4;
//问题二:编译通过,运行时也通过
// Object obj = new Woman();
// Person p = (Person)obj;
//问题三:编译不通过
// Man m5 = new Woman();
//string和Date没有关系
// String str = new Date();
//编译时通过,运行时过不了
// Object o = new Date();
// String str1 = (String)o;
object类的使用
clone()方法的返回值类型是object,在复制的时候需要考虑是否需要强转为所需要的类型,克隆的对象和前对象的地址不同
finalize方法:在垃圾回收器之前会自动调用的一个方法
getClass():获取当前对象的类
==与equals()
一、回顾 == 的使用:
- == :运算符
-
- 可以使用在基本数据类型变量和引用数据类型变量中
-
- 如果比较的是基本数据类型变量:比较两个变量保存的数据是否相等。(不一定类型要相同)
- 如果比较的是引用数据类型变量:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
- 补充: == 符号使用时,必须保证符号左右两边的变量类型一致。
- 二、equals()方法的使用:
-
- 是一个方法,而非运算符
-
- 只能适用于引用数据类型
-
- Object类中equals()的定义:
- public boolean equals(Object obj) {
return (this == obj);
} - 说明:Object类中定义的equals()和==的作用是相同的:比较两个对象的地址值是否相同.即两个引用是否指向同一个对象实体
-
- 像String、Date、File、包装类等都重写了Object类中的equals()方法。重写以后,比较的不是两个引用的地址是否相同,而是比较两个对象的"实体内容"是否相同。
-
- 通常情况下,我们自定义的类如果使用equals()的话,也通常是比较两个对象的"实体内容"是否相同。那么,我们就需要对Object类中的equals()进行重写.
- 重写的原则:比较两个对象的实体内容是否相同.
equals()方法重写思路:
1、先判断地址是否相等
2、判断是否是需要判断的类
若是的话,先进行强转,接着再比较各个属性值是否相等
开发中可以自动生成
单元测试方法
- Java中的JUnit单元测试
- 步骤:
- 1.选中当前工程 - 右键选择:build path - add libraries - JUnit 4 - 下一步
- 2.创建Java类,进行单元测试。
- 此时的Java类要求:① 此类是public的 ②此类提供公共的无参的构造器
- 3.此类中声明单元测试方法。
- 此时的单元测试方法:方法的权限是public,没有返回值,没有形参
- 4.此单元测试方法上需要声明注解:@Test,并在单元测试类中导入:import org.junit.Test;
- 5.声明好单元测试方法以后,就可以在方法体内测试相关的代码。
- 6.写完代码以后,左键双击单元测试方法名,右键:run as - JUnit Test
- (详细的步骤如上,也可以直接在符合要求的类里面写上@Test和要测试的类,报错的时候直接点击解决办法就会一步到位解决了)
- 说明:
- 1.如果执行结果没有任何异常:绿条
- 2.如果执行结果出现异常:红条
Junit不用new对象就能用属性了
在Junit里面可以选中不同的方法单独进行测试
在实际开发当中可以写完一个功能就在测试类里面写一个测试方法
注意:自己在造类的时候避免使用Test名字
包装类
基本数据类型——包装类
注意在使用new Integer()时候,括号里面可以放整数,也可以放字符串,但是字符串只能全是数字,不然会报错
int num1 = 10;
// System.out.println(num1.toString());
Integer in1 = new Integer(num1);
System.out.println(in1.toString());
Integer in2 = new Integer("123");
System.out.println(in2.toString());
//报异常
// Integer in3 = new Integer("123abc");
// System.out.println(in3.toString());
对于布尔型
只要是true,不管大小写,都输出true,其他的输出false
Boolean b1 = new Boolean(true);//()里面可以直接放true,也可以放string类型的
Boolean b2 = new Boolean("TrUe");//输出true
System.out.println(b2);
Boolean b3 = new Boolean("true123");
System.out.println(b3);//false
布尔类型的包装类的默认值是null
基本数据类型只需要记住以下转换就可以
自动拆箱与自动装箱
/*
* JDK 5.0 新特性:自动装箱 与自动拆箱
*/
@Test
public void test3(){
// int num1 = 10;
// //基本数据类型-->包装类的对象
// method(num1);
//自动装箱:基本数据类型 --->包装类
int num2 = 10;
Integer in1 = num2;//自动装箱
boolean b1 = true;
Boolean b2 = b1;//自动装箱
//自动拆箱:包装类--->基本数据类型
System.out.println(in1.toString());
int num3 = in1;//自动拆箱
}
public void method(Object obj){
System.out.println(obj);
}
基本数据类型、包装类——string类
记住以下两种方法就ok,各个方法之间的效率差别不大
前两者转换为string类:valueOf(3.4f)
string类转换为前两者:parseXxx(string)
面试题
@Test
public void test3() {
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i == j);//false
//Integer内部定义了IntegerCache结构,IntegerCache中定义了Integer[],
//保存了从-128~127范围的整数。如果我们使用自动装箱的方式,给Integer赋值的范围在
//-128~127范围内时,可以直接使用数组中的元素,不用再去new了。目的:提高效率
Integer m = 1;
Integer n = 1;
System.out.println(m == n);//true
Integer x = 128;//相当于new了一个Integer对象
Integer y = 128;//相当于new了一个Integer对象
System.out.println(x == y);//false
}
和String的常量池一样,基本数据类型也有常量池,取值是-128到127,所以引用的话地址相同,但是超过这个范围则是在堆里new一个了,另外浮点型不在常量池中
创建抽象类的匿名子类对象
package com.atguigu.java;
/*
* abstract关键字的使用
* 1.abstract:抽象的
* 2.abstract可以用来修饰的结构:类、方法
*
* 3. abstract修饰类:抽象类
* > 此类不能实例化
* > 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
* > 开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作
*
*
* 4. abstract修饰方法:抽象方法
* > 抽象方法只有方法的声明,没有方法体
* > 包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
* > 若子类重写了父类中的所有的抽象方法后,此子类方可实例化
* 若子类没有重写父类中的所有的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
*/
public class AbstractTest {
public static void main(String[] args) {
//一旦Person类抽象了,就不可实例化
// Person p1 = new Person();
// p1.eat();
}
}
abstract class Creature{
public abstract void breath();
}
abstract class Person extends Creature{
String name;
int age;
public Person(){
}
public Person(String name,int age){
this.name = name;
this.age = age;
}
//不是抽象方法:
// public void eat(){
//
// }
//抽象方法
public abstract void eat();
public void walk(){
System.out.println("人走路");
}
}
class Student extends Person{
public Student(String name,int age){
super(name,age);
}
public Student(){
}
public void eat(){
System.out.println("学生多吃有营养的食物");
}
@Override
public void breath() {
System.out.println("学生应该呼吸新鲜的没有雾霾的空气");
}
}
package com.atguigu.java;
/*
* 抽象类的匿名子类
*
*/
public class PersonTest {
public static void main(String[] args) {
method(new Student());//匿名对象
Worker worker = new Worker();
method1(worker);//非匿名的类非匿名的对象
method1(new Worker());//非匿名的类匿名的对象
System.out.println("********************");
//创建了一匿名子类的对象:p
//这个子类没有什么实际含义,就是想用里面的抽象方法,
//所以就不显式定义子类了,是一种简化的写法
/*
* 类person为抽象类,正常抽象类是无法实例化的,
* 但如下用法相当于给person创建了一个临时子类,
* 并声明出person类中的方法此时相当于给Person类创建了一个匿名子类,
* 子类的实例化对象叫做p
*/
Person p = new Person(){
@Override
public void eat() {
System.out.println("吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸");
}
};
method1(p);
System.out.println("********************");
//创建匿名子类的匿名对象
method1(new Person(){
@Override
public void eat() {
System.out.println("吃好吃东西");
}
@Override
public void breath() {
System.out.println("好好呼吸新鲜空气");
}
});
}
public static void method1(Person p){
p.eat();
p.breath();
}
public static void method(Student s){
}
}
class Worker extends Person{
@Override
public void eat() {
}
@Override
public void breath() {
}
}
创建接口匿名实现类的对象
匿名类就是把定义拉到函数内,匿名对象就是直接new不创建引用
package com.atguigu.java1;
/*
* 接口的使用
* 1.接口使用上也满足多态性
* 2.接口,实际上就是定义了一种规范
* 3.开发中,体会面向接口编程!
*
*/
public class USBTest {
public static void main(String[] args) {
Computer com = new Computer();
//1.创建了接口的非匿名实现类的非匿名对象
Flash flash = new Flash();
com.transferData(flash);
//2. 创建了接口的非匿名实现类的匿名对象
com.transferData(new Printer());
//3. 创建了接口的匿名实现类的非匿名对象
USB phone = new USB(){
@Override
public void start() {
System.out.println("手机开始工作");
}
@Override
public void stop() {
System.out.println("手机结束工作");
}
};
com.transferData(phone);
//4. 创建了接口的匿名实现类的匿名对象
com.transferData(new USB(){
@Override
public void start() {
System.out.println("mp3开始工作");
}
@Override
public void stop() {
System.out.println("mp3结束工作");
}
});
}
}
class Computer{
public void transferData(USB usb){//USB usb = new Flash();
usb.start();
System.out.println("具体传输数据的细节");
usb.stop();
}
}
interface USB{
//常量:定义了长、宽、最大最小的传输速度等
void start();
void stop();
}
class Flash implements USB{
@Override
public void start() {
System.out.println("U盘开启工作");
}
@Override
public void stop() {
System.out.println("U盘结束工作");
}
}
class Printer implements USB{
@Override
public void start() {
System.out.println("打印机开启工作");
}
@Override
public void stop() {
System.out.println("打印机结束工作");
}
}
高级编程部分
多线程
有共享数据才会出现线程安全的问题
sleep函数会增加出现线程安全问题的概率
sleep不会释放锁
wait执行会释放锁
notify是把阻塞的线程唤醒,wait才是释放锁
异步并行,同步串行
p455
如果在name的地方又重新new了string类型的话又要在堆空间重新创建两个空间,此时的==比较结果应该是false
集合
set相关面试题
package com.atguigu.exer;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
/**
* @author shkstart
* @create 2019 上午 9:36
*/
public class CollectionTest {
@Test
public void test1(){
Collection coll = new ArrayList();
coll.add(123);
coll.add(456);
coll.add(343);
coll.add(343);
coll.forEach(System.out::println);
}
//练习:在List内去除重复数字值,要求尽量简单
public static List duplicateList(List list) {
HashSet set = new HashSet();
set.addAll(list);
return new ArrayList(set);
}
@Test
public void test2(){
List list = new ArrayList();
list.add(new Integer(1));
list.add(new Integer(2));
list.add(new Integer(2));
list.add(new Integer(4));
list.add(new Integer(4));
List list2 = duplicateList(list);
for (Object integer : list2) {
System.out.println(integer);
}
}
@Test
public void test3(){
HashSet set = new HashSet();
Person p1 = new Person(1001,"AA");
Person p2 = new Person(1002,"BB");
set.add(p1);
set.add(p2);
System.out.println(set);
p1.name = "CC";
set.remove(p1);
System.out.println(set);
set.add(new Person(1001,"CC"));
System.out.println(set);
set.add(new Person(1001,"AA"));
System.out.println(set);
}
}
存储的时候才会按照属性的hash值去存,后来要是属性改了,当前hash值不会改,但是调用remove方法的hash值会是是新的属性对应的hash值,那么有大概率找不到原来的位置,所以就没办法remove掉p1,p1放的位置是根据原始的p1的哈希值算出来进行放置的,而后续加的(1001,CC)算出来的哈希值和刚开始的p1算出来的不一样,因此会放在新的位置。第四个加进去的时候会触发equals函数,一个是AA,一个是CC,所以内容不一样,以链表的形式进行存储。
remove方法是比较的哈希值
反射
最常用的是第三种方法,第一种在编译的时候就必须得确认是哪种类型的对象,第三种在运行的时候才去确定是哪种对象